惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

T
The Blog of Author Tim Ferriss
罗磊的独立博客
月光博客
月光博客
GbyAI
GbyAI
腾讯CDC
G
Google Developers Blog
H
Hackread – Cybersecurity News, Data Breaches, AI and More
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
U
Unit 42
Engineering at Meta
Engineering at Meta
A
About on SuperTechFans
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
aimingoo的专栏
aimingoo的专栏
C
Check Point Blog
Y
Y Combinator Blog
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
Microsoft Azure Blog
Microsoft Azure Blog
I
InfoQ
雷峰网
雷峰网
B
Blog RSS Feed
美团技术团队
M
MIT News - Artificial intelligence
有赞技术团队
有赞技术团队
D
Docker

Hyde Blog

Hyde Blog Hyde Blog Hyde Blog Hyde Blog Hyde Blog Hyde Blog Hyde Blog Hyde Blog Hyde Blog Hyde Blog Hyde Blog Hyde Blog Hyde Blog Hyde Blog Hyde Blog Hyde Blog Hyde Blog Hyde Blog Hyde Blog Hyde Blog Hyde Blog Hyde Blog Hyde Blog Hyde Blog Hyde Blog Hyde Blog Hyde Blog Hyde Blog Hyde Blog Hyde Blog
Hyde Blog
Hyde · 2026-05-19 · via Hyde Blog

astro

<!-- 时间进度组件 -->
---
import WidgetLayout from "@/components/common/WidgetLayout.astro";

interface Props {
	class?: string;
	style?: string;
}

const { class: className, style } = Astro.props;
---

<WidgetLayout id="schedule-widget" class={className} style={style}>
  <div class="schedule-container" data-schedule-id="schedule-${Math.random().toString(36).substr(2, 9)}">
    <!-- 进度区域 -->
    <div class="progress-section space-y-4">
      <!-- 本年进度 -->
      <div class="progress-item flex items-center gap-3">
        <span class="text-[14px] font-semibold text-(--primary) shrink-0 w-[60px] year-progress">--%</span>
        <div class="flex-1 min-w-0">
          <span class="text-sm text-neutral-700 dark:text-neutral-300 year-days-left">本年还剩 -- 天</span>
          <progress max="365" class="schedule-progress-bar pBar_year" value="0"></progress>
        </div>
      </div>

      <!-- 本月进度 -->
      <div class="progress-item flex items-center gap-3">
        <span class="text-[14px] font-semibold text-(--primary) shrink-0 w-[60px] month-progress">--%</span>
        <div class="flex-1 min-w-0">
          <span class="text-sm text-neutral-700 dark:text-neutral-300 month-days-left">本月还剩 -- 天</span>
          <progress max="31" class="schedule-progress-bar pBar_month" value="0"></progress>
        </div>
      </div>

      <!-- 本周进度 -->
      <div class="progress-item flex items-center gap-3">
        <span class="text-[14px] font-semibold text-(--primary) shrink-0 w-[60px] week-progress">--%</span>
        <div class="flex-1 min-w-0">
          <span class="text-sm text-neutral-700 dark:text-neutral-300 week-days-left">本周还剩 -- 天</span>
          <progress max="7" class="schedule-progress-bar pBar_week" value="0"></progress>
        </div>
      </div>
    </div>

    <!-- 节假日倒计时区域 -->
    <div class="holiday-section mt-4 pt-4 border-t border-neutral-200 dark:border-neutral-700">
      <div class="text-center">
        <p class="text-base font-semibold text-neutral-700 dark:text-neutral-300 mb-0 nearest-holiday-full">距离 --</p>
        <p class="text-4xl font-bold text-(--primary) mb-2 nearest-holiday-days">--</p>
        <p class="text-xs text-neutral-500 dark:text-neutral-400 nearest-holiday-date">--</p>
      </div>
    </div>
  </div>
</WidgetLayout>

