From: root Date: Wed, 20 May 2026 02:40:12 +0000 (+0800) Subject: update: 修改generate_report.py,修复输出文件乱码问题 X-Git-Url: http://acesimba.cloud/gitweb/?a=commitdiff_plain;h=20ababa7174121318092595d86452d55193c3d0e;p=studybase.git update: 修改generate_report.py,修复输出文件乱码问题 --- diff --git a/generate_report.py b/generate_report.py index 0d6d16b..b21b5c4 100644 --- a/generate_report.py +++ b/generate_report.py @@ -16,7 +16,7 @@ EXCLUDE_DIRS = {".git", "__pycache__", "ZZ Old Version", "Media"} EXCLUDE_EXTS = {".exe", ".pyc", ".png", ".jpg", ".jpeg", ".gif", ".pdf", ".zip", ".tar", ".gz"} # 需要排除的具体文件名 -EXCLUDE_FILES = {OUTPUT_FILENAME, ".DS_Store","gitea"} +EXCLUDE_FILES = {OUTPUT_FILENAME, ".DS_Store", "gitea"} # =========================================== ROOT_DIR = os.path.abspath(os.path.dirname(__file__)) @@ -81,9 +81,15 @@ def gather_all_files(root): matches.sort() return matches +def is_text_file(filepath): + """检查文件是否为文本文件""" + text_extensions = ['.txt', '.py', '.html', '.css', '.js', '.md'] # 可根据需要扩展 + _, ext = os.path.splitext(filepath) + return ext.lower() in text_extensions + def read_file_content(path): """尝试多种编码读取文件内容,非文本或无法读取则跳过""" - encodings = ["utf-8", "utf-8-sig", "gbk", "latin-1"] + encodings = ["utf-8-sig", "utf-8", "gbk", "latin-1"] # 优先使用 utf-8-sig for enc in encodings: try: with io.open(path, "r", encoding=enc, errors="strict") as f: @@ -93,7 +99,7 @@ def read_file_content(path): return "[警告:该文件编码不兼容或为二进制格式,已跳过内容提取]" def generate_report(root, out_path): - with io.open(out_path, "w", encoding="utf-8") as out: + with io.open(out_path, "w", encoding="utf-8-sig") as out: # 使用 utf-8-sig 输出 # 第一部分:目录结构 write_line(out, "==================================================") write_line(out, "第一部分:项目目录结构") @@ -113,16 +119,17 @@ def generate_report(root, out_path): write_line(out, "[未找到符合条件的可处理文件]") for rel_path, full_path in all_files: - write_line(out, f"\n>>> FILE START: {rel_path} <<<") - write_line(out, "=" * 40) - - content = read_file_content(full_path) - out.write(content) - - if not content.endswith("\n"): - out.write("\n") - write_line(out, "=" * 40) - write_line(out, f">>> FILE END: {rel_path} <<<\n") + if is_text_file(full_path): # 仅处理文本文件 + write_line(out, f"\n>>> FILE START: {rel_path} <<<") + write_line(out, "=" * 40) + + content = read_file_content(full_path) + out.write(content) + + if not content.endswith("\n"): + out.write("\n") + write_line(out, "=" * 40) + write_line(out, f">>> FILE END: {rel_path} <<<\n") if __name__ == "__main__": try: diff --git a/project_all_in_one.txt b/project_all_in_one.txt index 8816595..3045718 100644 --- a/project_all_in_one.txt +++ b/project_all_in_one.txt @@ -1,9 +1,11 @@ -================================================== +================================================== 第一部分:项目目录结构 ================================================== ./ .gitignore + app.log app.py + database.py generate_report.py custom/ custom/conf/ @@ -14,76 +16,200 @@ index.html templates/questions/ index.html + question_list.html templates/papers/ index.html templates/subject/ + chapters.html index.html + knowledge.html + _clipboard_temp/ static/ static/css/ style.css + s/ + schema.sql + studybase.db + studybase.sqbpro + workmode-studybase.sql ================================================== 第二部分:文件内容整合 ================================================== ->>> FILE START: .gitignore <<< -======================================== -__pycache__/ -*.py[cod] -*$py.class -.venv/ -venv/ -ENV/ -*.db -.DS_Store -======================================== ->>> FILE END: .gitignore <<< - - >>> FILE START: app.py <<< ======================================== -from flask import Flask, render_template +from flask import Flask, render_template, request, jsonify +from database import query_db app = Flask(__name__) -# 首页 +# ==================== 1. 核心页面路由 ==================== + @app.route('/') def index(): return render_template('index.html') -# 科目面板 -@app.route('/subject/') -def subject(): - return render_template('subject/index.html') - -# 题目管理 +# --- 题目列表模块 --- @app.route('/questions/') -def questions(): +def questions_list(): return render_template('questions/index.html') -# 试卷管理 +# --- 试卷列表模块 --- @app.route('/papers/') -def papers(): +def papers_list(): return render_template('papers/index.html') -# 系统设定 +# --- 系统设定模块 --- @app.route('/settings/') -def settings(): +def settings_page(): return render_template('settings/index.html') +# --- 错题分析模块 (刚才修复的部分) --- +@app.route('/subject/') +def subject_portal(): + subjects = query_db("SELECT ID, Subject as name FROM Subject WHERE enabled = 1") + stages = query_db("SELECT ID, stage_name FROM stage WHERE enabled = 1 ORDER BY ID") + default_id = subjects[0]['ID'] if subjects else None + return render_template('subject/index.html', subjects=subjects, stages=stages, default_id=default_id) + +@app.route('/subject/chapters') +def subject_chapters(): + subjects = query_db("SELECT DISTINCT Subject FROM Subject ORDER BY Subject") + return render_template('subject/chapters.html', subjects=subjects) + +@app.route('/subject/knowledge') +def subject_knowledge(): + subjects = query_db("SELECT DISTINCT Subject FROM Subject ORDER BY Subject") + chapter_id = request.args.get('chapter_id', '') + return render_template('subject/knowledge.html', subjects=subjects, chapter_id=chapter_id) + + +# ==================== 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 + +# 章节数据接口 (已适配 chapter_desc) +@app.route('/api/chapters') +def get_chapters(): + subject_name = request.args.get('subject', '全部') + keyword = request.args.get('keyword', '') + params = [] + where = " WHERE 1=1" + if subject_name != '全部': + where += " AND s.Subject = ?"; params.append(subject_name) + if keyword: + where += " AND c.chapter_desc LIKE ?"; params.append(f"%{keyword}%") + + sql = f""" + SELECT c.id AS chapter_id, s.Subject AS SubjectName, c.chapter_desc AS ChapterDesc, + (SELECT COUNT(DISTINCT r.question_id) FROM question_kp_relation r JOIN knowledge_point kp ON r.kp_id = kp.id WHERE kp.chapter_id = c.id) AS total, + (SELECT COUNT(DISTINCT r.question_id) FROM question_kp_relation r JOIN knowledge_point kp ON r.kp_id = kp.id JOIN questions q ON r.question_id = q.id WHERE kp.chapter_id = c.id AND q.right_times > 0) AS correct + FROM chapters c JOIN Subject s ON c.subject_id = s.ID {where} + """ + try: + rows = query_db(sql, params) + result = [dict(row) for row in rows] + for item in result: + item['not_correct'] = (item['total'] or 0) - (item['correct'] or 0) + return jsonify([i for i in result if i['total'] > 0]) + except Exception as e: + return jsonify({"error": str(e)}), 500 + +# 知识点数据接口 +@app.route('/api/knowledge') +def get_knowledge(): + subject_name = request.args.get('subject', '全部') + keyword = request.args.get('keyword', '') + chapter_id = request.args.get('chapter_id', '') + params = [] + where = " WHERE kp.is_enabled = 1" + if chapter_id: + where += " AND kp.chapter_id = ?"; params.append(chapter_id) + if subject_name != '全部': + where += " AND s.Subject = ?"; params.append(subject_name) + if keyword: + where += " AND kp.content LIKE ?"; params.append(f"%{keyword}%") + + sql = f""" + SELECT kp.id, s.Subject as subject_name, kp.content, + (SELECT COUNT(*) FROM question_kp_relation r WHERE r.kp_id = kp.id) as total, + (SELECT COUNT(DISTINCT r.question_id) FROM question_kp_relation r JOIN questions q ON r.question_id = q.id WHERE r.kp_id = kp.id AND q.right_times >= 1) as correct + FROM knowledge_point kp JOIN Subject s ON kp.subject_id = s.ID {where} + """ + try: + rows = query_db(sql, params) + result = [dict(row) for row in rows] + for item in result: + item['not_correct'] = (item['total'] or 0) - (item['correct'] or 0) + return jsonify(result) + except Exception as e: + return jsonify({"error": str(e)}), 500 + if __name__ == '__main__': - app.run(host='0.0.0.0', port=5000) + app.run(host='0.0.0.0', port=5000, debug=True) ======================================== >>> FILE END: app.py <<< ->>> FILE START: custom/conf/app.ini <<< +>>> FILE START: database.py <<< ======================================== -[server] -LOCAL_ROOT_URL = http://localhost:4999/ +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: custom/conf/app.ini <<< +>>> FILE END: database.py <<< >>> FILE START: generate_report.py <<< @@ -106,7 +232,7 @@ EXCLUDE_DIRS = {".git", "__pycache__", "ZZ Old Version", "Media"} EXCLUDE_EXTS = {".exe", ".pyc", ".png", ".jpg", ".jpeg", ".gif", ".pdf", ".zip", ".tar", ".gz"} # 需要排除的具体文件名 -EXCLUDE_FILES = {OUTPUT_FILENAME, ".DS_Store","gitea"} +EXCLUDE_FILES = {OUTPUT_FILENAME, ".DS_Store", "gitea"} # =========================================== ROOT_DIR = os.path.abspath(os.path.dirname(__file__)) @@ -171,9 +297,15 @@ def gather_all_files(root): matches.sort() return matches +def is_text_file(filepath): + """检查文件是否为文本文件""" + text_extensions = ['.txt', '.py', '.html', '.css', '.js', '.md'] # 可根据需要扩展 + _, ext = os.path.splitext(filepath) + return ext.lower() in text_extensions + def read_file_content(path): """尝试多种编码读取文件内容,非文本或无法读取则跳过""" - encodings = ["utf-8", "utf-8-sig", "gbk", "latin-1"] + encodings = ["utf-8-sig", "utf-8", "gbk", "latin-1"] # 优先使用 utf-8-sig for enc in encodings: try: with io.open(path, "r", encoding=enc, errors="strict") as f: @@ -183,7 +315,7 @@ def read_file_content(path): return "[警告:该文件编码不兼容或为二进制格式,已跳过内容提取]" def generate_report(root, out_path): - with io.open(out_path, "w", encoding="utf-8") as out: + with io.open(out_path, "w", encoding="utf-8-sig") as out: # 使用 utf-8-sig 输出 # 第一部分:目录结构 write_line(out, "==================================================") write_line(out, "第一部分:项目目录结构") @@ -203,16 +335,17 @@ def generate_report(root, out_path): write_line(out, "[未找到符合条件的可处理文件]") for rel_path, full_path in all_files: - write_line(out, f"\n>>> FILE START: {rel_path} <<<") - write_line(out, "=" * 40) - - content = read_file_content(full_path) - out.write(content) - - if not content.endswith("\n"): - out.write("\n") - write_line(out, "=" * 40) - write_line(out, f">>> FILE END: {rel_path} <<<\n") + if is_text_file(full_path): # 仅处理文本文件 + write_line(out, f"\n>>> FILE START: {rel_path} <<<") + write_line(out, "=" * 40) + + content = read_file_content(full_path) + out.write(content) + + if not content.endswith("\n"): + out.write("\n") + write_line(out, "=" * 40) + write_line(out, f">>> FILE END: {rel_path} <<<\n") if __name__ == "__main__": try: @@ -228,15 +361,16 @@ if __name__ == "__main__": >>> FILE START: static/css/style.css <<< ======================================== -/* 全局样式 */ +/* 全局基础样式 */ body { margin: 0; padding: 0; background-color: #f0f2f5; - font-family: "Helvetica Neue", Helvetica, Arial, "Microsoft YaHei", sans-serif; + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; + -webkit-font-smoothing: antialiased; } -/* 顶部导航栏 */ +/* 顶部导航栏基础样式 */ .navbar { height: 60px; background: linear-gradient(135deg, #4088d8 0%, #2a69b5 100%); @@ -244,29 +378,49 @@ body { justify-content: center; align-items: center; color: white; - font-size: 24px; + font-size: 22px; font-weight: bold; box-shadow: 0 2px 10px rgba(0,0,0,0.1); + position: sticky; + top: 0; + z-index: 1000; } -/* 主体区域 - 设置为不换行,确保在一行显示 */ +/* 子页面特定的 Header 颜色 - 对应首页按钮色系 */ +.header-subject { background: linear-gradient(135deg, #0066cc 0%, #004499 100%) !important; } +.header-question { background: linear-gradient(135deg, #009944 0%, #007733 100%) !important; } +.header-paper { background: linear-gradient(135deg, #e67e22 0%, #d35400 100%) !important; } +.header-setting { background: linear-gradient(135deg, #8e44ad 0%, #732d91 100%) !important; } + +/* 主体容器 - 响应式 Flex 布局 */ .container { display: flex; - flex-direction: row; /* 横向排列 */ + flex-wrap: wrap; /* 核心:允许在空间不足时换行 */ + flex-direction: row; justify-content: center; align-items: center; - gap: 20px; /* 适当缩小间距以适应屏幕宽度 */ - padding: 60px 20px; + gap: 20px; + padding: 40px 20px; + max-width: 1200px; + margin: 0 auto; +} + +/* 详情页容器 */ +.sub-container { + padding: 30px 20px; + text-align: center; + max-width: 800px; margin: 0 auto; - overflow-x: auto; /* 如果屏幕太窄,允许横向滚动 */ } /* 卡片按钮基础样式 */ .card { - width: 260px; /* 略微调窄一点点确保一行能放下 */ - height: 80px; + flex: 1; /* 自动伸缩 */ + min-width: 240px; /* 保证电脑端不会太窄 */ + max-width: 280px; /* 保证电脑端不会太宽 */ + height: 85px; background-color: white; - border-radius: 12px; + border-radius: 15px; display: flex; align-items: center; justify-content: center; @@ -275,45 +429,77 @@ body { cursor: pointer; transition: all 0.3s cubic-bezier(0.25, 0.8, 0.25, 1); text-decoration: none; - box-shadow: 0 4px 6px rgba(0,0,0,0.05); + box-shadow: 0 4px 12px rgba(0,0,0,0.08); border: 1px solid rgba(0,0,0,0.05); } .card:hover { transform: translateY(-5px); - box-shadow: 0 8px 15px rgba(0,0,0,0.1); + box-shadow: 0 12px 20px rgba(0,0,0,0.15); } -/* 丰富的配色方案 */ -/* ① 科目面板 - 蓝色系 */ -.card-subject { color: #0066cc; border-left: 6px solid #0066cc; } -.card-subject:hover { background-color: #f0f7ff; color: #004499; } +/* 四个模块的丰富配色方案 */ +.card-subject { color: #0066cc; border-left: 8px solid #0066cc; } +.card-subject:hover { background-color: #f0f7ff; } -/* ② 题目管理 - 绿色系 */ -.card-question { color: #009944; border-left: 6px solid #009944; } -.card-question:hover { background-color: #f0fff4; color: #007733; } +.card-question { color: #009944; border-left: 8px solid #009944; } +.card-question:hover { background-color: #f0fff4; } -/* ③ 试卷管理 - 橙色系 */ -.card-paper { color: #e67e22; border-left: 6px solid #e67e22; } -.card-paper:hover { background-color: #fffaf5; color: #d35400; } +.card-paper { color: #e67e22; border-left: 8px solid #e67e22; } +.card-paper:hover { background-color: #fffaf5; } -/* ④ 系统设定 - 紫色/灰色系 */ -.card-setting { color: #8e44ad; border-left: 6px solid #8e44ad; } -.card-setting:hover { background-color: #f9f5fb; color: #732d91; } +.card-setting { color: #8e44ad; border-left: 8px solid #8e44ad; } +.card-setting:hover { background-color: #f9f5fb; } +/* 图标/数字样式 */ .icon-num { margin-right: 12px; font-size: 22px; - font-style: italic; - opacity: 0.8; + font-weight: bold; + opacity: 0.7; } -/* 子页面特定的 Header 颜色 */ -.header-subject { background: linear-gradient(135deg, #0066cc 0%, #004499 100%) !important; } -.header-question { background: linear-gradient(135deg, #009944 0%, #007733 100%) !important; } -.header-paper { background: linear-gradient(135deg, #e67e22 0%, #d35400 100%) !important; } -.header-setting { background: linear-gradient(135deg, #8e44ad 0%, #732d91 100%) !important; } +/* 返回按钮样式 */ +.back-btn { + display: inline-block; + margin-top: 30px; + padding: 12px 30px; + background: #666; + color: white; + text-decoration: none; + border-radius: 25px; + font-weight: 500; + transition: background 0.3s; +} +.back-btn:hover { + background: #333; +} + +/* === 移动端适配 (iPhone 等) === */ +@media (max-width: 600px) { + .navbar { + font-size: 18px; + height: 50px; + } + + .container { + padding: 20px 15px; + gap: 15px; + } + .card { + /* 在手机上,让按钮占据几乎全部宽度,防止并排重叠 */ + min-width: 100%; + max-width: 100%; + height: 75px; + font-size: 17px; + border-left-width: 10px; /* 手机上加粗色块更醒目 */ + } + + .sub-container h2 { + font-size: 20px; + } +} ======================================== >>> FILE END: static/css/style.css <<< @@ -395,6 +581,152 @@ body { >>> FILE END: templates/questions/index.html <<< +>>> FILE START: templates/questions/question_list.html <<< +======================================== + + + + + + 试题列表 + + + + +
试题列表
+
+ + + + + + + + + + +
+ +
+ + + + + + + + + + + + + + + + + + +
ID科目题目类型题干内容答案解析答题时间输入时间题目知识点
正在加载数据...
+
+ + + + +======================================== +>>> FILE END: templates/questions/question_list.html <<< + + >>> FILE START: templates/settings/index.html <<< ======================================== @@ -421,28 +753,429 @@ body { >>> FILE END: templates/settings/index.html <<< +>>> FILE START: templates/subject/chapters.html <<< +======================================== + + + + + + 章节分析 + + + + + +
+
+ + + + 返回 +
+ +
+ + + + + + + + + + +
科目章节总数待完成
+
+
+ + + + +======================================== +>>> FILE END: templates/subject/chapters.html <<< + + >>> FILE START: templates/subject/index.html <<< ======================================== - + - 科目首页 - 错题系统 - + + 科目概要面板 + - + + +
-

这里是科目首页内容区域

-

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

- 返回首页 + +
+
+ + +
+ + +
+ {% for stage in stages %} +
+ {{ stage.stage_name }} +
+ {% endfor %} +
+
+ + +
+ +
+

错题情况回顾

+ 0% +
总数0
+
已解决0
+
遗留0
+
+ + +
+

强化推题追踪

+ 0% +
总数0
+
已完成0
+
遗留0
+
+
+ + + + + +
+ 返回首页 +
+ + ======================================== >>> FILE END: templates/subject/index.html <<< + +>>> FILE START: templates/subject/knowledge.html <<< +======================================== + + + + + + 知识点分析 + + + + + +
+
+ + + + 返回 +
+ +
+ + + + + + + + + + +
科目知识点内容总数待完成
+
+
+ + + + +======================================== +>>> FILE END: templates/subject/knowledge.html <<< + diff --git a/templates/questions/question_list.html b/templates/questions/question_list.html new file mode 100644 index 0000000..a76aea1 --- /dev/null +++ b/templates/questions/question_list.html @@ -0,0 +1,140 @@ + + + + + + 试题列表 + + + + +
试题列表
+
+ + + + + + + + + + +
+ +
+ + + + + + + + + + + + + + + + + + +
ID科目题目类型题干内容答案解析答题时间输入时间题目知识点
正在加载数据...
+
+ + + +