穿梭框

双栏穿梭选择框。

何时使用

  • 需要在多个可选项中进行多选时。
  • 比起 Select 和 TreeSelect,穿梭框占据更大的空间,可以展示可选项的更多信息。

穿梭选择框用直观的方式在两栏中移动元素,完成选择行为。

选择一个或以上的选项后,点击对应的方向键,可以把选中的选项移动到另一栏。
其中,左边一栏为 source,右边一栏为 target,API 的设计也反映了这两个概念。

代码演示

Transfer穿梭框 - 图1

基本用法

最基本的用法,展示了 dataSourcetargetKeys、每行的渲染函数 render 以及回调函数 change selectChange scroll 的用法。

  1. <template>
  2. <div>
  3. <a-transfer
  4. :data-source="mockData"
  5. :titles="['Source', 'Target']"
  6. :target-keys="targetKeys"
  7. :selected-keys="selectedKeys"
  8. :render="item => item.title"
  9. :disabled="disabled"
  10. @change="handleChange"
  11. @selectChange="handleSelectChange"
  12. @scroll="handleScroll"
  13. />
  14. <a-switch
  15. un-checked-children="enabled"
  16. checked-children="disabled"
  17. :checked="disabled"
  18. style="margin-top: 16px"
  19. @change="handleDisable"
  20. />
  21. </div>
  22. </template>
  23. <script>
  24. export default {
  25. data() {
  26. const mockData = [];
  27. for (let i = 0; i < 20; i++) {
  28. mockData.push({
  29. key: i.toString(),
  30. title: `content${i + 1}`,
  31. description: `description of content${i + 1}`,
  32. disabled: i % 3 < 1,
  33. });
  34. }
  35. const oriTargetKeys = mockData.filter(item => +item.key % 3 > 1).map(item => item.key);
  36. return {
  37. mockData,
  38. targetKeys: oriTargetKeys,
  39. selectedKeys: ['1', '4'],
  40. disabled: false,
  41. };
  42. },
  43. methods: {
  44. handleChange(nextTargetKeys, direction, moveKeys) {
  45. this.targetKeys = nextTargetKeys;
  46. console.log('targetKeys: ', nextTargetKeys);
  47. console.log('direction: ', direction);
  48. console.log('moveKeys: ', moveKeys);
  49. },
  50. handleSelectChange(sourceSelectedKeys, targetSelectedKeys) {
  51. this.selectedKeys = [...sourceSelectedKeys, ...targetSelectedKeys];
  52. console.log('sourceSelectedKeys: ', sourceSelectedKeys);
  53. console.log('targetSelectedKeys: ', targetSelectedKeys);
  54. },
  55. handleScroll(direction, e) {
  56. console.log('direction:', direction);
  57. console.log('target:', e.target);
  58. },
  59. handleDisable(disabled) {
  60. this.disabled = disabled;
  61. },
  62. },
  63. };
  64. </script>

Transfer穿梭框 - 图2

带搜索框

带搜索框的穿梭框,可以自定义搜索函数。

  1. <template>
  2. <a-transfer
  3. :data-source="mockData"
  4. show-search
  5. :filter-option="filterOption"
  6. :target-keys="targetKeys"
  7. :render="item => item.title"
  8. @change="handleChange"
  9. @search="handleSearch"
  10. />
  11. </template>
  12. <script>
  13. export default {
  14. data() {
  15. return {
  16. mockData: [],
  17. targetKeys: [],
  18. };
  19. },
  20. mounted() {
  21. this.getMock();
  22. },
  23. methods: {
  24. getMock() {
  25. const targetKeys = [];
  26. const mockData = [];
  27. for (let i = 0; i < 20; i++) {
  28. const data = {
  29. key: i.toString(),
  30. title: `content${i + 1}`,
  31. description: `description of content${i + 1}`,
  32. chosen: Math.random() * 2 > 1,
  33. };
  34. if (data.chosen) {
  35. targetKeys.push(data.key);
  36. }
  37. mockData.push(data);
  38. }
  39. this.mockData = mockData;
  40. this.targetKeys = targetKeys;
  41. },
  42. filterOption(inputValue, option) {
  43. return option.description.indexOf(inputValue) > -1;
  44. },
  45. handleChange(targetKeys, direction, moveKeys) {
  46. console.log(targetKeys, direction, moveKeys);
  47. this.targetKeys = targetKeys;
  48. },
  49. handleSearch(dir, value) {
  50. console.log('search:', dir, value);
  51. },
  52. },
  53. };
  54. </script>

Transfer穿梭框 - 图3

高级用法

