# 导出预览功能实现计划 > **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` 的 `