树形控件

何时使用

文件夹、组织架构、生物分类、国家地区等等,世间万物的大多数结构都是树形结构。使用树控件可以完整展现其中的层级关系,并具有展开收起选择等交互功能。

代码演示

Tree 树形控件 - 图1

受控操作示例

受控操作示例

  1. <template>
  2. <a-tree
  3. checkable
  4. @expand="onExpand"
  5. :expandedKeys="expandedKeys"
  6. :autoExpandParent="autoExpandParent"
  7. v-model="checkedKeys"
  8. @select="onSelect"
  9. :selectedKeys="selectedKeys"
  10. :treeData="treeData"
  11. />
  12. </template>
  13. <script>
  14. const treeData = [
  15. {
  16. title: '0-0',
  17. key: '0-0',
  18. children: [
  19. {
  20. title: '0-0-0',
  21. key: '0-0-0',
  22. children: [
  23. { title: '0-0-0-0', key: '0-0-0-0' },
  24. { title: '0-0-0-1', key: '0-0-0-1' },
  25. { title: '0-0-0-2', key: '0-0-0-2' },
  26. ],
  27. },
  28. {
  29. title: '0-0-1',
  30. key: '0-0-1',
  31. children: [
  32. { title: '0-0-1-0', key: '0-0-1-0' },
  33. { title: '0-0-1-1', key: '0-0-1-1' },
  34. { title: '0-0-1-2', key: '0-0-1-2' },
  35. ],
  36. },
  37. {
  38. title: '0-0-2',
  39. key: '0-0-2',
  40. },
  41. ],
  42. },
  43. {
  44. title: '0-1',
  45. key: '0-1',
  46. children: [
  47. { title: '0-1-0-0', key: '0-1-0-0' },
  48. { title: '0-1-0-1', key: '0-1-0-1' },
  49. { title: '0-1-0-2', key: '0-1-0-2' },
  50. ],
  51. },
  52. {
  53. title: '0-2',
  54. key: '0-2',
  55. },
  56. ];
  57. export default {
  58. data() {
  59. return {
  60. expandedKeys: ['0-0-0', '0-0-1'],
  61. autoExpandParent: true,
  62. checkedKeys: ['0-0-0'],
  63. selectedKeys: [],
  64. treeData,
  65. };
  66. },
  67. watch: {
  68. checkedKeys(val) {
  69. console.log('onCheck', val);
  70. },
  71. },
  72. methods: {
  73. onExpand(expandedKeys) {
  74. console.log('onExpand', expandedKeys);
  75. // if not set autoExpandParent to false, if children expanded, parent can not collapse.
  76. // or, you can remove all expanded children keys.
  77. this.expandedKeys = expandedKeys;
  78. this.autoExpandParent = false;
  79. },
  80. onCheck(checkedKeys) {
  81. console.log('onCheck', checkedKeys);
  82. this.checkedKeys = checkedKeys;
  83. },
  84. onSelect(selectedKeys, info) {
  85. console.log('onSelect', info);
  86. this.selectedKeys = selectedKeys;
  87. },
  88. },
  89. };
  90. </script>

Tree 树形控件 - 图2

基本用法

最简单的用法,展示可勾选,可选中,禁用,默认展开等功能。

  1. <template>
  2. <a-tree
  3. checkable
  4. :treeData="treeData"
  5. :defaultExpandedKeys="['0-0-0', '0-0-1']"
  6. :defaultSelectedKeys="['0-0-0', '0-0-1']"
  7. :defaultCheckedKeys="['0-0-0', '0-0-1']"
  8. @select="this.onSelect"
  9. @check="this.onCheck"
  10. >
  11. <span slot="title0010" style="color: #1890ff">sss</span>
  12. </a-tree>
  13. </template>
  14. <script>
  15. const treeData = [
  16. {
  17. title: 'parent 1',
  18. key: '0-0',
  19. children: [
  20. {
  21. title: 'parent 1-0',
  22. key: '0-0-0',
  23. disabled: true,
  24. children: [
  25. { title: 'leaf', key: '0-0-0-0', disableCheckbox: true },
  26. { title: 'leaf', key: '0-0-0-1' },
  27. ],
  28. },
  29. {
  30. title: 'parent 1-1',
  31. key: '0-0-1',
  32. children: [{ key: '0-0-1-0', slots: { title: 'title0010' } }],
  33. },
  34. ],
  35. },
  36. ];
  37. export default {
  38. data() {
  39. return {
  40. treeData,
  41. };
  42. },
  43. methods: {
  44. onSelect(selectedKeys, info) {
  45. console.log('selected', selectedKeys, info);
  46. },
  47. onCheck(checkedKeys, info) {
  48. console.log('onCheck', checkedKeys, info);
  49. },
  50. },
  51. };
  52. </script>

