const CountdownTimer = (() => { const holidays = [ { name: "元旦", date: (year) => new Date(year, 0, 1) }, { name: "春节", date: getChineseNewYear }, // 需要农历转换 { name: "清明节", date: getQingmingFestival }, { name: "五一", date: (year) => new Date(year, 4, 1) }, { name: "端午节", date: getDragonBoatFestival }, { name: "中秋", date: getMidAutumnFestival }, { name: "国庆节", date: (year) => new Date(year, 9, 1) } ]; function getChineseNewYear(year = new Date().getFullYear()) { // 示例:使用农历转换库获取春节日期(可替换为你的算法或数据表) // 这里是硬编码示例年份 const lunarNewYears = { 2024: '2024-02-10', 2025: '2025-01-29', 2026: '2026-02-17', 2027: '2027-02-06' }; return new Date(lunarNewYears[year] || lunarNewYears[year + 1]); } function getQingmingFestival(year = new Date().getFullYear()) { // 清明节一般为4月4日或5日 return new Date(year, 3, 4); // 公历 4 月 4 日 } function getDragonBoatFestival(year = new Date().getFullYear()) { const lunar = { 2024: '2024-06-10', 2025: '2025-05-31', 2026: '2026-06-20' }; return new Date(lunar[year] || lunar[year + 1]); } function getMidAutumnFestival(year = new Date().getFullYear()) { const lunar = { 2024: '2024-09-17', 2025: '2025-10-06', 2026: '2026-09-26' }; return new Date(lunar[year] || lunar[year + 1]); } function findNearestHoliday() { const now = new Date(); let nearest = null; let minDiff = Infinity; for (let holiday of holidays) { let date = holiday.date(now.getFullYear()); if (date < now) { date = holiday.date(now.getFullYear() + 1); } const diff = date - now; if (diff < minDiff) { minDiff = diff; nearest = { name: holiday.name, date }; } } return nearest; } const config = { targetName: "节日", units: { day: { text: "今日", unit: "小时" }, week: { text: "本周", unit: "天" }, month: { text: "本月", unit: "天" }, year: { text: "本年", unit: "天" } } }; const calculators = { day: () => { const hours = new Date().getHours(); return { remaining: 24 - hours, percentage: (hours / 24) * 100 }; }, week: () => { const day = new Date().getDay(); const passed = day === 0 ? 6 : day - 1; return { remaining: 6 - passed, percentage: ((passed + 1) / 7) * 100 }; }, month: () => { const now = new Date(); const total = new Date(now.getFullYear(), now.getMonth() + 1, 0).getDate(); const passed = now.getDate() - 1; return { remaining: total - passed, percentage: (passed / total) * 100 }; }, year: () => { const now = new Date(); const start = new Date(now.getFullYear(), 0, 1); const total = 365 + (now.getFullYear() % 4 === 0 ? 1 : 0); const passed = Math.floor((now - start) / 86400000); return { remaining: total - passed, percentage: (passed / total) * 100 }; } }; function updateCountdown() { const elements = ['eventName', 'eventDate', 'daysUntil', 'countRight'] .map(id => document.getElementById(id)); if (elements.some(el => !el)) return; const [eventName, eventDate, daysUntil, countRight] = elements; const now = new Date(); const { name, date } = findNearestHoliday(); // 获取最近节日 eventName.textContent = name; eventDate.textContent = date.toISOString().split('T')[0]; daysUntil.textContent = Math.ceil((date - new Date(now.getFullYear(), now.getMonth(), now.getDate())) / 86400000); countRight.innerHTML = Object.entries(config.units) .map(([key, { text, unit }]) => { const { remaining, percentage } = calculators[key](); return `
${text}
${percentage.toFixed(2)}% 还剩${remaining}${unit}
`; }).join(''); } function injectStyles() { const styles = ` .item-content { display: flex; } .cd-count-left { position: relative; display: flex; flex-direction: column; margin-right: 0.8rem; line-height: 1.5; align-items: center; justify-content: center; } .cd-count-left .cd-text { font-size: 14px; } .cd-count-left .cd-name { font-weight: bold; font-size: 18px; } .cd-count-left .cd-time { font-size: 30px; font-weight: bold; color: var(--heo-main); } .cd-count-left .cd-date { font-size: 12px; opacity: 0.6; } .cd-count-left::after { content: ""; position: absolute; right: -0.8rem; width: 2px; height: 80%; background-color: var(--heo-main); opacity: 0.5; } .cd-count-right { flex: 1; margin-left: .8rem; display: flex; flex-direction: column; justify-content: space-between; } .cd-count-item { display: flex; flex-direction: row; align-items: center; height: 24px; } .cd-item-name { font-size: 14px; margin-right: 0.8rem; white-space: nowrap; } .cd-item-progress { position: relative; display: flex; flex-direction: row; align-items: center; justify-content: space-between; height: 100%; width: 100%; border-radius: 8px; background-color: var(--heo-background); overflow: hidden; } .cd-progress-bar { height: 100%; border-radius: 8px; background-color: var(--heo-main); } .cd-percentage, .cd-remaining { position: absolute; font-size: 12px; margin: 0 6px; transition: opacity 0.3s ease-in-out, transform 0.3s ease-in-out; } .cd-many { color: #fff; } .cd-remaining { opacity: 0; transform: translateX(10px); } .item-content:hover .cd-remaining { transform: translateX(0); opacity: 1; } .item-content:hover .cd-percentage { transform: translateX(-10px); opacity: 0; } `; const styleSheet = document.createElement("style"); styleSheet.textContent = styles; document.head.appendChild(styleSheet); } let timer; const start = () => { injectStyles(); updateCountdown(); timer = setInterval(updateCountdown, 600000); // 每10分钟刷新一次 }; ['pjax:complete', 'DOMContentLoaded'].forEach(event => document.addEventListener(event, start)); document.addEventListener('pjax:send', () => timer && clearInterval(timer)); return { start, stop: () => timer && clearInterval(timer) }; })();