当前位置: 首页 > news >正文

UniApp项目实战:用ba-tree-picker插件打造一个可复用的‘部门-员工’选择组件(附完整代码)

UniApp企业级实战:封装高复用性组织架构选择组件

在开发OA、CRM等后台管理系统时,"选择部门/人员"几乎是每个表单页面的标配功能。我曾参与过一个大型ERP系统的重构,发现37%的交互表单都包含组织架构选择器——但团队里每个开发者都在重复造轮子,有的用<picker>简单嵌套,有的引入第三方库但样式不统一,甚至出现同一个系统中三种不同风格的选择器并存的情况。这种混乱不仅增加维护成本,更影响用户体验的一致性。

1. 为什么需要封装组织架构选择组件?

企业级应用对组织架构选择器有特殊要求:需要展示树形层级关系(公司>部门>小组>员工)、支持快速检索、允许单选/多选混合模式,还要能处理"选择父级即选中所有子级"的业务逻辑。直接使用原生picker或基础树形控件往往需要大量重复代码。

以审批流设置为例,典型需求包括:

  • 选择特定部门时自动包含其下所有员工
  • 支持单独添加某个员工
  • 已选条目需要显示完整路径(如"总部/技术中心/前端组/张三")
  • 在移动端保持流畅的交互体验

ba-tree-picker作为UniApp生态中专为树形选择设计的插件,提供了不错的底层能力,但直接使用仍存在几个问题:

  1. 业务数据格式与组件要求格式不匹配
  2. 选择结果处理逻辑分散在各页面
  3. 样式需要根据企业VI统一调整
  4. 缺乏缓存等性能优化
// 典型问题示例:每个页面都需要写转换逻辑 function convertApiData(apiData) { return apiData.map(dept => ({ id: dept.deptId, name: dept.deptName, children: convertApiData(dept.children || []) })) }

2. 组件设计与核心实现

2.1 组件接口设计

我们的目标是创建一个<org-picker>组件,对外暴露简洁的API:

<org-picker v-model="selected" mode="multiple" :api="getOrgApi" placeholder="请选择负责人" @change="handleChange" />

关键props设计:

参数类型默认值说明
v-modelArray[]绑定已选ID数组
modeString'single'单选(single)/多选(multiple)
apiFunction-获取组织架构的Promise方法
cacheBooleantrue是否缓存组织数据
showPathBooleantrue是否显示完整路径

2.2 数据转换层实现

企业API返回的数据结构往往与组件所需格式不一致。我们创建transform.js处理这种转换:

// 转换示例:将扁平结构转为树形结构 export function flatToTree(flatData, idKey = 'id', parentKey = 'parentId') { const map = new Map() const roots = [] flatData.forEach(item => { map.set(item[idKey], { ...item, children: [] }) }) flatData.forEach(item => { const node = map.get(item[idKey]) if (item[parentKey]) { const parent = map.get(item[parentKey]) parent?.children.push(node) } else { roots.push(node) } }) return roots }

提示:对于超大型组织架构(如万人企业),建议在后端完成树形构建,前端只做属性映射

2.3 核心组件封装

ba-tree-picker基础上进行二次封装:

