更换到Astro #9
@@ -121,11 +121,62 @@ const recentPosts = posts.slice(0, 5);
|
|||||||
(() => {
|
(() => {
|
||||||
const dayNames = ['周日', '周一', '周二', '周三', '周四', '周五', '周六'];
|
const dayNames = ['周日', '周一', '周二', '周三', '周四', '周五', '周六'];
|
||||||
const pad = (value) => String(value).padStart(2, '0');
|
const pad = (value) => String(value).padStart(2, '0');
|
||||||
|
const dayMs = 86400000;
|
||||||
const percent = (used, total) => Math.min(100, Math.max(0, (used / total) * 100));
|
const percent = (used, total) => Math.min(100, Math.max(0, (used / total) * 100));
|
||||||
|
const lunarFormatter = new Intl.DateTimeFormat('zh-CN-u-ca-chinese', {
|
||||||
|
year: 'numeric',
|
||||||
|
month: 'long',
|
||||||
|
day: 'numeric',
|
||||||
|
});
|
||||||
|
const solarFestivals = {
|
||||||
|
'01-01': '元旦',
|
||||||
|
'03-08': '妇女节',
|
||||||
|
'03-12': '植树节',
|
||||||
|
'05-01': '劳动节',
|
||||||
|
'05-04': '青年节',
|
||||||
|
'06-01': '儿童节',
|
||||||
|
'07-01': '建党节',
|
||||||
|
'08-01': '建军节',
|
||||||
|
'09-10': '教师节',
|
||||||
|
'10-01': '国庆节',
|
||||||
|
};
|
||||||
|
const lunarFestivals = {
|
||||||
|
'正月-1': '春节',
|
||||||
|
'正月-15': '元宵',
|
||||||
|
'五月-5': '端午',
|
||||||
|
'七月-7': '七夕',
|
||||||
|
'七月-15': '中元',
|
||||||
|
'八月-15': '中秋',
|
||||||
|
'九月-9': '重阳',
|
||||||
|
'腊月-8': '腊八',
|
||||||
|
'腊月-23': '北方小年',
|
||||||
|
'腊月-24': '南方小年',
|
||||||
|
};
|
||||||
|
const springFestivalDates = [
|
||||||
|
'2024-02-10',
|
||||||
|
'2025-01-29',
|
||||||
|
'2026-02-17',
|
||||||
|
'2027-02-06',
|
||||||
|
'2028-01-26',
|
||||||
|
'2029-02-13',
|
||||||
|
'2030-02-03',
|
||||||
|
'2031-01-23',
|
||||||
|
'2032-02-11',
|
||||||
|
];
|
||||||
const setText = (id, value) => {
|
const setText = (id, value) => {
|
||||||
const el = document.getElementById(id);
|
const el = document.getElementById(id);
|
||||||
if (el) el.textContent = value;
|
if (el) el.textContent = value;
|
||||||
};
|
};
|
||||||
|
const toDateKey = (date) => `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}`;
|
||||||
|
const toMonthDayKey = (date) => `${pad(date.getMonth() + 1)}-${pad(date.getDate())}`;
|
||||||
|
const escapeHtml = (value) =>
|
||||||
|
String(value).replace(/[&<>"']/g, (char) => ({
|
||||||
|
'&': '&',
|
||||||
|
'<': '<',
|
||||||
|
'>': '>',
|
||||||
|
'"': '"',
|
||||||
|
"'": ''',
|
||||||
|
})[char]);
|
||||||
|
|
||||||
function dayOfYear(date) {
|
function dayOfYear(date) {
|
||||||
const start = new Date(date.getFullYear(), 0, 1);
|
const start = new Date(date.getFullYear(), 0, 1);
|
||||||
@@ -134,15 +185,55 @@ const recentPosts = posts.slice(0, 5);
|
|||||||
|
|
||||||
function weekOfYear(date) {
|
function weekOfYear(date) {
|
||||||
const firstDay = new Date(date.getFullYear(), 0, 1);
|
const firstDay = new Date(date.getFullYear(), 0, 1);
|
||||||
const pastDays = Math.floor((date - firstDay) / 86400000);
|
const pastDays = Math.floor((date - firstDay) / dayMs);
|
||||||
return Math.ceil((pastDays + firstDay.getDay() + 1) / 7);
|
return Math.ceil((pastDays + firstDay.getDay() + 1) / 7);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getLunarInfo(date) {
|
||||||
|
const parts = lunarFormatter.formatToParts(date);
|
||||||
|
const month = parts.find((part) => part.type === 'month')?.value || '';
|
||||||
|
const day = parts.find((part) => part.type === 'day')?.value || '';
|
||||||
|
const yearName = parts.find((part) => part.type === 'yearName')?.value || '';
|
||||||
|
return {
|
||||||
|
yearName,
|
||||||
|
month,
|
||||||
|
day,
|
||||||
|
dayName: day === '1' ? month : `${day}日`,
|
||||||
|
festival: lunarFestivals[`${month}-${day}`] || '',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function getFestival(date) {
|
||||||
|
const lunar = getLunarInfo(date);
|
||||||
|
const solar = solarFestivals[toMonthDayKey(date)] || '';
|
||||||
|
return solar || lunar.festival;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getNextNewYearEve(now) {
|
||||||
|
const today = new Date(now.getFullYear(), now.getMonth(), now.getDate());
|
||||||
|
const nextSpring = springFestivalDates
|
||||||
|
.map((date) => new Date(`${date}T00:00:00`))
|
||||||
|
.find((date) => date > today);
|
||||||
|
|
||||||
|
if (!nextSpring) {
|
||||||
|
return new Date(now.getFullYear() + 1, 1, 16);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Date(nextSpring.getFullYear(), nextSpring.getMonth(), nextSpring.getDate() - 1);
|
||||||
|
}
|
||||||
|
|
||||||
function renderCalendar(now) {
|
function renderCalendar(now) {
|
||||||
|
const todayLunar = getLunarInfo(now);
|
||||||
|
const todayFestival = getFestival(now);
|
||||||
setText('calendar-week', `第${weekOfYear(now)}周 ${dayNames[now.getDay()]}`);
|
setText('calendar-week', `第${weekOfYear(now)}周 ${dayNames[now.getDay()]}`);
|
||||||
setText('calendar-date', pad(now.getDate()));
|
setText('calendar-date', pad(now.getDate()));
|
||||||
setText('calendar-solar', `${now.getFullYear()}年${now.getMonth() + 1}月 第${dayOfYear(now)}天`);
|
setText('calendar-solar', `${now.getFullYear()}年${now.getMonth() + 1}月 第${dayOfYear(now)}天`);
|
||||||
setText('calendar-lunar', '万千风景,五历如一');
|
setText(
|
||||||
|
'calendar-lunar',
|
||||||
|
todayFestival
|
||||||
|
? `${todayLunar.yearName}年 ${todayFestival}`
|
||||||
|
: `${todayLunar.yearName}年 农历${todayLunar.month}${todayLunar.day}日`
|
||||||
|
);
|
||||||
|
|
||||||
const grid = document.getElementById('calendar-grid');
|
const grid = document.getElementById('calendar-grid');
|
||||||
if (!grid) return;
|
if (!grid) return;
|
||||||
@@ -162,8 +253,19 @@ const recentPosts = posts.slice(0, 5);
|
|||||||
: dayNumber < 1
|
: dayNumber < 1
|
||||||
? previousDays + dayNumber
|
? previousDays + dayNumber
|
||||||
: dayNumber - days;
|
: dayNumber - days;
|
||||||
|
const cellDate = new Date(year, month, dayNumber);
|
||||||
|
const lunar = getLunarInfo(cellDate);
|
||||||
const isToday = isCurrent && displayDay === now.getDate();
|
const isToday = isCurrent && displayDay === now.getDate();
|
||||||
cells.push(`<span class="${isCurrent ? '' : 'muted'} ${isToday ? 'today' : ''}">${displayDay}</span>`);
|
const title = `${toDateKey(cellDate)} 农历${lunar.month}${lunar.day}日`;
|
||||||
|
cells.push(`
|
||||||
|
<span
|
||||||
|
class="${isCurrent ? '' : 'muted'} ${isToday ? 'today' : ''}"
|
||||||
|
title="${escapeHtml(title)}"
|
||||||
|
aria-label="${escapeHtml(title)}"
|
||||||
|
>
|
||||||
|
<b>${displayDay}</b>
|
||||||
|
</span>
|
||||||
|
`);
|
||||||
}
|
}
|
||||||
|
|
||||||
grid.innerHTML = cells.join('');
|
grid.innerHTML = cells.join('');
|
||||||
@@ -171,8 +273,8 @@ const recentPosts = posts.slice(0, 5);
|
|||||||
|
|
||||||
function renderSchedule(now) {
|
function renderSchedule(now) {
|
||||||
const year = now.getFullYear();
|
const year = now.getFullYear();
|
||||||
const nextEve = new Date(year + 1, 1, 16);
|
const nextEve = getNextNewYearEve(now);
|
||||||
const daysToEve = Math.max(0, Math.ceil((nextEve - now) / 86400000));
|
const daysToEve = Math.max(0, Math.ceil((nextEve - now) / dayMs));
|
||||||
const yearStart = new Date(year, 0, 1);
|
const yearStart = new Date(year, 0, 1);
|
||||||
const yearEnd = new Date(year + 1, 0, 1);
|
const yearEnd = new Date(year + 1, 0, 1);
|
||||||
const monthStart = new Date(year, now.getMonth(), 1);
|
const monthStart = new Date(year, now.getMonth(), 1);
|
||||||
@@ -192,10 +294,15 @@ const recentPosts = posts.slice(0, 5);
|
|||||||
rows.forEach(([name, start, end]) => {
|
rows.forEach(([name, start, end]) => {
|
||||||
const total = end - start;
|
const total = end - start;
|
||||||
const used = now - start;
|
const used = now - start;
|
||||||
const left = Math.max(0, Math.ceil((end - now) / 86400000));
|
const progressValue = percent(used, total);
|
||||||
|
const left = Math.max(0, Math.ceil((end - now) / dayMs));
|
||||||
const bar = document.getElementById(`${name}-progress`);
|
const bar = document.getElementById(`${name}-progress`);
|
||||||
if (bar) bar.value = percent(used, total);
|
if (bar) {
|
||||||
setText(`${name}-left`, `还剩${left}天`);
|
bar.value = progressValue;
|
||||||
|
bar.title = `已过 ${progressValue.toFixed(1)}%`;
|
||||||
|
bar.setAttribute('aria-valuetext', `已过 ${progressValue.toFixed(1)}%`);
|
||||||
|
}
|
||||||
|
setText(`${name}-left`, `${progressValue.toFixed(0)}% / ${left}天`);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+24
-10
@@ -800,17 +800,24 @@ a {
|
|||||||
.calendar-grid {
|
.calendar-grid {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(7, minmax(0, 1fr));
|
grid-template-columns: repeat(7, minmax(0, 1fr));
|
||||||
gap: 5px 7px;
|
gap: 4px 7px;
|
||||||
color: #8aa6be;
|
color: #8aa6be;
|
||||||
font-size: 0.78rem;
|
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.calendar-grid span {
|
.calendar-grid span {
|
||||||
display: grid;
|
display: grid;
|
||||||
place-items: center;
|
place-items: center;
|
||||||
min-height: 20px;
|
min-height: 22px;
|
||||||
|
padding: 0;
|
||||||
|
border: 1px solid transparent;
|
||||||
border-radius: 999px;
|
border-radius: 999px;
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.calendar-grid b {
|
||||||
|
font-size: 0.78rem;
|
||||||
|
font-weight: 700;
|
||||||
}
|
}
|
||||||
|
|
||||||
.calendar-grid .muted {
|
.calendar-grid .muted {
|
||||||
@@ -820,6 +827,7 @@ a {
|
|||||||
.calendar-grid .today {
|
.calendar-grid .today {
|
||||||
color: #fff;
|
color: #fff;
|
||||||
background: #66c8f5;
|
background: #66c8f5;
|
||||||
|
border-color: #66c8f5;
|
||||||
}
|
}
|
||||||
|
|
||||||
.schedule-bars {
|
.schedule-bars {
|
||||||
@@ -829,7 +837,7 @@ a {
|
|||||||
|
|
||||||
.schedule-row {
|
.schedule-row {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 34px 1fr 64px;
|
grid-template-columns: 34px minmax(52px, 1fr) 74px;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
color: var(--muted);
|
color: var(--muted);
|
||||||
@@ -838,27 +846,33 @@ a {
|
|||||||
|
|
||||||
.schedule-row progress {
|
.schedule-row progress {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 18px;
|
height: 10px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
border: 0;
|
border: 0;
|
||||||
border-radius: 4px;
|
border-radius: 999px;
|
||||||
background: rgba(102, 200, 245, 0.18);
|
background: rgba(102, 200, 245, 0.16);
|
||||||
}
|
}
|
||||||
|
|
||||||
.schedule-row progress::-webkit-progress-bar {
|
.schedule-row progress::-webkit-progress-bar {
|
||||||
background: rgba(102, 200, 245, 0.18);
|
border-radius: 999px;
|
||||||
|
background: rgba(102, 200, 245, 0.16);
|
||||||
}
|
}
|
||||||
|
|
||||||
.schedule-row progress::-webkit-progress-value {
|
.schedule-row progress::-webkit-progress-value {
|
||||||
background: rgba(102, 200, 245, 0.58);
|
border-radius: 999px;
|
||||||
|
background: linear-gradient(90deg, #5ec7f4, #42d4c4);
|
||||||
}
|
}
|
||||||
|
|
||||||
.schedule-row progress::-moz-progress-bar {
|
.schedule-row progress::-moz-progress-bar {
|
||||||
background: rgba(102, 200, 245, 0.58);
|
border-radius: 999px;
|
||||||
|
background: linear-gradient(90deg, #5ec7f4, #42d4c4);
|
||||||
}
|
}
|
||||||
|
|
||||||
.schedule-row em {
|
.schedule-row em {
|
||||||
|
min-width: 0;
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
|
text-align: right;
|
||||||
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sidebar-posts {
|
.sidebar-posts {
|
||||||
|
|||||||
Reference in New Issue
Block a user