昨天我們把月度色卡完成得差不多了:算出了各個顏色的百分比,也讓佔比最高的主導色決定畫布的光暈。整個開瓶與色卡彈窗看起來有模有樣,底下放了一個寫著「珍藏這張月卡」的按鈕。
但如果你前幾天有因為好奇去按了那顆按鈕,會發現它就只是關閉視窗而已,沒有其他動作:
<button class="btn-seal" @click="showMonthlyCard = false">
珍藏這張月卡
</button>
今天我們就要讓珍藏按鈕真正發揮作用,並蓋一間「時光相簿」,把過去每個月總結出來的色卡整整齊齊收納起來。
在動手寫程式之前,我停下來想了一下資料要怎麼存。在 Vue 裡做這個功能,通常有兩種完全不同的思路:
['2026-09', '2026-10'])。使用者點進去哪個月,系統就當場把那個月的所有光珠抓出來,重新跑一次百分比和漸層計算。localStorage。電子相簿只負責讀這張「已經完整的卡片」。本來想偷懶選 A,反正歷史光珠都在,隨叫隨算好像很省事。但轉念一想:
所以我選擇了做法 B 作為之後要呈現的方向。
既然決定做整張色卡的儲存,資料結構就不用太複雜,只要記錄月份、總顆數、佔比陣列與主導色就好:
// 初始化已珍藏的月度色卡清單
const archivedCards = ref(JSON.parse(localStorage.getItem('store_archived_cards') || '[]'))
// 點擊「珍藏這張月卡」時執行
function sealMonthlyCard() {
const monthTarget = cardMonthTarget.value
if (!monthTarget || colorStats.value.length === 0) {
showMonthlyCard.value = false
return
}
// 檢查相簿裡是不是已經存過這個月份了
const existingIndex = archivedCards.value.findIndex(card => card.month === monthTarget)
const cardSnapshot = {
month: monthTarget,
totalGems: targetMonthGems.value.length,
stats: colorStats.value,
dominantColor: colorStats.value[0]?.color || '#ffffff',
}
if (existingIndex !== -1) {
// 如果當月有存過,就覆蓋更新
archivedCards.value[existingIndex] = cardSnapshot
} else {
// 如果是第一次珍藏:放到陣列最前面,讓相簿最新月份排在最上面
archivedCards.value.unshift(cardSnapshot)
}
// 存進 localStorage 並關閉彈窗
localStorage.setItem('store_archived_cards', JSON.stringify(archivedCards.value))
showMonthlyCard.value = false
}
接著把按鈕的點擊事件換掉:
<button class="btn-seal" @click="sealMonthlyCard">珍藏這張月卡</button>
現在點下去不再是關閉彈窗,而是會有一張月度卡片被寫進了儲存庫。
有了存檔,接下來需要一個入口跟展示空間。
原本隨手把相簿按鈕丟在畫面最底下,實機測試時發現畫面被拉得很長,而且孤零零一顆按鈕顯得很突兀。
最後我決定把「時光相簿」收進瓶子下方的操作列,跟「查看本月微光色卡」並排放在一起,並直接在按鈕上顯示目前收藏的張數:
<!-- 瓶子下方的操作按鈕列 -->
<div class="card-action-row">
<button class="btn-sub-capsule" @click="openCurrentMonthCard">
查看本月微光色卡
</button>
<button class="btn-sub-capsule" @click="showAlbum = true">
時光相簿({{ archivedCards.length }})
</button>
</div>
剛開始用的使用者,相簿點進去一定什麼都沒有。如果只留下一片空白,看起來會很像系統壞掉。所以我給它補了一個空狀態的提示詞:
<!-- 時光相簿彈窗 -->
<div v-if="showAlbum" class="album-overlay" @click.self="showAlbum = false">
<div class="album-modal">
<div class="album-header">
<div>
<h2>時光相簿</h2>
<p>收錄每個月珍藏的總結色卡</p>
</div>
<button class="btn-close-album" @click="showAlbum = false">✕</button>
</div>
<!-- 還沒有卡片時的空狀態 -->
<div v-if="archivedCards.length === 0" class="album-empty">
<p class="empty-text">相簿裡還空空的,沒有任何紀錄。</p>
<p class="empty-hint">到了月底,在月度色卡點擊「珍藏這張月卡」就會收錄在這裡。</p>
</div>
<!-- 有卡片 -->
<div v-else class="album-grid">
<div
v-for="card in archivedCards"
:key="card.month"
class="album-card-item"
>
<!-- 迷你圖片 -->
<div class="mini-aurora-frame" :style="getCardGradientStyle(card)">
<div class="mini-inner-glow"></div>
<span class="mini-month-tag">{{ card.month }}</span>
</div>
<!-- 卡片資訊摘要 -->
<div class="mini-card-meta">
<span class="mini-gem-count">{{ card.totalGems }} 顆光珠</span>
<div class="mini-swatches">
<span
v-for="(st, idx) in card.stats.slice(0, 3)"
:key="idx"
class="mini-swatch-dot"
:style="{ backgroundColor: st.color }"
:title="`${st.ratio}%`"
></span>
</div>
</div>
</div>
</div>
</div>
</div>
為了讓每張小圖卡能還原當時的色彩,這邊補上了一個計算微型漸層背景的輔助函式:
// 依據數據產生小圖卡的色彩背景
function getCardGradientStyle(card) {
if (!card.stats || card.stats.length === 0) return { background: '#2d3748' }
const anchorPoints = ['25% 25%', '80% 75%', '75% 25%', '20% 80%', '50% 50%']
const radialLayers = card.stats.map((item, idx) => {
const pos = anchorPoints[idx % anchorPoints.length]
const renderColor = item.color.toLowerCase() === '#ffffff' ? 'rgba(255, 255, 255, 0.9)' : item.color
const radius = Math.min(90, Math.max(50, item.ratio + 30))
return `radial-gradient(circle at ${pos}, ${renderColor} 0%, rgba(255, 255, 255, 0) ${radius}%)`
})
return {
backgroundImage: radialLayers.join(', '),
backgroundColor: card.dominantColor || card.stats[0].color
}
}
在相簿裡,每張卡片就是縮小版的色卡:上面是漸層(沿用色卡顏色佔比產生),左下角壓上月份標籤,下方則列出該月的總顆數和前三名的重點色票。
在把彈窗組件拼裝進來時,有兩個小插曲發生:
@click="showAlbum = true",實機按下去卻毫無動靜。打開 Console 檢查才發現自己漏了一步:<script setup> 裡忘了宣告 const showAlbum = ref(false)。補上狀態宣告並把彈窗專屬的遮罩樣式(position: fixed)加上去後,相簿才順利跑出來。openPreviousMonthCard,導致明明是當月卻一直開成上個月的空卡片。將它改為獨立的 openCurrentMonthCard 鎖定當月後,色卡就能正確抓到當月的數據了。localStorage,外面按鈕數字隨之由 (0) 變為 (1)。


現在卡片已經能好好存在時光相簿裡了,但目前在相簿中,它們還只是一張張縮微的小圖片。
明天(Day 22),我們要實作「色卡匯出成圖片下載」與「相簿卡片點擊展開大圖」的功能!
我們將採用雙入口的設計: