|
<!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: {
|
|
'lathe-dark': '#1e293b',
|
|
'lathe-teal': '#0f766e',
|
|
'lathe-accent': '#0284c7',
|
|
}
|
|
}
|
|
}
|
|
}
|
|
</script>
|
|
<style>
|
|
.form-input {
|
|
padding: 8px;
|
|
border: 1px solid #cbd5e1;
|
|
border-radius: 6px;
|
|
width: 100%;
|
|
font-size: 0.875rem;
|
|
}
|
|
.form-input:focus {
|
|
outline: none;
|
|
border-color: #0284c7;
|
|
box-shadow: 0 0 0 2px rgba(2, 132, 199, 0.2);
|
|
}
|
|
.required-label::after {
|
|
content: ' *';
|
|
color: #ef4444;
|
|
margin-left: 2px;
|
|
}
|
|
</style>
|
|
</head>
|
|
<body class="bg-slate-50 p-4 md:p-6">
|
|
|
|
<div id="app" class="max-w-7xl mx-auto space-y-6">
|
|
|
|
<div class="flex flex-col md:flex-row md:items-center md:justify-between border-b-4 border-slate-200 pb-4">
|
|
<div>
|
|
<h1 class="text-2xl md:text-3xl font-bold text-slate-800 flex items-center gap-2">
|
|
🛠️ 車床站點與刀具配置看板
|
|
</h1>
|
|
<p class="text-sm text-slate-500 mt-1">管理車床之現行指派站點</p>
|
|
</div>
|
|
<button
|
|
@click="openDialog('add')"
|
|
class="mt-3 md:mt-0 px-5 py-2.5 bg-lathe-accent text-white font-semibold rounded-lg hover:bg-blue-600 transition shadow-sm flex items-center justify-center gap-2"
|
|
>
|
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" viewBox="0 0 20 20" fill="currentColor"><path fill-rule="evenodd" d="M10 5a1 1 0 011 1v3h3a1 1 0 110 2h-3v3a1 1 0 11-2 0v-3H6a1 1 0 110-2h3V6a1 1 0 011-1z" clip-rule="evenodd" /></svg>
|
|
<span>指派工作站點</span>
|
|
</button>
|
|
</div>
|
|
|
|
<div class="bg-white p-4 rounded-xl shadow-sm border border-slate-200">
|
|
<div class="flex flex-col sm:flex-row gap-3">
|
|
<div class="relative flex-1">
|
|
<input
|
|
v-model="searchKeyword"
|
|
type="text"
|
|
placeholder="輸入關鍵字直接搜尋車床(如:自動線車床01)、線別、站點名稱或刀具編號..."
|
|
class="w-full pl-10 pr-4 py-2.5 border rounded-lg text-sm bg-slate-50 focus:bg-white focus:ring-2 focus:ring-sky-500"
|
|
>
|
|
<div class="absolute left-3 top-3 text-slate-400">
|
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" /></svg>
|
|
</div>
|
|
</div>
|
|
<div v-if="searchKeyword" class="flex items-center text-xs text-slate-500 bg-sky-50 px-3 py-2 rounded-lg border border-sky-100">
|
|
🔍 篩選條件:<span class="font-bold text-sky-700 mx-1">"{{ searchKeyword }}"</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="bg-white rounded-xl shadow-md border border-slate-200 overflow-hidden">
|
|
<div class="p-4 bg-slate-50 border-b flex justify-between items-center">
|
|
<h3 class="font-bold text-slate-700 text-sm md:text-base">車床工作站點與刀具現況總覽</h3>
|
|
<span class="text-xs text-slate-500 font-mono">符合項目: {{ filteredBindings.length }} 筆</span>
|
|
</div>
|
|
|
|
<div class="overflow-x-auto">
|
|
<table class="min-w-full table-auto text-sm text-left">
|
|
<thead class="bg-slate-100 text-slate-700 border-b border-slate-200 font-semibold">
|
|
<tr>
|
|
<th class="px-4 py-3 text-center w-16">ID</th>
|
|
<th class="px-4 py-3 w-32">生產線別</th>
|
|
<th class="px-4 py-3 w-44 text-lathe-teal">車床設備</th>
|
|
<th class="px-4 py-3 w-48">指派工作站點</th>
|
|
<th class="px-4 py-3">APP 現場動態綁定刀具 (可支援多筆)</th>
|
|
<th class="px-4 py-3 w-20 text-center">操作</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody class="divide-y divide-slate-200">
|
|
<tr v-for="item in filteredBindings" :key="item.id" class="hover:bg-sky-50/50 transition-colors">
|
|
<td class="px-4 py-3 text-center text-slate-400 font-mono text-xs">{{ item.id }}</td>
|
|
|
|
<td class="px-4 py-3">
|
|
<span :class="item.lineType === '自動線' ? 'bg-indigo-100 text-indigo-800 border-indigo-200' : 'bg-amber-100 text-amber-800 border-amber-200'" class="inline-block px-2 py-0.5 text-xs font-bold rounded-full border">
|
|
{{ item.lineType }}
|
|
</span>
|
|
</td>
|
|
|
|
<td class="px-4 py-3">
|
|
<span class="inline-flex items-center px-2.5 py-1 rounded-md bg-teal-50 text-lathe-teal font-bold border border-teal-200">
|
|
{{ item.machineNo }}
|
|
</span>
|
|
</td>
|
|
|
|
<td class="px-4 py-3 text-slate-800 font-semibold">
|
|
{{ item.stationName }}
|
|
</td>
|
|
|
|
<td class="px-4 py-3 text-slate-700">
|
|
<div v-if="item.tools && item.tools.length > 0" class="space-y-1.5 py-1">
|
|
<div v-for="(tool, tIdx) in item.tools" :key="tIdx" class="font-mono text-xs bg-slate-100 px-2 py-1 rounded border border-slate-200 inline-block mr-2">
|
|
<span class="font-bold text-slate-800">{{ tool.toolNo }}</span>
|
|
<span v-if="tool.toolName" class="text-slate-500 ml-1.5">({{ tool.toolName }})</span>
|
|
</div>
|
|
</div>
|
|
<div v-else class="text-slate-400 font-mono py-1">-</div>
|
|
</td>
|
|
|
|
<td class="px-4 py-3 text-center">
|
|
<button
|
|
v-if="!item.tools || item.tools.length === 0"
|
|
@click="openDialog('delete', item)"
|
|
class="p-1.5 text-red-600 hover:bg-red-50 rounded-md transition"
|
|
title="刪除此站點配置"
|
|
>
|
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 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>
|
|
</td>
|
|
</tr>
|
|
<tr v-if="filteredBindings.length === 0">
|
|
<td colspan="6" class="px-4 py-8 text-center text-slate-400 bg-slate-200/20">
|
|
📭 沒有找到符合條件的車床站點資料。
|
|
</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
|
|
<div v-if="dialog.mode === 'add'" class="fixed inset-0 flex items-center justify-center bg-black/60 z-50 p-4 backdrop-blur-sm">
|
|
<div class="bg-white p-6 rounded-xl shadow-2xl w-full max-w-md border border-slate-200">
|
|
<h2 class="text-xl font-bold text-slate-800 mb-4 border-b pb-2 flex items-center gap-2">
|
|
<span>🆕 指派車床工作站點</span>
|
|
</h2>
|
|
|
|
<form @submit.prevent="saveBinding" class="space-y-5">
|
|
<div>
|
|
<label class="block mb-1.5 font-semibold text-slate-700 required-label text-sm">1. 選擇生產線別:</label>
|
|
<select v-model="dialog.data.lineType" @change="onLineTypeChange" class="form-input font-bold text-indigo-900 bg-white" required>
|
|
<option value="">-- 請選擇線別 --</option>
|
|
<option value="手動線">手動線</option>
|
|
<option value="自動線">自動線</option>
|
|
</select>
|
|
</div>
|
|
|
|
<div>
|
|
<label class="block mb-1.5 font-semibold text-slate-700 required-label text-sm">2. 選擇目標車床:</label>
|
|
<select
|
|
v-model="dialog.data.machineNo"
|
|
class="form-input font-bold text-lathe-teal bg-white animate-in fade-in duration-200"
|
|
:disabled="!dialog.data.lineType"
|
|
required
|
|
>
|
|
<option value="">{{ dialog.data.lineType ? '-- 請選擇對應設備 --' : '(請先選擇上方生產線別)' }}</option>
|
|
<option v-for="lathe in availableLathes" :key="lathe" :value="lathe">{{ lathe }}</option>
|
|
</select>
|
|
</div>
|
|
|
|
<div>
|
|
<label class="block mb-1.5 font-semibold text-slate-700 required-label text-sm">3. 選擇指定工作站點(途程):</label>
|
|
<select
|
|
v-model="dialog.data.stationName"
|
|
class="form-input bg-white font-bold text-slate-800 animate-in fade-in duration-200"
|
|
:disabled="!dialog.data.machineNo"
|
|
required
|
|
>
|
|
<option value="">{{ dialog.data.machineNo ? '-- 請選擇生產途程站點 --' : '(請先選擇上方目標車床)' }}</option>
|
|
<option v-for="station in availableStations" :key="station" :value="station">
|
|
{{ station }}
|
|
</option>
|
|
</select>
|
|
</div>
|
|
|
|
<div class="flex justify-end space-x-2 border-t pt-3">
|
|
<button @click="closeDialog" type="button" class="px-4 py-2 bg-slate-200 text-slate-700 text-sm font-medium rounded-md hover:bg-slate-300">取消</button>
|
|
<button type="submit" class="px-4 py-2 bg-lathe-teal text-white text-sm font-medium rounded-md hover:bg-teal-800 shadow-sm">
|
|
確認指派
|
|
</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
|
|
<div v-if="dialog.mode === 'delete'" class="fixed inset-0 flex items-center justify-center bg-black/60 z-50 p-4 backdrop-blur-sm">
|
|
<div class="bg-white p-5 rounded-xl shadow-2xl w-full max-w-sm border border-slate-200 animate-in fade-in zoom-in-95 duration-150">
|
|
<h2 class="text-lg font-bold text-red-600 mb-2 flex items-center gap-1.5">
|
|
⚠️ 移除站點指派確認
|
|
</h2>
|
|
<p class="text-slate-600 text-sm mb-4">您確定要將此站點配置從車床中移除嗎?</p>
|
|
|
|
<div class="p-3 bg-slate-50 border border-slate-200 rounded-lg text-xs font-medium text-slate-700 space-y-1.5">
|
|
<div>• 生產線別:<span class="text-slate-900 font-bold">{{ dialog.data.lineType }}</span></div>
|
|
<div>• 目標設備:<span class="text-slate-900 font-bold">{{ dialog.data.machineNo }}</span></div>
|
|
<div>• 途程站點:<span class="text-red-600 font-bold">{{ dialog.data.stationName }}</span></div>
|
|
</div>
|
|
|
|
<div class="flex justify-end space-x-2 mt-5 border-t pt-3">
|
|
<button @click="closeDialog" type="button" class="px-4 py-2 bg-slate-200 text-slate-700 text-sm font-medium rounded-md hover:bg-slate-300">取消</button>
|
|
<button @click="confirmDelete" type="button" class="px-4 py-2 bg-red-600 text-white text-sm font-medium rounded-md hover:bg-red-700 shadow-sm">
|
|
確定刪除
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<script>
|
|
const { createApp, ref, reactive, computed } = Vue;
|
|
|
|
createApp({
|
|
setup() {
|
|
// 1. 分流車床主檔資料清單
|
|
const manualLathes = ref(['車床01', '車床02', '車床03', '車床04', '車床05', '車床06']);
|
|
const autoLathes = ref(['自動線車床01', '自動線車床02', '自動線車床03']);
|
|
|
|
// 2. 途程工作站點清單
|
|
const manualStations = ref([
|
|
'凹面車削', 'BC拋光', '半成品BC檢驗', '黏著膠片', '凸面車削',
|
|
'凸面拋光', '脫膠清洗', 'Edge研磨', '研磨後檢驗', 'BC成品檢驗',
|
|
'度數檢驗', '雷刻', '外觀檢查', '電漿', '包裝'
|
|
]);
|
|
const autoStations = ref([
|
|
'複測站', '自動線_外觀檢查', '自動線_包裝'
|
|
]);
|
|
|
|
const searchKeyword = ref('');
|
|
|
|
// 看板展示模擬數據(相容自動線車床命名)
|
|
const routeToolBindings = ref([
|
|
{
|
|
id: 1,
|
|
machineNo: '車床01',
|
|
lineType: '手動線',
|
|
stationName: '凹面車削',
|
|
tools: [
|
|
{ toolNo: 'T-CN-01', toolName: '金剛石金屬粗刃刀' },
|
|
{ toolNo: 'T-CN-02', toolName: 'R0.45精車精面刀' }
|
|
]
|
|
},
|
|
{
|
|
id: 2,
|
|
machineNo: '自動線車床01',
|
|
lineType: '自動線',
|
|
stationName: '複測站',
|
|
tools: [
|
|
{ toolNo: 'T-AUTO-99', toolName: '自動線高速量測刀組' }
|
|
]
|
|
},
|
|
{ id: 3, machineNo: '車床01', lineType: '手動線', stationName: 'Edge研磨', tools: [] },
|
|
{
|
|
id: 4,
|
|
machineNo: '車床02',
|
|
lineType: '手動線',
|
|
stationName: '凹面車削',
|
|
tools: [
|
|
{ toolNo: 'T-CN-01', toolName: '金剛石金屬粗刃刀' }
|
|
]
|
|
},
|
|
{ id: 5, machineNo: '自動線車床02', lineType: '自動線', stationName: '自動線_外觀檢查', tools: [] },
|
|
{ id: 6, machineNo: '自動線車床03', lineType: '自動線', stationName: '自動線_包裝', tools: [] }
|
|
]);
|
|
|
|
const dialog = reactive({ mode: null, data: {} });
|
|
|
|
// 自動算下一個流水號 ID
|
|
const getNextId = () => {
|
|
if (routeToolBindings.value.length === 0) return 1;
|
|
return Math.max(...routeToolBindings.value.map(b => b.id)) + 1;
|
|
};
|
|
|
|
// 核心連動計算 1:根據選擇線別動態回傳車床清單
|
|
const availableLathes = computed(() => {
|
|
if (dialog.data.lineType === '手動線') return manualLathes.value;
|
|
if (dialog.data.lineType === '自動線') return autoLathes.value;
|
|
return [];
|
|
});
|
|
|
|
// 核心連動計算 2:根據選擇線別動態回傳途程站點清單
|
|
const availableStations = computed(() => {
|
|
if (dialog.data.lineType === '手動線') return manualStations.value;
|
|
if (dialog.data.lineType === '自動線') return autoStations.value;
|
|
return [];
|
|
});
|
|
|
|
// 當最頂層線別改變時,必須把下層選擇值全部洗掉,防止殘留錯誤
|
|
const onLineTypeChange = () => {
|
|
dialog.data.machineNo = '';
|
|
dialog.data.stationName = '';
|
|
};
|
|
|
|
// 關鍵字即時穿透搜尋
|
|
const filteredBindings = computed(() => {
|
|
let list = routeToolBindings.value;
|
|
const keyword = searchKeyword.value.trim().toLowerCase();
|
|
|
|
if (keyword) {
|
|
list = list.filter(item =>
|
|
item.machineNo.toLowerCase().includes(keyword) ||
|
|
item.lineType.toLowerCase().includes(keyword) ||
|
|
item.stationName.toLowerCase().includes(keyword) ||
|
|
item.tools.some(t => t.toolNo.toLowerCase().includes(keyword) || (t.toolName && t.toolName.toLowerCase().includes(keyword)))
|
|
);
|
|
}
|
|
|
|
return list.sort((a, b) => a.machineNo.localeCompare(b.machineNo));
|
|
});
|
|
|
|
// 開啟視窗
|
|
const openDialog = (mode, item = {}) => {
|
|
dialog.mode = mode;
|
|
if (mode === 'add') {
|
|
dialog.data = { lineType: '', machineNo: '', stationName: '' };
|
|
} else if (mode === 'delete') {
|
|
dialog.data = JSON.parse(JSON.stringify(item));
|
|
}
|
|
};
|
|
|
|
const closeDialog = () => { dialog.mode = null; dialog.data = {}; };
|
|
|
|
// 儲存指派
|
|
const saveBinding = () => {
|
|
if (dialog.mode === 'add') {
|
|
routeToolBindings.value.push({
|
|
id: getNextId(),
|
|
lineType: dialog.data.lineType,
|
|
machineNo: dialog.data.machineNo,
|
|
stationName: dialog.data.stationName,
|
|
tools: []
|
|
});
|
|
}
|
|
closeDialog();
|
|
};
|
|
|
|
// 確定刪除
|
|
const confirmDelete = () => {
|
|
const idx = routeToolBindings.value.findIndex(b => b.id === dialog.data.id);
|
|
if (idx !== -1 && (!routeToolBindings.value[idx].tools || routeToolBindings.value[idx].tools.length === 0)) {
|
|
routeToolBindings.value.splice(idx, 1);
|
|
}
|
|
closeDialog();
|
|
};
|
|
|
|
return {
|
|
searchKeyword,
|
|
routeToolBindings,
|
|
filteredBindings,
|
|
dialog,
|
|
availableLathes,
|
|
availableStations,
|
|
onLineTypeChange,
|
|
openDialog,
|
|
closeDialog,
|
|
saveBinding,
|
|
confirmDelete
|
|
};
|
|
}
|
|
}).mount('#app');
|
|
</script>
|
|
</body>
|
|
</html>
|