iT邦幫忙

2025 iThome 鐵人賽

DAY 22
0
Modern Web

Modern Web × AI《拖延怪日記》:語錄陪伴擺脫拖延系列 第 22

【Day 22】— 入門 JavaScript 網頁架設:匯出 JSON / CSV 記錄

  • 分享至 

  • xImage
  •  

摘要
Day 21 我們完成了圖表統計(長條/圓餅/趨勢)。今天把重點放回「資料主權」:把瀏覽器裡的紀錄 匯出到電腦。

  • JSON 匯出:原樣保存,方便再次匯入或做進階處理。
  • CSV 匯出:用 Excel / Google 試算表就能開啟,快速做樞紐分析或圖表。

讓資料不只待在瀏覽器裡,也能被你帶走、備份、分析。

為什麼要做匯出?

  • 避免遺失:清除瀏覽器、換裝置都不怕。
  • 易於分析:CSV 直接丟試算表,立刻做排序、篩選、圖表。
  • 互通性:JSON 與 CSV 幾乎是所有工具都認識的格式。

學習重點

  1. Blob + URL.createObjectURL() 產生下載連結並自動觸發下載(JSON/CSV 都用得到)。
  2. CSV 字串組合:處理逗號、換行與引號的跳脫,並加上 UTF-8 BOM 以避免 Excel 亂碼。
  3. 可近性/體驗:匯出成功或空資料時的回饋。

核心流程

  1. 在「歷史回顧」區塊新增 「匯出 JSON」 與 「匯出 CSV」 按鈕。
  2. 讀取 STORAGE_KEY(readRecords()),若無資料給出友善提示。
  3. 以 Blob 建立暫時 URL → 動態 <a download> → 觸發點擊 → 回收 URL。

實作

假設已具備:

  • readRecords():讀出 STORAGE_KEY 陣列
  • writeRecords():維持原有
  • 既有的初始化與顯示切換邏輯
  1. HTML:在歷史區塊新增匯出按鈕與訊息區
    把下面這段插到「歷史回顧」頁面中 「清空/Undo 按鈕區塊下方」(或你想要的任一位置):
<!-- [Day22-NEW] 匯出功能 -->
<div id="exportSection" style="margin-top:.75rem;">
  <button id="btnExportJSON" type="button">匯出 JSON</button>
  <button id="btnExportCSV" type="button">匯出 CSV</button>
  <p id="exportFeedback" class="muted" aria-live="polite" style="margin:.25rem 0 0;"></p>
</div>
  1. JS:工具函式與事件處理
    將以下程式碼貼在你「常數 / 偏好」區塊附近(如 // [Day18-NEW] 分享與查看歷史 之後、渲染歷史之前),只要埋好節點即可。
// [Day22-NEW] 匯出節點
const btnExportJSON   = document.getElementById('btnExportJSON');
const btnExportCSV    = document.getElementById('btnExportCSV');
const exportFeedback  = document.getElementById('exportFeedback');

// [Day22-NEW] 下載工具:把字串/Uint8Array 變成檔案下載
function triggerDownload(filename, mime, data) {
  const blob = new Blob([data], { type: mime });
  const url  = URL.createObjectURL(blob);
  const a    = document.createElement('a');
  a.href = url;
  a.download = filename;
  document.body.appendChild(a);
  a.click();
  a.remove();
  // 釋放 URL
  setTimeout(() => URL.revokeObjectURL(url), 1000);
}

// [Day22-NEW] 組 CSV:確保跳脫與 BOM
function toCSVRow(cells) {
  return cells.map(v => {
    const s = String(v ?? '');
    // 若包含逗號、雙引號或換行,需用雙引號包起來,且把內部 " 轉成 ""
    return /[",\n]/.test(s) ? `"${s.replace(/"/g, '""')}"` : s;
  }).join(',');
}
function buildCSV(records) {
  const header = ['createdAt(ISO)','createdAt(Local)','task','reasonCode','reason','quote'];
  const rows = records.map(r => {
    const dt = new Date(r.createdAt || Date.now());
    return toCSVRow([
      dt.toISOString(),
      dt.toLocaleString(),
      r.task || '',
      r.reasonCode || '',
      r.reason || '',
      r.quote || ''
    ]);
  });
  return [toCSVRow(header), ...rows].join('\n');
}

// [Day22-NEW] 匯出 JSON
btnExportJSON?.addEventListener('click', () => {
  const list = readRecords();
  if (!Array.isArray(list) || list.length === 0) {
    exportFeedback.textContent = '目前沒有可匯出的紀錄。';
    return;
  }
  const pretty = JSON.stringify(list, null, 2);
  triggerDownload('procrastinator-records.json', 'application/json;charset=utf-8', pretty);
  exportFeedback.textContent = `已匯出 ${list.length} 筆紀錄(JSON)。`;
});

// [Day22-NEW] 匯出 CSV(含 UTF-8 BOM,避免 Excel 亂碼)
btnExportCSV?.addEventListener('click', () => {
  const list = readRecords();
  if (!Array.isArray(list) || list.length === 0) {
    exportFeedback.textContent = '目前沒有可匯出的紀錄。';
    return;
  }
  const csv = buildCSV(list);
  const BOM = '\uFEFF'; // UTF-8 Byte Order Mark
  triggerDownload('procrastinator-records.csv', 'text/csv;charset=utf-8', BOM + csv);
  exportFeedback.textContent = `已匯出 ${list.length} 筆紀錄(CSV)。`;
});

想加碼?你也可以用同樣方式,額外提供
readMoodLog() 的 JSON 匯出(例如 mood-log.json),但本篇主線聚焦在「任務紀錄」。

驗證

  1. 有資料時:按下「匯出 JSON」會下載 procrastinator-records.json,內容是整個 STORAGE_KEY 陣列;按「匯出 CSV」會下載 procrastinator-records.csv,用 Excel/Google 試算表開啟欄位正常。
  2. 無資料時:按任一匯出鍵,頁面下方顯示「目前沒有可匯出的紀錄。」(不會產生空檔)。
  3. CSV 亂碼:Windows 上用 Excel 開啟中文不亂碼(因為前面加了 UTF-8 BOM)。

常見錯誤 & 排查

  1. Excel 顯示亂碼
  • 檢查是否在字串前加了 BOM(\uFEFF)。
  • 或用「資料 → 自文字/CSV」匯入並指定 UTF-8。
  1. 欄位錯亂、換行被切開
    確認 toCSVRow() 有把含「逗號、雙引號、換行」的欄位用 " 包住,內部 " 變 ""。

  2. 點按沒有下載

  • 有些企業管控或 iOS Safari 可能阻擋彈出;請確認沒有阻擋下載動作。
  • 確認 triggerDownload() 有被呼叫,且 download 屬性被支援(大多數現代瀏覽器 OK)。
  1. 檔名被瀏覽器改掉或附加 (1)
    屬於瀏覽器行為(避免覆蓋),不是錯誤。你可以在檔名中加入日期:
    procrastinator-records-${new Date().toISOString().slice(0,10)}.csv

上一篇
【Day 21】— 入門 JavaScript 網頁架設:圓餅/趨勢圖(Chart.js)
下一篇
【Day 23】— 入門 JavaScript 網頁架設:ngrok + backup.json 簡易雲端備份
系列文
Modern Web × AI《拖延怪日記》:語錄陪伴擺脫拖延23
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言