ant-design-vue Tree 树形控件完全指南:从 API 详解到源码级实现原理
【免费下载链接】ant-design-vue🌈 An enterprise-class UI components based on Ant Design and Vue. 🐜项目地址: https://gitcode.com/gh_mirrors/an/ant-design-vue
多层次的结构列表在业务系统中无处不在——文件夹目录、组织架构、生物分类、国家地区,世间万物的大多数结构都是树形结构。ant-design-vue 的Tree树形控件正是为展现这种层级关系而设计,它完整支持展开/收起、节点选择、复选框勾选、异步加载、拖拽排序、虚拟滚动等交互能力,并内置了适合文件目录场景的DirectoryTree。
本文以 ant-design-vue 仓库中 components/tree/index.zh-CN.md 官方文档为骨架,结合Tree.tsx、DirectoryTree.tsx、vc-tree底层实现与仓库内 13 个 demo 示例,系统讲解 Tree 的全部配置项、事件、方法与常见实战场景,帮助你在项目中熟练驾驭树形控件,并理解其底层工作原理。
何时使用 Tree
树形控件的核心价值在于用最直观的层级缩进和展开收起,完整展现数据的父子关系。当你的业务数据满足以下特征时,优先考虑使用Tree:
- 数据存在明确的父子层级(如文件夹、组织架构、类目体系);
- 需要交互能力(展开/收起、选择、勾选、拖拽、右键菜单)来浏览和操作层级数据;
- 需要在大数据量下保持良好的浏览性能(配合虚拟滚动)。
快速上手:基本用法
Tree 既支持通过treeData数据驱动渲染,也支持(旧式)直接以a-tree-node子组件手动构造节点。官方推荐使用treeData,仓库源码 Tree.tsx 中也会对仅使用slots.default而不传treeData的写法发出弃用警告。
以官方基础示例 demo/basic.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 lang="ts" setup> import { ref, watch } from 'vue'; import type { TreeProps } from 'ant-design-vue'; const treeData: TreeProps['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<string[]>(['0-0-0', '0-0-1']); const selectedKeys = ref<string[]>(['0-0-0', '0-0-1']); const checkedKeys = ref<string[]>(['0-0-0', '0-0-1']); </script>这里演示了三个关键能力:
- v-model 双向绑定:
expandedKeys(展开)、selectedKeys(选中)、checkedKeys(勾选)均可通过v-model:前缀实现受控绑定; - 节点级控制:
disabled禁用整节点、disableCheckbox仅禁用该节点复选框; - 插槽定制:通过
#title插槽拿到{ title, key, dataRef }等上下文,自定义标题渲染。
Tree props:完整配置详解
官方文档共定义了 25 个Tree配置项,下表在保留文档原意的基础上,结合 Tree.tsx 中的 props 定义补充了类型与默认值细节。
| 参数 | 说明 | 类型 | 默认值 | 版本 |
|---|---|---|---|---|
| allowDrop | 是否允许拖拽时放置在该节点 | ({ dropNode, dropPosition }) => boolean | - | |
| autoExpandParent | 是否自动展开父节点(搜索联动场景常用) | boolean | false | |
| blockNode | 是否节点占据一整行(拖拽/目录树场景常用) | boolean | false | |
| checkable | 节点前添加 Checkbox 复选框 | boolean | false | |
| checkedKeys (v-model) | (受控)选中复选框的树节点。注意父子关联:传入父节点 key 则子节点自动选中;全部子节点 key 都传入时父节点也自动选中。当同时设置checkable与checkStrictly时,其值为含checked与halfChecked属性的对象,父子选中不再关联 | string[] | number[] | { checked: string[] | number[]; halfChecked: string[] | number[] } | [] | |
| checkStrictly | checkable 状态下节点选择完全受控,父子节点选中状态不再关联 | boolean | false | |
| defaultExpandAll | 默认展开所有节点。异步数据需在数据返回后再实例化,建议用v-if="data.length";当存在expandedKeys时defaultExpandAll失效 | boolean | false | |
| disabled | 将整棵树禁用 | bool | false | |
| draggable | 设置节点可拖拽 | boolean | false | |
| expandedKeys (v-model) | (受控)展开指定的树节点 | string[] | number[] | [] | |
| fieldNames | 替换 treeNode 中 title、key、children 字段为 treeData 中对应的字段 | object | { children: 'children', title: 'title', key: 'key' } | 3.0.0 |
| filterTreeNode | 按需筛选树节点(高亮),返回 true 表示命中 | function(node) | - | |
| height | 设置虚拟滚动容器高度,设置后内部节点不再支持横向滚动 | number | - | |
| loadData | 异步加载数据,展开节点时触发 | function(node) | - | |
| loadedKeys | (受控)已经加载的节点,需配合loadData使用 | string[] | number[] | [] | |
| multiple | 支持点选多个节点(节点本身,非复选框) | boolean | false | |
| selectable | 是否可选中 | boolean | true | |
| selectedKeys (v-model) | (受控)设置选中的树节点 | string[] | number[] | - | |
| showIcon | 是否展示 TreeNode title 前的图标,无默认样式,置 true 后需自行定义图标与相关样式 | boolean | false | |
| showLine | 是否展示连接线,常用于文件目录结构 | boolean | { showLeafIcon: boolean }(3.0+) | false | |
| switcherIcon | 自定义展开/折叠图标,插槽参数含 active、checked、expanded、loading、selected、halfChecked、title、key、children、dataRef、data、defaultIcon、switcherCls 等 | v-slot | - | |
| title | 自定义标题(旧 API,推荐使用#title插槽) | slot | - | 2.0.0 |
| treeData | treeNodes 数据,设置后无需手动构造 TreeNode 节点;key 在整个树范围内必须唯一 | TreeNode[] | -- | |
| virtual | 设置 false 时关闭虚拟滚动 | boolean | true | 3.0 |
源码中的默认值佐证:Tree.tsx 通过initDefaultProps显式声明了checkable: false、selectable: true、showIcon: false、blockNode: false四个默认值,其余未声明默认值的项由底层vc-tree(components/vc-tree/props.ts)接管。
值得注意的源码细节
- 虚拟滚动默认开启:
Tree.tsx中virtual直接取useConfigInject('tree', props)返回的全局配置(Tree.tsx),默认值true,渲染时通过virtual={virtual.value}传给底层VcTree; - 虚拟列表行高固定 28px:
itemHeight = 28作为默认行高传入(Tree.tsx),该值决定虚拟滚动的视口计算精度; replaceFields已废弃:源码通过watchEffect给出 dev 警告(Tree.tsx),并在渲染时用fieldNames = props.replaceFields做兼容,新项目请一律使用fieldNames;- RTL 支持:当 ConfigProvider 的
direction为rtl时,Tree 自动追加-rtl样式类(Tree.tsx)。
数据驱动:treeData 与字段映射 fieldNames
treeData的数据结构为DataNode(定义见 components/vc-tree/interface.tsx):
interface DataNode extends BasicDataNode { children?: DataNode[]; key: string | number; // 全树唯一 title?: any; // 节点标题,可为字符串或 VNode }自定义字段映射 fieldNames
当后端返回的字段名不是title/key/children时,无需改造接口,直接映射即可。参考示例 demo/replaceFields.vue:
<a-tree v-model:expandedKeys="expandedKeys" v-model:selectedKeys="selectedKeys" v-model:checkedKeys="checkedKeys" checkable :tree-data="treeData" :field-names="fieldNames" > <template #title="{ name, key }"> <span v-if="key === '0-0-1'" style="color: #1890ff">{{ name }}</span> <template v-else>{{ name }}</template> </template> </a-tree> <script lang="ts" setup> const fieldNames: TreeProps['fieldNames'] = { children: 'child', title: 'name', }; const treeData = ref([ { name: 'parent 1', key: '0-0', child: [ /* ... */ ] }, ]); </script>注意fieldNames的类型定义(components/vc-tree/interface.tsx)支持title、key、children三个字段的任意组合替换。字段映射在底层通过fillFieldNames归一化后贯穿数据转换、展开传导、勾选传导等全部逻辑(如 DirectoryTree.tsx 中的fillFieldNames(props.fieldNames))。
事件系统:交互回调解读
Tree 共暴露 11 个事件,官方文档定义如下:
| 事件名称 | 说明 | 回调参数 |
|---|---|---|
| check | 点击复选框触发 | function(checkedKeys, e: { checked: bool, checkedNodes, node, event }) |
| dragend | dragend 触发时调用 | function({ event, node }) |
| dragenter | dragenter 触发时调用 | function({ event, node, expandedKeys }) |
| dragleave | dragleave 触发时调用 | function({ event, node }) |
| dragover | dragover 触发时调用 | function({ event, node }) |
| dragstart | 开始拖拽时调用 | function({ event, node }) |
| drop | drop 触发时调用 | 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.tsx 的实现可以看到,check、expand、select三个核心事件在发出时会同步派发对应的update:xxxKeys更新事件,这正是v-model:checkedKeys/v-model:expandedKeys/v-model:selectedKeys双向绑定得以工作的底层机制。
底层事件签名(含类型化参数)定义在 components/vc-tree/props.ts,例如drop的完整参数还包括dropPosition(-1/0/1 分别表示放置在上方/内部/下方)与dropToGap,这在拖拽重排数据时非常关键(详见下文拖拽示例)。
复选框勾选的父子关联原理
checkedKeys的"父子联动"并非简单约定,而是由底层算法fillConductCheck保证的(components/vc-tree/utils/conductUtil.ts):
- 自上而下传导:父节点被勾选时,所有未禁用的子节点 key 被递归加入
checkedKeys; - 自下而上回填:从叶子层向上,若某父节点的子节点全部被勾选,则父节点置为勾选;若仅部分勾选,父节点进入
halfCheckedKeys(半选状态); - 禁用边界:
disabled、disableCheckbox或checkable === false的节点(isCheckDisabled判定)不参与传导,既不会被自动勾选,也不会被自动回填。
当设置checkStrictly后该传导被关闭,此时checkedKeys需传入{ checked, halfChecked }对象形式以区分勾选与半选。
Tree 方法:scrollTo
| 名称 | 说明 |
|---|---|
| scrollTo({ key: string | number; align?: 'top' | 'bottom' | 'auto'; offset?: number }) | 虚拟滚动下,滚动到指定 key 条目 |
方法通过组件 ref 暴露,Tree.tsx 中expose了scrollTo以及selectedKeys、checkedKeys、halfCheckedKeys、loadedKeys、loadingKeys、expandedKeys等计算属性,方便父组件在受控场景下读取最新状态:
<script lang="ts" setup> import { ref } from 'vue'; const treeRef = ref(); // 在虚拟滚动树中定位到某个节点 const jumpTo = () => { treeRef.value?.scrollTo({ key: '0-0-0-1', align: 'top', offset: 0 }); }; </script> <template> <a-tree ref="treeRef" :height="233" :tree-data="treeData" /> </template>TreeNode:节点级配置
当你选择以子组件方式构造节点(旧式写法)而非使用treeData时,TreeNode提供以下节点级配置(官方文档表格):
| 参数 | 说明 | 类型 | 默认值 | 版本 |
|---|---|---|---|---|
| checkable | 当树为 checkable 时,设置独立节点是否展示 Checkbox | boolean | - | |
| class | 节点的 class | string | - | |
| disableCheckbox | 禁掉 checkbox | boolean | false | |
| disabled | 禁掉响应 | boolean | false | |
| icon | 自定义图标,可接收组件,props 为当前节点 props | slot | slot-scope | - | |
| isLeaf | 设置为叶子节点(设置了loadData时有效) | boolean | - | |
| key | 被树的 (default)ExpandedKeys / (default)CheckedKeys / (default)SelectedKeys 属性所用。注意:整个树范围内所有节点的 key 值不能重复! | string | number | 内部计算出的节点位置 | |
| selectable | 设置节点是否可被选中 | boolean | true | |
| style | 节点的 style | string | object | - | |
| title | 标题 | string | '---' |
节点级属性同样可以直接写在treeData的节点对象上(BasicDataNode定义见 components/vc-tree/interface.tsx),因此在数据驱动写法下,每个节点对象都可以声明自己的disabled、disableCheckbox、isLeaf、selectable、checkable等属性。
DirectoryTree:内置目录树
DirectoryTree是 Tree 的场景化封装,专为"文件资源管理器"式的目录浏览设计,在 index.tsx 中随Tree、TreeNode一并导出并注册为全局组件。
DirectoryTree props
| 参数 | 说明 | 类型 | 默认值 |
|---|---|---|---|
| expandAction | 目录展开逻辑,可选false、'click'、'dblclick' | string | click |
与普通 Tree 的区别(源码佐证)
- 默认展示文件夹图标:源码默认
showIcon: true(DirectoryTree.tsx),并通过getIcon按节点状态渲染图标——叶子节点为FileOutlined,文件夹展开为FolderOpenOutlined、收起为FolderOutlined(DirectoryTree.tsx); - 默认整行块级展示:
blockNode强制为true; - 点击/双击展开:
expandAction: 'click'时点击文件夹即触发展开,内部通过 200ms 防抖(debounce(expandFolderNode, 200, { leading: true }))调用底层onNodeExpand(DirectoryTree.tsx),并在 shift/ctrl/meta 组合键按下时跳过展开以保留多选手势; - 原生多选体验:
multiple模式下支持ctrl(Windows) /command(Mac) 点选追加、shift范围连选。范围选中的 key 集合由 utils/dictUtil.ts 中的calcRangeKeys基于展开态节点计算(只考虑已展开节点以优化性能),选中的节点列表则经convertDirectoryKeysToNodes还原。
参考示例 demo/directory.vue:
<a-directory-tree v-model:expandedKeys="expandedKeys" v-model:selectedKeys="selectedKeys" multiple :tree-data="treeData" />实战场景一:异步加载(loadData)
点击展开节点时才向后端请求子级数据,适用于大型组织架构、动态目录等场景。参考 demo/dynamic.vue:
<a-tree v-model:expandedKeys="expandedKeys" v-model:selectedKeys="selectedKeys" :load-data="onLoadData" :tree-data="treeData" /> <script lang="ts" setup> const treeData = ref<TreeProps['treeData']>([ { title: 'Expand to load', key: '0' }, { title: 'Expand to load', key: '1' }, { title: 'Tree Node', key: '2', isLeaf: true }, // 标记为叶子,不触发加载 ]); const onLoadData: TreeProps['loadData'] = treeNode => { return new Promise<void>(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>要点:
loadData必须返回 Promise,加载完成后通过替换treeData引用(treeData.value = [...treeData.value])触发重渲染;- 不希望展开时请求的节点,用
isLeaf: true标记为叶子; - 若需精确控制"已加载"状态,可配合受控的
loadedKeys;加载过程 Tree 会自动展示 loading 图标(见 iconUtil.tsx,loading 状态下渲染LoadingOutlined)。
实战场景二:搜索高亮
可搜索树是后台管理系统的标配。核心思路:监听搜索词 → 计算命中节点的所有祖先 key→ 更新expandedKeys并配合autoExpandParent自动展开父级;标题命中部分用#title插槽高亮。参考 demo/search.vue:
<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> <script lang="ts" setup> watch(searchValue, value => { // 遍历 dataList 找到所有命中节点,向上回溯其父级 key 并去重 const expanded = dataList .map(item => (item.title.indexOf(value) > -1 ? getParentKey(item.key, gData.value) : null)) .filter((item, i, self) => item && self.indexOf(item) === i); expandedKeys.value = expanded; autoExpandParent.value = true; // 重新展开父节点 }); </script>搜索时需自行实现getParentKey递归回溯父级(demo 中给出了完整实现),并在onExpand中将autoExpandParent置回false,避免用户手动收起节点后又被强制展开。
实战场景三:拖拽排序(draggable)
设置draggable后节点即可被拖拽到其他节点内部或前后。Tree 提供完整的dragstart/dragenter/dragover/dragleave/drop/dragend事件链,但数据重排需要你自行完成——组件只负责交互与回调,不直接改写treeData。
参考 demo/draggable.vue 的drop处理核心逻辑:
const onDrop = (info: AntTreeNodeDropEvent) => { 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]); // dropPosition: -1 放置到目标节点前,1 放置到目标节点后,0 放入内部 // 1. 从 treeData 中摘除被拖拽节点 // 2. 按 dropToGap / dropPosition / 目标节点展开状态判断插入位置 // 3. 重新赋值 gData.value 触发更新 };关键判定逻辑:
info.dropToGap === false:放入目标节点内部(作为其子级);dropToGap === true且目标节点有子级、已展开、dropPosition === 1:追加为目标节点的最后一个子级;- 其余情况按
dropPosition === -1(插入到目标节点前)或1(插入到目标节点后)处理。
若需要对放置位置做精细化限制,可通过allowDrop: ({ dropNode, dropPosition }) => boolean实现(底层类型AllowDropOptions见 components/vc-tree/props.ts)。
实战场景四:虚拟滚动(virtual / height)
当节点数量达到数千级时,默认开启的虚拟滚动(virtual: true)可保证流畅滚动。参考 demo/virtual-scroll.vue:
<a-tree v-model:selectedKeys="selectedKeys" v-model:checkedKeys="checkedKeys" default-expand-all checkable :height="233" <!-- 设置高度即切换为虚拟滚动 --> :tree-data="dig()" <!-- 递归生成 10×10×10 规模数据 --> />注意文档中的两点约束:
- 设置
height后虚拟列表被激活,内部节点不再支持横向滚动(横向内容将被裁剪); - 若业务上不需要虚拟化,可显式
:virtual="false"关闭。
配合scrollTo方法,可在虚拟滚动树中实现"定位到某个节点"的导航能力。
实战场景五:连接线、自定义图标与右键菜单
showLine 连接线
文件目录场景常用showLine开启节点间的连接线(示例 demo/line.vue)。它接受boolean或{ showLeafIcon: boolean }(3.0+)。连接线模式下展开/收起图标默认切换为MinusSquareOutlined/PlusSquareOutlined,叶子节点默认渲染FileOutlined,均可在 utils/iconUtil.tsx 中看到完整分支。
switcherIcon 自定义展开/折叠图标
通过#switcherIcon插槽接管展开/收起图标,插槽参数中的switcherCls用于保持图标与默认样式对齐,defaultIcon为默认图标可作兜底(示例 demo/switcher-icon.vue):
<a-tree v-model:expandedKeys="expandedKeys" show-line :tree-data="treeData"> <template #switcherIcon="{ switcherCls }"> <down-outlined :class="switcherCls" /> </template> </a-tree>当switcherIcon传入函数/插槽时,底层会注入{ ...nodeProps, defaultIcon, switcherCls }上下文(iconUtil.tsx),因此你可以针对不同节点状态(展开、选中、加载中等)渲染不同图标。
自定义节点图标(icon / showIcon)
showIcon开启 title 前图标位,配合#icon插槽按节点 key 或选中状态定制图标(示例 demo/customized-icon.vue)。注意官方文档提示:showIcon只负责展示,没有默认样式,开启后需要自行定义图标相关样式。
rightClick 右键菜单
用@rightClick拿到{ event, node },结合a-dropdown的contextmenu触发方式即可实现节点级右键菜单(示例 demo/context-menu.vue):
<a-tree v-model:expandedKeys="expandedKeys" :tree-data="treeData"> <template #title="{ key: treeKey, title }"> <a-dropdown :trigger="['contextmenu']"> <span>{{ title }}</span> <template #overlay> <a-menu @click="({ key: menuKey }) => onContextMenuClick(treeKey, menuKey)"> <a-menu-item key="1">1st menu item</a-menu-item> <a-menu-item key="2">2nd menu item</a-menu-item> </a-menu> </template> </a-dropdown> </template> </a-tree>FAQ:showLine 时如何隐藏子节点图标?
官方 FAQ 明确说明:连接线模式下出现在子节点前的"图标"实际上是通过switcherIcon渲染出来的(叶子节点默认FileOutlined)。若不需要该图标,可通过覆盖对应样式类隐藏,例如:
/* 隐藏 showLine 模式下叶子节点的文件图标 */ .ant-tree-show-line .ant-tree-switcher-leaf-line { display: none; }从 iconUtil.tsx 的实现看,叶子节点在showLine且showLeafIcon为 false(showLine传对象形式)时,渲染的是-switcher-leaf-line这个纯样式 span(连接线的占位),否则渲染FileOutlined(-switcher-line-icon)。因此更精确的做法是按需配置:
<!-- 连接线保留、但叶子节点不展示文件图标 --> <a-tree :show-line="{ showLeafIcon: false }" :tree-data="treeData" />源码结构导览
若想深入 Tree 的实现,建议按以下路径阅读:
- components/tree/Tree.tsx:
ATree组件主体,props 定义、默认值、事件转发、expose方法、样式注入(CSS-in-JS); - components/tree/DirectoryTree.tsx:目录树封装,图标逻辑、expandAction、shift/ctrl 多选;
- components/tree/index.tsx:导出
Tree、TreeNode、DirectoryTree及类型,并提供app.use()全局注册; - components/vc-tree/props.ts:底层树全部 props 与事件签名(与 ant-design 的 rc-tree 对齐);
- components/vc-tree/utils/conductUtil.ts:勾选父子传导算法(
fillConductCheck); - components/vc-tree/utils/treeUtil.ts:
treeData与实体关系(keyEntities)转换、fieldNames归一化; - components/tree/utils/iconUtil.tsx:展开/折叠/叶子图标渲染规则;
- components/tree/utils/dictUtil.ts:目录树范围选择(shift 连选)计算;
- components/tree/demo:13 个官方示例(basic、dynamic、search、draggable、directory、line、virtual-scroll、context-menu、switcher-icon、replaceFields、customized-icon 等),是最佳上手素材;
- components/tree/tests/demo.test.js:通过
demoTest('tree')对全部 demo 做渲染冒烟测试,可作为组件行为的回归验证参考。
结语
ant-design-vue 的 Tree 是一个"开箱即用、深可定制"的树形组件:数据驱动的treeData+fieldNames让它能无缝对接任意后端数据结构;v-model受控三件套(checked/selected/expanded)与完整事件体系覆盖绝大多数交互诉求;DirectoryTree、异步加载、虚拟滚动、拖拽与右键菜单等能力则支撑起文件管理、组织架构、权限树等真实业务场景。理解其底层"勾选传导算法"与"受控事件转发"机制后,即使遇到高度定制化的需求,也能基于源码从容扩展。
【免费下载链接】ant-design-vue🌈 An enterprise-class UI components based on Ant Design and Vue. 🐜项目地址: https://gitcode.com/gh_mirrors/an/ant-design-vue
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考