跳到主要内容

首页侧边栏日历及节日倒计时

最后更新于:

首页侧边栏日历及节日倒计时

image-20251021143003155

目录

[[toc]]

[toc]

背景

Hyde大佬自动和女友爬山回来,灵感陡增,产出干活满满。这不,我就可以马上抄作业了。🤣

版权

次功能来自《Hyde》大佬,感谢大佬。❤️❤️❤️

环境

Teek@1.5.1-2025.10.19版本

Teek-one开箱即用版仓库:https://cnb.cool/onedayxyy/vitepress-theme-teek-one-public

配置

(1)创建2个文件

docs\.vitepress\theme\components\CalendarCard.vue

docs\.vitepress\theme\components\ScheduleCard.vue

  1<template>
  2    <TkPageCard>
  3      <div class="card-widget" id="card-widget-calendar">
  4        <div class="item-headline">
  5          <i class="icon-calendar"></i>
  6        </div>
  7        <div class="item-content">
  8          <div id="calendar-area-left">
  9            <div id="calendar-week">
 10              {{ weekNumber }}&nbsp;{{ weekDays[today.getDay()] }}
 11            </div>
 12            <div id="calendar-date">{{ today.getDate() }}</div>
 13            <div id="calendar-solar">
 14              {{ today.getFullYear() }}{{ today.getMonth() + 1 }}月第{{
 15                dayOfYear
 16              }}
 17            </div>
 18            <div id="calendar-lunar">
 19              {{ lunarYear }}&nbsp;{{ lunarMonth }}&nbsp;{{ lunarDay }}
 20            </div>
 21          </div>
 22          <div id="calendar-area-right">
 23            <div id="calendar-main">
 24              <!-- 星期标题行 -->
 25              <div class="calendar-r0">
 26                <div class="calendar-d0"><a></a></div>
 27                <div class="calendar-d1"><a></a></div>
 28                <div class="calendar-d2"><a></a></div>
 29                <div class="calendar-d3"><a></a></div>
 30                <div class="calendar-d4"><a></a></div>
 31                <div class="calendar-d5"><a></a></div>
 32                <div class="calendar-d6"><a></a></div>
 33              </div>
 34  
 35              <!-- 日期行 -->
 36              <div
 37                v-for="(week, weekIndex) in calendarWeeks"
 38                :key="weekIndex"
 39                :class="`calendar-r${weekIndex + 1}`"
 40              >
 41                <div
 42                  v-for="(day, dayIndex) in week"
 43                  :key="dayIndex"
 44                  :class="`calendar-d${dayIndex}`"
 45                >
 46                  <a
 47                    :class="{ now: day.isToday, 'other-month': day.isOtherMonth }"
 48                    v-if="day.date"
 49                  >
 50                    {{ day.date }}
 51                  </a>
 52                  <a v-else></a>
 53                </div>
 54              </div>
 55            </div>
 56          </div>
 57        </div>
 58      </div>
 59    </TkPageCard>
 60  </template>
 61  
 62  <script setup>
 63  import { TkPageCard } from "vitepress-theme-teek";
 64  import { ref, onMounted, computed } from "vue";
 65  
 66  // 星期几中文映射
 67  const weekDays = ["周日", "周一", "周二", "周三", "周四", "周五", "周六"];
 68  
 69  // 当前日期
 70  const today = ref(new Date());
 71  
 72  // 生成当前月份的日历数据
 73  const calendarWeeks = computed(() => {
 74    const year = today.value.getFullYear();
 75    const month = today.value.getMonth();
 76  
 77    // 当月第一天
 78    const firstDay = new Date(year, month, 1);
 79    // 当月最后一天
 80    const lastDay = new Date(year, month + 1, 0);
 81  
 82    // 日历需要显示的第一天(可能是上月的日期)
 83    const startDay = new Date(firstDay);
 84    startDay.setDate(firstDay.getDate() - firstDay.getDay());
 85  
 86    // 日历需要显示的最后一天(可能是下月的日期)
 87    const endDay = new Date(lastDay);
 88    if (endDay.getDay() < 6) {
 89      endDay.setDate(lastDay.getDate() + (6 - endDay.getDay()));
 90    }
 91  
 92    // 生成日历数据
 93    const weeks = [];
 94    let currentDay = new Date(startDay);
 95  
 96    while (currentDay <= endDay) {
 97      const week = [];
 98      for (let i = 0; i < 7; i++) {
 99        const date = currentDay.getDate();
100        const isToday = currentDay.toDateString() === today.value.toDateString();
101        const isOtherMonth = currentDay.getMonth() !== month;
102  
103        week.push({ date, isToday, isOtherMonth });
104  
105        currentDay.setDate(currentDay.getDate() + 1);
106      }
107      weeks.push(week);
108    }
109  
110    return weeks;
111  });
112  
113  // 计算当前是今年的第几天
114  const dayOfYear = computed(() => {
115    const start = new Date(today.value.getFullYear(), 0, 0);
116    const diff = today.value - start;
117    const oneDay = 1000 * 60 * 60 * 24;
118    return Math.floor(diff / oneDay);
119  });
120  
121  // 计算当前是第几周
122  const weekNumber = computed(() => {
123    const firstDay = new Date(today.value.getFullYear(), 0, 1);
124    const pastDaysOfYear = (today.value - firstDay) / 86400000;
125    return Math.ceil((pastDaysOfYear + firstDay.getDay() + 1) / 7);
126  });
127  
128  // 农历转换相关
129  const lunarInfo = [
130    0x04bd8, 0x04ae0, 0x0a570, 0x054d5, 0x0d260, 0x0d950, 0x16554, 0x056a0,
131    0x09ad0, 0x055d2, 0x04ae0, 0x0a5b6, 0x0a4d0, 0x0d250, 0x1d255, 0x0b540,
132    0x0d6a0, 0x0ada2, 0x095b0, 0x14977, 0x04970, 0x0a4b0, 0x0b4b5, 0x06a50,
133    0x06d40, 0x1ab54, 0x02b60, 0x09570, 0x052f2, 0x04970, 0x06566, 0x0d4a0,
134    0x0ea50, 0x06e95, 0x05ad0, 0x02b60, 0x186e3, 0x092e0, 0x1c8d7, 0x0c950,
135    0x0d4a0, 0x1d8a6, 0x0b550, 0x056a0, 0x1a5b4, 0x025d0, 0x092d0, 0x0d2b2,
136    0x0a950, 0x0b557, 0x06ca0, 0x0b550, 0x15355, 0x04da0, 0x0a5b0, 0x14573,
137    0x052b0, 0x0a9a8, 0x0e950, 0x06aa0, 0x0aea6, 0x0ab50, 0x04b60, 0x0aae4,
138    0x0a570, 0x05260, 0x0f263, 0x0d950, 0x05b57, 0x056a0, 0x096d0, 0x04dd5,
139    0x04ad0, 0x0a4d0, 0x0d4d4, 0x0d250, 0x0d558, 0x0b540, 0x0b5a0, 0x195a6,
140    0x095b0, 0x049b0, 0x0a974, 0x0a4b0, 0x0b27a, 0x06a50, 0x06d40, 0x0af46,
141    0x0ab60, 0x09570, 0x04af5, 0x04970, 0x064b0, 0x074a3, 0x0ea50, 0x06b58,
142    0x055c0, 0x0ab60, 0x096d5, 0x092e0, 0x0c960, 0x0d954, 0x0d4a0, 0x0da50,
143    0x07552, 0x056a0, 0x0abb7, 0x025d0, 0x092d0, 0x0cab5, 0x0a950, 0x0b4a0,
144    0x0baa4, 0x0ad50, 0x055d9, 0x04ba0, 0x0a5b0, 0x15176, 0x052b0, 0x0a930,
145    0x07954, 0x06aa0, 0x0ad50, 0x05b52, 0x04b60, 0x0a6e6, 0x0a4e0, 0x0d260,
146    0x0ea65, 0x0d530, 0x05aa0, 0x076a3, 0x096d0, 0x04bd7, 0x04ad0, 0x0a4d0,
147    0x1d0b6, 0x0d250, 0x0d520, 0x0dd45, 0x0b5a0, 0x056d0, 0x055b2, 0x049b0,
148    0x0a577, 0x0a4b0, 0x0aa50, 0x1b255, 0x06d20, 0x0ada0,
149  ];
150  
151  const gan = ["甲", "乙", "丙", "丁", "戊", "己", "庚", "辛", "壬", "癸"];
152  const zhi = [
153    "子",
154    "丑",
155    "寅",
156    "卯",
157    "辰",
158    "巳",
159    "午",
160    "未",
161    "申",
162    "酉",
163    "戌",
164    "亥",
165  ];
166  const animals = [
167    "鼠",
168    "牛",
169    "虎",
170    "兔",
171    "龙",
172    "蛇",
173    "马",
174    "羊",
175    "猴",
176    "鸡",
177    "狗",
178    "猪",
179  ];
180  const lunarMonths = [
181    "正月",
182    "二月",
183    "三月",
184    "四月",
185    "五月",
186    "六月",
187    "七月",
188    "八月",
189    "九月",
190    "十月",
191    "十一月",
192    "十二月",
193  ];
194  const lunarDays = [
195    "初一",
196    "初二",
197    "初三",
198    "初四",
199    "初五",
200    "初六",
201    "初七",
202    "初八",
203    "初九",
204    "初十",
205    "十一",
206    "十二",
207    "十三",
208    "十四",
209    "十五",
210    "十六",
211    "十七",
212    "十八",
213    "十九",
214    "二十",
215    "廿一",
216    "廿二",
217    "廿三",
218    "廿四",
219    "廿五",
220    "廿六",
221    "廿七",
222    "廿八",
223    "廿九",
224    "三十",
225  ];
226  
227  // 转换为农历
228  const getLunarDate = (date) => {
229    const year = date.getFullYear();
230    const month = date.getMonth() + 1;
231    const day = date.getDate();
232  
233    let springStart = new Date(2000, 1, 4); // 2000年春节是2月4日
234    if (year > 2000) {
235      // 简单计算春节日期,实际应用中可能需要更精确的算法
236      springStart = new Date(year, 1, 4 + Math.floor((year - 2000) * 0.2422));
237    }
238  
239    let lunarYear = year;
240    let isLeap = false;
241    let lunarMonthIdx = 0;
242    let lunarDayIdx = 0;
243  
244    // 简化的农历计算,实际应用可能需要更复杂的算法
245    const offset = Math.floor((date - new Date(year, 0, 0)) / 86400000);
246    let days = 0;
247    let i = 0;
248  
249    for (; i < 12; i++) {
250      const monthDays = (lunarInfo[year - 1900] >> (12 - i)) & 0x1 ? 30 : 29;
251      if (days + monthDays >= offset) {
252        lunarDayIdx = offset - days - 1;
253        break;
254      }
255      days += monthDays;
256    }
257    lunarMonthIdx = i;
258  
259    // 计算农历年的干支和生肖
260    const ganIndex = (year - 3) % 10;
261    const zhiIndex = (year - 3) % 12;
262    const lunarYearStr = `${gan[ganIndex]}${zhi[zhiIndex]}${animals[zhiIndex]}年`;
263  
264    return {
265      lunarYear: lunarYearStr,
266      lunarMonth: lunarMonths[lunarMonthIdx],
267      lunarDay: lunarDays[lunarDayIdx],
268    };
269  };
270  
271  // 响应式农历数据
272  const lunarDate = computed(() => getLunarDate(today.value));
273  const lunarYear = computed(() => lunarDate.value.lunarYear);
274  const lunarMonth = computed(() => lunarDate.value.lunarMonth);
275  const lunarDay = computed(() => lunarDate.value.lunarDay);
276  
277  // 每天更新一次日历
278  onMounted(() => {
279    // 检查是否需要更新(跨天)
280    const checkUpdate = () => {
281      const now = new Date();
282      if (now.toDateString() !== today.value.toDateString()) {
283        today.value = now;
284      }
285    };
286  
287    // 每分钟检查一次
288    setInterval(checkUpdate, 60000);
289  });
290  </script>
291  
292  <style scoped>
293  /* 基础样式重置 */
294  * {
295    margin: 0;
296    padding: 0;
297    box-sizing: border-box;
298    font-family: "Microsoft YaHei", sans-serif;
299  }
300  
301  body {
302    background-color: #f5f7fa;
303    padding: 20px;
304  }
305  
306  :root {
307    --other-month-bg: #fa0000;
308  }
309  
310  .dark {
311    --other-month-bg: #0080ff;
312  }
313  
314  .tk-page-card {
315    margin-top: 10px;
316  }
317  
318  .card-widget {
319    max-height: calc(100vh - 100px);
320    position: relative;
321  }
322  
323  #card-widget-calendar .item-headline {
324    padding-bottom: 0;
325    margin-left: 8px;
326    font-size: 1em;
327    font-weight: 700;
328    display: flex;
329    align-items: center;
330    gap: 5px;
331    margin-bottom: 10px;
332  }
333  
334  #card-widget-calendar .item-headline i {
335    font-size: 18px;
336  }
337  
338  #card-widget-calendar .item-content {
339    display: flex;
340  }
341  
342  #calendar-area-left,
343  #calendar-area-right {
344    height: 100%;
345    padding: 4px;
346    display: flex;
347    flex-direction: column;
348    align-items: center;
349    justify-content: center;
350  }
351  
352  #calendar-area-left {
353    width: 45%;
354  }
355  
356  #calendar-week {
357    height: 1.2rem;
358    font-size: 14px;
359    letter-spacing: 1px;
360    font-weight: 700;
361    align-items: center;
362    display: flex;
363  }
364  
365  #calendar-date {
366    height: 3rem;
367    line-height: 1.3;
368    font-size: 36px;
369    letter-spacing: 3px;
370    color: var(--vp-c-brand-1);
371    font-weight: 700;
372    align-items: center;
373    display: flex;
374    position: absolute;
375    top: calc(50% - 2.1rem);
376  }
377  
378  #calendar-solar {
379    bottom: 2.1rem;
380  }
381  
382  #calendar-lunar,
383  #calendar-solar {
384    height: 1rem;
385    font-size: 11px;
386    align-items: center;
387    display: flex;
388    position: absolute;
389  }
390  
391  #calendar-lunar {
392    bottom: 1rem;
393  }
394  
395  #calendar-area-right {
396    width: 55%;
397  }
398  
399  #calendar-main {
400    width: 100%;
401  }
402  
403  .calendar-r0,
404  .calendar-r1,
405  .calendar-r2,
406  .calendar-r3,
407  .calendar-r4,
408  .calendar-r5,
409  .calendar-rh {
410    height: 1.2rem;
411    display: flex;
412  }
413  
414  .calendar-d0,
415  .calendar-d1,
416  .calendar-d2,
417  .calendar-d3,
418  .calendar-d4,
419  .calendar-d5,
420  .calendar-d6 {
421    width: calc(100% / 7);
422    display: flex;
423    justify-content: center;
424    align-items: center;
425  }
426  
427  #calendar-main a {
428    height: 1.2rem;
429    width: 1.5rem;
430    border-radius: 50%;
431    font-size: 12px;
432    line-height: 12px;
433    display: flex;
434    justify-content: center;
435    align-items: center;
436    text-decoration: none;
437    color: var(--calendar-main-a-clolr);
438  }
439  
440  #calendar-main a.now {
441    background: var(--vp-c-brand-1);
442    color: #fff;
443  }
444  
445  #calendar-main a.other-month {
446    color: var(--other-month-clolr);
447  }
448  </style>
449  
  1<template>
  2  <TkPageCard>
  3    <div class="card-widget" id="card-widget-schedule">
  4      <div class="item-headline">
  5        <i></i>
  6      </div>
  7      <div class="item-content">
  8        <div id="schedule-area-right">
  9          <div class="schedule-r0">
 10            <!-- <div class="schedule-d0">本年</div> -->
 11            <div class="schedule-d1">
 12              <span id="p_span_year" class="aside-span1"
 13                >{{ yearProgress }}%</span
 14              >
 15              <div class="progress-container">
 16                <span class="aside-span2"
 17                  >本年还剩<a id="year_days_left">{{ yearDaysLeft }}</a
 18                  ></span
 19                >
 20                <progress
 21                  max="365"
 22                  id="pBar_year"
 23                  :value="yearPassedDays"
 24                ></progress>
 25              </div>
 26            </div>
 27          </div>
 28          <div class="schedule-r1">
 29            <!-- <div class="schedule-d0">本月</div> -->
 30            <div class="schedule-d1">
 31              <span id="p_span_month" class="aside-span1"
 32                >{{ monthProgress }}%</span
 33              >
 34              <div class="progress-container">
 35                <span class="aside-span2"
 36                  >本月还剩<a id="month_days_left">{{ monthDaysLeft }}</a
 37                  ></span
 38                >
 39                <progress
 40                  max="31"
 41                  id="pBar_month"
 42                  :value="monthPassedDays"
 43                ></progress>
 44              </div>
 45            </div>
 46          </div>
 47          <div class="schedule-r2">
 48            <!-- <div class="schedule-d0">本周</div> -->
 49            <div class="schedule-d1">
 50              <span id="p_span_week" class="aside-span1"
 51                >{{ weekProgress }}%</span
 52              >
 53              <div class="progress-container">
 54                <span class="aside-span2"
 55                  >本周还剩<a id="week_days_left">{{ weekDaysLeft }}</a
 56                  ></span
 57                >
 58                <progress
 59                  max="7"
 60                  id="pBar_week"
 61                  :value="weekDisplayValue"
 62                ></progress>
 63              </div>
 64            </div>
 65          </div>
 66        </div>
 67        <div id="schedule-area-left">
 68          <div id="schedule-title">距离春节</div>
 69          <div id="schedule-days">{{ springFestivalDays }}</div>
 70          <div id="schedule-date">{{ springFestivalDateText }}</div>
 71        </div>
 72      </div>
 73    </div>
 74  </TkPageCard>
 75</template>
 76
 77<script setup>
 78import { TkPageCard } from "vitepress-theme-teek";
 79import { ref, onMounted } from "vue";
 80
 81// 响应式变量
 82const springFestivalDays = ref("--");
 83const springFestivalDateText = ref("--"); // 动态春节日期文本
 84const yearProgress = ref("--");
 85const yearDaysLeft = ref("--");
 86const yearPassedDays = ref(0);
 87const monthProgress = ref("--");
 88const monthDaysLeft = ref("--");
 89const monthPassedDays = ref(0);
 90const weekProgress = ref("--");
 91const weekDaysLeft = ref("--");
 92const weekDisplayValue = ref(0);
 93
 94/**
 95 * 计算指定年份春节(农历正月初一)的公历日期(1900-2100年适用)
 96 * @param {number} lunarYear - 农历年份
 97 * @returns {Date} 春节的公历日期
 98 */
 99const getSpringFestivalDate = (lunarYear) => {
100  // 1900-2100年春节公历日期表([月, 日]),已精准修正2026年(索引126)为[2,17]
101  const springFestivalData = [
102    [2, 19], [2, 8], [1, 28], [2, 16], [2, 5], [1, 25], [2, 13], [2, 2], [1, 22], [2, 10], // 1900-1909 (0-9)
103    [1, 30], [2, 18], [2, 7], [1, 26], [2, 14], [2, 3], [1, 23], [2, 11], [1, 31], [2, 19], // 1910-1919 (10-19)
104    [2, 8], [1, 28], [2, 16], [2, 5], [1, 24], [2, 12], [2, 1], [1, 21], [2, 9], [1, 28],  // 1920-1929 (20-29)
105    [2, 16], [2, 5], [1, 24], [2, 12], [2, 1], [1, 21], [2, 9], [1, 29], [2, 17], [2, 6],  // 1930-1939 (30-39)
106    [1, 26], [2, 14], [2, 2], [1, 22], [2, 10], [1, 29], [2, 17], [2, 6], [1, 26], [2, 13], // 1940-1949 (40-49)
107    [2, 2], [1, 22], [2, 10], [1, 30], [2, 17], [2, 6], [1, 25], [2, 13], [2, 1], [1, 21], // 1950-1959 (50-59)
108    [2, 8], [1, 28], [2, 15], [2, 5], [1, 24], [2, 12], [1, 31], [2, 18], [2, 7], [1, 27], // 1960-1969 (60-69)
109    [2, 15], [2, 3], [1, 23], [2, 11], [1, 31], [2, 18], [2, 6], [1, 26], [2, 14], [2, 3], // 1970-1979 (70-79)
110    [1, 23], [2, 10], [1, 29], [2, 16], [2, 5], [1, 24], [2, 12], [2, 1], [1, 22], [2, 9], // 1980-1989 (80-89)
111    [1, 28], [2, 15], [2, 4], [1, 23], [2, 10], [1, 30], [2, 17], [2, 6], [1, 26], [2, 14], // 1990-1999 (90-99)
112    [2, 2], [1, 22], [2, 10], [1, 29], [2, 17], [2, 5], [1, 24], [2, 12], [1, 31], [2, 18], // 2000-2009 (100-109)
113    [2, 7], [1, 26], [2, 14], [2, 3], [1, 23], [2, 10], [1, 31], [2, 18], [2, 7], [1, 26], // 2010-2019 (110-119)
114    [2, 12], [2, 1], [1, 22], [2, 10], [1, 29], [2, 17], [2, 17], [1, 24], [2, 12], [2, 1], // 2020-2029 (120-129) 
115    // 索引126对应2026年,已明确设置为[2,17]
116    [1, 22], [2, 10], [1, 29], [2, 17], [2, 6], [1, 26], [2, 14], [2, 3], [1, 23], [2, 10], // 2030-2039 (130-139)
117    [1, 30], [2, 17], [2, 6], [1, 26], [2, 13], [2, 2], [1, 22], [2, 10], [1, 29], [2, 17], // 2040-2049 (140-149)
118    [2, 5], [1, 25], [2, 13], [2, 1], [1, 21], [2, 9], [1, 28], [2, 16], [2, 5], [1, 24], // 2050-2059 (150-159)
119    [2, 12], [2, 1], [1, 21], [2, 9], [1, 28], [2, 15], [2, 4], [1, 24], [2, 11], [1, 31], // 2060-2069 (160-169)
120    [2, 18], [2, 7], [1, 27], [2, 15], [2, 3], [1, 23], [2, 11], [1, 31], [2, 18], [2, 6], // 2070-2079 (170-179)
121    [1, 26], [2, 14], [2, 3], [1, 23], [2, 10], [1, 29], [2, 16], [2, 5], [1, 24], [2, 12], // 2080-2089 (180-189)
122    [2, 1], [1, 22], [2, 9], [1, 28], [2, 15], [2, 4], [1, 23], [2, 10], [1, 30], [2, 17], // 2090-2099 (190-199)
123    [2, 5] // 2100年 (200)
124  ];
125
126  const index = lunarYear - 1900;
127  if (index >= 0 && index < springFestivalData.length) {
128    const [month, day] = springFestivalData[index];
129    return new Date(lunarYear, month - 1, day); // 月份在Date中为0基,需减1
130  }
131  return new Date(lunarYear, 1, 1); // 超出范围默认2月1日
132};
133
134/**
135 * 动态获取目标春节日期(今年或明年)
136 * @returns {Date} 目标春节日期
137 */
138const getTargetSpringFestival = () => {
139  const today = new Date();
140  const currentYear = today.getFullYear();
141  const currentYearSpring = getSpringFestivalDate(currentYear);
142  // 若今天在今年春节前,目标为今年春节;否则为明年春节
143  return today < currentYearSpring ? currentYearSpring : getSpringFestivalDate(currentYear + 1);
144};
145
146/**
147 * 计算两个日期的天数差
148 * @param {Date} date1 - 起始日期
149 * @param {Date} date2 - 目标日期
150 * @returns {number} 天数差(向上取整)
151 */
152const getDaysDifference = (date1, date2) => {
153  const oneDay = 24 * 60 * 60 * 1000;
154  const diffTime = date2 - date1;
155  return Math.ceil(diffTime / oneDay);
156};
157
158/**
159 * 更新春节倒计时及日期显示
160 */
161const updateSpringFestivalCountdown = () => {
162  const today = new Date();
163  today.setHours(0, 0, 0, 0); // 忽略时分秒,按整天计算
164  const targetSpring = getTargetSpringFestival();
165  // 计算剩余天数
166  springFestivalDays.value = getDaysDifference(today, targetSpring);
167  // 格式化日期为YYYY-MM-DD
168  const year = targetSpring.getFullYear();
169  const month = String(targetSpring.getMonth() + 1).padStart(2, '0');
170  const day = String(targetSpring.getDate()).padStart(2, '0');
171  springFestivalDateText.value = `${year}-${month}-${day}`;
172};
173
174/**
175 * 更新年度进度
176 */
177const updateYearProgress = () => {
178  const today = new Date();
179  const year = today.getFullYear();
180  const startOfYear = new Date(year, 0, 1);
181  const endOfYear = new Date(year, 11, 31);
182  const totalDays = getDaysDifference(startOfYear, endOfYear) + 1;
183  const daysPassed = getDaysDifference(startOfYear, today);
184  const daysLeft = totalDays - daysPassed;
185  const progress = (daysPassed / totalDays) * 100;
186
187  yearDaysLeft.value = daysLeft;
188  yearProgress.value = progress.toFixed(1);
189  yearPassedDays.value = daysPassed;
190};
191
192/**
193 * 更新月度进度
194 */
195const updateMonthProgress = () => {
196  const today = new Date();
197  const year = today.getFullYear();
198  const month = today.getMonth();
199  const startOfMonth = new Date(year, month, 1);
200  const endOfMonth = new Date(year, month + 1, 0);
201  const totalDays = getDaysDifference(startOfMonth, endOfMonth) + 1;
202  const daysPassed = today.getDate() - 1; // 当月已过天数(不含今天)
203  const daysLeft = totalDays - daysPassed - 1; // 剩余天数(含今天)
204  const progress = (daysPassed / totalDays) * 100;
205
206  monthDaysLeft.value = daysLeft;
207  monthProgress.value = progress.toFixed(1);
208  monthPassedDays.value = daysPassed;
209};
210
211/**
212 * 更新本周进度(已修复0%问题)
213 */
214const updateWeekProgress = () => {
215  const today = new Date();
216  let dayOfWeek = today.getDay() || 7; // 周日转为7,周一到周六为1-6
217  const daysPassed = dayOfWeek; // 已过天数(含今天:周一=1,周日=7)
218  const daysLeft = 7 - dayOfWeek; // 剩余天数
219
220  // 避免进度条完全消失(最小0.1)
221  const displayValue = daysPassed === 0 ? 0.1 : daysPassed;
222
223  weekDaysLeft.value = daysLeft;
224  weekProgress.value = ((daysPassed / 7) * 100).toFixed(1);
225  weekDisplayValue.value = displayValue;
226};
227
228/**
229 * 统一更新所有数据
230 */
231const updateAllData = () => {
232  updateSpringFestivalCountdown();
233  updateYearProgress();
234  updateMonthProgress();
235  updateWeekProgress();
236};
237
238// 页面加载时初始化,并设置每天凌晨更新
239onMounted(() => {
240  updateAllData();
241
242  // 计算距离次日凌晨0:00:01的毫秒数,设置定时器每天更新
243  const now = new Date();
244  const tomorrow = new Date(now);
245  tomorrow.setDate(now.getDate() + 1);
246  tomorrow.setHours(0, 0, 1, 0);
247  const timeToUpdate = tomorrow - now;
248
249  setInterval(updateAllData, timeToUpdate);
250});
251</script>
252
253<style scoped>
254.tk-page-card {
255  margin-top: 10px;
256}
257
258/* 进度条样式 */
259progress {
260  width: 100%;
261  height: 10px;
262  border-radius: 5px;
263  background-color: #f0f0f0;
264  border: none;
265}
266progress::-webkit-progress-bar {
267  background-color: #f0f0f0;
268  border-radius: 5px;
269}
270progress::-webkit-progress-value {
271  background-color: var(--vp-c-brand-1);
272  border-radius: 5px;
273  transition: width 0.3s ease;
274}
275
276/* 基础样式 */
277* {
278  margin: 0;
279  padding: 0;
280  box-sizing: border-box;
281  font-family: "Microsoft YaHei", sans-serif;
282}
283
284.item-headline span {
285  font-size: 18px;
286  font-weight: 600;
287  color: #333333;
288}
289
290/* 内容区域样式 */
291.item-content {
292  display: flex;
293  flex-wrap: wrap;
294  gap: 20px;
295}
296
297/* 左侧春节倒计时样式 */
298#schedule-area-left {
299  flex: 1;
300  min-width: 200px;
301  border-radius: 8px;
302  padding: 20px;
303  text-align: center;
304  border: 1px solid var(--vp-c-brand-1);
305}
306#schedule-title {
307  font-size: 16px;
308  color: var(--vp-c-brand-1);
309  margin-bottom: 10px;
310}
311#schedule-days {
312  font-size: 48px;
313  font-weight: 700;
314  color: var(--vp-c-brand-1);
315  margin: 10px 0;
316}
317#schedule-date {
318  font-size: 14px;
319  color: #666666;
320}
321
322/* 右侧进度区域样式 */
323#schedule-area-right {
324  flex: 2;
325  display: flex;
326  flex-direction: column;
327  gap: 16px;
328}
329.schedule-r0,
330.schedule-r1,
331.schedule-r2 {
332  display: flex;
333  flex-direction: column;
334  gap: 6px;
335}
336.schedule-d0 {
337  font-size: 14px;
338  color: #333333;
339  font-weight: 500;
340}
341.schedule-d1 {
342  display: flex;
343  align-items: center;
344  gap: 12px;
345}
346.progress-container {
347  width: 100%;
348  display: flex;
349  flex-direction: column;
350  gap: 4px;
351  flex: 2;
352}
353.aside-span1 {
354  font-size: 14px;
355  color: var(--vp-c-brand-1);
356  font-weight: 600;
357  width: 60px;
358  display: inline-block;
359}
360.aside-span2 {
361  font-size: 13px;
362  color: #666666;
363  flex: 1;
364}
365.aside-span2 a {
366  color: var(--vp-c-brand-1);
367  text-decoration: none;
368  font-weight: 600;
369}
370
371/* 响应式样式 */
372@media (max-width: 480px) {
373  .item-content {
374    flex-direction: column;
375  }
376  #schedule-days {
377    font-size: 36px;
378  }
379}
380</style>

(2)引入

编辑docs\.vitepress\theme\components\TeekLayoutProvider.vue文件:

 1import CalendarCard from "./CalendarCard.vue"; //日历卡片
 2import ScheduleCard from "./ScheduleCard.vue"; //节日倒计时卡片
 3
 4    <!-- 自定义公告卡片 公告-->
 5    <template #teek-home-card-top-article-before>
 6      <!-- <WelcomeCard /> -->
 7      <NoticeCard />
 8      <CalendarCard />
 9      <ScheduleCard />
10    </template>

具体代码可见如下源码:https://cnb.cool/onedayxyy/vitepress-theme-teek-one-public/-/blob/main/docs/.vitepress/theme/components/TeekLayoutProvider.vue

(3)验证

image-20251021142900347

结束。

最新文章

文档导航