diff --git a/frontend-react/README.md b/frontend-react/README.md
new file mode 100644
index 0000000..35ed414
--- /dev/null
+++ b/frontend-react/README.md
@@ -0,0 +1,76 @@
+# React 前端重构说明
+
+## 📁 项目结构
+
+```
+frontend-react/
+├── src/
+│ ├── components/
+│ │ ├── Calendar.js # ⭐ 日历组件(核心)
+│ │ ├── DiaryDetail.js # 日记详情
+│ │ ├── ExperienceList.js # 经验总结列表
+│ │ └── StatsPanel.js # 统计面板
+│ ├── App.js # 主应用
+│ ├── index.js # 入口
+│ └── index.css # 样式
+├── package.json
+└── README.md
+```
+
+## 🎯 组件化优势
+
+### 之前的 HTML/JS 大杂烩
+- ❌ 所有逻辑混在一个文件
+- ❌ 难以维护
+- ❌ 看不清功能边界
+- ❌ 容易误删核心功能
+
+### 现在的 React 组件
+- ✅ 每个功能独立组件
+- ✅ 清晰的依赖关系
+- ✅ 易于测试和维护
+- ✅ 组件级别注释保护
+
+## 🔧 开发指南
+
+### 修改日历功能
+```bash
+# 只需修改这个文件
+src/components/Calendar.js
+
+# 阅读文档
+docs/CALENDAR.md
+
+# 测试
+npm start
+```
+
+### 添加新功能
+1. 在 `components/` 创建新组件
+2. 在 `App.js` 中引入
+3. 运行测试
+
+## 🚀 构建部署
+
+```bash
+# 安装依赖
+npm install
+
+# 开发模式
+npm start
+
+# 生产构建
+npm run build
+
+# 部署构建产物
+cp -r build/* ../frontend/
+```
+
+## ⚠️ 核心组件
+
+**不可删除的组件:**
+- `Calendar.js` - 日历组件 ⭐⭐⭐
+- `App.js` - 主应用 ⭐⭐
+- `StatsPanel.js` - 统计面板 ⭐
+
+修改前必须阅读 `docs/` 对应文档。
diff --git a/frontend-react/package.json b/frontend-react/package.json
new file mode 100644
index 0000000..bb7fb83
--- /dev/null
+++ b/frontend-react/package.json
@@ -0,0 +1,29 @@
+{
+ "name": "diary-system-react",
+ "version": "1.0.0",
+ "private": true,
+ "dependencies": {
+ "react": "^18.2.0",
+ "react-dom": "^18.2.0",
+ "react-scripts": "5.0.1",
+ "axios": "^1.6.0"
+ },
+ "scripts": {
+ "start": "react-scripts start",
+ "build": "react-scripts build",
+ "test": "react-scripts test",
+ "eject": "react-scripts eject"
+ },
+ "browserslist": {
+ "production": [
+ ">0.2%",
+ "not dead",
+ "not op_mini all"
+ ],
+ "development": [
+ "last 1 chrome version",
+ "last 1 firefox version",
+ "last 1 safari version"
+ ]
+ }
+}
diff --git a/frontend-react/src/App.js b/frontend-react/src/App.js
new file mode 100644
index 0000000..33915ea
--- /dev/null
+++ b/frontend-react/src/App.js
@@ -0,0 +1,100 @@
+import React, { useState, useEffect } from 'react';
+import axios from 'axios';
+import StatsPanel from './components/StatsPanel';
+import Calendar from './components/Calendar';
+import DiaryDetail from './components/DiaryDetail';
+import ExperienceList from './components/ExperienceList';
+
+const API_BASE = '/api';
+
+/**
+ * ⚠️ 主应用组件
+ *
+ * 核心功能:
+ * - 统计面板
+ * - 日历组件 ⭐
+ * - 日记详情
+ * - 经验总结
+ *
+ * ⚠️ 修改前阅读 docs/README.md
+ */
+function App() {
+ const [selectedDate, setSelectedDate] = useState(new Date().toISOString().split('T')[0]);
+ const [diaryDates, setDiaryDates] = useState([]);
+ const [entries, setEntries] = useState([]);
+ const [experiences, setExperiences] = useState([]);
+ const [stats, setStats] = useState({});
+ const [loading, setLoading] = useState(true);
+ const [error, setError] = useState(null);
+
+ useEffect(() => {
+ loadData();
+ }, []);
+
+ const loadData = async () => {
+ try {
+ const [statsRes, entriesRes, expRes] = await Promise.all([
+ axios.get(`${API_BASE}/entries/stats/`),
+ axios.get(`${API_BASE}/entries/`),
+ axios.get(`${API_BASE}/experiences/recent/`)
+ ]);
+
+ setStats(statsRes.data);
+ setEntries(entriesRes.data);
+ setExperiences(expRes.data);
+ setDiaryDates(entriesRes.data.map(e => e.date));
+ setLoading(false);
+ } catch (err) {
+ setError(`加载失败:${err.message}`);
+ setLoading(false);
+ }
+ };
+
+ const handleDateSelect = (date) => {
+ setSelectedDate(date);
+ };
+
+ const selectedEntry = entries.find(e => e.date === selectedDate);
+
+ if (loading) {
+ return
加载中...
;
+ }
+
+ if (error) {
+ return {error}
;
+ }
+
+ return (
+
+
+ ⚡ 码神的日记系统
+ 记录每天的进步与成长
+
+
+
+
+
+
+
📅 日历
+
+
+
+
+
📝 {selectedDate} 的日记
+
+
+
+
+
+
💡 经验总结
+
+
+
+ );
+}
+
+export default App;
diff --git a/frontend-react/src/components/Calendar.js b/frontend-react/src/components/Calendar.js
new file mode 100644
index 0000000..cca2074
--- /dev/null
+++ b/frontend-react/src/components/Calendar.js
@@ -0,0 +1,84 @@
+import React, { useState } from 'react';
+
+/**
+ * ⚠️ 核心组件:日历组件
+ *
+ * 功能:
+ * - 显示月历视图
+ * - 标记有日记的日期
+ * - 点击日期选择
+ * - 上月/下月切换
+ *
+ * ⚠️ 不可删除此组件,修改前阅读 docs/CALENDAR.md
+ */
+function Calendar({ selectedDate, onDateSelect, diaryDates }) {
+ const [currentDate, setCurrentDate] = useState(new Date());
+
+ const year = currentDate.getFullYear();
+ const month = currentDate.getMonth();
+
+ const firstDay = new Date(year, month, 1);
+ const lastDay = new Date(year, month + 1, 0);
+ const startDay = firstDay.getDay();
+ const totalDays = lastDay.getDate();
+
+ const today = new Date().toISOString().split('T')[0];
+ const monthNames = ['一月', '二月', '三月', '四月', '五月', '六月', '七月', '八月', '九月', '十月', '十一月', '十二月'];
+ const dayNames = ['日', '一', '二', '三', '四', '五', '六'];
+
+ const prevMonth = () => {
+ setCurrentDate(new Date(year, month - 1, 1));
+ };
+
+ const nextMonth = () => {
+ setCurrentDate(new Date(year, month + 1, 1));
+ };
+
+ const renderDays = () => {
+ let days = [];
+
+ // 上个月的日期
+ for (let i = 0; i < startDay; i++) {
+ days.push();
+ }
+
+ // 当月日期
+ for (let day = 1; day <= totalDays; day++) {
+ const dateStr = `${year}-${String(month + 1).padStart(2, '0')}-${String(day).padStart(2, '0')}`;
+ const isToday = dateStr === today;
+ const isSelected = dateStr === selectedDate;
+ const hasDiary = diaryDates.includes(dateStr);
+
+ days.push(
+ onDateSelect(dateStr)}
+ >
+ {day}
+ {hasDiary && 📝}
+
+ );
+ }
+
+ return days;
+ };
+
+ return (
+
+
+
+ {year}年 {monthNames[month]}
+
+
+
+ {dayNames.map(day => (
+
{day}
+ ))}
+ {renderDays()}
+
+
+ );
+}
+
+export default Calendar;
diff --git a/frontend-react/src/components/DiaryDetail.js b/frontend-react/src/components/DiaryDetail.js
new file mode 100644
index 0000000..a4c766b
--- /dev/null
+++ b/frontend-react/src/components/DiaryDetail.js
@@ -0,0 +1,65 @@
+import React from 'react';
+
+/**
+ * 日记详情组件
+ * 显示选中日期的日记内容
+ */
+function DiaryDetail({ entry }) {
+ if (!entry) {
+ return (
+
+ );
+ }
+
+ return (
+
+
{entry.title || '每日日记'}
+
+ {entry.completed_tasks && (
+
+
✅ 完成的任务
+
{entry.completed_tasks}
+
+ )}
+
+ {entry.learned && (
+
+
📚 学到的东西
+
{entry.learned}
+
+ )}
+
+ {entry.problems && (
+
+
🐛 遇到的问题
+
{entry.problems}
+
+ )}
+
+ {entry.reflections && (
+
+
💡 想法和反思
+
{entry.reflections}
+
+ )}
+
+ {entry.improvements && (
+
+
📈 进步点
+
{entry.improvements}
+
+ )}
+
+ {entry.plans && (
+
+
🎯 明日计划
+
{entry.plans}
+
+ )}
+
+ );
+}
+
+export default DiaryDetail;
diff --git a/frontend-react/src/components/ExperienceList.js b/frontend-react/src/components/ExperienceList.js
new file mode 100644
index 0000000..69a0a27
--- /dev/null
+++ b/frontend-react/src/components/ExperienceList.js
@@ -0,0 +1,39 @@
+import React from 'react';
+
+/**
+ * 经验总结列表组件
+ */
+function ExperienceList({ experiences }) {
+ if (!experiences || experiences.length === 0) {
+ return 暂无经验总结
;
+ }
+
+ return (
+
+ {experiences.map(exp => (
+
+
+ {exp.title}
+ {exp.category}
+
+
+
+
✅ 解决方案
+
{exp.solution}
+
+ {exp.lesson_learned && (
+
+
📌 经验教训
+
{exp.lesson_learned}
+
+ )}
+
+ ))}
+
+ );
+}
+
+export default ExperienceList;
diff --git a/frontend-react/src/components/StatsPanel.js b/frontend-react/src/components/StatsPanel.js
new file mode 100644
index 0000000..54dba61
--- /dev/null
+++ b/frontend-react/src/components/StatsPanel.js
@@ -0,0 +1,38 @@
+import React from 'react';
+
+/**
+ * 统计面板组件
+ * 显示系统统计数据
+ */
+function StatsPanel({ stats }) {
+ return (
+
+
+
{stats.total_entries || 0}
+
总日记
+
+
+
{stats.total_tasks || 0}
+
总任务
+
+
+
{stats.progressing || 0}
+
进行中
+
+
+
{stats.completed || 0}
+
已完成
+
+
+
{stats.completion_rate || 0}%
+
完成率
+
+
+
{stats.total_experiences || 0}
+
经验
+
+
+ );
+}
+
+export default StatsPanel;
diff --git a/frontend-react/src/index.css b/frontend-react/src/index.css
new file mode 100644
index 0000000..7a015f6
--- /dev/null
+++ b/frontend-react/src/index.css
@@ -0,0 +1,270 @@
+* {
+ margin: 0;
+ padding: 0;
+ box-sizing: border-box;
+}
+
+body {
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+ min-height: 100vh;
+ padding: 20px;
+}
+
+.container {
+ max-width: 1400px;
+ margin: 0 auto;
+}
+
+header {
+ text-align: center;
+ color: white;
+ margin-bottom: 30px;
+}
+
+header h1 {
+ font-size: 2.5em;
+ margin-bottom: 10px;
+}
+
+header p {
+ opacity: 0.9;
+}
+
+/* 统计面板 */
+.stats {
+ display: grid;
+ grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
+ gap: 20px;
+ margin-bottom: 30px;
+}
+
+.stat-card {
+ background: white;
+ padding: 15px;
+ border-radius: 10px;
+ text-align: center;
+ box-shadow: 0 4px 6px rgba(0,0,0,0.1);
+}
+
+.stat-card h3 {
+ color: #667eea;
+ font-size: 1.8em;
+ margin-bottom: 5px;
+}
+
+.stat-card p {
+ color: #666;
+ font-size: 0.9em;
+}
+
+/* 内容区域 */
+.grid-2 {
+ display: grid;
+ grid-template-columns: repeat(auto-fit, minmax(500px, 1fr));
+ gap: 30px;
+ margin-bottom: 30px;
+}
+
+.section-box {
+ background: white;
+ border-radius: 10px;
+ padding: 20px;
+ box-shadow: 0 4px 6px rgba(0,0,0,0.1);
+}
+
+.section-box h2 {
+ color: #333;
+ margin-bottom: 20px;
+ padding-bottom: 10px;
+ border-bottom: 2px solid #667eea;
+}
+
+/* 日历组件 */
+.calendar {
+ background: #f8f9fa;
+ padding: 15px;
+ border-radius: 8px;
+}
+
+.calendar-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin-bottom: 15px;
+}
+
+.calendar-header button {
+ background: #667eea;
+ color: white;
+ border: none;
+ padding: 8px 12px;
+ border-radius: 5px;
+ cursor: pointer;
+}
+
+.calendar-header button:hover {
+ background: #5568d3;
+}
+
+.calendar-grid {
+ display: grid;
+ grid-template-columns: repeat(7, 1fr);
+ gap: 2px;
+}
+
+.calendar-day-header {
+ text-align: center;
+ font-weight: bold;
+ color: #666;
+ padding: 5px;
+ font-size: 0.9em;
+}
+
+.calendar-day {
+ aspect-ratio: 1;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ background: white;
+ border-radius: 5px;
+ cursor: pointer;
+ position: relative;
+ transition: all 0.2s;
+}
+
+.calendar-day:hover {
+ background: #eef1f5;
+ transform: scale(1.05);
+}
+
+.calendar-day.today {
+ background: #667eea;
+ color: white;
+ font-weight: bold;
+}
+
+.calendar-day.selected {
+ border: 2px solid #667eea;
+}
+
+.calendar-day.has-diary::after {
+ content: '📝';
+ font-size: 0.7em;
+ position: absolute;
+ bottom: 2px;
+}
+
+.calendar-day.empty {
+ cursor: default;
+ background: transparent;
+}
+
+/* 日记详情 */
+.diary-detail {
+ padding: 15px;
+}
+
+.diary-section {
+ margin: 15px 0;
+}
+
+.section-title {
+ font-weight: bold;
+ color: #555;
+ margin-bottom: 8px;
+}
+
+.section-content {
+ color: #666;
+ line-height: 1.6;
+ white-space: pre-wrap;
+ margin-left: 20px;
+}
+
+/* 经验总结 */
+.experience-item {
+ padding: 15px;
+ border-left: 4px solid #f59e0b;
+ background: #fffbeb;
+ margin-bottom: 15px;
+ border-radius: 5px;
+}
+
+.experience-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin-bottom: 10px;
+}
+
+.experience-title {
+ font-weight: bold;
+ color: #333;
+}
+
+.experience-category {
+ background: #f59e0b;
+ color: white;
+ padding: 4px 12px;
+ border-radius: 20px;
+ font-size: 0.85em;
+}
+
+.experience-problem, .experience-solution {
+ margin: 10px 0;
+}
+
+.experience-problem-title {
+ font-weight: bold;
+ color: #dc2626;
+ margin-bottom: 5px;
+}
+
+.experience-solution-title {
+ font-weight: bold;
+ color: #059669;
+ margin-bottom: 5px;
+}
+
+.experience-lesson {
+ margin: 10px 0;
+ padding: 10px;
+ background: #fef3c7;
+ border-radius: 5px;
+}
+
+.experience-lesson-title {
+ font-weight: bold;
+ color: #92400e;
+ margin-bottom: 5px;
+}
+
+/* 状态 */
+.loading {
+ text-align: center;
+ padding: 40px;
+ color: white;
+ font-size: 1.2em;
+}
+
+.error {
+ background: #fee;
+ color: #c00;
+ padding: 20px;
+ border-radius: 10px;
+ margin-bottom: 20px;
+}
+
+.empty-state {
+ text-align: center;
+ padding: 40px;
+ color: #999;
+}
+
+/* 移动端 */
+@media (max-width: 768px) {
+ .grid-2 {
+ grid-template-columns: 1fr;
+ }
+}
diff --git a/frontend-react/src/index.js b/frontend-react/src/index.js
new file mode 100644
index 0000000..2cb1087
--- /dev/null
+++ b/frontend-react/src/index.js
@@ -0,0 +1,11 @@
+import React from 'react';
+import ReactDOM from 'react-dom/client';
+import './index.css';
+import App from './App';
+
+const root = ReactDOM.createRoot(document.getElementById('root'));
+root.render(
+
+
+
+);