摘要
今天我們要把「精神狀態小測驗」升級成長期回顧工具!
不只保留最新一次的選項(MOOD_KEY),還要新增 MOOD_LOG_KEY 陣列,把每次提交的結果都存起來。這樣就能在歷史回顧頁顯示統計清單,甚至用 Chart.js 畫出長條圖,看到自己的精神狀態分布。
假設已具備:
<canvas>
外包一層固定高度容器:<!-- [Day20-NEW] 精神狀態分布(圖表或清單) -->
<div id="moodStatsSection" aria-labelledby="mood-stats-title" style="margin:.5rem 0;">
<h2 id="mood-stats-title">精神狀態分布</h2>
<!-- 固定高度的容器,避免圖表無限放大 -->
<div id="moodChartWrap" style="position:relative; height:260px; max-width:680px;">
<canvas id="moodChart" hidden aria-label="精神狀態長條圖" role="img"></canvas>
</div>
<!-- 回退清單 -->
<ul id="moodStatsList" class="muted" aria-live="polite"></ul>
</div>
// [Day20-NEW] 跨次提交的快照陣列
const MOOD_LOG_KEY = 'daily_mood_log';
function readMoodLog() {
try { return JSON.parse(localStorage.getItem(MOOD_LOG_KEY)) || []; }
catch { return []; }
}
function writeMoodLog(arr) {
localStorage.setItem(MOOD_LOG_KEY, JSON.stringify(Array.isArray(arr) ? arr : []));
}
function appendMoodSnapshot(snapshot) {
const list = readMoodLog();
list.push(snapshot);
writeMoodLog(list);
}
// 由 MOOD_KEY 當前資料生成「一次提交快照」
function makeSnapshotFromCurrentMood() {
const moodData = safeParseJSON(localStorage.getItem(MOOD_KEY), {});
const snap = { ts: Date.now() };
if (moodData.morning) snap.morning = moodData.morning;
if (moodData.afternoon) snap.afternoon = moodData.afternoon;
if (moodData.evening) snap.evening = moodData.evening;
return snap;
}
// 聚合統計
function aggregateMoodCounts() {
const log = readMoodLog();
const counts = new Map();
for (const s of log) {
['morning','afternoon','evening'].forEach(p => {
const v = s[p];
if (!v) return;
counts.set(v, (counts.get(v) || 0) + 1);
});
}
return counts;
}
// [Day20-NEW] 渲染統計(清單 + 圖表)
async function renderMoodStats() {
const chartEl = document.getElementById('moodChart');
const listEl = document.getElementById('moodStatsList');
if (!chartEl || !listEl) return;
const counts = aggregateMoodCounts();
// 清單模式
if (counts.size === 0) {
listEl.innerHTML = '<li>尚無統計資料(去做一次精神小測驗並按 submit 吧!)</li>';
} else {
const rows = [...counts.entries()].sort((a,b)=>b[1]-a[1]);
listEl.innerHTML = rows.map(([k,v]) => `<li>${k}:${v} 次</li>`).join('');
}
// 載入 Chart.js
try {
if (!window.Chart) {
await new Promise((resolve, reject) => {
const s = document.createElement('script');
s.src = 'https://cdn.jsdelivr.net/npm/chart.js';
s.onload = resolve; s.onerror = reject;
document.head.appendChild(s);
});
}
} catch {
chartEl.hidden = true;
return;
}
if (counts.size === 0) {
chartEl.hidden = true;
return;
}
// 準備資料
const rows = [...counts.entries()].sort((a,b)=>b[1]-a[1]);
const labels = rows.map(([k]) => k);
const data = rows.map(([,v]) => v);
chartEl.hidden = false;
if (chartEl._chartInstance) chartEl._chartInstance.destroy();
const ctx = chartEl.getContext('2d');
chartEl._chartInstance = new Chart(ctx, {
type: 'bar',
data: { labels, datasets: [{ label: '出現次數', data }] },
options: {
responsive: true,
maintainAspectRatio: false, // 交給容器高度控制
plugins: { legend: { display: false } },
scales: { y: { beginAtZero: true, precision: 0 } }
}
});
}
在 showPage('history') 時呼叫:
if (page === 'history') {
historySection.hidden = false;
renderMoodStats().finally(() => {
if (typeof renderHistory === 'function') renderHistory();
});
}