]> acesimba.cloud Git - daydayup.git/commitdiff
添加了知识点首页和章节首页功能
authorroot <root@localhost.localdomain>
Wed, 20 May 2026 13:51:54 +0000 (21:51 +0800)
committerroot <root@localhost.localdomain>
Wed, 20 May 2026 13:51:54 +0000 (21:51 +0800)
studybase/backend/__pycache__/app.cpython-312.pyc
studybase/backend/app.py
studybase/frontend/package.json
studybase/frontend/src/App.css
studybase/frontend/src/App.js
studybase/frontend/src/templates/subject/Chapters.js [new file with mode: 0644]
studybase/frontend/src/templates/subject/Knowledge.js [new file with mode: 0644]
studybase/frontend/src/templates/subject/Subject.js
studybase/project_all_in_one.txt

index eeda479d198cca9fc604c4a8089eabac540b3dff..d8b53fbf8e08314d7ae82bac7005ce5e8ec4c30d 100644 (file)
Binary files a/studybase/backend/__pycache__/app.cpython-312.pyc and b/studybase/backend/__pycache__/app.cpython-312.pyc differ
index 07e09688c137b15bf9701b18142a06fbe957a8d6..c7108063c49ac3a7f106a2e6a02e6edb2b1420b4 100644 (file)
@@ -1,42 +1,34 @@
 from flask import Flask, render_template, request, jsonify
+from flask_cors import CORS
 import os
 from database import query_db
 
-# 创建 Flask 应用实例
 app = Flask(__name__)
+CORS(app)
 
-# ==================== 1. 核心页面路由 ====================
-
+# --- 1. 页面路由 ---
 @app.route('/')
 def index():
     return render_template('index.html')
 
-# --- 题目列表模块 ---
-@app.route('/questions/')
-def questions_list():
-    return render_template('questions/index.html')
-
-# ==================== 2. API 数据接口 ====================
-
+# --- 2. 统计接口 ---
 @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:
+        return jsonify({"error": "Missing subject_id"}), 400
     
-    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')
+        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)
index a7d96fd80864cbe0d72a1fb3cdb3204fd7f921c5..331740e237ec141291fe6d63d5c5879d101e1f79 100644 (file)
@@ -19,6 +19,7 @@
     "test": "react-scripts test",
     "eject": "react-scripts eject"
   },
+  "proxy": "http://localhost:5002",
   "eslintConfig": {
     "extends": [
       "react-app",
index b85114e905c06f038201906558c0e39022b8a9e4..b1de159ac7bf7cccace84d52bb6ed0bc873c894c 100644 (file)
@@ -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%;
+}
index bac01558ff67a61255b02c1121038a130a898d3e..4a8cb64d8982157151410cce10241770c366e90b 100644 (file)
@@ -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 组件的内容 */}
                 <Route path="/subject" element={<Subject />} />
+                <Route path="/subject/chapters" element={<Chapters />} />
+                <Route path="/subject/knowledge" element={<Knowledge />} />
                 <Route path="/questions" element={<Questions />} />
                 <Route path="/papers" element={<Papers />} />
                 <Route path="/settings" element={<Settings />} />