<template> <view> <ba-tree-picker ref="treePicker" :multiple="multiple" :localdata="treeData" valueKey="id" textKey="name" childrenKey="children" @select-change="handleSelect" /> <view @click="showPicker" class="picker-trigger"> {{ displayText || placeholder }} </view> </view> </template> <script> export default { props: { api: { type: Function, required: true }, value: { type: Array, default: () => [] } }, data() { return { treeData: [], loading: false } }, async created() { await this.loadData() }, methods: { async loadData() { this.loading = true try { const res = await this.api() this.treeData = transformApiData(res) // 使用前面的转换方法 } finally { this.loading = false } }, handleSelect(ids, names) { this.$emit('input', ids) this.$emit('change', { ids, names }) } } } </script>

3. 高级功能实现

3.1 带搜索功能的选择器

在企业场景中,快速定位人员是关键需求。我们扩展基础组件增加搜索功能:

computed: { filteredData() { if (!this.searchText) return this.treeData return this.deepFilter(this.treeData, item => item.name.includes(this.searchText) ) } }, methods: { deepFilter(nodes, predicate) { return nodes.filter(node => { const children = this.deepFilter(node.children || [], predicate) return predicate(node) || children.length > 0 }).map(node => ({ ...node, children: node.children ? this.deepFilter(node.children, predicate) : [] })) } }

3.2 选择结果展示优化

已选项需要显示完整路径而不仅是节点名称:

function getFullPath(tree, id, path = []) { for (const node of tree) { if (node.id === id) return [...path, node.name] if (node.children) { const found = getFullPath(node.children, id, [...path, node.name]) if (found) return found } } return null }

3.3 性能优化技巧

对于大型组织架构,这些优化很关键:

  • 数据缓存:将API响应存入localStorage,设置合理过期时间
  • 虚拟滚动:对超深层级树实现节点动态加载
  • 防抖搜索:用户输入时延迟执行过滤逻辑
// 缓存实现示例 async loadData() { const cacheKey = 'org_cache' const cached = uni.getStorageSync(cacheKey) if (cached) { this.treeData = cached } const freshData = await this.api() uni.setStorageSync(cacheKey, freshData) this.treeData = freshData }

4. 完整组件代码与使用示例

以下是经过实战检验的完整实现(关键部分):

<!-- components/org-picker.vue --> <template> <view class="org-picker"> <ba-tree-picker ref="picker" :title="title" :multiple="multiple" :localdata="processedData" :valueKey="valueKey" :textKey="textKey" :childrenKey="childrenKey" @select-change="handleSelect" /> <view @click="showPicker" class="trigger"> <text v-if="!selectedNames.length">{{ placeholder }}</text> <text v-else>{{ displayText }}</text> </view> <!-- 搜索框 --> <view v-if="searchable" class="search-box"> <uni-search-bar v-model="searchText" placeholder="搜索部门/人员" /> </view> </view> </template> <script> // 完整实现见GitHub仓库 export default { // 所有前述功能整合 } </script> <style lang="scss"> .org-picker { .trigger { padding: 12px; border: 1px solid #eee; border-radius: 4px; } .search-box { padding: 8px; background: #f5f5f5; } } </style>

使用示例:

// 页面中使用 import OrgPicker from '@/components/org-picker.vue' export default { components: { OrgPicker }, methods: { async fetchOrgData() { return (await uni.request({ url: '/api/org-structure' })).data } } }
<template> <view> <org-picker v-model="selectedUsers" :api="fetchOrgData" mode="multiple" placeholder="选择审批人" /> </view> </template>

5. 工程化建议与踩坑记录

在实际企业项目中,我总结了这些经验:

  1. 数据同步问题:组织架构变更后要及时清除缓存,建议监听登出事件自动清理

    uni.$on('logout', () => { uni.removeStorageSync('org_cache') })
  2. 性能监控:对万级节点的树形数据要添加加载指示器

    async loadData() { this.loading = true try { // ...加载逻辑 } catch (error) { uni.showToast({ title: '加载失败', icon: 'none' }) } finally { this.loading = false } }
  3. 移动端适配:深层级树在iOS上可能出现滚动卡顿,需要特殊处理:

    /* 强制开启GPU加速 */ .tree-node { transform: translateZ(0); }
  4. 测试要点

    • 超长部门名称的显示效果
    • 同时选择500+节点时的性能
    • 网络异常时的降级方案
    • 与uni-popup等弹窗组件的兼容性

这个组件在我们多个企业项目中节省了约60%的相关开发时间,特别是在需要多处使用时,只需统一修改组件就能全局更新所有实例。对于更复杂的场景(如按角色过滤、跨组织选择等),可以在现有基础上继续扩展——良好的封装设计应该像乐高积木一样支持灵活组合。

http://www.jsqmd.com/news/655676/

相关文章:

  • MATLAB还是Python?MODIS HDF转TIFF及全球拼接的两种实战方案对比
  • React 用 Flux 怎么管理状态?
  • CentOS 7 安装 Redis(使用默认 6379 端口)完整实践与踩坑总结
  • UniPush消息推送实战:如何让安卓、iOS的在线/离线消息都能稳定送达并正确跳转?
  • -:RAG 入门-向量存储与企业级向量数据库 milvus
  • 西门子840D后处理实战:用TCL脚本自动生成刀具清单,告别手动编号
  • 终极指南:如何使用TlbbGmTool轻松管理单机游戏数据
  • TCP可靠传输的基石:从停止等待到滑动窗口,ARQ协议如何守护你的数据?
  • Obsidian Smart Connections 技术深度解析:如何构建零配置的AI笔记关联引擎
  • 2026届毕业生推荐的六大降重复率助手推荐
  • 实战指南:从COCO标注(.json)到YOLO训练(.txt)的无损格式转换
  • 3个场景告诉你:为什么这个工具能让Windows体验提升300%?
  • Sign Language Interpreter:用深度学习打破沟通壁垒的实时手语翻译工具
  • 2026南宁建筑行业AI获客落地指南:AI获客服务商参考、成本与时效全详解
  • Windhawk终极指南:Windows系统定制与界面增强完整手册
  • 7-Zip完整指南:如何用这款免费开源压缩工具提升工作效率 [特殊字符]
  • 2026贵阳南明区正宗铁签烤肉与烤鱼美食体验地标(含官方联系方式) - 精选优质企业推荐官
  • CSAPP-MallocLab:从隐式空闲链表到显式分离链表的性能跃迁
  • 世贸通美国EB5投资移民:赴美生子将遭重创,美宝家庭身份危机 - 速递信息
  • 告别NAS卡顿!用PC版tinyMediaManager 4.x给群晖电影库刮削海报和信息(附Java环境配置)
  • 南京离婚律师哪家技术强 - 资讯焦点
  • 如何选择直剪仪专业制造商,台式直剪仪价格与品牌分析 - 工业设备
  • 2026年全国工业降温设备十大品牌口碑推荐:负压风机/工业冷风机/降温湿帘厂家排名 - 安互工业信息
  • NavMeshPlus:Unity 2D游戏智能寻路的终极解决方案
  • 手把手教你用FastAPI给DeepSeek-OCR模型做个Web界面,还能兼容OpenAI的API格式
  • RISC-V分支预测入门:从BTFN到两级预测器,手把手理解CPU如何‘猜’对指令
  • 深圳会议酒店推荐|从福田CBD到前海,酒店哥哥一篇搞定你的办会选址难题
  • OpenHarmony 5.0.2 USB摄像头适配:从配置修改到图像显示的完整调试指南
  • Go语言中的图形界面开发实战解析:从GUI到WebAssembly
  • 开源DICOM查看器Weasis:零成本构建专业医学影像分析平台