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

7.9 KiB
Raw Blame History

学生档案报告前端预览 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.tsgenerateReport 方法之后,新增:

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 方法之后新增:

@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: 验证后端接口
# 启动后端后测试
curl -H "Authorization: Bearer <token>" http://localhost:3000/api/archive/1/report-html

预期返回 { "html": "<!DOCTYPE html>..." }HTML 内容完整。

  • Step 4: Commit
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

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();
  }
};

将按钮文本和图标改为预览:

extra={
  <PermissionButton
    permission="student:view"
    type="primary"
    icon={<EyeOutlined />}
    onClick={handlePreviewReport}
  >
    预览报告
  </PermissionButton>
}

需要在文件顶部 import 添加 EyeOutlined(已有 DownloadOutlined 可删除)。

  • Step 2: 浏览器验证
# 启动前端 dev server
cd apps/admin && npm run dev
  1. 打开学生档案页面
  2. 点击「预览报告」按钮
  3. 确认新窗口打开完整报告,样式正确
  4. 新窗口 Ctrl+P → 确认打印预览分页正常
  • Step 3: Commit
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

// 删除这两行
import puppeteer from 'puppeteer';
import { Response } from 'express';
  • Step 2: 删除旧的 report 端点

archive.controller.ts 中删除 GET /archive/:studentId/reportgenerateReport 方法(第 343-362 行)。

同步删除 @Res 装饰器的 import检查 @Res 是否被其他地方使用,如果只在 generateReport 中使用,则一并移除)。

  • Step 3: 移除 puppeteer 依赖
cd apps/server && npm uninstall puppeteer
  • Step 4: 清理 Dockerfile

读取 apps/server/Dockerfile,移除 Chromium/ Puppeteer 相关依赖安装。常见需要移除的:

  • chromium / chromium-browser 等包

  • Puppeteer 相关环境变量如 PUPPETEER_SKIP_CHROMIUM_DOWNLOAD

  • Step 5: 验证构建

cd apps/server && npm run build

确认编译通过,无 puppeteer 相关 import 错误。

  • Step 6: Commit
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: 启动完整环境
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:

  • 新增 /report-html 端点 → Task 1 Step 2
  • 复用现有 buildHtml → Task 1 Step 1
  • 删除 Puppeteer 和旧端点 → Task 3
  • 前端按钮改为预览 → Task 2
  • 验收标准全部覆盖 → Task 4

2. Placeholder scan: 无 TBD/TODO/占位符。

3. Type consistency: generateReportHtml 签名在三处一致Service 定义、Controller 调用、接口文档。