Project

General

Profile

Feature #141 » 設備檢驗規則_0715.html

鎮源 游, 07/16/2026 03:45 AM

 
<!DOCTYPE html>
<html lang="zh-TW">
<head>
<meta charset="UTF-8">
<title>設備檢驗規則</title>
<script src="https://unpkg.com/vue@3/dist/vue.global.prod.js"></script>
<script src="https://cdn.tailwindcss.com"></script>
<script>
tailwind.config = {
theme: {
extend: {
colors: {
'teal-800': '#1e4646',
'teal-200': '#b2f0f0',
'teal-600': '#00a399',
'light-blue': '#4c9aff',
'dark-red': '#cc0000',
'gray-500': '#6b7280',
/* 優化狀態顏色 */
'status-ok': '#10b981',
'status-ng': '#ef4444',
'status-wip': '#f59e0b',
'status-idle': '#3b82f6',
/* 背景色 */
'bg-ok': '#dcfce7',
'bg-ng': '#fee2e2',
'bg-wip': '#fef3c7',
'bg-idle': '#dbeafe',
'bg-primary': '#eff6ff', /* blue-50 */

/* 綁定相關 */
'bind-color': '#007bff',
'bind-bg': '#e7f3ff',
'pm-daily-color': '#059669', /* 綠色 */
'pm-daily-bg': '#d1fae5',
'pm-weekly-color': '#c2410c', /* 橘色 */
'pm-weekly-bg': '#fff7ed',
'pm-monthly-color': '#6d28d9', /* 紫色 */
'pm-monthly-bg': '#f3e8ff',
}
}
}
}
</script>
<style>
/* 設備監控頁樣式 (與原檔案相同) */
.filter-button {
padding: 8px 16px;
border-radius: 9999px;
font-weight: 600;
transition: all 0.2s;
cursor: pointer;
}
.action-button-mini {
padding: 4px 8px;
border-radius: 4px;
font-size: 0.75rem;
font-weight: 500;
transition: background-color 0.15s, opacity 0.15s;
}
.filter-button.active {
background-color: #4c9aff;
color: white;
}
.filter-button.inactive {
background-color: #e5e7eb;
color: #1f2937;
}
.required-label::after {
content: ' *';
color: #ef4444;
margin-left: 2px;
}
.status-pill {
padding: 4px 10px;
border-radius: 4px;
font-size: 0.8rem;
font-weight: 700;
}
.bg-status-ok { background-color: var(--tw-colors-bg-ok); color: var(--tw-colors-status-ok); border: 1px solid var(--tw-colors-status-ok); }
.bg-status-ng { background-color: var(--tw-colors-bg-ng); color: var(--tw-colors-status-ng); border: 1px solid var(--tw-colors-status-ng); }
.bg-status-wip { background-color: var(--tw-colors-bg-wip); color: var(--tw-colors-status-wip); border: 1px solid var(--tw-colors-status-wip); }
.bg-status-idle { background-color: var(--tw-colors-bg-idle); color: var(--tw-colors-status-idle); border: 1px solid var(--tw-colors-status-idle); }

.bg-pm-daily, .bg-pm-weekly, .bg-pm-monthly {
background-color: var(--tw-colors-bind-bg) !important;
color: var(--tw-colors-bind-color) !important;
border: 1px solid var(--tw-colors-bind-color) !important;
}
/* 動態明細樣式 */
.detail-row-input {
width: 100%;
padding: 6px 8px;
border: 1px solid #d1d5db;
border-radius: 4px;
appearance: none;
background-color: white;
}
.detail-item-container {
display: flex;
align-items: center;
gap: 8px;
background-color: white;
padding: 8px;
border-radius: 4px;
box-shadow: 0 1px 2px 0 rgba(0,0,0,0.05);
border: 1px solid #e5e7eb;
}

