前前后后各种管理系统做了不下十几套,我发现只要业务稍微沾点组织架构,就跑不掉一个需求:部门树。
不管是做人员归属、数据权限范围、审批流里的部门选择,还是单纯的行政组织架构展示,最后都会被产品经理指着一张原型图说——“就做成那种左边一棵树、右边列表的经典布局”。所以当我看到Ease UI在2026-04-08这版更新里把xly-dept-tree部门树组件从业务代码中提炼成通用组件时,我心里其实挺有感触的。因为这种组件太容易被低估了,表面上看不就是el-tree套个皮吗?实际做进去才知道,轮子自己在项目里造和在组件库里沉淀成标准件,完全是两码事。
这篇文章就想跟你聊聊这次新增的xly-dept-tree到底解决了我平时开发中的哪些问题,它的核心设计思路是什么,以及我实际接入时的一些配置过程和踩坑心得。如果你所在的团队也在自研组件库,或者正头疼怎么把散落在各个业务项目里的“部门树”抽成统一组件,这篇内容应该能给你一些参考。
1. 为什么需要一个独立的部门树组件
1.1 部门树在业务系统中的高频场景
先说说部门树这东西在真实系统里到底有多高频。我粗略回想了一下手头这几个项目,凡是涉及内部管理系统,十个里有八个组织架构是硬需求。最常见的两种形态:一种是组织管理页面,左边整棵部门树,右边是部门详情或成员列表;另一种是各类弹窗里选部门,比如新建用户时指定所属部门、发起审批时选择审批部门、分配数据权限时勾选可见部门范围。
这两种形态的诉求其实还有差别。管理页面讲究的是树的结构完整、状态同步、折叠记忆;弹窗选部门则更看重搜索定位、父子级勾选策略、单选多选切换。如果这些场景每次都从头写一遍,代码量其实是小事,真正麻烦的是各写各的,有的用递归组件实现、有的拿第三方库里现成的树改造、还有的直接从后端拿组织结构图在前端硬拼,等做到第四个系统时你就会发现,同样的一个“展开-选择-回填”逻辑,在三个系统里长得完全不一样。
Ease UI这次把xly-dept-tree做成独立组件,本质上就是在替大家认领这个重复劳动。它把部门树相关的交互细化、接口约定、数据格式都统一收口,业务侧只需要关心数据从哪来、选中结果怎么用,剩下的树形展示、节点操作、搜索过滤、状态回显这些通用能力,组件内部直接消化掉。
1.2 直接使用通用Tree组件和部门树专组件的差距
可能有人会问,组件库里本来就有通用的Tree,为什么还要再包一层?这其实就是通用组件和领域组件之间的经典权衡。通用Tree确实什么都能做,但恰恰因为什么都能做,它天然不会为了组织架构这个特定场景做太多内置约束。
举个例子,通用Tree的节点数据通常就是label、children这类抽象字段,但部门树的数据一般长这样:deptId、deptName、parentId、sortNo、status。如果直接用通用Tree,每次接接口我都得写一层mapper,状态过滤、排序、空节点清理也得业务侧自己循环处理,这些代码零散且重复。而xly-dept-tree直接把部门数据字段约定好,甚至支持字段名映射,接后端接口时少写很多胶水代码。
另一个差距在交互层面。部门树的搜索定位和普通树搜索是不一样的,部门名往往有层级前缀,比如“技术中心/前端组”和“技术中心/后台组”这种,同时按部门状态过滤(启用、停用)也是业务里很常见的诉求。通用Tree不会默认帮你考虑这些,而这恰恰是xly-dept-tree这类领域组件的价值所在。
1.3 这次更新重点解决的几个历史痛点
从我使用Ease UI的版本节奏来看,之前的Tree组件在部门场景下有几个比较头疼的问题。其一,虚拟滚动和树节点的展开动画一直配合得不太好,当部门数据量上到几千个节点时,全量渲染明显卡顿;其二,勾选父子联动时缺少“只选叶子节点”和“取消关联勾选”这类业务常用的细化策略;其三,弹窗场景下回显部门名称时,往往需要额外调接口拼接口,没有统一的数据回显方案。
这次xly-dept-tree的发布说明里,组件对这几个方向都做了专门优化。它把大数据量下的渲染优化作为基础能力内置,而不是留给使用方自己去做虚拟滚动适配。同时对勾选模式、搜索过滤、远程数据加载、回显逻辑都做了部门业务场景下的默认约定。说白了,它不是一个简单包了层预设样式的el-tree,而是真正从组织架构业务中提炼出来的专用组件。
2. 核心特性与设计思路拆解
2.1 简洁的数据约定与灵活字段映射
先看数据层面。xly-dept-tree约定的基础数据结构是:
// 标准结构示例 const deptTree = [ { id: 'dept-root', name: '总公司', parentId: null, sortNo: 1, status: 'active', children: [ { id: 'dept-tech', name: '技术中心', parentId: 'dept-root', sortNo: 11, status: 'active', children: [ { id: 'dept-fe', name: '前端组', parentId: 'dept-tech', sortNo: 111, status: 'active' } ] } ] } ]如果你后端用的字段名不是这套,比如你拿到的是departmentId、departmentName,也没关系,组件提供了props字段映射,可以像Element的tree-props一样配置别名:
<xly-dept-tree :data="deptData" :props="{ label: 'deptName', children: 'childNodes', id: 'departmentId' }" />这种设计实际上是在可预测性和灵活度之间取了一个中间值。一方面组件内部能按约定好的字段快速处理排序、过滤、状态判断等逻辑;另一方面不同后端返回的字段各异的现实也留了后门。集成成本低,又不至于让你为了用个组件还得先改后端接口。
2.2 勾选策略与状态回显的精细控制
部门树在选人的场景里,勾选逻辑是重头戏。常见的情况:新增用户时只能选末级部门不能选父级;分配数据权限时要能勾选父级并联动所有子级;还有些场景要求父子完全独立勾选。这三种策略xly-dept-tree都支持,通过check-strictly和only-leaf-checkable两个属性组合实现。
这四个模式基本覆盖了我在真实项目里的所有勾选诉求:
| 业务场景 | check-strictly | only-leaf-checkable | 效果 |
|---|---|---|---|
| 用户归属部门 | false | true | 只能勾选叶子部门,父级自动半选 |
| 权限范围分配 | false | false | 勾选父级联动子级,符合权限继承 |
| 独立角色菜单 | true | false | 父子互不影响,按需勾选 |
| 跨部门临时访问 | true | true | 仅叶子可选,且互不关联 |
回显方面,组件接收v-model绑定选中节点的id数组,同时提供了一个expose方法getCheckedNodes用来获取完整的节点数据。这个设计我很喜欢,因为很多场景下我不但要拿到id提交给后端,还要拿部门名称做展示,只绑一个id数组往往不够。组件内置了从id反查节点信息的逻辑,回显名称时不用再单独拼接口。
2.3 树节点的高性能渲染与懒加载设计
部门树数据量大是常态。尤其是那种集团型公司,几千个部门、五六层的层级关系很常见。把整棵树一次性展开渲染,DOM节点数量开销极大。
xly-dept-tree这次采用了按需渲染策略。默认只渲染当前展开节点对应的直接子级,节点展开时才去渲染下一层,配合内部维护的渲染状态树,解决了全量展开时的性能问题。实测中,面对单层超过500个兄弟节点的数据,展开和折叠操作能保持在流畅体验范围内。
懒加载也是配套提供的。如果你的部门数据量实在太大,或者接口设计上就是父子级分开查询的,可以不开data,改用:load函数在节点展开时动态加载子级:
<xly-dept-tree lazy :load="loadDeptChildren" :props="{ label: 'name', isLeaf: 'leaf' }" />async function loadDeptChildren(node, resolve) { const parentId = node.data?.id ?? 0 const res = await fetchDeptByParent(parentId) resolve(formatDeptList(res.data)) }懒加载模式下,每个节点首次展开前才触发对应父级的数据请求,性能天然可控。而且组件内部对这个过程做了缓存处理,同一个节点二次展开不会重复发请求,这个细节对用户体验来说非常重要。
2.4 多种选择模式适配业务弹窗
之前的树组件默认都是单选或复选二选一,但真实业务中部门选择经常面临左右横跳的情况。比如你做一个用户编辑弹窗,如果系统规定一个用户只能归属一个部门,那这里是单选;如果允许多部门兼职,那这里又变成多选。为了这两个场景,业务侧往往维护着两套不同写法的Tree用法。
xly-dept-tree把这种差异收敛成一个multiple属性。默认false时是单选,点击节点直接选中并触发确认;设置multiple后才展示复选勾选框。单选模式下还有一个很好的细节,父节点选中后不会自动联动子级,选中的就是当前这个部门,这个逻辑更贴合“人员归属部门”这类实际业务语义。
3. 实际接入与基础配置流程
3.1 安装和快速接入
接入第一步比较常规,在项目根目录安装最新版本:
npm install ease-ui@latest如果你用的是Vue 3 + Vite项目,一般在入口文件中全量注册或者按需引入:
// main.js 全量引入示例 import { createApp } from 'vue' import EaseUI from 'ease-ui' import 'ease-ui/dist/index.css' import App from './App.vue' createApp(App).use(EaseUI).mount('#app')Ease UI也支持unplugin-vue-components之类的按需自动导入,这个看团队基建情况自行配置。组件更新后记得在package.json里确认版本号已经指向包含xly-dept-tree的版本。
3.2 基础场景三步跑通
用一个最简场景帮你测试组件是否正常:接口请求部门列表,渲染成树,勾选后弹出选中id。
第一步,在页面中引入组件:
<template> <div class="dept-select-page"> <xly-dept-tree v-model="selectedDeptIds" :data="deptList" node-key="id" :default-expanded-keys="expandedKeys" show-checkbox /> <p>当前选中ID:{{ selectedDeptIds }}</p> </div> </template>第二步,在script setup中定义数据:
import { ref, onMounted } from 'vue' const selectedDeptIds = ref([]) const deptList = ref([]) const expandedKeys = ref([]) async function fetchDeptTree() { const res = await fetch('/api/dept/tree') deptList.value = res.data // 默认展开第一层 expandedKeys.value = res.data.map(item => item.id).slice(0, 1) } onMounted(fetchDeptTree)第三步,后端返回的数据经过简单的字段校准,保证包含id、name、children等基础字段,树就能正常渲染。
这个流程跑通后,组件的基础工作就算正常。实际开发中我建议先在这个三层结构上加一个接口loading状态,尤其当部门数据接口比较慢时,组件的空数据状态和加载状态会让页面稳定很多。
3.3 基本配置项全览与推荐值
这次更新的xly-dept-tree在Props设计上基本和Ease UI其他表单类组件保持一致,上手成本不高。配置项主要有这几类:
| 分类 | 属性名 | 类型 | 默认值 | 建议 |
|---|---|---|---|---|
| 数据 | data / lazy / load | Array / Boolean / Function | - | 数据量小用data,超过1000节点建议lazy |
| 展示 | show-checkbox / multiple | Boolean | false | 单选场景务必关掉复选 |
| 节点 | node-key / props | String / Object | id | 键名以后端稳定字段为准 |
| 过滤 | filterable / filter-method | Boolean / Function | false | 弹窗选部门强烈建议开启 |
| 状态 | default-expand-all / expanded-keys | Boolean / Array | false | 层级深的树不建议全展开 |
| 勾选 | check-strictly / only-leaf-checkable | Boolean | false | 根据业务语义精细控制 |
| 数据回显 | model-value / v-model | Array | - | 传id数组即可 |
这些配置整体上倾向于让使用方少写代码。比如默认的勾选关联模式就是父子联动,但同时又提供了关闭联动的开关,不同业务的差异化配置都能覆盖到。
3.4 表单场景的完整接入模板
表单里用部门树,核心需求有三个:弹窗内展示树、选择后回填、再次打开时正确回显。这里我贴一个在实际项目中改动后稳定使用的模板。
<template> <el-dialog v-model="visible" title="选择部门" width="560px" > <xly-dept-tree ref="deptTreeRef" v-model="deptIdList" :data="deptTreeData" node-key="id" show-checkbox filterable :only-leaf-checkable="true" :default-expanded-keys="defaultExpandList" @check="handleDeptCheck" /> <template #footer> <el-button @click="visible = false">取消</el-button> <el-button type="primary" @click="handleSubmit"> 确定 </el-button> </template> </el-dialog> </template>const visible = ref(false) const deptTreeRef = ref(null) const deptIdList = ref([]) function openDialog(initIds = []) { deptIdList.value = initIds ? [...initIds] : [] visible.value = true // 等树渲染完成后,主动回显勾选 nextTick(() => { deptTreeRef.value?.setCheckedKeys(deptIdList.value) }) } function handleSubmit() { // 提交时直接提交id数组,同时通过getCheckedNodes补充名称字段 const checkedNodes = deptTreeRef.value.getCheckedNodes() const submitPayload = checkedNodes.map(node => ({ id: node.id, name: node.name })) console.log('提交的数据:', submitPayload) visible.value = false }这里有两个容易踩的坑。一个是v-model和setCheckedKeys的同步时序问题,首次打开弹窗时树可能还没渲染完就调用了setCheckedKeys,导致回显失败,所以一定要放在nextTick里执行。另一个是半选问题,如果提交时只用getCheckedKeys,父级半选节点也会被带出来,而很多后端接口不接受这种父级id,所以补一个only-leaf-checkable或者提交前自己过滤一次叶子节点。
4. 完整实操:从接口到页面一次通
4.1 模拟一套真实的部门数据接口
为了演示完整流程,我自己模拟一个比较常见的后端返回结构:
{ "code": 0, "data": { "list": [ { "deptId": 1, "deptName": "苍穹科技", "parentId": 0, "orderNum": 1, "status": 0 }, { "deptId": 2, "deptName": "研发中心", "parentId": 1, "orderNum": 1, "status": 0 }, { "deptId": 3, "deptName": "前端组", "parentId": 2, "orderNum": 1, "status": 0 }, { "deptId": 4, "deptName": "后端组", "parentId": 2, "orderNum": 2, "status": 0 }, { "deptId": 5, "deptName": "测试组", "parentId": 2, "orderNum": 3, "status": 0 }, { "deptId": 6, "deptName": "产品部", "parentId": 1, "orderNum": 2, "status": 0 }, { "deptId": 7, "deptName": "已停用部门", "parentId": 1, "orderNum": 3, "status": 1 } ] } }这里我特意让字段名不是组件默认值,来演示字段映射的配置提交流程。
4.2 前端列表转树的两种常见姿势
拿到扁平列表后,常规做法是前端把它转成树形结构。转换函数网上有很多版本,我自己常用这个:
function listToTree(list, rootParentId = 0) { const map = new Map() list.forEach(item => { map.set(item.deptId, { ...item, children: [] }) }) const tree = [] map.forEach(node => { const parentId = node.parentId ?? rootParentId if (parentId === rootParentId || !map.has(parentId)) { tree.push(node) } else { const parentNode = map.get(parentId) parentNode.children.push(node) } }) // 按orderNum排序 const sortTree = (nodes) => { nodes.sort((a, b) => a.orderNum - b.orderNum) nodes.forEach(n => n.children?.length && sortTree(n.children)) } sortTree(tree) return tree }如果你觉得前端转换太占内存,也可以让后端直接返回树形结构,这个视接口能力而定。
4.3 页面集成完整代码演示
数据准备好之后,组件的使用就非常纯粹了。完整集成代码如下:
<template> <div class="dept-manage-container"> <div class="dept-tree-panel"> <xly-dept-tree v-model="selectedIdList" :data="treeData" node-key="deptId" :props="treeProps" show-checkbox check-strictly default-expand-all @node-click="handleNodeClick" /> </div> <div class="selected-info"> 选中部门ID:{{ selectedIdList }} </div> </div> </template> <script setup> import { ref, onMounted } from 'vue' const treeProps = { id: 'deptId', label: 'deptName', children: 'children' } const treeData = ref([]) const selectedIdList = ref([]) onMounted(async () => { const res = await fetch('/api/dept/list').then(r => r.json()) const list = res.data.list treeData.value = listToTree(list) }) function handleNodeClick(data) { console.log('点击了节点', data) } </script>这里我把check-strictly设为true,因为示例业务是给一个角色分配可见部门,父级和子级可以独立勾选,互不干扰,比较贴近权限范围配置的场景。如果业务是选择用户归属部门,这个配置就要改成false并加上only-leaf-checkable。
4.4 搜索过滤:让大树变矮的关键
部门树一大,最头疼的就是定位。xly-dept-tree的过滤功能是组件内部自己实现的,不依赖外部输入框状态:
<xly-dept-tree ref="deptTreeRef" :data="treeData" node-key="deptId" :props="treeProps" filterable :filter-method="filterDeptByName" placeholder="输入部门名称搜索" />function filterDeptByName(keyword, nodeData) { if (!keyword) return true return nodeData.deptName.includes(keyword) }组件内部会在输入关键词后自动展开所有命中的节点路径,并高亮保留匹配节点,同时折叠无关分支。这里有个实现细节值得给Ease UI点赞,过滤后节点的原始展开状态会被缓存,清空关键词后能恢复到过滤前的状态,而不是整个树被重置成初始折叠状态。这个细节在真实使用中非常提升体验。
5. 高级玩法与二次开发建议
5.1 懒加载模式对接真实后端接口
当部门数据超过5000条,或者部门层级深且数据不在同一次接口返回时,全量加载方案就会遇到性能瓶颈。这时lazy模式更合适。
<xly-dept-tree ref="deptTreeRef" lazy :load="loadDeptChildren" :props="lazyProps" placeholder="展开加载子部门" />const lazyProps = { id: 'deptId', label: 'deptName', children: 'children', isLeaf: 'leaf' } async function loadDeptChildren(node, resolve) { const parentId = node.level === 0 ? 0 : node.data.deptId const res = await fetch(`/api/dept/list?parentId=${parentId}`).then(r => r.json()) const list = res.data.list const formatted = list.map(item => ({ ...item, // 通过没有children字段或者leaf字段判断是否为叶子节点 leaf: !item.isParent })) resolve(formatted) }注意resolve一定要被调用,不然节点会一直处于加载状态。同时确定好leaf字段的值,否则组件无法判断叶子节点,会导致所有没有子级的节点也渲染出展开箭头。
5.2 自定义节点内容:加数字角标或操作按钮
树组件不可能满足所有展示需求,比如某个部门下在职人数超过100人,产品要求在部门名旁边显示一个红色的数字角标。xly-dept-tree提供了默认插槽,可以自定义节点内容:
<xly-dept-tree :data="treeData" node-key="deptId" :props="treeProps" > <template #default="{ node, data }"> <span class="custom-node-wrapper"> <span class="dept-name">{{ data.deptName }}</span> <span v-if="data.memberCount > 100" class="dept-hot-tag"> {{ data.memberCount }} </span> </span> </template> </xly-dept-tree>插槽参数里能拿到当前节点对象和原始数据,自由度很高。我之前在项目里就基于这个插槽做了行内操作按钮,实现悬浮某个部门时出现“新增子部门”和“编辑”两个小图标,交互效果很接近甲方经常对标的那种商业管理系统。
5.3 节点状态控制与禁用逻辑
部门树不是所有节点都能随意勾选的。比如某些部门已经停用,或者当前用户对某个部门只有只读权限,这时就需要禁用对应节点的选择能力。组件提供了disabled字段控制:
const treeProps = { label: 'deptName', disabled: (data) => data.status !== 0 || data.readonly }disabled既可以是字符串字段名,也可以是函数,函数形式可以基于节点数据动态判断。这样配合搜索过滤,停用部门能正常看到但不允许选择,从交互源头规避了用户选了不该选的部门导致后续流程报错的问题。
5.4 手写一个部门树弹窗选择器
前面几节把组件能力都过了一遍,这里组合成一个完整的高频业务组件:部门弹窗选择器。
这个组件的核心功能:点击输入框弹出树形选择器,支持搜索,选中部门后回显名称,再次打开能正确勾选。我封装成一个小组件,后续项目中几乎零成本复用。
<template> <el-popover trigger="click" placement="bottom-start" width="360px" > <template #reference> <el-input :model-value="displayNames.join('、')" placeholder="请选择部门" readonly suffix-icon="arrow-down" @clear="handleClear" /> </template> <xly-dept-tree ref="popoverTreeRef" :data="deptTreeData" node-key="deptId" :props="treeProps" show-checkbox filterable only-leaf-checkable @check="handleTreeCheck" /> </el-popover> </template> <script setup> import { ref, computed, watch, nextTick } from 'vue' const props = defineProps({ modelValue: { type: Array, default: () => [] }, deptTreeData: { type: Array, default: () => [] } }) const emit = defineEmits(['update:modelValue']) const popoverTreeRef = ref(null) const checkedNodeMap = ref({}) const displayNames = computed(() => { return Object.values(checkedNodeMap.value).map(n => n.deptName) }) watch(() => props.modelValue, (ids) => { if (!ids || ids.length === 0) return nextTick(() => { popoverTreeRef.value?.setCheckedKeys(ids) }) }, { immediate: true }) function handleTreeCheck(checkedNodes) { const map = {} checkedNodes.forEach(node => { map[node.deptId] = node }) checkedNodeMap.value = map emit('update:modelValue', Object.keys(map)) } </script>这个封装模式在实际业务里很吃香。输入框只读展示名称,弹层负责选择,数据流用v-model打通,业务侧完全不用关心树内部的勾选细节。
6. 常见问题与排查技巧实录
6.1 树渲染出来了但勾选没反应
这个问题多半出在node-key配置上。node-key字段在组件内部是节点唯一标识,如果你页面上的字段是deptId,却忘了在props里映射,组件默认按id找,自然找不到。排查方法很简单,给组件加一个@check事件,看事件回调里拿到的节点数据deptId是否为空。
6.2 懒加载模式下二级节点不显示展开箭头
前面提过,懒加载时判断叶子节点依赖isLeaf字段。如果接口没有返回类似isParent的标记,组件会认为每个节点都有子级,导致叶子节点一直显示展开箭头,点击后请求接口返回空数组,看着像Bug。解法是在load函数里对返回结果做一次判断,没有子级的节点显式加上leaf: true。
6.3 回显时部分节点勾选状态丢失
回显的setCheckedKeys和父子联动勾选之间有一个经典冲突。如果组件开着父子联动,你往setCheckedKeys里塞一个父级id,组件会联动勾选所有子级;但你只塞了部分子级id,父级又是半选状态,看起来就像“有些节点没勾上”。
我的经验是:回显前先明确当前场景的勾选策略。如果业务语义是选择叶子部门,回显时只塞叶子id,同时确保叶子节点在树中已加载完成。如果是懒加载,回显前需要先展开到对应层级,或者使用组件的load预先缓存路径,否则节点没渲染,setCheckedKeys找不到对象,也会静默失败。
6.4 大数据量下搜索仍然卡顿
filterable开启后,组件内部会对整棵树做遍历匹配。部门节点数量超过5000时,每次输入都全量遍历确实会有卡顿感。这不是组件本身性能不行,而是前端字符串匹配天然有这种损耗。
我建议配合节流处理输入事件,同时在数据量极大时,把filter-method改成先查后端。也就是说前端只保留层级结构,搜索时调接口拿匹配结果,再通过setExpandedKeys和setCheckedKeys手动控制展示。这个方案能彻底解决前端大数据量搜索的瓶颈。
7. 版本联动与配套升级建议
7.1 这次更新对现有项目的影响评估
xly-dept-tree作为新增组件,对存量项目是纯增量更新,理论上不会破坏现有页面。但有几个点我建议升级时顺手检查一下。第一,组件库的全局样式是否和业务自定义样式冲突,尤其是弹窗层级和树节点缩进样式;第二,如果业务里已经有大量基于通用Tree实现的部门选择代码,可以保留现状,不必为了新组件强行重构,等对应模块要做迭代时再顺手切换,风险更低。
7.2 从业务代码沉淀到组件库的思考
这次Ease UI把xly-dept-tree做成独立组件,背后其实是一个很典型的组件提炼思路:先从多个业务项目中找出同类结构,抽象出公共数据模型和交互模式,再通过配置项解决差异化诉求。对自研组件库的团队来说,这个决策路径值得参考——不是把所有相似的东西都塞进通用组件,也不是直接把某条业务线的代码原封不动提出来,而是在“够通用”和“够专用”之间找到稳定的组件边界。
拿部门树来说,它和通用Tree的边界就在于:是否默认处理了部门数据格式、是否内置了搜索过滤的部门语义、是否考虑了数据权限场景的勾选模式。越过这条边界,组件就成了一个“半通用半专用”的模糊产物,反而让人无从下手。
7.3 踩过坑之后的最终使用建议
基于我用下来的感受,给你几条实在的建议。
第一,接口数据结构务必在联调前对齐。字段映射虽然能兜底,但反复转换数据结构本身就是在增加复杂度。
第二,弹窗选择场景优先开启filterable和only-leaf-checkable。这两个配置组合,能让用户体验从“在一棵大树里大海捞针”升级为“搜到即选、选完即走”。
第三,懒加载不一定比一次加载更快。如果部门数据只有几百条,全量返回反而省事,懒加载会带来接口请求次数增加和节点展开延迟的问题。
第四,勾选策略要和产品经理确认清楚,尤其注意父子联动和叶子选择的边界场景。这是部门树组件里最影响用户感知的配置项,错了用户会明显感觉到“勾了父级子级全被选上了”“想选父级却只能选叶子”的落差。
8. 实际使用中的体会与收获
这次xly-dept-tree上手用下来,我最大的感受是组件设计是真的在认真解决业务问题,而不是简单把通用Tree换个名字。它给部门这个高频业务场景定了一个相对标准的交互框架,后续新项目接组织架构模块时,我不需要再从头梳理选人逻辑、树展示逻辑、搜索逻辑和回显逻辑,直接在这个组件基础上做业务适配就行。
最后再分享一个小记忆点:如果你在同一个页面里同时用了多棵xly-dept-tree(比如一个人既要选归属部门,又要选可管辖范围),记得给每一棵树单独绑定ref和v-model,不要复用一个数组。这个坑我当年踩过一次,两个树的数据互相串了,排查了大半天才定位到问题是数据引用没有隔离。这种细节文档里通常不会写,但真正写业务代码时一旦碰到,还挺耗时间的。