Tree 树形控件 - 图3

自定义TreeNode字段

替换treeNode中 title,key,children字段为treeData中对应的字段

  1. <template>
  2. <a-tree
  3. checkable
  4. :treeData="treeData"
  5. :defaultExpandedKeys="['0-0-0', '0-0-1']"
  6. :defaultSelectedKeys="['0-0-0', '0-0-1']"
  7. :defaultCheckedKeys="['0-0-0', '0-0-1']"
  8. @select="this.onSelect"
  9. @check="this.onCheck"
  10. :replaceFields="replaceFields"/>
  11. </template>
  12. <script>
  13. const treeData = [
  14. {
  15. name: 'parent 1',
  16. key: '0-0',
  17. child: [
  18. {
  19. name: '张晨成',
  20. key: '0-0-0',
  21. disabled: true,
  22. child: [
  23. { name: 'leaf', key: '0-0-0-0', disableCheckbox: true },
  24. { name: 'leaf', key: '0-0-0-1' },
  25. ],
  26. },
  27. {
  28. name: 'parent 1-1',
  29. key: '0-0-1',
  30. child: [{ key: '0-0-1-0', name:'zcvc' }],
  31. },
  32. ],
  33. },
  34. ];
  35. export default {
  36. data() {
  37. return {
  38. treeData,
  39. replaceFields:{
  40. children:'child',
  41. title:'name'
  42. }
  43. };
  44. },
  45. methods: {
  46. onSelect(selectedKeys, info) {
  47. console.log('selected', selectedKeys, info);
  48. },
  49. onCheck(checkedKeys, info) {
  50. console.log('onCheck', checkedKeys, info);
  51. },
  52. },
  53. };
  54. </script>

Tree 树形控件 - 图4

自定义图标

可以针对不同的节点定制图标。

<template>
  <a-tree :treeData="treeData" showIcon defaultExpandAll :defaultSelectedKeys="['0-0-0']">
    <a-icon type="down" slot="switcherIcon" />
    <a-icon slot="smile" type="smile-o" />
    <a-icon slot="meh" type="smile-o" />
    <template slot="custom" slot-scope="{selected}">
      <a-icon :type="selected ? 'frown':'frown-o'" />
    </template>
  </a-tree>
</template>
<script>
  const treeData = [
    {
      title: 'parent 1',
      key: '0-0',
      slots: {
        icon: 'smile',
      },
      children: [
        { title: 'leaf', key: '0-0-0', slots: { icon: 'meh' } },
        { title: 'leaf', key: '0-0-1', scopedSlots: { icon: 'custom' } },
      ],
    },
  ];

  export default {
    data() {
      return {
        treeData,
      };
    },
    methods: {
      onSelect(selectedKeys, info) {
        console.log('selected', selectedKeys, info);
      },
      onCheck(checkedKeys, info) {
        console.log('onCheck', checkedKeys, info);
      },
    },
  };
</script>

Tree 树形控件 - 图5

拖动示例

将节点拖拽到其他节点内部或前后。

<template>
  <a-tree
    class="draggable-tree"
    :defaultExpandedKeys="expandedKeys"
    draggable
    @dragenter="onDragEnter"
    @drop="onDrop"
    :treeData="gData"
  />
</template>

