Files
gongxue-base/docs/superpowers/plans/2026-07-07-archive-report-frontend-preview.md

269 lines
7.9 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 学生档案报告前端预览 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<string>`
- Produces: `GET /archive/:studentId/report-html``{ html: string }`
- [ ] **Step 1: 在 ArchiveReportService 新增 generateReportHtml 方法**
`archive-report.service.ts``generateReport` 方法之后,新增:
```typescript
async generateReportHtml(studentId: number): Promise<string> {
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 <token>" http://localhost:3000/api/archive/1/report-html
```
预期返回 `{ "html": "<!DOCTYPE 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={
<PermissionButton
permission="student:view"
type="primary"
icon={<EyeOutlined />}
onClick={handlePreviewReport}
>
预览报告
</PermissionButton>
}
```
需要在文件顶部 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 调用、接口文档。