From d3757954cdb58b8a493783b0d30ed9d2211f3c53 Mon Sep 17 00:00:00 2001 From: yuetsh <517252939@qq.com> Date: Thu, 27 Aug 2026 00:18:58 -0600 Subject: [PATCH] =?UTF-8?q?fix(AI=20=E5=AD=A6=E6=83=85=E6=8A=A5=E5=91=8A):?= =?UTF-8?q?=20pinnedOnly=20=E9=82=A3=E6=94=AF=E8=BF=94=E5=9B=9E=E8=A3=B8?= =?UTF-8?q?=E6=95=B0=E7=BB=84=EF=BC=8C=E9=A1=B5=E9=9D=A2=E6=AF=8F=E6=AC=A1?= =?UTF-8?q?=E6=89=93=E5=BC=80=E9=83=BD=E7=99=BD=E5=B1=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `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 --- apps/api/src/routes/admin/ai.ts | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/apps/api/src/routes/admin/ai.ts b/apps/api/src/routes/admin/ai.ts index 5490b8f..1acb996 100644 --- a/apps/api/src/routes/admin/ai.ts +++ b/apps/api/src/routes/admin/ai.ts @@ -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 })