---
name: "Grades Viewer(自用成绩看板)"
status: "live"
domain: "grades-viewer.linweiheng2009.workers.dev"
stack: ["Astro 7", "@astrojs/cloudflare 14", "Cloudflare Worker Static Assets", "D1", "Claude Haiku Vision"]
tags: ["product", "family", "parent-tool"]
priority: "medium"
summary: "自用成绩看板 · 学生 CRUD + CSV/拍照 OCR 导入 + 趋势/雷达图 + 老师评语融合的 AI 评语 + PDF 导出 · 仅自用无登录"
started: 2026-08-25
updated: 2026-09-22
visibility: "private"
opc: 22
---
# Grades Viewer

`grades-viewer.linweiheng2009.workers.dev` — 自用孩子成绩看板。

## 形态

- 单一学生看板页:趋势图 + 雷达图 + 最近考试 + 下次预测 + AI 评语 + 老师评语卡片
- 列表/详情双视图,首页统计
- 浏览器原生 A4 PDF 导出 (window.print CSS)
- 评论 JSON 导出
- 无登录、无社交、无 public share

## 数据录入 (两条路)

### CSV (P509+)
- 13 列: `date / type / semester / subject / score / full_score / class_rank / grade_rank / total_score / class_size / grade_size / teacher_comment / notes`
- 走 `/import` 页面 → dry-run 校验预览 → 确认导入

### 拍成绩单图片 OCR (P511)
- 调 Claude Haiku Vision,严格 `output_config.format: { type: 'json_schema', strict: true }` 输出
- HEIC 客户端 canvas 转 JPEG (iPhone 默认格式)
- 4MB 上限,JPEG/PNG/WEBP/GIF 白名单
- 同样的 13 列 CSV → 走相同 import 流程 (零代码重复)

## 数据展示

- **TrendChart 趋势图** (P513) — Tailwind 8 色柔和调色板,按学期分组多列 X 轴,viewBox 按数据量自适应,3 点移动平均,90/60 阈值线,分数标签 ≥12 点自动隐藏
- RadarChart 雷达图 / 班级中位线
- 最近考试 + 线性回归预测下次分数 (3 点滑动窗口)

## AI 评语 (3 模式 + 老师评语融合)

- 类型: `exam` / `semester` / `year`
- 模型: Claude Haiku (`ANTHROPIC_API_KEY` / `ANTHROPIC_BASE_URL` 可换),无 key 走规则引擎兜底
- P513 起:**`teacher_comments` 进 prompt** — exams.teacher_comment 字段 + 按 type 过滤后作为时间线上下文,让评语从冷冰冰数字变成"上次老师说..."
- 评语保存到 `ai_comments` 表,可重复生成 / 删除 / JSON 导出

## 老师评语卡片 (P513 新)

- `exams` 表加 `teacher_comment TEXT` 列
- 详情页 + 打印页 同步展示 (倒序 + 班级排名徽标)
- CSV/OCR 端到端:13 列 schema + UPDATE `COALESCE(?, teacher_comment)` 保留手工编辑

## 数据示例 (D1 已 seed)

- `seed-demo.sql` — 小明 (4年级, 4 exams × 3 科 = 12 分数)
- `seed-multi-grade.sql` — 乐乐 (1-3 年级共 23 场 × 81 分数, 含 5 条真实风格老师评语覆盖各学年关键节点)

## 工程要点

- Astro 7.3 SSR + @astrojs/cloudflare 14 (Worker Static Assets, **不是** Pages)
- `cloudflare:workers` env 取 D1 (Astro 6+ 已删 `Astro.locals.runtime.env`)
- D1 `db.exec()` 多行 SQL 不可靠 → `db.prepare(s).run()` 串行
- `db` 接口抽象兼容 better-sqlite3 本地 + D1 远程,dev/prod 同代码
- `import { env } from 'cloudflare:workers'` 替代 locals.runtime

## 部署

- Worker: `npm run deploy` (= predeploy 注入 main + wrangler deploy + postbuild 清 ASSETS binding)
- D1 schema: 手动 `wrangler d1 execute --remote --file=schema.sql` 一次 (worker 内 ensureSchema 作 fallback)
- 当前版本: `0a5b6189-488b-4663-8e0c-1e0694eccb95`

## 踩坑

- 详见 memory `grades-viewer-deploy-cf-d1` (vite sync 阶段读 main / D1 multiline exec / ASSETS binding 冲突)
- 详见 memory `grades-viewer-ocr-vision` (json_schema strict / HEIC / 老师评语字段映射)

## 下一步

- [ ] AI 自动发现成绩趋势异常并提示
- [ ] 多孩子横向对比 (同年级 / 同班级)
- [ ] OCR 准确度自检 (同一张图两次跑,字段不一致 → 报警)
- [ ] 老师评语自动按学科拆分 (现在 1 张图只 1 条 teacher_comment)

## 设置页 (P514)

- **主题切换**: ☀️🌙🖥️ 三模式 (light/dark/system),localStorage 持久化,early-inject 避免 FOUC,跟随系统模式自动响应 `prefers-color-scheme` 变化
- **AI 连接测试**: `POST /api/ai-test` 真发 16 token 极简请求 (max_tokens=16, prompt "回复一个字"),返回 `{ok, latencyMs, reply, costUSD}` 端到端验证 key+baseUrl+model
- **学期别名管理**: `kv_settings` 单行 JSON `{"2025-fall": "2025 秋季"}`,TrendChart 顶部标签 + 详情页学期卡片消费该别名
- **数据导出**: `GET /api/settings-export` 返回 JSON (students/exams/scores/comments),Content-Disposition 触发浏览器下载;支持 `?student_id=xxx` 单学生
- **重置**: `POST /api/settings-reset` body `{"confirm": "RESET"}`,清空所有业务表 (kv_settings 保留)

## 工程要点 (P514 增量)

- `kv_settings` 表: 单行 key-value 配置,新增 `getSetting/setSetting/listSettings` 三个 repo 函数
- `ALLOWED_KEYS` 白名单过滤 `/api/settings` 写入,只暴露允许的 key
- 别名 JSON 必须合法,set 端做 `JSON.parse` 校验
- Astro 7 + @astrojs/cloudflare 14 已删 `Astro.locals.runtime.env`,探测 D1 必须用 `import { env as cfEnv } from 'cloudflare:workers'` (踩过坑,settings.astro 第一次部署直接 500)