plan: 学生档案报告前端预览实施计划
This commit is contained in:
@@ -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<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 调用、接口文档。
|
||||
Reference in New Issue
Block a user