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}
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)
"test": "react-scripts test",
"eject": "react-scripts eject"
},
+ "proxy": "http://localhost:5002",
"eslintConfig": {
"extends": [
"react-app",
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%;
+}
// 导入页面组件
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';
{/* 当 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 />} />
--- /dev/null
+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;
--- /dev/null
+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;
-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
.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
==================================================
>>> 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 <<<
>>> 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 <<<
<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>