Skip to content

Tree 树形控件

多层次的结构列表。

代码演示

基础用法

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

parent 1
parent 1-0
leaf
leaf
parent 1-1
sss
vue
<template>
  <a-tree
    v-model:expandedKeys="expandedKeys"
    v-model:selectedKeys="selectedKeys"
    v-model:checkedKeys="checkedKeys"
    checkable
    :tree-data="treeData"
  >
    <template #title="{ title, key }">
      <span v-if="key === '0-0-1-0'" style="color: #1890ff">{{ title }}</span>
      <template v-else>{{ title }}</template>
    </template>
  </a-tree>
</template>
<script setup>
import { ref, watch } from 'vue';
const treeData = [
  {
    title: 'parent 1',
    key: '0-0',
    children: [
      {
        title: 'parent 1-0',
        key: '0-0-0',
        disabled: true,
        children: [
          {
            title: 'leaf',
            key: '0-0-0-0',
            disableCheckbox: true,
          },
          {
            title: 'leaf',
            key: '0-0-0-1',
          },
        ],
      },
      {
        title: 'parent 1-1',
        key: '0-0-1',
        children: [
          {
            key: '0-0-1-0',
            title: 'sss',
          },
        ],
      },
    ],
  },
];
const expandedKeys = ref(['0-0-0', '0-0-1']);
const selectedKeys = ref(['0-0-0', '0-0-1']);
const checkedKeys = ref(['0-0-0', '0-0-1']);
watch(expandedKeys, () => {
  console.log('expandedKeys', expandedKeys);
});
watch(selectedKeys, () => {
  console.log('selectedKeys', selectedKeys);
});
watch(checkedKeys, () => {
  console.log('checkedKeys', checkedKeys);
});
</script>

拖动示例

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

0-0
0-1
0-2
vue
<template>
  <a-tree
    class="draggable-tree"
    draggable
    block-node
    :tree-data="gData"
    @dragenter="onDragEnter"
    @drop="onDrop"
  />
</template>
<script setup>
import { ref } from 'vue';
const x = 3;
const y = 2;
const z = 1;
const genData = [];
const generateData = (_level, _preKey, _tns) => {
  const preKey = _preKey || '0';
  const tns = _tns || genData;
  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);
const gData = ref(genData);
const onDragEnter = info => {
  console.log(info);
  // expandedKeys 需要展开时
  // expandedKeys.value = info.expandedKeys;
};
const onDrop = info => {
  console.log(info);
  const dropKey = info.node.key;
  const dragKey = info.dragNode.key;
  const dropPos = info.node.pos.split('-');
  const dropPosition = info.dropPosition - Number(dropPos[dropPos.length - 1]);
  const loop = (data, key, callback) => {
    data.forEach((item, index) => {
      if (item.key === key) {
        return callback(item, index, data);
      }
      if (item.children) {
        return loop(item.children, key, callback);
      }
    });
  };
  const data = [...gData.value];

  // 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.unshift(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 = 0;
    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);
    }
  }
  gData.value = data;
};
</script>

异步数据加载

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

Expand to load
Expand to load
Tree Node
vue
<template>
  <a-tree
    v-model:expandedKeys="expandedKeys"
    v-model:selectedKeys="selectedKeys"
    :load-data="onLoadData"
    :tree-data="treeData"
  />
