/** * 花信 · 北京花期追踪 * 应用逻辑:数据加载、渲染、分享 */ // ========== 节气与花信数据 ========== // 二十四节气(简化版,固定日期) const SOLAR_TERMS = [ { name: '小寒', month: 1, day: 5, flower: '梅花', signal: '雁北乡' }, { name: '大寒', month: 1, day: 20, flower: '山茶', signal: '鸡始乳' }, { name: '立春', month: 2, day: 4, flower: '水仙', signal: '东风解冻' }, { name: '雨水', month: 2, day: 19, flower: '李花', signal: '獭祭鱼' }, { name: '惊蛰', month: 3, day: 6, flower: '桃花', signal: '桃始华' }, { name: '春分', month: 3, day: 21, flower: '海棠', signal: '玄鸟至' }, { name: '清明', month: 4, day: 5, flower: '桐花', signal: '桐始华' }, { name: '谷雨', month: 4, day: 20, flower: '牡丹', signal: '萍始生' }, { name: '立夏', month: 5, day: 6, flower: '芍药', signal: '蝼蝈鸣' }, { name: '小满', month: 5, day: 21, flower: '石榴', signal: '苦菜秀' }, { name: '芒种', month: 6, day: 6, flower: '萱草', signal: '螳螂生' }, { name: '夏至', month: 6, day: 21, flower: '荷花', signal: '鹿角解' }, { name: '小暑', month: 7, day: 7, flower: '茉莉', signal: '温风至' }, { name: '大暑', month: 7, day: 23, flower: '凤仙', signal: '腐草为萤' }, { name: '立秋', month: 8, day: 8, flower: '牵牛', signal: '凉风至' }, { name: '处暑', month: 8, day: 23, flower: '桂花', signal: '鹰乃祭鸟' }, { name: '白露', month: 9, day: 8, flower: '菊花', signal: '鸿雁来' }, { name: '秋分', month: 9, day: 23, flower: '海棠', signal: '雷始收声' }, { name: '寒露', month: 10, day: 8, flower: '木槿', signal: '鸿雁来宾' }, { name: '霜降', month: 10, day: 24, flower: '芙蓉', signal: '豺乃祭兽' }, { name: '立冬', month: 11, day: 8, flower: '芦花', signal: '水始冰' }, { name: '小雪', month: 11, day: 22, flower: '茶花', signal: '虹藏不见' }, { name: '大雪', month: 12, day: 7, flower: '腊梅', signal: '鹖鴠不鸣' }, { name: '冬至', month: 12, day: 22, flower: '瑞香', signal: '蚯蚓结' }, ]; // 天干地支 const TIANGAN = ['甲', '乙', '丙', '丁', '戊', '己', '庚', '辛', '壬', '癸']; const DIZHI = ['子', '丑', '寅', '卯', '辰', '巳', '午', '未', '申', '酉', '戌', '亥']; // 中文数字 const CHINESE_NUMBERS = ['〇', '一', '二', '三', '四', '五', '六', '七', '八', '九']; // ========== 工具函数 ========== /** * 根据日期获取当前节气 */ function getSolarTerm(date) { const year = date.getFullYear(); const month = date.getMonth() + 1; const day = date.getDate(); // 找到当前日期所在的节气区间 let currentTerm = SOLAR_TERMS[0]; for (let i = SOLAR_TERMS.length - 1; i >= 0; i--) { const term = SOLAR_TERMS[i]; const termDate = new Date(year, term.month - 1, term.day); if (date >= termDate) { currentTerm = term; break; } } return currentTerm; } /** * 获取下一个节气 */ function getNextSolarTerm(date) { const year = date.getFullYear(); const currentTerm = getSolarTerm(date); const currentIndex = SOLAR_TERMS.findIndex(t => t.name === currentTerm.name); const nextIndex = (currentIndex + 1) % SOLAR_TERMS.length; return SOLAR_TERMS[nextIndex]; } /** * 计算干支纪年 */ function getGanzhiYear(year) { // 1984年是甲子年(60年周期的起点) const offset = (year - 1984) % 60; const ganIndex = offset % 10; const zhiIndex = offset % 12; return TIANGAN[ganIndex >= 0 ? ganIndex : ganIndex + 10] + DIZHI[zhiIndex >= 0 ? zhiIndex : zhiIndex + 12]; } /** * 格式化中文日期 */ function formatChineseDate(date) { const year = date.getFullYear(); const month = date.getMonth() + 1; const yearStr = year.toString().split('').map(d => CHINESE_NUMBERS[parseInt(d)]).join(''); const monthStr = CHINESE_NUMBERS[month]; return `${yearStr} · ${monthStr}月`; } /** * 获取时间段副标题 */ function getTimeSubtitle(locations) { // 根据即将盛开地点的 best_time 推断 const upcoming = locations.filter(l => l.status === '初开'); if (upcoming.length > 0) { const bestTime = upcoming[0].best_time; if (bestTime.includes('周末')) return '本周末'; if (bestTime.includes('下周')) return '下周'; if (bestTime.includes('月底')) return '本月底'; } return '本周'; } /** * 获取未来花期时间段 */ function getFutureTimeRange(locations) { const future = locations.filter(l => l.status === '未开'); if (future.length > 0) { return future[0].best_time || '待定'; } return '3月底至4月'; } // ========== 数据处理 ========== /** * 从JSON加载花期数据 */ async function loadData() { try { const response = await fetch('/output/latest/report.json'); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } return await response.json(); } catch (error) { console.error('Failed to load data:', error); return null; } } /** * 将地点分类到三段 */ function classifyLocations(locations) { const blooming = []; const upcoming = []; const future = []; // 按 hotness 排序 const sorted = [...locations].sort((a, b) => b.hotness - a.hotness); for (const loc of sorted) { if (loc.status === '盛开' || loc.bloom_percent >= 60) { blooming.push(loc); } else if (loc.status === '初开' || (loc.bloom_percent >= 20 && loc.bloom_percent < 60)) { upcoming.push(loc); } else { future.push(loc); } } return { blooming, upcoming, future }; } /** * 预估开放度 */ function estimateBloom(location) { const current = location.bloom_percent; if (location.best_time.includes('周末') || location.best_time.includes('本周')) { return Math.min(current + 25, 85); } if (location.best_time.includes('下周')) { return Math.min(current + 35, 90); } return Math.min(current + 20, 80); } /** * 格式化开放度显示 */ function formatBloomPercent(location, type) { if (type === 'upcoming') { const estimated = estimateBloom(location); return `${location.bloom_percent}% → ${estimated}%`; } return `${location.bloom_percent}%`; } // ========== 渲染函数 ========== /** * 渲染主视觉区域 */ function renderHeader(data) { const date = new Date(data.report_date); const solarTerm = getSolarTerm(date); document.getElementById('season-date').textContent = formatChineseDate(date); document.getElementById('season-name').innerHTML = ` ${solarTerm.name} · ${solarTerm.signal} `; } /** * 渲染单个地点卡片 */ function renderLocationCard(location, type, topNotes, statusChanges) { // 查找热门笔记 const note = topNotes.find(n => n.title.includes(location.flower) || n.title.includes(location.name.split(/[·\/]/)[0])); // 查找状态变化 const change = statusChanges.find(c => c.location === location.name); if (type === 'blooming') { return `
${location.name} ${location.bloom_percent}%
${location.flower} · ${location.summary || '正值最佳观赏期'}
${note ? `
↑ ${note.liked_count}+ 赞
` : ''} ${change ? `
↑ 从昨日 ${change.old_status.match(/\d+/)?.[0] || ''}%
` : ''}
`; } if (type === 'upcoming') { const estimated = estimateBloom(location); return `
${location.name} ${location.bloom_percent}% → ${estimated}%
${location.flower} · ${location.summary || location.best_time + '最佳'}
`; } // future 类型不需要卡片 return ''; } /** * 渲染未来花期列表项 */ function renderFutureItem(location) { return `
${location.name} · ${location.flower} · ${location.best_time}
`; } /** * 渲染时间轴 */ function renderTimeline(data) { const { blooming, upcoming, future } = classifyLocations(data.locations); const { top_notes, status_changes } = data; // 更新时间段副标题 if (upcoming.length > 0) { document.querySelector('#upcoming-section .timeline-subtitle').textContent = `· ${getTimeSubtitle(data.locations)}`; } if (future.length > 0) { document.querySelector('#future-section .timeline-subtitle').textContent = `· ${getFutureTimeRange(data.locations)}`; } // 渲染正在盛开 const bloomingCards = blooming.map(loc => renderLocationCard(loc, 'blooming', top_notes, status_changes)).join(''); document.getElementById('blooming-cards').innerHTML = bloomingCards || '
暂无盛开中的花卉
'; // 渲染即将盛开 const upcomingCards = upcoming.map(loc => renderLocationCard(loc, 'upcoming', top_notes, status_changes)).join(''); document.getElementById('upcoming-cards').innerHTML = upcomingCards || '
暂无即将盛开的花卉
'; // 渲染未来花期 const futureItems = future.map(loc => renderFutureItem(loc)).join(''); document.getElementById('future-list').innerHTML = futureItems || '
暂无未来花期信息
'; } /** * 渲染页脚 */ function renderFooter(data) { const date = new Date(data.report_date); const ganzhi = getGanzhiYear(date.getFullYear()); const solarTerm = getSolarTerm(date); document.getElementById('footer-time').textContent = `${ganzhi}年 · ${solarTerm.name}`; } /** * 主渲染函数 */ function render(data) { if (!data) { document.querySelector('.timeline-section').innerHTML = `
暂无花期数据
请稍后再试
`; return; } renderHeader(data); renderTimeline(data); renderFooter(data); } // ========== 分享功能 ========== /** * 生成分享图片 */ async function shareAsImage() { const btn = document.getElementById('share-btn'); btn.disabled = true; btn.textContent = '生成中...'; try { // 隐藏分享按钮 const shareSection = document.querySelector('.share-section'); shareSection.style.display = 'none'; // 截图范围:主视觉 + 时间轴 const target = document.querySelector('.container'); const canvas = await html2canvas(target, { backgroundColor: '#faf8f5', scale: 2, useCORS: true, logging: false }); // 恢复分享按钮 shareSection.style.display = 'block'; // 下载图片 const link = document.createElement('a'); const date = new Date(); const dateStr = `${date.getFullYear()}${(date.getMonth()+1).toString().padStart(2,'0')}${date.getDate().toString().padStart(2,'0')}`; link.download = `花信-${dateStr}.png`; link.href = canvas.toDataURL('image/png'); link.click(); btn.textContent = '分享花期'; btn.disabled = false; } catch (error) { console.error('Share failed:', error); btn.textContent = '分享花期'; btn.disabled = false; alert('生成图片失败,请稍后再试'); } } // ========== 初始化 ========== async function init() { const data = await loadData(); render(data); } // 页面加载完成后初始化 document.addEventListener('DOMContentLoaded', init);