]> acesimba.cloud Git - studybase.git/commitdiff
update: 修改generate_report.py,修复输出文件乱码问题
authorroot <root@localhost.localdomain>
Wed, 20 May 2026 02:40:12 +0000 (10:40 +0800)
committerroot <root@localhost.localdomain>
Wed, 20 May 2026 02:40:12 +0000 (10:40 +0800)
generate_report.py
project_all_in_one.txt
templates/questions/question_list.html [new file with mode: 0644]

index 0d6d16bbfc66e09a12c60d3c485d4393ec04eda5..b21b5c48d9872e436913dc44d23c55c948db7bf2 100644 (file)
@@ -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:
index 8816595ce24b2088e192eb91187d3b975dce29f4..304571893be349070b1f3db484b279324e842b2b 100644 (file)
@@ -1,9 +1,11 @@
-==================================================
+==================================================
 第一部分:项目目录结构
 ==================================================
 ./
     .gitignore
+    app.log
     app.py
+    database.py
     generate_report.py
     custom/
         custom/conf/
             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 <<<
+========================================
+<!DOCTYPE html>
+<html lang="zh-CN">
+<head>
+    <meta charset="UTF-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <title>试题列表</title>
+    <link rel="stylesheet" href="/static/css/style.css">
+    <style>
+        body {
+            font-family: 'Microsoft YaHei', sans-serif;
+            background-color: #F0F0F0;
+        }
+        .header {
+            background-color: #4A90E2;
+            color: white;
+            padding: 15px;
+            text-align: center;
+            font-size: 24px;
+        }
+        .filter-panel {
+            margin: 20px;
+            background: white;
+            padding: 15px;
+            border-radius: 8px;
+            box-shadow: 0 2px 4px rgba(0,0,0,0.1);
+        }
+        .filter-panel label {
+            margin-right: 10px;
+        }
+        .table-container {
+            margin: 20px;
+            background: white;
+            border-radius: 8px;
+            box-shadow: 0 2px 4px rgba(0,0,0,0.1);
+        }
+        table {
+            width: 100%;
+            border-collapse: collapse;
+        }
+        th, td {
+            padding: 12px;
+            text-align: left;
+            border-bottom: 1px solid #ddd;
+        }
+        th {
+            background-color: #f2f2f2;
+        }
+    </style>
+</head>
+<body>
+    <div class="header">试题列表</div>
+    <div class="filter-panel">
+        <label for="subjectFilter">科目:</label>
+        <select id="subjectFilter">
+            <option value="全部">全部</option>
+            <!-- 动态加载科目选项 -->
+            {% for subject in subjects %}
+            <option value="{{ subject }}">{{ subject }}</option>
+            {% endfor %}
+        </select>
+        
+        <label for="questionTypeFilter">题目类型:</label>
+        <select id="questionTypeFilter">
+            <option value="全部">全部</option>
+            <option value="错题">错题</option>
+            <option value="推题">推题</option>
+        </select>
+        
+        <label for="answerStatusFilter">答题状态:</label>
+        <select id="answerStatusFilter">
+            <option value="全部">全部</option>
+            <option value="未完成">未完成</option>
+            <option value="正确">正确</option>
+            <option value="错误">错误</option>
+            <option value="部分正确">部分正确</option>
+        </select>
+        
+        <button id="searchButton">搜索</button>
+    </div>
+
+    <div class="table-container">
+        <table id="questionTable">
+            <thead>
+                <tr>
+                    <th>ID</th>
+                    <th>科目</th>
+                    <th>题目类型</th>
+                    <th>题干内容</th>
+                    <th>答案解析</th>
+                    <th>答题时间</th>
+                    <th>输入时间</th>
+                    <th>题目知识点</th>
+                </tr>
+            </thead>
+            <tbody id="questionBody">
+                <tr>
+                    <td colspan="8">正在加载数据...</td>
+                </tr>
+            </tbody>
+        </table>
+    </div>
+
+    <script>
+        async function loadQuestionData() {
+            const subject = document.getElementById('subjectFilter').value;
+            const questionType = document.getElementById('questionTypeFilter').value;
+            const answerStatus = document.getElementById('answerStatusFilter').value;
+
+            const response = await fetch(`/api/question_list?subject=${subject}&question_type=${questionType}&answer_status=${answerStatus}`);
+            const data = await response.json();
+
+            const questionBody = document.getElementById('questionBody');
+            questionBody.innerHTML = '';
+
+            if (data.error) {
+                questionBody.innerHTML = '<tr><td colspan="8">加载失败: ' + data.error + '</td></tr>';
+                return;
+            }
+
+            data.forEach(question => {
+                const row = document.createElement('tr');
+                row.innerHTML = `
+                    <td>${question.id}</td>
+                    <td>${question.Subject || '-'}</td>
+                    <td>${question.question_type || '-'}</td>
+                    <td>${question.content || '-'}</td>
+                    <td>${question.analysis || '-'}</td>
+                    <td>${question.answer_time || '-'}</td>
+                    <td>${question.input_time || '-'}</td>
+                    <td>${question.knowledge_points || '-'}</td>
+                `;
+                questionBody.appendChild(row);
+            });
+        }
+
+        document.getElementById('searchButton').addEventListener('click', loadQuestionData);
+        document.addEventListener('DOMContentLoaded', loadQuestionData);
+    </script>
+</body>
+</html>
+========================================
+>>> FILE END: templates/questions/question_list.html <<<
+
+
 >>> FILE START: templates/settings/index.html <<<
 ========================================
 <!DOCTYPE html>