</template>
<script setup>
import { ref } from 'vue';
const expandedKeys = ref([]);
const selectedKeys = ref([]);
const treeData = ref([
  {
    title: 'Expand to load',
    key: '0',
  },
  {
    title: 'Expand to load',
    key: '1',
  },
  {
    title: 'Tree Node',
    key: '2',
    isLeaf: true,
  },
]);
const 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`,
        },
      ];
      treeData.value = [...treeData.value];
      resolve();
    }, 1000);
  });
};
</script>

可搜索

可搜索的树。

0-0
0-1
0-2
vue
<template>
  <div>
    <a-input-search v-model:value="searchValue" style="margin-bottom: 8px" placeholder="Search" />
    <a-tree
      :expanded-keys="expandedKeys"
      :auto-expand-parent="autoExpandParent"
      :tree-data="gData"
      @expand="onExpand"
    >
      <template #title="{ title }">
        <span v-if="title.indexOf(searchValue) > -1">
          {{ title.substring(0, title.indexOf(searchValue)) }}
          <span style="color: #f50">{{ searchValue }}</span>
          {{ title.substring(title.indexOf(searchValue) + searchValue.length) }}
        </span>
        <span v-else>{{ title }}</span>
      </template>
    </a-tree>
  </div>
</template>
<script setup>
import { ref, watch } from 'vue';
const x = 3;
const y = 2;
const z = 1;
const genData = [];
const generateData = (_level, _preKey, _tns) => {
  const preKey = _preKey || '0';
  const tns = _tns || genData;
  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);
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);
    }
  }
};
generateList(genData);
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;
};
const expandedKeys = ref([]);
const searchValue = ref('');
const autoExpandParent = ref(true);
const gData = ref(genData);
const onExpand = keys => {
  expandedKeys.value = keys;
  autoExpandParent.value = false;
};
watch(searchValue, value => {
  const expanded = dataList
    .map(item => {
      if (item.title.indexOf(value) > -1) {
        return getParentKey(item.key, gData.value);
      }
      return null;
    })
    .filter((item, i, self) => item && self.indexOf(item) === i);
  expandedKeys.value = expanded;
  searchValue.value = value;
  autoExpandParent.value = true;
});
</script>

目录

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

parent 0
leaf 0-0
leaf 0-1
parent 1
leaf 1-0
leaf 1-1
vue
<template>
  <a-directory-tree
    v-model:expandedKeys="expandedKeys"
    v-model:selectedKeys="selectedKeys"
    multiple
    :tree-data="treeData"
  ></a-directory-tree>
</template>
<script setup>
import { ref } from 'vue';
const expandedKeys = ref(['0-0', '0-1']);
const selectedKeys = ref([]);
const treeData = [
  {
    title: 'parent 0',
    key: '0-0',
    children: [
      {
        title: 'leaf 0-0',
        key: '0-0-0',
        isLeaf: true,
      },
      {
        title: 'leaf 0-1',
        key: '0-0-1',
        isLeaf: true,
      },
    ],
  },
  {
    title: 'parent 1',
    key: '0-1',
    children: [
      {
        title: 'leaf 1-0',
        key: '0-1-0',
        isLeaf: true,
      },
      {
        title: 'leaf 1-1',
        key: '0-1-1',
        isLeaf: true,
      },
    ],
  },
];
</script>

API

Tree props

参数说明类型默认值
allowDrop是否允许拖拽时放置在该节点({ dropNode, dropPosition }) => boolean-
autoExpandParent是否自动展开父节点booleanfalse
blockNode是否节点占据一行booleanfalse
checkable节点前添加 Checkbox 复选框booleanfalse
checkedKeys(v-model)(受控)选中复选框的树节点(注意:父子节点有关联,如果传入父节点 key,则子节点自动选中;相应当子节点 key 都传入,父节点也自动选中。当设置checkable和checkStrictly,它是一个有checked和halfChecked属性的对象,并且父子节点的选中与否不再关联string[] | number[] | {checked: string[] | number[], halfChecked: string[] | number[] }[]
checkStrictlycheckable 状态下节点选择完全受控(父子节点选中状态不再关联)booleanfalse
defaultExpandAll默认展开所有树节点, 如果是异步数据,需要在数据返回后再实例化,建议用 v-if="data.length";当有 expandedKeys 时,defaultExpandAll 将失效booleanfalse
disabled将树禁用boolfalse
draggable设置节点可拖拽booleanfalse
expandedKeys(v-model)(受控)展开指定的树节点string[] | number[][]
fieldNames替换 treeNode 中 title,key,children 字段为 treeData 中对应的字段object{children:'children', title:'title', key:'key' }
filterTreeNode按需筛选树节点(高亮),返回 truefunction(node)-
height设置虚拟滚动容器高度,设置后内部节点不再支持横向滚动number-
loadData异步加载数据function(node)-
loadedKeys(受控)已经加载的节点,需要配合 loadData 使用string[] | number[][]
multiple支持点选多个节点(节点本身)booleanfalse
selectable是否可选中booleantrue
selectedKeys(v-model)(受控)设置选中的树节点string[] | number[]-
showIcon是否展示 TreeNode title 前的图标,没有默认样式,如设置为 true,需要自行定义图标相关样式booleanfalse
showLine是否展示连接线boolean | {showLeafIcon: boolean }false
switcherIcon自定义树节点的展开/折叠图标v-slot:switcherIcon=" {active, checked, expanded, loading, selected, halfChecked, title, key, children, dataRef, data, defaultIcon, switcherCls }"-
title自定义标题slot
treeDatatreeNodes 数据,如果设置则不需要手动构造 TreeNode 节点(key 在整个树范围内唯一)TreeNode[]-
virtual设置 false 时关闭虚拟滚动booleantrue

事件

事件名称说明回调参数
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})

Tree 方法

名称说明
scrollTo({ key: stringnumber; align?: 'top'

TreeNode

参数说明类型默认值
checkable当树为 checkable 时,设置独立节点是否展示 Checkboxboolean-
class节点的 classstring-
disableCheckbox禁掉 checkboxbooleanfalse
disabled禁掉响应booleanfalse
icon自定义图标。可接收组件,props 为当前节点 propsslot | slot-scope-
isLeaf设置为叶子节点(设置了loadData时有效)boolean-
key被树的 (default)ExpandedKeys / (default)CheckedKeys / (default)SelectedKeys 属性所用。注意:整个树范围内的所有节点的 key 值不能重复!string | number内部计算出的节点位置
selectable设置节点是否可被选中booleantrue
style节点的 stylestring | object-
title标题string'---'

DirectoryTree props

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