练习单导出

新增:

  • 导出当前解析结果
  • 导出当前学生错题本
  • 导出学习建议
  • 新开页面生成练习单
  • 浏览器里直接 打印 / 存成 PDF

后端新增导出接口

1)修改 backend/app/main.py

先补充 import:

javascript 复制代码
from fastapi.responses import HTMLResponse

2)新增一个导出页面接口

把下面接口加到 main.py 里:

xml 复制代码
@app.get("/api/export/report", response_class=HTMLResponse)
def export_report_html(
    student_id: int = Query(1),
    db: Session = Depends(get_db)
):
    student = db.query(Student).filter(Student.id == student_id).first()
    student_name = student.name if student else f"学生{student_id}"

    rows = (
        db.query(QuestionHistory)
        .filter(QuestionHistory.student_id == student_id)
        .order_by(QuestionHistory.id.desc())
        .all()
    )

    report = build_learning_report(db, student_id)
    suggestion = build_study_suggestion(db, student_id)

    wrong_rows = [row for row in rows if row.is_wrong][:10]

    wrong_html = ""
    for row in wrong_rows:
        try:
            knowledge_points = json.loads(row.knowledge_points or "[]")
        except Exception:
            knowledge_points = []

        try:
            steps = json.loads(row.steps or "[]")
        except Exception:
            steps = []

        wrong_html += f"""
        <div class="card">
          <h3>错题 #{row.id}</h3>
          <p><strong>题目:</strong>{row.question}</p>
          <p><strong>答案:</strong>{row.answer}</p>
          <p><strong>知识点:</strong>{'、'.join(knowledge_points) if knowledge_points else '无'}</p>
          <div>
            <strong>步骤解析:</strong>
            <ol>
              {''.join([f'<li>{step}</li>' for step in steps])}
            </ol>
          </div>
        </div>
        """

    top_kp_html = "".join([
        f"<li>{item['name']}({item['count']}次)</li>"
        for item in report["top_knowledge_points"]
    ])

    weak_html = "".join([
        f"""
        <div class="card">
          <h3>{item['name']}</h3>
          <p>错误 {item['wrong_count']} 次 / 共出现 {item['total_count']} 次 / 错误率 {item['wrong_rate']}%</p>
          <p>{item['suggestion']}</p>
        </div>
        """
        for item in suggestion["weak_knowledge_points"]
    ])

    html = f"""
    <!DOCTYPE html>
    <html lang="zh-CN">
    <head>
      <meta charset="UTF-8" />
      <title>{student_name} - 学习练习单</title>
      <style>
        body {{
          font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
          margin: 0;
          padding: 24px;
          color: #222;
          background: #f7f8fa;
        }}
        .container {{
          max-width: 960px;
          margin: 0 auto;
          background: #fff;
          padding: 32px;
          border-radius: 16px;
        }}
        h1, h2, h3 {{
          margin-top: 0;
        }}
        .summary {{
          display: grid;
          grid-template-columns: repeat(4, 1fr);
          gap: 12px;
          margin-bottom: 24px;
        }}
        .summary-item {{
          background: #f5f7fa;
          border-radius: 12px;
          padding: 16px;
          text-align: center;
        }}
        .summary-label {{
          color: #666;
          font-size: 14px;
          margin-bottom: 8px;
        }}
        .summary-value {{
          font-size: 28px;
          font-weight: bold;
          color: #18a058;
        }}
        .card {{
          background: #fafafa;
          border-radius: 12px;
          padding: 16px;
          margin-bottom: 16px;
        }}
        .section {{
          margin-top: 32px;
        }}
        .print-bar {{
          margin-bottom: 24px;
        }}
        .print-btn {{
          padding: 10px 16px;
          border: none;
          background: #18a058;
          color: #fff;
          border-radius: 8px;
          cursor: pointer;
        }}
        @media print {{
          body {{
            background: #fff;
            padding: 0;
          }}
          .container {{
            max-width: none;
            border-radius: 0;
            padding: 0;
          }}
          .print-bar {{
            display: none;
          }}
        }}
      </style>
    </head>
    <body>
      <div class="container">
        <div class="print-bar">
          <button class="print-btn" onclick="window.print()">打印 / 保存为 PDF</button>
        </div>

        <h1>{student_name} - 学习练习单</h1>

        <div class="summary">
          <div class="summary-item">
            <div class="summary-label">总题数</div>
            <div class="summary-value">{report['total_count']}</div>
          </div>
          <div class="summary-item">
            <div class="summary-label">错题数</div>
            <div class="summary-value">{report['wrong_count']}</div>
          </div>
          <div class="summary-item">
            <div class="summary-label">正确数</div>
            <div class="summary-value">{report['correct_count']}</div>
          </div>
          <div class="summary-item">
            <div class="summary-label">错题率</div>
            <div class="summary-value">{report['wrong_rate']}%</div>
          </div>
        </div>

        <div class="section">
          <h2>整体学习建议</h2>
          <div class="card">{suggestion['overall_suggestion']}</div>
        </div>

        <div class="section">
          <h2>高频知识点</h2>
          <div class="card">
            <ul>{top_kp_html or '<li>暂无</li>'}</ul>
          </div>
        </div>

        <div class="section">
          <h2>薄弱知识点分析</h2>
          {weak_html or '<div class="card">暂无薄弱知识点</div>'}
        </div>

        <div class="section">
          <h2>错题本(最近10题)</h2>
          {wrong_html or '<div class="card">暂无错题</div>'}
        </div>
      </div>
    </body>
    </html>
    """
    return html