/* 保養填寫頁樣式 */
.detail-input-pm {
padding: 8px;
border: 1px solid #d1d5db;
border-radius: 4px;
width: 100%;
}
/* 狀態轉換標籤樣式 */
.status-transition-tag {
background-color: var(--tw-colors-bind-bg);
color: var(--tw-colors-bind-color);
padding: 2px 8px;
border-radius: 4px;
font-size: 0.75rem;
font-weight: 600;
margin-right: 4px;
border: 1px solid var(--tw-colors-bind-color);
}
</style>
</head>
<body class="bg-slate-100 p-10">

<div id="app">

<div v-if="viewMode === 'list'" class="main-content-wrapper">
<h1 class="text-3xl font-bold text-teal-800 mb-6 border-b-4 border-teal-200 pb-3">
⚙️ 設備稼動規則與保養排程
</h1>
<div class="bg-white p-6 rounded-2xl shadow-xl border border-gray-200">
<h2 class="text-xl font-bold text-gray-700 mb-4 border-b pb-2">
設備稼動規格設定
</h2>

<div class="flex space-x-4 mb-6">
<button
@click="activeFilter = '生產機台'"
:class="['filter-button', activeFilter === '生產機台' ? 'active' : 'inactive']"
>
生產設備
</button>
<button
@click="activeFilter = 'IOT設備'"
:class="['filter-button', activeFilter === 'IOT設備' ? 'active' : 'inactive']"
>
IOT設備
</button>
<button
@click="activeFilter = '磅秤'"
:class="['filter-button', activeFilter === '磅秤' ? 'active' : 'inactive']"
>
磅秤
</button>
<button
@click="activeFilter = '全部'"
:class="['filter-button', activeFilter === '全部' ? 'active' : 'inactive']"
>
全部
</button>
</div>
<div class="flex justify-between items-center mb-4">
<input v-model="searchKeyword" placeholder="搜尋設備編號/名稱..." class="p-2 border rounded-lg w-64">
</div>

<table class="min-w-full table-auto border border-gray-200 text-sm">
<thead class="bg-gray-100">
<tr>
<th class="px-3 py-2 border">設備編號</th>
<th class="px-3 py-2 border">設備名稱</th>
<th class="px-3 py-2 border">設備類型</th>
<th class="px-3 py-2 border w-32">表單配置</th>
<th class="px-3 py-2 border w-48">狀態變更維護</th>
</tr>
</thead>
<tbody>
<tr v-for="equipment in filteredEquipment" :key="equipment.id" class="hover:bg-teal-50">
<td class="px-3 py-2 border">{{ equipment.no }}</td>
<td class="px-3 py-2 border">{{ equipment.name }}</td>
<td class="px-3 py-2 border">{{ equipment.type }}</td>
<td class="px-3 py-2 border text-center">
<button @click="openDialog('bind_pm', equipment)"
class="action-button-mini bg-bind-color hover:bg-light-blue text-white w-full">
配置單據 ({{ equipment.maintenanceForms ? Object.keys(equipment.maintenanceForms).length : 0 }})
</button>
</td>
<td class="px-3 py-2 border text-center">
<div v-if="!equipment.maintenanceForms || Object.keys(equipment.maintenanceForms).length === 0"
class="text-gray-500 font-semibold text-sm">
N/A
</div>
<div v-else class="space-y-1">
<div v-for="(formId, statusKey) in equipment.maintenanceForms" :key="statusKey"
class="flex items-center justify-center text-sm text-gray-800">
<template v-for="(part, pIndex) in statusKey.split('->')" :key="pIndex">
<span class="font-normal">
{{ part.trim() }}
</span>
<span v-if="pIndex === 0" class="mx-1.5 font-bold text-gray-800">
</span>
</template>
</div>
</div>
</td>
</tr>
</tbody>
</table>
</div>
</div>

