From cc1a6459bd66d95d9372f49b6b840eee13c52a71 Mon Sep 17 00:00:00 2001 From: TianyangZhang Date: Wed, 25 Mar 2026 16:53:16 +0800 Subject: [PATCH] =?UTF-8?q?docs:=20=E6=9B=B4=E6=96=B0=E5=88=86=E9=A1=B5?= =?UTF-8?q?=E5=8A=9F=E8=83=BD=E8=AE=BE=E8=AE=A1=E6=96=87=E6=A1=A3=EF=BC=8C?= =?UTF-8?q?=E6=89=A9=E5=B1=95=E5=88=B0=E6=89=80=E6=9C=89=E7=AE=A1=E7=90=86?= =?UTF-8?q?=E8=A1=A8=E6=A0=BC=E9=A1=B5=E9=9D=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...26-03-25-organization-pagination-design.md | 389 ++++++++++++++++++ 1 file changed, 389 insertions(+) create mode 100644 docs/superpowers/specs/2026-03-25-organization-pagination-design.md diff --git a/docs/superpowers/specs/2026-03-25-organization-pagination-design.md b/docs/superpowers/specs/2026-03-25-organization-pagination-design.md new file mode 100644 index 0000000..e2518cc --- /dev/null +++ b/docs/superpowers/specs/2026-03-25-organization-pagination-design.md @@ -0,0 +1,389 @@ +# 管理后台分页功能设计文档 + +**日期**: 2026-03-25 +**功能**: 为管理后台所有表格页面(职位管理、投递管理、组织架构、用户管理)添加分页功能 +**优先级**: 标准 +**涉及页面**: +- 职位管理 (JobManageView.vue) +- 投递管理 (ApplicationManageView.vue) +- 组织架构 (OrganizationManageView.vue) +- 用户管理 (UserManageView.vue) + +## 概述 + +当前所有管理后台表格一次性加载所有数据到表格中。为了改善用户体验和减少初始加载时间,为所有管理表格页面实现统一的标准分页功能。后端已配置 `PageNumberPagination`(每页 20 条),前端只需统一接入分页参数和 UI 组件。 + +## 背景 + +- **现状**: 所有管理表格页面(职位、投递、部门、用户)都直接加载所有数据到表格,无分页 +- **问题**: 数据量增多时,页面加载缓慢,表格拥挤,用户体验差 +- **后端支持**: Django REST Framework 已全局配置分页(`DEFAULT_PAGE_SIZE: 20`),自动处理 `?page=1` 参数 +- **目标**: 为所有管理表格统一添加分页功能,提升用户体验 + +## 设计方案 + +### 1. 后端 API 层 + +**无需改动**。`OrganizationManageViewSet` 继承 DRF 的 `ModelViewSet`,已自动支持 `PageNumberPagination`。 + +**API 响应格式**(现有): +```json +{ + "count": 50, + "next": "http://.../?page=2", + "previous": null, + "results": [ + { "id": 1, "name": "集团", "email": "...", "is_active": true, "parent": null }, + ... + ] +} +``` + +### 2. 前端 API 层修改 + +**文件**: `offer_frontend/src/api/organizations.js` + +修改 `manageOrganizations` 函数,支持可选的页码参数: + +```javascript +export const manageOrganizations = (page = 1) => + client.get(`/organizations/manage/?page=${page}`) +``` + +**调用示例**: +```javascript +// 获取第 1 页 +const { data } = await manageOrganizations(1) +// 获取第 2 页 +const { data } = await manageOrganizations(2) +``` + +### 3. 前端组件修改 + +**文件**: `offer_frontend/src/views/admin/OrganizationManageView.vue` + +#### 3.1 状态管理 + +新增和保留响应式状态: +```javascript +const orgs = ref([]) // 当前页部门列表 +const allOrgs = ref([]) // 所有部门列表(用于下拉框) +const loading = ref(false) // 当前页加载状态 +const currentPage = ref(1) // 当前页码 +const pageSize = ref(20) // 每页行数(与后端 PAGE_SIZE 一致) +const total = ref(0) // 总记录数 +``` + +#### 3.2 修改 fetchOrgs 函数 + +用于加载分页数据: +```javascript +const fetchOrgs = async (page = 1) => { + loading.value = true + try { + const { data } = await manageOrganizations(page) + orgs.value = data.results + total.value = data.count + currentPage.value = page + } catch (error) { + ElMessage.error('加载部门列表失败,请重试') + } finally { + loading.value = false + } +} +``` + +#### 3.3 新增 fetchAllOrgs 函数(用于下拉框) + +为了支持下拉框选择任意部门,需要单独加载**所有部门**(不分页): +```javascript +const fetchAllOrgs = async () => { + try { + // 调用同一个 API,但不传 page 参数时默认返回第 1 页 + // 实际需要:修改后端 API 或调用一个无分页的端点 + // 临时方案:调用 getOrganizations() 获取公开的组织(如果有) + // 或者:预加载足够多的页(假设部门不超过 500,调用 3-4 次) + const { data } = await manageOrganizations(1) + // 如果只有少量部门,可以一次性加载最多 5 页 + let allResults = data.results + for (let i = 2; i <= Math.ceil(data.count / 20) && i <= 5; i++) { + const { data: nextData } = await manageOrganizations(i) + allResults = allResults.concat(nextData.results) + } + allOrgs.value = allResults + } catch (error) { + ElMessage.error('加载部门列表失败') + } +} +``` + +> **注意**: 这是一个临时方案。如果部门数量可能超过 100,建议后端新增一个 `list_all` 参数或单独的无分页 API 端点。 + +#### 3.4 添加分页事件处理 + +```javascript +function handlePageChange(newPage) { + fetchOrgs(newPage) +} +``` + +#### 3.5 修改保存后的逻辑 + +无论新增还是编辑,保存成功后都重置分页状态并返回第 1 页(保持用户体验一致): + +```javascript +async function save() { + saving.value = true + try { + if (editing.value) await updateOrganization(editing.value.id, form) + else await createOrganization(form) + ElMessage.success('保存成功') + dialogVisible.value = false + currentPage.value = 1 // 重置到第 1 页 + fetchOrgs(1) // 重新加载第 1 页(明确传入页码) + fetchAllOrgs() // 刷新下拉框数据 + } catch (error) { + ElMessage.error('保存失败') + } finally { + saving.value = false + } +} +``` + +#### 3.6 新增删除功能 + +添加 `handleDelete()` 函数: + +```javascript +async function handleDelete(id) { + try { + await ElMessageBox.confirm('确认删除该部门?', '提示', { + type: 'warning', + confirmButtonText: '确认', + cancelButtonText: '取消' + }) + await deleteOrganization(id) + ElMessage.success('已删除') + currentPage.value = 1 // 删除后回到第 1 页 + fetchOrgs(1) // 重新加载第 1 页(明确传入页码) + fetchAllOrgs() // 刷新下拉框数据 + } catch (error) { + // ElMessageBox.confirm 取消时会抛出 ElMessageBoxCancel 异常,需要判断 + if (error.name !== 'ElMessageBoxCancel') { + ElMessage.error('删除失败') + } + } +} +``` + +#### 3.7 初始化时加载所有数据 + +修改 `onMounted` 钩子: + +```javascript +onMounted(() => { + fetchOrgs() // 加载分页数据(第 1 页) + fetchAllOrgs() // 加载所有部门用于下拉框 +}) +``` + +### 4. UI 层修改 + +#### 4.1 表格加载状态 + +修改表格标签,添加 `v-loading` 指令: + +```vue + +``` + +#### 4.2 操作列添加删除按钮 + +修改操作列,添加删除按钮: + +```vue + + + +``` + +#### 4.3 下拉框使用所有部门列表 + +修改编辑对话框中的"上级公司"选择,使用 `allOrgs` 而非 `orgs`: + +```vue + + + + + +``` + +#### 4.4 表格下方添加分页控件 + +```vue +
+ +
+``` + +**样式说明**: +- `layout="total, prev, pager, next, jumper"` - 显示总数、前一页、页码、后一页、跳转输入框 +- 右对齐,与表格宽度对称 +- `margin-top: 16px` 与表格距离保持一致 + +### 5. 数据流 + +``` +用户交互 + ↓ +点击分页按钮 → handlePageChange(newPage) + ↓ +fetchOrgs(newPage) → manageOrganizations(newPage) + ↓ +API 返回 { count, results } + ↓ +更新 orgs、total、currentPage + ↓ +表格重新渲染 +``` + +### 6. 错误处理 + +- **加载失败**: 显示 ElMessage.error(),保留当前页状态 +- **删除部门**: 删除后调用 `currentPage.value = 1; fetchOrgs()` 回到第 1 页(避免最后一条删除后页码超出范围) +- **网络异常**: 用户可点击分页按钮重试 + +### 7. 边界情况 + +| 场景 | 处理方式 | +|------|---------| +| 初始化(无数据) | `currentPage=1, total=0, orgs=[]`,分页组件自动隐藏 | +| 新增部门后 | 重置到第 1 页,重新加载分页数据和下拉框数据 | +| 编辑部门后 | 重置到第 1 页,重新加载分页数据和下拉框数据(保持一致体验) | +| 删除部门后 | 重置到第 1 页,重新加载分页数据和下拉框数据 | +| 当前页无数据 | 自动回到第 1 页(由分页组件自动处理) | +| 用户取消删除确认 | 不执行任何操作,保留当前状态 | + +### 8. 下拉框数据一致性 + +**问题**: 分页后,表格显示当前页的 20 条部门,但"父公司"下拉框需要显示所有部门(因为用户可能需要将某个部门的父级设置为其他页的部门) + +**解决方案**: +- 维护单独的 `allOrgs` 数组,通过 `fetchAllOrgs()` 函数加载 +- 表格显示分页数据 `orgs`(当前页) +- 下拉框显示所有数据 `allOrgs`(所有页) +- 新增/编辑/删除后同时刷新两个列表 + +**临时方案说明**: +当前方案通过预加载前几页(最多 5 页、共 100 条)来获取所有部门。若部门数量可能超过 100,建议后续优化为: +1. 后端添加 `?list_all=true` 参数,返回无分页的完整列表 +2. 或单独新增一个无分页的 API 端点(如 `/organizations/manage/all/`) + +**后续优化**: 若需要搜索功能,改为搜索型下拉框(`el-select` + `filterable`) + +## 实现清单 + +**后端** (无需改动): +- [x] Django REST Framework 已支持 PageNumberPagination + +**前端 - API 层** (各修改一次): +- [ ] `organizations.js` - `manageOrganizations()` 添加 `page` 参数 +- [ ] `jobs.js` - `manageJobs()` 添加 `page` 参数 +- [ ] `applications.js` - `getManageApplications()` 添加 `page` 参数 +- [ ] `/auth/users/` 调用位置 - 传入 `page` 参数 + +**前端 - 组件层** (共 4 个页面): +- [ ] **OrganizationManageView.vue** (部门管理) + - [ ] 添加分页状态和函数 + - [ ] 修改表格和操作列 + - [ ] 添加分页控件 + +- [ ] **JobManageView.vue** (职位管理) + - [ ] 添加分页状态和函数 + - [ ] 修改表格和操作列 + - [ ] 添加分页控件 + +- [ ] **ApplicationManageView.vue** (投递管理) + - [ ] 添加分页状态和函数 + - [ ] 修改表格 + - [ ] 添加分页控件 + +- [ ] **UserManageView.vue** (用户管理) + - [ ] 添加分页状态和函数 + - [ ] 修改表格和操作列 + - [ ] 添加分页控件 + +**测试** (每个页面): +- [ ] 验证分页功能(第 1 页、中间页、最后一页) +- [ ] 验证新增/编辑/删除后的分页重置 +- [ ] 验证网络错误恢复 + +## 性能考量 + +- **API 调用**: 每次页码变更触发一次 API 调用(频率不高,通常用户不会频繁翻页) +- **数据量**: 每次最多 20 条记录,前端内存占用低 +- **首屏加载**: 从一次性加载 N 条改为加载 20 条,性能改善明显 + +## 向后兼容性 + +无破坏性改动: +- API 默认参数 `page=1`,未传参时返回第 1 页 +- 现有其他功能不受影响 + +## 测试场景 + +1. **初次加载** + - 验证第 1 页正确显示(最多 20 条) + - 验证总数、页码正确 + - 验证下拉框加载所有部门 + +2. **翻页** + - 点击下一页、上一页、特定页码,数据正确更新 + - 验证 loading 状态在加载期间显示 + +3. **跳转输入** + - 直接输入页码(如第 5 页),正确跳转 + - 输入超出范围的页码(如第 100 页),无报错 + +4. **新增部门** + - 新增后,回到第 1 页并显示新部门 + - 验证下拉框立即更新,新部门可被选为父级 + +5. **编辑部门** + - 编辑后,回到第 1 页,验证修改内容生效 + - 下拉框同时更新 + +6. **删除部门** + - 点击删除,出现确认对话框 + - 确认删除后,回到第 1 页,验证被删除部门消失 + - 下拉框同时移除该部门 + - 取消删除,页面无变化 + +7. **边界情况** + - 当前为最后一页,删除该页最后一条,自动回到第 1 页 + - 删除所有部门,表格显示空,分页组件隐藏 + +8. **错误恢复** + - 网络超时,显示错误提示,保留当前页状态 + - 点击分页按钮重试,正确加载 + +9. **下拉框完整性** + - 在下拉框中能选到所有部门(不仅仅是当前页) + - 搜索或滚动下拉框,能找到目标部门 + +## 后续扩展 + +- 为其他管理页面(职位、用户)添加相同的分页逻辑 +- 考虑将分页逻辑抽象为可复用的 Composable +- 支持自定义每页行数选项 +