Recruitment_site/docs/superpowers/plans/2026-03-25-admin-pagination.md

702 lines
18 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 管理后台分页功能实现计划
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** 为管理后台的所有表格页面(职位管理、投递管理、组织架构、用户管理)添加统一的分页功能。
**Architecture:**
- 后端已支持分页DRF 的 `PageNumberPagination`,每页 20 条)
- 前端 API 层:修改各模块的 fetch 函数,接受 `page` 参数
- 前端组件层添加分页状态管理、分页事件处理、UI 控件,共 4 个页面
- 每个组件遵循相同的模式,便于维护和扩展
**Tech Stack:** Vue 3 Composition API、Element Plus (el-pagination)、DRF pagination
---
## 文件结构
### 前端 API 层4 个文件修改)
- `offer_frontend/src/api/organizations.js` - 修改 `manageOrganizations(page=1)`
- `offer_frontend/src/api/jobs.js` - 修改 `manageJobs(page=1)`
- `offer_frontend/src/api/applications.js` - 修改 `getManageApplications(page=1)`
- `offer_frontend/src/api/client.js` 或页面内 - 用户管理 API 调用
### 前端组件层4 个页面修改)
- `offer_frontend/src/views/admin/OrganizationManageView.vue`
- `offer_frontend/src/views/admin/JobManageView.vue`
- `offer_frontend/src/views/admin/ApplicationManageView.vue`
- `offer_frontend/src/views/admin/UserManageView.vue`
---
## 实现任务
### Task 1: 修改 organizations.js - 支持分页参数
**Files:**
- Modify: `offer_frontend/src/api/organizations.js`
- [ ] **Step 1: 修改 manageOrganizations 函数**
编辑 `offer_frontend/src/api/organizations.js`,将 `manageOrganizations` 函数改为(使用 params 对象方式保持与其他 API 的一致性):
```javascript
export const manageOrganizations = (page = 1) =>
client.get('/organizations/manage/', { params: { page } })
```
> 注:这个改动方式与 jobs.js 和 applications.js 中已有的 `{ params: { ... } }` 方式保持一致
- [ ] **Step 2: 验证其他导出函数不受影响**
检查文件中的其他函数(`getOrganizations`, `createOrganization` 等)保持不变。
- [ ] **Step 3: Commit**
```bash
git add offer_frontend/src/api/organizations.js
git commit -m "feat(api): 为 manageOrganizations 添加分页参数"
```
---
### Task 2: 修改 jobs.js - 支持分页参数
**Files:**
- Modify: `offer_frontend/src/api/jobs.js`
- [ ] **Step 1: 修改 manageJobs 函数**
编辑 `offer_frontend/src/api/jobs.js`,找到 `manageJobs` 函数(通常已有 params 参数),改为支持分页:
```javascript
export const manageJobs = (page = 1, params = {}) =>
client.get('/jobs/manage/', { params: { page, ...params } })
```
> 如果现有函数签名已经是 `(params = {})` 的形式,则改为:
> ```javascript
> export const manageJobs = (params = {}) =>
> client.get('/jobs/manage/', { params: { page: params.page || 1, ...params } })
> ```
- [ ] **Step 2: 验证其他导出函数**
检查 `getJobs`, `createJob`, `updateJob`, `deleteJob` 等函数保持不变。
- [ ] **Step 3: Commit**
```bash
git add offer_frontend/src/api/jobs.js
git commit -m "feat(api): 为 manageJobs 添加分页参数"
```
---
### Task 3: 修改 applications.js - 支持分页参数
**Files:**
- Modify: `offer_frontend/src/api/applications.js`
- [ ] **Step 1: 修改 getManageApplications 函数**
编辑 `offer_frontend/src/api/applications.js`,找到 `getManageApplications` 函数,改为(支持分页 + 保留其他参数):
```javascript
export const getManageApplications = (params = {}) =>
client.get('/applications/manage/', { params: { page: params.page || 1, ...params } })
```
> 调用时改为:`getManageApplications({ page: currentPage.value })`
- [ ] **Step 2: 验证其他函数**
确保 `applyJob`, `myApplications`, `updateApplicationStatus` 等函数不变。
- [ ] **Step 3: Commit**
```bash
git add offer_frontend/src/api/applications.js
git commit -m "feat(api): 为 getManageApplications 添加分页参数"
```
---
### Task 4: 修改 OrganizationManageView.vue - 添加分页
**Files:**
- Modify: `offer_frontend/src/views/admin/OrganizationManageView.vue`
**Step 4.1: 导入所需函数**
- [ ]`<script setup>` 导入部分添加(如果还没有):
```javascript
import { ElMessageBox } from 'element-plus'
import { deleteOrganization } from '@/api/organizations'
```
**Step 4.2: 添加分页和数据获取状态**
- [ ]`const form = reactive(...)` 之后添加以下状态变量:
```javascript
const allOrgs = ref([]) // 所有部门(用于下拉框)
const currentPage = ref(1) // 当前页码
const pageSize = ref(20) // 每页行数
const total = ref(0) // 总条数
```
**Step 4.3: 修改 fetchOrgs 函数**
- [ ] 将现有的 `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
}
}
```
**Step 4.4: 新增 fetchAllOrgs 函数**
- [ ]`fetchOrgs` 函数之后添加(用于获取所有部门给下拉框使用):
```javascript
const fetchAllOrgs = async () => {
try {
// 预加载前几页,保证下拉框有足够的数据
let allResults = []
for (let i = 1; i <= 5; i++) {
const { data } = await manageOrganizations(i)
allResults = allResults.concat(data.results)
if (i === Math.ceil(data.count / 20)) break // 已加载全部
}
allOrgs.value = allResults
} catch (error) {
ElMessage.error('加载部门列表失败')
}
}
```
**Step 4.5: 添加分页事件处理函数**
- [ ]`fetchAllOrgs` 函数之后添加:
```javascript
function handlePageChange(newPage) {
fetchOrgs(newPage)
}
```
**Step 4.6: 新增删除函数**
- [ ] 添加 `handleDelete()` 函数:
```javascript
async function handleDelete(id) {
try {
await ElMessageBox.confirm('确认删除该部门?', '提示', {
type: 'warning',
confirmButtonText: '确认',
cancelButtonText: '取消'
})
await deleteOrganization(id)
ElMessage.success('已删除')
currentPage.value = 1
fetchOrgs(1)
fetchAllOrgs()
} catch (error) {
if (error.name !== 'ElMessageBoxCancel') {
ElMessage.error('删除失败')
}
}
}
```
**Step 4.7: 修改保存逻辑**
- [ ] 找到 `save()` 函数,修改最后的调用为:
```javascript
currentPage.value = 1
fetchOrgs(1)
fetchAllOrgs()
```
**Step 4.8: 修改初始化**
- [ ] 修改 `onMounted` 钩子:
```javascript
onMounted(() => {
fetchOrgs()
fetchAllOrgs()
})
```
**Step 4.9: 修改表格的下拉框**
- [ ] 找到表单中的"上级公司"下拉框,修改为使用 `allOrgs`
```vue
<el-form-item label="上级公司">
<el-select v-model="form.parent" clearable placeholder="不选则为集团顶级">
<el-option v-for="o in allOrgs" :key="o.id" :value="o.id" :label="o.name" />
</el-select>
</el-form-item>
```
**Step 4.10: 修改表格模板**
- [ ] 找到 `<el-table :data="orgs"` 这一行,改为:
```vue
<el-table :data="orgs" v-loading="loading" border>
```
**Step 4.11: 添加删除按钮到操作列**
- [ ] 找到操作列(`<el-table-column label="操作"`修改为
```vue
<el-table-column label="操作" width="120">
<template #default="{ row }">
<el-button size="small" @click="openDialog(row)">编辑</el-button>
<el-button size="small" type="danger" @click="handleDelete(row.id)">删除</el-button>
</template>
</el-table-column>
```
**Step 4.12: 添加分页控件**
- [ ] `</el-table>` 之后、`<el-dialog` 之前添加
```vue
<div style="margin-top: 16px; text-align: right;">
<el-pagination
v-model:current-page="currentPage"
:page-size="pageSize"
:total="total"
@current-change="handlePageChange"
layout="total, prev, pager, next, jumper"
/>
</div>
```
- [ ] **Commit**
```bash
git add offer_frontend/src/views/admin/OrganizationManageView.vue
git commit -m "feat(admin): 为部门管理页面添加分页、删除功能
- 添加分页状态管理currentPage, pageSize, total
- 修改 fetchOrgs 支持分页参数
- 新增 fetchAllOrgs 用于下拉框完整数据
- 新增 handleDelete 删除功能
- 修改下拉框使用 allOrgs 而非 orgs
- 添加分页控件和 loading 指示
- 删除操作后重置分页和刷新数据"
```
---
### Task 5: 修改 JobManageView.vue - 添加分页
**Files:**
- Modify: `offer_frontend/src/views/admin/JobManageView.vue`
**Step 5.1: 添加分页状态**
- [ ] `<script setup>` 中添加分页相关状态 `const form = reactive(...)` 之后
```javascript
const currentPage = ref(1)
const pageSize = ref(20)
const total = ref(0)
```
**Step 5.2: 修改 fetchJobs 函数**
- [ ] 替换现有的 `fetchJobs` 函数为
```javascript
const fetchJobs = async (page = 1) => {
loading.value = true
try {
const { data } = await manageJobs(page)
jobs.value = data.results
total.value = data.count
currentPage.value = page
} catch (error) {
ElMessage.error('加载职位列表失败,请重试')
} finally {
loading.value = false
}
}
```
**Step 5.3: 添加分页事件处理**
- [ ] `fetchJobs` 之后添加
```javascript
function handlePageChange(newPage) {
fetchJobs(newPage)
}
```
**Step 5.4: 修改 handleSave 函数**
- [ ] 找到 `handleSave()` 函数中的 `fetchJobs()` 调用改为
```javascript
currentPage.value = 1
fetchJobs(1)
```
**Step 5.5: 修改表格 v-loading**
- [ ] 找到 `<el-table :data="jobs"` 这一行确保有 `v-loading="loading"`可能已有
**Step 5.6: 添加分页控件**
- [ ] `</el-table>` 之后添加
```vue
<div style="margin-top: 16px; text-align: right;">
<el-pagination
v-model:current-page="currentPage"
:page-size="pageSize"
:total="total"
@current-change="handlePageChange"
layout="total, prev, pager, next, jumper"
/>
</div>
```
- [ ] **Commit**
```bash
git add offer_frontend/src/views/admin/JobManageView.vue
git commit -m "feat(admin): 为职位管理页面添加分页功能"
```
---
### Task 6: 修改 ApplicationManageView.vue - 添加分页
**Files:**
- Modify: `offer_frontend/src/views/admin/ApplicationManageView.vue`
**Step 6.1: 添加分页状态**
- [ ] `<script setup>` 中添加 `const currentResume = ref(null)` 之后
```javascript
const currentPage = ref(1)
const pageSize = ref(20)
const total = ref(0)
```
**Step 6.2: 新建 fetchApplications 函数**
- [ ] `onMounted` 钩子之前添加新函数
```javascript
const fetchApplications = async (page = 1) => {
loading.value = true
try {
const { data } = await getManageApplications({ page })
applications.value = data.results
total.value = data.count
currentPage.value = page
} catch (error) {
ElMessage.error('加载投递列表失败,请重试')
} finally {
loading.value = false
}
}
```
**Step 6.3: 添加分页事件处理**
- [ ] `fetchApplications` 之后添加函数
```javascript
function handlePageChange(newPage) {
fetchApplications(newPage)
}
```
**Step 6.4: 修改 updateStatus 函数**
- [ ] 找到 `updateStatus()` 函数修改最后为停留在当前页重新加载
```javascript
fetchApplications(currentPage.value)
```
**Step 6.5: 修改 onMounted**
- [ ] 修改 `onMounted` 钩子中的初始化调用为
```javascript
onMounted(() => {
fetchApplications()
})
```
**Step 6.6: 修改表格**
- [ ] 找到 `<el-table :data="applications"` 这一行确保有 `v-loading="loading"`
```vue
<el-table :data="applications" v-loading="loading" border>
```
**Step 6.7: 添加分页控件**
- [ ] `</el-table>` 之后、`<el-dialog` 之前添加
```vue
<div style="margin-top: 16px; text-align: right;">
<el-pagination
v-model:current-page="currentPage"
:page-size="pageSize"
:total="total"
@current-change="handlePageChange"
layout="total, prev, pager, next, jumper"
/>
</div>
```
- [ ] **Commit**
```bash
git add offer_frontend/src/views/admin/ApplicationManageView.vue
git commit -m "feat(admin): 为投递管理页面添加分页功能
- 新增 fetchApplications 函数支持分页
- 修改 updateStatus 保留当前页并重新加载数据
- 添加分页状态和事件处理
- 添加分页控件和 loading 指示"
```
---
### Task 7: 修改 UserManageView.vue - 添加分页
**Files:**
- Modify: `offer_frontend/src/views/admin/UserManageView.vue`
**Step 7.1: 添加分页相关状态**
- [ ] `<script setup>` 中添加 `const saving = ref(false)` 之后
```javascript
const loading = ref(false) // 注意:该页面原本没有 loading ref需要新添加
const currentPage = ref(1)
const pageSize = ref(20)
const total = ref(0)
```
**Step 7.2: 修改 fetchUsers 函数**
- [ ] 替换现有的 `fetchUsers` 函数为
```javascript
const fetchUsers = async (page = 1) => {
loading.value = true
try {
const { data } = await client.get('/auth/users/', { params: { page } })
users.value = data.results
total.value = data.count
currentPage.value = page
} catch (error) {
ElMessage.error('加载用户列表失败,请重试')
} finally {
loading.value = false
}
}
```
> **说明**: 假设后端已启用分页,响应格式为 `{ count, results, ... }`。如果返回格式不同,需要相应调整。
**Step 7.3: 添加分页事件处理**
- [ ] 添加函数
```javascript
function handlePageChange(newPage) {
fetchUsers(newPage)
}
```
**Step 7.4: 修改 save 函数**
- [ ] 找到 `save()` 函数修改其中的 `fetchUsers()` 调用为
```javascript
currentPage.value = 1
fetchUsers(1)
```
**Step 7.5: 修改 onMounted**
- [ ] 确保 `onMounted` 中调用为
```javascript
onMounted(() => {
fetchUsers()
fetchOrgs()
})
```
**Step 7.6: 修改表格模板**
- [ ] 找到 `<el-table :data="users"` 这一行改为
```vue
<el-table :data="users" v-loading="loading" border>
```
**Step 7.7: 添加分页控件**
- [ ] `</el-table>` 之后、`<el-dialog` 之前添加
```vue
<div style="margin-top: 16px; text-align: right;">
<el-pagination
v-model:current-page="currentPage"
:page-size="pageSize"
:total="total"
@current-change="handlePageChange"
layout="total, prev, pager, next, jumper"
/>
</div>
```
- [ ] **Commit**
```bash
git add offer_frontend/src/views/admin/UserManageView.vue
git commit -m "feat(admin): 为用户管理页面添加分页功能
- 新增 loading ref
- 修改 fetchUsers 支持分页参数
- 添加分页状态和事件处理
- 添加分页控件和 loading 指示
- 新增用户后重置分页到第1页"
```
---
### Task 8: 功能测试
**Files:**
- Test: 手动测试前端功能
- [ ] **Step 1: 启动前端开发服务器**
```bash
cd offer_frontend
npm run dev
```
- [ ] **Step 2: 测试部门管理页面分页**
- 访问 `/admin/organizations`
- 验证首次加载显示第 1 总数正确
- 点击下一页数据更新
- 点击最后一页显示剩余数据
- 新增部门后页面返回第 1
- [ ] **Step 3: 测试职位管理页面分页**
- 访问 `/admin/jobs`
- 验证分页功能翻页跳转
- 新增/编辑职位后返回第 1
- [ ] **Step 4: 测试投递管理页面分页**
- 访问 `/admin/applications`
- 验证分页功能翻页跳转
- **关键**: 修改投递状态如改为"已查看"页面停留在当前页并重新加载当前页数据不重置分页
- 验证修改生效
- [ ] **Step 5: 测试用户管理页面分页**
- 访问 `/admin/users`
- 验证分页功能
- 新增/编辑用户后返回第 1
- [ ] **Step 6: 错误处理测试**
- 模拟网络错误F12 打开控制台修改请求验证是否显示错误提示
- 点击分页按钮重试是否能恢复
- [ ] **Step 7: Commit**
```bash
git add -A
git commit -m "test: 手动验证所有管理表格分页功能"
```
---
## 验收标准
所有 4 个管理表格都支持分页
- 表格显示当前页数据最多 20
- 底部分页控件正常工作上一页下一页页码跳转
- 总数显示正确
- 新增/编辑/删除后自动返回第 1
- 网络错误时显示错误提示允许重试
- 加载中显示 loading 指示器
代码质量
- 所有改动遵循现有代码风格
- 分页逻辑与组件逻辑清晰分离
- 无性能问题不应有多余的 API 调用
Git 提交
- 每个页面一个 commit
- API 层改动一个 commit
- Commit message 清晰规范
---
## 已知限制与后续优化
1. **下拉框数据**仅部门管理当前下拉框显示当前页的部门列表如果部门数量超过 100部分部门无法在下拉框中选择可在后续优化为搜索型下拉框或添加"加载更多"。
2. **搜索/过滤**当前不支持与搜索结合的分页如需要可在分页基础上添加搜索字段和 API 参数
3. **代码复用**4 个页面的分页逻辑基本相同可在后续抽象为 Vue Composable `usePagination`进一步 DRY
---
## 检查清单
完成实现前请验证
- [ ] 所有 API 函数已更新支持 `page` 参数
- [ ] 所有页面的分页状态变量已添加
- [ ] 所有页面的 fetch 函数已修改支持分页
- [ ] 所有页面的模板已添加 `<el-pagination>` 控件
- [ ] 所有 CRUD 操作后都重置到第 1
- [ ] 所有页面都有 loading 状态指示
- [ ] Git 提交消息遵循约定式提交规范
- [ ] 手动测试通过所有 4 个页面