前端新增导出按钮

修改 frontend/src/api/math.ts

新增一个方法:

javascript 复制代码
export function getExportReportUrl(student_id: number) {
  return `http://127.0.0.1:8000/api/export/report?student_id=${student_id}`
}

修改 frontend/src/App.vue

1)补充 import

getExportReportUrl 加进去:

python 复制代码
import {
  solveMathQuestion,
  solveMathImage,
  getHistoryList,
  getWrongQuestionList,
  markWrongQuestion,
  generatePracticeByKnowledge,
  regenerateQuestion,
  getLearningReport,
  getStudySuggestion,
  getStudentList,
  createStudent,
  getExportReportUrl,
  type SolveResponse,
  type HistoryItem,
  type PracticeQuestionItem,
  type LearningReportResponse,
  type StudySuggestionResponse,
  type StudentItem,
} from './api/math'

2)新增导出方法

script setup 里新增:

javascript 复制代码
const handleExportReport = () => {
  const url = getExportReportUrl(currentStudentId.value)
  window.open(url, '_blank')
}

页面里加导出入口

修改 frontend/src/App.vue

在学生切换区域 student-bar 里,最后加一个按钮:

arduino 复制代码
<button class="wrong-btn" @click="handleExportReport">
  导出练习单
</button>

重启看效果

重启后端

lua 复制代码
uvicorn app.main:app --reload --port 8000

重启前端

arduino 复制代码
npm run dev

不同学生 也没问题

相关推荐
kyriewen113 小时前
你点的“刷新”是假刷新?前端路由的瞒天过海术
开发语言·前端·javascript·ecmascript·html5
极梦网络无忧4 小时前
OpenClaw 基础使用说明(中文版)
python
codeJinger4 小时前
【Python】操作Excel文件
python·excel
XLYcmy4 小时前
一个针对医疗RAG系统的数据窃取攻击工具
python·网络安全·ai·llm·agent·rag·ai安全
Islucas5 小时前
Claude code入门保姆级教程
python·bash·claude
skywalk81635 小时前
Kotti Next的tinyfrontend前端模仿Kotti 首页布局还是不太好看,感觉比Kotti差一点
前端
萝卜白菜。5 小时前
TongWeb7.0相同的类指明加载顺序
开发语言·python·pycharm
赵钰老师5 小时前
【ADCIRC】基于“python+”潮汐、风驱动循环、风暴潮等海洋水动力模拟实践技术应用
python·信息可视化·数据分析
爬山算法5 小时前
MongoDB(80)如何在MongoDB中使用多文档事务?
数据库·python·mongodb