@jet-w/astro-blog 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (140) hide show
  1. package/dist/chunk-FXPGR372.js +0 -0
  2. package/dist/chunk-GYLSY3OJ.js +173 -0
  3. package/dist/config/index.d.ts +166 -0
  4. package/dist/config/index.js +38 -0
  5. package/dist/index.d.ts +34 -0
  6. package/dist/index.js +59 -0
  7. package/dist/types/index.d.ts +75 -0
  8. package/dist/types/index.js +1 -0
  9. package/package.json +84 -0
  10. package/src/components/EChartsCard.vue +118 -0
  11. package/src/components/Mermaid.vue +73 -0
  12. package/src/components/about/ContentCard.astro +27 -0
  13. package/src/components/about/IconCard.astro +77 -0
  14. package/src/components/about/SocialLinks.astro +54 -0
  15. package/src/components/about/TagCard.astro +65 -0
  16. package/src/components/about/TagGroup.astro +33 -0
  17. package/src/components/about/TimelineCard.astro +52 -0
  18. package/src/components/blog/FloatingToc.vue +198 -0
  19. package/src/components/blog/Hero.astro +147 -0
  20. package/src/components/blog/NavigationTabs.vue +245 -0
  21. package/src/components/blog/PostCard.astro +161 -0
  22. package/src/components/blog/PostNavigation.astro +106 -0
  23. package/src/components/blog/RelatedPosts.astro +175 -0
  24. package/src/components/blog/TableOfContents.astro +153 -0
  25. package/src/components/blog/TagCloud.astro +91 -0
  26. package/src/components/home/FeaturedPostsSection.astro +54 -0
  27. package/src/components/home/QuickNavSection.astro +81 -0
  28. package/src/components/home/RecentPostsSection.astro +52 -0
  29. package/src/components/home/StatsSection.astro +44 -0
  30. package/src/components/layout/Footer.astro +103 -0
  31. package/src/components/layout/Header.astro +68 -0
  32. package/src/components/layout/Sidebar.astro +594 -0
  33. package/src/components/media/Bilibili.astro +114 -0
  34. package/src/components/media/Slides.astro +313 -0
  35. package/src/components/media/Video.astro +111 -0
  36. package/src/components/media/VideoPlayer.astro +89 -0
  37. package/src/components/media/YouTube.astro +92 -0
  38. package/src/components/pte/StudyCalendar.vue +1348 -0
  39. package/src/components/ui/Icon.astro +187 -0
  40. package/src/components/ui/MobileMenu.vue +201 -0
  41. package/src/components/ui/Pagination.astro +143 -0
  42. package/src/components/ui/SearchBox.vue +179 -0
  43. package/src/components/ui/SearchInterface.vue +409 -0
  44. package/src/components/ui/SidebarToggle.vue +57 -0
  45. package/src/components/ui/ThemeToggle.vue +90 -0
  46. package/src/layouts/AboutLayout.astro +18 -0
  47. package/src/layouts/BaseLayout.astro +362 -0
  48. package/src/layouts/PageLayout.astro +217 -0
  49. package/src/layouts/SlidesLayout.astro +320 -0
  50. package/src/plugins/rehype-clean-containers.mjs +24 -0
  51. package/src/plugins/rehype-relative-links.mjs +43 -0
  52. package/src/plugins/rehype-tabs.mjs +116 -0
  53. package/src/plugins/remark-containers.mjs +407 -0
  54. package/src/plugins/remark-mermaid.mjs +46 -0
  55. package/src/styles/global.css +870 -0
  56. package/src/styles/slides.css +220 -0
  57. package/src/utils/sidebar.ts +492 -0
  58. package/templates/default/astro.config.mjs +51 -0
  59. package/templates/default/content/pages/about.mdx +93 -0
  60. package/templates/default/content/pages/index.mdx +20 -0
  61. package/templates/default/content/posts/blog_docs/01-quick-start.md +162 -0
  62. package/templates/default/content/posts/blog_docs/02-frontmatter.md +277 -0
  63. package/templates/default/content/posts/blog_docs/03-markdown-basic.md +350 -0
  64. package/templates/default/content/posts/blog_docs/04-containers.md +331 -0
  65. package/templates/default/content/posts/blog_docs/05-code-blocks.md +388 -0
  66. package/templates/default/content/posts/blog_docs/06-mermaid.md +431 -0
  67. package/templates/default/content/posts/blog_docs/07-video.md +243 -0
  68. package/templates/default/content/posts/blog_docs/08-latex.md +382 -0
  69. package/templates/default/content/posts/blog_docs/09-icons.md +326 -0
  70. package/templates/default/content/posts/blog_docs/10-sidebar.md +445 -0
  71. package/templates/default/content/posts/blog_docs/11-config.md +334 -0
  72. package/templates/default/content/posts/blog_docs/12-slides.mdx +552 -0
  73. package/templates/default/content/posts/blog_docs/README.md +151 -0
  74. package/templates/default/content/slides/demo.md +146 -0
  75. package/templates/default/content/slides/docs/basic-demo.md +35 -0
  76. package/templates/default/content/slides/docs/code-demo.md +62 -0
  77. package/templates/default/content/slides/docs/echarts-demo.md +139 -0
  78. package/templates/default/content/slides/docs/fragment-demo.md +35 -0
  79. package/templates/default/content/slides/docs/math-demo.md +48 -0
  80. package/templates/default/content/slides/docs/mermaid-demo.md +105 -0
  81. package/templates/default/content/slides/docs/theme-demo.md +38 -0
  82. package/templates/default/content/slides/docs/vertical-demo.md +50 -0
  83. package/templates/default/package.json +31 -0
  84. package/templates/default/public/favicon-bak.svg +4 -0
  85. package/templates/default/public/images/avatar.jpg +0 -0
  86. package/templates/default/public/images/avatar.svg +142 -0
  87. package/templates/default/public/js/mermaid-container.js +402 -0
  88. package/templates/default/public/js/mermaid-init.js +131 -0
  89. package/templates/default/public/js/mermaid-render.js +98 -0
  90. package/templates/default/public/js/mermaid-simple.js +95 -0
  91. package/templates/default/public/js/tabs-init.js +86 -0
  92. package/templates/default/public/media/individual_portfolio/INDIVIDUAL PORTFOLIO.png +0 -0
  93. package/templates/default/public/slides/plugin/highlight/highlight.js +5 -0
  94. package/templates/default/public/slides/plugin/highlight/monokai.css +71 -0
  95. package/templates/default/public/slides/plugin/markdown/markdown.js +7 -0
  96. package/templates/default/public/slides/plugin/math/math.js +1 -0
  97. package/templates/default/public/slides/plugin/notes/notes.js +1 -0
  98. package/templates/default/public/slides/reveal.css +9 -0
  99. package/templates/default/public/slides/reveal.js +9 -0
  100. package/templates/default/public/slides/theme/beige.css +366 -0
  101. package/templates/default/public/slides/theme/black-contrast.css +362 -0
  102. package/templates/default/public/slides/theme/black.css +359 -0
  103. package/templates/default/public/slides/theme/blood.css +392 -0
  104. package/templates/default/public/slides/theme/dracula.css +385 -0
  105. package/templates/default/public/slides/theme/league.css +368 -0
  106. package/templates/default/public/slides/theme/moon.css +362 -0
  107. package/templates/default/public/slides/theme/night.css +360 -0
  108. package/templates/default/public/slides/theme/serif.css +363 -0
  109. package/templates/default/public/slides/theme/simple.css +362 -0
  110. package/templates/default/public/slides/theme/sky.css +370 -0
  111. package/templates/default/public/slides/theme/solarized.css +363 -0
  112. package/templates/default/public/slides/theme/white-contrast.css +362 -0
  113. package/templates/default/public/slides/theme/white.css +359 -0
  114. package/templates/default/public/slides/theme/white_contrast_compact_verbatim_headers.css +360 -0
  115. package/templates/default/public/test-complete.html +43 -0
  116. package/templates/default/public/test-mermaid.html +124 -0
  117. package/templates/default/src/config/index.ts +114 -0
  118. package/templates/default/src/content.config.ts +96 -0
  119. package/templates/default/src/pages/[...slug].astro +27 -0
  120. package/templates/default/src/pages/archives/[year]/[month]/page/[page].astro +176 -0
  121. package/templates/default/src/pages/archives/[year]/[month].astro +158 -0
  122. package/templates/default/src/pages/archives/index.astro +210 -0
  123. package/templates/default/src/pages/categories/[category]/page/[page].astro +218 -0
  124. package/templates/default/src/pages/categories/[category].astro +198 -0
  125. package/templates/default/src/pages/categories/index.astro +190 -0
  126. package/templates/default/src/pages/container-test.astro +79 -0
  127. package/templates/default/src/pages/mermaid-direct.html +78 -0
  128. package/templates/default/src/pages/posts/[...slug].astro +335 -0
  129. package/templates/default/src/pages/posts/index.astro +541 -0
  130. package/templates/default/src/pages/posts/page/[page].astro +146 -0
  131. package/templates/default/src/pages/rss.xml.ts +28 -0
  132. package/templates/default/src/pages/search-index.json.ts +21 -0
  133. package/templates/default/src/pages/search.astro +50 -0
  134. package/templates/default/src/pages/slides/[...slug].astro +54 -0
  135. package/templates/default/src/pages/slides/index.astro +135 -0
  136. package/templates/default/src/pages/tags/[tag]/page/[page].astro +211 -0
  137. package/templates/default/src/pages/tags/[tag].astro +191 -0
  138. package/templates/default/src/pages/tags/index.astro +167 -0
  139. package/templates/default/tailwind.config.mjs +78 -0
  140. package/templates/default/tsconfig.json +9 -0