<script>
  const x = 3;
  const y = 2;
  const z = 1;
  const gData = [];

  const generateData = (_level, _preKey, _tns) => {
    const preKey = _preKey || '0';
    const tns = _tns || gData;

    const children = [];
    for (let i = 0; i < x; i++) {
      const key = `${preKey}-${i}`;
      tns.push({ title: key, key });
      if (i < y) {
        children.push(key);
      }
    }
    if (_level < 0) {
      return tns;
    }
    const level = _level - 1;
    children.forEach((key, index) => {
      tns[index].children = [];
      return generateData(level, key, tns[index].children);
    });
  };
  generateData(z);
  export default {
    data() {
      return {
        gData,
        expandedKeys: ['0-0', '0-0-0', '0-0-0-0'],
      };
    },
    methods: {
      onDragEnter(info) {
        console.log(info);
        // expandedKeys 需要受控时设置
        // this.expandedKeys = info.expandedKeys
      },
      onDrop(info) {
        console.log(info);
        const dropKey = info.node.eventKey;
        const dragKey = info.dragNode.eventKey;
        const dropPos = info.node.pos.split('-');
        const dropPosition = info.dropPosition - Number(dropPos[dropPos.length - 1]);
        const loop = (data, key, callback) => {
          data.forEach((item, index, arr) => {
            if (item.key === key) {
              return callback(item, index, arr);
            }
            if (item.children) {
              return loop(item.children, key, callback);
            }
          });
        };
        const data = [...this.gData];

        // Find dragObject
        let dragObj;
        loop(data, dragKey, (item, index, arr) => {
          arr.splice(index, 1);
          dragObj = item;
        });
        if (!info.dropToGap) {
          // Drop on the content
          loop(data, dropKey, item => {
            item.children = item.children || [];
            // where to insert 示例添加到尾部,可以是随意位置
            item.children.push(dragObj);
          });
        } else if (
          (info.node.children || []).length > 0 && // Has children
          info.node.expanded && // Is expanded
          dropPosition === 1 // On the bottom gap
        ) {
          loop(data, dropKey, item => {
            item.children = item.children || [];
            // where to insert 示例添加到尾部,可以是随意位置
            item.children.unshift(dragObj);
          });
        } else {
          let ar;
          let i;
          loop(data, dropKey, (item, index, arr) => {
            ar = arr;
            i = index;
          });
          if (dropPosition === -1) {
            ar.splice(i, 0, dragObj);
          } else {
            ar.splice(i + 1, 0, dragObj);
          }
        }
        this.gData = data;
      },
    },
  };
</script>

Tree 树形控件 - 图6

异步数据加载

点击展开节点,动态加载数据。

<template>
  <a-tree :loadData="onLoadData" :treeData="treeData" />
</template>

<script>
  export default {
    data() {
      return {
        treeData: [
          { title: 'Expand to load', key: '0' },
          { title: 'Expand to load', key: '1' },
          { title: 'Tree Node', key: '2', isLeaf: true },
        ],
      };
    },
    methods: {
      onLoadData(treeNode) {
        return new Promise(resolve => {
          if (treeNode.dataRef.children) {
            resolve();
            return;
          }
          setTimeout(() => {
            treeNode.dataRef.children = [
              { title: 'Child Node', key: `${treeNode.eventKey}-0` },
              { title: 'Child Node', key: `${treeNode.eventKey}-1` },
            ];
            this.treeData = [...this.treeData];
            resolve();
          }, 1000);
        });
      },
    },
  };
</script>

Tree 树形控件 - 图7

连接线

带连接线的树。

<template>
  <a-tree showLine :defaultExpandedKeys="['0-0-0']" @select="onSelect">
    <a-tree-node key="0-0">
      <span slot="title" style="color: #1890ff">parent 1</span>
      <a-tree-node title="parent 1-0" key="0-0-0">
        <a-tree-node title="leaf" key="0-0-0-0" />
        <a-tree-node title="leaf" key="0-0-0-1" />
        <a-tree-node title="leaf" key="0-0-0-2" />
      </a-tree-node>
      <a-tree-node title="parent 1-1" key="0-0-1">
        <a-tree-node title="leaf" key="0-0-1-0" />
      </a-tree-node>
      <a-tree-node title="parent 1-2" key="0-0-2">
        <a-tree-node title="leaf" key="0-0-2-0" />
        <a-tree-node title="leaf" key="0-0-2-1" />
      </a-tree-node>
    </a-tree-node>
  </a-tree>