穿梭框高级用法,可配置操作文案,可定制宽高,可对底部进行自定义渲染。

  1. <template>
  2. <a-transfer
  3. :data-source="mockData"
  4. show-search
  5. :list-style="{
  6. width: '250px',
  7. height: '300px',
  8. }"
  9. :operations="['to right', 'to left']"
  10. :target-keys="targetKeys"
  11. :render="item => `${item.title}-${item.description}`"
  12. @change="handleChange"
  13. >
  14. <a-button
  15. slot="footer"
  16. slot-scope="props"
  17. size="small"
  18. style="float:right;margin: 5px"
  19. @click="getMock"
  20. >
  21. reload
  22. </a-button>
  23. <span slot="notFoundContent">
  24. 没数据
  25. </span>
  26. </a-transfer>
  27. </template>
  28. <script>
  29. export default {
  30. data() {
  31. return {
  32. mockData: [],
  33. targetKeys: [],
  34. };
  35. },
  36. mounted() {
  37. this.getMock();
  38. },
  39. methods: {
  40. getMock() {
  41. const targetKeys = [];
  42. const mockData = [];
  43. for (let i = 0; i < 20; i++) {
  44. const data = {
  45. key: i.toString(),
  46. title: `content${i + 1}`,
  47. description: `description of content${i + 1}`,
  48. chosen: Math.random() * 2 > 1,
  49. };
  50. if (data.chosen) {
  51. targetKeys.push(data.key);
  52. }
  53. mockData.push(data);
  54. }
  55. this.mockData = mockData;
  56. this.targetKeys = targetKeys;
  57. },
  58. handleChange(targetKeys, direction, moveKeys) {
  59. console.log(targetKeys, direction, moveKeys);
  60. this.targetKeys = targetKeys;
  61. },
  62. },
  63. };
  64. </script>

Transfer穿梭框 - 图4

自定义渲染行数据

自定义渲染每一个 Transfer Item,可用于渲染复杂数据。

  1. <template>
  2. <a-transfer
  3. :data-source="mockData"
  4. :list-style="{
  5. width: '300px',
  6. height: '300px',
  7. }"
  8. :target-keys="targetKeys"
  9. :render="renderItem"
  10. @change="handleChange"
  11. />
  12. </template>
  13. <script>
  14. export default {
  15. data() {
  16. return {
  17. mockData: [],
  18. targetKeys: [],
  19. };
  20. },
  21. mounted() {
  22. this.getMock();
  23. },
  24. methods: {
  25. getMock() {
  26. const targetKeys = [];
  27. const mockData = [];
  28. for (let i = 0; i < 20; i++) {
  29. const data = {
  30. key: i.toString(),
  31. title: `content${i + 1}`,
  32. description: `description of content${i + 1}`,
  33. chosen: Math.random() * 2 > 1,
  34. };
  35. if (data.chosen) {
  36. targetKeys.push(data.key);
  37. }
  38. mockData.push(data);
  39. }
  40. this.mockData = mockData;
  41. this.targetKeys = targetKeys;
  42. },
  43. renderItem(item) {
  44. const customLabel = (
  45. <span class="custom-item">
  46. {item.title} - {item.description}
  47. </span>
  48. );
  49. return {
  50. label: customLabel, // for displayed item
  51. value: item.title, // for title and filter matching
  52. };
  53. },
  54. handleChange(targetKeys, direction, moveKeys) {
  55. console.log(targetKeys, direction, moveKeys);
  56. this.targetKeys = targetKeys;
  57. },
  58. },
  59. };
  60. </script>

Transfer穿梭框 - 图5

表格穿梭框

