]> acesimba.cloud Git - words.git/commitdiff
首页可以正常显示进度
authorroot <root@localhost.localdomain>
Mon, 25 May 2026 15:14:59 +0000 (23:14 +0800)
committerroot <root@localhost.localdomain>
Mon, 25 May 2026 15:14:59 +0000 (23:14 +0800)
backend/db/update_db.sql [new file with mode: 0644]
backend/main.py
frontend/src/components/Home.css

diff --git a/backend/db/update_db.sql b/backend/db/update_db.sql
new file mode 100644 (file)
index 0000000..e24b32e
--- /dev/null
@@ -0,0 +1,43 @@
+-- ==========================================
+-- 1. 创建学习进度与统计日志表 (study_logs)
+-- ==========================================
+CREATE TABLE IF NOT EXISTS study_logs (
+    id           INTEGER PRIMARY KEY AUTOINCREMENT,
+    group_id     INTEGER NOT NULL,                     -- 组号
+    op_type      TEXT NOT NULL,                        -- 操作类型: 'study', 'test', 'review1', 'review2', 'review3'
+    status       TEXT NOT NULL DEFAULT 'completed',    -- 状态: 'completed', 'started'
+    duration_sec    INTEGER DEFAULT 0,                 -- 执行耗时 (秒)
+    word_count      INTEGER DEFAULT 0,                 -- 本组单词总数
+    first_correct   INTEGER DEFAULT 0,                 -- 第一次尝试正确个数
+    first_wrong     INTEGER DEFAULT 0,                 -- 第一次尝试错误个数
+    total_rounds    INTEGER DEFAULT 1,                 -- 通过本组所用的执行轮数
+    log_date     DATE,                                 -- 业务完成日期 (YYYY-MM-DD)
+    created_at   DATETIME DEFAULT CURRENT_TIMESTAMP,   -- 记录入库时间
+    remark       TEXT,                                 -- 备注
+    UNIQUE(group_id, op_type, log_date)                -- 唯一性约束
+);
+
+CREATE INDEX IF NOT EXISTS idx_progress_lookup ON study_logs (op_type, group_id);
+
+-- ==========================================
+-- 2. 将结构信息补充到 schema_comments
+-- ==========================================
+
+-- 插入表描述
+INSERT OR REPLACE INTO schema_comments (object_type, table_name, column_name, description)
+VALUES ('table', 'study_logs', NULL, '存储单词学习、测试和复习的进度及效率统计日志');
+
+-- 插入列描述
+INSERT OR REPLACE INTO schema_comments (object_type, table_name, column_name, description) VALUES
+('column', 'study_logs', 'id', '自增主键'),
+('column', 'study_logs', 'group_id', '单词分组编号'),
+('column', 'study_logs', 'op_type', '操作模式类型: study(学习), test(测试), review1/2/3(复习轮次)'),
+('column', 'study_logs', 'status', '任务执行状态: started(开始), completed(已完成)'),
+('column', 'study_logs', 'duration_sec', '完成该任务所花费的总时长(秒)'),
+('column', 'study_logs', 'word_count', '该分组包含的单词总数'),
+('column', 'study_logs', 'first_correct', '在第一轮测试/复习中直接答对的单词数量'),
+('column', 'study_logs', 'first_wrong', '在第一轮测试/复习中答错的单词数量'),
+('column', 'study_logs', 'total_rounds', '为了达到全对通过,该组测试重复执行的总轮数'),
+('column', 'study_logs', 'log_date', '学习任务归属的业务日期'),
+('column', 'study_logs', 'created_at', '数据库记录创建的系统时间'),
+('column', 'study_logs', 'remark', '额外备注信息或原始日志文本');
index 80eb8be4cdd8c10efce9c1d19b7aeb0f5c0d6fae..ce7dab375cef0033569f8fc1d24809eaf855ca0f 100644 (file)
@@ -2,7 +2,6 @@ from fastapi import FastAPI, HTTPException
 from fastapi.middleware.cors import CORSMiddleware
 import sqlite3
 import os
-import re
 import datetime
 from pydantic import BaseModel
 from typing import List, Optional
@@ -18,11 +17,13 @@ app.add_middleware(
     allow_headers=["*"],
 )
 
-# --- 文件路径配置 ---
-# 确保路径与你的服务器目录结构一致
+# --- 路径自动修复逻辑 ---
 BASE_DIR = os.path.dirname(os.path.abspath(__file__))
-DATABASE_PATH = os.path.join(BASE_DIR, "db", "bcd.db")
-LOG_FILE_PATH = os.path.join(BASE_DIR, "a", "log.txt")
+# 优先检查 /home/words/backend/a/bcd.db
+DATABASE_PATH = os.path.join(BASE_DIR, "a", "bcd.db")
+if not os.path.exists(DATABASE_PATH):
+    # 备选检查 /home/words/backend/db/bcd.db
+    DATABASE_PATH = os.path.join(BASE_DIR, "db", "bcd.db")
 
 # --- 数据模型 ---
 class HomeData(BaseModel):
@@ -37,32 +38,55 @@ class ProgressData(BaseModel):
     review2: str
     review3: str
 
-# --- 辅助逻辑函数(移植自原 MainApp) ---
+# --- 辅助逻辑函数 ---
 
 def calculate_hours_since(date_str: str) -> Optional[float]:
     if not date_str:
         return None
     try:
