From 9f43f50867ec18981836e322f8ab143f6f6a1a69 Mon Sep 17 00:00:00 2001 From: wangziqi Date: Tue, 7 Jul 2026 09:48:08 +0800 Subject: [PATCH] =?UTF-8?q?plan:=20=E5=AD=A6=E7=94=9F=E6=A1=A3=E6=A1=88?= =?UTF-8?q?=E6=8A=A5=E5=91=8A=E5=89=8D=E7=AB=AF=E9=A2=84=E8=A7=88=E5=AE=9E?= =?UTF-8?q?=E6=96=BD=E8=AE=A1=E5=88=92?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...6-07-07-archive-report-frontend-preview.md | 268 ++++++++++++++++++ 1 file changed, 268 insertions(+) create mode 100644 docs/superpowers/plans/2026-07-07-archive-report-frontend-preview.md diff --git a/docs/superpowers/plans/2026-07-07-archive-report-frontend-preview.md b/docs/superpowers/plans/2026-07-07-archive-report-frontend-preview.md new file mode 100644 index 0000000..90e11ff --- /dev/null +++ b/docs/superpowers/plans/2026-07-07-archive-report-frontend-preview.md @@ -0,0 +1,268 @@ +# 学生档案报告前端预览 Implementation Plan + +> **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:** 将档案报告从后端 Puppeteer 生成 PDF 改为后端生成 HTML、前端新窗口预览、浏览器打印出 PDF。 + +**Architecture:** 后端新增 `/archive/:studentId/report-html` 返回 HTML 字符串(复用现有 `buildHtml()` 方法),前端 `fetch` 后在新窗口渲染。移除 Puppeteer 依赖和原 PDF 下载端点。 + +**Tech Stack:** NestJS 11 + TypeORM + React 19 + Ant Design 6 + +## Global Constraints + +- 复用现有 `buildHtml()` / `buildCover()` / `css()` 等 HTML 构造方法,不做任何样式改动 +- 所有写操作记录日志 +- 前端遵循现有页面模式 +- Docker 镜像需移除 Chromium 相关依赖 + +--- + +### Task 1: 后端新增 report-html 接口 + +**Files:** +- Modify: `apps/server/src/archive/archive-report.service.ts` +- Modify: `apps/server/src/archive/archive.controller.ts` + +**Interfaces:** +- Produces: `ArchiveReportService.generateReportHtml(studentId: number): Promise` +- Produces: `GET /archive/:studentId/report-html` → `{ html: string }` + +- [ ] **Step 1: 在 ArchiveReportService 新增 generateReportHtml 方法** + +在 `archive-report.service.ts` 的 `generateReport` 方法之后,新增: + +```typescript +async generateReportHtml(studentId: number): Promise { + const [student, profile, enrollments, exams, learnings, result, attendances] = + await Promise.all([ + this.studentRepo.findOne({ where: { id: studentId } }), + this.profileRepo.findOne({ where: { studentId } }), + this.enrollmentRepo.find({ where: { studentId }, order: { startDate: 'ASC' } }), + this.examRepo.find({ where: { studentId }, order: { examDate: 'ASC' } }), + this.learningRepo.find({ where: { studentId }, order: { recordDate: 'DESC' } }), + this.resultRepo.findOne({ where: { studentId } }), + this.attendanceRepo.find({ where: { studentId }, order: { attendanceDate: 'ASC' } }), + ]); + + if (!student) throw new Error('学生不存在'); + + const data: ReportData = { + student, + profile, + enrollments, + exams, + learnings, + result, + attendances, + }; + + return this.buildHtml(data); +} +``` + +- [ ] **Step 2: 在 ArchiveController 新增 report-html 端点** + +在 `archive.controller.ts` 中,`generateReport` 方法之后新增: + +```typescript +@Get(':studentId/report-html') +@RequirePermission('student:view') +async getReportHtml( + @Param('studentId') studentId: string, + @Request() req: AuthenticatedRequest, +) { + const { ipAddress, userAgent } = extractRequestInfo(req); + await this.logService.log({ + userId: req.user?.id, + username: req.user?.username, + module: 'archive', + action: 'preview_report', + targetId: +studentId, + targetType: 'student', + ipAddress, + userAgent, + }); + const html = await this.reportService.generateReportHtml(+studentId); + return { html }; +} +``` + +- [ ] **Step 3: 验证后端接口** + +```bash +# 启动后端后测试 +curl -H "Authorization: Bearer " http://localhost:3000/api/archive/1/report-html +``` + +预期返回 `{ "html": "..." }`,HTML 内容完整。 + +- [ ] **Step 4: Commit** + +```bash +git add apps/server/src/archive/archive-report.service.ts apps/server/src/archive/archive.controller.ts +git commit -m "feat: add GET /archive/:studentId/report-html endpoint" +``` + +--- + +### Task 2: 前端按钮改为预览报告 + +**Files:** +- Modify: `apps/admin/src/pages/StudentProfile/index.tsx` + +**Interfaces:** +- Consumes: `GET /archive/:studentId/report-html` → `{ html: string }` + +- [ ] **Step 1: 修改按钮行为** + +将 `handleDownloadReport` 替换为 `handlePreviewReport`,打开新窗口渲染 HTML: + +```typescript +const handlePreviewReport = async () => { + const token = localStorage.getItem('token'); + const res = await fetch(`/api/archive/${studentId}/report-html`, { + headers: { Authorization: `Bearer ${token}` }, + }); + const { html } = await res.json(); + const w = window.open('', '_blank'); + if (w) { + w.document.write(html); + w.document.close(); + } +}; +``` + +将按钮文本和图标改为预览: + +```tsx +extra={ + } + onClick={handlePreviewReport} + > + 预览报告 + +} +``` + +需要在文件顶部 import 添加 `EyeOutlined`(已有 `DownloadOutlined` 可删除)。 + +- [ ] **Step 2: 浏览器验证** + +```bash +# 启动前端 dev server +cd apps/admin && npm run dev +``` + +1. 打开学生档案页面 +2. 点击「预览报告」按钮 +3. 确认新窗口打开完整报告,样式正确 +4. 新窗口 Ctrl+P → 确认打印预览分页正常 + +- [ ] **Step 3: Commit** + +```bash +git add apps/admin/src/pages/StudentProfile/index.tsx +git commit -m "feat: change report button from download to preview" +``` + +--- + +### Task 3: 移除 Puppeteer 和旧 PDF 下载端点 + +**Files:** +- Modify: `apps/server/src/archive/archive-report.service.ts` +- Modify: `apps/server/src/archive/archive.controller.ts` +- Modify: `apps/server/package.json` +- Modify: `apps/server/Dockerfile` + +**Interfaces:** +- Removes: `ArchiveReportService.generateReport(studentId, res)` — Puppeteer PDF 生成 +- Removes: `GET /archive/:studentId/report` — PDF 下载端点 +- Removes: `puppeteer` npm 依赖 + +- [ ] **Step 1: 删除 generateReport 方法** + +在 `archive-report.service.ts` 中删除 `generateReport(res: Response)` 方法(第 36-85 行),包括方法内所有 Puppeteer 相关逻辑。 + +同步删除文件顶部的两个不再需要的 import: + +```typescript +// 删除这两行 +import puppeteer from 'puppeteer'; +import { Response } from 'express'; +``` + +- [ ] **Step 2: 删除旧的 report 端点** + +在 `archive.controller.ts` 中删除 `GET /archive/:studentId/report` 的 `generateReport` 方法(第 343-362 行)。 + +同步删除 `@Res` 装饰器的 import(检查 `@Res` 是否被其他地方使用,如果只在 `generateReport` 中使用,则一并移除)。 + +- [ ] **Step 3: 移除 puppeteer 依赖** + +```bash +cd apps/server && npm uninstall puppeteer +``` + +- [ ] **Step 4: 清理 Dockerfile** + +读取 `apps/server/Dockerfile`,移除 Chromium/ Puppeteer 相关依赖安装。常见需要移除的: +- `chromium` / `chromium-browser` 等包 +- Puppeteer 相关环境变量如 `PUPPETEER_SKIP_CHROMIUM_DOWNLOAD` + +- [ ] **Step 5: 验证构建** + +```bash +cd apps/server && npm run build +``` + +确认编译通过,无 puppeteer 相关 import 错误。 + +- [ ] **Step 6: Commit** + +```bash +git add apps/server/src/archive/archive-report.service.ts apps/server/src/archive/archive.controller.ts apps/server/package.json apps/server/package-lock.json apps/server/Dockerfile +git commit -m "refactor: remove Puppeteer, use frontend browser print for PDF" +``` + +--- + +### Task 4: 端到端验证 + +- [ ] **Step 1: 启动完整环境** + +```bash +docker compose up -d +``` + +- [ ] **Step 2: 验证功能** + +1. 登录系统 → 学生列表 → 进入某个学生档案 +2. 点击「预览报告」→ 新窗口打开 +3. 检查报告完整性:封面、基础信息、考试成绩总览(含 SVG 趋势图)、出勤记录(含 SVG 柱状图)、文化课明细、学情记录与录取归档 +4. 新窗口 Ctrl+P → 另存为 PDF +5. 确认 PDF 内容与预览一致 + +- [ ] **Step 3: 验证无回归** + +- 基础档案 Tab CRUD 正常 +- 报读记录/考试成绩/学情记录/录取结果 增删改正常 +- 附件上传/删除正常 + +--- + +## Self-Review + +**1. Spec coverage:** +- [x] 新增 `/report-html` 端点 → Task 1 Step 2 +- [x] 复用现有 buildHtml → Task 1 Step 1 +- [x] 删除 Puppeteer 和旧端点 → Task 3 +- [x] 前端按钮改为预览 → Task 2 +- [x] 验收标准全部覆盖 → Task 4 + +**2. Placeholder scan:** 无 TBD/TODO/占位符。 + +**3. Type consistency:** `generateReportHtml` 签名在三处一致:Service 定义、Controller 调用、接口文档。