我們在前面已完成收藏與心得的 LocalStorage。今天加上版本化與資料完整性:可避免未來結構變更導致讀取出錯,並保留欄位升級空間。
改哪裡:js/app.js(收藏)、js/reviews.js(心得)
把 Day10 中單純儲存 id 陣列 → 儲存物件陣列 { id, savedAt },並用版本鍵控制。
// js/app.js 收藏段落改寫
const FAV_KEY = 'dramaweb:fav:v2'; // v2 版本
function loadFavSet(){
try{
const raw = JSON.parse(localStorage.getItem(FAV_KEY) || '[]');
// 支援舊版(v1:純 id 陣列)
if (Array.isArray(raw) && raw.length && typeof raw[0] === 'string'){
return new Map(raw.map(id => [id, {id, savedAt: Date.now()}]));
}
// v2:[{id,savedAt}]
const map = new Map();
(raw||[]).forEach(o => { if (o && o.id) map.set(o.id, {id:o.id, savedAt:o.savedAt || Date.now()}); });
return map;
}catch(e){ return new Map(); }
}
function saveFavMap(map){
localStorage.setItem(FAV_KEY, JSON.stringify([...map.values()]));
}
let favMap = loadFavSet();
function isFav(id){ return favMap.has(id); }
function toggleFav(id){
if (favMap.has(id)) favMap.delete(id);
else favMap.set(id, { id, savedAt: Date.now() });
saveFavMap(favMap);
}
favorites.js讀取時也要相容 v2(把Set改成讀物件陣列,依savedAt排序渲染)。
// js/favorites.js 讀取收藏
const FAV_KEY = 'dramaweb:fav:v2';
function loadFavList(){
try{
const raw = JSON.parse(localStorage.getItem(FAV_KEY) || '[]');
if (Array.isArray(raw) && raw.length && typeof raw[0] === 'string'){
return raw.map(id => ({id, savedAt: Date.now()}));
}
return Array.isArray(raw) ? raw : [];
}catch(e){ return []; }
}
function saveFavList(list){
localStorage.setItem(FAV_KEY, JSON.stringify(list));
}
function render(){
const list = loadFavList().sort((a,b)=> (b.savedAt||0)-(a.savedAt||0));
if (!list.length){ $list.html(`<div class="empty">目前沒有收藏的劇集</div>`); return; }
const map = new Map(SHOWS.map(s => [s.id, s]));
const items = list.map(o => map.get(o.id)).filter(Boolean);
$list.html(items.map(cardTemplate).join(''));
}
// 取消收藏
$(document).on('click', '#favCards .fav', function(){
const id = $(this).closest('.card').data('id');
const list = loadFavList().filter(o => o.id !== id);
saveFavList(list);
render();
});
// 清空
$btnClear.on('click', function(){
if (!confirm('確定清空全部收藏?')) return;
saveFavList([]);
render();
});
在 js/reviews.js 把 RV_KEY 換成 dramaweb:reviews:v2,讀入時補齊缺欄位。
const RV_KEY = 'dramaweb:reviews:v2';
function loadReviews(){
try{
const arr = JSON.parse(localStorage.getItem(RV_KEY) || '[]');
if (!Array.isArray(arr)) return [];
return arr.map(x => ({
id: x.id || ('rv_' + Math.random().toString(36).slice(2)),
title: x.title || '',
rating: x.rating || '0.0',
name: x.name || '匿名',
content: x.content || '',
genres: Array.isArray(x.genres) ? x.genres : [],
createdAt: x.createdAt || new Date().toISOString()
}));
}catch(e){ return []; }
}
function saveReviews(list){
localStorage.setItem(RV_KEY, JSON.stringify(list));
}
驗收
feat(ui): Day13 對話框心得卡+載入更多
docs: Day14 週次小結 README
feat(ux): Day15 微動效(卡片/收藏/chips)
feat(rwd): Day16 手機導覽開關
feat(review): Day17 linkify/@mention 與排版強化
feat(sort): Day18 劇集與心得排序(欄位+方向)
feat(search): Day19 搜尋正規化與模糊比對
chore(storage): Day20 收藏/心得 LocalStorage 版本化與結構升級