From 52bc00c6dbfd65213e9b3f31d4d7feddb0094565 Mon Sep 17 00:00:00 2001 From: root Date: Wed, 20 May 2026 21:51:54 +0800 Subject: [PATCH] =?utf8?q?=E6=B7=BB=E5=8A=A0=E4=BA=86=E7=9F=A5=E8=AF=86?= =?utf8?q?=E7=82=B9=E9=A6=96=E9=A1=B5=E5=92=8C=E7=AB=A0=E8=8A=82=E9=A6=96?= =?utf8?q?=E9=A1=B5=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit --- .../backend/__pycache__/app.cpython-312.pyc | Bin 2945 -> 5813 bytes studybase/backend/app.py | 136 +++++++++++------- studybase/frontend/package.json | 1 + studybase/frontend/src/App.css | 22 +++ studybase/frontend/src/App.js | 4 + .../src/templates/subject/Chapters.js | 90 ++++++++++++ .../src/templates/subject/Knowledge.js | 94 ++++++++++++ .../frontend/src/templates/subject/Subject.js | 100 ++++++++++++- studybase/project_all_in_one.txt | 116 ++++++++++++++- 9 files changed, 506 insertions(+), 57 deletions(-) create mode 100644 studybase/frontend/src/templates/subject/Chapters.js create mode 100644 studybase/frontend/src/templates/subject/Knowledge.js diff --git a/studybase/backend/__pycache__/app.cpython-312.pyc b/studybase/backend/__pycache__/app.cpython-312.pyc index eeda479d198cca9fc604c4a8089eabac540b3dff..d8b53fbf8e08314d7ae82bac7005ce5e8ec4c30d 100644 GIT binary patch literal 5813 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 delta 1834 zcmZuxUrbY17(eISf4wa&|5~5|S{W#vlM3iW%p$DC=!SJ0!lq(K?YUx01-$o?Les7_ zGodvm+TAQd(1dKuqR|KTuw~g38sGNP+GXku%d%uI+sn!@n`19Kr%+^nzV?3ScfRlU zoqN9Tp6`6I`%<;#8?)I6h$QcrgU^Mur3U>D(#_4UBOpOZxC^NdfKvv^5UgCon>A*N z49s2zch_B~jFg>DIyqATeCa$;A_-U=X$C;H5mW8vHP$dTViPX8XTHm<%^{ewOj(?z z+Q+a}TY_(S?m(F(t~+3b5>|J*&W~M?T-Y~7{uARaMV-J?XMKv~I_vX~NTb;08y{~O zpVGO&D3&JWQW7L;$5E}x|y zN(@9nP*}s8HD-%d^?+;8kA{ICogus!j6wxo<)bi-u8J(N#^4n(Wt34$RBBYQxCZ9P zk0+0Cll=!a+(hx>(tQB4L7L^i1dGE^r8yTQB^LP+4uB%QV!)M2LsCo{&oS6|S&ShO zAP_~^RAj`MwK*=?NR};F;^qwsS1_12QTP`*M42cXWiU75q2Gbx2-z&Q&~4z-ITn}Q zLZcA#loufuD2&3wNLTG zFvUd(GYWUA_nYMk1yY%@>Ks8i7Dxvfnl;~}44IzJY|lB~#9;?Q}L9nFWb97GZc?M-g~Py?AN z14KPffF~7$nKOaRU@#BB2y`Pdc+fFAxRI9*rEjAovt+5duFMR~9GRcW*6mq1ywH)Y zJGf~5)^a$>JgwcDRIazDCNqYn`EH`LMQ%_0XH3?QFfFa;R$-IQNwPd92cD6tIH7DK zE~hBdBI>M99#(W_SW3RJqfy4G%va`NV(;vpdGs)~Go z@TzIREZOcVxjoF*YlEr5$L9L1xqc>;Gw(_qUb5ES=uP)Nwl-$1jq|4$8gkZsiDOS3 zyB|B+vyS!!ch1of|E{cSrt)j|hn0`Yx)Mi!G?mZVmrAQ{w=P^<+?(ki$Zmi4$;-PI ze2a~l6a87oz%m1C-h(RumYsxo07asCV6DEe2O-bQ%`&S*Ao>RHj)AM0swf>KJNqbe8HU9bAHc;LTpFi87JrbNP zyGXM4s|1gcy}y+u`ayH|c26mISSortnTIvio_)+i7o>O}=jqgpHW%Y{TC{_ 0') - p_total = get_count('推题'); p_fixed = get_count('推题', 'AND right_times > 0') + def get_count(q_type, extra_sql=""): + sql = f"SELECT COUNT(*) as c FROM questions WHERE subject_id = ? AND question_type = ? {extra_sql}" + res = query_db(sql, [sub_id, q_type], one=True) + return res['c'] if res else 0 + + w_total = get_count('错题') + w_fixed = get_count('错题', 'AND right_times > 0') + p_total = get_count('推题') + p_fixed = get_count('推题', 'AND right_times > 0') + return jsonify({ "wrong": {"total": w_total, "fixed": w_fixed, "ratio": f"{(w_fixed/w_total*100):.1f}%" if w_total > 0 else "0%", "remained": w_total - w_fixed}, "push": {"total": p_total, "fixed": p_fixed, "ratio": f"{(p_fixed/p_total*100):.1f}%" if p_total > 0 else "0%", "remained": p_total - p_fixed} @@ -44,35 +36,83 @@ def get_subject_stats(): except Exception as e: return jsonify({"error": str(e)}), 500 -# ==================== 3. 测试接口 (移动至此处) ==================== +# --- 3. 章节分析接口 --- +@app.route('/api/chapters') +def get_chapters(): + subject_id = request.args.get('subject_id') + sql = """ + SELECT + c.id as chapter_id, + c.chapter_desc as ChapterDesc, + s.Subject as SubjectName, + COUNT(q.id) as total, + SUM(CASE WHEN q.latest_status != '正确' OR q.latest_status IS NULL THEN 1 ELSE 0 END) as not_correct + FROM chapters c + JOIN Subject s ON c.subject_id = s.ID + LEFT JOIN knowledge_point kp ON kp.chapter_id = c.id + LEFT JOIN question_kp_relation qkr ON qkr.kp_id = kp.id + LEFT JOIN questions q ON q.id = qkr.question_id + WHERE 1=1 + """ + params = [] + if subject_id: + sql += " AND c.subject_id = ?" + params.append(subject_id) + sql += " GROUP BY c.id" + + try: + results = query_db(sql, params) + return jsonify([dict(row) for row in results]) + except Exception as e: + return jsonify({"error": str(e)}), 500 + +# --- 4. 知识点分析接口 --- +@app.route('/api/knowledge') +@app.route('/api/knowledge') +def get_knowledge(): + chapter_id = request.args.get('chapter_id') + subject_id = request.args.get('subject_id') + + sql = """ + SELECT + kp.id, + kp.content, + c.chapter_desc as chapter_name, + s.Subject as subject_name, + COUNT(qkr.question_id) as total, + SUM(CASE WHEN q.latest_status != '正确' OR q.latest_status IS NULL THEN 1 ELSE 0 END) as not_correct + FROM knowledge_point kp + JOIN Subject s ON kp.subject_id = s.ID + LEFT JOIN chapters c ON kp.chapter_id = c.id + LEFT JOIN question_kp_relation qkr ON qkr.kp_id = kp.id + LEFT JOIN questions q ON q.id = qkr.question_id + WHERE 1=1 + """ + params = [] + if chapter_id: + sql += " AND kp.chapter_id = ?" + params.append(chapter_id) + elif subject_id: + sql += " AND kp.subject_id = ?" + params.append(subject_id) + + sql += " GROUP BY kp.id" + + try: + results = query_db(sql, params) + return jsonify([dict(row) for row in results]) + except Exception as e: + return jsonify({"error": str(e)}), 500 + +# --- 5. 调试接口 --- @app.route('/debug/db_test') def db_test(): try: - # 1. 检查数据库文件路径 - from database import DB_PATH - db_exists = os.path.exists(DB_PATH) - - # 2. 测试基础连接:查询科目表 - subjects = query_db("SELECT * FROM Subject LIMIT 5") - - # 3. 测试 questions 表:查一下总题数 - q_count = query_db("SELECT COUNT(*) as total FROM questions", one=True) - - return jsonify({ - "status": "success", - "database_path": DB_PATH, - "db_exists": db_exists, - "subjects_found": [dict(s) for s in subjects], - "total_questions": q_count['total'] if q_count else 0 - }) + subjects = query_db("SELECT * FROM Subject WHERE enabled = 1") + return jsonify({"status": "success", "subjects_found": [dict(s) for s in subjects]}) except Exception as e: - return jsonify({ - "status": "error", - "message": str(e) - }), 500 - -# ==================== 4. 启动服务 (必须在最后) ==================== + return jsonify({"status": "error", "message": str(e)}), 500 if __name__ == '__main__': app.run(host='0.0.0.0', port=5002, debug=True) diff --git a/studybase/frontend/package.json b/studybase/frontend/package.json index a7d96fd..331740e 100644 --- a/studybase/frontend/package.json +++ b/studybase/frontend/package.json @@ -19,6 +19,7 @@ "test": "react-scripts test", "eject": "react-scripts eject" }, + "proxy": "http://localhost:5002", "eslintConfig": { "extends": [ "react-app", diff --git a/studybase/frontend/src/App.css b/studybase/frontend/src/App.css index b85114e..b1de159 100644 --- a/studybase/frontend/src/App.css +++ b/studybase/frontend/src/App.css @@ -102,3 +102,25 @@ body { border-left-width: 10px; /* 手机上加粗色块更醒目 */ } } + +/* 辅助样式 */ +.sub-container { + padding: 20px; +} + +.back-btn:hover { + background-color: #444; + transition: 0.3s; +} + +.header-subject { + background: linear-gradient(135deg, #0066cc 0%, #004488 100%) !important; +} + +/* 让 Link 渲染的 a 标签也具备卡片样式 */ +.analysis-btn-group .card { + display: flex; + align-items: center; + justify-content: center; + width: 100%; +} diff --git a/studybase/frontend/src/App.js b/studybase/frontend/src/App.js index bac0155..4a8cb64 100644 --- a/studybase/frontend/src/App.js +++ b/studybase/frontend/src/App.js @@ -4,6 +4,8 @@ import './App.css'; // 导入页面组件 import Subject from './templates/subject/Subject'; +import Chapters from './templates/subject/Chapters'; // 新增 +import Knowledge from './templates/subject/Knowledge'; // 新增 import Questions from './templates/questions/Questions'; import Papers from './templates/papers/Papers'; import Settings from './templates/settings/Settings'; @@ -40,6 +42,8 @@ function App() { {/* 当 URL 匹配以下路径时,只渲染对应的组件,不再渲染 Home 组件的内容 */} } /> + } /> + } /> } /> } /> } /> diff --git a/studybase/frontend/src/templates/subject/Chapters.js b/studybase/frontend/src/templates/subject/Chapters.js new file mode 100644 index 0000000..84a3dec --- /dev/null +++ b/studybase/frontend/src/templates/subject/Chapters.js @@ -0,0 +1,90 @@ +import React, { useState, useEffect } from 'react'; +import { Link, useSearchParams } from 'react-router-dom'; + +function Chapters() { + const [searchParams] = useSearchParams(); + const [data, setData] = useState([]); + const [loading, setLoading] = useState(true); + const [sortConfig, setSortConfig] = useState({ key: 'not_correct', direction: 'desc' }); + const subjectId = searchParams.get('subject_id'); + + useEffect(() => { + fetch(`/api/chapters?subject_id=${subjectId || ''}`) + .then(res => res.json()) + .then(json => { + // 默认按待完成数量逆序排列 + const sortedData = [...json].sort((a, b) => b.not_correct - a.not_correct); + setData(sortedData); + setLoading(false); + }) + .catch(err => { + console.error("加载章节失败:", err); + setLoading(false); + }); + }, [subjectId]); + + // 排序函数 + const requestSort = (key) => { + let direction = 'desc'; + if (sortConfig.key === key && sortConfig.direction === 'desc') { + direction = 'asc'; + } + setSortConfig({ key, direction }); + + const sortedData = [...data].sort((a, b) => { + if (a[key] < b[key]) return direction === 'asc' ? -1 : 1; + if (a[key] > b[key]) return direction === 'asc' ? 1 : -1; + return 0; + }); + setData(sortedData); + }; + + const getSortIcon = (key) => { + if (sortConfig.key !== key) return '↕️'; + return sortConfig.direction === 'asc' ? '🔼' : '🔽'; + }; + + return ( +
+
章节分析
+
+
+ 返回 +
+ +
+ + + + + + + + + + {loading ? ( + + ) : data.length === 0 ? ( + + ) : data.map((row, index) => ( + + + + + + ))} + +
requestSort('ChapterDesc')} style={{ padding: '12px', textAlign: 'left' }}>章节名称 {getSortIcon('ChapterDesc')} requestSort('total')} style={{ padding: '12px', textAlign: 'center' }}>总数 {getSortIcon('total')} requestSort('not_correct')} style={{ padding: '12px', textAlign: 'center' }}>待完成 {getSortIcon('not_correct')}
加载中...
暂无数据
+ + {row.ChapterDesc || '未命名章节'} + + {row.total} + {row.not_correct} +
+
+
+
+ ); +} + +export default Chapters; diff --git a/studybase/frontend/src/templates/subject/Knowledge.js b/studybase/frontend/src/templates/subject/Knowledge.js new file mode 100644 index 0000000..2844241 --- /dev/null +++ b/studybase/frontend/src/templates/subject/Knowledge.js @@ -0,0 +1,94 @@ +import React, { useState, useEffect } from 'react'; +import { Link, useSearchParams } from 'react-router-dom'; + +function Knowledge() { + const [searchParams] = useSearchParams(); + const [data, setData] = useState([]); + const [loading, setLoading] = useState(true); + const [sortConfig, setSortConfig] = useState({ key: 'not_correct', direction: 'desc' }); + const chapterId = searchParams.get('chapter_id'); + const subjectId = searchParams.get('subject_id'); + + useEffect(() => { + let url = `/api/knowledge?`; + if (chapterId) url += `chapter_id=${chapterId}`; + else if (subjectId) url += `subject_id=${subjectId}`; + + fetch(url) + .then(res => res.json()) + .then(json => { + // 默认按待完成数量逆序排列 + const sortedData = [...json].sort((a, b) => b.not_correct - a.not_correct); + setData(sortedData); + setLoading(false); + }) + .catch(err => { + console.error("加载知识点失败:", err); + setLoading(false); + }); + }, [chapterId, subjectId]); + + const requestSort = (key) => { + let direction = 'desc'; + if (sortConfig.key === key && sortConfig.direction === 'desc') { + direction = 'asc'; + } + setSortConfig({ key, direction }); + + const sortedData = [...data].sort((a, b) => { + const valA = a[key] || ''; + const valB = b[key] || ''; + if (valA < valB) return direction === 'asc' ? -1 : 1; + if (valA > valB) return direction === 'asc' ? 1 : -1; + return 0; + }); + setData(sortedData); + }; + + const getSortIcon = (key) => { + if (sortConfig.key !== key) return '↕️'; + return sortConfig.direction === 'asc' ? '🔼' : '🔽'; + }; + + return ( +
+
知识点分析
+
+
+ +
+ +
+ + + + + + + + + + + {loading ? ( + + ) : data.length === 0 ? ( + + ) : data.map((row, index) => ( + + + + + + + ))} + +
requestSort('chapter_name')} style={{ padding: '12px', textAlign: 'left' }}>所属章节 {getSortIcon('chapter_name')} requestSort('content')} style={{ padding: '12px', textAlign: 'left' }}>知识点 {getSortIcon('content')} requestSort('total')} style={{ padding: '12px', textAlign: 'center' }}>总数 {getSortIcon('total')} requestSort('not_correct')} style={{ padding: '12px', textAlign: 'center' }}>待完成 {getSortIcon('not_correct')}
加载中...
暂无数据
{row.chapter_name || '-'}{row.content}{row.total} + {row.not_correct} +
+
+
+
+ ); +} + +export default Knowledge; diff --git a/studybase/frontend/src/templates/subject/Subject.js b/studybase/frontend/src/templates/subject/Subject.js index 39f315e..5211173 100644 --- a/studybase/frontend/src/templates/subject/Subject.js +++ b/studybase/frontend/src/templates/subject/Subject.js @@ -1,13 +1,101 @@ -import React from 'react'; +import React, { useState, useEffect } from 'react'; +import { Link } from 'react-router-dom'; function Subject() { + const [subjects, setSubjects] = useState([]); + const [selectedSubject, setSelectedSubject] = useState(''); + const [stats, setStats] = useState(null); + const [loading, setLoading] = useState(true); + + // 1. 初始化:获取科目列表 + useEffect(() => { + fetch('/debug/db_test') + .then(res => res.json()) + .then(data => { + if (data.status === "success") { + setSubjects(data.subjects_found); + if (data.subjects_found.length > 0) { + setSelectedSubject(data.subjects_found[0].ID); + } + } + setLoading(false); + }) + .catch(err => console.error("加载科目失败:", err)); + }, []); + + // 2. 核心逻辑:当选中的科目 ID 改变时,获取统计数据 + useEffect(() => { + if (!selectedSubject) return; + + // 调用你后端 app.py 里的 /api/subject_stats 接口 + // 假设默认查询阶段 1 和 2 (stage_ids=1,2) + fetch(`/api/subject_stats?subject_id=${selectedSubject}&stage_ids=1,2`) + .then(res => res.json()) + .then(data => { + setStats(data); + }) + .catch(err => console.error("加载统计数据失败:", err)); + }, [selectedSubject]); + return (
-
科目面板
-
-

这里是科目面板内容区域

-

正在从原 Python 程序迁移逻辑...

- 返回首页 {/* 保留返回首页的按钮 */} +
+ 科目概要面板 +
+ +
+ + {/* 科目选择卡片 */} +
+ + +
+ + {/* 统计数据展示区域 (原本 HTML 中的概要信息) */} + {stats && ( +
+ {/* 错题统计 */} +
+

❌ 错题统计

+

总数:{stats.wrong.total}

+

已消灭:{stats.wrong.fixed}

+

剩余:{stats.wrong.remained}

+

消灭率:{stats.wrong.ratio}

+
+ {/* 推题统计 */} +
+

🚀 推题统计

+

总数:{stats.push.total}

+

已完成:{stats.push.fixed}

+

剩余:{stats.push.remained}

+

完成率:{stats.push.ratio}

+
+
+ )} + + {/* 功能导航按钮 */} +
+ + 📊 章节分析 + + + 💡 知识点分析 + +
+ +
+ + 返回首页 + +
); diff --git a/studybase/project_all_in_one.txt b/studybase/project_all_in_one.txt index 3c93b90..f9bcbef 100644 --- a/studybase/project_all_in_one.txt +++ b/studybase/project_all_in_one.txt @@ -5,6 +5,7 @@ .env .gitignore README.md + code-server_4.95.3_amd64.deb package-lock.json package.json backend/ @@ -69,6 +70,11 @@ index.html manifest.json robots.txt + s/ + schema.sql + studybase.db + studybase.sqbpro + workmode-studybase.sql ================================================== @@ -153,7 +159,84 @@ This section has moved here: [https://facebook.github.io/create-react-app/docs/t >>> FILE START: backend/app.py <<< ======================================== - +from flask import Flask, render_template, request, jsonify +import os +from database import query_db + +# 创建 Flask 应用实例 +app = Flask(__name__) + +# ==================== 1. 核心页面路由 ==================== + +@app.route('/') +def index(): + return render_template('index.html') + +# --- 题目列表模块 --- +@app.route('/questions/') +def questions_list(): + return render_template('questions/index.html') + +# ==================== 2. API 数据接口 ==================== + +@app.route('/api/subject_stats') +def get_subject_stats(): + sub_id = request.args.get('subject_id') + stage_ids_raw = request.args.get('stage_ids', '') + stage_ids = [sid for sid in stage_ids_raw.split(',') if sid] + + if not sub_id or not stage_ids: + return jsonify({"wrong": {"total": 0, "fixed": 0, "ratio": "0%", "remained": 0}, + "push": {"total": 0, "fixed": 0, "ratio": "0%", "remained": 0}}) + + placeholders = ','.join(['?'] * len(stage_ids)) + def get_count(q_type, extra_sql=""): + sql = f"""SELECT COUNT(*) as c FROM questions WHERE subject_id = ? AND stage_id IN ({placeholders}) AND question_type = ? {extra_sql}""" + res = query_db(sql, [sub_id] + stage_ids + [q_type], one=True) + return res['c'] if res else 0 + + try: + w_total = get_count('错题'); w_fixed = get_count('错题', 'AND right_times > 0') + p_total = get_count('推题'); p_fixed = get_count('推题', 'AND right_times > 0') + return jsonify({ + "wrong": {"total": w_total, "fixed": w_fixed, "ratio": f"{(w_fixed/w_total*100):.1f}%" if w_total > 0 else "0%", "remained": w_total - w_fixed}, + "push": {"total": p_total, "fixed": p_fixed, "ratio": f"{(p_fixed/p_total*100):.1f}%" if p_total > 0 else "0%", "remained": p_total - p_fixed} + }) + except Exception as e: + return jsonify({"error": str(e)}), 500 + +# ==================== 3. 测试接口 (移动至此处) ==================== + +@app.route('/debug/db_test') +def db_test(): + try: + # 1. 检查数据库文件路径 + from database import DB_PATH + db_exists = os.path.exists(DB_PATH) + + # 2. 测试基础连接:查询科目表 + subjects = query_db("SELECT * FROM Subject LIMIT 5") + + # 3. 测试 questions 表:查一下总题数 + q_count = query_db("SELECT COUNT(*) as total FROM questions", one=True) + + return jsonify({ + "status": "success", + "database_path": DB_PATH, + "db_exists": db_exists, + "subjects_found": [dict(s) for s in subjects], + "total_questions": q_count['total'] if q_count else 0 + }) + except Exception as e: + return jsonify({ + "status": "error", + "message": str(e) + }), 500 + +# ==================== 4. 启动服务 (必须在最后) ==================== + +if __name__ == '__main__': + app.run(host='0.0.0.0', port=5002, debug=True) ======================================== >>> FILE END: backend/app.py <<< @@ -167,7 +250,34 @@ This section has moved here: [https://facebook.github.io/create-react-app/docs/t >>> FILE START: backend/database.py <<< ======================================== - +import sqlite3 +import os + +# 数据库文件的绝对路径 +DB_PATH = os.path.join(os.path.dirname(__file__), '..', 's', 'studybase.db') + +def get_db_connection(): + """获取数据库连接,并将结果集设置为字典模式(方便前端调用)""" + conn = sqlite3.connect(DB_PATH) + # 这行代码能让你通过 row['column_name'] 访问数据,而不是 row[0] + conn.row_factory = sqlite3.Row + return conn + +def query_db(query, args=(), one=False): + """通用的查询函数""" + conn = get_db_connection() + cur = conn.execute(query, args) + rv = cur.fetchall() + conn.close() + return (rv[0] if rv else None) if one else rv + +def execute_db(query, args=()): + """通用的增删改函数""" + conn = get_db_connection() + cur = conn.execute(query, args) + conn.commit() + conn.close() + return cur ======================================== >>> FILE END: backend/database.py <<< @@ -650,7 +760,7 @@ function Papers() {
试卷管理
-

这里是试卷管理内容区域

+

这里是试卷管理内容区域11223344

正在从原 Python 程序迁移逻辑...

返回首页
-- 2.43.0