fix(AI 学情报告): pinnedOnly 那支返回裸数组,页面每次打开都白屏
Some checks failed
Deploy / deploy (push) Has been cancelled

`GET /admin/ai/reports` 同一个 URL 返回两种形状:带 limit/offset 时是
`{results, total}`,带 `pinnedOnly=true` 时是裸数组。前端 getPinnedAIReports
声明的返回类型是 AdminAiReportList、读的是 `res.results`,于是
`pinnedReports.value` 被赋成 undefined,模板里 `pinnedReports.length > 0`
在渲染时抛 `Cannot read properties of undefined (reading 'length')`,
整个页面白屏。空库也照抛 —— 裸数组同样没有 .results。

置顶那支改成同样的 `{results, total}` 信封,total 就是条数。「不分页」的意图
没变,只是别再让一个 URL 有两种形状:调用方没法照着一个类型写,类型标注也就
成了谎话。

验证:本机起 dev 栈,用 headless chromium 走 CDP 抓未捕获异常。修之前
/admin/ai/reports 稳定复现 `TypeError ... at Proxy._sfc_render
(src/admin/ai/list.vue:182)`;修之后同一页面零异常,且置顶提示条正确渲染出
「以下 2 位用户的 AI 分析报告已被锁定」加两个用户标签、表格 3 行,PIN 切换
往返(取消 → total 1、再 PIN → total 2)也正常。

顺带把另外 19 个后台/学生端页面扫了一遍,只有这一处抛异常。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-27 00:18:58 -06:00
parent a75c70c82d
commit d3757954cd

View File

@@ -43,13 +43,20 @@ adminAiRoutes.get("/ai/reports", requireTeacher, async (c) => {
const username = c.req.query("username")?.trim()
const where = username ? ilike(schema.user.username, `%${username}%`) : undefined
// 置顶列表不分页:它是「每个学生最新钉住的那份」,数量等于学生数,前端一次性拿走
// 置顶列表不分页:它是「每个学生最新钉住的那份」,数量等于学生数,前端一次性拿走
// 但**形状必须和分页那支一样**:同一个 URL 返回两种形状,调用方没法照着一个类型写。
// 这里原来返回裸数组,前端 getPinnedAIReports 声明的是 AdminAiReportList、
// 读的是 res.results于是拿到 undefined`pinnedReports.length` 在渲染时抛
// 「Cannot read properties of undefined」——空库也照抛这个页面每次打开都白屏。
if (c.req.query("pinnedOnly") === "true") {
const rows = await db.select(listColumns).from(schema.aiAnalysis)
.innerJoin(schema.user, eq(schema.aiAnalysis.userId, schema.user.id))
.where(and(eq(schema.aiAnalysis.isPinned, true), where))
.orderBy(desc(schema.aiAnalysis.createTime))
return success(c, rows.map(listItem))
return success(c, adminAiReportListSchema.parse({
results: rows.map(listItem),
total: rows.length,
}))
}
const limit = queryInteger(c.req.query("limit"), 10, { min: 1, max: 250 })