|
<!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>
|
|
</head>
|
|
<body class="bg-slate-100 p-10">
|
|
|
|
<div id="app">
|
|
<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 mb-8 border border-gray-200">
|
|
<div class="flex justify-between items-center mb-4">
|
|
<h2 class="text-xl font-bold text-gray-700">系統使用者列表</h2>
|
|
<button @click="addUser" class="px-4 py-2 bg-teal-600 text-white rounded-lg hover:bg-teal-700 transition">➕ 新增人員</button>
|
|
</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">身分</th>
|
|
<th class="px-3 py-2 border">技能認證 (Skill Tags)</th>
|
|
<th class="px-3 py-2 border text-center">操作</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<tr v-for="user in users" :key="user.id" class="hover:bg-teal-50">
|
|
<td class="px-3 py-2 border font-medium">{{ user.name }}</td>
|
|
<td class="px-3 py-2 border font-mono">{{ user.employeeNo }}</td>
|
|
<td class="px-3 py-2 border">{{ user.department }}</td>
|
|
<td class="px-3 py-2 border">{{ getRoleName(user.roleId) }}</td>
|
|
<td class="px-3 py-2 border">
|
|
<div class="flex flex-wrap gap-1">
|
|
<span v-for="skill in user.skills" :key="skill" class="bg-blue-100 text-blue-700 px-2 py-0.5 rounded-full text-xs font-semibold border border-blue-200">
|
|
{{ skill }}
|
|
</span>
|
|
<span v-if="!user.skills || user.skills.length === 0" class="text-gray-400 italic text-xs">尚無認證</span>
|
|
</div>
|
|
</td>
|
|
<td class="px-3 py-2 border text-center space-x-2">
|
|
<button @click="editUser(user, 'base')" class="px-2 py-1 bg-blue-500 text-white rounded hover:bg-blue-600 transition">修改資料</button>
|
|
<button @click="editUser(user, 'password')" class="px-2 py-1 bg-yellow-500 text-white rounded hover:bg-yellow-600 transition">密碼</button>
|
|
<button @click="confirmDelete(user)" class="px-2 py-1 bg-red-500 text-white rounded hover:bg-red-600 transition">刪除</button>
|
|
</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
|
|
<!-- 新增/修改彈窗 -->
|
|
<div v-if="editingUser && editMode !== 'password'" class="fixed inset-0 flex items-center justify-center bg-black bg-opacity-60 z-40 overflow-y-auto">
|
|
<div class="bg-white p-6 rounded-xl shadow-2xl w-11/12 max-w-2xl border border-gray-200 my-8">
|
|
<h2 class="text-2xl font-bold text-teal-700 mb-4">
|
|
{{ isNewUser ? '📝 新增人員' : `✏️ 修改人員:${editingUser.name}` }}
|
|
</h2>
|
|
|
|
<div class="grid grid-cols-2 gap-4 mb-6">
|
|
<div class="col-span-1">
|
|
<label class="block mb-1 font-semibold">人員名稱:</label>
|
|
<input v-model="editingUser.name" class="w-full p-2 border rounded-lg focus:ring-2 focus:ring-teal-500 outline-none">
|
|
</div>
|
|
<div class="col-span-1">
|
|
<label class="block mb-1 font-semibold">人員編號:</label>
|
|
<input v-model="editingUser.employeeNo" :readonly="!isNewUser" :class="{'bg-gray-100': !isNewUser}" class="w-full p-2 border rounded-lg focus:ring-2 focus:ring-teal-500 outline-none">
|
|
</div>
|
|
<div class="col-span-1">
|
|
<label class="block mb-1 font-semibold">帳號:</label>
|
|
<input v-model="editingUser.account" class="w-full p-2 border rounded-lg focus:ring-2 focus:ring-teal-500 outline-none">
|
|
</div>
|
|
<div class="col-span-1">
|
|
<label class="block mb-1 font-semibold">部門:</label>
|
|
<input v-model="editingUser.department" class="w-full p-2 border rounded-lg focus:ring-2 focus:ring-teal-500 outline-none">
|
|
</div>
|
|
<div class="col-span-1">
|
|
<label class="block mb-1 font-semibold">系統身分:</label>
|
|
<select v-model="editingUser.roleId" class="w-full p-2 border rounded-lg focus:ring-2 focus:ring-teal-500 outline-none">
|
|
<option disabled value="">--- 請選擇 ---</option>
|
|
<option v-for="role in roles" :key="role.id" :value="role.id">{{ role.name }}</option>
|
|
</select>
|
|
</div>
|
|
<div v-if="isNewUser" class="col-span-1">
|
|
<label class="block mb-1 font-semibold">初始密碼:</label>
|
|
<input v-model="editingUser.password" type="password" class="w-full p-2 border rounded-lg focus:ring-2 focus:ring-teal-500 outline-none">
|
|
</div>
|
|
|
|
<!-- 技能認證多選功能 -->
|
|
<div class="col-span-2 mt-2">
|
|
<label class="block mb-2 font-semibold text-teal-800">技能認證 (Station Qualifications):</label>
|
|
<div class="grid grid-cols-3 gap-2 bg-gray-50 p-3 rounded-lg border border-dashed border-gray-300">
|
|
<label v-for="skill in availableSkills" :key="skill" class="flex items-center space-x-2 cursor-pointer hover:bg-white p-1 rounded transition">
|
|
<input type="checkbox" :value="skill" v-model="editingUser.skills" class="w-4 h-4 text-teal-600 rounded">
|
|
<span class="text-sm text-gray-700">{{ skill }}</span>
|
|
</label>
|
|
</div>
|
|
<p class="mt-1 text-xs text-gray-500 italic">* 勾選後該人員方具備操作對應站點之權限</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="flex justify-end space-x-3">
|
|
<button @click="saveUser" class="px-6 py-2 bg-green-600 text-white rounded-lg hover:bg-green-700 shadow-md">💾 保存</button>
|
|
<button @click="cancelEdit" class="px-6 py-2 bg-gray-200 text-gray-700 rounded-lg hover:bg-gray-300">取消</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 修改密碼彈窗與刪除確認略 (維持原樣) -->
|
|
<div v-if="editingUser && editMode === 'password'" class="fixed inset-0 flex items-center justify-center bg-black bg-opacity-60 z-40">
|
|
<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-teal-700 mb-4">🔑 修改密碼:{{ editingUser.name }}</h2>
|
|
<div class="mb-6 space-y-4">
|
|
<div><label class="block mb-1 font-semibold text-red-600">新密碼:</label><input v-model="editingUser.newPassword" type="password" class="w-full p-2 border rounded-lg" placeholder="請輸入新密碼"></div>
|
|
</div>
|
|
<div class="flex justify-end space-x-3">
|
|
<button @click="savePassword" class="px-5 py-2 bg-green-600 text-white rounded hover:bg-green-700">💾 更新</button>
|
|
<button @click="cancelEdit" class="px-5 py-2 bg-gray-200 text-gray-700 rounded hover:bg-gray-300">取消</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div v-if="userToDelete" class="fixed inset-0 flex items-center justify-center bg-black bg-opacity-60 z-30">
|
|
<div class="bg-white p-6 rounded-xl shadow-2xl w-96 border border-gray-200">
|
|
<h3 class="text-xl font-semibold mb-4 text-red-600">⚠️ 確認刪除?</h3>
|
|
<p class="mb-6 text-lg">{{ userToDelete.name }} ({{ userToDelete.employeeNo }})</p>
|
|
<div class="flex justify-end space-x-3">
|
|
<button @click="deleteUser" class="px-4 py-2 bg-red-600 text-white rounded hover:bg-red-700">確認</button>
|
|
<button @click="userToDelete=null" class="px-4 py-2 bg-gray-200 text-gray-700 rounded hover:bg-gray-300">取消</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
const { createApp, ref } = Vue;
|
|
|
|
createApp({
|
|
setup() {
|
|
const roles = ref([
|
|
{ id: 1, name: '管理員' },
|
|
{ id: 2, name: '操作員' },
|
|
{ id: 3, name: '訪客' },
|
|
]);
|
|
|
|
// 定義系統中所有的站點技能標籤 (可從資料庫取得)
|
|
const availableSkills = ref([
|
|
'SMT貼片', 'DIP插件', '錫爐焊接', 'AOI檢測', '功能測試', '組裝包裝', '倉儲管理'
|
|
]);
|
|
|
|
const users = ref([
|
|
{ id: 1, name: '張小明', employeeNo: 'E001', account: 'xm.zhang', department: '製造一部', roleId: 2, skills: ['SMT貼片', 'AOI檢測'] },
|
|
{ id: 2, name: '李阿華', employeeNo: 'E002', account: 'ah.li', department: '測試課', roleId: 2, skills: ['功能測試'] },
|
|
]);
|
|
|
|
const editingUser = ref(null);
|
|
const userToDelete = ref(null);
|
|
const editMode = ref('base');
|
|
const isNewUser = ref(false);
|
|
|
|
const addUser = () => {
|
|
const newId = users.value.length ? Math.max(...users.value.map(u => u.id)) + 1 : 1;
|
|
editingUser.value = {
|
|
id: newId, name: '', employeeNo: `E${String(newId).padStart(3, '0')}`,
|
|
account: '', department: '', roleId: '', skills: [], password: '', newPassword: ''
|
|
};
|
|
isNewUser.value = true;
|
|
editMode.value = 'base';
|
|
};
|
|
|
|
const editUser = (user, mode) => {
|
|
editingUser.value = JSON.parse(JSON.stringify(user));
|
|
// 如果舊資料沒有 skills 欄位,初始化為陣列
|
|
if(!editingUser.value.skills) editingUser.value.skills = [];
|
|
editingUser.value.newPassword = '';
|
|
editMode.value = mode;
|
|
isNewUser.value = false;
|
|
};
|
|
|
|
const saveUser = () => {
|
|
const u = editingUser.value;
|
|
if (!u.name || !u.employeeNo || !u.account || !u.roleId) {
|
|
alert('請填寫完整基本資料'); return;
|
|
}
|
|
|
|
const idx = users.value.findIndex(user => user.id === u.id);
|
|
if (isNewUser.value) {
|
|
users.value.push(JSON.parse(JSON.stringify(u)));
|
|
} else {
|
|
users.value[idx] = JSON.parse(JSON.stringify(u));
|
|
}
|
|
editingUser.value = null;
|
|
};
|
|
|
|
const savePassword = () => {
|
|
const idx = users.value.findIndex(u => u.id === editingUser.value.id);
|
|
if(idx >= 0 && editingUser.value.newPassword) {
|
|
users.value[idx].password = editingUser.value.newPassword;
|
|
alert('密碼已更新');
|
|
}
|
|
editingUser.value = null;
|
|
};
|
|
|
|
const deleteUser = () => {
|
|
users.value = users.value.filter(u => u.id !== userToDelete.value.id);
|
|
userToDelete.value = null;
|
|
};
|
|
|
|
const confirmDelete = (user) => { userToDelete.value = user; };
|
|
const cancelEdit = () => { editingUser.value = null; };
|
|
const getRoleName = (id) => roles.value.find(r => r.id === id)?.name || '未設定';
|
|
|
|
return {
|
|
users, roles, editingUser, userToDelete, editMode, isNewUser, availableSkills,
|
|
addUser, editUser, cancelEdit, saveUser, savePassword, confirmDelete, deleteUser, getRoleName
|
|
};
|
|
}
|
|
}).mount('#app');
|
|
</script>
|
|
</body>
|
|
</html>
|