</template>

<script>
  export default {
    methods: {
      onSelect(selectedKeys, info) {
        console.log('selected', selectedKeys, info);
      },
    },
  };
</script>

Tree 树形控件 - 图8

可搜索

可搜索的树。

<template>
  <div>
    <a-input-search style="margin-bottom: 8px" placeholder="Search" @change="onChange" />
    <a-tree
      @expand="onExpand"
      :expandedKeys="expandedKeys"
      :autoExpandParent="autoExpandParent"
      :treeData="gData"
    >
      <template slot="title" slot-scope="{title}">
        <span v-if="title.indexOf(searchValue) > -1">
          {{title.substr(0, title.indexOf(searchValue))}}
          <span style="color: #f50">{{searchValue}}</span>
          {{title.substr(title.indexOf(searchValue) + searchValue.length)}}
        </span>
        <span v-else>{{title}}</span>
      </template>
    </a-tree>
  </div>
</template>

<script>
  const x = 3;
  const y = 2;
  const z = 1;
  const gData = [];

  const generateData = (_level, _preKey, _tns) => {
    const preKey = _preKey || '0';
    const tns = _tns || gData;

    const children = [];
    for (let i = 0; i < x; i++) {
      const key = `${preKey}-${i}`;
      tns.push({ title: key, key, scopedSlots: { title: 'title' } });
      if (i < y) {
        children.push(key);
      }
    }
    if (_level < 0) {
      return tns;
    }
    const level = _level - 1;
    children.forEach((key, index) => {
      tns[index].children = [];
      return generateData(level, key, tns[index].children);
    });
  };
  generateData(z);

  const dataList = [];
  const generateList = data => {
    for (let i = 0; i < data.length; i++) {
      const node = data[i];
      const key = node.key;
      dataList.push({ key, title: key });
      if (node.children) {
        generateList(node.children, node.key);
      }
    }
  };
  generateList(gData);

  const getParentKey = (key, tree) => {
    let parentKey;
    for (let i = 0; i < tree.length; i++) {
      const node = tree[i];
      if (node.children) {
        if (node.children.some(item => item.key === key)) {
          parentKey = node.key;
        } else if (getParentKey(key, node.children)) {
          parentKey = getParentKey(key, node.children);
        }
      }
    }
    return parentKey;
  };
  export default {
    data() {
      return {
        expandedKeys: [],
        searchValue: '',
        autoExpandParent: true,
        gData,
      };
    },
    methods: {
      onExpand(expandedKeys) {
        this.expandedKeys = expandedKeys;
        this.autoExpandParent = false;
      },
      onChange(e) {
        const value = e.target.value;
        const expandedKeys = dataList
          .map(item => {
            if (item.key.indexOf(value) > -1) {
              return getParentKey(item.key, gData);
            }
            return null;
          })
          .filter((item, i, self) => item && self.indexOf(item) === i);
        Object.assign(this, {
          expandedKeys,
          searchValue: value,
          autoExpandParent: true,
        });
      },
    },
  };
</script>

Tree 树形控件 - 图9

目录

内置的目录树,multiple 模式支持 ctrl(Windows) / command(Mac) 复选。

<template>
  <a-directory-tree multiple defaultExpandAll @select="onSelect" @expand="onExpand">
    <a-tree-node title="parent 0" key="0-0">
      <a-tree-node title="leaf 0-0" key="0-0-0" isLeaf />
      <a-tree-node title="leaf 0-1" key="0-0-1" isLeaf />
    </a-tree-node>
    <a-tree-node title="parent 1" key="0-1">
      <a-tree-node title="leaf 1-0" key="0-1-0" isLeaf />
      <a-tree-node title="leaf 1-1" key="0-1-1" isLeaf />
    </a-tree-node>
  </a-directory-tree>
</template>
<script>
  export default {
    methods: {
      onSelect(keys) {
        console.log('Trigger Select', keys);
      },
      onExpand() {
        console.log('Trigger Expand');
      },
    },
  };
</script>

API

Tree props