<div v-if="dialog.mode === 'status_change'" class="fixed inset-0 flex items-center justify-center bg-black bg-opacity-60 z-40 p-4">
<div class="bg-white p-6 rounded-xl shadow-2xl w-11/12 max-w-md border border-gray-200">
<h2 class="text-2xl font-bold text-teal-700 mb-4 border-b pb-2">
Dialog-設備狀態變更 ({{ dialog.data.no }})
</h2>
<div class="mb-6 space-y-4">
<p>當前狀態:<span :class="['status-pill', getStatusColor(dialog.data.status)]">{{ dialog.data.status }}</span></p>
<div class="space-y-3">
<div>
<label class="block mb-1 font-semibold required-label">目標狀態:</label>
<div class="relative">
<select v-model="dialog.statusChange.newStatus" class="w-full p-2 border rounded-lg appearance-none bg-white">
<option v-for="status in availableStatuses(dialog.data.status)" :key="status" :value="status">
{{ status }}
</option>
</select>
<svg class="w-5 h-5 absolute right-2 top-1/2 transform -translate-y-1/2 pointer-events-none text-gray-500" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor"><path fill-rule="evenodd" d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z" clip-rule="evenodd" /></svg>
</div>
</div>
<div>
<label class="block mb-1 font-semibold required-label">變更原因/紀錄:</label>
<textarea v-model="dialog.statusChange.reason" class="w-full p-2 border rounded-lg" rows="3" placeholder="請說明狀態變更的原因"></textarea>
</div>
</div>
</div>
<div class="border-t pt-4">
<h3 class="text-sm font-bold text-gray-700 mb-2">最近變更紀錄</h3>
<div class="h-20 overflow-y-auto bg-gray-50 p-2 text-xs rounded">
<p v-for="(log, i) in dialog.data.statusLog" :key="i" class="mb-1 text-gray-600">
[{{ log.time }}] {{ log.oldStatus }} -> {{ log.newStatus }} (原因: {{ log.reason }})
</p>
<p v-if="!dialog.data.statusLog || dialog.data.statusLog.length === 0" class="text-gray-400">尚無狀態變更紀錄</p>
</div>
</div>

<div class="flex justify-end space-x-3 mt-4">
<button @click="closeDialog" class="px-5 py-2 bg-gray-200 text-gray-700 rounded hover:bg-gray-300">取消</button>
<button @click="saveEquipmentStatusChange" class="px-5 py-2 bg-teal-600 text-white rounded hover:bg-teal-700">確認變更</button>
</div>
</div>
</div>

<div v-if="dialog.mode === 'bind_pm'" class="fixed inset-0 flex items-center justify-center bg-black bg-opacity-60 z-40 p-4">
<div class="bg-white p-6 rounded-xl shadow-2xl w-11/12 max-w-xl border border-gray-200">
<h2 class="text-2xl font-bold text-bind-color mb-4 border-b pb-2">
📋 配置設備狀態轉換卡控單據 ({{ dialog.data.no }})
</h2>
<div class="mb-6 space-y-4">
<p class="text-sm text-gray-600 mb-4">設備名稱:<span class="font-bold">{{ dialog.data.name }}</span></p>
<h3 class="text-lg font-bold text-gray-700 mb-3 border-t pt-3">保養單據卡控規則設定</h3>
<p class="text-sm text-gray-500 mb-4">請設定當設備**狀態轉換**發生時,需強制執行的保養單據。</p>
<div class="space-y-3 max-h-96 overflow-y-auto p-1 border rounded-lg bg-gray-50">
<div v-for="(formId, status) in dialog.data.maintenanceForms" :key="status" class="flex items-center space-x-2 bg-white p-3 rounded shadow-sm border border-gray-200">
<div class="w-1/3">
<span class="status-transition-tag">{{ status }}</span>
</div>
<div class="flex-1 relative">
<select v-model="dialog.data.maintenanceForms[status]" class="detail-row-input">
<option :value="null">-- 取消綁定此狀態轉換 --</option>
<option v-for="form in pmFormPool" :key="form.id" :value="form.id">
{{ form.name }} (週期: {{ form.type }})
</option>
</select>
<svg class="w-4 h-4 absolute right-2 top-1/2 transform -translate-y-1/2 pointer-events-none text-gray-500" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor"><path fill-rule="evenodd" d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z" clip-rule="evenodd" /></svg>
</div>
<button @click="removePMFormBinding(status)" class="text-dark-red hover:text-red-700 p-1">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 inline" viewBox="0 0 20 20" fill="currentColor"><path fill-rule="evenodd" d="M9 2a1 1 0 00-.894.553L7.382 4H4a1 1 0 000 2v10a2 2 0 002 2h8a2 2 0 002-2V6a1 1 0 100-2h-3.382l-.724-1.447A1 1 0 0011 2H9zM7 6v10h6V6H7z" clip-rule="evenodd" /></svg>
</button>

