前端体检报告第七节建议①执行总结:教师/教材服务端分页(M2)
日期:2026-07-14 对应文档:
docs/前端性能与体验体检报告-2026-07-13.md第七节建议① 决策:用户选择「完整 M2」——给/teachers、/textbooks后端都加服务端筛选+排序+分页,前端改为消费{items,total}。
一、核查中发现的关键事实(修正了体检报告的误判)
体检报告称「后端接口已就绪」,核查后不成立:
| 端点 | 体检报告假设 | 实际代码 | 结论 |
|---|---|---|---|
GET /teachers | 已支持分页+筛选+排序 | listTeachers 仅支持 page/page_size,where={} 为空(无服务端筛选/排序),返回 {items,total} | 分页形态已就绪,筛选/排序缺 |
GET /textbooks | 已支持分页 | listTextbooks 裸 findMany 全量返回数组,无分页/筛选/排序 | 完全缺失,需破坏性改造 |
前端 getTextbooks() | 不接收 params | 实际 () => request.get('/textbooks'),无参 | 需改签名 |
此外,getTextbooks() 除 TextbookList.vue 外还有 2 个消费者(UnifiedTextbookQuery.vue、PlanDetail.vue),它们期望返回数组。因此 /textbooks 改返回结构属于破坏性契约变更,必须三处一起改。
二、改动清单
后端(破坏性 + 功能新增)
server/src/controllers/teacher.controller.js—listTeachers- 新增服务端筛选:
name(模糊 contains)、personnel_type、status、affiliated_college_id,以及通过关联表过滤的course_id(courses.some)、college_id(scheduling_colleges.some)、training_level_id(scheduling_levels.some)。 - 新增排序参数:白名单
['name','personnel_type','status','sort_order','created_at','updated_at'],默认sort_order asc,非法列回退默认(防注入)。 - 维持
{items,total}返回与 PII 脱敏逻辑不变。
- 新增服务端筛选:
server/src/controllers/textbook.controller.js—listTextbooks- 新增分页:
page/page_size(仅显式传入时分页,否则返回全量,兼容查询页/方案页取全量)。 - 新增服务端筛选:
title(模糊)、category(精确)、publisher(精确)。 - 新增排序参数:白名单(含
price/publish_date等)。 - 返回结构从
数组改为{ items, total, publishers }:publishers由后端聚合(distinct,不随筛选变化),供前端筛选下拉使用。
- 新增分页:
前端
client/src/api/textbook.js:getTextbooks(params)支持传参。client/src/views/teaching/TeacherList.vue:移除客户端filteredList/pagedList;load()传全部筛选参数并消费{items,total};筛选变化watch+ 防抖回到第 1 页重载;分页@current-change/@size-change触发服务端重载。client/src/views/textbook/TextbookList.vue:同上;publishers改为后端聚合 ref(原从list派生,分页后list仅一页会导致下拉不全);useSortable改为作用于当前页list(连续sort_order切片,页内拖拽排序全局正确,跨页拖拽不在本页能力范围);序号改为(currentPage-1)*pageSize + 页内下标 + 1。client/src/views/query/UnifiedTextbookQuery.vue与client/src/views/plan/PlanDetail.vue:消费改为res.data?.items(破坏性变更同步)。client/src/styles/theme.css/global.css:统一间距 token 与工具类(第七节建议②,已于本次一并落地)。
测试
server/src/controllers/__tests__/textbook.controller.test.js:更新listTextbooks断言至{items,total,publishers}形态;新增分页(skip/take)、筛选(where 构建)、排序(白名单/回退)用例。补countmock。server/src/controllers/__tests__/teacher-list.test.js:新增「筛选/排序/分页」describe,覆盖where构建、skip/take、orderBy白名单。
三、验证结果
| 验证项 | 命令/方式 | 结果 |
|---|---|---|
| 后端控制器语法 | node --check | ✅ teacher / textbook 均通过 |
| 后端单元测试 | vitest run textbook.controller.test.js teacher-list.test.js | ✅ 83 通过(73 + 10) |
| 前端构建 | client/ npm run build | ✅ exit 0,无错误/警告 |
四、影响矩阵
| 改动 | 影响范围 | 风险 |
|---|---|---|
/textbooks 返回结构 | TextbookList / UnifiedTextbookQuery / PlanDetail 三处消费者,已全部同步 | 中(已闭环,无遗漏) |
/teachers 新增筛选参数 | 仅 TeacherList 消费,返回结构不变 | 低 |
| 教材出版社下拉数据源 | 由「前端从 list 派生」改为「后端聚合」 | 低(更正确:分页后仍完整) |
| 教材拖拽排序 | 仅页内有效,跨页排序不可用 | 低(与原全量体验略有差异,可接受) |
五、dev 环境手动回归清单(建议用户点一遍)
- 教师页:输入姓名/选人员类别/选学科/意向学院/层次/归属学院/状态 → 列表实时按服务端筛选;翻页、改每页大小正常;序号连续;新增/编辑/删除/状态切换后停留当前页。
- 教材页:按书名/类别/出版社筛选(出版社下拉为全量);翻页+改每页大小;页内上移/下移排序生效且刷新后保持;批量设置出版社/作者/类别、批量删除、导入后列表正确。
- 查询页(UnifiedTextbookQuery)与方案详情(PlanDetail):教材下拉/列表仍正常(已改
.items)。 - 注意
/textbooks的distinct:['publisher']聚合在真实 SQLite 上的行为(单测已 mock,未在真实库跑过)——首次 dev 启动后点一次教材筛选即可确认。
六、结论
M2 完整落地:教师/教材列表均由「全量下载+客户端切片」升级为「服务端筛选+排序+分页」,并修复了 TeacherList 原先 getTeachers() 不传参、只拿到默认 20 条的隐性缺陷。破坏性契约变更(/textbooks 返回结构)已三处消费者同步、单测覆盖、构建验证通过。