🌐Vue 3 + SheetJS 匯出多分頁 Excel:一次搞懂「變數未定義」與「合併儲存格」的坑

 

為什麼會寫這篇?

我們在一個 Vue 3 專案裡,用 SheetJS (xlsx) 把多個 API 回傳的資料匯出成 多分頁 Excel。每個分頁都有自訂雙層表頭、欄寬、數字格式;如果某個分頁「沒有資料」,就要在標題下方放一行文字(例如:此版本無使用SupplyPegLog),而且那一行要橫向合併到和表頭一樣寬。

結果遇到兩個經典地雷:

  1. ReferenceError: wsThree is not defined

  2. wsX["!merges"].push(...) 時崩掉,因為 !merges 沒被初始化

以下用工程師視角,白話講給不熟的人看,順便給出可直接複用的寫法。




背景結構(簡述)

  • Vue 3 + Vite

  • Pinia 管版本參數

  • Axios 打 /api/Data/...

  • SheetJS 建立 Workbook → 為每張表建立 Worksheet → 設定合併、欄寬、格式 → book_append_sheetwriteFile




問題 1:ReferenceError(變數作用域)

症狀:主控台顯示
ReferenceError: wsThree is not defined

常見原因:你在 if/elseelse 區塊裡 才宣告了 const wsThree = ...,但在區塊外又去使用 wsThree(例如 book_append_sheet(wb, wsThree, ...) 或在 if 分支裡 push merges)。
JS 的 const/let區塊作用域,離開那個 {} 就看不到了。

修法重點

先在 if/else 之外建立 wsThree,再在分支內寫入資料或放空訊息。




問題 2:!merges 未初始化

症狀:當資料為空時,你會做:

wsX["!merges"].push({ s:{ r:2, c:0 }, e:{ r:2, c:headerRow.length - 1 } })

但很多分頁(例如 wsThree/wsFour…)沒有先設定 wsX["!merges"],導致 push 時炸掉。你可能只有在 2.RemainSupply 那頁先賦值了 !merges,其他頁沒有。

修法重點

每張表在建立之後,一定先做 wsX["!merges"] = [],接著再 push 要合併的範圍。資料為空要顯示訊息時,也是在這個陣列裡加一個合併設定。




萬用寫法(建議遵循的 5 個步驟)

以任何一張分頁為例,流程都是:

  1. 建立表頭const ws = XLSX.utils.aoa_to_sheet([headerRow1, headerRow2, ...])

  2. 初始化合併ws["!merges"] = []

  3. 加入表頭需要的合併範圍(如果你的第一列、第二列要跨欄)

  4. 有資料sheet_add_json沒資料sheet_add_aoa([["此版本無使用XXX"]], { origin: "A3" }) + !merges.push(...)

  5. append 到活頁簿XLSX.utils.book_append_sheet(wb, ws, "工作表名稱")




範例:3.SupplyPegLog(可直接貼用的骨架)

// 1) 表頭
const headerRow31 = [
  "From APS Input", "", "", "", "", "",
  "APS Kitting 過程中物料分配到哪些需求中了", "", "", "", "", "", "", ""
];
const headerRow32 = [
  "SITE","ORG","ITEM","SUPPLY_DATE","SUPPLY_QTY","SUPPLY_TYPE",
  "SEQ_NO","Site","AppL","Customer","PRODUCT_ID","MODEL","PEG_Date","PEG_QTY"
];

// 2) 建立 sheet 並初始化 merges
const wsThree = XLSX.utils.aoa_to_sheet([headerRow31, headerRow32]);
wsThree["!merges"] = [];

// 3) 表頭合併
wsThree["!merges"].push(
  { s: { r:0, c:0 }, e: { r:0, c:5 } },   // A1:F1
  { s: { r:0, c:6 }, e: { r:0, c:13 } }  // G1:N1
);

