|
<!DOCTYPE html>
|
|
<html lang="zh-Hant">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<title>MES系統 - 工單列印設定</title>
|
|
<style>
|
|
:root{
|
|
--blue-900:#1e3a8a; --blue-800:#1e40af; --blue-700:#1d4ed8; --blue-600:#2563eb; --blue-500:#3b82f6;
|
|
--gray-50:#f8fafc; --gray-100:#f1f5f9; --gray-200:#e2e8f0; --gray-300:#cbd5e1; --gray-500:#64748b; --gray-600:#475569; --gray-800:#1e293b;
|
|
--green-600:#16a34a; --green-50:#f0fdf4;
|
|
--amber-600:#d97706; --amber-50:#fffbeb;
|
|
--purple-600:#7c3aed; --purple-50:#f5f3ff;
|
|
--red-600:#dc2626; --red-50:#fef2f2;
|
|
}
|
|
*{box-sizing:border-box;}
|
|
body{margin:0;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI","PingFang TC","Microsoft JhengHei",sans-serif;background:var(--gray-100);color:var(--gray-800);display:flex;min-height:100vh;font-size:14px;}
|
|
|
|
.sidebar{width:220px;background:linear-gradient(180deg,var(--blue-900),var(--blue-800));color:#fff;flex-shrink:0;display:flex;flex-direction:column;}
|
|
.sidebar-header{padding:16px 18px;font-weight:700;font-size:16px;display:flex;align-items:center;justify-content:space-between;border-bottom:1px solid rgba(255,255,255,.12);}
|
|
.sidebar-header span.ver{font-size:11px;opacity:.6;font-weight:400;}
|
|
.nav-item{padding:12px 18px;font-size:13.5px;cursor:default;opacity:.85;border-left:3px solid transparent;}
|
|
.nav-item.active{background:rgba(255,255,255,.1);opacity:1;border-left-color:#fff;font-weight:600;}
|
|
.nav-sub{padding:10px 18px 10px 40px;font-size:13px;opacity:.85;}
|
|
.nav-sub.current{background:rgba(255,255,255,.15);opacity:1;font-weight:600;border-left:3px solid #fff;}
|
|
.sidebar-footer{margin-top:auto;padding:14px 18px;border-top:1px solid rgba(255,255,255,.12);display:flex;align-items:center;gap:10px;font-size:13px;}
|
|
.avatar{width:28px;height:28px;border-radius:50%;background:rgba(255,255,255,.2);display:flex;align-items:center;justify-content:center;font-size:12px;}
|
|
|
|
.main{flex:1;display:flex;flex-direction:column;min-width:0;}
|
|
.topbar{background:#fff;padding:16px 28px;font-size:19px;font-weight:700;border-bottom:1px solid var(--gray-200);}
|
|
.topbar .breadcrumb{font-size:12.5px;font-weight:400;color:var(--gray-500);margin-top:4px;}
|
|
.content{padding:20px 28px 40px;display:flex;flex-direction:column;gap:20px;}
|
|
|
|
.card{background:#fff;border-radius:10px;border:1px solid var(--gray-200);box-shadow:0 1px 2px rgba(0,0,0,.03);}
|
|
.card-head{padding:16px 20px;display:flex;align-items:center;justify-content:space-between;border-bottom:1px solid var(--gray-200);gap:16px;}
|
|
.card-head h2{margin:0;font-size:15.5px;}
|
|
.card-head p{margin:3px 0 0;font-size:12.5px;color:var(--gray-500);font-weight:400;}
|
|
|
|
.btn{border:none;border-radius:6px;padding:9px 16px;font-size:13.5px;font-weight:600;cursor:pointer;display:inline-flex;align-items:center;gap:6px;transition:filter .15s;white-space:nowrap;}
|
|
.btn:hover{filter:brightness(0.93);}
|
|
.btn:active{filter:brightness(0.85);}
|
|
.btn:disabled{opacity:.45;cursor:not-allowed;filter:none;}
|
|
.btn-green{background:var(--green-600);color:#fff;}
|
|
.btn-blue{background:var(--blue-600);color:#fff;}
|
|
.btn-gray{background:var(--gray-200);color:var(--gray-600);}
|
|
.btn-outline{background:#fff;color:var(--blue-600);border:1px solid var(--blue-500);}
|
|
.btn-sm{padding:6px 12px;font-size:12.5px;}
|
|
|
|
.status-pill{display:inline-flex;align-items:center;gap:6px;font-size:12.5px;font-weight:600;padding:5px 12px;border-radius:999px;}
|
|
.status-ok{background:var(--green-50);color:var(--green-600);}
|
|
.status-fail{background:var(--red-50);color:var(--red-600);}
|
|
.status-dot{width:7px;height:7px;border-radius:50%;background:currentColor;}
|
|
|
|
.field{display:flex;flex-direction:column;gap:5px;}
|
|
.field label{font-size:12.5px;color:var(--gray-600);font-weight:600;}
|
|
.field input[type=text],.field select{padding:9px 12px;border:1px solid var(--gray-300);border-radius:6px;font-size:13.5px;background:#fff;width:100%;}
|
|
.field input:focus,.field select:focus{outline:none;border-color:var(--blue-500);box-shadow:0 0 0 3px rgba(59,130,246,.15);}
|
|
|
|
table{width:100%;border-collapse:collapse;}
|
|
thead th{text-align:left;font-size:12.5px;color:var(--gray-500);font-weight:600;padding:12px 16px;border-bottom:1px solid var(--gray-200);white-space:nowrap;background:#fff;}
|
|
tbody td{padding:14px 16px;border-bottom:1px solid var(--gray-100);font-size:13.5px;vertical-align:middle;}
|
|
tbody tr:hover{background:var(--gray-50);}
|
|
|
|
.tag{display:inline-block;padding:4px 12px;border-radius:999px;font-size:12px;font-weight:700;letter-spacing:.3px;}
|
|
.tag-ok{background:var(--green-50);color:var(--green-600);border:1px solid #bbf7d0;}
|
|
.tag-myok{background:var(--amber-50);color:var(--amber-600);border:1px solid #fde68a;}
|
|
.tag-rgp{background:var(--purple-50);color:var(--purple-600);border:1px solid #ddd6fe;}
|
|
.tag-custom{background:var(--gray-100);color:var(--gray-600);border:1px solid var(--gray-300);}
|
|
|
|
select.api-select{padding:8px 10px;border:1px solid var(--gray-300);border-radius:6px;font-size:13px;min-width:220px;background:#fff;}
|
|
.endpoint-text{font-family:Consolas,monospace;font-size:12px;color:var(--gray-500);word-break:break-all;}
|
|
|
|
.tray-btn{
|
|
display:inline-flex;flex-direction:column;align-items:flex-start;gap:3px;
|
|
background:#fff;border:1px solid var(--gray-300);border-radius:8px;padding:8px 12px;cursor:pointer;font-size:12px;min-width:180px;
|
|
}
|
|
.tray-btn:hover{border-color:var(--blue-500);background:#f8fbff;}
|
|
.tray-btn .tray-title{font-weight:700;color:var(--gray-600);display:flex;align-items:center;gap:6px;}
|
|
.tray-btn .tray-text{font-family:Consolas,monospace;font-size:11px;color:var(--blue-600);}
|
|
.tray-btn .tray-text.muted{color:var(--gray-500);font-family:inherit;}
|
|
|
|
.radio-option{display:flex;align-items:flex-start;gap:8px;font-weight:400;font-size:13.5px;cursor:pointer;line-height:1.5;}
|
|
.radio-option input{margin-top:3px;}
|
|
|
|
.toggle{position:relative;display:inline-block;width:38px;height:22px;}
|
|
.toggle input{opacity:0;width:0;height:0;}
|
|
.toggle .slider{position:absolute;cursor:pointer;inset:0;background:var(--gray-300);border-radius:999px;transition:.2s;}
|
|
.toggle .slider:before{position:absolute;content:"";height:16px;width:16px;left:3px;top:3px;background:#fff;border-radius:50%;transition:.2s;}
|
|
.toggle input:checked + .slider{background:var(--green-600);}
|
|
.toggle input:checked + .slider:before{transform:translateX(16px);}
|
|
|
|
.row-actions{display:flex;gap:6px;}
|
|
.icon-btn{border:1px solid var(--gray-300);background:#fff;color:var(--gray-600);border-radius:6px;padding:6px 10px;font-size:12px;cursor:pointer;display:inline-flex;align-items:center;gap:4px;}
|
|
.icon-btn:hover{background:var(--gray-50);}
|
|
.icon-btn.test{color:var(--purple-600);border-color:#ddd6fe;}
|
|
.icon-btn.test:hover{background:var(--purple-50);}
|
|
.icon-btn.del{color:var(--red-600);border-color:#fecaca;}
|
|
.icon-btn.del:hover{background:var(--red-50);}
|
|
|
|
.note-box{display:flex;gap:10px;background:#eff6ff;border:1px solid #bfdbfe;border-radius:8px;padding:12px 14px;font-size:12.5px;color:#1e40af;line-height:1.6;margin:16px 20px 0;}
|
|
.note-box .ico{font-size:16px;}
|
|
|
|
.card-foot{display:flex;justify-content:space-between;align-items:center;padding:14px 20px;border-top:1px solid var(--gray-200);}
|
|
.card-foot .last-saved{font-size:12px;color:var(--gray-500);}
|
|
|
|
.modal-overlay{position:fixed;inset:0;background:rgba(15,23,42,.5);display:none;align-items:center;justify-content:center;z-index:50;}
|
|
.modal-overlay.show{display:flex;}
|
|
.modal{background:#fff;border-radius:12px;width:420px;max-width:90vw;padding:28px 26px;text-align:center;box-shadow:0 20px 50px rgba(0,0,0,.25);}
|
|
.modal h3{margin:0 0 6px;font-size:16px;}
|
|
.modal p{margin:0 0 4px;color:var(--gray-500);font-size:13px;}
|
|
.spinner{width:42px;height:42px;border:4px solid var(--gray-200);border-top-color:var(--purple-600);border-radius:50%;margin:18px auto 14px;animation:spin .8s linear infinite;}
|
|
@keyframes spin{to{transform:rotate(360deg);}}
|
|
.check-circle{width:52px;height:52px;border-radius:50%;background:var(--green-50);color:var(--green-600);display:flex;align-items:center;justify-content:center;margin:14px auto 16px;font-size:28px;}
|
|
.cross-circle{width:52px;height:52px;border-radius:50%;background:var(--red-50);color:var(--red-600);display:flex;align-items:center;justify-content:center;margin:14px auto 16px;font-size:28px;}
|
|
.modal-log{text-align:left;background:var(--gray-50);border-radius:8px;padding:12px 14px;margin:14px 0;font-size:12px;font-family:Consolas,monospace;color:var(--gray-600);max-height:150px;overflow-y:auto;}
|
|
.modal-log div{padding:2px 0;}
|
|
.modal-close{margin-top:10px;width:100%;}
|
|
|
|
.toast{position:fixed;bottom:24px;right:24px;background:var(--gray-800);color:#fff;padding:12px 18px;border-radius:8px;font-size:13.5px;box-shadow:0 8px 20px rgba(0,0,0,.25);display:none;z-index:60;align-items:center;gap:8px;}
|
|
.toast.show{display:flex;}
|
|
|
|
.add-row-form{display:none;background:var(--gray-50);border:1px dashed var(--gray-300);border-radius:8px;padding:16px;margin:16px 20px 0;gap:12px;flex-wrap:wrap;align-items:flex-end;}
|
|
.add-row-form.show{display:flex;}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
|
|
<div class="sidebar">
|
|
<div class="sidebar-header">MES系統 <span class="ver">v1.2.1</span></div>
|
|
<div class="nav-item">首頁儀表板</div>
|
|
<div class="nav-item">部門管理</div>
|
|
<div class="nav-item">人員/權限管理</div>
|
|
<div class="nav-item">物料管理</div>
|
|
<div class="nav-item">設備管理</div>
|
|
<div class="nav-item">設備維修管理</div>
|
|
<div class="nav-item">品質管理</div>
|
|
<div class="nav-item">異常管理</div>
|
|
<div class="nav-item">課程模組</div>
|
|
<div class="nav-item active">製造執行中心</div>
|
|
<div class="nav-sub">工單管理</div>
|
|
<div class="nav-sub">工單生產紀錄查詢</div>
|
|
<div class="nav-sub">生產即時監控看板</div>
|
|
<div class="nav-sub current">工單列印設定</div>
|
|
<div class="nav-item">刀具管理</div>
|
|
<div class="sidebar-footer">
|
|
<div class="avatar">👤</div>
|
|
<div>admin<br><span style="opacity:.6;font-size:11px;">admin</span></div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="main">
|
|
<div class="topbar">
|
|
工單列印設定
|
|
<div class="breadcrumb">製造執行中心 / 工單列印設定</div>
|
|
</div>
|
|
|
|
<div class="content">
|
|
|
|
<div class="card">
|
|
<div class="card-head">
|
|
<div>
|
|
<h2>工單類別 ↔ 列印 API 綁定</h2>
|
|
<p>每個工單類別已各自建好對應的 BarTender API(.btw 已於後台建置完成)。送印時依此對應表決定呼叫哪一支 API;紙匣預設沿用 .btw 範本設定,也可在「紙匣設定」自訂印表機/紙匣於送印時覆寫。</p>
|
|
</div>
|
|
<div style="display:flex;gap:8px;">
|
|
<button class="btn btn-outline btn-sm" id="addBindingBtn">+ 新增類別綁定</button>
|
|
<button class="btn btn-green btn-sm" id="saveAllBtn">儲存全部設定</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="add-row-form" id="addRowForm">
|
|
<div class="field" style="min-width:160px;">
|
|
<label>工單類別代碼</label>
|
|
<input type="text" id="newCatCode" placeholder="例如 VIP">
|
|
</div>
|
|
<div class="field" style="min-width:260px;">
|
|
<label>對應 API</label>
|
|
<select class="api-select" id="newCatApi"></select>
|
|
</div>
|
|
<button class="btn btn-blue btn-sm" id="confirmAddBtn">加入</button>
|
|
<button class="btn btn-gray btn-sm" id="cancelAddBtn">取消</button>
|
|
</div>
|
|
|
|
<div style="overflow-x:auto;">
|
|
<table>
|
|
<thead>
|
|
<tr>
|
|
<th>工單類別</th>
|
|
<th>對應 API</th>
|
|
<th>API Endpoint</th>
|
|
<th>紙匣設定</th>
|
|
<th>狀態</th>
|
|
<th>上次測試結果</th>
|
|
<th>操作</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody id="bindingBody"></tbody>
|
|
</table>
|
|
</div>
|
|
|
|
<div class="card-foot">
|
|
<span class="last-saved" id="lastSaved">上次儲存:2026/08/09 上午 09:12</span>
|
|
<span class="last-saved">共 <span id="bindingCount">0</span> 筆類別綁定</span>
|
|
</div>
|
|
</div>
|
|
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Tray / printer override modal -->
|
|
<div class="modal-overlay" id="trayModal">
|
|
<div class="modal wide">
|
|
<h3 id="trayModalTitle">紙匣 / 印表機設定</h3>
|
|
<p id="trayModalSub"></p>
|
|
<div style="text-align:left;margin-top:16px;display:flex;flex-direction:column;gap:14px;">
|
|
<div class="field">
|
|
<label>紙匣來源</label>
|
|
<div style="display:flex;flex-direction:column;gap:10px;margin-top:6px;">
|
|
<label class="radio-option">
|
|
<input type="radio" name="traySource" id="traySourceDefault" value="default">
|
|
<span>沿用 .btw 範本設定(預設,不覆寫印表機/紙匣,紙匣已在後台 .btw 建置時設定好)</span>
|
|
</label>
|
|
<label class="radio-option">
|
|
<input type="radio" name="traySource" id="traySourceCustom" value="custom">
|
|
<span>由此處指定印表機與紙匣(送印時覆寫 .btw 內的設定)</span>
|
|
</label>
|
|
</div>
|
|
</div>
|
|
<div id="trayCustomFields" style="display:none;flex-direction:column;gap:14px;">
|
|
<div class="field">
|
|
<label>印表機</label>
|
|
<input type="text" id="trayPrinter" placeholder="例如 Zebra ZT411 - 出貨包裝線">
|
|
</div>
|
|
<div class="field">
|
|
<label>紙匣 / 卡夾</label>
|
|
<select id="trayTraySelect">
|
|
<option value="">請選擇</option>
|
|
<option value="Tray 1">Tray 1</option>
|
|
<option value="Tray 2">Tray 2</option>
|
|
<option value="Tray 3">Tray 3</option>
|
|
<option value="自動選擇">自動選擇(印表機自動判斷)</option>
|
|
<option value="手動送紙">手動送紙(Manual Feed)</option>
|
|
<option value="__custom__">自訂...</option>
|
|
</select>
|
|
<input type="text" id="trayTrayCustom" placeholder="輸入自訂紙匣名稱" style="display:none;margin-top:8px;">
|
|
</div>
|
|
<div class="note-box" style="margin:0;">
|
|
<span class="ico">ℹ️</span>
|
|
<div>紙匣名稱由印表機驅動程式決定,不同印表機型號命名可能不同(如 Tray 1、Cassette 2),請與印表機管理者確認實際名稱後再填寫。</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div style="display:flex;gap:10px;margin-top:20px;">
|
|
<button class="btn btn-gray" style="flex:1;" id="cancelTrayBtn">取消</button>
|
|
<button class="btn btn-green" style="flex:1;" id="saveTrayBtn">儲存</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="modal-overlay" id="actionModal">
|
|
<div class="modal">
|
|
<div id="modalLoading">
|
|
<h3 id="loadingTitle">處理中</h3>
|
|
<p id="loadingDesc">請稍候…</p>
|
|
<div class="spinner"></div>
|
|
<div class="modal-log" id="loadingLog"></div>
|
|
</div>
|
|
<div id="modalResult" style="display:none;">
|
|
<div id="resultIcon"></div>
|
|
<h3 id="resultTitle"></h3>
|
|
<p id="resultDesc"></p>
|
|
<div class="modal-log" id="resultLog"></div>
|
|
<button class="btn btn-blue modal-close" id="modalCloseBtn">關閉</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="toast" id="toast"></div>
|
|
|
|
<script>
|
|
// 已在 BarTender 後台建立好的 API 清單(每支 API 已對應各自的 .btw 範本 / 印表機)
|
|
const availableApis = [
|
|
{ name:"OK_Label_API", endpoint:"http://192.168.10.55:6608/api/print/OK_Label" },
|
|
{ name:"MYOK_Label_API", endpoint:"http://192.168.10.55:6608/api/print/MYOK_Label" },
|
|
{ name:"RGP_Label_API", endpoint:"http://192.168.10.55:6608/api/print/RGP_Label" },
|
|
{ name:"Backup_Label_API", endpoint:"http://192.168.10.55:6608/api/print/Backup_Label" }
|
|
];
|
|
|
|
function apiByName(name){ return availableApis.find(a=>a.name===name); }
|
|
|
|
const tagClass = {OK:"tag-ok",MYOK:"tag-myok",RGP:"tag-rgp"};
|
|
function classFor(cat){ return tagClass[cat] || "tag-custom"; }
|
|
|
|
let bindings = [
|
|
{ cat:"OK", api:"OK_Label_API", enabled:true, lastTest:{ok:true, time:"2026/08/08 17:20"}, traySource:"default", printer:"", tray:"" },
|
|
{ cat:"MYOK", api:"MYOK_Label_API", enabled:true, lastTest:{ok:true, time:"2026/08/08 17:21"}, traySource:"custom", printer:"Zebra ZT411 - 出貨包裝線", tray:"Tray 2" },
|
|
{ cat:"RGP", api:"RGP_Label_API", enabled:false, lastTest:{ok:false, time:"2026/08/07 11:03"}, traySource:"default", printer:"", tray:"" },
|
|
];
|
|
|
|
function populateApiSelect(select, selected){
|
|
select.innerHTML = availableApis.map(a=>`<option value="${a.name}" ${a.name===selected?"selected":""}>${a.name}</option>`).join("");
|
|
}
|
|
|
|
function renderBindings(){
|
|
const body = document.getElementById("bindingBody");
|
|
body.innerHTML = "";
|
|
bindings.forEach((b,idx)=>{
|
|
const tr = document.createElement("tr");
|
|
const ep = apiByName(b.api) ? apiByName(b.api).endpoint : "";
|
|
tr.innerHTML = `
|
|
<td><span class="tag ${classFor(b.cat)}">${b.cat}</span></td>
|
|
<td><select class="api-select" data-idx="${idx}" data-role="api"></select></td>
|
|
<td><span class="endpoint-text" data-idx="${idx}" data-role="endpoint">${ep}</span></td>
|
|
<td>
|
|
<button class="tray-btn" data-idx="${idx}" data-action="edit-tray">
|
|
<span class="tray-title">🖨️ 紙匣設定</span>
|
|
<span class="tray-text ${b.traySource==='custom' ? '' : 'muted'}">${b.traySource==='custom' ? `${b.tray || '未設定紙匣'} · ${b.printer || '未設定印表機'}` : '沿用 .btw 範本'}</span>
|
|
</button>
|
|
</td>
|
|
<td>
|
|
<label class="toggle">
|
|
<input type="checkbox" data-idx="${idx}" data-role="enabled" ${b.enabled?"checked":""}>
|
|
<span class="slider"></span>
|
|
</label>
|
|
</td>
|
|
<td>
|
|
${b.lastTest
|
|
? `<span class="status-pill ${b.lastTest.ok?"status-ok":"status-fail"}"><span class="status-dot"></span>${b.lastTest.ok?"成功":"失敗"} · ${b.lastTest.time}</span>`
|
|
: `<span style="color:var(--gray-500);font-size:12.5px;">尚未測試</span>`}
|
|
</td>
|
|
<td>
|
|
<div class="row-actions">
|
|
<button class="icon-btn test" data-idx="${idx}" data-action="test">🖨️ 測試列印</button>
|
|
<button class="icon-btn del" data-idx="${idx}" data-action="delete">🗑 移除</button>
|
|
</div>
|
|
</td>
|
|
`;
|
|
body.appendChild(tr);
|
|
const sel = tr.querySelector('select[data-role="api"]');
|
|
populateApiSelect(sel, b.api);
|
|
});
|
|
document.getElementById("bindingCount").textContent = bindings.length;
|
|
}
|
|
|
|
document.getElementById("bindingBody").addEventListener("change", e=>{
|
|
const idx = e.target.dataset.idx;
|
|
const role = e.target.dataset.role;
|
|
if(idx===undefined) return;
|
|
if(role==="api"){
|
|
bindings[idx].api = e.target.value;
|
|
const row = e.target.closest("tr");
|
|
row.querySelector('[data-role="endpoint"]').textContent = apiByName(e.target.value).endpoint;
|
|
}
|
|
if(role==="enabled") bindings[idx].enabled = e.target.checked;
|
|
});
|
|
|
|
document.getElementById("bindingBody").addEventListener("click", e=>{
|
|
const btn = e.target.closest("button[data-action]");
|
|
if(!btn) return;
|
|
const idx = btn.dataset.idx;
|
|
const action = btn.dataset.action;
|
|
if(action==="delete"){
|
|
const cat = bindings[idx].cat;
|
|
bindings.splice(idx,1);
|
|
renderBindings();
|
|
showToast(`已移除「${cat}」類別綁定`);
|
|
}
|
|
if(action==="test"){
|
|
runTestPrint(bindings[idx]);
|
|
}
|
|
if(action==="edit-tray"){
|
|
openTrayModal(idx);
|
|
}
|
|
});
|
|
|
|
// ---------- Tray / printer override modal ----------
|
|
const trayModal = document.getElementById("trayModal");
|
|
let editingTrayIdx = null;
|
|
|
|
function updateTrayFieldsVisibility(){
|
|
const custom = document.getElementById("traySourceCustom").checked;
|
|
document.getElementById("trayCustomFields").style.display = custom ? "flex" : "none";
|
|
}
|
|
|
|
function openTrayModal(idx){
|
|
editingTrayIdx = idx;
|
|
const b = bindings[idx];
|
|
document.getElementById("trayModalTitle").textContent = `紙匣 / 印表機設定 — ${b.cat}`;
|
|
document.getElementById("trayModalSub").textContent = `此設定僅套用於「${b.cat}」類別送印時使用`;
|
|
document.getElementById("traySourceDefault").checked = b.traySource!=="custom";
|
|
document.getElementById("traySourceCustom").checked = b.traySource==="custom";
|
|
document.getElementById("trayPrinter").value = b.printer || "";
|
|
|
|
const presetValues = ["Tray 1","Tray 2","Tray 3","自動選擇","手動送紙"];
|
|
const traySelect = document.getElementById("trayTraySelect");
|
|
const trayCustomInput = document.getElementById("trayTrayCustom");
|
|
if(b.tray && !presetValues.includes(b.tray)){
|
|
traySelect.value = "__custom__";
|
|
trayCustomInput.value = b.tray;
|
|
trayCustomInput.style.display = "block";
|
|
} else {
|
|
traySelect.value = b.tray || "";
|
|
trayCustomInput.value = "";
|
|
trayCustomInput.style.display = "none";
|
|
}
|
|
updateTrayFieldsVisibility();
|
|
trayModal.classList.add("show");
|
|
}
|
|
|
|
document.getElementById("traySourceDefault").addEventListener("change", updateTrayFieldsVisibility);
|
|
document.getElementById("traySourceCustom").addEventListener("change", updateTrayFieldsVisibility);
|
|
document.getElementById("trayTraySelect").addEventListener("change", e=>{
|
|
const custom = e.target.value === "__custom__";
|
|
document.getElementById("trayTrayCustom").style.display = custom ? "block" : "none";
|
|
if(custom) document.getElementById("trayTrayCustom").focus();
|
|
});
|
|
|
|
document.getElementById("cancelTrayBtn").addEventListener("click", ()=> trayModal.classList.remove("show"));
|
|
|
|
document.getElementById("saveTrayBtn").addEventListener("click", ()=>{
|
|
const source = document.getElementById("traySourceCustom").checked ? "custom" : "default";
|
|
const b = bindings[editingTrayIdx];
|
|
if(source==="default"){
|
|
b.traySource = "default";
|
|
b.printer = "";
|
|
b.tray = "";
|
|
} else {
|
|
const printer = document.getElementById("trayPrinter").value.trim();
|
|
const traySelectVal = document.getElementById("trayTraySelect").value;
|
|
const tray = traySelectVal==="__custom__" ? document.getElementById("trayTrayCustom").value.trim() : traySelectVal;
|
|
if(!printer || !tray){
|
|
showToast("請填寫印表機與紙匣,或改選「沿用 .btw 範本設定」");
|
|
return;
|
|
}
|
|
b.traySource = "custom";
|
|
b.printer = printer;
|
|
b.tray = tray;
|
|
}
|
|
trayModal.classList.remove("show");
|
|
renderBindings();
|
|
showToast(`已更新「${b.cat}」的紙匣設定,請記得儲存設定`);
|
|
});
|
|
|
|
const addRowForm = document.getElementById("addRowForm");
|
|
document.getElementById("addBindingBtn").addEventListener("click", ()=>{
|
|
populateApiSelect(document.getElementById("newCatApi"), availableApis[0].name);
|
|
document.getElementById("newCatCode").value="";
|
|
addRowForm.classList.add("show");
|
|
});
|
|
document.getElementById("cancelAddBtn").addEventListener("click", ()=> addRowForm.classList.remove("show"));
|
|
document.getElementById("confirmAddBtn").addEventListener("click", ()=>{
|
|
const code = document.getElementById("newCatCode").value.trim().toUpperCase();
|
|
const api = document.getElementById("newCatApi").value;
|
|
if(!code){ showToast("請輸入工單類別代碼"); return; }
|
|
if(bindings.some(b=>b.cat===code)){ showToast(`類別「${code}」已存在`); return; }
|
|
bindings.push({cat:code, api, enabled:true, lastTest:null, traySource:"default", printer:"", tray:""});
|
|
renderBindings();
|
|
addRowForm.classList.remove("show");
|
|
showToast(`已新增「${code}」類別綁定,請記得儲存設定`);
|
|
});
|
|
|
|
const actionModal = document.getElementById("actionModal");
|
|
const modalLoading = document.getElementById("modalLoading");
|
|
const modalResult = document.getElementById("modalResult");
|
|
|
|
function runModalSequence({title, desc, steps, onDone}){
|
|
actionModal.classList.add("show");
|
|
modalLoading.style.display="block";
|
|
modalResult.style.display="none";
|
|
document.getElementById("loadingTitle").textContent = title;
|
|
document.getElementById("loadingDesc").textContent = desc;
|
|
const log = document.getElementById("loadingLog");
|
|
log.innerHTML = "";
|
|
let i=0;
|
|
const interval = setInterval(()=>{
|
|
if(i<steps.length){
|
|
const line = document.createElement("div");
|
|
line.textContent = "› " + steps[i];
|
|
log.appendChild(line);
|
|
log.scrollTop = log.scrollHeight;
|
|
i++;
|
|
} else {
|
|
clearInterval(interval);
|
|
setTimeout(onDone, 400);
|
|
}
|
|
}, 280);
|
|
}
|
|
|
|
function showModalResult({ok, title, desc, log}){
|
|
modalLoading.style.display="none";
|
|
modalResult.style.display="block";
|
|
document.getElementById("resultIcon").innerHTML = ok
|
|
? `<div class="check-circle">✓</div>` : `<div class="cross-circle">✕</div>`;
|
|
document.getElementById("resultTitle").textContent = title;
|
|
document.getElementById("resultDesc").textContent = desc;
|
|
document.getElementById("resultLog").innerHTML = (log||[]).map(l=>`<div>${l}</div>`).join("");
|
|
}
|
|
|
|
document.getElementById("modalCloseBtn").addEventListener("click", ()=> actionModal.classList.remove("show"));
|
|
|
|
function runTestPrint(binding){
|
|
const ep = apiByName(binding.api) ? apiByName(binding.api).endpoint : "";
|
|
const useCustomTray = binding.traySource==="custom";
|
|
const steps = [`呼叫 API:${ep}`, "驗證工單類別參數..."];
|
|
if(useCustomTray){
|
|
steps.push(`覆寫印表機/紙匣:${binding.printer} · ${binding.tray}`);
|
|
}
|
|
steps.push("送出測試列印工作...");
|
|
runModalSequence({
|
|
title:`測試列印 — ${binding.cat}`,
|
|
desc:`呼叫「${binding.api}」送出測試標籤`,
|
|
steps,
|
|
onDone: ()=>{
|
|
const success = binding.enabled;
|
|
binding.lastTest = { ok: success, time: new Date().toLocaleString('zh-TW',{hour12:false}).slice(0,16).replace(',','') };
|
|
showModalResult({
|
|
ok: success,
|
|
title: success ? "測試列印成功" : "測試列印失敗",
|
|
desc: success ? `已成功呼叫「${binding.api}」送出列印` : "此綁定目前為停用狀態,未送出列印",
|
|
log: success
|
|
? [`✓ API 回應 200 OK`, `✓ 已透過 ${binding.api} 送出`, useCustomTray ? `✓ 紙匣已覆寫為 ${binding.tray}(${binding.printer})` : `✓ 紙匣沿用 .btw 範本設定`]
|
|
: [`✕ 綁定狀態為停用,請先啟用後再測試`]
|
|
});
|
|
renderBindings();
|
|
}
|
|
});
|
|
}
|
|
|
|
document.getElementById("saveAllBtn").addEventListener("click", ()=>{
|
|
runModalSequence({
|
|
title:"儲存工單列印設定",
|
|
desc:"正在儲存類別 ↔ API 綁定設定",
|
|
steps:[
|
|
"驗證綁定資料...",
|
|
`寫入 ${bindings.length} 筆類別綁定設定...`,
|
|
"更新送印呼叫對照表..."
|
|
],
|
|
onDone: ()=>{
|
|
showModalResult({
|
|
ok:true,
|
|
title:"設定已儲存",
|
|
desc:`共 ${bindings.length} 筆工單類別綁定設定已生效`,
|
|
log: bindings.map(b=>`✓ ${b.cat} → ${b.api}${b.enabled?"":"(停用)"} · 紙匣:${b.traySource==="custom" ? `${b.tray}(${b.printer})` : "沿用 .btw 範本"}`)
|
|
});
|
|
document.getElementById("lastSaved").textContent = "上次儲存:" + new Date().toLocaleString('zh-TW',{hour12:true});
|
|
}
|
|
});
|
|
});
|
|
|
|
function showToast(msg){
|
|
const t = document.getElementById("toast");
|
|
t.textContent = "✓ " + msg;
|
|
t.classList.add("show");
|
|
setTimeout(()=>t.classList.remove("show"), 3000);
|
|
}
|
|
|
|
renderBindings();
|
|
</script>
|
|
</body>
|
|
</html>
|