参数说明类型默认值
treeData节点的配置描述,具体项见下表, 1.1.4 之前的版本使用treeNodesarray
replaceFields替换treeNode中 title,key,children字段为treeData中对应的字段object{children:'children', title:'title', key:'key' }
autoExpandParent是否自动展开父节点booleantrue
checkable节点前添加 Checkbox 复选框booleanfalse
checkedKeys(v-model)(受控)选中复选框的树节点(注意:父子节点有关联,如果传入父节点 key,则子节点自动选中;相应当子节点 key 都传入,父节点也自动选中。当设置checkablecheckStrictly,它是一个有checkedhalfChecked属性的对象,并且父子节点的选中与否不再关联string[] | number[] | {checked: string[] | number[], halfChecked: string[] | number[]}[]
checkStrictlycheckable 状态下节点选择完全受控(父子节点选中状态不再关联)booleanfalse
defaultCheckedKeys默认选中复选框的树节点string[] | number[][]
defaultExpandAll默认展开所有树节点booleanfalse
defaultExpandedKeys默认展开指定的树节点string[] | number[][]
defaultExpandParent默认展开父节点booltrue
defaultSelectedKeys默认选中的树节点string[] | number[][]
disabled将树禁用boolfalse
draggable设置节点可拖拽booleanfalse
expandedKeys(.sync)(受控)展开指定的树节点string[] | number[][]
filterTreeNode按需筛选树节点(高亮),返回 truefunction(node)-
loadData异步加载数据function(node)-
loadedKeys(受控)已经加载的节点,需要配合 loadData 使用string[] | number[][]
multiple支持点选多个节点(节点本身)booleanfalse
selectedKeys(.sync)(受控)设置选中的树节点string[] | number[]-
showIcon是否展示 TreeNode title 前的图标,没有默认样式,如设置为 true,需要自行定义图标相关样式booleanfalse
switcherIcon自定义树节点的展开/折叠图标slot-
showLine是否展示连接线booleanfalse

事件

事件名称说明回调参数
check点击复选框触发function(checkedKeys, e:{checked: bool, checkedNodes, node, event})
dragenddragend 触发时调用function({event, node})
dragenterdragenter 触发时调用function({event, node, expandedKeys})
dragleavedragleave 触发时调用function({event, node})
dragoverdragover 触发时调用function({event, node})
dragstart开始拖拽时调用function({event, node})
dropdrop 触发时调用function({event, node, dragNode, dragNodesKeys})
expand展开/收起节点时触发function(expandedKeys, {expanded: bool, node})
load节点加载完毕时触发function(loadedKeys, {event, node})
rightClick响应右键点击function({event, node})
select点击树节点触发function(selectedKeys, e:{selected: bool, selectedNodes, node, event})

TreeNode props

结点描述数据对象,是 treeNodes 中的一项,TreeNode 使用相同的 API。

参数说明类型默认值
class节点的 classstring-
style节点的 stylestring|object-
disableCheckbox禁掉 checkboxbooleanfalse
disabled禁掉响应booleanfalse
icon自定义图标。可接收组件,props 为当前节点 propsslot|slot-scope-
isLeaf设置为叶子节点(设置了loadData时有效)booleanfalse
key被树的 (default)ExpandedKeys / (default)CheckedKeys / (default)SelectedKeys 属性所用。注意:整个树范围内的所有节点的 key 值不能重复!string | number内部计算出的节点位置
selectable设置节点是否可被选中booleantrue
title标题string|slot|slot-scope'—-'
slots使用 treeNodes 时,可以通过该属性配置支持 slot 的属性,如 slots: { title: 'XXX'}object-
scopedSlots使用 columns 时,可以通过该属性配置支持 slot-scope 的属性,如 scopedSlots: { title: 'XXX'}object-
on事件对象,仅在 treeNodes 使用方式中生效,如{click: () => {}}object'—-'

DirectoryTree props

参数说明类型默认值
expandAction目录展开逻辑,可选 false 'click' 'dblclick'stringclick

FAQ

在 showLine 时,如何隐藏子节点图标?

文件图标通过 switcherIcon 来实现,如果不需要你可以覆盖对应的样式