<think>我们需要使用Vue2和AntDesign Vue(通常称为antd-vue)来实现一个穿梭框(Transfer)组件,该组件通常用于在两个表格之间移动选项。AntDesign Vue的Transfer组件已经内置了这种功能,但题目要求使用两个表格来实现,所以我们可以通过自定义渲染(`render`属性)来使用两个表格展示内容。注意:antd-vue的Transfer组件默认是列表形式,但我们可以自定义渲染为表格。这里我们使用Transfer组件,并通过自定义`render`来渲染表格。步骤:1.安装antd-vue(如果尚未安装):`npminstall ant-design-vue@1.7.8 --save`(Vue2对应的是1.x版本)2.在项目中引入antd-vue的Transfer、Table、Button等组件。由于要求使用两个表格,我们将使用Transfer组件的自定义渲染功能,将左右两边的列表渲染成表格。示例代码:注意:这里我们使用Vue2的语法。我们将实现一个简单的穿梭框,数据源包含key和标题,并且可以显示在左右两个表格中,通过按钮进行移动。代码结构:-使用a-transfer组件-自定义render属性,返回一个表格(a-table)-定义数据源(mock数据)-定义目标键(targetKeys)用于存储右侧(目标)数据的key功能:-全选:表格头部的复选框可以全选当前列表-穿梭:通过中间的按钮移动选中的数据注意:Transfer组件本身已经处理了数据的移动,我们只需要提供自定义的渲染方式。由于Transfer组件要求数据源格式为:`[{ key, title}]`,但我们在表格中可能需要展示更多字段,因此我们可以将数据扩展,比如:`[{key,name,age,... }]`,然后在自定义渲染的表格中展示多个字段。实现:-使用`scopedSlots`自定义渲染列表(在antd-vue1.x中,Transfer的`render`属性是一个函数,它接收一个参数(每一项数据),返回一个VNode。但为了渲染整个列表为表格,我们需要使用`list-style`和`render`属性,然而antd-vue的Transfer组件并没有直接支持整个列表渲染为表格。因此,我们可以通过自定义两个插槽的方式,但是antd-vue1.x的Transfer不支持插槽。所以,我们这里使用另一种方式:利用Transfer组件的`render`属性返回一个表格行(即每一项作为表格的一行),同时整个列表会渲染成一个表格。但是这样需要我们自己构建整个表格结构。实际上,Transfer组件的左右两个列表是分别渲染的,我们可以为每个列表渲染一个表格,每个表格只有tbody部分(没有表头),但这样不够美观。因此,我们可以自定义整个列表,包括表格和表头。然而,Transfer组件的自定义渲染函数(`render`)是针对每一项的,所以我们需要在Transfer组件内部包裹一个表格,这样就需要在`render`函数中返回一个表格行(tr)。然后我们在Transfer组件外部包裹两个表格,但这样会破坏Transfer组件的结构。因此,我们采用另一种思路:不使用Transfer组件自带的列表样式,而是完全自定义左右两个面板(使用两个Table组件),然后通过中间的控制按钮来实现数据的移动。这样我们就可以完全控制两个表格的渲染,并且可以实现全选等功能。但是题目要求使用antd-vue,且要利用其Transfer组件,所以我们还是按照antd-vueTransfer组件的自定义渲染方式来实现。在antd-vue1.x中,Transfer组件提供了`render`属性,用于自定义每一项的渲染。我们可以这样:-将整个列表渲染成一个表格,包括表头和表体。但是Transfer组件的每个列表是一个`ul`,我们无法直接替换成`table`。因此,我们需要在`render`函数中返回一个`tr`元素,然后在Transfer组件的两个列表容器中,我们通过CSS将其内部结构改为`table`。但是这样操作比较复杂。考虑到这些,我们可以使用Transfer组件的另一个特性:`list-style`可以设置列表容器的样式,然后我们通过`render`返回一个表格行,然后在列表容器内部使用`table`标签包裹。但是Transfer组件的列表容器是`ul`,我们无法改变其标签。因此,我们放弃使用Transfer组件自带的列表容器,而是使用自定义渲染整个面板(通过插槽或者自定义整个组件)。但是antd-vue1.x的Transfer不支持插槽自定义整个面板。所以,这里我们采用两个Table组件和中间的控制按钮自己实现一个穿梭框,这样更灵活,也符合题目要求(两个表格)。自己实现的思路:-左侧表格:显示源数据(未选择的数据)-右侧表格:显示已选择的数据(目标数据)-中间有按钮:将左侧选中的数据移动到右侧,也可以将右侧选中的移回左侧,以及全移等。步骤:1.定义两个数组:leftData(源数据),rightData(目标数据)2.两个表格分别绑定leftData和rightData,并且每个表格都有多选框列3.定义两个数组:leftSelectedKeys(左侧选中行的key数组),rightSelectedKeys(右侧选中行的key数组)4.按钮事件:将左侧选中的key对应的数据从leftData中移除,并添加到rightData中,同时清空leftSelectedKeys;反之亦然。由于使用antd-vue,我们可以使用a-table组件,它自带了多选功能(通过rowSelection属性)。示例代码:注意:由于是Vue2,我们需要安装并引入antd-vue,并注册使用的组件(Table, Button, Icon等)。以下是具体实现:</think>以下是使用 Vue2 配合 Ant Design Vue (antd-vue) 实现两表格穿梭框的完整解决方案:
```html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Vue2 + Antd 穿梭框</title>
<link href="https://cdn.jsdelivr.net/npm/ant-design-vue@1.7.8/dist/antd.min.css" rel="stylesheet">
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.14/dist/vue.js"></script>
<script src="https://cdn.jsdelivr.net/npm/ant-design-vue@1.7.8/dist/antd.min.js"></script>
<style>
.transfer-container {
display: flex;
justify-content: center;
margin-top: 40px;
}
.table-container {
width: 45%;
border: 1px solid #e8e8e8;
border-radius: 4px;
overflow: hidden;
}
.transfer-actions {
display: flex;
flex-direction: column;
justify-content: center;
padding: 0 20px;
}
.action-button {
margin: 10px 0;
}
.selected-count {
margin-top: 10px;
text-align: center;
font-size: 12px;
color: #999;
}
</style>
</head>
<body>
<div id="app">
<a-card title="员工部门分配" style="width: 90%; margin: 20px auto">
<div class="transfer-container">
<!-- 左侧表格 -->
<div class="table-container">
<a-table
:row-selection="{ selectedRowKeys: leftSelectedKeys, onChange: onLeftSelectChange }"
:columns="columns"
:data-source="leftData"
row-key="id"
:pagination="false"
>
<template slot="title">
<div style="display: flex; justify-content: space-between; align-items: center;">
<span>未分配员工 ({{ leftData.length }})</span>
<a-checkbox
:indeterminate="leftIndeterminate"
:checked="leftCheckAll"
@change="onLeftCheckAllChange"
>
全选
</a-checkbox>
</div>
</template>
</a-table>
</div>
<!-- 中间操作按钮 -->
<div class="transfer-actions">
<a-button
type="primary"
icon="right"
class="action-button"
:disabled="leftSelectedKeys.length === 0"
@click="moveToRight"
>
向右移动
</a-button>
<a-button
type="primary"
icon="left"
class="action-button"
:disabled="rightSelectedKeys.length === 0"
@click="moveToLeft"
>
向左移动
</a-button>
<a-button
type="danger"
icon="reload"
class="action-button"
@click="resetData"
>
重置
</a-button>
</div>
<!-- 右侧表格 -->
<div class="table-container">
<a-table
:row-selection="{ selectedRowKeys: rightSelectedKeys, onChange: onRightSelectChange }"
:columns="columns"
:data-source="rightData"
row-key="id"
:pagination="false"
>
<template slot="title">
<div style="display: flex; justify-content: space-between; align-items: center;">
<span>已分配员工 ({{ rightData.length }})</span>
<a-checkbox
:indeterminate="rightIndeterminate"
:checked="rightCheckAll"
@change="onRightCheckAllChange"
>
全选
</a-checkbox>
</div>
</template>
</a-table>
</div>
</div>
<div class="selected-count">
已选择: {{ selectedItems.map(i => i.name).join(', ') || '无' }}
</div>
</a-card>
</div>
<script>
Vue.use(antd);
new Vue({
el: '#app',
data() {
return {
// 表格列定义
columns: [
{
title: '姓名',
dataIndex: 'name',
key: 'name',
},
{
title: '年龄',
dataIndex: 'age',
key: 'age',
},
{
title: '部门',
dataIndex: 'department',
key: 'department',
}
],
// 初始数据
originalData: [
{ id: 1, name: '张三', age: 25, department: '技术部' },
{ id: 2, name: '李四', age: 30, department: '市场部' },
{ id: 3, name: '王五', age: 28, department: '财务部' },
{ id: 4, name: '赵六', age: 32, department: '人事部' },
{ id: 5, name: '钱七', age: 26, department: '技术部' }
],
// 左侧表格数据
leftData: [],
// 右侧表格数据
rightData: [],
// 左侧选中项
leftSelectedKeys: [],
// 右侧选中项
rightSelectedKeys: [],
// 左侧全选状态
leftCheckAll: false,
// 右侧全选状态
rightCheckAll: false,
// 左侧半选状态
leftIndeterminate: false,
// 右侧半选状态
rightIndeterminate: false
};
},
computed: {
// 当前选中的项目
selectedItems() {
const leftSelected = this.leftData.filter(item =>
this.leftSelectedKeys.includes(item.id)
);
const rightSelected = this.rightData.filter(item =>
this.rightSelectedKeys.includes(item.id)
);
return [...leftSelected, ...rightSelected];
}
},
created() {
// 初始化数据
this.resetData();
},
methods: {
// 初始化数据
resetData() {
this.leftData = [...this.originalData];
this.rightData = [];
this.leftSelectedKeys = [];
this.rightSelectedKeys = [];
this.updateLeftCheckState();
this.updateRightCheckState();
},
// 向左移动选中项
moveToLeft() {
const selectedItems = this.rightData.filter(item =>
this.rightSelectedKeys.includes(item.id)
);
// 从右侧移除
this.rightData = this.rightData.filter(item =>
!this.rightSelectedKeys.includes(item.id)
);
// 添加到左侧
this.leftData = [...this.leftData, ...selectedItems];
// 重置选中状态
this.rightSelectedKeys = [];
this.updateRightCheckState();
this.updateLeftCheckState();
},
// 向右移动选中项
moveToRight() {
const selectedItems = this.leftData.filter(item =>
this.leftSelectedKeys.includes(item.id)
);
// 从左侧移除
this.leftData = this.leftData.filter(item =>
!this.leftSelectedKeys.includes(item.id)
);
// 添加到右侧
this.rightData = [...this.rightData, ...selectedItems];
// 重置选中状态
this.leftSelectedKeys = [];
this.updateLeftCheckState();
this.updateRightCheckState();
},
// 左侧选择变化
onLeftSelectChange(selectedKeys) {
this.leftSelectedKeys = selectedKeys;
this.updateLeftCheckState();
},
// 右侧选择变化
onRightSelectChange(selectedKeys) {
this.rightSelectedKeys = selectedKeys;
this.updateRightCheckState();
},
// 左侧全选变化
onLeftCheckAllChange(e) {
const checked = e.target.checked;
this.leftSelectedKeys = checked ? this.leftData.map(item => item.id) : [];
this.updateLeftCheckState();
},
// 右侧全选变化
onRightCheckAllChange(e) {
const checked = e.target.checked;
this.rightSelectedKeys = checked ? this.rightData.map(item => item.id) : [];
this.updateRightCheckState();
},
// 更新左侧选择状态
updateLeftCheckState() {
const total = this.leftData.length;
const selected = this.leftSelectedKeys.length;
this.leftCheckAll = total > 0 && selected === total;
this.leftIndeterminate = selected > 0 && selected < total;
},
// 更新右侧选择状态
updateRightCheckState() {
const total = this.rightData.length;
const selected = this.rightSelectedKeys.length;
this.rightCheckAll = total > 0 && selected === total;
this.rightIndeterminate = selected > 0 && selected < total;
}
}
});
</script>
</body>
</html>
```
### 功能说明:
1. **双表格穿梭框**:
- 左侧表格显示未分配员工
- 右侧表格显示已分配员工
- 中间操作按钮实现数据双向移动
2. **全选功能**:
- 每个表格都有全选复选框
- 支持全选/取消全选操作
- 半选状态显示(部分选中)
3. **选择状态管理**:
- 使用 `selectedKeys` 管理选中项
- 实时更新全选和半选状态
- 底部显示当前选中项信息
4. **数据管理**:
- 初始化时所有数据在左侧表格
- 支持数据双向移动
- 提供重置功能恢复初始状态
5. **Ant Design Vue 组件**:
- 使用 `a-table` 实现表格展示
- 使用 `a-checkbox` 实现全选功能
- 使用 `a-button` 实现操作按钮
- 使用 `a-card` 作为容器
### 关键实现细节:
1. **数据分离**:
- 使用 `leftData` 和 `rightData` 分别管理两侧数据
- 通过数组过滤实现数据移动
2. **选择状态同步**:
- 使用 `selectedKeys` 数组记录选中项
- 通过 `updateCheckState` 方法同步全选/半选状态
3. **性能优化**:
- 使用 `row-key` 确保行标识唯一性
- 避免不必要的全表渲染
4. **UI 布局**:
- 使用 Flex 布局实现三栏式结构
- 操作按钮垂直居中排列
- 表格标题显示数据计数