基于Vant的移动端公共选人/选部门组件设计文档
1. 组件概述
1.1 组件名称
SelectPersonDeptMobile - 基于Vant的移动端公共选人/选部门组件
1.2 组件功能
- 支持选择用户、角色、组织架构、群组、快速查找
- 支持多选/单选模式(通过multiple布尔值控制)
- 支持搜索功能
- 支持平铺列表展示,通过下级按钮加载新数据
- 支持已选人员/部门展示与管理
- 移动端友好的交互设计
- 基于Vant组件库实现
- 支持自定义选择类型
1.3 设计理念
- 模块化设计,便于复用
- 基于Vant组件库,保证移动端体验一致性
- 支持灵活配置,适应不同业务场景
- 类型安全,使用TypeScript确保类型正确
- 良好的用户体验,流畅的交互效果
- 轻量级设计,性能优化
- 纯UI组件设计,与数据逻辑分离
2. 组件结构设计
2.1 目录结构
bash
components/
└── SelectPersonDeptMobile/
├── SelectPersonDeptMobile.vue # 主组件
├── SelectPersonDept.types.ts # 类型定义文件
├── components/ # 子组件
│ ├── SearchBar.vue # 搜索栏组件(基于Vant Search)
│ ├── TabNav.vue # 标签导航组件(基于Vant Tabs)
│ ├── SelectList.vue # 平铺列表组件(基于Vant List/Cell)
│ └── SelectedList.vue # 已选列表组件(基于Vant Tag/Cell)
└── utils/ # 工具函数
└── helper.ts # 辅助函数
3. 类型定义
3.1 基础类型
typescript
// SelectPersonDept.types.ts
// 选择项类型
export interface SelectPersonDeptProps<T extends string = 'user' | 'role' | 'dept' | 'group' | 'quick_search'> {
tabs?: Array<{
tab: T;
tab_name: string;
uniKey: string;
[key: string]: any;
}>;
// 其他props保持不变
}
// 标签配置
export interface TabConfig {
tab: SelectItemType;
tab_name: string;
interface?: string; // 可选,标签页对应的数据接口标识
[key: string]: any;
}
// 用户信息
export interface UserInfo {
userId: string;
userName: string;
deptName?: string;
userCode: string;
[key: string]: any;
}
// 部门信息
export interface DeptInfo {
deptId: string;
deptName: string;
parentId?: string;
hasChildren?: boolean; // 是否有下级数据
[key: string]: any;
}
// 角色信息
export interface RoleInfo {
roleId: string;
roleName: string;
[key: string]: any;
}
// 群组信息
export interface GroupInfo {
groupId: string;
groupName: string;
[key: string]: any;
}
// 通用选择项(适配Vant组件)
export interface SelectOption {
id: string; // 对应Vant的value
name: string; // 对应Vant的text
type: SelectItemType;
hasChildren?: boolean; // 是否有下级数据
disabled?: boolean; // 是否禁用
[key: string]: any;
}
// 已选结果
export interface SelectedResult {
users: UserInfo[];
depts: DeptInfo[];
roles: RoleInfo[];
groups: GroupInfo[];
quickContacts: UserInfo[];
}
// 组件Props
export interface SelectPersonDeptProps {
modelValue: SelectedResult; // 已选结果(用于回显和双向绑定)
visible?: boolean; // 组件显示状态
tabs?: TabConfig[]; // 标签配置
multiple?: boolean; // 是否多选,true=多选,false=单选
placeholder?: string; // 搜索框占位符
showSelectedList?: boolean; // 是否显示已选列表
showConfirmBtn?: boolean; // 是否显示确认按钮
confirmBtnText?: string; // 确认按钮文本
cancelBtnText?: string; // 取消按钮文本
currentList?: SelectOption[]; // 当前列表数据
loading?: boolean; // 加载状态
customUserList?: UserInfo[]; // 自定义用户列表
}
// 组件事件
export interface SelectPersonDeptEmits {
'update:modelValue': [value: SelectedResult]; // 已选结果变化
'update:visible': [visible: boolean]; // 组件显示状态变化
'confirm': [value: SelectedResult]; // 确认选择
'cancel': []; // 取消选择
'search': [keyword: string]; // 搜索
'tab-change': [tab: SelectItemType]; // 标签切换
'next-level': [item: SelectOption]; // 点击下级按钮
'back-level': [breadcrumb: Array<{ name: string; id: string; type: SelectItemType }>]; // 返回上一级
'load-data': [tab: SelectItemType, params?: any]; // 请求加载数据
'load-quick-search': []; // 请求加载快速查找数据
'select-all': [checked: boolean]; // 全选/取消全选
'close': []; // 关闭组件
}
js
<template>
<SelectPersonDept<MyCustomType>
:tabs="customTabs"
@tab-change="handleTabChange"
/>
</template>
<script setup lang="ts">
type MyCustomType = 'user1' | 'user2' | 'dept' | 'role';
const customTabs = [
{ tab: 'user1', tab_name: '普通用户', uniKey: 'user1' },
{ tab: 'user2', tab_name: 'VIP用户', uniKey: 'user2' },
{ tab: 'dept', tab_name: '部门', uniKey: 'dept' },
{ tab: 'role', tab_name: '角色', uniKey: 'role' }
] as const;
const handleTabChange = (tab: MyCustomType) => {
// 获得完整的类型提示
console.log('Tab changed:', tab);
};
</script>
4. 移动端纯UI组件设计(SelectPersonDeptMobile)
4.1 组件定位
- 纯UI组件:仅负责界面渲染和用户交互
- 数据驱动:所有数据由父组件通过props传入
- 事件驱动:通过事件与父组件通信,触发数据获取等操作
- 可复用:适用于各种需要选人/选部门的移动端场景
4.2 核心设计原则
- UI与数据分离:组件不包含任何数据获取逻辑
- 事件驱动通信:通过事件通知父组件执行数据操作
- 单向数据流:数据从父组件流向子组件,变化通过事件反馈
- 灵活配置:支持通过props自定义各种行为和样式
5. 数据获取渲染流程
5.1 完整流程概览
5.2 详细流程说明
5.2.0 0. 回显数据初始化(新增)
触发条件:父组件准备使用组件并需要显示已选数据时
数据流向:
- 父组件根据业务需求准备初始已选数据(回显数据)
- 父组件将初始已选数据通过
modelValueprops传入纯UI组件 - 纯UI组件在初始化时接收
modelValue
交互说明:
- 回显数据可以是来自API请求、表单数据或其他业务逻辑
- 父组件需要确保回显数据格式符合
SelectedResult接口定义 - 支持空回显数据(初始未选择任何项)
5.2.1 1. 组件初始化与显示
触发条件:父组件需要打开选人组件时
数据流向:
- 父组件通过设置
visible=true打开组件 - 纯UI组件渲染并显示
- 纯UI组件根据传入的
modelValue更新本地选中状态(回显处理核心逻辑)
交互说明:
- 组件打开时自动处理回显数据,并同步选中状态
- 支持通过props预设初始数据,避免白屏
5.2.2 2. 初始数据加载
触发条件:组件打开后
数据流向:
- 父组件根据事件类型调用对应API接口
- API返回数据后,父组件通过props更新到纯UI组件
- 纯UI组件接收到新数据后渲染
- 纯UI组件根据本地保存的选中状态,自动标记列表中的已选项(回显显示)
交互说明:
- 父组件负责处理API调用和数据转换
- 支持设置
loading状态,显示加载动画 - 支持处理空数据
- 数据加载完成后自动显示已选项标记,实现回显效果
5.2.3 3. 标签切换数据加载
触发条件:用户点击不同标签时
数据流向:
- 纯UI组件触发
tab-change事件,携带当前标签类型 - 父组件根据标签类型调用对应API接口
- API返回数据后,父组件通过
currentListprops更新 - 纯UI组件重新渲染对应标签的数据
- 纯UI组件根据本地保存的选中状态,自动标记列表中的已选项(回显显示)
交互说明:
- 标签切换时自动重置面包屑
- 支持不同标签使用不同的API接口
- 标签切换时显示加载状态
- 不同标签下的数据会自动根据全局选中状态标记已选项
5.2.4 4. 搜索数据加载
触发条件:用户在搜索框输入关键词时
数据流向:
- 纯UI组件触发
search事件,携带搜索关键词 - 父组件调用搜索API接口
- API返回搜索结果后,父组件通过
currentListprops更新 - 纯UI组件渲染搜索结果
- 纯UI组件根据本地保存的选中状态,自动标记搜索结果中的已选项(回显显示)
交互说明:
- 支持实时搜索或防抖搜索
- 搜索结果为空时显示空状态
- 支持清空搜索条件,返回原始列表
- 搜索结果中会自动标记已选项,保持回显状态
5.2.5 5. 下级数据加载
触发条件:用户点击列表项右侧的"下级"按钮时
数据流向:
- 纯UI组件触发
next-level事件,携带当前项信息 - 父组件调用获取下级数据的API接口
- API返回下级数据后,父组件通过
currentListprops更新 - 纯UI组件渲染下级数据
- 同时更新面包屑导航
- 纯UI组件根据本地保存的选中状态,自动标记下级数据中的已选项(回显显示)
交互说明:
- 仅当列表项
hasChildren=true时显示"下级"按钮 - 点击后自动更新面包屑,便于返回
- 支持多级嵌套导航
- 下级数据中会自动标记已选项,保持回显状态
5.2.6 6. 返回上一级数据加载
触发条件:用户点击"返回上一级"按钮或面包屑项时
数据流向:
- 纯UI组件触发
back-level事件,携带当前面包屑信息 - 父组件调用获取上一级数据的API接口
- API返回上一级数据后,父组件通过
currentListprops更新 - 纯UI组件渲染上一级数据
- 同时更新面包屑导航
- 纯UI组件根据本地保存的选中状态,自动标记上一级数据中的已选项(回显显示)
交互说明:
- 支持通过面包屑直接跳转到任意层级
- 面包屑项支持点击事件
- 无上级数据时自动隐藏返回按钮
- 上一级数据中会自动标记已选项,保持回显状态
5.2.7 7. 选择项处理
触发条件:用户点击选择框(Checkbox/Radio)时
数据流向:
- 纯UI组件触发
select事件,携带选择项和选中状态 - 父组件根据选择状态更新已选结果
- 父组件通过
modelValueprops更新已选结果 - 纯UI组件更新已选列表显示
交互说明:
- 支持多选和单选模式
- 支持全选/取消全选功能
- 实时更新已选列表
5.2.8 8. 组件关闭
触发条件:用户点击"确认"或"取消"按钮时
数据流向:
- 纯UI组件触发
confirm或cancel事件 - 父组件处理最终结果(保存或丢弃)
- 父组件设置
visible=false关闭组件 - 纯UI组件隐藏
交互说明:
- 确认时返回最终已选结果
- 取消时不保存任何更改
5.2.9 9. 虚拟滚动
针对大数据量(如10000+条人员数据)的情况,我从后端、前端和交互体验三个层面设计了完整的优化策略:
1. 后端层面:分页查询是基础
- 接口必须支持分页,默认每页返回20-50条数据
- 包含page(当前页码)、size(每页条数)和total(总条数)参数
- 示例请求:api.loadData(tab, { page: 1, size: 30 })
- 确保分页查询的性能,建议对查询字段建立索引
2. 前端层面:虚拟滚动是核心
-
使用Vant的List组件实现虚拟滚动,只渲染可视区域内的列表项
-
配置预加载偏移量(50-100px),提前加载下一页数据
-
示例代码:
iniVue <van-list v-model:loading="loading" :finished="finished" @load="onLoad" :offset="50" > <van-cell v-for="item in currentList" :key="item.id" :title="item.name" > <!-- 列表项内容 --> </van-cell> </van-list>
3. 搜索优化:减少不必要的请求
-
200-300ms防抖处理,避免频繁输入导致的多次请求
-
搜索关键词长度限制(至少2个字符)
-
搜索结果支持分页加载
-
示例代码:
iniTypeScript const debouncedSearch = debounce ((keyword: string) => { if (keyword.trim().length >= 2) { handleSearch(keyword); } }, 300);
5.3 事件与Props映射关系
| 事件名 | 触发时机 | 对应Props更新 | 说明 |
|---|---|---|---|
tab-change |
标签切换 | currentList |
切换标签时触发 |
search |
搜索输入 | currentList |
搜索关键词变化时触发 |
next-level |
点击下级按钮 | currentList |
加载下级数据 |
back-level |
返回上一级 | currentList |
加载上一级数据 |
select |
选择/取消选择 | modelValue |
更新已选结果 |
confirm |
确认选择 | - | 确认最终结果 |
cancel |
取消选择 | - | 取消选择 |
| - | 组件初始化 | modelValue |
父组件传入回显数据,纯UI组件处理并更新本地选中状态 |
5.4 父组件核心职责
-
数据管理:
- 调用API获取各种数据
- 处理数据转换和格式化
- 维护已选结果状态
- 准备和管理回显数据(初始已选数据)
-
事件处理:
- 监听纯UI组件发出的所有事件
- 根据事件类型执行对应逻辑
- 更新组件props
- 处理回显数据的初始化和更新
-
数据缓存策略:
-
使用Map数据结构缓存已加载的数据
-
高效的查找性能 :Map的查找时间复杂度为O(1),比数组遍历更高效
-
灵活的键类型 :可以使用复合字符串作为键,便于区分不同tab和参数组合的数据
-
避免重复请求 :切换tab或返回上一级时,直接从缓存获取数据,减少API调用次数
-
提升用户体验 :缓存数据可立即显示,避免加载等待,提升交互流畅度
-
-
组件关闭时清空缓存
- 避免内存泄漏 :长时间不清除缓存可能导致内存占用过高
- 数据时效性 :确保下次打开组件时获取最新数据
- 减少不必要的资源占用 :组件不再使用时释放相关资源
- 避免数据不一致 :防止缓存数据与实际业务数据不一致
5.5 纯UI组件核心职责
-
界面渲染:
- 根据props渲染列表数据
- 渲染已选列表
- 渲染加载状态和空状态
- 根据回显数据标记已选项
-
用户交互:
- 处理用户点击、滑动等操作
- 维护本地临时状态(如选中状态)
- 触发相应事件
-
状态同步:
- 响应props变化,更新UI
- 保持与父组件数据一致
- 处理回显数据:将modelValue转换为本地选中状态
- 在数据加载完成后,根据本地选中状态标记已选项
6. 组件API
6.1 Props
| 参数名 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| v-model | SelectedResult |
- | 双向绑定的已选结果 |
| v-model:visible | boolean |
false |
控制组件显示/隐藏 |
| tabs | TabConfig[] |
见默认配置 | 标签配置,包含快速查找项 |
| multiple | boolean |
true |
是否支持多选,true=多选,false=单选 |
| placeholder | string |
'请输入搜索关键词' |
搜索框占位符 |
| showSelectedList | boolean |
true |
是否显示已选列表 |
| showConfirmBtn | boolean |
true |
是否显示确认按钮 |
| confirmBtnText | string |
'确认' |
确认按钮文本 |
| cancelBtnText | string |
'取消' |
取消按钮文本 |
| currentList | SelectOption[] |
[] |
当前列表数据 |
| loading | boolean |
false |
加载状态 |
| lowcodeStoreId | string |
'' |
低代码存储ID |
| customUserList | UserInfo[] |
[] |
自定义用户列表 |
6.2 Emits
| 事件名 | 类型 | 说明 |
|---|---|---|
| update:modelValue | (value: SelectedResult) => void |
已选结果变化时触发 |
| update:visible | (visible: boolean) => void |
组件显示状态变化时触发 |
| confirm | (value: SelectedResult) => void |
确认选择时触发 |
| cancel | () => void |
取消选择时触发 |
| search | (keyword: string) => void |
搜索时触发,携带搜索关键词 |
| tab-change | (tab: SelectItemType) => void |
标签切换时触发,携带标签类型 |
| next-level | (item: SelectOption) => void |
点击下级按钮时触发,携带当前项信息 |
| back-level | (breadcrumb: Array<{ name: string; id: string; type: SelectItemType }>) => void |
返回上一级时触发,携带面包屑信息 |
| select-all | (checked: boolean) => void |
全选/取消全选时触发,携带选中状态 |
| close | () => void |
关闭组件时触发 |
7. 工时表
| 日期 | 任务名称 | 详细工作内容 | 优先级 | 计划工时(h) |
|------|---------------|-------------------------------------------------------------------|-----|---------|---|---|---|
| 第1天 | 组件结构搭建 | 1. 创建组件目录结构 2. 创建主组件文件 3. 搭建基础布局框架 | 高 | 8 |
| 第2天 | 子组件开发(搜索栏+标签) | 1. 开发SearchBar搜索栏组件 2. 开发TabNav标签导航组件 3. 集成子组件到主组件 4. 实现搜索和标签切换事件 | 高 | 8 |
| 第3天 | 平铺列表核心功能 | 1. 开发SelectList平铺列表组件 2. 实现列表项渲染 3. 开发"下级"按钮功能 4. 实现面包屑导航 | 高 | 8 |
| 第4天 | 选择功能实现 | 1. 实现单选/多选逻辑 2. 开发选择状态管理 3. 实现全选/取消全选功能 4. 集成到列表组件 | 高 | 8 |
| 第5天 | 已选列表开发 | 1. 开发SelectedList已选列表组件 2. 实现已选项展示 3. 开发已选项删除功能 4. 集成到主组件 | 中 | 8 |
| 第6天 | 快速查找功能 | 1. 开发快速查找模块 4. 集成到主组件 | 中 | 8 |
| 第7天 | 回显数据逻辑 | 1. 实现modelValue双向绑定 2. 开发回显数据处理 3. 实现本地选中状态管理 | 高 | 8 |
| 第8天 | 组件集成与调试 | 1. 整合所有子组件 2. 调试组件间通信 3. 修复发现的问题 | 高 | 8 |
| 第9天 | 单个旧组件替换并调试 | 1. 接口联调 2. 重构原有功能点 | 高 | 8 |
| 第10天 | 单个旧组件替换并调试 | 1. 接口联调 2. 重构原有功能点 | 高 | 8 |
| 第11天 | 单个旧组件替换并调试 | 1. 测试移动端适配 2. 修复测试中发现的问题 | 中 | 8 | | 中 | 8 |
| 第12天 | 所有旧组件替换并调试 | 1. 接口联调 2. 重构原有功能点 | 中 | 8 |
| 第13天 | 所有旧组件替换并调试 | 1. 接口联调 2. 重构原有功能点 | 中 | 8 |
| 第14天 | 所有旧组件替换并调试 | 1. 测试移动端适配 2. 修复测试中发现的问题 | 中 | 8 |
8. 总结
SelectPersonDeptMobile 是一个基于Vant的纯UI移动端公共选人/选部门组件,通过事件驱动的方式与父组件通信,实现了UI与数据逻辑的分离。组件支持多种选择类型、搜索功能、多级嵌套导航和灵活的配置选项,能够适应各种业务场景。
通过清晰的数据流向和交互流程设计,组件实现了高效的数据流管理和良好的用户体验。父组件负责数据获取和业务逻辑,纯UI组件负责界面渲染和用户交互,两者分工明确,便于维护和扩展。
这种设计模式使得组件具有良好的可复用性和可扩展性,能够快速适配不同的业务需求,是一个可靠的移动端公共选人/选部门组件解决方案。