</div>

<div v-if="!dialog.data.maintenanceForms || Object.keys(dialog.data.maintenanceForms).length === 0" class="p-3 text-center text-gray-500">
目前沒有設定任何單據卡控點。
</div>
</div>
<h4 class="text-md font-semibold text-gray-700 pt-3 border-t">新增卡控點</h4>
<div class="flex space-x-2">
<div class="relative w-1/3">
<select v-model="dialog.tempBinding.fromStatus" class="detail-row-input">
<option value="">-- 起始狀態 --</option>
<option v-for="status in Object.keys(STATUS_MAP)" :key="status" :value="status">
{{ status }}
</option>
</select>
</div>
<div class="relative w-1/3">
<select v-model="dialog.tempBinding.toStatus" class="detail-row-input">
<option value="">-- 目標狀態 --</option>
<option v-for="status in Object.keys(STATUS_MAP).filter(s => s !== dialog.tempBinding.fromStatus)" :key="status" :value="status">
{{ status }}
</option>
</select>
</div>
<div class="relative w-1/3">
<button @click="addPMFormBinding" class="px-3 py-1 bg-light-blue text-white rounded-lg hover:bg-blue-600 transition h-full w-full" :disabled="!dialog.tempBinding.fromStatus || !dialog.tempBinding.toStatus">
新增卡控點
</button>
</div>
</div>

</div>

<div class="flex justify-end space-x-3 mt-4">
<button @click="closeDialog" class="px-5 py-2 bg-gray-200 text-gray-700 rounded hover:bg-gray-300">取消</button>
<button @click="saveMaintenanceBinding" class="px-5 py-2 bg-teal-600 text-white rounded hover:bg-teal-700">確認綁定</button>
</div>
</div>
</div>

<div v-if="dialog.mode === 'pm_prompt'" class="fixed inset-0 flex items-center justify-center bg-black bg-opacity-60 z-50 p-4">
<div class="bg-white p-6 rounded-xl shadow-2xl w-11/12 max-w-lg border border-gray-200">
<h2 class="text-2xl font-bold text-orange-600 mb-4 border-b pb-2">
🚨 狀態變更流程卡控!
</h2>
<p class="mb-6 text-gray-700">設備 **{{ dialog.data.no }}** 將由 [{{ dialog.data._pendingOldStatus }}] 變更為 [{{ dialog.data._pendingNewStatus }}],請確認是否需填寫以下**保養單**:</p>
<ul class="space-y-2 mb-6">
<li v-for="pm in dialog.data.pendingPM" :key="pm.id"
:class="['p-2 rounded border font-semibold', getStatusColor(pm.type)]">
✅ 點檢表:{{ pm.name }} (週期: {{ pm.type }})
</li>
</ul>

<div class="flex justify-end space-x-3">
<button @click="closeDialogAndExecuteStatusChange(dialog.data)" class="px-5 py-2 bg-gray-200 text-gray-700 rounded hover:bg-gray-300">略過 (不推薦)</button>
<button @click="navigateToPMEntry(dialog.data.no, dialog.data.name, dialog.data.pendingPM?.[0], dialog.data._pendingNewStatus, dialog.data._pendingReason, dialog.data._pendingOldStatus)" class="px-5 py-2 bg-orange-500 text-white rounded hover:bg-orange-600">立即填寫</button>
</div>
</div>
</div>

