树形结构三级分类列表
使用vue3加antd-design实现树形控件三级分类主要内容包括使用a-tree组件构建树形结构支持三级分类展示实现分类的增删改查功能通过右键菜单提供创建、编辑、删除操作使用模态框进行表单交互添加搜索功能可过滤分类并自动展开匹配节点响应式设计动态计算分类层级实时更新树形数据自动展开/收起节点template div classcbox div styledisplay:flex;align-items:center;justify-content: space-between span stylefont-size: 15px;创建分类/span PlusOutlined clickopenCreateModal(null, 1) stylecursor: pointer / /div div a-input-search v-model:valuesearchValue allowClear stylewidth: 100%; margin: 10px 0px placeholder搜索分类 inputonSearch / /div !-- 使用 Ant Design Tree 组件 -- a-tree :tree-datafilteredTreeData :expanded-keysexpandedKeys :selected-keysselectedKeys expandonExpand selectonSelect block-node template #title{ node } div classtree-node-title span{{ node.title }}/span a-dropdown :trigger[click] placementbottomRight MoreOutlined classnode-actions-icon click.stop / template #overlay a-menu click({ key }) handleMenuAction(key, node) a-menu-item keycreate v-ifnode.level 3创建{{ getSubLevelName(node.level) }}分类/a-menu-item a-menu-item keyedit编辑/a-menu-item a-menu-item keydelete删除/a-menu-item /a-menu /template /a-dropdown /div /template /a-tree !-- 创建/编辑弹窗 -- a-modal v-model:visiblemodalVisible :titlemodalTitle okhandleModalOk cancelhandleModalCancel a-form layoutvertical a-form-item label分类名称 required a-input v-model:valueformData.name placeholder请输入分类名称 / /a-form-item /a-form /a-modal /div /template script langts setup import { ref, computed } from vue; import { PlusOutlined, MoreOutlined } from ant-design/icons-vue; import { message, TreeProps } from ant-design-vue; interface TreeNode { key: string; title: string; level: number; parentKey?: string; children?: TreeNode[]; scopedSlots?: { title: string }; } const treeData refTreeNode[]([ { key: 1, title: 全部分类 (13), level: 1, scopedSlots: { title: title }, children: [ { key: 1-1, title: 未分组 (13), level: 2, parentKey: 1, scopedSlots: { title: title }, }, { key: 1-2, title: 分类1 (0), level: 2, parentKey: 1, scopedSlots: { title: title }, children: [ { key: 1-2-1, title: 子分类1 (0), level: 3, parentKey: 1-2, scopedSlots: { title: title }, } ] } ] } ]); const searchValue ref(); const expandedKeys refstring[]([1]); const selectedKeys refstring[]([]); const modalVisible ref(false); const modalTitle ref(); const formData ref({ name: }); const currentEditNode refTreeNode | null(null); const currentParentNode refTreeNode | null(null); const createLevel ref(1); const getSubLevelName (level: number) { if (level 1) return 二级; if (level 2) return 三级; return ; }; const handleMenuAction (action: string, node: TreeNode) { if (action create) { openCreateModal(node, node.level 1); } else if (action edit) { openEditModal(node); } else if (action delete) { handleDelete(node); } }; const openCreateModal (parentNode: TreeNode | null, level: number) { if (level 3) { message.warning(最多支持三级分类); return; } currentParentNode.value parentNode; currentEditNode.value null; createLevel.value level; modalTitle.value 创建${level 2 ? 二级 : level 3 ? 三级 : }分类; formData.value.name ; modalVisible.value true; }; const openEditModal (node: TreeNode) { currentEditNode.value node; modalTitle.value 编辑分类; formData.value.name node.title.replace(/\(\d\)$/, ).trim(); modalVisible.value true; }; const handleDelete (node: TreeNode) { const deleteNode (nodes: TreeNode[], key: string): boolean { for (let i 0; i nodes.length; i) { if (nodes[i].key key) { nodes.splice(i, 1); return true; } if (nodes[i].children deleteNode(nodes[i].children, key)) { return true; } } return false; }; const newData [...treeData.value]; deleteNode(newData, node.key); treeData.value newData; message.success(删除成功); }; const handleModalOk () { if (!formData.value.name.trim()) { message.warning(请输入分类名称); return; } if (currentEditNode.value) { // 编辑逻辑 const updateNode (nodes: TreeNode[]): boolean { for (let i 0; i nodes.length; i) { if (nodes[i].key currentEditNode.value!.key) { const countMatch nodes[i].title.match(/\((\d)\)$/); nodes[i].title countMatch ? ${formData.value.name} (${countMatch[1]}) : formData.value.name; return true; } if (nodes[i].children updateNode(nodes[i].children)) { return true; } } return false; }; const newData [...treeData.value]; updateNode(newData); treeData.value newData; message.success(编辑成功); } else { // 创建逻辑 const newNode: TreeNode { key: key_${Date.now()}_${Math.random()}, title: ${formData.value.name} (0), level: createLevel.value, scopedSlots: { title: title }, children: createLevel.value 3 ? undefined : [], }; if (!currentParentNode.value) { // 根节点创建 treeData.value [...treeData.value, newNode]; } else { // 子节点创建 const addToParent (nodes: TreeNode[]): boolean { for (let i 0; i nodes.length; i) { if (nodes[i].key currentParentNode.value!.key) { if (!nodes[i].children) nodes[i].children []; nodes[i].children!.push(newNode); // 自动展开父节点 if (!expandedKeys.value.includes(nodes[i].key)) { expandedKeys.value.push(nodes[i].key); } return true; } if (nodes[i].children addToParent(nodes[i].children)) { return true; } } return false; }; const newData [...treeData.value]; addToParent(newData); treeData.value newData; } message.success(创建成功); } modalVisible.value false; }; const handleModalCancel () { modalVisible.value false; }; const onExpand (keys: string[]) { expandedKeys.value keys; }; const onSelect (keys: string[]) { selectedKeys.value keys; }; // 搜索过滤 const onSearch () { if (!searchValue.value) { expandedKeys.value [1]; return; } // 展开所有匹配的节点路径 const getAllParentKeys (nodes: TreeNode[], keyword: string): string[] { let keys: string[] []; for (const node of nodes) { if (node.title.toLowerCase().includes(keyword.toLowerCase())) { keys.push(node.key); let parent findParentNode(treeData.value, node.key); while (parent) { keys.push(parent.key); parent findParentNode(treeData.value, parent.key); } } if (node.children) { keys [...keys, ...getAllParentKeys(node.children, keyword)]; } } return [...new Set(keys)]; }; const findParentNode (nodes: TreeNode[], childKey: string): TreeNode | null { for (const node of nodes) { if (node.children?.some(child child.key childKey)) { return node; } if (node.children) { const found findParentNode(node.children, childKey); if (found) return found; } } return null; }; if (searchValue.value) { expandedKeys.value getAllParentKeys(treeData.value, searchValue.value); } }; const filteredTreeData computed(() { if (!searchValue.value) return treeData.value; const filterNodes (nodes: TreeNode[]): TreeNode[] { return nodes.reduceTreeNode[]((acc, node) { const matches node.title.toLowerCase().includes(searchValue.value.toLowerCase()); let filteredChildren: TreeNode[] []; if (node.children) { filteredChildren filterNodes(node.children); } if (matches || filteredChildren.length 0) { acc.push({ ...node, children: filteredChildren.length 0 ? filteredChildren : node.children, }); } return acc; }, []); }; return filterNodes(treeData.value); }); /script style scoped langscss .cbox { padding: 16px; background: #fff; border-radius: 8px; } .tree-node-title { display: flex; align-items: center; justify-content: space-between; width: 100%; padding-right: 8px; .node-actions-icon { opacity: 0; transition: opacity 0.2s; cursor: pointer; font-size: 14px; color: #999; :hover { color: #1890ff; } } :hover .node-actions-icon { opacity: 1; } } :deep(.ant-tree-node-content-wrapper) { width: 100%; } /style