transform: rotate(360deg);
}
}
+/* 全局基础样式 */
+body {
+ margin: 0;
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
+ background-color: #f9fafb; /* 极简浅灰背景 */
+ color: #1a1a1a;
+ -webkit-font-smoothing: antialiased;
+}
+
+.App {
+ max-width: 1000px;
+ margin: 0 auto;
+ padding: 40px 20px;
+}
+
+/* 标题样式 */
+h1 {
+ font-size: 2rem;
+ font-weight: 700;
+ color: #111827;
+ margin-bottom: 30px;
+ text-align: center;
+ letter-spacing: -0.025em;
+}
+
+/* 表格容器:卡片风格 */
+.table-container {
+ background: #ffffff;
+ border-radius: 12px;
+ box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06);
+ overflow: hidden; /* 确保圆角生效 */
+ border: 1px solid #e5e7eb;
+}
+
+/* 表格样式 */
+table {
+ width: 100%;
+ border-collapse: collapse;
+ text-align: left;
+ font-size: 14px;
+}
+
+/* 表头 */
+thead {
+ background-color: #f8fafc;
+ border-bottom: 1px solid #e5e7eb;
+}
+
+th {
+ padding: 12px 16px;
+ font-weight: 600;
+ color: #4b5563;
+ text-transform: uppercase;
+ font-size: 12px;
+ letter-spacing: 0.05em;
+}
+
+/* 单元格 */
+td {
+ padding: 10px 16px;
+ border-bottom: 1px solid #f3f4f6;
+ line-height: 1.5;
+ color: #374151;
+}
+
+/* 紧凑调整:如果是单词列,加粗 */
+td:first-child {
+ font-weight: 600;
+ color: #2563eb; /* 蓝色链接色 */
+ width: 20%;
+}
+
+/* 悬停效果 */
+tr:hover {
+ background-color: #f9fafb;
+ transition: background-color 0.2s ease;
+}
+
+/* 响应式处理 */
+@media (max-width: 600px) {
+ .App {
+ padding: 20px 10px;
+ }
+ th, td {
+ padding: 8px 10px;
+ font-size: 13px;
+ }
+}
-.main-app {
- background-color: #fdfdfd;
+/* 全局基础样式 - 清爽护眼版 */
+* {
+ margin: 0;
+ padding: 0;
+ box-sizing: border-box;
+ }
+
+ body {
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
+ background-color: #f0f7ff;
+ color: #1a1a1a;
+ -webkit-font-smoothing: antialiased;
min-height: 100vh;
- padding: 40px;
- font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
-}
-
-.header {
+ padding: 20px 15px;
+ }
+
+ .App {
+ max-width: 900px;
+ margin: 0 auto;
+ display: flex;
+ flex-direction: column;
+ gap: 24px;
+ align-items: center;
+ }
+
+ /* 标题样式 */
+ h1 {
+ font-size: 22px;
+ font-weight: 700;
+ color: #1e40af;
text-align: center;
- margin-bottom: 20px;
-}
-
-.header h1 {
- font-size: 28px;
- color: #2c3e50;
- margin-bottom: 5px;
-}
-
-.stats-info {
- color: #7f8c8d;
- font-size: 14px;
-}
-
-.action-bar {
+ margin: 10px 0;
+ }
+
+ /* 统计信息居中排版 */
+ .stat-info {
+ text-align: center;
+ font-size: 15px;
+ color: #475569;
+ line-height: 1.8;
+ }
+
+ /* 按钮组居中布局 */
+ .btn-group {
display: flex;
+ gap: 16px;
justify-content: center;
- margin-bottom: 20px;
-}
-
-.toggle-btn {
- background: white;
- border: 1px solid #dee2e6;
- padding: 8px 20px;
- color: #6c757d;
+ flex-wrap: wrap;
+ }
+
+ /* 美化通用按钮样式 */
+ .btn {
+ padding: 10px 24px;
+ border-radius: 8px;
+ border: none;
+ font-size: 14px;
+ font-weight: 500;
cursor: pointer;
- border-radius: 4px;
-}
-
-.table-container {
- background: white;
- box-shadow: 0 1px 3px rgba(0,0,0,0.1);
- border-radius: 4px;
+ transition: all 0.2s ease;
+ }
+
+ .btn-show {
+ background-color: #e0efff;
+ color: #1d4ed8;
+ }
+
+ .btn-error {
+ background-color: #fef2f2;
+ color: #dc2626;
+ }
+
+ .btn:hover {
+ transform: translateY(-2px);
+ box-shadow: 0 4px 12px rgba(0,0,0,0.1);
+ }
+
+ /* 通用容器居中统一样式 */
+ .table-container,
+ .error-mode-wrap {
+ background: #ffffff;
+ border-radius: 16px;
+ box-shadow: 0 8px 24px rgba(17, 24, 39, 0.06);
+ border: none;
overflow: hidden;
-}
-
-.progress-table {
+ width: 100%;
+ max-width: 700px;
+ margin: 0 auto;
+ }
+
+ /* 表格样式 */
+ table {
width: 100%;
border-collapse: collapse;
-}
-
-.progress-table th {
- background-color: #f1f3f5;
- color: #495057;
- padding: 15px;
- font-weight: bold;
- border-bottom: 1px solid #dee2e6;
-}
-
-.progress-table td {
- padding: 15px;
+ table-layout: fixed;
+ }
+
+ /* 表头 */
+ th {
+ background-color: #eff6ff;
+ padding: 16px 12px;
+ font-weight: 600;
+ color: #1e40af;
+ font-size: 14px;
+ text-align: center;
+ white-space: nowrap;
+ }
+
+ /* 表格内容 */
+ td {
+ padding: 16px 12px;
+ font-size: 15px;
+ color: #1f2937;
+ border-bottom: 1px solid #f1f5f9;
text-align: center;
- border-bottom: 1px solid #f8f9fa;
- height: 55px;
-}
-
-.group-name {
- font-weight: bold;
- color: #2c3e50;
-}
-
-/* 状态颜色映射 */
-.status-done { color: #28a745; }
-.status-start { color: #007bff; font-weight: bold; }
-.status-warning { color: #dc3545; font-weight: bold; }
-
-.footer {
+ }
+
+ /* 四列等宽均分 */
+ 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; }
+
+ /* 最后一行无边框 */
+ tr:last-child td {
+ border-bottom: none;
+ }
+
+ /* hover高亮 */
+ tr:hover {
+ background-color: #f8fafc;
+ transition: background-color 0.2s ease;
+ }
+
+ /* 消错模式整体居中对齐 */
+ .error-mode-wrap {
display: flex;
- justify-content: center;
- margin-top: 40px;
-}
-
-.error-correction-btn {
- background-color: #f8d7da;
- color: #721c24;
- border: none;
- padding: 15px 50px;
- font-size: 18px;
- font-weight: bold;
- border-radius: 4px;
- cursor: pointer;
-}
+ flex-direction: column;
+ align-items: center;
+ padding: 20px;
+ }
+
+ /* ====================== 响应式适配 ====================== */
+ @media (max-width: 768px) {
+ .table-container,
+ .error-mode-wrap {
+ max-width: 90%;
+ }
+ h1 {
+ font-size: 20px;
+ }
+ th, td {
+ padding: 14px 8px;
+ font-size: 14px;
+ }
+ .btn {
+ padding: 9px 20px;
+ }
+ }
+
+ @media (max-width: 640px) {
+ .table-container,
+ .error-mode-wrap {
+ max-width: 95%;
+ }
+ h1 {
+ font-size: 18px;
+ }
+ th {
+ padding: 12px 6px;
+ font-size: 12px;
+ }
+ td {
+ padding: 12px 6px;
+ font-size: 13px;
+ }
+ th:nth-child(4), td:nth-child(4) {
+ font-size: 11px;
+ }
+ .btn-group {
+ gap: 10px;
+ }
+ .btn {
+ padding: 8px 16px;
+ font-size: 13px;
+ }
+ }
\ No newline at end of file
+++ /dev/null
-import React, { useEffect, useState } from 'react';
-import axios from 'axios';
-import './Home.css'; // 我们稍后创建这个样式文件
-
-function Home() {
- const [progressData, setProgressData] = useState([]);
- const [showAll, setShowAll] = useState(false);
- const [stats, setStats] = useState({ pictures_count: 0, words_count: 0 });
-
- // 获取统计数据和进度数据
- const fetchData = async () => {
- try {
- const resStats = await axios.get('http://124.222.157.124:1060/api/home');
- setStats(resStats.data);
-
- const resProcess = await axios.get('http://124.222.157.124:1060/api/home/process');
- setProgressData(resProcess.data);
- } catch (error) {
- console.error("获取数据失败:", error);
- }
- };
-
- useEffect(() => {
- fetchData();
- const timer = setInterval(fetchData, 30000); // 30秒自动刷新
- return () => clearInterval(timer);
- }, []);
-
- // 过滤逻辑:模拟 Python 中的 toggle_show_all_groups
- const filteredData = progressData.filter(item => {
- if (showAll) return true;
- // 检查是否所有环节都已完成(包含 ✔️)
- const isDone = [item.study, item.test, item.review1, item.review2, item.review3]
- .every(val => val && val.includes('✔️'));
- return !isDone;
- });
-
- // 根据后端返回的文本内容决定样式类
- const getStatusClass = (text) => {
- if (!text) return '';
- if (text.includes('✔️')) return 'status-done';
- if (text.includes('✅')) return 'status-start';
- if (text.includes('⚠️')) return 'status-warning';
- return '';
- };
-
- return (
- <div className="main-app">
- <header className="header">
- <h1>单词学习进度记录</h1>
- <p className="stats-info">
- 图片总数: {stats.pictures_count} | 单词总数: {stats.words_count}
- </p>
- </header>
-
- <div className="action-bar">
- <button className="toggle-btn" onClick={() => setShowAll(!showAll)}>
- {showAll ? "➖ 隐藏已完成组" : "➕ 显示已完成的组"}
- </button>
- </div>
-
- <div className="table-container">
- <table className="progress-table">
- <thead>
- <tr>
- <th>组别</th>
- <th>基础学习</th>
- <th>单元测试</th>
- <th>复习 I</th>
- <th>复习 II</th>
- <th>复习 III</th>
- </tr>
- </thead>
- <tbody>
- {filteredData.map((item, index) => (
- <tr key={index}>
- <td className="group-name">{item.group}</td>
- <td className={getStatusClass(item.study)}>{item.study}</td>
- <td className={getStatusClass(item.test)}>{item.test}</td>
- <td className={getStatusClass(item.review1)}>{item.review1}</td>
- <td className={getStatusClass(item.review2)}>{item.review2}</td>
- <td className={getStatusClass(item.review3)}>{item.review3}</td>
- </tr>
- ))}
- </tbody>
- </table>
- </div>
-
- <footer className="footer">
- <button className="error-correction-btn">进入消错模式</button>
- </footer>
- </div>
- );
-}
-
-export default Home;