iT邦幫忙

2026 iThome 鐵人賽

DAY 17
0
Modern Web

微光採集瓶:用 Vue 3 打造每日美好記錄網頁系列 第 17 篇

【Day 17】忘了填寫怎麼辦:自動生成缺席的微光

  • 分享至 

  • xImage
  •  

市面上大多數的習慣打卡工具,往往帶著一種隱形的心理壓迫感:一旦中斷連續天數,畫面就會跳出刺眼的紅色警示,甚至歸零重來。這種機制很容易讓人從「想記錄生活」變成「害怕漏掉打卡」,最後壓力太大索性放棄。

但微光採集瓶的初衷是陪伴與療癒,所以如果有一天你忘了打開它,可能是因為生活本身已經足夠充實、讓你無暇低頭看螢幕。今天,我們要讓玻璃瓶在感知到空白的日子時,補上一顆專屬光芒:「這天忘了寫,是因為正忙著快樂吧!」

一、規則設計

在動手寫程式碼前,我們先把體驗規則與防呆機制定義清楚:

  • 以第一顆光珠作為起點:
    如果使用者剛進來、瓶子是空的,系統不會擅自幫他補珠;只有在「已經有了第一道微光」之後的日子中斷,才會觸發機制。
  • 回溯上限 7 天(避免塞爆瓶子):
    如果使用者隔了一個月甚至半年才重新打開,一口氣塞進三、四十顆光珠會破壞視覺平衡,也意味著生活型態已經改變。因此我們設定最多只追溯最近 7 天,過了七天瓶子內就不會有系統補的光珠了。
  • 專屬「純白微光」與標記:
    自動補上的光珠採用純白色,象徵這天雖未被文字記錄下來但真實存在,並帶上 isAutoGenerated: true標記,與親手採集的心情光珠做出區隔。

二、程式碼實作:模組化拆解

為了避免函式過於冗長,我們將這項功能拆解成 3 個單一功能的區塊來說明:

1. 小工具:安全解析日期字串

在時間比對時,直接用字串轉日期容易碰到時差的問題。我們寫一個將 YYYY-MM-DD 轉為 Date 物件的輔助函式:

// 將 YYYY-MM-DD 字串轉成 Date 物件(以中午 12:00 解析,避開跨日時差)
function parseLocalDate(dateStr) {
  const [year, month, day] = dateStr.split('-').map(Number)
  return new Date(year, month - 1, day, 12, 0, 0)
}

在解析日期字串時,我們特別把時間點固定在中午 12:00。

這是因為如果停在半夜 00:00:00,稍微做點時間加減運算,很容易因為跨日邊界差一秒就跑回前一天;定在中午就像把時間放在「正中央」,留了整整 12 小時的安全緩衝,怎麼加減都不會算錯天~

2. 小工廠:純白微光生成器

把「建立微光物件」的細節單獨抽出來,保持主邏輯的簡潔:

// 留白微光的資料結構
function createHappyGem(dateStr, timestamp) {
  const randomOffsetX = Math.floor(Math.random() * 9) - 4
  const randomOffsetY = Math.floor(Math.random() * 7) - 3

  return {
    id: timestamp,
    date: dateStr,
    color: '#FFFFFF', 
    note: '這天忘了寫,是因為正忙著快樂吧!',
    offsetX: randomOffsetX,
    offsetY: randomOffsetY,
    isAutoGenerated: true // 標記為系統生成的溫暖微光
  }
}

3. 主流程:四步驟巡邏與補償

這個主函式負責決定檢查起訖點,並透過迴圈逐日排查補漏:

// 檢查缺席的日期並補償微光
function checkMissedDays() {
  // 步驟 1:防呆檢查(若瓶子是空的,代表初次使用,直接略過)
  if (gems.value.length === 0) return

  // 步驟 2:收集現有日期清單,並找出最開始的那一天
  // 使用 Set 加速查詢,避免在迴圈中重複掃描陣列
  const existingDates = new Set(gems.value.map(g => g.date).filter(Boolean))
  const sortedDates = Array.from(existingDates).sort()
  if (sortedDates.length === 0) return

  // 步驟 3:決定檢查區間(起點取「第一顆珠子」與「7 天前」中較晚者,最多回溯 7 天)
  const firstGemDate = parseLocalDate(sortedDates[0])
  const currentDate = parseLocalDate(todayStr)
  const sevenDaysAgo = new Date(currentDate.getTime() - 7 * 24 * 60 * 60 * 1000)

  let loopDate = new Date(Math.max(firstGemDate.getTime(), sevenDaysAgo.getTime()))
  // 從起點的隔天開始巡邏(檢查到今天的前一天)
  loopDate = new Date(loopDate.getTime() + 24 * 60 * 60 * 1000)

  // 步驟 4:巡邏每一天,沒紀錄就補上一顆純白微光
  while (loopDate < currentDate) {
    const checkDateStr = formatDate(loopDate)

    if (!existingDates.has(checkDateStr)) {
      const newGem = createHappyGem(checkDateStr, loopDate.getTime())
      gems.value.push(newGem)
      existingDates.add(checkDateStr) // 加入集合,避免同一天重複補發
    }

    // 時間往後推進 1 天
    loopDate = new Date(loopDate.getTime() + 24 * 60 * 60 * 1000)
  }
}

// 元件載入時自動執行一次檢查
checkMissedDays()

三、彈窗卡片的客製化鎖定文字

當使用者點開這顆純白微光時,由於它的日期落在過去,本來就會自動被 Day 15 的「歷史鎖定機制」轉為唯讀。我們只需在 <template> 彈窗裡加上專屬文案判斷:

<!-- 情況 B:過期歷史光珠 -->
<div v-else class="locked-hint">
  <span v-if="activeGem.isAutoGenerated">留白也是很棒的一天,回想這天發生了什麼快樂的事呢?</span>
  <span v-else>昔日回憶已無法撤回</span>
</div>

四、開發者測試:如何瞬間驗證補珠?

不用真的苦等好幾天不寫,我們一樣利用瀏覽器開發者工具就能驗證:

  1. 打開開發者工具,切換至 Application 分頁。
  2. 左側展開 Local Storage,點擊本機網址。
  3. 找到 store_gems 項目,點兩下展開內容,找到其中一顆光珠,手動將其 "date" 改成 3 天前 的日期(例如今天是 10-01,改成 2026-09-28),按 Enter 儲存。
  4. 重新整理網頁。
  5. 畫面重整後,瓶子會自動生成 2026-09-29 與 2026-09-30 兩顆純白色的微光!點開卡片,就能看見那句系統留下的文字,且無法被修改或撤回。

測試缺席紀錄_影片

明日預告

瓶子裡的光珠隨著日子一天天積累,有自己親手採集的各色心情,也有留白時自動落下的純白光芒。
但當滿瓶的光珠聚集在一起時,我們要如何更輕鬆、更精準地找到並回顧某個特定日子的小美好?
明天(Day 18),我們要在瓶子上方打造一個「時光篩選器」!只要挑選特定日期,瓶中屬於那天的微光就會聚焦亮起,讓你可以清楚看到當初採集的是哪顆光珠。


上一篇
【Day 16】重新定義「今天」:睡前才算一天結束的時間邏輯
下一篇
【Day 18】時光篩選器:快速找到特定日子的回憶
系列文
微光採集瓶:用 Vue 3 打造每日美好記錄網頁 共 19 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言