diff --git a/studybase/frontend/src/templates/subject/Chapters.js b/studybase/frontend/src/templates/subject/Chapters.js
new file mode 100644 (file)
index 0000000..84a3dec
--- /dev/null
@@ -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 (
+        <div>
+            <div className="navbar header-subject"><span className="nav-title">章节分析</span></div>
+            <div className="sub-container">
+                <div style={{ marginBottom: '15px' }}>
+                    <Link to="/subject" className="back-btn" style={{ fontSize: '14px', padding: '5px 15px', textDecoration: 'none' }}>返回</Link>
+                </div>
+
+                <div className="table-container" style={{ background: 'white', borderRadius: '8px', overflow: 'hidden', boxShadow: '0 2px 8px rgba(0,0,0,0.1)' }}>
+                    <table style={{ width: '100%', borderCollapse: 'collapse' }}>
+                        <thead>
+                            <tr style={{ background: '#f5f5f5', cursor: 'pointer' }}>
+                                <th onClick={() => requestSort('ChapterDesc')} style={{ padding: '12px', textAlign: 'left' }}>章节名称 {getSortIcon('ChapterDesc')}</th>
+                                <th onClick={() => requestSort('total')} style={{ padding: '12px', textAlign: 'center' }}>总数 {getSortIcon('total')}</th>
+                                <th onClick={() => requestSort('not_correct')} style={{ padding: '12px', textAlign: 'center' }}>待完成 {getSortIcon('not_correct')}</th>
+                            </tr>
+                        </thead>
+                        <tbody>
+                            {loading ? (
+                                <tr><td colSpan="3" style={{ textAlign: 'center', padding: '20px' }}>加载中...</td></tr>
+                            ) : data.length === 0 ? (
+                                <tr><td colSpan="3" style={{ textAlign: 'center', padding: '20px' }}>暂无数据</td></tr>
+                            ) : data.map((row, index) => (
+                                <tr key={index} style={{ borderBottom: '1px solid #eee' }}>
+                                    <td style={{ padding: '12px' }}>
+                                        <Link to={`/subject/knowledge?chapter_id=${row.chapter_id}`} style={{ color: '#0066cc', textDecoration: 'none' }}>
+                                            {row.ChapterDesc || '未命名章节'}
+                                        </Link>
+                                    </td>
+                                    <td style={{ padding: '12px', textAlign: 'center' }}>{row.total}</td>
+                                    <td style={{ padding: '12px', textAlign: 'center' }}>
+                                        <span style={{ color: '#cf1322', fontWeight: 'bold' }}>{row.not_correct}</span>
+                                    </td>
+                                </tr>
+                            ))}
+                        </tbody>
+                    </table>
+                </div>
+            </div>
+        </div>
+    );
+}
+
+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 (file)
index 0000000..2844241
--- /dev/null
@@ -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 (
+        <div>
+            <div className="navbar header-subject"><span className="nav-title">知识点分析</span></div>
+            <div className="sub-container">
+                <div style={{ marginBottom: '15px' }}>
+                    <button onClick={() => window.history.back()} className="back-btn" style={{ fontSize: '14px', padding: '5px 15px', border: 'none', cursor: 'pointer' }}>返回</button>
+                </div>
+
+                <div className="table-container" style={{ background: 'white', borderRadius: '8px', overflow: 'hidden', boxShadow: '0 2px 8px rgba(0,0,0,0.1)' }}>
+                    <table style={{ width: '100%', borderCollapse: 'collapse' }}>
+                        <thead>
+                            <tr style={{ background: '#f5f5f5', cursor: 'pointer' }}>
+                                <th onClick={() => requestSort('chapter_name')} style={{ padding: '12px', textAlign: 'left' }}>所属章节 {getSortIcon('chapter_name')}</th>
+                                <th onClick={() => requestSort('content')} style={{ padding: '12px', textAlign: 'left' }}>知识点 {getSortIcon('content')}</th>
+                                <th onClick={() => requestSort('total')} style={{ padding: '12px', textAlign: 'center' }}>总数 {getSortIcon('total')}</th>
+                                <th onClick={() => requestSort('not_correct')} style={{ padding: '12px', textAlign: 'center' }}>待完成 {getSortIcon('not_correct')}</th>
+                            </tr>
+                        </thead>
+                        <tbody>
+                            {loading ? (
+                                <tr><td colSpan="4" style={{ textAlign: 'center', padding: '20px' }}>加载中...</td></tr>
+                            ) : data.length === 0 ? (
+                                <tr><td colSpan="4" style={{ textAlign: 'center', padding: '20px' }}>暂无数据</td></tr>
+                            ) : data.map((row, index) => (
+                                <tr key={index} style={{ borderBottom: '1px solid #eee' }}>
+                                    <td style={{ padding: '12px', color: '#666', fontSize: '14px' }}>{row.chapter_name || '-'}</td>
+                                    <td style={{ padding: '12px' }}>{row.content}</td>
+                                    <td style={{ padding: '12px', textAlign: 'center' }}>{row.total}</td>
+                                    <td style={{ padding: '12px', textAlign: 'center' }}>
+                                        <span style={{ color: '#cf1322', fontWeight: 'bold' }}>{row.not_correct}</span>
+                                    </td>
+                                </tr>
+                            ))}
+                        </tbody>
+                    </table>
+                </div>
+            </div>
+        </div>
+    );
+}
+
+export default Knowledge;
index 39f315e9f78a80c03483e548009bb15741f1782f..5211173874e76b5d06bafe08ac391315e2a45787 100644 (file)
-import React from 'react';\r
+import React, { useState, useEffect } from 'react';\r
+import { Link } from 'react-router-dom';\r
 \r
 function Subject() {\r
+    const [subjects, setSubjects] = useState([]);\r
+    const [selectedSubject, setSelectedSubject] = useState('');\r
+    const [stats, setStats] = useState(null);\r
+    const [loading, setLoading] = useState(true);\r
+\r
+    // 1. 初始化:获取科目列表\r
+    useEffect(() => {\r
+        fetch('/debug/db_test')\r
+            .then(res => res.json())\r
+            .then(data => {\r
+                if (data.status === "success") {\r
+                    setSubjects(data.subjects_found);\r
+                    if (data.subjects_found.length > 0) {\r
+                        setSelectedSubject(data.subjects_found[0].ID);\r
+                    }\r
+                }\r
+                setLoading(false);\r
+            })\r
+            .catch(err => console.error("加载科目失败:", err));\r
+    }, []);\r
+\r
+    // 2. 核心逻辑:当选中的科目 ID 改变时,获取统计数据\r
+    useEffect(() => {\r
+        if (!selectedSubject) return;\r
+\r
+        // 调用你后端 app.py 里的 /api/subject_stats 接口\r
+        // 假设默认查询阶段 1 和 2 (stage_ids=1,2)\r
+        fetch(`/api/subject_stats?subject_id=${selectedSubject}&stage_ids=1,2`)\r
+            .then(res => res.json())\r
+            .then(data => {\r
+                setStats(data);\r
+            })\r
+            .catch(err => console.error("加载统计数据失败:", err));\r
+    }, [selectedSubject]);\r
+\r
     return (\r
         <div>\r
-            <div className="navbar header-subject"><span className="nav-title">科目面板</span></div>\r
-            <div className="sub-container">\r
-                <h2>这里是科目面板内容区域</h2>\r
-                <p>正在从原 Python 程序迁移逻辑...</p>\r
-                <a href="/" className="back-btn">返回首页</a> {/* 保留返回首页的按钮 */}\r
+            <div className="navbar header-subject">\r
+                <span className="nav-title">科目概要面板</span>\r
+            </div>\r
+\r
+            <div className="sub-container" style={{ padding: '20px', maxWidth: '600px', margin: '0 auto' }}>\r
+                \r
+                {/* 科目选择卡片 */}\r
+                <div style={{ background: 'white', padding: '20px', borderRadius: '12px', boxShadow: '0 2px 10px rgba(0,0,0,0.05)', marginBottom: '20px' }}>\r
+                    <label style={{ fontWeight: 'bold', display: 'block', marginBottom: '8px' }}>当前科目:</label>\r
+                    <select \r
+                        value={selectedSubject}\r
+                        onChange={(e) => setSelectedSubject(e.target.value)}\r
+                        style={{ width: '100%', padding: '12px', borderRadius: '8px', border: '1px solid #ddd', fontSize: '16px' }}\r
+                    >\r
+                        {subjects.map(sub => (\r
+                            <option key={sub.ID} value={sub.ID}>{sub.Subject}</option>\r
+                        ))}\r
+                    </select>\r
+                </div>\r
+\r
+                {/* 统计数据展示区域 (原本 HTML 中的概要信息) */}\r
+                {stats && (\r
+                    <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '15px', marginBottom: '20px' }}>\r
+                        {/* 错题统计 */}\r
+                        <div style={{ background: '#fff1f0', padding: '15px', borderRadius: '12px', border: '1px solid #ffa39e' }}>\r
+                            <h4 style={{ margin: '0 0 10px 0', color: '#cf1322' }}>❌ 错题统计</h4>\r
+                            <p style={{ margin: '5px 0' }}>总数:<b>{stats.wrong.total}</b></p>\r
+                            <p style={{ margin: '5px 0' }}>已消灭:<span style={{ color: '#389e0d' }}>{stats.wrong.fixed}</span></p>\r
+                            <p style={{ margin: '5px 0' }}>剩余:<span style={{ color: '#cf1322' }}>{stats.wrong.remained}</span></p>\r
+                            <p style={{ margin: '5px 0', fontSize: '12px', color: '#666' }}>消灭率:{stats.wrong.ratio}</p>\r
+                        </div>\r
+                        {/* 推题统计 */}\r
+                        <div style={{ background: '#f6ffed', padding: '15px', borderRadius: '12px', border: '1px solid #b7eb8f' }}>\r
+                            <h4 style={{ margin: '0 0 10px 0', color: '#389e0d' }}>🚀 推题统计</h4>\r
+                            <p style={{ margin: '5px 0' }}>总数:<b>{stats.push.total}</b></p>\r
+                            <p style={{ margin: '5px 0' }}>已完成:<span style={{ color: '#389e0d' }}>{stats.push.fixed}</span></p>\r
+                            <p style={{ margin: '5px 0' }}>剩余:<span style={{ color: '#0958d9' }}>{stats.push.remained}</span></p>\r
+                            <p style={{ margin: '5px 0', fontSize: '12px', color: '#666' }}>完成率:{stats.push.ratio}</p>\r
+                        </div>\r
+                    </div>\r
+                )}\r
+\r
+                {/* 功能导航按钮 */}\r
+                <div className="analysis-btn-group" style={{ display: 'flex', flexDirection: 'column', gap: '15px' }}>\r
+                    <Link to={`/subject/chapters?subject_id=${selectedSubject}`} className="card card-subject" style={{ maxWidth: '100%', height: '60px', textDecoration: 'none' }}>\r
+                        📊 章节分析\r
+                    </Link>\r
+                    <Link to={`/subject/knowledge?subject_id=${selectedSubject}`} className="card card-question" style={{ maxWidth: '100%', height: '60px', textDecoration: 'none' }}>\r
+                        💡 知识点分析\r
+                    </Link>\r
+                </div>\r
+\r
+                <div style={{ textAlign: 'center', marginTop: '30px' }}>\r
+                    <Link to="/" className="back-btn" style={{ display: 'inline-block', padding: '10px 30px', backgroundColor: '#666', color: 'white', borderRadius: '20px', textDecoration: 'none' }}>\r
+                        返回首页\r
+                    </Link>\r
+                </div>\r
             </div>\r
         </div>\r
     );\r
index 3c93b90fa6f07be38451b51384a9b323fd213215..f9bcbefafb5790e5c0e28fd49d953c324021450a 100644 (file)
@@ -5,6 +5,7 @@
     .env
     .gitignore
     README.md
+    code-server_4.95.3_amd64.deb
     package-lock.json
     package.json
     backend/
             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() {
         <div>
             <div className="navbar header-paper">试卷管理</div>
             <div className="sub-container">
-                <h2>这里是试卷管理内容区域</h2>
+                <h2>这里是试卷管理内容区域11223344</h2>
                 <p>正在从原 Python 程序迁移逻辑...</p>
                 <a href="/" className="back-btn">返回首页</a>
             </div>