<script is:inline>
(function () {
  // 春节日期数据(1900-2100年)
  const springFestivalData = [
    [2,19],[2,8],[1,28],[2,16],[2,5],[1,25],[2,13],[2,2],[1,22],[2,10],
    [1,30],[2,18],[2,7],[1,26],[2,14],[2,3],[1,23],[2,11],[1,31],[2,19],
    [2,8],[1,28],[2,16],[2,5],[1,24],[2,12],[2,1],[1,21],[2,9],[1,28],
    [2,16],[2,5],[1,24],[2,12],[2,1],[1,21],[2,9],[1,29],[2,17],[2,6],
    [1,26],[2,14],[2,2],[1,22],[2,10],[1,29],[2,17],[2,6],[1,26],[2,13],
    [2,2],[1,22],[2,10],[1,30],[2,17],[2,6],[1,25],[2,13],[2,1],[1,21],
    [2,8],[1,28],[2,15],[2,5],[1,24],[2,12],[1,31],[2,18],[2,7],[1,27],
    [2,15],[2,3],[1,23],[2,11],[1,31],[2,18],[2,6],[1,26],[2,14],[2,3],
    [1,23],[2,10],[1,29],[2,16],[2,5],[1,24],[2,12],[2,1],[1,22],[2,9],
    [1,28],[2,15],[2,4],[1,23],[2,10],[1,30],[2,17],[2,6],[1,26],[2,14],
    [2,2],[1,22],[2,10],[1,29],[2,17],[2,5],[1,24],[2,12],[1,31],[2,18],
    [2,7],[1,26],[2,14],[2,3],[1,23],[2,10],[1,31],[2,18],[2,7],[1,26],
    [2,12],[2,1],[1,22],[2,10],[1,29],[2,17],[2,17],[1,24],[2,12],[2,1],
    [1,22],[2,10],[1,29],[2,17],[2,6],[1,26],[2,14],[2,3],[1,23],[2,10],
    [1,30],[2,17],[2,6],[1,26],[2,13],[2,2],[1,22],[2,10],[1,29],[2,17],
    [2,5],[1,25],[2,13],[2,1],[1,21],[2,9],[1,28],[2,16],[2,5],[1,24],
    [2,12],[2,1],[1,21],[2,9],[1,28],[2,15],[2,4],[1,24],[2,11],[1,31],
    [2,18],[2,7],[1,27],[2,15],[2,3],[1,23],[2,10],[1,30],[2,17],[2,6],
    [1,25],[2,13],[2,2],[1,22],[2,10],[1,29],[2,17],[2,5],[1,25],[2,13],
    [2,1],[1,21],[2,9],[1,29],[2,17],[2,6],[1,26],[2,14],[2,3],[1,23],
    [2,11],[1,30],[2,18],[2,7],[1,27],[2,15],[2,4],[1,24],[2,12],[2,1],
    [1,21],[2,9],[1,28],[2,16],[2,5],[1,24],[2,12],[2,1],[1,21],[2,9]
  ];

  // 端午节日期数据(农历五月初五,1900-2100年公历日期)
  const dragonBoatData = [
    [6,9],[5,30],[6,18],[6,7],[5,28],[6,15],[6,4],[5,24],[6,12],[6,1],
    [5,22],[6,9],[5,30],[6,17],[6,5],[5,25],[6,12],[6,1],[5,22],[6,10],
    [5,30],[6,17],[6,6],[5,27],[6,14],[6,3],[5,23],[6,11],[5,31],[6,18],
    [6,7],[5,28],[6,16],[6,5],[5,26],[6,13],[6,2],[5,23],[6,10],[5,30],
    [6,18],[6,6],[5,27],[6,14],[6,3],[5,24],[6,11],[5,31],[6,19],[6,8],
    [5,29],[6,16],[6,5],[5,26],[6,13],[6,2],[5,22],[6,10],[5,30],[6,17],
    [6,5],[5,26],[6,14],[6,3],[5,24],[6,11],[5,31],[6,19],[6,8],[5,29],
    [6,16],[6,5],[5,26],[6,14],[6,2],[5,22],[6,10],[5,30],[6,17],[6,6],
    [5,27],[6,15],[6,4],[5,24],[6,12],[6,1],[5,22],[6,9],[5,29],[6,17],
    [6,6],[5,27],[6,15],[6,3],[5,24],[6,11],[5,31],[6,19],[6,8],[5,29],
    [6,16],[6,5],[5,25],[6,13],[6,2],[5,22],[6,10],[5,30],[6,18],[6,7],
    [5,28],[6,15],[6,4],[5,24],[6,12],[6,1],[5,21],[6,9],[5,29],[6,17],
    [6,6],[5,27],[6,14],[6,3],[5,23],[6,11],[6,19],[6,18],[6,7],[5,28],
    [6,15],[6,4],[5,25],[6,13],[6,2],[5,22],[6,10],[5,30],[6,17],[6,6],
    [5,27],[6,15],[6,4],[5,24],[6,12],[6,1],[5,22],[6,10],[5,30],[6,18],
    [6,7],[5,28],[6,16],[6,5],[5,25],[6,13],[6,2],[5,23],[6,11],[5,31],
    [6,19],[6,8],[5,29],[6,16],[6,5],[5,26],[6,14],[6,3],[5,24],[6,12],
    [6,1],[5,22],[6,10],[5,30],[6,17],[6,6],[5,27],[6,15],[6,4],[5,24],
    [6,12],[6,1],[5,21],[6,9],[5,29],[6,17],[6,6],[5,26],[6,14],[6,3],
    [5,24],[6,12],[6,1],[5,22],[6,10],[5,30],[6,18],[6,7],[5,28],[6,16],
    [6,5],[5,26],[6,13],[6,2],[5,22],[6,10],[5,30],[6,17],[6,5],[5,26],
    [6,14],[6,3],[5,24],[6,12],[6,1],[5,22],[6,10],[5,29],[6,17],[6,6]
  ];

  const getSpringFestivalDate = (lunarYear) => {
    const index = lunarYear - 1900;
    if (index >= 0 && index < springFestivalData.length) {
      const [month, day] = springFestivalData[index];
      return new Date(lunarYear, month - 1, day);
    }
    return new Date(lunarYear, 1, 1);
  };

  const getDragonBoatDate = (year) => {
    // 端午节是农历五月初五,使用精确的公历日期映射
    const dragonBoatMap = {
      2020: [6, 25], 2021: [6, 14], 2022: [6, 3], 2023: [6, 22], 2024: [6, 10],
      2025: [5, 31], 2026: [6, 19], 2027: [6, 8], 2028: [5, 28], 2029: [6, 16],
      2030: [6, 5], 2031: [5, 25], 2032: [6, 12], 2033: [6, 1], 2034: [6, 20]
    };
    if (dragonBoatMap[year]) {
      const [month, day] = dragonBoatMap[year];
      return new Date(year, month - 1, day);
    }
    // 默认回退到农历五月初五的近似日期
    return new Date(year, 4, 22);
  };

  const getHolidays = () => {
    const now = new Date();
    const year = now.getFullYear();
    
    return [
      { name: '元旦', date: new Date(year, 0, 1) },
      { name: '春节', date: getSpringFestivalDate(year) },
      { name: '清明', date: new Date(year, 3, 4) },
      { name: '劳动节', date: new Date(year, 4, 1) },
      { name: '端午', date: getDragonBoatDate(year) },
      { name: '中秋', date: new Date(year, 8, 15) },
      { name: '国庆', date: new Date(year, 9, 1) },
      { name: '元旦', date: new Date(year + 1, 0, 1) }
    ];
  };

  const calculateProgress = () => {
    const now = new Date();
    const year = now.getFullYear();
    const month = now.getMonth();
    const day = now.getDate();
    const weekDay = now.getDay();

    // 本年进度
    const isLeapYear = (year % 4 === 0 && year % 100 !== 0) || year % 400 === 0;
    const daysInYear = isLeapYear ? 366 : 365;
    const startOfYear = new Date(year, 0, 0);
    const yearPassedDays = Math.floor((now - startOfYear) / 86400000);
    const yearDaysLeft = daysInYear - yearPassedDays;
    const yearProgress = ((yearPassedDays / daysInYear) * 100).toFixed(1);

    // 本月进度
    const daysInMonth = new Date(year, month + 1, 0).getDate();
    const monthPassedDays = day;
    const monthDaysLeft = daysInMonth - day;
    const monthProgress = ((monthPassedDays / daysInMonth) * 100).toFixed(1);

    // 本周进度
    const weekPassedDays = weekDay === 0 ? 7 : weekDay;
    const weekDaysLeft = 7 - weekPassedDays;
    const weekProgress = ((weekPassedDays / 7) * 100).toFixed(1);

    // 最近节假日
    const holidays = getHolidays();
    let nearestHoliday = { name: '元旦', days: '--', date: '--' };
    
    for (const holiday of holidays) {
      const diffTime = holiday.date - now;
      if (diffTime >= 0) {
        const days = Math.ceil(diffTime / (1000 * 60 * 60 * 24));
        const year = holiday.date.getFullYear();
        const month = String(holiday.date.getMonth() + 1).padStart(2, '0');
        const day = String(holiday.date.getDate()).padStart(2, '0');
        nearestHoliday = {
          name: holiday.name,
          days: days === 0 ? '今天' : days,
          date: `${year}-${month}-${day}`
        };
        break;
      }
    }

    // 更新所有 Schedule 组件实例
    const containers = document.querySelectorAll('.schedule-container');
    containers.forEach(container => {
      // 更新进度条
      const pBarYear = container.querySelector('.pBar_year');
      const pBarMonth = container.querySelector('.pBar_month');
      const pBarWeek = container.querySelector('.pBar_week');
      
      if (pBarYear) pBarYear.value = yearPassedDays;
      if (pBarMonth) pBarMonth.value = monthPassedDays;
      if (pBarWeek) pBarWeek.value = weekPassedDays;
      
      // 更新进度百分比显示
      const yearProgressEl = container.querySelector('.year-progress');
      const monthProgressEl = container.querySelector('.month-progress');
      const weekProgressEl = container.querySelector('.week-progress');
      
      const yearDaysLeftEl = container.querySelector('.year-days-left');
      const monthDaysLeftEl = container.querySelector('.month-days-left');
      const weekDaysLeftEl = container.querySelector('.week-days-left');

      if (yearProgressEl) yearProgressEl.textContent = yearProgress + '%';
      if (monthProgressEl) monthProgressEl.textContent = monthProgress + '%';
      if (weekProgressEl) weekProgressEl.textContent = weekProgress + '%';
      
      if (yearDaysLeftEl) yearDaysLeftEl.textContent = `本年还剩 ${yearDaysLeft} 天`;
      if (monthDaysLeftEl) monthDaysLeftEl.textContent = `本月还剩 ${monthDaysLeft} 天`;
      if (weekDaysLeftEl) weekDaysLeftEl.textContent = `本周还剩 ${weekDaysLeft} 天`;
      
      const holidayFull = container.querySelector('.nearest-holiday-full');
      const holidayDays = container.querySelector('.nearest-holiday-days');
      const holidayDate = container.querySelector('.nearest-holiday-date');
      
      if (holidayFull) holidayFull.textContent = `距离${nearestHoliday.name}节`;
      if (holidayDays) holidayDays.textContent = nearestHoliday.days;
      if (holidayDate) holidayDate.textContent = nearestHoliday.date;
    });
  };

  // 轮询检查DOM元素是否存在
  const waitForElement = (callback, maxAttempts = 50, interval = 100) => {
    let attempts = 0;
    const check = () => {
      // 检查关键DOM元素是否存在(使用类选择器)
      if (document.querySelector('.schedule-container')) {
        callback();
      } else if (attempts < maxAttempts) {
        attempts++;
        setTimeout(check, interval);
      }
    };
    check();
  };

  // 初始化
  const init = () => {
    calculateProgress();

    // 每天更新
    const now = new Date();
    const tomorrow = new Date(now.getFullYear(), now.getMonth(), now.getDate() + 1, 0, 0, 0, 0);
    dayTimer = setTimeout(function updateDaily() {
      calculateProgress();
      const nextDay = new Date();
      nextDay.setDate(nextDay.getDate() + 1);
      nextDay.setHours(0, 0, 0, 0);
      dayTimer = setTimeout(updateDaily, nextDay - new Date());
    }, tomorrow - now);
  };

  // 使用轮询等待DOM元素就绪
  waitForElement(init);

  // 清理
  const cleanup = () => {
    clearTimeout(dayTimer);
  };

  if (window) {
    window.addEventListener('pagehide', cleanup);
  }
})();
</script>