@@ -421,28 +753,429 @@ body {
 >>> FILE END: templates/settings/index.html <<<
 
 
+>>> FILE START: templates/subject/chapters.html <<<
+========================================
+<!DOCTYPE html>
+<html lang="zh-CN">
+<head>
+    <meta charset="UTF-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
+    <title>章节分析</title>
+    <link rel="stylesheet" href="/static/css/style.css">
+    <style>
+        .filter-bar { background: white; padding: 12px; margin-bottom: 15px; border-radius: 12px; display: flex; gap: 8px; box-shadow: 0 2px 8px rgba(0,0,0,0.05); align-items: center; }
+        /* 缩短科目选择框 */
+        #subjectFilter { width: 90px; flex: none; padding: 8px 4px; border: 1px solid #ddd; border-radius: 6px; }
+        #keyword { flex: 1; padding: 8px; border: 1px solid #ddd; border-radius: 6px; min-width: 0; }
+        .btn-query { background: #4A90E2; color: white; border: none; padding: 8px 15px; border-radius: 6px; cursor: pointer; white-space: nowrap; }
+        /* 新增:筛选栏中的返回按钮 */
+        .btn-back-nav { background: #f0f0f0; color: #666; border: none; padding: 8px 12px; border-radius: 6px; cursor: pointer; white-space: nowrap; text-decoration: none; font-size: 14px; }
+        
+        .table-container { background: white; border-radius: 12px; overflow: hidden; box-shadow: 0 2px 8px rgba(0,0,0,0.05); margin-bottom: 50px; }
+        table { width: 100%; border-collapse: collapse; }
+        th, td { padding: 12px 10px; text-align: left; border-bottom: 1px solid #f0f0f0; font-size: 14px; }
+        th { background-color: #f8f9fa; color: #666; font-weight: 600; cursor: pointer; position: relative; }
+        th:after { content: '↕'; font-size: 10px; margin-left: 4px; color: #ccc; }
+        .clickable { color: #4A90E2; cursor: pointer; font-weight: bold; }
+        .num-badge { font-weight: bold; color: #d32f2f; background: #fff1f0; padding: 2px 6px; border-radius: 4px; }
+    </style>
+</head>
+<body>
+    <div class="navbar header-subject"><span class="nav-title">章节分析</span></div>
+    <div class="sub-container">
+        <div class="filter-bar">
+            <select id="subjectFilter">
+                <option value="全部">全部</option>
+                {% for sub in subjects %}<option value="{{ sub.Subject }}">{{ sub.Subject }}</option>{% endfor %}
+            </select>
+            <input type="text" id="keyword" placeholder="搜索章节...">
+            <button class="btn-query" onclick="loadData()">查询</button>
+            <a href="/subject/" class="btn-back-nav">返回</a>
+        </div>
+
+        <div class="table-container">
+            <table>
+                <thead>
+                    <tr>
+                        <th onclick="sortTable('SubjectName')">科目</th>
+                        <th onclick="sortTable('ChapterDesc')">章节</th>
+                        <th onclick="sortTable('total')">总数</th>
+                        <th onclick="sortTable('not_correct')">待完成</th>
+                    </tr>
+                </thead>
+                <tbody id="chapterBody"></tbody>
+            </table>
+        </div>
+    </div>
+
+    <script>
+        let currentData = [];
+        let sortDirection = -1;
+
+        async function loadData() {
+            const sub = document.getElementById('subjectFilter').value;
+            const key = document.getElementById('keyword').value;
+            const tbody = document.getElementById('chapterBody');
+            tbody.innerHTML = '<tr><td colspan="4" style="text-align:center;padding:20px;">加载中...</td></tr>';
+            try {
+                const response = await fetch('/api/chapters?subject=' + encodeURIComponent(sub) + '&keyword=' + encodeURIComponent(key));
+                currentData = await response.json();
+                currentData.sort((a, b) => b.not_correct - a.not_correct);
+                renderTable(currentData);
+            } catch (err) {
+                tbody.innerHTML = '<tr><td colspan="4" style="text-align:center;color:red;">加载失败</td></tr>';
+            }
+        }
+
+        function sortTable(column) {
+            sortDirection *= -1;
+            currentData.sort((a, b) => {
+                let valA = a[column] || '';
+                let valB = b[column] || '';
+                return typeof valA === 'string' 
+                    ? sortDirection === 1 ? valA.localeCompare(valB) : valB.localeCompare(valA)
+                    : sortDirection === 1 ? valA - valB : valB - valA;
+            });
+            renderTable(currentData);
+        }
+
+        function renderTable(data) {
+            const tbody = document.getElementById('chapterBody');
+            if (!data || data.length === 0) {
+                tbody.innerHTML = '<tr><td colspan="4" style="text-align:center;padding:20px;">暂无数据</td></tr>';
+                return;
+            }
+            tbody.innerHTML = data.map(row => `
+                <tr>
+                    <td>${row.SubjectName || '-'}</td>
+                    <td class="clickable" onclick="location.href='/subject/knowledge?chapter_id=${row.chapter_id}'">${row.ChapterDesc || '未命名'}</td>
+                    <td>${row.total || 0}</td>
+                    <td><span class="num-badge">${row.not_correct || 0}</span></td>
+                </tr>
+            `).join('');
+        }
+        window.onload = loadData;
+    </script>
+</body>
+</html>
+========================================
+>>> FILE END: templates/subject/chapters.html <<<
+
+
 >>> FILE START: templates/subject/index.html <<<
 ========================================
 <!DOCTYPE html>
-<html lang="zh">
+<html lang="zh-CN">
 <head>
     <meta charset="UTF-8">
-    <title>科目首页 - 错题系统</title>
-    <link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
+    <title>科目概要面板</title>
+    <link rel="stylesheet" href="/static/css/style.css">
     <style>
-        .sub-container { padding: 50px; text-align: center; }
-        .back-btn { display: inline-block; margin-top: 20px; padding: 10px 20px; background: #666; color: white; text-decoration: none; border-radius: 5px; }
+        /* 针对统计页面的微调 */
+        .stats-grid {
+            display: grid;
+            grid-template-columns: 1fr 1fr;
+            gap: 15px;
+            margin-bottom: 20px;
+        }
+        @media (max-width: 600px) {
+            .stats-grid { grid-template-columns: 1fr; }
+        }
+        .stat-card {
+            background: white;
+            padding: 15px;
+            border-radius: 12px;
+            box-shadow: 0 4px 10px rgba(0,0,0,0.05);
+            border-top: 4px solid #4A90E2;
+        }
+        .stat-card.push { border-top-color: #52C41A; }
+        .stat-card h3 { margin: 0 0 10px 0; font-size: 16px; color: #333; }
+        .stat-line { display: flex; justify-content: space-between; margin: 8px 0; font-size: 14px; color: #666; }
+        .stat-value { font-weight: bold; color: #333; }
+        .stat-ratio { 
+            font-size: 24px; 
+            font-weight: bold; 
+            color: #4A90E2; 
+            text-align: center; 
+            margin: 10px 0;
+            display: block;
+        }
+        .push .stat-ratio { color: #52C41A; }
+        
+        .filter-section {
+            background: #fff;
+            padding: 15px;
+            border-radius: 12px;
+            margin-bottom: 20px;
+        }
+        .stage-chips {
+            display: flex;
+            flex-wrap: wrap;
+            gap: 8px;
+            margin-top: 10px;
+        }
+        .stage-chip {
+            padding: 5px 12px;
+            border-radius: 15px;
+            background: #f0f2f5;
+            font-size: 13px;
+            cursor: pointer;
+            border: 1px solid #ddd;
+        }
+        .stage-chip.active {
+            background: #4A90E2;
+            color: white;
+            border-color: #4A90E2;
+        }
+        .analysis-btn-group {
+            display: flex;
+            gap: 15px;
+            margin: 20px 0;
+        }
+        .analysis-btn-group .card {
+            flex: 1;
+            height: 70px;
+            margin: 0;
+            display: flex;
+            align-items: center;
+            justify-content: center;
+            font-size: 16px;
+            font-weight: bold;
+        }
     </style>
 </head>
 <body>
-    <div class="navbar header-subject">科目首页</div>
+    <!-- 顶部导航 -->
+    <div class="navbar header-subject">
+        <span class="nav-title">科目概要面板</span>
+    </div>
+
     <div class="sub-container">
-        <h2>这里是科目首页内容区域</h2>
-        <p>正在从原 Python 程序迁移逻辑...</p>
-        <a href="/" class="back-btn">返回首页</a>
+        <!-- 1. 筛选区 -->
+        <div class="filter-section">
+            <div style="margin-bottom: 15px;">
+                <label style="font-weight: bold;">当前科目:</label>
+                <select id="subjectSelect" style="width: 100%; padding: 10px; margin-top: 5px; border-radius: 8px; border: 1px solid #ddd;">
+                    {% for sub in subjects %}
+                    <option value="{{ sub.ID }}" {% if sub.ID == default_id %}selected{% endif %}>{{ sub.name }}</option>
+                    {% endfor %}
+                </select>
+            </div>
+            
+            <label style="font-weight: bold;">统计阶段:</label>
+            <div class="stage-chips" id="stageContainer">
+                {% for stage in stages %}
+                <div class="stage-chip active" data-id="{{ stage.ID }}" onclick="toggleStage(this)">
+                    {{ stage.stage_name }}
+                </div>
+                {% endfor %}
+            </div>
+        </div>
+
+        <!-- 2. 统计卡片区 -->
+        <div class="stats-grid">
+            <!-- 错题卡片 -->
+            <div class="stat-card">
+                <h3>错题情况回顾</h3>
+                <span class="stat-ratio" id="wrong_ratio">0%</span>
+                <div class="stat-line"><span>总数</span><span class="stat-value" id="wrong_total">0</span></div>
+                <div class="stat-line"><span>已解决</span><span class="stat-value" id="wrong_fixed" style="color: #2E7D32;">0</span></div>
+                <div class="stat-line"><span>遗留</span><span class="stat-value" id="wrong_remained" style="color: #D32F2F;">0</span></div>
+            </div>
+
+            <!-- 推题卡片 -->
+            <div class="stat-card push">
+                <h3>强化推题追踪</h3>
+                <span class="stat-ratio" id="push_ratio">0%</span>
+                <div class="stat-line"><span>总数</span><span class="stat-value" id="push_total">0</span></div>
+                <div class="stat-line"><span>已完成</span><span class="stat-value" id="push_fixed" style="color: #2E7D32;">0</span></div>
+                <div class="stat-line"><span>遗留</span><span class="stat-value" id="push_remained" style="color: #D32F2F;">0</span></div>
+            </div>
+        </div>
+
+        <!-- 3. 深入分析按钮区 -->
+        <div class="analysis-btn-group">
+            <a href="/subject/chapters" class="card card-subject">
+                📊 章节分析
+            </a>
+            <a href="/subject/knowledge" class="card card-question">
+                💡 知识点分析
+            </a>
+        </div>
+
+        <!-- 4. 返回按钮 -->
+        <div style="text-align: center; margin-top: 30px;">
+            <a href="/" class="back-btn">返回首页</a>
+        </div>
     </div>
+
+    <script>
+        // 切换阶段选中状态
+        function toggleStage(el) {
+            el.classList.toggle('active');
+            refreshStats();
+        }
+
+        // 获取当前选中的阶段ID数组
+        function getSelectedStages() {
+            const actives = document.querySelectorAll('.stage-chip.active');
+            return Array.from(actives).map(el => el.dataset.id);
+        }
+
+        // 异步刷新数据
+        async function refreshStats() {
+            const subId = document.getElementById('subjectSelect').value;
+            const stages = getSelectedStages().join(',');
+            
+            if (!stages) {
+                updateUI(null);
+                return;
+            }
+
+            try {
+                const response = await fetch(`/api/subject_stats?subject_id=${subId}&stage_ids=${stages}`);
+                const data = await response.json();
+                updateUI(data);
+            } catch (error) {
+                console.error("加载数据失败:", error);
+            }
+        }
+
+        function updateUI(data) {
+            if (!data) {
+                const keys = ['wrong_total', 'wrong_fixed', 'wrong_remained', 'wrong_ratio', 
+                             'push_total', 'push_fixed', 'push_remained', 'push_ratio'];
+                keys.forEach(k => document.getElementById(k).innerText = k.includes('ratio') ? '0%' : '0');
+                return;
+            }
+            
+            document.getElementById('wrong_total').innerText = data.wrong.total;
+            document.getElementById('wrong_fixed').innerText = data.wrong.fixed;
+            document.getElementById('wrong_remained').innerText = data.wrong.remained;
+            document.getElementById('wrong_ratio').innerText = data.wrong.ratio;
+
+            document.getElementById('push_total').innerText = data.push.total;
+            document.getElementById('push_fixed').innerText = data.push.fixed;
+            document.getElementById('push_remained').innerText = data.push.remained;
+            document.getElementById('push_ratio').innerText = data.push.ratio;
+        }
+
+        // 监听科目切换
+        document.getElementById('subjectSelect').addEventListener('change', refreshStats);
+
+        // 页面加载时自动执行一次
+        window.onload = refreshStats;
+    </script>
 </body>
 </html>
 ========================================
 >>> FILE END: templates/subject/index.html <<<
 
+
+>>> FILE START: templates/subject/knowledge.html <<<
+========================================
+<!DOCTYPE html>
+<html lang="zh-CN">
+<head>
+    <meta charset="UTF-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
+    <title>知识点分析</title>
+    <link rel="stylesheet" href="/static/css/style.css">
+    <style>
+        .filter-bar { background: white; padding: 12px; margin-bottom: 15px; border-radius: 12px; display: flex; gap: 8px; box-shadow: 0 2px 8px rgba(0,0,0,0.05); align-items: center; }
+        #subjectFilter { width: 90px; flex: none; padding: 8px 4px; border: 1px solid #ddd; border-radius: 6px; }
+        #keyword { flex: 1; padding: 8px; border: 1px solid #ddd; border-radius: 6px; min-width: 0; }
+        .btn-query { background: #4A90E2; color: white; border: none; padding: 8px 15px; border-radius: 6px; cursor: pointer; white-space: nowrap; }
+        .btn-back-nav { background: #f0f0f0; color: #666; border: none; padding: 8px 12px; border-radius: 6px; cursor: pointer; white-space: nowrap; text-decoration: none; font-size: 14px; }
+        
+        .table-container { background: white; border-radius: 12px; overflow: hidden; box-shadow: 0 2px 8px rgba(0,0,0,0.05); margin-bottom: 50px; }
+        table { width: 100%; border-collapse: collapse; }
+        th, td { padding: 12px 10px; text-align: left; border-bottom: 1px solid #f0f0f0; font-size: 14px; }
+        th { background-color: #f8f9fa; color: #666; font-weight: 600; cursor: pointer; position: relative; }
+        th:after { content: '↕'; font-size: 10px; margin-left: 4px; color: #ccc; }
+        .num-badge { font-weight: bold; color: #d32f2f; background: #fff1f0; padding: 2px 6px; border-radius: 4px; }
+    </style>
+</head>
+<body>
+    <div class="navbar header-subject"><span class="nav-title">知识点分析</span></div>
+    <div class="sub-container">
+        <div class="filter-bar">
+            <select id="subjectFilter">
+                <option value="全部">全部</option>
+                {% for sub in subjects %}<option value="{{ sub.Subject }}">{{ sub.Subject }}</option>{% endfor %}
+            </select>
+            <input type="text" id="keyword" placeholder="搜索知识点...">
+            <button class="btn-query" onclick="loadData()">查询</button>
+            <a href="/subject/chapters" class="btn-back-nav">返回</a>
+        </div>
+
+        <div class="table-container">
+            <table>
+                <thead>
+                    <tr>
+                        <th onclick="sortTable('subject_name')">科目</th>
+                        <th onclick="sortTable('content')">知识点内容</th>
+                        <th onclick="sortTable('total')">总数</th>
+                        <th onclick="sortTable('not_correct')">待完成</th>
+                    </tr>
+                </thead>
+                <tbody id="knowledgeBody"></tbody>
+            </table>
+        </div>
+    </div>
+
+    <script>
+        let currentData = [];
+        let sortDirection = -1;
+        const chapterId = "{{ chapter_id }}";
+
+        async function loadData() {
+            const sub = document.getElementById('subjectFilter').value;
+            const key = document.getElementById('keyword').value;
+            const tbody = document.getElementById('knowledgeBody');
+            tbody.innerHTML = '<tr><td colspan="4" style="text-align:center;padding:20px;">加载中...</td></tr>';
+            try {
+                const response = await fetch(`/api/knowledge?subject=${encodeURIComponent(sub)}&keyword=${encodeURIComponent(key)}&chapter_id=${chapterId}`);
+                currentData = await response.json();
+                currentData.sort((a, b) => b.not_correct - a.not_correct);
+                renderTable(currentData);
+            } catch (err) {
+                tbody.innerHTML = '<tr><td colspan="4" style="text-align:center;color:red;">加载失败</td></tr>';
+            }
+        }
+
+        function sortTable(column) {
+            sortDirection *= -1;
+            currentData.sort((a, b) => {
+                let valA = a[column] || '';
+                let valB = b[column] || '';
+                return typeof valA === 'string'
+                    ? sortDirection === 1 ? valA.localeCompare(valB) : valB.localeCompare(valA)
+                    : sortDirection === 1 ? valA - valB : valB - valA;
+            });
+            renderTable(currentData);
+        }
+
+        function renderTable(data) {
+            const tbody = document.getElementById('knowledgeBody');
+            if (!data || data.length === 0) {
+                tbody.innerHTML = '<tr><td colspan="4" style="text-align:center;padding:20px;">暂无数据</td></tr>';
+                return;
+            }
+            tbody.innerHTML = data.map(row => `
+                <tr>
+                    <td>${row.subject_name || '-'}</td>
+                    <td>${row.content || '-'}</td>
+                    <td>${row.total || 0}</td>
+                    <td><span class="num-badge">${row.not_correct || 0}</span></td>
+                </tr>
+            `).join('');
+        }
+        window.onload = loadData;
+    </script>
+</body>
+</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 (file)
index 0000000..a76aea1
--- /dev/null
@@ -0,0 +1,140 @@
+<!DOCTYPE html>
+<html lang="zh-CN">
+<head>
+    <meta charset="UTF-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <title>试题列表</title>
+    <link rel="stylesheet" href="/static/css/style.css">
+    <style>
+        body {
+            font-family: 'Microsoft YaHei', sans-serif;
+            background-color: #F0F0F0;
+        }
+        .header {
+            background-color: #4A90E2;
+            color: white;
+            padding: 15px;
+            text-align: center;
+            font-size: 24px;
+        }
+        .filter-panel {
+            margin: 20px;
+            background: white;
+            padding: 15px;
+            border-radius: 8px;
+            box-shadow: 0 2px 4px rgba(0,0,0,0.1);
+        }
+        .filter-panel label {
+            margin-right: 10px;
+        }
+        .table-container {
+            margin: 20px;
+            background: white;
+            border-radius: 8px;
+            box-shadow: 0 2px 4px rgba(0,0,0,0.1);
+        }
+        table {
+            width: 100%;
+            border-collapse: collapse;
+        }
+        th, td {
+            padding: 12px;
+            text-align: left;
+            border-bottom: 1px solid #ddd;
+        }
+        th {
+            background-color: #f2f2f2;
+        }
+    </style>
+</head>
+<body>
+    <div class="header">试题列表</div>
+    <div class="filter-panel">
+        <label for="subjectFilter">科目:</label>
+        <select id="subjectFilter">
+            <option value="全部">全部</option>
+            <!-- 动态加载科目选项 -->
+            {% for subject in subjects %}
+            <option value="{{ subject }}">{{ subject }}</option>
+            {% endfor %}
+        </select>
+        
+        <label for="questionTypeFilter">题目类型:</label>
+        <select id="questionTypeFilter">
+            <option value="全部">全部</option>
+            <option value="错题">错题</option>
+            <option value="推题">推题</option>
+        </select>
+        
+        <label for="answerStatusFilter">答题状态:</label>
+        <select id="answerStatusFilter">
+            <option value="全部">全部</option>
+            <option value="未完成">未完成</option>
+            <option value="正确">正确</option>
+            <option value="错误">错误</option>
+            <option value="部分正确">部分正确</option>
+        </select>
+        
+        <button id="searchButton">搜索</button>
+    </div>
+
+    <div class="table-container">
+        <table id="questionTable">
+            <thead>
+                <tr>
+                    <th>ID</th>
+                    <th>科目</th>
+                    <th>题目类型</th>
+                    <th>题干内容</th>
+                    <th>答案解析</th>
+                    <th>答题时间</th>
+                    <th>输入时间</th>
+                    <th>题目知识点</th>
+                </tr>
+            </thead>
+            <tbody id="questionBody">
+                <tr>
+                    <td colspan="8">正在加载数据...</td>
+                </tr>
+            </tbody>
+        </table>
+    </div>
+
+    <script>
+        async function loadQuestionData() {
+            const subject = document.getElementById('subjectFilter').value;
+            const questionType = document.getElementById('questionTypeFilter').value;
+            const answerStatus = document.getElementById('answerStatusFilter').value;
+
+            const response = await fetch(`/api/question_list?subject=${subject}&question_type=${questionType}&answer_status=${answerStatus}`);
+            const data = await response.json();
+
+            const questionBody = document.getElementById('questionBody');
+            questionBody.innerHTML = '';
+
+            if (data.error) {
+                questionBody.innerHTML = '<tr><td colspan="8">加载失败: ' + data.error + '</td></tr>';
+                return;
+            }
+
+            data.forEach(question => {
+                const row = document.createElement('tr');
+                row.innerHTML = `
+                    <td>${question.id}</td>
+                    <td>${question.Subject || '-'}</td>
+                    <td>${question.question_type || '-'}</td>
+                    <td>${question.content || '-'}</td>
+                    <td>${question.analysis || '-'}</td>
+                    <td>${question.answer_time || '-'}</td>
+                    <td>${question.input_time || '-'}</td>
+                    <td>${question.knowledge_points || '-'}</td>
+                `;
+                questionBody.appendChild(row);
+            });
+        }
+
+        document.getElementById('searchButton').addEventListener('click', loadQuestionData);
+        document.addEventListener('DOMContentLoaded', loadQuestionData);
+    </script>
+</body>
+</html>