From c6f7d9f7e3a48da5bcfc77eb7cc3da116b28b524 Mon Sep 17 00:00:00 2001 From: hmo Date: Tue, 28 Apr 2026 12:27:26 +0800 Subject: [PATCH] docs: add export preview implementation plan --- .../plans/2026-04-28-export-preview-plan.md | 430 ++++++++++++++++++ 1 file changed, 430 insertions(+) create mode 100644 docs/superpowers/plans/2026-04-28-export-preview-plan.md diff --git a/docs/superpowers/plans/2026-04-28-export-preview-plan.md b/docs/superpowers/plans/2026-04-28-export-preview-plan.md new file mode 100644 index 0000000..f1816ce --- /dev/null +++ b/docs/superpowers/plans/2026-04-28-export-preview-plan.md @@ -0,0 +1,430 @@ +# 导出预览功能实现计划 + +> **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:** 在方案详情页新增「预览」功能,用户选择模板后可即时看到套用模板导出后的最终效果(所见即所得,接近 PDF 视觉效果) + +**Architecture:** 后端新增 preview 路由,复用 export_pdf 的模板渲染逻辑,将 Markdown 转 HTML 后返回;前端用模态框 + CSS 镜像 PDF 样式,包括水印效果 + +**Tech Stack:** Flask, markdown (Python), marked.js, Bootstrap 5 + +--- + +## 文件变更总览 + +| 文件 | 改动 | +|------|------| +| `requirements.txt` | 添加 `markdown` 包 | +| `app/routes/plans.py` | 新增 `/api/plans//preview` 路由 | +| `app/templates/plan_detail.html` | 新增预览按钮 + 模态框 + CSS | + +--- + +## Task 1: 安装 markdown 包 + +**Files:** +- Modify: `requirements.txt` + +- [ ] **Step 1: 添加 markdown 到 requirements.txt** + +在 requirements.txt 末尾添加: +``` +markdown>=3.4 +``` + +- [ ] **Step 2: 安装到 venv** + +```bash +cd "D:\F\NewI\opencode\daily-workspace\projects\青年钢琴集体课\练习方案系统" +venv\Scripts\pip install markdown +``` + +- [ ] **Step 3: 验证安装** + +```bash +venv\Scripts\python -c "import markdown; print(markdown.__version__)" +``` + +预期输出类似:`3.4.3` + +--- + +## Task 2: 新增后端 preview 路由 + +**Files:** +- Modify: `app/routes/plans.py` (在 `export_md` 函数之后添加新路由) + +- [ ] **Step 1: 找到 export_md 函数的位置** + +在 plans.py 中找到 `def export_md(plan_id):` 和它结束的位置(下一个 `@main_bp.route` 之前) + +- [ ] **Step 2: 在 export_md 结束后添加 preview 路由** + +在 `export_md` 路由结束处(`@main_bp.route("/plans//wechat"` 之前)添加: + +```python +@main_bp.route("/api/plans//preview", methods=["GET"]) +@login_required_json +def preview_report(plan_id): + """预览报告模板渲染结果 - 返回HTML片段""" + import markdown + from app.models import Template + + plan = PracticePlan.query.get_or_404(plan_id) + content = json.loads(plan.content) + student_name = plan.student.name if plan.student else "未知学员" + + # 获取选中的报告模板 + template_id = request.args.get('template_id', type=int) + report_template = None + try: + if template_id: + tmpl = Template.query.get(template_id) + if tmpl and tmpl.type == "report": + report_template = tmpl.content + else: + tmpl = Template.query.filter_by(type="report").order_by(Template.sort_order.asc()).first() + if tmpl: + report_template = tmpl.content + except: + pass + + if not report_template: + return "
无可用模板
", 200 + + # 占位符替换(复用 export_pdf 逻辑) + rendered = report_template + rendered = rendered.replace("{student_name}", student_name) + rendered = rendered.replace("{practice_time}", content.get('practice_time', 'N/A')) + rendered = rendered.replace("{total_minutes}", str(content.get('total_daily_minutes', 0))) + rendered = rendered.replace("{generated_at}", content.get('generated_at', '')) + + from app.models import User + user_id = session.get('user_id') + user_name = '未知' + if user_id: + user = User.query.get(user_id) + if user and user.name: + user_name = user.name + rendered = rendered.replace("{generated_by}", user_name) + + if content.get('ai_report'): + rendered = rendered.replace("{ai_report}", content['ai_report']) + else: + rendered = rendered.replace("{ai_report}", "(未生成AI报告)") + + problem_tags = "" + for problem in content.get('problems', []): + problem_tags += f"- **{problem.get('name', '')}** ({problem.get('severity', '')})\n" + rendered = rendered.replace("{problem_tags}", problem_tags or "(无)") + + # 学员目标 + from app.models import StudentGoal + student_goals_list = StudentGoal.query.filter_by(student_id=plan.student_id).all() if plan.student_id else [] + goals_text_parts = [] + for g in student_goals_list: + if g.status != "已完成": + goals_text_parts.append(f"- **{g.goal.name}**\n {g.goal.content if g.goal else '未提供具体内容'}") + goals_text = "\n".join(goals_text_parts) if goals_text_parts else "(无)" + rendered = rendered.replace("{student_goals}", goals_text) + + # Markdown 转 HTML + html_content = markdown.markdown(rendered, extensions=['tables', 'fenced_code']) + + return html_content, 200, {'Content-Type': 'text/html; charset=utf-8'} +``` + +- [ ] **Step 3: 验证路由语法正确** + +运行开发服务器检查是否有语法错误: +```bash +cd "D:\F\NewI\opencode\daily-workspace\projects\青年钢琴集体课\练习方案系统" +venv\Scripts\python -c "from app import create_app; app = create_app(); print('OK')" +``` + +预期输出:`OK` + +--- + +## Task 3: 前端添加预览模态框 + +**Files:** +- Modify: `app/templates/plan_detail.html` (在文件末尾 `` 之前添加模态框) + +- [ ] **Step 1: 在 plan_detail.html 末尾添加预览模态框** + +在 `` (最后一个 closing div) 之前添加: + +```html + + +``` + +- [ ] **Step 2: 添加预览区域的 CSS 样式** + +在 `plan_detail.html` 的 `