@@ -0,0 +1,1348 @@
1
+ <template>
2
+ <div class="study-calendar">
3
+ <!-- 头部导航 -->
4
+ <div class="calendar-header">
5
+ <button @click="prevMonth" class="nav-btn">&lt;</button>
6
+ <h2 class="month-title">{{ currentYear }} 年 {{ currentMonthName }}</h2>
7
+ <button @click="nextMonth" class="nav-btn">&gt;</button>
8
+ </div>
9
+
10
+ <!-- 阶段标签 -->
11
+ <div class="phase-tags">
12
+ <span
13
+ v-for="phase in parsedPhases"
14
+ :key="phase.id"
15
+ class="phase-tag"
16
+ :style="{ backgroundColor: phase.color }"
17
+ >
18
+ {{ phase.name }} ({{ phase.weeks }}周)
19
+ </span>
20
+ </div>
21
+
22
+ <!-- 星期头 -->
23
+ <div class="weekdays">
24
+ <div v-for="day in weekdays" :key="day" class="weekday">{{ day }}</div>
25
+ </div>
26
+
27
+ <!-- 日历网格 -->
28
+ <div class="calendar-grid">
29
+ <div
30
+ v-for="(day, index) in calendarDays"
31
+ :key="index"
32
+ class="calendar-day"
33
+ :class="{
34
+ 'other-month': !day.currentMonth,
35
+ 'today': day.isToday,
36
+ 'has-task': day.tasks.length > 0,
37
+ 'past-day': isPastDay(day) && day.tasks.length > 0
38
+ }"
39
+ :style="day.tasks.length ? { borderLeft: `4px solid ${day.tasks[0].color}` } : {}"
40
+ @click="selectDay(day)"
41
+ >
42
+ <span class="day-number">{{ day.date }}</span>
43
+ <div class="day-tasks">
44
+ <div
45
+ v-for="task in day.tasks.slice(0, 2)"
46
+ :key="task.id"
47
+ class="task-item"
48
+ :style="{ backgroundColor: task.color + '20', color: task.color }"
49
+ >
50
+ {{ task.title }}
51
+ </div>
52
+ <div v-if="day.tasks.length > 2" class="more-tasks">
53
+ +{{ day.tasks.length - 2 }} 更多
54
+ </div>
55
+ </div>
56
+ </div>
57
+ </div>
58
+
59
+ <!-- 弹窗遮罩 -->
60
+ <div v-if="showModal" class="modal-overlay" @click="closeModal">
61
+ <div class="modal-content" @click.stop>
62
+ <button class="modal-close" @click="closeModal">&times;</button>
63
+
64
+ <div class="modal-header">
65
+ <h3>{{ selectedDay?.fullDate }}</h3>
66
+ <span
67
+ v-if="selectedDay && getDayStatus(selectedDay)"
68
+ class="day-status"
69
+ :class="getDayStatus(selectedDay)"
70
+ >
71
+ {{ getDayStatus(selectedDay) === 'completed' ? '已完成' : getDayStatus(selectedDay) === 'today' ? '今日任务' : '待完成' }}
72
+ </span>
73
+ <span
74
+ v-if="selectedDay && selectedDay.tasks.length > 0 && selectedDay.tasks[0].isCustomDate"
75
+ class="custom-date-badge"
76
+ >
77
+ 📌 自定义
78
+ </span>
79
+ </div>
80
+
81
+ <div v-if="!selectedDay || selectedDay.tasks.length === 0" class="no-tasks">
82
+ <div class="no-tasks-icon">📅</div>
83
+ <p>暂无学习任务</p>
84
+ <p class="no-tasks-hint">这是休息日,好好放松一下吧!</p>
85
+ </div>
86
+
87
+ <div v-else class="task-list">
88
+ <div
89
+ v-for="(task, taskIndex) in selectedDay.tasks"
90
+ :key="task.id"
91
+ class="task-detail"
92
+ :style="{ borderLeft: `4px solid ${task.color}` }"
93
+ >
94
+ <div class="task-header">
95
+ <span class="task-phase" :style="{ color: task.color }">{{ task.phase }}</span>
96
+ <span class="task-duration">⏱️ {{ task.duration }}</span>
97
+ </div>
98
+ <div class="task-title">{{ task.title }}</div>
99
+ <div class="task-desc">{{ task.description }}</div>
100
+ <div v-if="task.materials" class="task-materials">
101
+ <span class="materials-icon">📚</span>
102
+ <span>{{ task.materials }}</span>
103
+ </div>
104
+
105
+ <!-- 练习详情列表 -->
106
+ <div v-if="task.exercises && task.exercises.length > 0" class="exercises-section">
107
+ <div class="exercises-header">📝 今日练习</div>
108
+ <div class="exercises-list">
109
+ <div
110
+ v-for="(exercise, exIndex) in task.exercises"
111
+ :key="exIndex"
112
+ class="exercise-item"
113
+ :class="{ completed: isExerciseCompleted(selectedDay, taskIndex, exIndex) }"
114
+ >
115
+ <span class="exercise-status">
116
+ {{ isExerciseCompleted(selectedDay, taskIndex, exIndex) ? '✅' : '⬜' }}
117
+ </span>
118
+ <span class="exercise-type" :style="{ backgroundColor: task.color + '20', color: task.color }">
119
+ {{ exercise.type }}
120
+ </span>
121
+ <span class="exercise-name">{{ exercise.name }}</span>
122
+ <span class="exercise-quantity">
123
+ <span class="quantity-done">{{ getExerciseDone(selectedDay, taskIndex, exIndex) }}</span>
124
+ <span class="quantity-sep">/</span>
125
+ <span class="quantity-total">{{ exercise.quantity }} {{ exercise.unit }}</span>
126
+ </span>
127
+ </div>
128
+ </div>
129
+ <!-- 总体完成进度 -->
130
+ <div class="task-progress-bar">
131
+ <div class="progress-label">
132
+ 完成进度: {{ getTaskCompletionPercent(selectedDay, taskIndex) }}%
133
+ </div>
134
+ <div class="progress-track">
135
+ <div
136
+ class="progress-fill"
137
+ :style="{
138
+ width: getTaskCompletionPercent(selectedDay, taskIndex) + '%',
139
+ backgroundColor: task.color
140
+ }"
141
+ ></div>
142
+ </div>
143
+ </div>
144
+ </div>
145
+
146
+ <!-- 整体完成状态(无练习详情时显示) -->
147
+ <div v-else class="task-completion-status">
148
+ <span v-if="isTaskCompleted(selectedDay, taskIndex)" class="status-completed">
149
+ ✅ 已完成
150
+ </span>
151
+ <span v-else class="status-pending">
152
+ ⬜ 待完成
153
+ </span>
154
+ </div>
155
+ </div>
156
+ </div>
157
+ </div>
158
+ </div>
159
+
160
+ <!-- 学习统计 -->
161
+ <div class="study-stats">
162
+ <div class="stat-item">
163
+ <span class="stat-value">{{ totalDays }}</span>
164
+ <span class="stat-label">总天数</span>
165
+ </div>
166
+ <div class="stat-item">
167
+ <span class="stat-value">{{ studyDaysCount }}</span>
168
+ <span class="stat-label">学习日</span>
169
+ </div>
170
+ <div class="stat-item">
171
+ <span class="stat-value">{{ restDaysCount }}</span>
172
+ <span class="stat-label">休息日</span>
173
+ </div>
174
+ <div class="stat-item">
175
+ <span class="stat-value">{{ progressPercent }}%</span>
176
+ <span class="stat-label">进度</span>
177
+ </div>
178
+ </div>
179
+ </div>
180
+ </template>
181
+
182
+ <script setup>
183
+ import { ref, computed, onMounted } from 'vue';
184
+
185
+ // Props 定义
186
+ const props = defineProps({
187
+ // 学习阶段配置
188
+ phases: {
189
+ type: Array,
190
+ default: () => [
191
+ { id: 1, name: '基础阶段', color: '#10b981', weeks: 4 },
192
+ { id: 2, name: '强化阶段', color: '#3b82f6', weeks: 4 },
193
+ { id: 3, name: '冲刺阶段', color: '#f59e0b', weeks: 4 },
194
+ ]
195
+ },
196
+ // 学习计划配置
197
+ studyPlan: {
198
+ type: Object,
199
+ default: () => ({
200
+ // 阶段1的任务
201
+ phase1: {
202
+ weekday: [
203
+ { title: '任务1', duration: '1小时', description: '描述1', materials: '资料1' },
204
+ ],
205
+ weekend: [
206
+ { title: '周末任务', duration: '2小时', description: '周末描述', materials: '周末资料' },
207
+ ]
208
+ },
209
+ phase2: {
210
+ weekday: [
211
+ { title: '任务2', duration: '1.5小时', description: '描述2', materials: '资料2' },
212
+ ],
213
+ weekend: [
214
+ { title: '周末任务', duration: '3小时', description: '周末描述', materials: '周末资料' },
215
+ ]
216
+ },
217
+ phase3: {
218
+ weekday: [
219
+ { title: '任务3', duration: '2小时', description: '描述3', materials: '资料3' },
220
+ ],
221
+ weekend: [
222
+ { title: '周末任务', duration: '3小时', description: '周末描述', materials: '周末资料' },
223
+ ]
224
+ }
225
+ })
226
+ },
227
+ // 开始日期(默认今天)
228
+ startDate: {
229
+ type: String,
230
+ default: ''
231
+ },
232
+ // 按日期精确配置(优先级高于周配置)
233
+ // 格式: { 'YYYY-MM-DD': { title, duration, description, materials, exercises } }
234
+ dateConfig: {
235
+ type: Object,
236
+ default: () => ({})
237
+ },
238
+ // 标题
239
+ title: {
240
+ type: String,
241
+ default: '学习日历'
242
+ }
243
+ });
244
+
245
+ // 解析阶段配置
246
+ const parsedPhases = computed(() => {
247
+ return props.phases.map((phase, index) => ({
248
+ ...phase,
249
+ id: phase.id || index + 1
250
+ }));
251
+ });
252
+
253
+ // 计算总天数
254
+ const totalDays = computed(() => {
255
+ return parsedPhases.value.reduce((sum, phase) => sum + (phase.weeks || 0) * 7, 0);
256
+ });
257
+
258
+ // 星期
259
+ const weekdays = ['日', '一', '二', '三', '四', '五', '六'];
260
+
261
+ // 当前日期
262
+ const today = new Date();
263
+ const currentMonth = ref(today.getMonth());
264
+ const currentYear = ref(today.getFullYear());
265
+ const selectedDay = ref(null);
266
+ const showModal = ref(false);
267
+
268
+ // 检查任务是否已完成(用于没有exercises的任务)
269
+ function isTaskCompleted(day, taskIndex) {
270
+ if (!day) return false;
271
+ const task = day.tasks[taskIndex];
272
+ if (!task) return false;
273
+
274
+ // 如果有 exercises,检查是否全部完成
275
+ if (task.exercises && task.exercises.length > 0) {
276
+ return task.exercises.every((ex, exIndex) => {
277
+ const done = getExerciseDone(day, taskIndex, exIndex);
278
+ return done >= ex.quantity;
279
+ });
280
+ }
281
+
282
+ // 没有 exercises,检查任务的 completed 字段
283
+ return task.completed === true;
284
+ }
285
+
286
+ // 检查单个练习是否已完成
287
+ function isExerciseCompleted(day, taskIndex, exIndex) {
288
+ if (!day) return false;
289
+ const task = day.tasks[taskIndex];
290
+ if (!task || !task.exercises || !task.exercises[exIndex]) return false;
291
+
292
+ const done = getExerciseDone(day, taskIndex, exIndex);
293
+ return done >= task.exercises[exIndex].quantity;
294
+ }
295
+
296
+ // 获取练习完成数量(从 exercises 的 completed 字段读取)
297
+ function getExerciseDone(day, taskIndex, exIndex) {
298
+ if (!day) return 0;
299
+ const task = day.tasks[taskIndex];
300
+ if (!task || !task.exercises || !task.exercises[exIndex]) return 0;
301
+
302
+ const exercise = task.exercises[exIndex];
303
+ return exercise.completed || 0;
304
+ }
305
+
306
+ // 计算任务完成百分比
307
+ function getTaskCompletionPercent(day, taskIndex) {
308
+ if (!day) return 0;
309
+ const task = day.tasks[taskIndex];
310
+ if (!task || !task.exercises || task.exercises.length === 0) return 0;
311
+
312
+ let totalQuantity = 0;
313
+ let totalDone = 0;
314
+
315
+ task.exercises.forEach((exercise, exIndex) => {
316
+ totalQuantity += exercise.quantity;
317
+ totalDone += getExerciseDone(day, taskIndex, exIndex);
318
+ });
319
+
320
+ if (totalQuantity === 0) return 0;
321
+ return Math.round((totalDone / totalQuantity) * 100);
322
+ }
323
+
324
+ // 检查是否是过去的日期
325
+ function isPastDay(day) {
326
+ if (!day || !day.dateObj) return false;
327
+ const dayDate = new Date(day.dateObj);
328
+ const todayDate = new Date();
329
+ todayDate.setHours(0, 0, 0, 0);
330
+ dayDate.setHours(0, 0, 0, 0);
331
+ return dayDate < todayDate;
332
+ }
333
+
334
+ // 获取日期状态
335
+ function getDayStatus(day) {
336
+ if (!day || day.tasks.length === 0) return null;
337
+
338
+ const dayDate = new Date(day.dateObj);
339
+ const todayDate = new Date();
340
+ todayDate.setHours(0, 0, 0, 0);
341
+ dayDate.setHours(0, 0, 0, 0);
342
+
343
+ // 检查是否所有任务都完成
344
+ let allCompleted = true;
345
+ day.tasks.forEach((task, taskIndex) => {
346
+ if (task.exercises && task.exercises.length > 0) {
347
+ // 有练习详情,检查每个练习
348
+ if (getTaskCompletionPercent(day, taskIndex) < 100) {
349
+ allCompleted = false;
350
+ }
351
+ } else {
352
+ // 无练习详情,检查任务整体
353
+ if (!isTaskCompleted(day, taskIndex)) {
354
+ allCompleted = false;
355
+ }
356
+ }
357
+ });
358
+
359
+ if (allCompleted) return 'completed';
360
+ if (dayDate.getTime() === todayDate.getTime()) return 'today';
361
+ if (dayDate < todayDate) return 'pending';
362
+ return 'future';
363
+ }
364
+
365
+ // 打开弹窗
366
+ function openModal(day) {
367
+ selectedDay.value = day;
368
+ showModal.value = true;
369
+ document.body.style.overflow = 'hidden';
370
+ }
371
+
372
+ // 关闭弹窗
373
+ function closeModal() {
374
+ showModal.value = false;
375
+ document.body.style.overflow = '';
376
+ }
377
+
378
+ // 备考开始日期
379
+ const planStartDate = computed(() => {
380
+ if (props.startDate) {
381
+ const date = new Date(props.startDate);
382
+ date.setHours(0, 0, 0, 0);
383
+ return date;
384
+ }
385
+ const date = new Date(today);
386
+ date.setHours(0, 0, 0, 0);
387
+ return date;
388
+ });
389
+
390
+ // 月份名称
391
+ const monthNames = ['一月', '二月', '三月', '四月', '五月', '六月',
392
+ '七月', '八月', '九月', '十月', '十一月', '十二月'];
393
+
394
+ const currentMonthName = computed(() => monthNames[currentMonth.value]);
395
+
396
+ // 获取阶段信息
397
+ function getPhaseInfo(weekNumber) {
398
+ let accumulatedWeeks = 0;
399
+ for (let i = 0; i < parsedPhases.value.length; i++) {
400
+ const phase = parsedPhases.value[i];
401
+ accumulatedWeeks += phase.weeks || 0;
402
+ if (weekNumber <= accumulatedWeeks) {
403
+ return {
404
+ phase: phase,
405
+ phaseIndex: i + 1,
406
+ phaseName: phase.name,
407
+ color: phase.color
408
+ };
409
+ }
410
+ }
411
+ return null;
412
+ }
413
+
414
+ // 格式化日期为 YYYY-MM-DD
415
+ function formatDateKey(date) {
416
+ const year = date.getFullYear();
417
+ const month = String(date.getMonth() + 1).padStart(2, '0');
418
+ const day = String(date.getDate()).padStart(2, '0');
419
+ return `${year}-${month}-${day}`;
420
+ }
421
+
422
+ // 生成任务
423
+ function generateTasks(date) {
424
+ const tasks = [];
425
+ const daysDiff = Math.floor((date - planStartDate.value) / (1000 * 60 * 60 * 24));
426
+
427
+ // 检查是否在备考范围内
428
+ if (daysDiff < 0 || daysDiff >= totalDays.value) return tasks;
429
+
430
+ const weekNumber = Math.floor(daysDiff / 7) + 1;
431
+ const phaseInfo = getPhaseInfo(weekNumber);
432
+ if (!phaseInfo) return tasks;
433
+
434
+ // 检查日期配置
435
+ const dateKey = formatDateKey(date);
436
+ const hasDateConfig = props.dateConfig && Object.keys(props.dateConfig).length > 0;
437
+ const dateTask = props.dateConfig && props.dateConfig[dateKey];
438
+
439
+ // 如果使用了日期配置模式
440
+ if (hasDateConfig) {
441
+ // 只有在 dateConfig 中有该日期配置时才显示任务
442
+ if (dateTask) {
443
+ tasks.push({
444
+ id: daysDiff,
445
+ title: dateTask.title || '学习任务',
446
+ duration: dateTask.duration || '',
447
+ description: dateTask.description || '',
448
+ materials: dateTask.materials || '',
449
+ exercises: dateTask.exercises || [],
450
+ phase: dateTask.phase || phaseInfo.phaseName,
451
+ color: phaseInfo.color,
452
+ isCustomDate: true // 标记为日期配置
453
+ });
454
+ }
455
+ // 如果没有该日期配置,返回空任务(休息日)
456
+ return tasks;
457
+ }
458
+
459
+ // 使用周配置(fallback,仅当 dateConfig 为空时)
460
+ const dayOfWeek = date.getDay();
461
+ const isWeekend = dayOfWeek === 0 || dayOfWeek === 6;
462
+
463
+ const phaseKey = `phase${phaseInfo.phaseIndex}`;
464
+ const phasePlan = props.studyPlan[phaseKey];
465
+
466
+ if (!phasePlan) return tasks;
467
+
468
+ const dayTasks = isWeekend ? (phasePlan.weekend || []) : (phasePlan.weekday || []);
469
+
470
+ // 根据星期几选择任务(循环使用任务列表)
471
+ if (dayTasks.length > 0) {
472
+ const taskIndex = isWeekend ? 0 : (dayOfWeek - 1) % dayTasks.length;
473
+ const task = dayTasks[taskIndex];
474
+
475
+ if (task) {
476
+ tasks.push({
477
+ id: daysDiff,
478
+ title: task.title || '学习任务',
479
+ duration: task.duration || '',
480
+ description: task.description || '',
481
+ materials: task.materials || '',
482
+ exercises: task.exercises || [],
483
+ phase: phaseInfo.phaseName,
484
+ color: phaseInfo.color,
485
+ isCustomDate: false
486
+ });
487
+ }
488
+ }
489
+
490
+ return tasks;
491
+ }
492
+
493
+ // 生成日历天数
494
+ const calendarDays = computed(() => {
495
+ const days = [];
496
+ const firstDay = new Date(currentYear.value, currentMonth.value, 1);
497
+ const lastDay = new Date(currentYear.value, currentMonth.value + 1, 0);
498
+ const startDay = firstDay.getDay();
499
+
500
+ // 上月填充
501
+ const prevMonthLastDay = new Date(currentYear.value, currentMonth.value, 0).getDate();
502
+ for (let i = startDay - 1; i >= 0; i--) {
503
+ const date = new Date(currentYear.value, currentMonth.value - 1, prevMonthLastDay - i);
504
+ days.push({
505
+ date: prevMonthLastDay - i,
506
+ currentMonth: false,
507
+ isToday: false,
508
+ tasks: generateTasks(date),
509
+ fullDate: formatDate(date),
510
+ dateObj: date
511
+ });
512
+ }
513
+
514
+ // 当月
515
+ for (let i = 1; i <= lastDay.getDate(); i++) {
516
+ const date = new Date(currentYear.value, currentMonth.value, i);
517
+ const isToday = date.toDateString() === today.toDateString();
518
+ days.push({
519
+ date: i,
520
+ currentMonth: true,
521
+ isToday,
522
+ tasks: generateTasks(date),
523
+ fullDate: formatDate(date),
524
+ dateObj: date
525
+ });
526
+ }
527
+
528
+ // 下月填充
529
+ const remaining = 42 - days.length;
530
+ for (let i = 1; i <= remaining; i++) {
531
+ const date = new Date(currentYear.value, currentMonth.value + 1, i);
532
+ days.push({
533
+ date: i,
534
+ currentMonth: false,
535
+ isToday: false,
536
+ tasks: generateTasks(date),
537
+ fullDate: formatDate(date),
538
+ dateObj: date
539
+ });
540
+ }
541
+
542
+ return days;
543
+ });
544
+
545
+ // 格式化日期
546
+ function formatDate(date) {
547
+ return `${date.getFullYear()}年${date.getMonth() + 1}月${date.getDate()}日`;
548
+ }
549
+
550
+ // 导航
551
+ function prevMonth() {
552
+ if (currentMonth.value === 0) {
553
+ currentMonth.value = 11;
554
+ currentYear.value--;
555
+ } else {
556
+ currentMonth.value--;
557
+ }
558
+ }
559
+
560
+ function nextMonth() {
561
+ if (currentMonth.value === 11) {
562
+ currentMonth.value = 0;
563
+ currentYear.value++;
564
+ } else {
565
+ currentMonth.value++;
566
+ }
567
+ }
568
+
569
+ // 选择日期(打开弹窗)
570
+ function selectDay(day) {
571
+ openModal(day);
572
+ }
573
+
574
+ // 统计数据
575
+ const studyDaysCount = computed(() => {
576
+ let count = 0;
577
+ for (let i = 0; i < totalDays.value; i++) {
578
+ const date = new Date(planStartDate.value);
579
+ date.setDate(planStartDate.value.getDate() + i);
580
+ if (generateTasks(date).length > 0) count++;
581
+ }
582
+ return count;
583
+ });
584
+
585
+ const restDaysCount = computed(() => totalDays.value - studyDaysCount.value);
586
+
587
+ const progressPercent = computed(() => {
588
+ const daysPassed = Math.floor((today - planStartDate.value) / (1000 * 60 * 60 * 24));
589
+ if (daysPassed < 0) return 0;
590
+ if (daysPassed >= totalDays.value) return 100;
591
+ return Math.round((daysPassed / totalDays.value) * 100);
592
+ });
593
+
594
+ // 初始化
595
+ onMounted(() => {
596
+ // 选中今天
597
+ const todayDay = calendarDays.value.find(d => d.isToday);
598
+ if (todayDay) {
599
+ selectedDay.value = todayDay;
600
+ }
601
+ });
602
+ </script>
603
+
604
+ <style scoped>
605
+ .study-calendar {
606
+ max-width: 900px;
607
+ margin: 0 auto;
608
+ padding: 20px;
609
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
610
+ background: #ffffff;
611
+ border-radius: 16px;
612
+ box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1);
613
+ }
614
+
615
+ .calendar-header {
616
+ display: flex;
617
+ justify-content: space-between;
618
+ align-items: center;
619
+ margin-bottom: 20px;
620
+ }
621
+
622
+ .nav-btn {
623
+ background: #f3f4f6;
624
+ border: none;
625
+ width: 40px;
626
+ height: 40px;
627
+ border-radius: 50%;
628
+ cursor: pointer;
629
+ font-size: 18px;
630
+ transition: all 0.2s;
631
+ }
632
+
633
+ .nav-btn:hover {
634
+ background: #e5e7eb;
635
+ }
636
+
637
+ .month-title {
638
+ font-size: 24px;
639
+ font-weight: 600;
640
+ color: #1f2937;
641
+ background: transparent;
642
+ }
643
+
644
+ :root .month-title,
645
+ html:not(.dark) .month-title {
646
+ color: #1f2937 !important;
647
+ }
648
+
649
+ .phase-tags {
650
+ display: flex;
651
+ gap: 12px;
652
+ margin-bottom: 20px;
653
+ flex-wrap: wrap;
654
+ }
655
+
656
+ .phase-tag {
657
+ padding: 6px 16px;
658
+ border-radius: 20px;
659
+ color: white;
660
+ font-size: 14px;
661
+ font-weight: 500;
662
+ }
663
+
664
+ .weekdays {
665
+ display: grid;
666
+ grid-template-columns: repeat(7, 1fr);
667
+ gap: 4px;
668
+ margin-bottom: 8px;
669
+ }
670
+
671
+ .weekday {
672
+ text-align: center;
673
+ padding: 10px;
674
+ font-weight: 600;
675
+ color: #6b7280;
676
+ font-size: 14px;
677
+ }
678
+
679
+ .calendar-grid {
680
+ display: grid;
681
+ grid-template-columns: repeat(7, 1fr);
682
+ gap: 4px;
683
+ }
684
+
685
+ .calendar-day {
686
+ min-height: 90px;
687
+ padding: 8px;
688
+ background: #f9fafb;
689
+ border-radius: 8px;
690
+ cursor: pointer;
691
+ transition: all 0.2s;
692
+ border-left: 4px solid transparent;
693
+ }
694
+
695
+ .calendar-day:hover {
696
+ background: #f3f4f6;
697
+ transform: translateY(-2px);
698
+ }
699
+
700
+ .calendar-day.other-month {
701
+ opacity: 0.4;
702
+ }
703
+
704
+ .calendar-day.today {
705
+ background: #eff6ff;
706
+ box-shadow: inset 0 0 0 2px #3b82f6;
707
+ }
708
+
709
+ .calendar-day.has-task {
710
+ background: #ffffff;
711
+ }
712
+
713
+ .calendar-day.past-day {
714
+ background: #e5e7eb;
715
+ }
716
+
717
+ .day-number {
718
+ font-weight: 600;
719
+ font-size: 14px;
720
+ color: #374151;
721
+ }
722
+
723
+ .day-tasks {
724
+ margin-top: 4px;
725
+ }
726
+
727
+ .task-item {
728
+ font-size: 11px;
729
+ padding: 2px 6px;
730
+ border-radius: 4px;
731
+ margin-bottom: 2px;
732
+ white-space: nowrap;
733
+ overflow: hidden;
734
+ text-overflow: ellipsis;
735
+ }
736
+
737
+ .more-tasks {
738
+ font-size: 10px;
739
+ color: #9ca3af;
740
+ }
741
+
742
+ /* 弹窗样式 */
743
+ .modal-overlay {
744
+ position: fixed;
745
+ top: 0;
746
+ left: 0;
747
+ right: 0;
748
+ bottom: 0;
749
+ background: rgba(0, 0, 0, 0.5);
750
+ display: flex;
751
+ align-items: center;
752
+ justify-content: center;
753
+ z-index: 1000;
754
+ padding: 20px;
755
+ }
756
+
757
+ .modal-content {
758
+ background: white;
759
+ border-radius: 16px;
760
+ max-width: 500px;
761
+ width: 100%;
762
+ max-height: 80vh;
763
+ overflow-y: auto;
764
+ padding: 24px;
765
+ position: relative;
766
+ box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
767
+ animation: modalSlideIn 0.3s ease;
768
+ }
769
+
770
+ @keyframes modalSlideIn {
771
+ from {
772
+ opacity: 0;
773
+ transform: translateY(-20px);
774
+ }
775
+ to {
776
+ opacity: 1;
777
+ transform: translateY(0);
778
+ }
779
+ }
780
+
781
+ .modal-close {
782
+ position: absolute;
783
+ top: 16px;
784
+ right: 16px;
785
+ background: #f3f4f6;
786
+ border: none;
787
+ width: 32px;
788
+ height: 32px;
789
+ border-radius: 50%;
790
+ font-size: 20px;
791
+ cursor: pointer;
792
+ display: flex;
793
+ align-items: center;
794
+ justify-content: center;
795
+ color: #6b7280;
796
+ transition: all 0.2s;
797
+ }
798
+
799
+ .modal-close:hover {
800
+ background: #e5e7eb;
801
+ color: #1f2937;
802
+ }
803
+
804
+ .modal-header {
805
+ display: flex;
806
+ align-items: center;
807
+ gap: 12px;
808
+ margin-bottom: 20px;
809
+ padding-right: 40px;
810
+ }
811
+
812
+ .modal-header h3 {
813
+ margin: 0;
814
+ font-size: 20px;
815
+ color: #1f2937;
816
+ }
817
+
818
+ .day-status {
819
+ padding: 4px 12px;
820
+ border-radius: 20px;
821
+ font-size: 12px;
822
+ font-weight: 500;
823
+ }
824
+
825
+ .day-status.completed {
826
+ background: #d1fae5;
827
+ color: #059669;
828
+ }
829
+
830
+ .day-status.today {
831
+ background: #dbeafe;
832
+ color: #2563eb;
833
+ }
834
+
835
+ .day-status.pending {
836
+ background: #fef3c7;
837
+ color: #d97706;
838
+ }
839
+
840
+ .day-status.future {
841
+ background: #f3f4f6;
842
+ color: #6b7280;
843
+ }
844
+
845
+ .custom-date-badge {
846
+ padding: 4px 10px;
847
+ border-radius: 20px;
848
+ font-size: 11px;
849
+ font-weight: 500;
850
+ background: #fef3c7;
851
+ color: #92400e;
852
+ margin-left: auto;
853
+ }
854
+
855
+ .no-tasks {
856
+ color: #9ca3af;
857
+ text-align: center;
858
+ padding: 40px 20px;
859
+ }
860
+
861
+ .no-tasks-icon {
862
+ font-size: 48px;
863
+ margin-bottom: 16px;
864
+ }
865
+
866
+ .no-tasks p {
867
+ margin: 0 0 8px 0;
868
+ }
869
+
870
+ .no-tasks-hint {
871
+ font-size: 14px;
872
+ color: #d1d5db;
873
+ }
874
+
875
+ .task-list {
876
+ display: flex;
877
+ flex-direction: column;
878
+ gap: 12px;
879
+ }
880
+
881
+ .task-detail {
882
+ background: white;
883
+ padding: 16px;
884
+ border-radius: 8px;
885
+ border-left: 4px solid;
886
+ }
887
+
888
+ .task-header {
889
+ display: flex;
890
+ justify-content: space-between;
891
+ margin-bottom: 8px;
892
+ }
893
+
894
+ .task-phase {
895
+ font-weight: 600;
896
+ font-size: 12px;
897
+ }
898
+
899
+ .task-duration {
900
+ font-size: 12px;
901
+ color: #6b7280;
902
+ background: #f3f4f6;
903
+ padding: 2px 8px;
904
+ border-radius: 4px;
905
+ }
906
+
907
+ .task-title {
908
+ font-size: 16px;
909
+ font-weight: 600;
910
+ color: #1f2937;
911
+ margin-bottom: 8px;
912
+ }
913
+
914
+ .task-desc {
915
+ font-size: 14px;
916
+ color: #4b5563;
917
+ margin-bottom: 8px;
918
+ }
919
+
920
+ .task-materials {
921
+ font-size: 13px;
922
+ color: #6b7280;
923
+ background: #f9fafb;
924
+ padding: 10px 12px;
925
+ border-radius: 6px;
926
+ display: flex;
927
+ align-items: flex-start;
928
+ gap: 8px;
929
+ margin-bottom: 12px;
930
+ }
931
+
932
+ .materials-icon {
933
+ flex-shrink: 0;
934
+ }
935
+
936
+ /* 练习详情样式 */
937
+ .exercises-section {
938
+ margin-top: 16px;
939
+ padding-top: 16px;
940
+ border-top: 1px solid #e5e7eb;
941
+ }
942
+
943
+ .exercises-header {
944
+ font-size: 14px;
945
+ font-weight: 600;
946
+ color: #374151;
947
+ margin-bottom: 12px;
948
+ }
949
+
950
+ .exercises-list {
951
+ display: flex;
952
+ flex-direction: column;
953
+ gap: 8px;
954
+ }
955
+
956
+ .exercise-item {
957
+ display: flex;
958
+ align-items: center;
959
+ gap: 10px;
960
+ padding: 10px 12px;
961
+ background: #f9fafb;
962
+ border-radius: 8px;
963
+ transition: all 0.2s;
964
+ }
965
+
966
+ .exercise-item.completed {
967
+ background: #ecfdf5;
968
+ }
969
+
970
+ .exercise-item.completed .exercise-name {
971
+ text-decoration: line-through;
972
+ color: #9ca3af;
973
+ }
974
+
975
+ .exercise-checkbox {
976
+ display: flex;
977
+ align-items: center;
978
+ cursor: pointer;
979
+ }
980
+
981
+ .exercise-checkbox input {
982
+ display: none;
983
+ }
984
+
985
+ .checkmark-small {
986
+ width: 18px;
987
+ height: 18px;
988
+ border: 2px solid #d1d5db;
989
+ border-radius: 4px;
990
+ display: flex;
991
+ align-items: center;
992
+ justify-content: center;
993
+ transition: all 0.2s;
994
+ flex-shrink: 0;
995
+ }
996
+
997
+ .exercise-checkbox input:checked + .checkmark-small {
998
+ background: #10b981;
999
+ border-color: #10b981;
1000
+ }
1001
+
1002
+ .exercise-checkbox input:checked + .checkmark-small::after {
1003
+ content: '✓';
1004
+ color: white;
1005
+ font-size: 12px;
1006
+ font-weight: bold;
1007
+ }
1008
+
1009
+ .exercise-status {
1010
+ font-size: 16px;
1011
+ flex-shrink: 0;
1012
+ }
1013
+
1014
+ .exercise-type {
1015
+ font-size: 11px;
1016
+ font-weight: 600;
1017
+ padding: 3px 8px;
1018
+ border-radius: 4px;
1019
+ flex-shrink: 0;
1020
+ }
1021
+
1022
+ .exercise-name {
1023
+ font-size: 13px;
1024
+ color: #374151;
1025
+ flex: 1;
1026
+ }
1027
+
1028
+ .exercise-quantity {
1029
+ font-size: 12px;
1030
+ color: #6b7280;
1031
+ display: flex;
1032
+ align-items: center;
1033
+ gap: 2px;
1034
+ }
1035
+
1036
+ .quantity-done {
1037
+ font-weight: 600;
1038
+ color: #10b981;
1039
+ }
1040
+
1041
+ .quantity-sep {
1042
+ color: #d1d5db;
1043
+ }
1044
+
1045
+ .quantity-total {
1046
+ color: #9ca3af;
1047
+ }
1048
+
1049
+ .task-completion-status {
1050
+ margin-top: 16px;
1051
+ padding-top: 16px;
1052
+ border-top: 1px solid #e5e7eb;
1053
+ font-size: 14px;
1054
+ }
1055
+
1056
+ .status-completed {
1057
+ color: #059669;
1058
+ font-weight: 500;
1059
+ }
1060
+
1061
+ .status-pending {
1062
+ color: #6b7280;
1063
+ }
1064
+
1065
+ /* 任务进度条 */
1066
+ .task-progress-bar {
1067
+ margin-top: 16px;
1068
+ }
1069
+
1070
+ .progress-label {
1071
+ font-size: 12px;
1072
+ color: #6b7280;
1073
+ margin-bottom: 6px;
1074
+ }
1075
+
1076
+ .progress-track {
1077
+ height: 8px;
1078
+ background: #e5e7eb;
1079
+ border-radius: 4px;
1080
+ overflow: hidden;
1081
+ }
1082
+
1083
+ .progress-fill {
1084
+ height: 100%;
1085
+ border-radius: 4px;
1086
+ transition: width 0.3s ease;
1087
+ }
1088
+
1089
+ .task-completion {
1090
+ margin-top: 12px;
1091
+ padding-top: 12px;
1092
+ border-top: 1px solid #e5e7eb;
1093
+ }
1094
+
1095
+ .completion-checkbox {
1096
+ display: flex;
1097
+ align-items: center;
1098
+ gap: 10px;
1099
+ cursor: pointer;
1100
+ font-size: 14px;
1101
+ color: #4b5563;
1102
+ }
1103
+
1104
+ .completion-checkbox input {
1105
+ display: none;
1106
+ }
1107
+
1108
+ .completion-checkbox .checkmark {
1109
+ width: 20px;
1110
+ height: 20px;
1111
+ border: 2px solid #d1d5db;
1112
+ border-radius: 4px;
1113
+ display: flex;
1114
+ align-items: center;
1115
+ justify-content: center;
1116
+ transition: all 0.2s;
1117
+ }
1118
+
1119
+ .completion-checkbox input:checked + .checkmark {
1120
+ background: #10b981;
1121
+ border-color: #10b981;
1122
+ }
1123
+
1124
+ .completion-checkbox input:checked + .checkmark::after {
1125
+ content: '✓';
1126
+ color: white;
1127
+ font-size: 14px;
1128
+ font-weight: bold;
1129
+ }
1130
+
1131
+ .completion-checkbox input:checked ~ span:last-child {
1132
+ color: #10b981;
1133
+ font-weight: 500;
1134
+ }
1135
+
1136
+ .study-stats {
1137
+ display: grid;
1138
+ grid-template-columns: repeat(4, 1fr);
1139
+ gap: 16px;
1140
+ margin-top: 24px;
1141
+ }
1142
+
1143
+ .stat-item {
1144
+ text-align: center;
1145
+ padding: 16px;
1146
+ background: #f9fafb;
1147
+ border-radius: 12px;
1148
+ }
1149
+
1150
+ .stat-value {
1151
+ display: block;
1152
+ font-size: 28px;
1153
+ font-weight: 700;
1154
+ color: #3b82f6;
1155
+ }
1156
+
1157
+ .stat-label {
1158
+ font-size: 14px;
1159
+ color: #6b7280;
1160
+ }
1161
+
1162
+ /* 暗色模式适配 */
1163
+ :global(.dark) .study-calendar {
1164
+ background: #1e293b;
1165
+ }
1166
+
1167
+ :global(.dark) .month-title {
1168
+ color: #f1f5f9 !important;
1169
+ }
1170
+
1171
+ :global(.dark) .nav-btn {
1172
+ background: #334155;
1173
+ color: #f1f5f9;
1174
+ }
1175
+
1176
+ :global(.dark) .nav-btn:hover {
1177
+ background: #475569;
1178
+ }
1179
+
1180
+ :global(.dark) .weekday {
1181
+ color: #94a3b8;
1182
+ }
1183
+
1184
+ :global(.dark) .calendar-day {
1185
+ background: #334155;
1186
+ }
1187
+
1188
+ :global(.dark) .calendar-day:hover {
1189
+ background: #475569;
1190
+ }
1191
+
1192
+ :global(.dark) .calendar-day.today {
1193
+ background: #1e3a5f;
1194
+ }
1195
+
1196
+ :global(.dark) .calendar-day.has-task {
1197
+ background: #1e293b;
1198
+ }
1199
+
1200
+ :global(.dark) .calendar-day.past-day {
1201
+ background: #475569;
1202
+ }
1203
+
1204
+ :global(.dark) .day-number {
1205
+ color: #e2e8f0;
1206
+ }
1207
+
1208
+ :global(.dark) .day-detail {
1209
+ background: #334155;
1210
+ }
1211
+
1212
+ :global(.dark) .day-detail h3 {
1213
+ color: #f1f5f9;
1214
+ }
1215
+
1216
+ :global(.dark) .task-detail {
1217
+ background: #1e293b;
1218
+ }
1219
+
1220
+ :global(.dark) .task-title {
1221
+ color: #f1f5f9;
1222
+ }
1223
+
1224
+ :global(.dark) .task-desc {
1225
+ color: #cbd5e1;
1226
+ }
1227
+
1228
+ :global(.dark) .stat-item {
1229
+ background: #334155;
1230
+ }
1231
+
1232
+ :global(.dark) .stat-label {
1233
+ color: #94a3b8;
1234
+ }
1235
+
1236
+ /* 弹窗暗色模式 */
1237
+ :global(.dark) .modal-content {
1238
+ background: #1e293b;
1239
+ }
1240
+
1241
+ :global(.dark) .modal-close {
1242
+ background: #334155;
1243
+ color: #94a3b8;
1244
+ }
1245
+
1246
+ :global(.dark) .modal-close:hover {
1247
+ background: #475569;
1248
+ color: #f1f5f9;
1249
+ }
1250
+
1251
+ :global(.dark) .modal-header h3 {
1252
+ color: #f1f5f9;
1253
+ }
1254
+
1255
+ :global(.dark) .no-tasks {
1256
+ color: #94a3b8;
1257
+ }
1258
+
1259
+ :global(.dark) .no-tasks-hint {
1260
+ color: #64748b;
1261
+ }
1262
+
1263
+ :global(.dark) .task-materials {
1264
+ background: #334155;
1265
+ color: #94a3b8;
1266
+ }
1267
+
1268
+ :global(.dark) .task-completion {
1269
+ border-top-color: #334155;
1270
+ }
1271
+
1272
+ :global(.dark) .completion-checkbox {
1273
+ color: #94a3b8;
1274
+ }
1275
+
1276
+ :global(.dark) .completion-checkbox .checkmark {
1277
+ border-color: #475569;
1278
+ }
1279
+
1280
+ /* 练习部分暗色模式 */
1281
+ :global(.dark) .exercises-section {
1282
+ border-top-color: #334155;
1283
+ }
1284
+
1285
+ :global(.dark) .exercises-header {
1286
+ color: #e2e8f0;
1287
+ }
1288
+
1289
+ :global(.dark) .exercise-item {
1290
+ background: #334155;
1291
+ }
1292
+
1293
+ :global(.dark) .exercise-item.completed {
1294
+ background: #1e3a3a;
1295
+ }
1296
+
1297
+ :global(.dark) .exercise-name {
1298
+ color: #e2e8f0;
1299
+ }
1300
+
1301
+ :global(.dark) .exercise-item.completed .exercise-name {
1302
+ color: #64748b;
1303
+ }
1304
+
1305
+ :global(.dark) .checkmark-small {
1306
+ border-color: #475569;
1307
+ }
1308
+
1309
+ :global(.dark) .progress-input {
1310
+ background: #1e293b;
1311
+ border-color: #475569;
1312
+ color: #e2e8f0;
1313
+ }
1314
+
1315
+ :global(.dark) .progress-track {
1316
+ background: #334155;
1317
+ }
1318
+
1319
+ :global(.dark) .progress-label {
1320
+ color: #94a3b8;
1321
+ }
1322
+
1323
+ /* 响应式 */
1324
+ @media (max-width: 768px) {
1325
+ .calendar-day {
1326
+ min-height: 60px;
1327
+ padding: 4px;
1328
+ }
1329
+
1330
+ .task-item {
1331
+ display: none;
1332
+ }
1333
+
1334
+ .calendar-day.has-task::after {
1335
+ content: '';
1336
+ display: block;
1337
+ width: 6px;
1338
+ height: 6px;
1339
+ background: currentColor;
1340
+ border-radius: 50%;
1341
+ margin-top: 4px;
1342
+ }
1343
+
1344
+ .study-stats {
1345
+ grid-template-columns: repeat(2, 1fr);
1346
+ }
1347
+ }
1348
+ </style>