<div v-if="viewMode === 'pm_entry'" class="main-content-wrapper">
<h1 class="text-3xl font-bold text-teal-800 mb-6 border-b-4 border-teal-200 pb-3">
📝 設備保養數據填寫
</h1>

<div class="bg-white p-6 rounded-2xl shadow-xl border border-gray-200">
<h2 class="text-xl font-bold text-gray-700 mb-4">
保養工單:<span class="text-light-blue">{{ pmEntryForm.pmFormName }}</span>
</h2>

<div class="grid grid-cols-2 gap-4 mb-6 pb-4 border-b">
<div><span class="font-semibold">設備編號:</span><span class="text-teal-600">{{ pmEntryForm.equipmentNo }}</span></div>
<div><span class="font-semibold">設備名稱:</span><span>{{ pmEntryForm.equipmentName }}</span></div>
<div><span class="font-semibold">保養類型:</span><span>{{ pmEntryForm.pmFormType }}</span></div>
<div><span class="font-semibold">保養人員:</span><input v-model="pmEntryForm.inspector" class="detail-input-pm w-2/3" placeholder="請輸入姓名/工號"></div>
</div>

<h3 class="text-lg font-bold text-gray-700 mb-3">細項點檢與記錄</h3>
<table class="min-w-full table-auto border border-gray-200 text-sm">
<thead class="bg-gray-100">
<tr>
<th class="px-3 py-2 border w-1/4">檢查內容</th>
<th class="px-3 py-2 border w-1/4">標準/規格</th>
<th class="px-3 py-2 border w-1/6">方法</th>
<th class="px-3 py-2 border w-1/6 required-label">結果</th>
<th class="px-3 py-2 border w-1/6">備註</th>
</tr>
</thead>
<tbody>
<tr v-for="(detail, index) in pmEntryForm.details" :key="index">
<td class="px-3 py-2 border">{{ detail.content }}</td>
<td class="px-3 py-2 border">{{ detail.standard }}</td>
<td class="px-3 py-2 border">{{ detail.method }}</td>
<td class="px-3 py-2 border">
<div v-if="detail.inputType === 'PASS/FAIL'">
<select v-model="detail.result" class="detail-input-pm">
<option value="">-- 選擇 --</option>
<option value="PASS">PASS</option>
<option value="FAIL">FAIL</option>
</select>
</div>
<div v-else>
<input v-model="detail.result" :type="detail.inputType === '數值' ? 'number' : 'text'" class="detail-input-pm" placeholder="填寫數值或文字">
</div>
</td>
<td class="px-3 py-2 border">
<input v-model="detail.notes" type="text" class="detail-input-pm" placeholder="選填">
</td>
</tr>
</tbody>
</table>

<div class="flex justify-end space-x-3 mt-6">
<button @click="viewMode = 'list'" class="px-5 py-2 bg-gray-200 text-gray-700 rounded hover:bg-gray-300">取消/返回</button>
<button @click="submitPMForm" class="px-5 py-2 bg-teal-600 text-white rounded hover:bg-teal-700">提交保養數據並變更狀態</button>
</div>

</div>
</div>

</div>

<script>
const { createApp, ref, reactive, computed } = Vue;