<style>
  .schedule-container {
    padding: 0;
  }

  .progress-section {
    padding: 0;
  }

  .progress-item {
    padding: 0;
  }

  /* 进度条样式 */
  .schedule-progress-bar {
    -webkit-appearance: none;
    appearance: none;
    border: none;
    width: 100%;
    height: 10px;
    border-radius: 5px;
    background-color: #f0f0f0;
    margin-top: 4px;
  }

  .schedule-progress-bar::-webkit-progress-bar {
    background-color: #f0f0f0;
    border-radius: 5px;
  }

  .schedule-progress-bar::-webkit-progress-value {
    background: var(--primary);
    border-radius: 5px;
    transition: width 0.3s ease;
  }

  .schedule-progress-bar::-moz-progress-bar {
    background: var(--primary);
    border-radius: 5px;
  }

  /* 暗色主题 */
  .dark .schedule-progress-bar {
    background-color: #374151;
  }

  .dark .schedule-progress-bar::-webkit-progress-bar {
    background-color: #374151;
  }
</style>

astro

---
import Schedule from "@/components/widget/Schedule.astro";

// 组件映射表 - 核心注册机制
const componentMap = {
    <!-- 其他组件 -->
    schedule: Schedule,  // ← 注册 Schedule 组件
} satisfies Record<WidgetComponentType, typeof Profile>;
---