|
<!DOCTYPE html>
|
|
<html lang="zh-TW">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>MES Mobile Flow - 檢驗流程</title>
|
|
<link href="https://fonts.googleapis.com/css2?family=Noto+Sans+TC:wght=400;500;700&display=swap" rel="stylesheet">
|
|
|
|
<style>
|
|
:root {
|
|
--primary-color: #1e70e4;
|
|
--bg-gray: #f4f6f9;
|
|
--card-bg: #ffffff;
|
|
--text-dark: #333333;
|
|
--text-muted: #666666;
|
|
--border-radius: 16px;
|
|
--shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
|
|
--iqc-color: #0284c7;
|
|
--fqc-color: #0d9488;
|
|
--oqc-color: #7c3aed;
|
|
}
|
|
|
|
* {
|
|
box-sizing: border-box;
|
|
margin: 0;
|
|
padding: 0;
|
|
font-family: 'Noto Sans TC', sans-serif;
|
|
}
|
|
|
|
body {
|
|
background-color: #e0e0e0;
|
|
display: flex;
|
|
justify-content: center;
|
|
align-items: center;
|
|
min-height: 100vh;
|
|
padding: 20px;
|
|
}
|
|
|
|
.phone-container {
|
|
width: 430px;
|
|
height: 880px;
|
|
background-color: var(--bg-gray);
|
|
border-radius: 40px;
|
|
box-shadow: 0 12px 30px rgba(0,0,0,0.25);
|
|
border: 12px solid #1a1a1a;
|
|
position: relative;
|
|
overflow: hidden;
|
|
display: flex;
|
|
flex-direction: column;
|
|
}
|
|
|
|
.phone-header {
|
|
height: 30px;
|
|
background-color: #1a1a1a;
|
|
width: 100%;
|
|
position: absolute;
|
|
top: 0;
|
|
z-index: 999;
|
|
}
|
|
|
|
.app-bar {
|
|
background-color: #334155;
|
|
color: #ffffff;
|
|
text-align: center;
|
|
padding: 18px 0;
|
|
font-size: 1.1rem;
|
|
font-weight: 500;
|
|
letter-spacing: 1px;
|
|
margin-top: 8px;
|
|
position: relative;
|
|
}
|
|
|
|
.app-content {
|
|
flex: 1;
|
|
padding: 20px;
|
|
display: flex;
|
|
flex-direction: column;
|
|
justify-content: space-between;
|
|
overflow-y: auto;
|
|
height: calc(100% - 66px);
|
|
}
|
|
|
|
.flow-step {
|
|
display: none;
|
|
flex-direction: column;
|
|
height: 100%;
|
|
justify-content: space-between;
|
|
}
|
|
|
|
.flow-step.active {
|
|
display: flex;
|
|
}
|
|
|
|
.card-group {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 14px;
|
|
width: 100%;
|
|
overflow-y: auto;
|
|
padding-bottom: 8px;
|
|
}
|
|
|
|
.info-card {
|
|
background: var(--card-bg);
|
|
border-radius: var(--border-radius);
|
|
padding: 16px;
|
|
box-shadow: var(--shadow);
|
|
}
|
|
|
|
.info-label {
|
|
color: var(--text-muted);
|
|
font-size: 0.85rem;
|
|
margin-bottom: 4px;
|
|
}
|
|
|
|
.info-value {
|
|
color: var(--text-dark);
|
|
font-size: 1.2rem;
|
|
font-weight: 700;
|
|
}
|
|
|
|
.input-group {
|
|
background: var(--card-bg);
|
|
border-radius: var(--border-radius);
|
|
padding: 20px 16px;
|
|
box-shadow: var(--shadow);
|
|
margin-top: 20px;
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 16px;
|
|
}
|
|
|
|
.input-item {
|
|
display: flex;
|
|
flex-direction: column;
|
|
}
|
|
|
|
.input-label {
|
|
font-size: 0.85rem;
|
|
color: var(--text-dark);
|
|
font-weight: 600;
|
|
margin-bottom: 6px;
|
|
}
|
|
|
|
.input-control {
|
|
width: 100%;
|
|
padding: 12px;
|
|
border: 1px solid #ddd;
|
|
border-radius: 10px;
|
|
font-size: 1rem;
|
|
outline: none;
|
|
transition: border-color 0.2s;
|
|
}
|
|
|
|
.input-control:focus {
|
|
border-color: var(--primary-color);
|
|
}
|
|
|
|
.menu-btn {
|
|
background: var(--card-bg);
|
|
border: none;
|
|
border-radius: var(--border-radius);
|
|
padding: 18px;
|
|
font-size: 1.15rem;
|
|
font-weight: 600;
|
|
color: var(--text-dark);
|
|
box-shadow: var(--shadow);
|
|
cursor: pointer;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
position: relative;
|
|
transition: transform 0.1s;
|
|
width: 100%;
|
|
}
|
|
|
|
.menu-btn:active {
|
|
transform: scale(0.98);
|
|
}
|
|
|
|
.menu-btn::before {
|
|
content: '';
|
|
position: absolute;
|
|
left: 0;
|
|
top: 15%;
|
|
height: 70%;
|
|
width: 6px;
|
|
border-radius: 0 4px 4px 0;
|
|
}
|
|
|
|
.btn-prod::before { background-color: #10b981; }
|
|
.btn-equip::before { background-color: #0284c7; }
|
|
.btn-inspect::before { background-color: #f59e0b; }
|
|
|
|
.btn-iqc::before { background-color: var(--iqc-color); }
|
|
.btn-fqc::before { background-color: var(--fqc-color); }
|
|
.btn-oqc::before { background-color: var(--oqc-color); }
|
|
|
|
/* 單據卡片 */
|
|
.ticket-card {
|
|
background: var(--card-bg);
|
|
border-radius: var(--border-radius);
|
|
padding: 16px;
|
|
box-shadow: var(--shadow);
|
|
border-left: 5px solid var(--primary-color);
|
|
cursor: pointer;
|
|
transition: transform 0.1s;
|
|
}
|
|
|
|
.ticket-card:active {
|
|
transform: scale(0.98);
|
|
}
|
|
|
|
.ticket-header {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
margin-bottom: 8px;
|
|
}
|
|
|
|
.ticket-no {
|
|
font-weight: 700;
|
|
color: var(--primary-color);
|
|
font-size: 1rem;
|
|
}
|
|
|
|
.badge {
|
|
background: #e0f2fe;
|
|
color: #0369a1;
|
|
padding: 2px 8px;
|
|
border-radius: 12px;
|
|
font-size: 0.75rem;
|
|
font-weight: 600;
|
|
}
|
|
|
|
/* 檢驗填寫卡片 */
|
|
.inspect-item-card {
|
|
background: #ffffff;
|
|
border: 1px solid #e2e8f0;
|
|
border-radius: 12px;
|
|
padding: 14px;
|
|
margin-bottom: 10px;
|
|
}
|
|
|
|
.inspect-item-title {
|
|
font-weight: 700;
|
|
color: #1e293b;
|
|
font-size: 0.95rem;
|
|
margin-bottom: 6px;
|
|
display: flex;
|
|
justify-content: space-between;
|
|
}
|
|
|
|
.inspect-spec {
|
|
font-size: 0.8rem;
|
|
color: #64748b;
|
|
margin-bottom: 10px;
|
|
}
|
|
|
|
.radio-group {
|
|
display: flex;
|
|
gap: 12px;
|
|
margin-top: 8px;
|
|
}
|
|
|
|
.radio-label {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 4px;
|
|
font-size: 0.9rem;
|
|
cursor: pointer;
|
|
}
|
|
|
|
.btn-submit {
|
|
background-color: var(--primary-color);
|
|
color: white;
|
|
border: none;
|
|
border-radius: var(--border-radius);
|
|
padding: 16px;
|
|
font-size: 1.1rem;
|
|
font-weight: 600;
|
|
cursor: pointer;
|
|
width: 100%;
|
|
box-shadow: 0 4px 12px rgba(30, 112, 228, 0.3);
|
|
margin-top: 10px;
|
|
}
|
|
|
|
.btn-back {
|
|
background-color: #e2e8f0;
|
|
color: #475569;
|
|
border: none;
|
|
border-radius: var(--border-radius);
|
|
padding: 14px;
|
|
font-size: 1rem;
|
|
font-weight: 500;
|
|
cursor: pointer;
|
|
width: 100%;
|
|
text-align: center;
|
|
margin-top: 10px;
|
|
}
|
|
|
|
.logo-area {
|
|
text-align: center;
|
|
margin-top: 30px;
|
|
}
|
|
|
|
.logo-box {
|
|
width: 80px;
|
|
height: 80px;
|
|
background-color: #cbd5e1;
|
|
border-radius: 20px;
|
|
margin: 0 auto 16px auto;
|
|
display: flex;
|
|
justify-content: center;
|
|
align-items: center;
|
|
color: #475569;
|
|
font-weight: bold;
|
|
border: 2px dashed #94a3b8;
|
|
}
|
|
|
|
.system-title {
|
|
color: var(--primary-color);
|
|
font-size: 1.5rem;
|
|
font-weight: 700;
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
|
|
<div class="phone-container">
|
|
<div class="phone-header"></div>
|
|
|
|
<div class="app-bar" id="appBarTitle">系統入口</div>
|
|
|
|
<div class="app-content">
|
|
|
|
<!-- STEP 1: 登入 -->
|
|
<div class="flow-step active" id="step-login">
|
|
<div>
|
|
<div class="logo-area">
|
|
<div class="logo-box">MES</div>
|
|
<div class="system-title">工廠智慧管理系統</div>
|
|
</div>
|
|
<div class="input-group">
|
|
<div class="input-item">
|
|
<label class="input-label">工號 USER ID</label>
|
|
<input type="text" class="input-control" value="RJ" id="userIdInput">
|
|
</div>
|
|
<div class="input-item">
|
|
<label class="input-label">密碼 PASSWORD</label>
|
|
<input type="password" class="input-control" value="••••••••" id="passwordInput">
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<button class="btn-submit" onclick="navigateTo('step-main-menu', '選擇功能類別')">登入系統</button>
|
|
</div>
|
|
|
|
<!-- STEP 2: 功能選擇類別 (顯示品質檢驗) -->
|
|
<div class="flow-step" id="step-main-menu">
|
|
<div class="card-group">
|
|
<div class="info-card">
|
|
<div class="info-label">當前登入人員</div>
|
|
<div class="info-value user-name-display">RJ</div>
|
|
</div>
|
|
<button class="menu-btn btn-prod" onclick="alert('前往生產管理')">
|
|
<span>生產管理</span>
|
|
</button>
|
|
<button class="menu-btn btn-equip" onclick="alert('前往設備管理')">
|
|
<span>設備管理</span>
|
|
</button>
|
|
<button class="menu-btn btn-inspect" onclick="navigateTo('step-inspect-category', '選擇檢驗類別')">
|
|
<span>品質檢驗</span>
|
|
</button>
|
|
</div>
|
|
<button class="btn-back" onclick="navigateTo('step-login', '系統入口')">登出</button>
|
|
</div>
|
|
|
|
<!-- STEP 3: 選擇檢驗類別 (IQC / FQC / OQC) -->
|
|
<div class="flow-step" id="step-inspect-category">
|
|
<div class="card-group">
|
|
<div class="info-card">
|
|
<div class="info-label">品質檢驗模組</div>
|
|
<div class="info-value">請選擇檢驗作業種類</div>
|
|
</div>
|
|
<button class="menu-btn btn-iqc" onclick="selectCategory('IQC')">
|
|
<span>IQC 進料檢驗</span>
|
|
</button>
|
|
<button class="menu-btn btn-fqc" onclick="selectCategory('FQC')">
|
|
<span>FQC 成品檢驗</span>
|
|
</button>
|
|
<button class="menu-btn btn-oqc" onclick="selectCategory('OQC')">
|
|
<span>OQC 出貨檢驗</span>
|
|
</button>
|
|
</div>
|
|
<button class="btn-back" onclick="navigateTo('step-main-menu', '選擇功能類別')">返回主選單</button>
|
|
</div>
|
|
|
|
<!-- STEP 4: 檢驗單據列表 -->
|
|
<div class="flow-step" id="step-ticket-list">
|
|
<div class="card-group" id="ticketListContainer">
|
|
<!-- 動態載入單據 -->
|
|
</div>
|
|
<button class="btn-back" onclick="navigateTo('step-inspect-category', '選擇檢驗類別')">返回類別選擇</button>
|
|
</div>
|
|
|
|
<!-- STEP 5: 點擊單據直接開始填寫表單 -->
|
|
<div class="flow-step" id="step-inspect-form">
|
|
<div class="card-group">
|
|
<div class="info-card" style="padding: 14px;" id="formHeaderCard">
|
|
<div style="font-weight:700; color:var(--primary-color); font-size:1.05rem; margin-bottom:4px;" id="infoTicketNo">-</div>
|
|
<div style="font-size:0.85rem; color:#475569;" id="infoMeta">-</div>
|
|
</div>
|
|
|
|
<div id="inspectItemsContainer"></div>
|
|
|
|
<div class="input-item">
|
|
<label class="input-label">現場檢驗備註</label>
|
|
<input type="text" class="input-control" placeholder="請輸入備註說明 (選填)" id="inspectRemark">
|
|
</div>
|
|
</div>
|
|
|
|
<div>
|
|
<button class="btn-submit" onclick="submitInspection()">💾 完成並送出檢驗結果</button>
|
|
<button class="btn-back" onclick="navigateTo('step-ticket-list', currentCategory + ' 檢驗單列表')">取消返回</button>
|
|
</div>
|
|
</div>
|
|
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
let currentCategory = '';
|
|
let currentTicket = null;
|
|
|
|
// 模擬各類型檢驗單據
|
|
const mockData = {
|
|
'IQC': [
|
|
{ ticketNo: 'IQC-20260908-01', refNo: 'PO-20260901-01', prodName: 'T8 LED 燈管', batchNo: 'B20260901', qty: 10 },
|
|
{ ticketNo: 'IQC-20260908-02', refNo: 'PO-20260901-08', prodName: '鋁合金散熱片', batchNo: 'B20260902', qty: 25 }
|
|
],
|
|
'FQC': [
|
|
{ ticketNo: 'FQC-20260908-01', refNo: 'WO-20260830-05', prodName: '藍牙耳機-標準版', batchNo: 'FB202608', qty: 5 },
|
|
{ ticketNo: 'FQC-20260908-02', refNo: 'WO-20260901-12', prodName: '鋰電池模組-A型', batchNo: 'FB202609', qty: 10 }
|
|
],
|
|
'OQC': [
|
|
{ ticketNo: 'OQC-20260908-01', refNo: 'DO-20260905-02', prodName: '智慧手錶-高配版', batchNo: 'OB202609', qty: 12 }
|
|
]
|
|
};
|
|
|
|
// 預設檢驗項目
|
|
const inspectItemsTemplate = [
|
|
{ itemName: '外觀刮傷與瑕疵檢查', condition: 'Boolean', spec: '規範:全機無明顯刮傷/凹痕', defaultVal: 'PASS' },
|
|
{ itemName: '關鍵電壓/尺寸測試', condition: 'RANGE', spec: '規範:11.5V ~ 12.5V', defaultVal: '12.0' },
|
|
{ itemName: '功能組裝確認', condition: 'Boolean', spec: '規範:零組件結構無鬆脫', defaultVal: 'PASS' }
|
|
];
|
|
|
|
// 選擇 IQC / FQC / OQC 種類
|
|
function selectCategory(category) {
|
|
currentCategory = category;
|
|
const container = document.getElementById('ticketListContainer');
|
|
container.innerHTML = '';
|
|
|
|
const list = mockData[category] || [];
|
|
|
|
if (list.length === 0) {
|
|
container.innerHTML = '<div class="info-card" style="text-align:center; color:#94a3b8;">目前無待檢驗單據</div>';
|
|
} else {
|
|
list.forEach(t => {
|
|
const card = document.createElement('div');
|
|
card.className = 'ticket-card';
|
|
card.onclick = () => startDirectInspection(t);
|
|
card.innerHTML = `
|
|
<div class="ticket-header">
|
|
<span class="ticket-no">${t.ticketNo}</span>
|
|
<span class="badge">${category}</span>
|
|
</div>
|
|
<div style="font-size:0.85rem; color:var(--text-muted); display:grid; grid-template-columns: 1fr 1fr; gap:6px;">
|
|
<div>來源:${t.refNo}</div>
|
|
<div>抽檢數:<b>${t.qty} 件</b></div>
|
|
<div style="grid-column: span 2;">產品:${t.prodName}</div>
|
|
<div style="grid-column: span 2;">批號:${t.batchNo}</div>
|
|
</div>
|
|
`;
|
|
container.appendChild(card);
|
|
});
|
|
}
|
|
|
|
navigateTo('step-ticket-list', `${category} 檢驗單列表`);
|
|
}
|
|
|
|
// 點擊單據 -> 直接開始檢驗 (跳過詳細資訊頁)
|
|
function startDirectInspection(ticket) {
|
|
currentTicket = ticket;
|
|
|
|
document.getElementById('infoTicketNo').innerText = `單號:${ticket.ticketNo}`;
|
|
document.getElementById('infoMeta').innerText = `產品:${ticket.prodName} | 批號:${ticket.batchNo} (${ticket.qty} 件)`;
|
|
|
|
const container = document.getElementById('inspectItemsContainer');
|
|
container.innerHTML = '';
|
|
|
|
inspectItemsTemplate.forEach((item, idx) => {
|
|
const itemDiv = document.createElement('div');
|
|
itemDiv.className = 'inspect-item-card';
|
|
|
|
let inputHtml = '';
|
|
if(item.condition === 'Boolean') {
|
|
inputHtml = `
|
|
<div class="radio-group">
|
|
<label class="radio-label"><input type="radio" name="item_${idx}" value="PASS" checked> 合格 (PASS)</label>
|
|
<label class="radio-label"><input type="radio" name="item_${idx}" value="FAIL"> 不合格 (FAIL)</label>
|
|
</div>
|
|
`;
|
|
} else {
|
|
inputHtml = `
|
|
<input type="text" class="input-control" value="${item.defaultVal}" placeholder="請輸入實測數值">
|
|
`;
|
|
}
|
|
|
|
itemDiv.innerHTML = `
|
|
<div class="inspect-item-title">
|
|
<span>${idx + 1}. ${item.itemName}</span>
|
|
<span style="font-size:0.75rem; color:#0369a1; background:#e0f2fe; padding:2px 6px; border-radius:4px;">${item.condition}</span>
|
|
</div>
|
|
<div class="inspect-spec">${item.spec}</div>
|
|
${inputHtml}
|
|
`;
|
|
container.appendChild(itemDiv);
|
|
});
|
|
|
|
navigateTo('step-inspect-form', `填寫 ${currentCategory} 檢驗單`);
|
|
}
|
|
|
|
function navigateTo(stepId, title) {
|
|
document.querySelectorAll('.flow-step').forEach(step => step.classList.remove('active'));
|
|
|
|
const targetStep = document.getElementById(stepId);
|
|
if (targetStep) targetStep.classList.add('active');
|
|
|
|
document.getElementById('appBarTitle').innerText = title;
|
|
|
|
if(stepId === 'step-main-menu') {
|
|
const userId = document.getElementById('userIdInput').value || 'RJ';
|
|
document.querySelectorAll('.user-name-display').forEach(el => el.innerText = userId);
|
|
}
|
|
}
|
|
|
|
function submitInspection() {
|
|
alert(`🎉 [${currentTicket.ticketNo}] 檢驗紀錄已成功儲存送出!`);
|
|
navigateTo('step-ticket-list', `${currentCategory} 檢驗單列表`);
|
|
}
|
|
</script>
|
|
</body>
|
|
</html>
|