createApp({
setup() {
// --- 視圖模式管理 ---
const viewMode = ref('list'); // 'list' (列表) | 'pm_entry' (保養填寫)

const activeFilter = ref('全部');
const searchKeyword = ref('');
// 狀態顏色映射
const STATUS_MAP = {
'正常運行': 'bg-status-ok',
'待機': 'bg-status-idle',
'維護中': 'bg-status-wip',
'故障': 'bg-status-ng',
};

// 模擬保養單細項數據
const mockPmDetails = [
{ content: '液壓油位', standard: '50%~80%', method: '目視', inputType: '數值', result: '', notes: '' },
{ content: '清潔濾網', standard: '無灰塵', method: '目視', inputType: 'PASS/FAIL', result: '', notes: '' },
{ content: '軸承溫度', standard: '< 50°C', method: '量測', inputType: '數值', result: '', notes: '' },
];

// 模擬保養單池 (所有可綁定的保養單)
const pmFormPool = ref([
{ id: 1, name: '日點檢-基本', type: '每日', details: mockPmDetails },
{ id: 2, name: '週保養-潤滑', type: '每週', details: mockPmDetails },
{ id: 3, name: '月保養-校正', type: '每月', details: mockPmDetails },
{ id: 4, name: '季點檢-大修', type: '每季', details: mockPmDetails },
]);

// 設備主檔數據
const equipmentRecords = ref([
{
id: 1, no: 'EQP-CNC-001', name: 'CNC機床A', type: '生產機台', status: '正常運行',
pmHours: 500,
maintenanceForms: {
'維護中->正常運行': 1,
'故障->維護中': 3,
},
statusLog: [{time: '14:30', oldStatus: '故障', newStatus: '維護中', reason: '報修單001處理中'}]
},
{
id: 2, no: 'EQP-LPR-002', name: '雷射雕刻機', type: '生產機台', status: '待機',
pmHours: 120,
maintenanceForms: {},
statusLog: [{time: '15:00', oldStatus: '正常運行', newStatus: '待機', reason: '閒置切換'}]
},
{
id: 3, no: 'IOT-TEMP-001', name: '溫度感應器', type: 'IOT設備', status: '正常運行',
pmHours: 0,
maintenanceForms: { '維護中->正常運行': 2 },
statusLog: []
},
{
id: 4, no: 'IOT-HUM-002', name: '濕度監測器', type: 'IOT設備', status: '維護中',
pmHours: 10,
maintenanceForms: {},
statusLog: [{time: '16:00', oldStatus: '正常運行', newStatus: '維護中', reason: '電池更換'}]
},
{
id: 5, no: 'WGH-001', name: '成品磅秤-A', type: '磅秤', status: '故障',
pmHours: 450,
maintenanceForms: {},
statusLog: [{time: '10:00', oldStatus: '正常運行', newStatus: '故障', reason: '開機失敗'}]
},
]);

const dialog = reactive({
mode: null,
data: {},
statusChange: { newStatus: '', reason: '' },
tempBinding: { fromStatus: '', toStatus: '' },
});

// 保養填寫表單的數據結構
const pmEntryForm = reactive({
equipmentNo: '',
equipmentName: '',
pmFormId: '',
pmFormName: '',
pmFormType: '',
inspector: '',
details: [],
_pendingNewStatus: '',
_pendingReason: '',
_pendingOldStatus: '',
});


// --- Computed & Helpers ---

const getPMFormInfo = (id) => {
const form = pmFormPool.value.find(f => f.id === id);
return form ? { name: form.name, type: form.type, id: form.id } : { name: '未知單據', type: 'N/A', id: null };
};
// 狀態顏色輔助函數
const getStatusColor = (status) => STATUS_MAP[status] || 'bg-gray-500';
// 篩選與搜尋邏輯
const filteredEquipment = computed(() => {
let records = equipmentRecords.value;
const keyword = searchKeyword.value.toLowerCase();
const filterType = activeFilter.value;

if (filterType !== '全部') {
records = records.filter(eq => eq.type === filterType);
}
if (keyword) {
records = records.filter(eq =>
eq.no.toLowerCase().includes(keyword) ||
eq.name.toLowerCase().includes(keyword) ||
eq.status.toLowerCase().includes(keyword) ||
(eq.pmHours !== undefined && String(eq.pmHours).includes(keyword))
);
}
return records;
});

// 狀態變更彈窗中可選的目標狀態
const availableStatuses = (currentStatus) => {
return Object.keys(STATUS_MAP).filter(s => s !== currentStatus);
};


// --- 單據卡控綁定邏輯 ---

const addPMFormBinding = () => {
const { fromStatus, toStatus } = dialog.tempBinding;
if (!fromStatus || !toStatus) {
alert('請選擇起始狀態和目標狀態!');
return;
}
const key = `${fromStatus}->${toStatus}`;
if (dialog.data.maintenanceForms[key]) {
alert(`錯誤:${key} 的卡控點已存在!`);
return;
}
const defaultFormId = pmFormPool.value[0]?.id || null;
dialog.data.maintenanceForms[key] = defaultFormId;
dialog.tempBinding.fromStatus = '';
dialog.tempBinding.toStatus = '';
};

const removePMFormBinding = (statusKey) => {
if (confirm(`確定要移除狀態轉換 ${statusKey} 的保養單據綁定嗎?`)) {
delete dialog.data.maintenanceForms[statusKey];
}
};

// 儲存保養綁定
const saveMaintenanceBinding = () => {
const data = dialog.data;
const index = equipmentRecords.value.findIndex(e => e.id === data.id);
const cleanedForms = {};
for (const key in data.maintenanceForms) {
if (data.maintenanceForms[key] !== null && data.maintenanceForms[key] !== '') {
if (!pmFormPool.value.find(f => f.id === data.maintenanceForms[key])) {
alert(`錯誤:狀態轉換 ${key} 綁定的保養單 ID 無效!`);
return;
}
cleanedForms[key] = data.maintenanceForms[key];
}
}
if (index !== -1) {
equipmentRecords.value[index].maintenanceForms = cleanedForms;
alert(`設備 ${data.no} 的保養單據綁定已更新 (${Object.keys(cleanedForms).length} 項卡控點)。`);
}
closeDialog();
};

// 儲存 PM 時數設定 (保留程式功能)
const savePmConfig = () => {
const data = dialog.data;
const pmHours = parseFloat(data.pmHours);

if (isNaN(pmHours) || pmHours < 0) {
alert('PM 時數必須是一個非負數值!');
return;
}

const index = equipmentRecords.value.findIndex(e => e.id === data.id);
if (index !== -1) {
equipmentRecords.value[index].pmHours = pmHours;
alert(`設備 ${data.no} 的 PM 時數已更新為 ${pmHours} Hr。`);
}
closeDialog();
};


// --- 導航與狀態邏輯 ---

// 導航到保養單填寫介面
const navigateToPMEntry = (equipmentNo, equipmentName, pmForm, newStatus, reason, oldStatus) => {
closeDialog();
if (!pmForm) return;
const formTemplate = pmFormPool.value.find(f => f.id === pmForm.id);

Object.assign(pmEntryForm, {
equipmentNo: equipmentNo,
equipmentName: equipmentName,
pmFormId: pmForm.id,
pmFormName: pmForm.name,
pmFormType: pmForm.type,
inspector: '',
details: JSON.parse(JSON.stringify(formTemplate.details)),
_pendingNewStatus: newStatus,
_pendingReason: reason,
_pendingOldStatus: oldStatus,
});
viewMode.value = 'pm_entry';
};


// 打開 Dialog
const openDialog = (mode, equipment = {}) => {
dialog.mode = mode;
const eqData = JSON.parse(JSON.stringify(equipment));
dialog.data = eqData;
if (mode === 'status_change') {
dialog.statusChange.newStatus = availableStatuses(eqData.status)[0] || '';
dialog.statusChange.reason = '';
} else if (mode === 'bind_pm') {
if (!dialog.data.maintenanceForms) {
dialog.data.maintenanceForms = {};
}
dialog.tempBinding = { fromStatus: '', toStatus: '' };
} else if (mode === 'pm_config') {
dialog.data.pmHours = eqData.pmHours !== undefined ? eqData.pmHours : 0;
dialog.data.name = eqData.name;
}
};

// 關閉 Dialog
const closeDialog = () => {
dialog.mode = null;
dialog.data = {};
};
// 略過單據,直接執行狀態變更
const closeDialogAndExecuteStatusChange = (data) => {
const index = equipmentRecords.value.findIndex(e => e.id === data.id);
if (index === -1) return;

const currentRecord = equipmentRecords.value[index];
const newStatus = data._pendingNewStatus;
const oldStatus = data._pendingOldStatus;
const reason = data._pendingReason;

currentRecord.status = newStatus;
const logEntry = {
time: new Date().toLocaleTimeString(),
oldStatus: oldStatus,
newStatus: newStatus,
reason: `[跳過保養單據] ${reason}`
};
currentRecord.statusLog.unshift(logEntry);
alert(`設備 ${data.no} 狀態已直接變更為 ${newStatus}。`);
closeDialog();
};


// 儲存設備狀態變更 (核心邏輯,加入卡控檢查)
const saveEquipmentStatusChange = () => {
const data = dialog.data;
const { newStatus, reason } = dialog.statusChange;

if (!newStatus || !reason) {
alert('請選擇目標狀態並填寫變更原因!');
return;
}

const index = equipmentRecords.value.findIndex(e => e.id === data.id);
if (index === -1) return;

const currentRecord = equipmentRecords.value[index];
const oldStatus = currentRecord.status;

// 狀態轉換卡控檢查
const transitionKey = `${oldStatus}->${newStatus}`;
const requiredFormId = currentRecord.maintenanceForms ? currentRecord.maintenanceForms[transitionKey] : null;

if (requiredFormId) {
const requiredFormInfo = getPMFormInfo(requiredFormId);
data._pendingNewStatus = newStatus;
data._pendingReason = reason;
data._pendingOldStatus = oldStatus;

closeDialog();
openDialog('pm_prompt', {
no: data.no, name: data.name, id: data.id,
pendingPM: [requiredFormInfo],
_pendingNewStatus: newStatus,
_pendingReason: reason,
_pendingOldStatus: oldStatus,
});
return;
}
currentRecord.status = newStatus;
const logEntry = {
time: new Date().toLocaleTimeString(),
oldStatus: oldStatus,
newStatus: newStatus,
reason: reason
};
currentRecord.statusLog.unshift(logEntry);
alert(`設備 ${data.no} 狀態已變更為 ${newStatus}。`);

closeDialog();
};


// 提交保養單的邏輯
const submitPMForm = () => {
if (!pmEntryForm.inspector || pmEntryForm.details.some(d => !d.result)) {
alert('請填寫保養人員及所有點檢結果!');
return;
}
alert(`✅ 保養數據提交成功!設備: ${pmEntryForm.equipmentNo}, 清單: ${pmEntryForm.pmFormName}`);
const eqIndex = equipmentRecords.value.findIndex(e => e.no === pmEntryForm.equipmentNo);
if (eqIndex !== -1) {
const currentRecord = equipmentRecords.value[eqIndex];
const newStatus = pmEntryForm._pendingNewStatus;
const oldStatus = pmEntryForm._pendingOldStatus;
const reason = `[${pmEntryForm.pmFormName}] 填寫完成,狀態變更為 ${newStatus}。 (原原因: ${pmEntryForm._pendingReason})`;
currentRecord.status = newStatus;

const logEntry = {
time: new Date().toLocaleTimeString(),
oldStatus: oldStatus,
newStatus: newStatus,
reason: reason
};
currentRecord.statusLog.unshift(logEntry);
}

viewMode.value = 'list';
};


return {
Object, // <--- 關鍵 Object 全域對象暴露給 template解讀 Object.keys 報錯
viewMode,
activeFilter,
searchKeyword,
filteredEquipment,
dialog,
pmFormPool,
pmEntryForm,
STATUS_MAP,
openDialog,
closeDialog,
getStatusColor,
availableStatuses,
saveEquipmentStatusChange,
saveMaintenanceBinding,
savePmConfig,
addPMFormBinding,
removePMFormBinding,
closeDialogAndExecuteStatusChange,
navigateToPMEntry,
submitPMForm,
};
}
}).mount('#app');
</script>
</body>
</html>
    (1-1/1)