使用 Table 组件作为自定义渲染列表。

  1. <template>
  2. <div>
  3. <a-transfer
  4. :data-source="mockData"
  5. :target-keys="targetKeys"
  6. :disabled="disabled"
  7. :show-search="showSearch"
  8. :filter-option="(inputValue, item) => item.title.indexOf(inputValue) !== -1"
  9. :show-select-all="false"
  10. @change="onChange"
  11. >
  12. <template
  13. slot="children"
  14. slot-scope="{
  15. props: { direction, filteredItems, selectedKeys, disabled: listDisabled },
  16. on: { itemSelectAll, itemSelect },
  17. }"
  18. >
  19. <a-table
  20. :row-selection="
  21. getRowSelection({ disabled: listDisabled, selectedKeys, itemSelectAll, itemSelect })
  22. "
  23. :columns="direction === 'left' ? leftColumns : rightColumns"
  24. :data-source="filteredItems"
  25. size="small"
  26. :style="{ pointerEvents: listDisabled ? 'none' : null }"
  27. :custom-row="
  28. ({ key, disabled: itemDisabled }) => ({
  29. on: {
  30. click: () => {
  31. if (itemDisabled || listDisabled) return;
  32. itemSelect(key, !selectedKeys.includes(key));
  33. },
  34. },
  35. })
  36. "
  37. />
  38. </template>
  39. </a-transfer>
  40. <a-switch
  41. un-checked-children="disabled"
  42. checked-children="disabled"
  43. :checked="disabled"
  44. style="margin-top: 16px"
  45. @change="triggerDisable"
  46. />
  47. <a-switch
  48. un-checked-children="showSearch"
  49. checked-children="showSearch"
  50. :checked="showSearch"
  51. style="margin-top: 16px"
  52. @change="triggerShowSearch"
  53. />
  54. </div>
  55. </template>
  56. <script>
  57. import difference from 'lodash/difference';
  58. const mockData = [];
  59. for (let i = 0; i < 20; i++) {
  60. mockData.push({
  61. key: i.toString(),
  62. title: `content${i + 1}`,
  63. description: `description of content${i + 1}`,
  64. disabled: i % 4 === 0,
  65. });
  66. }
  67. const originTargetKeys = mockData.filter(item => +item.key % 3 > 1).map(item => item.key);
  68. const leftTableColumns = [
  69. {
  70. dataIndex: 'title',
  71. title: 'Name',
  72. },
  73. {
  74. dataIndex: 'description',
  75. title: 'Description',
  76. },
  77. ];
  78. const rightTableColumns = [
  79. {
  80. dataIndex: 'title',
  81. title: 'Name',
  82. },
  83. ];
  84. export default {
  85. data() {
  86. return {
  87. mockData,
  88. targetKeys: originTargetKeys,
  89. disabled: false,
  90. showSearch: false,
  91. leftColumns: leftTableColumns,
  92. rightColumns: rightTableColumns,
  93. };
  94. },
  95. methods: {
  96. onChange(nextTargetKeys) {
  97. this.targetKeys = nextTargetKeys;
  98. },
  99. triggerDisable(disabled) {
  100. this.disabled = disabled;
  101. },
  102. triggerShowSearch(showSearch) {
  103. this.showSearch = showSearch;
  104. },
  105. getRowSelection({ disabled, selectedKeys, itemSelectAll, itemSelect }) {
  106. return {
  107. getCheckboxProps: item => ({ props: { disabled: disabled || item.disabled } }),
  108. onSelectAll(selected, selectedRows) {
  109. const treeSelectedKeys = selectedRows
  110. .filter(item => !item.disabled)
  111. .map(({ key }) => key);
  112. const diffKeys = selected
  113. ? difference(treeSelectedKeys, selectedKeys)
  114. : difference(selectedKeys, treeSelectedKeys);
  115. itemSelectAll(diffKeys, selected);
  116. },
  117. onSelect({ key }, selected) {
  118. itemSelect(key, selected);
  119. },
  120. selectedRowKeys: selectedKeys,
  121. };
  122. },
  123. },
  124. };
  125. </script>

Transfer穿梭框 - 图6

树穿梭框

使用 Tree 组件作为自定义渲染列表。

  1. <template>
  2. <div>
  3. <a-transfer
  4. class="tree-transfer"
  5. :data-source="dataSource"
  6. :target-keys="targetKeys"
  7. :render="item => item.title"
  8. :show-select-all="false"
  9. @change="onChange"
  10. >
  11. <template
  12. slot="children"
  13. slot-scope="{
  14. props: { direction, selectedKeys },
  15. on: { itemSelect },
  16. }"
  17. >
  18. <a-tree
  19. v-if="direction === 'left'"
  20. blockNode
  21. checkable
  22. checkStrictly
  23. defaultExpandAll
  24. :checkedKeys="[...selectedKeys, ...targetKeys]"
  25. :treeData="treeData"
  26. @check="(_, props) => {onChecked(_, props, [...selectedKeys, ...targetKeys],itemSelect)}"
  27. @select="(_, props) => {onChecked(_, props, [...selectedKeys, ...targetKeys],itemSelect)}"
  28. />
  29. </template>
  30. </a-transfer>
  31. </div>
  32. </template>
  33. <script>
  34. const treeData = [
  35. { key: '0-0', title: '0-0' },
  36. {
  37. key: '0-1',
  38. title: '0-1',
  39. children: [{ key: '0-1-0', title: '0-1-0' }, { key: '0-1-1', title: '0-1-1' }],
  40. },
  41. { key: '0-2', title: '0-3' },
  42. ];
  43. const transferDataSource = [];
  44. function flatten(list = []) {
  45. list.forEach(item => {
  46. transferDataSource.push(item);
  47. flatten(item.children);
  48. });
  49. }
  50. flatten(JSON.parse(JSON.stringify(treeData)))
  51. function isChecked(selectedKeys, eventKey) {
  52. return selectedKeys.indexOf(eventKey) !== -1;
  53. }
  54. function handleTreeData(data, targetKeys = []) {
  55. data.forEach(item => {
  56. item['disabled'] = targetKeys.includes(item.key)
  57. if(item.children) {
  58. handleTreeData(item.children, targetKeys)
  59. }
  60. })
  61. return data
  62. }
  63. export default {
  64. data() {
  65. return {
  66. targetKeys: [],
  67. dataSource: transferDataSource
  68. };
  69. },
  70. computed: {
  71. treeData() {
  72. return handleTreeData(treeData, this.targetKeys)
  73. }
  74. },
  75. methods: {
  76. onChange(targetKeys) {
  77. console.log('Target Keys:', targetKeys);
  78. this.targetKeys = targetKeys;
  79. },
  80. onChecked(_, e, checkedKeys, itemSelect){
  81. const { eventKey } = e.node
  82. itemSelect(eventKey, !isChecked(checkedKeys, eventKey));
  83. }
  84. },
  85. };
  86. </script>
  87. <style scoped>
  88. .tree-transfer .ant-transfer-list:first-child {
  89. width: 50%;
  90. flex: none;
  91. }
  92. </style>