// 欄寬(可選)
wsThree["!cols"] = [
  { wch:12 },{ wch:8 },{ wch:30 },{ wch:15 },{ wch:15 },{ wch:18 },
  { wch:20 },{ wch:12 },{ wch:12 },{ wch:20 },{ wch:20 },{ wch:20 },
  { wch:15 },{ wch:15 }
];

// 4) 依資料量處理
if (dataThree.length === 0) {
  XLSX.utils.sheet_add_aoa(wsThree, [["此版本無使用SupplyPegLog"]], { origin: "A3" });
  // A3 到 N3(headerRow32 長度 = 14)
  wsThree["!merges"].push({ s: { r:2, c:0 }, e: { r:2, c: headerRow32.length - 1 } });
} else {
  XLSX.utils.sheet_add_json(wsThree, dataThree, { skipHeader: true, origin: "A3" });

  // 數字欄位格式化(E=SUPPLY_QTY, N=PEG_QTY)
  const range = XLSX.utils.decode_range(wsThree["!ref"]);
  for (let r = range.s.r + 2; r <= range.e.r; r++) {
    [4, 13].forEach(c => {
      const cell = wsThree[XLSX.utils.encode_cell({ r, c })];
      if (cell && cell.t === "n") cell.z = (cell.v % 1 !== 0) ? "#,##0.0000" : "#,##0";
    });
  }
}

// 5) 加進活頁簿
XLSX.utils.book_append_sheet(wb, wsThree, "3.SupplyPegLog");

只要把「標題列、欄位數量、要合併到哪一欄」換成各分頁的設定,就能套用在 4.DemandPegLog6.新產品7.策備8.物料限制9.模具10.供應商產能




2.RemainSupply 的特例

你的 2.RemainSupply 原本就做對了:先建立 wsTwo、設定表頭合併,再在沒有資料時加上


其他分頁也要比照這個流程,差別只是表頭長度與合併範圍不同。




快速檢查清單(你一定會用到)

  • 每張表 wsX 建立後立刻wsX["!merges"] = []

  • 表頭合併範圍先 push!merges

  • if (data.length === 0)sheet_add_aoa([...], { origin: "A3" }) + 合併 A3headerRow.length 尾欄

  • elsesheet_add_jsonskipHeader: true,再做數字欄位格式化

  • 最後book_append_sheet

  • 變數不要只在 else 裡宣告;在區塊外先建立 wsX(避免 ReferenceError




常見小坑再提醒

  • headerRow.length - 10-based 欄位索引,別寫錯。

  • decode_range(ws["!ref"]) 取得範圍後,資料列起點是 A3 ⇒ 索引 = 2

  • cell.t === 'n' 才能套數字格式;字串不會生效。

  • sheet_add_jsonorigin: "A3" 會把資料從第三列開始寫,剛好避開雙層表頭。

  • 若 API 回傳的 key 跟 Excel 欄位不一致,可用你現成的 normalizeRow 先轉,再 sheet_add_json




總結

這次踩雷的本質其實只有兩件事:

  1. 變數作用域wsX 要在 if/else 外就建好,否則會 is not defined

  2. 陣列初始化:每張表的 wsX["!merges"] 先設成空陣列,再 push 合併範圍;資料為空時也一樣。

照著上面的「5 步驟萬用寫法」改,你的每個分頁都能在沒有資料時顯示一行「此版本無使用(分頁名)」且橫向合併到跟表頭一樣寬;有資料時則正常寫入並套上數字格式。
穩、清楚、可維護。下一位同事接手也不會崩潰。

留言

這個網誌中的熱門文章

🛠【ASP.NET Core + Oracle】解決 ORA-00904 "FALSE": 無效的 ID 錯誤與資料欄位動態插入顯示問題

🛠【實戰排除教學】從 VS Code 的 _logger 錯誤,到 PowerShell 找不到 npm/serve,再到 Oracle ORA-03135 連線中斷——一次搞懂!

🔎如何在 Oracle PL/SQL 儲存過程中為文字欄位加入換行符號(CHR(10))——以 Updlcmremark 為例