-        # 兼容原格式 "2023-05-24 10:00:00"
-        last = datetime.datetime.strptime(date_str[:19], "%Y-%m-%d %H:%M:%S")
+        # 兼容数据库中的 YYYY-MM-DD HH:MM:SS 或 YYYY-MM-DD
+        date_part = date_str[:19]
+        fmt = "%Y-%m-%d %H:%M:%S" if " " in date_part else "%Y-%m-%d"
+        last = datetime.datetime.strptime(date_part, fmt)
         return (datetime.datetime.now() - last).total_seconds() / 3600
     except:
         return None
 
-def get_progress_from_log():
-    """解析 log.txt 获取每个组的完成时间"""
+def get_progress_from_db():
+    """解析数据库 study_logs 表获取每个组的完成时间"""
     progress = {} 
-    if os.path.exists(LOG_FILE_PATH):
-        with open(LOG_FILE_PATH, 'r', encoding='utf-8') as f:
-            for line in f:
-                # 匹配原有的日志格式
-                match = re.search(r'PROGRESS_UPDATE: Group:(.+?), Type:(.+?), Status:completed, Date:(.+)', line)
-                if match:
-                    g_n, a_t, c_d = match.groups()
-                    g_n = g_n.strip()
-                    if g_n not in progress:
-                        progress[g_n] = {'study': None, 'test': None, 'review1': None, 'review2': None, 'review3': None}
-                    progress[g_n][a_t.strip()] = c_d.strip()
+    try:
+        if not os.path.exists(DATABASE_PATH):
+            return progress
+            
+        conn = sqlite3.connect(DATABASE_PATH)
+        conn.row_factory = sqlite3.Row
+        cursor = conn.cursor()
+        
+        # 查询每个组每种类型的最后完成时间
+        query = """
+            SELECT group_id, op_type, MAX(created_at) as last_date 
+            FROM study_logs 
+            WHERE status = 'completed'
+            GROUP BY group_id, op_type
+        """
+        cursor.execute(query)
+        rows = cursor.fetchall()
+        
+        for row in rows:
+            g_n = str(row['group_id'])
+            a_t = row['op_type']
+            c_d = row['last_date']
+            
+            if g_n not in progress:
+                progress[g_n] = {'study': None, 'test': None, 'review1': None, 'review2': None, 'review3': None}
+            
+            if a_t in progress[g_n]:
+                progress[g_n][a_t] = c_d
+                
+        conn.close()
+    except Exception as e:
+        print(f"Database error in get_progress_from_db: {e}")
     return progress
 
 # --- 路由实现 ---
@@ -84,17 +108,17 @@ async def get_home_data():
 @app.get("/api/home/process", response_model=List[ProgressData])
 async def get_home_process():
     try:
-        # 1. 从数据库获取所有分组
+        # 1. 获取所有分组
         conn = sqlite3.connect(DATABASE_PATH)
         cursor = conn.cursor()
         cursor.execute("SELECT DISTINCT groupid FROM words ORDER BY CAST(groupid AS INTEGER) ASC")
         all_groups = [str(row[0]) for row in cursor.fetchall()]
         conn.close()
 
-        # 2. ä»\8eæ\97¥å¿\97è§£æ\9e\90进度
-        log_progress = get_progress_from_log()
+        # 2. ä»\8eæ\95°æ\8d®åº\93读å\8f\96进度
+        log_progress = get_progress_from_db()
 
-        # 3. 模拟原有的 UI 显示逻辑
+        # 3. 业务逻辑处理 (完全保留你要求的 UI 逻辑)
         results = []
         first_unstudied = None
         for g in all_groups:
@@ -104,8 +128,6 @@ async def get_home_process():
 
         for g_name in all_groups:
             p = log_progress.get(g_name, {'study': None, 'test': None, 'review1': None, 'review2': None, 'review3': None})
-            
-            # 构建每一行数据
             row = {"group": f"Group {g_name}"}
 
             # 基础学习列
@@ -134,7 +156,6 @@ async def get_home_process():
                     row[key] = f"✔️ {p[key][:10]}"
                 elif p[prev_key]:
                     hours = calculate_hours_since(p[prev_key])
-                    # 原逻辑:超过4小时显示“尽快复习”,否则显示“开始复习”
                     if hours and hours > 4:
                         row[key] = "⚠️请尽快复习"
                     else:
index 1c3c9092c3b5828eeb2608130b33041c577e0d27..92770cca4a8335f28c8e017d8cfb6b6eb039f063 100644 (file)
     text-align: center;
   }
   
-  /* 四列等宽均分 */
-  th:nth-child(1), td:nth-child(1) { width: 25%; font-weight: 600; color: #1d4ed8; }
-  th:nth-child(2), td:nth-child(2) { width: 25%; }
-  th:nth-child(3), td:nth-child(3) { width: 25%; }
-  th:nth-child(4), td:nth-child(4) { width: 25%; color: #64748b; font-size: 13px; }
+ /* 修改为六列均分(100 / 6 ≈ 16.6%) */
+  th:nth-child(1), td:nth-child(1) { width: 16.6%; font-weight: 600; color: #1d4ed8; }
+  th:nth-child(2), td:nth-child(2) { width: 16.6%; }
+  th:nth-child(3), td:nth-child(3) { width: 16.6%; }
+  th:nth-child(4), td:nth-child(4) { width: 16.6%; }
+  th:nth-child(5), td:nth-child(5) { width: 16.6%; }
+  th:nth-child(6), td:nth-child(6) { width: 16.6%; color: #64748b; font-size: 13px; }
   
   /* 最后一行无边框 */
   tr:last-child td {