API

参数说明类型默认值版本
dataSource数据源,其中的数据将会被渲染到左边一栏中,targetKeys 中指定的除外。[{key: string.isRequired,title: string.isRequired,description: string,disabled: bool}][][]
disabled是否禁用booleanfalse
filterOption接收 inputValue option 两个参数,当 option 符合筛选条件时,应返回 true,反之则返回 false(inputValue, option): boolean
footer可以设置为一个 作用域插槽slot=”footer” slot-scope=”props”
lazyTransfer 使用了 [vc-lazy-load]优化性能,这里可以设置相关参数。设为 false 可以关闭懒加载。object|boolean{ height: 32, offset: 32 }
listStyle两个穿梭框的自定义样式object
locale各种语言object{ itemUnit: ‘项’, itemsUnit: ‘项’, notFoundContent: ‘列表为空’, searchPlaceholder: ‘请输入搜索内容’ }
operations操作文案集合,顺序从上至下string[][‘>’, ‘<’]
render每行数据渲染函数,该函数的入参为 dataSource 中的项,返回值为 element。或者返回一个普通对象,其中 label 字段为 element,value 字段为 titleFunction(record)
selectedKeys设置哪些项应该被选中string[][]
showSearch是否显示搜索框booleanfalse
showSelectAll是否展示全选勾选框booleantrue1.5.0
targetKeys显示在右侧框数据的 key 集合string[][]
titles标题集合,顺序从左至右string[][‘’, ‘’]

事件

事件名称说明回调参数
change选项在两栏之间转移时的回调函数(targetKeys, direction, moveKeys): void
scroll选项列表滚动时的回调函数(direction, event): void
search搜索框内容时改变时的回调函数(direction: ‘left’|’right’, value: string): void
selectChange选中项发生改变时的回调函数(sourceSelectedKeys, targetSelectedKeys): void

Render Props

1.5.0 新增。Transfer 支持接收 children 自定义渲染列表,并返回以下参数:

  1. {
  2. "props": {
  3. "direction": String,
  4. "disabled": Boolean,
  5. "filteredItems": Array,
  6. "selectedKeys": Array
  7. },
  8. "on": {
  9. "itemSelect": Function,
  10. "itemSelectAll": Function
  11. }
  12. }
参数说明类型版本
direction渲染列表的方向‘left’ | ‘right’1.5.0
disabled是否禁用列表boolean1.5.0
filteredItems过滤后的数据TransferItem[]1.5.0
selectedKeys选中的条目string[]1.5.0
itemSelect勾选条目(key: string, selected: boolean)1.5.0
itemSelectAll勾选一组条目(keys: string[], selected: boolean)1.5.0

参考示例

  1. <a-transfer>
  2. <template
  3. slot="children"
  4. slot-scope="{
  5. props: {
  6. direction,
  7. filteredItems,
  8. selectedKeys,
  9. disabled: listDisabled
  10. }, on: {
  11. itemSelectAll,
  12. itemSelect,
  13. }
  14. }"
  15. >
  16. <your-component />
  17. <template>
  18. </a-transfer>

注意

按照 Vue 最新的规范,所有的组件数组最好绑定 key。在 Transfer 中,dataSource里的数据值需要指定 key 值。对于 dataSource 默认将每列数据的 key 属性作为唯一的标识。

如果你的数据没有这个属性,务必使用 rowKey 来指定数据列的主键。

  1. // 比如你的数据主键是 uid
  2. return <Transfer :rowKey="record => record.uid" />;