{"product_id":"schedule","title":"免費排班工具","description":"\u003c!-- WKidea 排班表（多店版）：整段貼進 Shopify 商品\/文章的「\u003c\u003e」原始碼模式即可，無需另外上傳檔案 --\u003e\n\u003cdiv style=\"max-width:1000px;margin:0 auto;\"\u003e\n\u003ciframe id=\"wkScheduleFrame\" style=\"width:100%;height:1900px;border:none;display:block;\" title=\"排班表\" srcdoc=\"\u0026lt;!DOCTYPE html\u0026gt;\n\u0026lt;html lang=\u0026quot;zh-Hant\u0026quot;\u0026gt;\n\u0026lt;head\u0026gt;\n\u0026lt;meta charset=\u0026quot;UTF-8\u0026quot;\u0026gt;\n\u0026lt;meta name=\u0026quot;viewport\u0026quot; content=\u0026quot;width=device-width, initial-scale=1.0\u0026quot;\u0026gt;\n\u0026lt;title\u0026gt;排班表\u0026lt;\/title\u0026gt;\n\u0026lt;link rel=\u0026quot;preconnect\u0026quot; href=\u0026quot;https:\/\/fonts.googleapis.com\u0026quot;\u0026gt;\n\u0026lt;link rel=\u0026quot;preconnect\u0026quot; href=\u0026quot;https:\/\/fonts.gstatic.com\u0026quot; crossorigin\u0026gt;\n\u0026lt;link href=\u0026quot;https:\/\/fonts.googleapis.com\/css2?family=Noto+Sans+TC:wght@400;500\u0026amp;family=Noto+Serif+TC:wght@500;600\u0026amp;display=swap\u0026quot; rel=\u0026quot;stylesheet\u0026quot;\u0026gt;\n\u0026lt;script src=\u0026quot;https:\/\/cdnjs.cloudflare.com\/ajax\/libs\/html2canvas\/1.4.1\/html2canvas.min.js\u0026quot;\u0026gt;\u0026lt;\/script\u0026gt;\n\u0026lt;style\u0026gt;\n  :root{\n    --paper:#FAF8F4; --ink:#3A3530; --sub:#8A8076;\n    --line:#E5DDD2; --line-soft:#F0EAE1; --accent:#C77B30; --accent-soft:#F5EBDF;\n    --white:#FFFFFF;\n  }\n  *{box-sizing:border-box;}\n  body{margin:0;background:var(--paper);color:var(--ink);\n    font-family:\u0026quot;Noto Sans TC\u0026quot;,\u0026quot;PingFang TC\u0026quot;,\u0026quot;Microsoft JhengHei\u0026quot;,sans-serif;\n    line-height:1.6;font-weight:400;-webkit-font-smoothing:antialiased;}\n  .serif{font-family:\u0026quot;Noto Serif TC\u0026quot;,\u0026quot;Songti TC\u0026quot;,serif;}\n  .wrap{max-width:1000px;margin:0 auto;padding:48px 20px 60px;}\n\n  .top{text-align:center;margin-bottom:8px;}\n  .top .eyebrow{font-size:11px;letter-spacing:6px;color:var(--accent);margin-bottom:14px;}\n  .top h1{font-size:30px;font-weight:500;letter-spacing:8px;margin:0;}\n  .top .sub{font-size:12px;color:var(--sub);letter-spacing:2px;margin-top:10px;}\n  .rule{width:40px;height:1px;background:var(--accent);margin:24px auto 0;}\n\n  .block{margin-top:28px;}\n  .block-label{font-size:12px;letter-spacing:3px;color:var(--sub);margin-bottom:12px;display:flex;align-items:center;gap:12px;}\n  .block-label::before{content:\u0026quot;\u0026quot;;width:14px;height:1px;background:var(--accent);}\n\n  \/* 店家列 *\/\n  .stores{display:flex;flex-wrap:wrap;gap:8px;align-items:center;}\n  .store-tab{padding:8px 16px;font-size:13px;letter-spacing:1px;border:1px solid var(--line);background:transparent;color:var(--sub);border-radius:2px;cursor:pointer;font-family:inherit;transition:.2s;}\n  .store-tab.on{background:var(--ink);color:var(--paper);border-color:var(--ink);}\n  .store-tab:hover{border-color:var(--accent);}\n  .store-add{padding:8px 14px;font-size:13px;color:var(--accent);background:transparent;border:1px dashed var(--line);border-radius:2px;cursor:pointer;font-family:inherit;}\n  .store-add:hover{border-color:var(--accent);}\n  .store-tools{margin-left:auto;display:flex;gap:8px;}\n  .mini{font-size:11px;color:var(--sub);background:transparent;border:1px solid var(--line);border-radius:2px;padding:6px 12px;cursor:pointer;font-family:inherit;letter-spacing:1px;}\n  .mini:hover{border-color:var(--accent);color:var(--accent);}\n\n  .field-row{display:flex;flex-wrap:wrap;gap:28px;}\n  .field{flex:1;min-width:220px;}\n  .field label{display:block;font-size:11px;color:var(--sub);letter-spacing:1px;margin-bottom:4px;white-space:nowrap;}\n  input[type=text],input[type=date],input[type=month]{\n    width:100%;padding:10px 2px;border:none;border-bottom:1px solid var(--line);\n    background:transparent;font-size:14px;color:var(--ink);font-family:inherit;transition:border-color .2s;}\n  input:focus{outline:none;border-bottom-color:var(--accent);}\n  input::placeholder{color:#C9C0B5;}\n\n  \/* 人員管理 *\/\n  .people{display:flex;flex-wrap:wrap;gap:10px;align-items:stretch;}\n  .person-chip{display:inline-flex;align-items:center;gap:4px;border:1px solid var(--line);border-radius:2px;padding:2px 4px 2px 12px;background:var(--white);}\n  .person-chip input{border:none!important;width:88px;padding:8px 2px!important;font-size:13px;background:transparent;}\n  .person-chip input:focus{outline:none;}\n  .person-chip .del{border:none;background:transparent;color:#C9C0B5;cursor:pointer;font-size:16px;line-height:1;padding:2px 6px;border-radius:2px;}\n  .person-chip .del:hover{color:#c0392b;}\n  .add-person{border:1px dashed var(--line);background:transparent;color:var(--accent);border-radius:2px;padding:0 18px;font-size:13px;cursor:pointer;font-family:inherit;letter-spacing:1px;}\n  .add-person:hover{border-color:var(--accent);}\n  .add-person:disabled{opacity:.4;cursor:not-allowed;}\n\n  .seg{display:inline-flex;border:1px solid var(--line);border-radius:2px;overflow:hidden;}\n  .seg button{background:transparent;border:none;padding:8px 22px;font-size:12px;letter-spacing:2px;color:var(--sub);cursor:pointer;font-family:inherit;transition:.2s;}\n  .seg button.on{background:var(--ink);color:var(--paper);}\n  .sched-head{display:flex;justify-content:space-between;align-items:flex-end;margin-bottom:18px;}\n\n  .shift-defs{display:grid;grid-template-columns:repeat(3,1fr);gap:20px;padding:4px 0 0;}\n  .shift-defs .sd-item{display:flex;flex-direction:column;gap:6px;}\n  .shift-defs .sd-name{font-size:11px;color:var(--sub);letter-spacing:1px;}\n  .shift-time{width:100%;border:none;border-bottom:1px solid var(--line);background:transparent;font-size:13px;color:var(--ink);padding:8px 2px;letter-spacing:0;font-family:inherit;}\n  .shift-time::placeholder{color:#C9C0B5;}\n  .shift-time:focus{outline:none;border-bottom-color:var(--accent);}\n  @media(max-width:560px){.shift-defs{grid-template-columns:1fr;gap:14px;}}\n\n  table{width:100%;border-collapse:collapse;}\n  th,td{padding:10px 6px;text-align:center;font-size:13px;}\n  thead th{font-weight:500;color:var(--sub);font-size:11px;letter-spacing:1px;border-bottom:1px solid var(--line);}\n  .th-day{font-size:13px;color:var(--ink);}\n  .th-date{font-size:10px;color:var(--sub);letter-spacing:0;margin-top:3px;}\n  tbody td{border-bottom:1px solid var(--line-soft);}\n  td.rowhead{color:var(--ink);font-weight:500;letter-spacing:1px;text-align:left;padding-left:2px;white-space:nowrap;}\n  tr.weekend td.rowhead{color:var(--accent);}\n  td.pick{cursor:pointer;color:#C9C0B5;font-size:13px;transition:.15s;border-radius:3px;min-width:64px;}\n  td.pick:hover{background:var(--accent-soft);}\n  td.pick.has{color:var(--ink);font-weight:500;}\n  .rh-name{font-weight:500;}\n  .rh-time{font-size:10px;color:var(--sub);margin-top:2px;letter-spacing:0;}\n  .pick-row{display:flex;align-items:center;gap:10px;padding:9px 4px;border-bottom:1px solid var(--line-soft);cursor:pointer;font-size:14px;}\n  .pick-row input{width:16px;height:16px;accent-color:var(--accent);}\n  .scroll{overflow-x:auto;}\n\n  .actions{text-align:center;margin-top:44px;}\n  .btn{font-family:inherit;cursor:pointer;letter-spacing:2px;transition:.2s;border-radius:2px;}\n  .btn-primary{background:var(--accent);color:#fff;border:none;padding:14px 40px;font-size:14px;font-weight:500;}\n  .btn-primary:hover{background:#b06d28;}\n  .btn-ghost{background:transparent;color:var(--sub);border:1px solid var(--line);padding:13px 28px;font-size:13px;margin-left:10px;}\n  .btn-ghost:hover{border-color:var(--accent);color:var(--accent);}\n  .note{font-size:11px;color:var(--sub);margin-top:14px;letter-spacing:.5px;}\n\n  .goods{margin-top:64px;border-top:1px solid var(--line);padding-top:32px;}\n  .goods-label{text-align:center;font-size:11px;letter-spacing:6px;color:var(--accent);margin-bottom:26px;}\n  .goods-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(140px,1fr));gap:20px;}\n  .good{text-decoration:none;color:var(--ink);text-align:center;transition:.25s;}\n  .good:hover{transform:translateY(-4px);}\n  .good .imgbox{width:100%;aspect-ratio:1;border-radius:3px;overflow:hidden;background:var(--line-soft);}\n  .good img{width:100%;height:100%;object-fit:cover;display:block;}\n  .good .tag{font-size:10px;color:var(--sub);letter-spacing:2px;margin-top:12px;}\n  .good .nm{font-size:13px;font-weight:500;letter-spacing:1px;margin-top:3px;}\n\n  footer{text-align:center;padding:48px 20px 30px;}\n  .foot-line{font-size:11px;color:var(--sub);letter-spacing:1px;margin-bottom:18px;}\n  .contact{display:flex;justify-content:center;flex-wrap:wrap;gap:12px;}\n  .cbtn{display:inline-flex;align-items:center;gap:7px;text-decoration:none;font-size:12px;letter-spacing:1px;padding:9px 18px;border-radius:2px;transition:.2s;}\n  .cbtn.line{background:#06C755;color:#fff;}\n  .cbtn.line:hover{background:#05a648;}\n  .cbtn.mail{background:transparent;color:var(--ink);border:1px solid var(--line);}\n  .cbtn.mail:hover{border-color:var(--accent);color:var(--accent);}\n\n  \/* 匯出畫布 *\/\n  #exportTmp{color:var(--ink);}\n  #exportTmp .ex-top{text-align:center;border-bottom:1px solid var(--line);padding-bottom:24px;margin-bottom:24px;}\n  #exportTmp .ex-title{font-family:\u0026quot;Noto Serif TC\u0026quot;,serif;font-size:22px;letter-spacing:6px;font-weight:500;}\n  #exportTmp .ex-meta{font-size:12px;color:var(--sub);letter-spacing:1px;margin-top:8px;}\n  #exportTmp .ex-shifts{text-align:center;font-size:12px;color:var(--ink);letter-spacing:1px;margin:0 0 18px;}\n  #exportTmp table{margin-top:4px;}\n  #exportTmp th,#exportTmp td{padding:11px 6px;font-size:13px;}\n  #exportTmp thead th{color:var(--sub);font-size:11px;border-bottom:1px solid var(--line);}\n  #exportTmp tbody td{border-bottom:1px solid var(--line-soft);}\n  #exportTmp td.rowhead{text-align:left;font-weight:500;color:var(--ink);white-space:nowrap;}\n  #exportTmp tr.weekend td.rowhead{color:var(--accent);}\n  #exportTmp .ex-foot{text-align:center;margin-top:32px;padding-top:20px;border-top:1px solid var(--line);font-size:10px;letter-spacing:3px;color:var(--sub);}\n\u0026lt;\/style\u0026gt;\n\u0026lt;\/head\u0026gt;\n\u0026lt;body\u0026gt;\n\u0026lt;div class=\u0026quot;wrap\u0026quot;\u0026gt;\n  \u0026lt;div class=\u0026quot;top\u0026quot;\u0026gt;\n    \u0026lt;div class=\u0026quot;eyebrow\u0026quot;\u0026gt;SCHEDULE\u0026lt;\/div\u0026gt;\n    \u0026lt;h1 class=\u0026quot;serif\u0026quot;\u0026gt;排班表\u0026lt;\/h1\u0026gt;\n    \u0026lt;div class=\u0026quot;sub\u0026quot;\u0026gt;多店管理 · 自由增減人員 · 週／月切換 · 一鍵存圖\u0026lt;\/div\u0026gt;\n    \u0026lt;div class=\u0026quot;rule\u0026quot;\u0026gt;\u0026lt;\/div\u0026gt;\n  \u0026lt;\/div\u0026gt;\n\n  \u0026lt;!-- 店家 --\u0026gt;\n  \u0026lt;div class=\u0026quot;block\u0026quot;\u0026gt;\n    \u0026lt;div class=\u0026quot;block-label\u0026quot;\u0026gt;店家\u0026lt;\/div\u0026gt;\n    \u0026lt;div class=\u0026quot;stores\u0026quot; id=\u0026quot;stores\u0026quot;\u0026gt;\u0026lt;\/div\u0026gt;\n  \u0026lt;\/div\u0026gt;\n\n  \u0026lt;!-- 基本設定 --\u0026gt;\n  \u0026lt;div class=\u0026quot;block\u0026quot;\u0026gt;\n    \u0026lt;div class=\u0026quot;block-label\u0026quot;\u0026gt;基本設定\u0026lt;\/div\u0026gt;\n    \u0026lt;div class=\u0026quot;field-row\u0026quot;\u0026gt;\n      \u0026lt;div class=\u0026quot;field\u0026quot;\u0026gt;\u0026lt;label\u0026gt;店點名稱\u0026lt;\/label\u0026gt;\u0026lt;input type=\u0026quot;text\u0026quot; id=\u0026quot;area\u0026quot; placeholder=\u0026quot;店名\u0026quot; oninput=\u0026quot;onStoreName(this)\u0026quot;\u0026gt;\u0026lt;\/div\u0026gt;\n      \u0026lt;div class=\u0026quot;field\u0026quot; id=\u0026quot;weekField\u0026quot;\u0026gt;\u0026lt;label\u0026gt;本週起始（週一）\u0026lt;\/label\u0026gt;\u0026lt;input type=\u0026quot;date\u0026quot; id=\u0026quot;weekStart\u0026quot;\u0026gt;\u0026lt;\/div\u0026gt;\n      \u0026lt;div class=\u0026quot;field\u0026quot; id=\u0026quot;monthField\u0026quot; style=\u0026quot;display:none;\u0026quot;\u0026gt;\u0026lt;label\u0026gt;排班月份\u0026lt;\/label\u0026gt;\u0026lt;input type=\u0026quot;month\u0026quot; id=\u0026quot;monthPick\u0026quot;\u0026gt;\u0026lt;\/div\u0026gt;\n    \u0026lt;\/div\u0026gt;\n  \u0026lt;\/div\u0026gt;\n\n  \u0026lt;!-- 人員 --\u0026gt;\n  \u0026lt;div class=\u0026quot;block\u0026quot;\u0026gt;\n    \u0026lt;div class=\u0026quot;block-label\u0026quot;\u0026gt;人員（最多 12 位）\u0026lt;\/div\u0026gt;\n    \u0026lt;div class=\u0026quot;people\u0026quot; id=\u0026quot;people\u0026quot;\u0026gt;\u0026lt;\/div\u0026gt;\n  \u0026lt;\/div\u0026gt;\n\n  \u0026lt;!-- 班別時段 --\u0026gt;\n  \u0026lt;div class=\u0026quot;block\u0026quot;\u0026gt;\n    \u0026lt;div class=\u0026quot;block-label\u0026quot;\u0026gt;班別時段（選填）\u0026lt;\/div\u0026gt;\n    \u0026lt;div class=\u0026quot;shift-defs\u0026quot; id=\u0026quot;shiftDefs\u0026quot;\u0026gt;\u0026lt;\/div\u0026gt;\n  \u0026lt;\/div\u0026gt;\n\n  \u0026lt;!-- 排班表 --\u0026gt;\n  \u0026lt;div class=\u0026quot;block\u0026quot;\u0026gt;\n    \u0026lt;div class=\u0026quot;sched-head\u0026quot;\u0026gt;\n      \u0026lt;div class=\u0026quot;block-label\u0026quot; style=\u0026quot;margin-bottom:0;\u0026quot;\u0026gt;排班內容\u0026lt;\/div\u0026gt;\n      \u0026lt;div class=\u0026quot;seg\u0026quot;\u0026gt;\n        \u0026lt;button id=\u0026quot;segW\u0026quot; class=\u0026quot;on\u0026quot; onclick=\u0026quot;setMode('week')\u0026quot;\u0026gt;週\u0026lt;\/button\u0026gt;\n        \u0026lt;button id=\u0026quot;segM\u0026quot; onclick=\u0026quot;setMode('month')\u0026quot;\u0026gt;月\u0026lt;\/button\u0026gt;\n      \u0026lt;\/div\u0026gt;\n    \u0026lt;\/div\u0026gt;\n    \u0026lt;div class=\u0026quot;scroll\u0026quot;\u0026gt;\u0026lt;table id=\u0026quot;schedule\u0026quot;\u0026gt;\u0026lt;thead\u0026gt;\u0026lt;\/thead\u0026gt;\u0026lt;tbody\u0026gt;\u0026lt;\/tbody\u0026gt;\u0026lt;\/table\u0026gt;\u0026lt;\/div\u0026gt;\n  \u0026lt;\/div\u0026gt;\n\n  \u0026lt;div class=\u0026quot;actions\u0026quot;\u0026gt;\n    \u0026lt;button class=\u0026quot;btn btn-primary\u0026quot; onclick=\u0026quot;downloadImage()\u0026quot;\u0026gt;儲存為圖片\u0026lt;\/button\u0026gt;\n    \u0026lt;button class=\u0026quot;btn btn-ghost\u0026quot; onclick=\u0026quot;clearStore()\u0026quot;\u0026gt;清空本店\u0026lt;\/button\u0026gt;\n    \u0026lt;div class=\u0026quot;note\u0026quot;\u0026gt;產生目前店家的排班表圖片，可儲存後分享至 Line、郵件或公告\u0026lt;\/div\u0026gt;\n  \u0026lt;\/div\u0026gt;\n\n  \u0026lt;div class=\u0026quot;goods\u0026quot;\u0026gt;\n    \u0026lt;div class=\u0026quot;goods-label\u0026quot;\u0026gt;經典木作\u0026lt;\/div\u0026gt;\n    \u0026lt;div class=\u0026quot;goods-grid\u0026quot; id=\u0026quot;goods\u0026quot;\u0026gt;\u0026lt;\/div\u0026gt;\n  \u0026lt;\/div\u0026gt;\n\u0026lt;\/div\u0026gt;\n\n\u0026lt;footer\u0026gt;\n  \u0026lt;div class=\u0026quot;foot-line\u0026quot;\u0026gt;免費排班工具 · 需客製功能歡迎洽詢\u0026lt;\/div\u0026gt;\n  \u0026lt;div class=\u0026quot;contact\u0026quot;\u0026gt;\n    \u0026lt;a class=\u0026quot;cbtn line\u0026quot; href=\u0026quot;https:\/\/line.me\/R\/ti\/p\/@wkidea\u0026quot; target=\u0026quot;_blank\u0026quot;\u0026gt;LINE　@wkidea\u0026lt;\/a\u0026gt;\n    \u0026lt;a class=\u0026quot;cbtn mail\u0026quot; href=\u0026quot;mailto:service@wkidea.com\u0026quot;\u0026gt;service@wkidea.com\u0026lt;\/a\u0026gt;\n  \u0026lt;\/div\u0026gt;\n\u0026lt;\/footer\u0026gt;\n\n\u0026lt;!-- 人員勾選彈窗 --\u0026gt;\n\u0026lt;div id=\u0026quot;pickerOverlay\u0026quot; style=\u0026quot;display:none;position:fixed;inset:0;z-index:9998;background:rgba(58,53,48,.55);align-items:center;justify-content:center;padding:24px;\u0026quot;\u0026gt;\n  \u0026lt;div style=\u0026quot;background:#fff;border-radius:6px;max-width:320px;width:100%;padding:24px;max-height:80vh;overflow:auto;\u0026quot;\u0026gt;\n    \u0026lt;div style=\u0026quot;font-size:13px;letter-spacing:2px;color:#8A8076;margin-bottom:16px;\u0026quot;\u0026gt;選擇值班人員\u0026lt;\/div\u0026gt;\n    \u0026lt;div id=\u0026quot;pickerList\u0026quot;\u0026gt;\u0026lt;\/div\u0026gt;\n    \u0026lt;div style=\u0026quot;display:flex;gap:10px;margin-top:20px;\u0026quot;\u0026gt;\n      \u0026lt;button onclick=\u0026quot;pickerSave()\u0026quot; style=\u0026quot;flex:1;background:#C77B30;color:#fff;border:none;border-radius:2px;padding:12px;font-size:14px;cursor:pointer;font-family:inherit;letter-spacing:1px;\u0026quot;\u0026gt;確定\u0026lt;\/button\u0026gt;\n      \u0026lt;button onclick=\u0026quot;closePicker()\u0026quot; style=\u0026quot;background:transparent;color:#8A8076;border:1px solid #E5DDD2;border-radius:2px;padding:12px 18px;font-size:13px;cursor:pointer;font-family:inherit;\u0026quot;\u0026gt;取消\u0026lt;\/button\u0026gt;\n    \u0026lt;\/div\u0026gt;\n  \u0026lt;\/div\u0026gt;\n\u0026lt;\/div\u0026gt;\n\n\u0026lt;!-- 圖片結果覆蓋層（iOS 長按儲存） --\u0026gt;\n\u0026lt;div id=\u0026quot;imgOverlay\u0026quot; style=\u0026quot;display:none;position:fixed;inset:0;z-index:9999;background:rgba(58,53,48,.92);overflow:auto;padding:24px 16px;text-align:center;\u0026quot;\u0026gt;\n  \u0026lt;div style=\u0026quot;max-width:560px;margin:0 auto;\u0026quot;\u0026gt;\n    \u0026lt;div style=\u0026quot;display:flex;justify-content:space-between;align-items:center;margin-bottom:14px;\u0026quot;\u0026gt;\n      \u0026lt;span style=\u0026quot;color:#fff;font-size:13px;letter-spacing:1px;\u0026quot;\u0026gt;長按圖片 →「加入照片」即可儲存\u0026lt;\/span\u0026gt;\n      \u0026lt;button onclick=\u0026quot;closeOverlay()\u0026quot; style=\u0026quot;background:#fff;border:none;border-radius:2px;padding:8px 16px;font-size:13px;cursor:pointer;font-family:inherit;\u0026quot;\u0026gt;關閉\u0026lt;\/button\u0026gt;\n    \u0026lt;\/div\u0026gt;\n    \u0026lt;img id=\u0026quot;resultImg\u0026quot; style=\u0026quot;width:100%;border-radius:4px;box-shadow:0 8px 30px rgba(0,0,0,.3);\u0026quot; alt=\u0026quot;排班表\u0026quot;\u0026gt;\n    \u0026lt;a id=\u0026quot;resultDownload\u0026quot; download=\u0026quot;排班表.png\u0026quot; style=\u0026quot;display:inline-block;margin-top:16px;color:#fff;font-size:13px;letter-spacing:1px;text-decoration:underline;\u0026quot;\u0026gt;或點此下載（電腦版）\u0026lt;\/a\u0026gt;\n  \u0026lt;\/div\u0026gt;\n\u0026lt;\/div\u0026gt;\n\n\u0026lt;script\u0026gt;\nconst LS_KEY=\u0026quot;wk_schedule_multi_v2\u0026quot;;\nconst MAX_PEOPLE=12;\nconst dayLabels=[\u0026quot;一\u0026quot;,\u0026quot;二\u0026quot;,\u0026quot;三\u0026quot;,\u0026quot;四\u0026quot;,\u0026quot;五\u0026quot;,\u0026quot;六\u0026quot;,\u0026quot;日\u0026quot;];\nconst shiftNames=[\u0026quot;早班\u0026quot;,\u0026quot;午班\u0026quot;,\u0026quot;晚班\u0026quot;];\n\n\/* ---------- 資料模型 ----------\n  state = { activeId, stores:[ store ] }\n  store = { id, name, weekStart, monthPick, mode,\n            people:[name,...], shiftTimes:[t,t,t],\n            cells:{ \u0026quot;week-r-c\u0026quot;:v, \u0026quot;month-r-c\u0026quot;:v } }\n*\/\nlet state=loadState();\n\nfunction newStore(name){\n  return {id:\u0026quot;s\u0026quot;+Date.now()+Math.floor(Math.random()*999),\n    name:name||\u0026quot;新店家\u0026quot;, weekStart:\u0026quot;\u0026quot;, monthPick:\u0026quot;\u0026quot;, mode:\u0026quot;week\u0026quot;,\n    people:[\u0026quot;\u0026quot;,\u0026quot;\u0026quot;], shiftTimes:[\u0026quot;\u0026quot;,\u0026quot;\u0026quot;,\u0026quot;\u0026quot;], cells:{}};\n}\nfunction loadState(){\n  try{const s=JSON.parse(localStorage.getItem(LS_KEY)); if(s\u0026amp;\u0026amp;s.stores\u0026amp;\u0026amp;s.stores.length) return s;}catch(e){}\n  const first=newStore(\u0026quot;店家一\u0026quot;); first.people=[\u0026quot;\u0026quot;,\u0026quot;\u0026quot;];\n  return {activeId:first.id, stores:[first]};\n}\nfunction saveState(){ try{localStorage.setItem(LS_KEY,JSON.stringify(state));}catch(e){} }\nfunction active(){ return state.stores.find(s=\u0026gt;s.id===state.activeId)||state.stores[0]; }\n\n\/* ---------- 店家列 ---------- *\/\nfunction renderStores(){\n  const wrap=document.getElementById(\u0026quot;stores\u0026quot;); wrap.innerHTML=\u0026quot;\u0026quot;;\n  state.stores.forEach(s=\u0026gt;{\n    const b=document.createElement(\u0026quot;button\u0026quot;);\n    b.className=\u0026quot;store-tab\u0026quot;+(s.id===state.activeId?\u0026quot; on\u0026quot;:\u0026quot;\u0026quot;);\n    b.textContent=s.name||\u0026quot;未命名\u0026quot;;\n    b.onclick=()=\u0026gt;{ state.activeId=s.id; saveState(); renderAll(); };\n    wrap.appendChild(b);\n  });\n  const add=document.createElement(\u0026quot;button\u0026quot;);\n  add.className=\u0026quot;store-add\u0026quot;; add.textContent=\u0026quot;＋ 新增店家\u0026quot;;\n  add.onclick=()=\u0026gt;{ const s=newStore(\u0026quot;店家\u0026quot;+(state.stores.length+1)); state.stores.push(s); state.activeId=s.id; saveState(); renderAll(); };\n  wrap.appendChild(add);\n\n  const tools=document.createElement(\u0026quot;div\u0026quot;); tools.className=\u0026quot;store-tools\u0026quot;;\n  if(state.stores.length\u0026gt;1){\n    const del=document.createElement(\u0026quot;button\u0026quot;); del.className=\u0026quot;mini\u0026quot;; del.textContent=\u0026quot;刪除本店\u0026quot;;\n    del.onclick=()=\u0026gt;{\n      if(!confirm(\u0026quot;確定刪除「\u0026quot;+active().name+\u0026quot;」及其排班？\u0026quot;)) return;\n      state.stores=state.stores.filter(s=\u0026gt;s.id!==state.activeId);\n      state.activeId=state.stores[0].id; saveState(); renderAll();\n    };\n    tools.appendChild(del);\n  }\n  wrap.appendChild(tools);\n}\nfunction onStoreName(el){ active().name=el.value; saveState(); renderStores(); }\n\n\/* ---------- 基本欄位 ---------- *\/\nfunction renderBasics(){\n  const s=active();\n  document.getElementById(\u0026quot;area\u0026quot;).value=s.name;\n  document.getElementById(\u0026quot;weekStart\u0026quot;).value=s.weekStart||\u0026quot;\u0026quot;;\n  document.getElementById(\u0026quot;monthPick\u0026quot;).value=s.monthPick||\u0026quot;\u0026quot;;\n  document.getElementById(\u0026quot;segW\u0026quot;).classList.toggle(\u0026quot;on\u0026quot;,s.mode!==\u0026quot;month\u0026quot;);\n  document.getElementById(\u0026quot;segM\u0026quot;).classList.toggle(\u0026quot;on\u0026quot;,s.mode===\u0026quot;month\u0026quot;);\n  document.getElementById(\u0026quot;weekField\u0026quot;).style.display=s.mode===\u0026quot;month\u0026quot;?\u0026quot;none\u0026quot;:\u0026quot;\u0026quot;;\n  document.getElementById(\u0026quot;monthField\u0026quot;).style.display=s.mode===\u0026quot;month\u0026quot;?\u0026quot;\u0026quot;:\u0026quot;none\u0026quot;;\n}\n\n\/* ---------- 人員 ---------- *\/\nfunction renderPeople(){\n  const s=active(); const wrap=document.getElementById(\u0026quot;people\u0026quot;); wrap.innerHTML=\u0026quot;\u0026quot;;\n  s.people.forEach((nm,i)=\u0026gt;{\n    const chip=document.createElement(\u0026quot;div\u0026quot;); chip.className=\u0026quot;person-chip\u0026quot;;\n    const inp=document.createElement(\u0026quot;input\u0026quot;); inp.type=\u0026quot;text\u0026quot;; inp.value=nm; inp.placeholder=\u0026quot;姓名 \u0026quot;+(i+1);\n    inp.oninput=()=\u0026gt;{ s.people[i]=inp.value; saveState(); renderTable(); };\n    chip.appendChild(inp);\n    const del=document.createElement(\u0026quot;button\u0026quot;); del.className=\u0026quot;del\u0026quot;; del.textContent=\u0026quot;×\u0026quot;; del.title=\u0026quot;移除\u0026quot;;\n    del.onclick=()=\u0026gt;{ if(s.people.length\u0026lt;=1){alert(\u0026quot;至少保留一位人員。\u0026quot;);return;} s.people.splice(i,1); saveState(); renderPeople(); renderTable(); };\n    chip.appendChild(del);\n    wrap.appendChild(chip);\n  });\n  const add=document.createElement(\u0026quot;button\u0026quot;); add.className=\u0026quot;add-person\u0026quot;; add.textContent=\u0026quot;＋ 新增人員\u0026quot;;\n  if(s.people.length\u0026gt;=MAX_PEOPLE){ add.disabled=true; add.textContent=\u0026quot;已達 \u0026quot;+MAX_PEOPLE+\u0026quot; 位上限\u0026quot;; }\n  add.onclick=()=\u0026gt;{ if(s.people.length\u0026lt;MAX_PEOPLE){ s.people.push(\u0026quot;\u0026quot;); saveState(); renderPeople(); renderTable(); } };\n  wrap.appendChild(add);\n}\nfunction personNames(){ return active().people.map((n,i)=\u0026gt;n.trim()||(\u0026quot;人員\u0026quot;+(i+1))); }\n\n\/* ---------- 班別時段 ---------- *\/\nfunction renderShiftDefs(){\n  const s=active(); const wrap=document.getElementById(\u0026quot;shiftDefs\u0026quot;); wrap.innerHTML=\u0026quot;\u0026quot;;\n  shiftNames.forEach((nm,i)=\u0026gt;{\n    const item=document.createElement(\u0026quot;div\u0026quot;); item.className=\u0026quot;sd-item\u0026quot;;\n    const lbl=document.createElement(\u0026quot;span\u0026quot;); lbl.className=\u0026quot;sd-name\u0026quot;; lbl.textContent=nm; item.appendChild(lbl);\n    const t=document.createElement(\u0026quot;input\u0026quot;); t.className=\u0026quot;shift-time\u0026quot;; t.type=\u0026quot;text\u0026quot;; t.value=s.shiftTimes[i]||\u0026quot;\u0026quot;; t.placeholder=\u0026quot;例如 09:00~14:00\u0026quot;;\n    t.oninput=()=\u0026gt;{ s.shiftTimes[i]=t.value; saveState(); };\n    item.appendChild(t); wrap.appendChild(item);\n  });\n}\n\n\/* ---------- 排班表 ---------- *\/\nfunction setMode(m){ active().mode=m; saveState(); renderBasics(); renderTable(); }\ndocument.getElementById(\u0026quot;weekStart\u0026quot;).addEventListener(\u0026quot;change\u0026quot;,e=\u0026gt;{ active().weekStart=e.target.value; saveState(); renderTable(); });\ndocument.getElementById(\u0026quot;monthPick\u0026quot;).addEventListener(\u0026quot;change\u0026quot;,e=\u0026gt;{ active().monthPick=e.target.value; saveState(); renderTable(); });\n\nfunction weekDates(ws){\n  if(!ws) return [];\n  const base=new Date(ws+\u0026quot;T00:00:00\u0026quot;); const out=[];\n  for(let i=0;i\u0026lt;7;i++){ const d=new Date(base); d.setDate(base.getDate()+i); out.push((d.getMonth()+1)+\u0026quot;\/\u0026quot;+d.getDate()); }\n  return out;\n}\n\nfunction renderTable(){\n  const s=active(); const mode=s.mode;\n  const thead=document.querySelector(\u0026quot;#schedule thead\u0026quot;);\n  const tbody=document.querySelector(\u0026quot;#schedule tbody\u0026quot;);\n  thead.innerHTML=\u0026quot;\u0026quot;; tbody.innerHTML=\u0026quot;\u0026quot;;\n  const names=personNames();\n\n  if(mode!==\u0026quot;month\u0026quot;){\n    \/\/ 週：班別 × 七天，格子點選打勾（可多人）\n    const dates=weekDates(s.weekStart);\n    let h=\u0026quot;\u0026lt;tr\u0026gt;\u0026lt;th style='text-align:left'\u0026gt;班別\u0026lt;\/th\u0026gt;\u0026quot;;\n    dayLabels.forEach((d,i)=\u0026gt;{ const sub=dates[i]?`\u0026lt;div class=\u0026quot;th-date\u0026quot;\u0026gt;${dates[i]}\u0026lt;\/div\u0026gt;`:\u0026quot;\u0026quot;; h+=`\u0026lt;th\u0026gt;\u0026lt;div class=\u0026quot;th-day\u0026quot;\u0026gt;${d}\u0026lt;\/div\u0026gt;${sub}\u0026lt;\/th\u0026gt;`; });\n    h+=\u0026quot;\u0026lt;\/tr\u0026gt;\u0026quot;; thead.innerHTML=h;\n    shiftNames.forEach((sn,r)=\u0026gt;{\n      const tr=document.createElement(\u0026quot;tr\u0026quot;);\n      const tline=s.shiftTimes[r]?`\u0026lt;div class=\u0026quot;rh-time\u0026quot;\u0026gt;${esc(s.shiftTimes[r])}\u0026lt;\/div\u0026gt;`:\u0026quot;\u0026quot;;\n      let html=`\u0026lt;td class=\u0026quot;rowhead\u0026quot;\u0026gt;\u0026lt;div class=\u0026quot;rh-name\u0026quot;\u0026gt;${sn}\u0026lt;\/div\u0026gt;${tline}\u0026lt;\/td\u0026gt;`;\n      for(let c=0;c\u0026lt;7;c++){ const key=`week-${r}-${c}`; html+=cellHTML(key,s.cells[key],names); }\n      tr.innerHTML=html; tbody.appendChild(tr);\n    });\n  }else{\n    \/\/ 月：日期 × 班別，格子點選打勾（可多人）\n    let h=`\u0026lt;tr\u0026gt;\u0026lt;th style='text-align:left;min-width:80px'\u0026gt;日期\u0026lt;\/th\u0026gt;`;\n    shiftNames.forEach((sn,i)=\u0026gt;{ const t=s.shiftTimes[i]?`\u0026lt;div class=\u0026quot;th-date\u0026quot;\u0026gt;${esc(s.shiftTimes[i])}\u0026lt;\/div\u0026gt;`:\u0026quot;\u0026quot;; h+=`\u0026lt;th\u0026gt;\u0026lt;div class=\u0026quot;th-day\u0026quot;\u0026gt;${sn}\u0026lt;\/div\u0026gt;${t}\u0026lt;\/th\u0026gt;`; });\n    h+=\u0026quot;\u0026lt;\/tr\u0026gt;\u0026quot;; thead.innerHTML=h;\n    const ym=s.monthPick; let days=31,year=null,month=null;\n    if(ym){ [year,month]=ym.split(\u0026quot;-\u0026quot;).map(Number); days=new Date(year,month,0).getDate(); }\n    for(let d=1;d\u0026lt;=days;d++){\n      const tr=document.createElement(\u0026quot;tr\u0026quot;);\n      let label=d+\u0026quot;日\u0026quot;;\n      if(year){ const wd=new Date(year,month-1,d).getDay(); label=`${d}（${dayLabels[(wd+6)%7]}）`; if(wd===0||wd===6) tr.className=\u0026quot;weekend\u0026quot;; }\n      let html=`\u0026lt;td class=\u0026quot;rowhead\u0026quot;\u0026gt;${label}\u0026lt;\/td\u0026gt;`;\n      for(let i=0;i\u0026lt;shiftNames.length;i++){ const key=`month-${d-1}-${i}`; html+=cellHTML(key,s.cells[key],names); }\n      tr.innerHTML=html; tbody.appendChild(tr);\n    }\n  }\n  \/\/ 綁格子點擊 → 開勾選彈窗\n  tbody.querySelectorAll(\u0026quot;td.pick\u0026quot;).forEach(td=\u0026gt;{\n    td.addEventListener(\u0026quot;click\u0026quot;,()=\u0026gt;openPicker(td.dataset.k));\n  });\n}\n\n\/\/ 產生一個可點選格子的 HTML\nfunction cellHTML(key,val,names){\n  const arr=Array.isArray(val)?val:[];\n  const txt=arr.filter(i=\u0026gt;i\u0026lt;names.length).map(i=\u0026gt;names[i]).join(\u0026quot;、\u0026quot;);\n  const cls=txt?\u0026quot;pick has\u0026quot;:\u0026quot;pick\u0026quot;;\n  return `\u0026lt;td class=\u0026quot;${cls}\u0026quot; data-k=\u0026quot;${key}\u0026quot;\u0026gt;${txt?esc(txt):\u0026quot;＋\u0026quot;}\u0026lt;\/td\u0026gt;`;\n}\n\n\/* ---------- 人員勾選彈窗 ---------- *\/\nlet _pickKey=null;\nfunction openPicker(key){\n  _pickKey=key;\n  const s=active(); const names=personNames();\n  const cur=Array.isArray(s.cells[key])?s.cells[key]:[];\n  const box=document.getElementById(\u0026quot;pickerList\u0026quot;); box.innerHTML=\u0026quot;\u0026quot;;\n  names.forEach((nm,i)=\u0026gt;{\n    const row=document.createElement(\u0026quot;label\u0026quot;); row.className=\u0026quot;pick-row\u0026quot;;\n    const cb=document.createElement(\u0026quot;input\u0026quot;); cb.type=\u0026quot;checkbox\u0026quot;; cb.checked=cur.includes(i); cb.value=i;\n    row.appendChild(cb);\n    const sp=document.createElement(\u0026quot;span\u0026quot;); sp.textContent=nm; row.appendChild(sp);\n    box.appendChild(row);\n  });\n  document.getElementById(\u0026quot;pickerOverlay\u0026quot;).style.display=\u0026quot;flex\u0026quot;;\n}\nfunction pickerSave(){\n  const s=active(); const sel=[];\n  document.querySelectorAll(\u0026quot;#pickerList input:checked\u0026quot;).forEach(cb=\u0026gt;sel.push(+cb.value));\n  if(sel.length) s.cells[_pickKey]=sel; else delete s.cells[_pickKey];\n  saveState(); closePicker(); renderTable();\n}\nfunction closePicker(){ document.getElementById(\u0026quot;pickerOverlay\u0026quot;).style.display=\u0026quot;none\u0026quot;; _pickKey=null; }\nfunction esc(t){ return (t||\u0026quot;\u0026quot;).replace(\/\u0026amp;\/g,\u0026quot;\u0026amp;amp;\u0026quot;).replace(\/\u0026quot;\/g,\u0026quot;\u0026amp;quot;\u0026quot;).replace(\/\u0026lt;\/g,\u0026quot;\u0026amp;lt;\u0026quot;).replace(\/\u0026gt;\/g,\u0026quot;\u0026amp;gt;\u0026quot;); }\n\n\/* ---------- 清空本店 ---------- *\/\nfunction clearStore(){\n  if(!confirm(\u0026quot;清空「\u0026quot;+active().name+\u0026quot;」的排班內容？（人員保留）\u0026quot;)) return;\n  active().cells={}; saveState(); renderTable();\n}\n\n\/* ---------- 商品 ---------- *\/\nconst products=[\n  {tag:\u0026quot;木與科技\u0026quot;,name:\u0026quot;實木防摔殼\u0026quot;,img:\u0026quot;https:\/\/cdn.shopify.com\/s\/files\/1\/0761\/1619\/7653\/files\/DSC01744_5350ebd3-d03b-4996-ab27-63fe51a1a206.jpg?v=1781842551\u0026quot;,url:\u0026quot;https:\/\/wkidea.com\/collections\/wood-tech-wooden-phone-cases\u0026quot;},\n  {tag:\u0026quot;木與建築\u0026quot;,name:\u0026quot;開關面板\u0026quot;,img:\u0026quot;https:\/\/cdn.shopify.com\/s\/files\/1\/0761\/1619\/7653\/files\/DSC01829.jpg?v=1781779314\u0026quot;,url:\u0026quot;https:\/\/wkidea.com\/collections\/architecture\u0026quot;},\n  {tag:\u0026quot;木與職場\u0026quot;,name:\u0026quot;側推華蓋式名片盒\u0026quot;,img:\u0026quot;https:\/\/cdn.shopify.com\/s\/files\/1\/0761\/1619\/7653\/files\/897688fd3b31086655979ed95083a58c.jpg?v=1772165497\u0026quot;,url:\u0026quot;https:\/\/wkidea.com\/collections\/wood-work\u0026quot;},\n  {tag:\u0026quot;木與生活\u0026quot;,name:\u0026quot;扁柏擴香組合\u0026quot;,img:\u0026quot;https:\/\/cdn.shopify.com\/s\/files\/1\/0761\/1619\/7653\/files\/v4.png?v=1777013396\u0026quot;,url:\u0026quot;https:\/\/wkidea.com\/collections\/life\u0026quot;},\n  {tag:\u0026quot;永續良品\u0026quot;,name:\u0026quot;原木杯墊手機立座\u0026quot;,img:\u0026quot;https:\/\/cdn.shopify.com\/s\/files\/1\/0761\/1619\/7653\/files\/DSC01792.jpg?v=1781324623\u0026quot;,url:\u0026quot;https:\/\/wkidea.com\/collections\/sustainable\u0026quot;}\n];\nfunction renderGoods(){\n  const gw=document.getElementById(\u0026quot;goods\u0026quot;); gw.innerHTML=\u0026quot;\u0026quot;;\n  products.forEach(p=\u0026gt;{\n    const a=document.createElement(\u0026quot;a\u0026quot;); a.className=\u0026quot;good\u0026quot;; a.href=p.url; a.target=\u0026quot;_blank\u0026quot;;\n    a.innerHTML=`\u0026lt;div class=\u0026quot;imgbox\u0026quot;\u0026gt;\u0026lt;img src=\u0026quot;${p.img}\u0026quot; alt=\u0026quot;${p.name}\u0026quot;\u0026gt;\u0026lt;\/div\u0026gt;\u0026lt;div class=\u0026quot;tag\u0026quot;\u0026gt;${p.tag}\u0026lt;\/div\u0026gt;\u0026lt;div class=\u0026quot;nm\u0026quot;\u0026gt;${p.name}\u0026lt;\/div\u0026gt;`;\n    gw.appendChild(a);\n  });\n}\n\n\/* ---------- 匯出圖片 ---------- *\/\nlet _exporting=false;\nfunction downloadImage(){\n  if(_exporting) return;            \/\/ 防止重複點擊造成卡死\n  if(typeof html2canvas!==\u0026quot;function\u0026quot;){ alert(\u0026quot;圖片元件尚未載入完成，請稍候幾秒再試。\u0026quot;); return; }\n  _exporting=true;\n\n  const s=active(); const mode=s.mode; const area=s.name||\u0026quot;本店\u0026quot;;\n  let metaText,fileTag;\n  if(mode!==\u0026quot;month\u0026quot;){ const w=s.weekStart||\u0026quot;—\u0026quot;; metaText=`${area}　本週起始 ${w}`; fileTag=`週_${w}`; }\n  else{ const ym=s.monthPick||\u0026quot;—\u0026quot;; metaText=`${area}　${ym}`; fileTag=`月_${ym}`; }\n\n  \/\/ clone 表格，把空格的「＋」清掉\n  const src=document.getElementById(\u0026quot;schedule\u0026quot;);\n  const clone=src.cloneNode(true);\n  clone.querySelectorAll(\u0026quot;td.pick\u0026quot;).forEach(td=\u0026gt;{\n    if(!td.classList.contains(\u0026quot;has\u0026quot;)) td.textContent=\u0026quot;·\u0026quot;;\n    td.style.color=td.classList.contains(\u0026quot;has\u0026quot;)?\u0026quot;#3A3530\u0026quot;:\u0026quot;#C9C0B5\u0026quot;;\n  });\n\n  let shiftLine=\u0026quot;\u0026quot;;\n  const parts=shiftNames.map((nm,i)=\u0026gt;s.shiftTimes[i]?`${nm} ${s.shiftTimes[i]}`:null).filter(Boolean);\n  if(parts.length) shiftLine=`\u0026lt;div class=\u0026quot;ex-shifts\u0026quot;\u0026gt;${parts.join(\u0026quot;　／　\u0026quot;)}\u0026lt;\/div\u0026gt;`;\n\n  \/\/ 建立臨時渲染容器（可見但定位在最上層之外，渲染後移除）\n  const ex=document.createElement(\u0026quot;div\u0026quot;);\n  ex.id=\u0026quot;exportTmp\u0026quot;;\n  ex.style.cssText=\u0026quot;position:absolute;left:0;top:0;width:860px;background:#fff;padding:48px;font-family:'Noto Sans TC','PingFang TC',sans-serif;z-index:-1;\u0026quot;;\n  ex.innerHTML=`\u0026lt;div class=\u0026quot;ex-top\u0026quot;\u0026gt;\u0026lt;div class=\u0026quot;ex-title\u0026quot;\u0026gt;排班表\u0026lt;\/div\u0026gt;\u0026lt;div class=\u0026quot;ex-meta\u0026quot;\u0026gt;${metaText}\u0026lt;\/div\u0026gt;\u0026lt;\/div\u0026gt;${shiftLine}${clone.outerHTML}\u0026lt;div class=\u0026quot;ex-foot\u0026quot;\u0026gt;WKIDEA.COM　·　經典木作\u0026lt;\/div\u0026gt;`;\n  document.body.appendChild(ex);\n\n  const fileName=`排班表_${area}_${fileTag}.png`;\n  const cleanup=()=\u0026gt;{ if(ex.parentNode) ex.parentNode.removeChild(ex); _exporting=false; };\n\n  const run=()=\u0026gt;{\n    html2canvas(ex,{scale:2,backgroundColor:\u0026quot;#ffffff\u0026quot;,useCORS:true,logging:false,width:860,windowWidth:860}).then(canvas=\u0026gt;{\n      const dataUrl=canvas.toDataURL(\u0026quot;image\/png\u0026quot;);\n      const isMobile=\/iPhone|iPad|iPod|Android\/i.test(navigator.userAgent);\n      if(isMobile){\n        document.getElementById(\u0026quot;resultImg\u0026quot;).src=dataUrl;\n        const dl=document.getElementById(\u0026quot;resultDownload\u0026quot;); dl.href=dataUrl; dl.download=fileName;\n        document.getElementById(\u0026quot;imgOverlay\u0026quot;).style.display=\u0026quot;block\u0026quot;;\n        window.scrollTo(0,0);\n      }else{\n        const link=document.createElement(\u0026quot;a\u0026quot;); link.download=fileName; link.href=dataUrl; link.click();\n      }\n      cleanup();\n    }).catch(err=\u0026gt;{ console.error(err); alert(\u0026quot;圖片產生失敗，請稍候再試。\u0026quot;); cleanup(); });\n  };\n\n  \/\/ 字型就緒再渲染；設保險超時，避免 fonts.ready 不 resolve 時卡住\n  let done=false;\n  const fire=()=\u0026gt;{ if(done) return; done=true; run(); };\n  if(document.fonts\u0026amp;\u0026amp;document.fonts.ready){ document.fonts.ready.then(()=\u0026gt;setTimeout(fire,50)); }\n  setTimeout(fire,800);  \/\/ 後援：最多等 0.8 秒一定執行\n}\nfunction closeOverlay(){ document.getElementById(\u0026quot;imgOverlay\u0026quot;).style.display=\u0026quot;none\u0026quot;; }\n\n\/* ---------- 高度回報（嵌入用，節流避免連環觸發） ---------- *\/\nlet _hT=null;\nfunction postHeight(){\n  if(_hT) return;\n  _hT=setTimeout(()=\u0026gt;{ _hT=null;\n    try{ if(window.parent\u0026amp;\u0026amp;window.parent!==window){ window.parent.postMessage({wkScheduleHeight:document.body.scrollHeight},\u0026quot;*\u0026quot;); } }catch(e){}\n  },120);\n}\nwindow.addEventListener(\u0026quot;load\u0026quot;,postHeight);\nwindow.addEventListener(\u0026quot;resize\u0026quot;,postHeight);\nsetTimeout(postHeight,600); setTimeout(postHeight,1600);\n\n\/* ---------- 整體渲染 ---------- *\/\nfunction renderAll(){ renderStores(); renderBasics(); renderPeople(); renderShiftDefs(); renderTable(); }\nrenderGoods();\nrenderAll();\n\u0026lt;\/script\u0026gt;\n\u0026lt;\/body\u0026gt;\n\u0026lt;\/html\u0026gt;\n\"\u003e\u003c\/iframe\u003e\n\u003c\/div\u003e\n\u003cscript\u003e\nwindow.addEventListener(\"message\",function(e){\n  if(e.data\u0026\u0026e.data.wkScheduleHeight){\n    var f=document.getElementById(\"wkScheduleFrame\");\n    if(f)f.style.height=(e.data.wkScheduleHeight+20)+\"px\";\n  }\n});\n\u003c\/script\u003e","brand":"WKidea","offers":[{"title":"Default Title","offer_id":49987680010517,"sku":null,"price":0.0,"currency_code":"TWD","in_stock":false}],"url":"https:\/\/wkidea.com\/en\/products\/schedule","provider":"WKidea","version":"1.0","type":"link"}