🔧 main_flow.js 三處修改
1

require 換掉 screenshot → print_pdf

  main_flow.js — 頂部 require
- const { takeScreenshot, isScreenshotExists } = require('./screenshot');
+ const { printToPdf, isPdfExists } = require('./print_pdf');
2

智慧補邏輯:isScreenshotExists → isPdfExists

投票前的過濾迴圈,用來跳過已有 PDF 的公司

  main_flow.js — 過濾迴圈
for (const meeting of meetings) {
  -   if (isScreenshotExists(saveDir, idNumber, meeting.stockCode, flat)) continue;
+   if (isPdfExists(saveDir, idNumber, meeting, flat)) {
+     sendLog(`已有 PDF,略過:${meeting.stockCode}`);
+     continue;
+   }
  // ... 執行投票(不變)
}
isPdfExists 接受整個 meeting 物件(需含 stockCodemeetingDate),舊版只傳 stockCode 字串,注意差異。
3

投票完成後:截圖 → 列印 PDF

  main_flow.js — 投票完成後
// 投票成功後
- const screenshotPath = await takeScreenshot(
-   webContents, saveDir, idNumber, meeting.stockCode, meeting.companyName, flat
- );
- sendLog(`截圖完成:${screenshotPath}`);
+ const pdfPath = await printToPdf(
+   webContents,    // BrowserView.webContents(停在投票確認頁)
+   meeting,        // { stockCode, meetingDate, ... }
+   saveDir,        // 使用者選擇的儲存根目錄
+   idNumber,       // 身分證字號
+   { flat }        // 沿用目錄結構設定
+ );
+ sendLog(`PDF 已儲存:${pdfPath}`);
📋 voting.js — meetingDate 欄位補充

print_pdf.js 需要 meeting.meetingDate 才能產生 MMDD 日期碼。
若集保頁面上有顯示「股東會日期」欄位,voting.js 爬清單時應一併取出:

  voting.js — 清單爬取(示意)
const meetings = await safeExecute(webContents, `
  Array.from(document.querySelectorAll('.meeting-row')).map(row => ({
    stockCode:   row.querySelector('.stock-code')?.textContent?.trim(),
    companyName: row.querySelector('.company-name')?.textContent?.trim(),
    meetingDate: row.querySelector('.meeting-date')?.textContent?.trim(),
    //  ↑ 對應集保頁面「股東會日期」的 CSS selector,依實際 DOM 調整
  }))
`);

// meetingDate 若 null,getMeetingDateCode() 自動 fallback 為今天的 MMDD
✅ 若集保頁面無日期欄位,meetingDatenull 即可,print_pdf.js 會自動使用台灣時間今日的 MMDD 作為 fallback。
📐 PDF 命名規則完整規格

格式:{MMDD}_{會員代號}_{股票代號}.pdf

欄位長度演算法 / 來源範例
MMDD
開會日期
4碼 月份(補零2碼) + 日期(補零2碼)
支援格式:YYYY-MM-DDYYYYMMDDMM-DDMMDD
0215(2月15日)
0601(6月1日)
會員代號
memberId
6碼 SHA-256(身分證字號.trim().toUpperCase()).slice(0,6).toUpperCase()
確定性:同一身分證永遠相同,不可逆推
51FF20(A123456789)
30DCC4(B987654321)
股票代號
stockCode
4~6碼 集保系統原始代號,直接使用 2330(台積電)
完整範例:0215_51FF20_2330.pdf  ←  2月15日 · A123456789 · 台積電(2330)
📁 輸出目錄結構
[saveDir]/
├── A123456789/ ← 分層模式(flat=false,預設)
│  ├── 0215_51FF20_2330.pdf
│  └── 0215_51FF20_2317.pdf
└── B987654321/
   └── 0215_30DCC4_2412.pdf

# 扁平模式(flat=true)
[saveDir]/
├── 0215_51FF20_2330.pdf
├── 0215_51FF20_2317.pdf
└── 0215_30DCC4_2412.pdf
📦 print_pdf.js API 參考
函式參數回傳說明
printToPdf(webContents, meetingInfo, saveDir, idNumber, [options]) 見下方 Promise<string> 完整路徑 對投票確認頁執行 Electron printToPDF,儲存 PDF
isPdfExists(saveDir, idNumber, meetingInfo, [flat]) 同上 boolean 檢查 PDF 是否已存在(智慧補邏輯)
generateMemberId(idNumber) string string 6碼 由身分證字號確定性產生會員代號
getMeetingDateCode(meetingInfo) object string MMDD 解析各種日期格式,回傳 MMDD
parseMmdd(raw) string|Date string MMDD 日期字串解析,支援多種格式
  options 參數
// printToPdf 第五個參數 options(可選)
{
  flat:      false,  // true=扁平目錄 / false=依身分證分層(預設)
  landscape: false,  // true=橫向A4 / false=直向A4(預設)
}
📂 平台檔案清單
電投代領作業平台/
├── index.html    首頁 / 工作流程導引
├── evote-pdf.html  電投 PDF 命名計算器(新增)
├── rename.html    PDF 批次改名 v4.0
├── crop-merge.html  PDF 截取合併 v7.0
├── dev-guide.html  開發者指南(本頁)
├── print_pdf.js   Electron 模組(整合至桌面版)
└── main_flow.patch.js main_flow.js 修改說明
📌 print_pdf.jsmain_flow.patch.js 屬於 Electron 桌面版的程式碼,放入 src/automation/ 目錄下使用。其餘 .html 頁面在瀏覽器中直接開啟即可。