diff --git a/schoolNewsWeb/src/apis/system/user.ts b/schoolNewsWeb/src/apis/system/user.ts index 6e124d2..377455f 100644 --- a/schoolNewsWeb/src/apis/system/user.ts +++ b/schoolNewsWeb/src/apis/system/user.ts @@ -62,17 +62,6 @@ export const userApi = { return response.data; }, - /** - * 获取用户分页列表 - * @param pageParam 分页参数 - * @param filter 过滤条件 - * @returns Promise> - */ - async getUserPage(pageParam: PageParam, filter: SysUser): Promise> { - const response = await api.post('/users/page', { pageParam, filter }); - return response.data; - }, - /** * 创建用户 * @param user 用户信息 diff --git a/schoolNewsWeb/src/components/user/README.md b/schoolNewsWeb/src/components/user/README.md new file mode 100644 index 0000000..7605098 --- /dev/null +++ b/schoolNewsWeb/src/components/user/README.md @@ -0,0 +1,213 @@ +# UserSelect 用户选择组件 + +## 功能特性 + +- ✅ 支持双栏选择器布局 +- ✅ 支持搜索功能(左右两侧独立搜索) +- ✅ 三种操作方式:双击、勾选+按钮、全部按钮 +- ✅ **支持滚动分页加载**(性能优化) +- ✅ 支持传入静态数据或API方法 +- ✅ 完全封装的样式和逻辑 + +## Props 配置 + +| 参数 | 类型 | 默认值 | 说明 | +|------|------|--------|------| +| visible | Boolean | false | 控制弹窗显示/隐藏 | +| mode | 'add' \| 'remove' | 'add' | 选择模式 | +| title | String | '人员选择' | 弹窗标题 | +| leftTitle | String | '可选人员' | 左侧面板标题 | +| rightTitle | String | '已选人员' | 右侧面板标题 | +| availableUsers | UserVO[] | [] | 左区域静态数据 | +| initialTargetUsers | UserVO[] | [] | 初始已选人员 | +| loading | Boolean | false | 确认按钮加载状态 | +| **usePagination** | Boolean | false | **是否启用分页加载** | +| **fetchApi** | Function | undefined | **分页加载API方法** | +| **filterParams** | Object | {} | **API过滤参数** | +| **pageSize** | Number | 20 | **每页数量** | + +## Events 事件 + +| 事件名 | 参数 | 说明 | +|--------|------|------| +| update:visible | (value: boolean) | 更新弹窗显示状态 | +| confirm | (users: UserVO[]) | 确认提交,返回选中的用户列表 | +| cancel | - | 取消操作 | + +## 使用方式 + +### 方式一:传入静态数据(适合数据量少的场景) + +```vue + + + +``` + +### 方式二:使用分页加载(推荐,适合数据量大的场景) + +```vue + + + +``` + +### 方式三:混合模式(添加模式用分页,删除模式用静态) + +```vue + + + +``` + +## API 方法要求 + +使用 `usePagination` 时,`fetchApi` 方法需要符合以下签名: + +```typescript +async function fetchApi( + pageParam: PageParam, + filter?: any +): Promise> +``` + +### PageParam 类型 + +```typescript +interface PageParam { + page: number; // 当前页码(从1开始) + size: number; // 每页数量 +} +``` + +### ResultDomain 类型 + +```typescript +interface ResultDomain { + success: boolean; + message?: string; + dataList?: T[]; + pageParam?: { + totalElements: number; // 总记录数 + // ... 其他分页信息 + }; +} +``` + +## 特性说明 + +### 滚动分页加载 + +- 当启用 `usePagination` 时,左侧面板支持滚动加载更多数据 +- 滚动到距离底部 50px 时自动加载下一页 +- 显示加载状态和"已加载全部数据"提示 +- 搜索时自动重置分页并重新加载 + +### 搜索功能 + +- **分页模式**:搜索关键词会传递给 API,在服务端进行过滤 +- **静态模式**:搜索在前端进行过滤 + +### 数据过滤 + +组件会自动过滤掉右侧已选择的用户,避免重复选择。 + +## 性能优化建议 + +1. **数据量 < 100**:使用静态数据模式 +2. **数据量 > 100**:使用分页加载模式 +3. **数据量 > 1000**:使用分页加载 + 服务端搜索 + +## 注意事项 + +1. 使用分页模式时,`availableUsers` 会被忽略 +2. 删除模式下通常使用静态数据(当前已分配的用户) +3. `filterParams` 支持传入额外的过滤条件,会合并到 API 请求中 + diff --git a/schoolNewsWeb/src/components/user/UserSelect.vue b/schoolNewsWeb/src/components/user/UserSelect.vue new file mode 100644 index 0000000..9d8dc1a --- /dev/null +++ b/schoolNewsWeb/src/components/user/UserSelect.vue @@ -0,0 +1,781 @@ + + + + + + diff --git a/schoolNewsWeb/src/components/user/index.ts b/schoolNewsWeb/src/components/user/index.ts new file mode 100644 index 0000000..6d1e56e --- /dev/null +++ b/schoolNewsWeb/src/components/user/index.ts @@ -0,0 +1,8 @@ +/** + * @description 用户相关组件 + * @author yslg + * @since 2025-10-22 + */ + +export { default as UserSelect } from './UserSelect.vue'; + diff --git a/schoolNewsWeb/src/views/task/LearningTaskList.vue b/schoolNewsWeb/src/views/task/LearningTaskList.vue index 44fa94b..617410b 100644 --- a/schoolNewsWeb/src/views/task/LearningTaskList.vue +++ b/schoolNewsWeb/src/views/task/LearningTaskList.vue @@ -317,9 +317,13 @@ :title="selectorMode === 'add' ? '添加人员' : '删除人员'" :left-title="selectorMode === 'add' ? '可添加人员' : '当前人员'" :right-title="selectorMode === 'add' ? '待添加人员' : '待删除人员'" - :available-users="availableUsers" + :available-users="selectorMode === 'remove' ? availableUsers : []" :initial-target-users="[]" :loading="saving" + :use-pagination="selectorMode === 'add'" + :fetch-api="selectorMode === 'add' ? userApi.getUserPage : undefined" + :filter-params="userFilterParams" + :page-size="20" @confirm="handleUserSelectConfirm" @cancel="closeSelectorModal" /> @@ -378,10 +382,10 @@ const deleting = ref(false); // 人员管理相关 const managingTask = ref(null); const selectorMode = ref<'add' | 'remove'>('add'); -const allUsers = ref([]); const currentUsers = ref([]); const availableUsers = ref([]); const saving = ref(false); +const userFilterParams = ref({}); // 计算显示的页码 const displayPages = computed(() => { @@ -519,22 +523,23 @@ async function handleUpdateUser(task: LearningTask) { managingTask.value = task; try { - // 加载所有用户 - const usersRes = await userApi.getUserList({}); - if (usersRes.success && usersRes.dataList) { - allUsers.value = usersRes.dataList; - } - // 加载任务详情(包含已分配的用户) const taskRes = await learningTaskApi.getTaskById(task.taskID!); if (taskRes.success && taskRes.data) { const taskUsers = taskRes.data.taskUsers || []; const assignedUserIds = taskUsers.map(tu => tu.userID!); - // 当前已分配的用户 - currentUsers.value = allUsers.value.filter(user => - assignedUserIds.includes(user.id!) - ); + // 加载用户详情(批量查询) + if (assignedUserIds.length > 0) { + const usersRes = await userApi.getUserList({}); + if (usersRes.success && usersRes.dataList) { + currentUsers.value = usersRes.dataList.filter(user => + assignedUserIds.includes(user.id!) + ); + } + } else { + currentUsers.value = []; + } } showUserList.value = true; @@ -555,11 +560,8 @@ function closeUserList() { function showAddUserSelector() { selectorMode.value = 'add'; - // 左侧显示未分配的用户 - const currentUserIds = currentUsers.value.map(u => u.id!); - availableUsers.value = allUsers.value.filter(user => - !currentUserIds.includes(user.id!) - ); + // 设置过滤参数(可以过滤掉已分配的用户,但在组件内部会自动过滤) + userFilterParams.value = {}; showUserSelector.value = true; }