/** * 管理功能模組 * 處理使用者管理、系統設定和統計功能 */ import { CSVUtils } from './csv.js'; const API_BASE_URL = '/api'; // ==================== 使用者管理 ==================== let usersData = [ { employeeId: 'A001', name: '系統管理員', email: 'admin@company.com', role: 'superadmin', createdAt: '2024-01-01' }, { employeeId: 'A002', name: '人資主管', email: 'hr_manager@company.com', role: 'admin', createdAt: '2024-01-15' }, { employeeId: 'A003', name: '一般員工', email: 'employee@company.com', role: 'user', createdAt: '2024-02-01' } ]; let editingUserId = null; export function showAddUserModal() { editingUserId = null; document.getElementById('userModalTitle').textContent = '新增使用者'; document.getElementById('userEmployeeId').value = ''; document.getElementById('userName').value = ''; document.getElementById('userEmail').value = ''; document.getElementById('userRole').value = ''; document.getElementById('userEmployeeId').disabled = false; document.getElementById('userModal').style.display = 'flex'; } export function editUser(employeeId) { const user = usersData.find(u => u.employeeId === employeeId); if (!user) return; editingUserId = employeeId; document.getElementById('userModalTitle').textContent = '編輯使用者'; document.getElementById('userEmployeeId').value = user.employeeId; document.getElementById('userEmployeeId').disabled = true; document.getElementById('userName').value = user.name; document.getElementById('userEmail').value = user.email; document.getElementById('userRole').value = user.role; document.getElementById('userModal').style.display = 'flex'; } export function closeUserModal() { document.getElementById('userModal').style.display = 'none'; editingUserId = null; } export function saveUser(event) { event.preventDefault(); const employeeId = document.getElementById('userEmployeeId').value; const name = document.getElementById('userName').value; const email = document.getElementById('userEmail').value; const role = document.getElementById('userRole').value; if (!employeeId || !name || !email || !role) { if (typeof showToast === 'function') showToast('請填寫所有必填欄位'); return; } if (editingUserId) { const index = usersData.findIndex(u => u.employeeId === editingUserId); if (index > -1) { usersData[index] = { ...usersData[index], name, email, role }; if (typeof showToast === 'function') showToast('使用者已更新'); } } else { if (usersData.some(u => u.employeeId === employeeId)) { if (typeof showToast === 'function') showToast('工號已存在'); return; } usersData.push({ employeeId, name, email, role, createdAt: new Date().toISOString().split('T')[0] }); if (typeof showToast === 'function') showToast('使用者已新增'); } closeUserModal(); renderUserList(); } export function deleteUser(employeeId) { if (confirm('確定要刪除此使用者嗎?')) { usersData = usersData.filter(u => u.employeeId !== employeeId); renderUserList(); if (typeof showToast === 'function') showToast('使用者已刪除'); } } export function renderUserList() { const tbody = document.getElementById('userListBody'); if (!tbody) return; const roleLabels = { 'superadmin': { text: '最高權限管理者', color: '#e74c3c' }, 'admin': { text: '管理者', color: '#f39c12' }, 'user': { text: '一般使用者', color: '#27ae60' } }; tbody.innerHTML = usersData.map(user => { const roleInfo = roleLabels[user.role] || { text: user.role, color: '#999' }; const isSuperAdmin = user.role === 'superadmin'; return ` ${sanitizeHTML(user.employeeId)} ${sanitizeHTML(user.name)} ${sanitizeHTML(user.email)} ${roleInfo.text} ${sanitizeHTML(user.createdAt)} ${!isSuperAdmin ? `` : ''} `; }).join(''); } export function exportUsersCSV() { const headers = ['employeeId', 'name', 'email', 'role', 'createdAt']; CSVUtils.exportToCSV(usersData, 'users.csv', headers); if (typeof showToast === 'function') showToast('使用者清單已匯出!'); } // ==================== 用戶信息與登出功能 ==================== export function loadUserInfo() { const currentUser = localStorage.getItem('currentUser'); if (currentUser) { try { const user = JSON.parse(currentUser); const userNameEl = document.getElementById('userName'); if (userNameEl) { userNameEl.textContent = user.name || user.username; } const userRoleEl = document.getElementById('userRole'); if (userRoleEl) { let roleText = ''; switch(user.role) { case 'user': roleText = '一般使用者 ★☆☆'; break; case 'admin': roleText = '管理者 ★★☆'; break; case 'superadmin': roleText = '最高管理者 ★★★'; break; default: roleText = '一般使用者'; } userRoleEl.textContent = roleText; } const userAvatarEl = document.getElementById('userAvatar'); if (userAvatarEl) { const avatarText = (user.name || user.username || 'U').charAt(0).toUpperCase(); userAvatarEl.textContent = avatarText; } console.log('用戶信息已載入:', user.name, user.role); } catch (error) { console.error('載入用戶信息失敗:', error); } } else { console.warn('未找到用戶信息,重定向到登入頁面'); window.location.href = 'login.html'; } } export function logout() { if (confirm('確定要登出系統嗎?')) { localStorage.removeItem('currentUser'); if (typeof showToast === 'function') showToast('已成功登出系統'); setTimeout(() => { window.location.href = 'login.html'; }, 1000); } } // ==================== Ollama 模型設定 ==================== export function saveOllamaModel() { const saveBtn = document.getElementById('saveModelBtn'); if (saveBtn) { saveBtn.style.display = 'inline-flex'; } hideAllMessages(); } export function saveOllamaModelWithConfirmation() { const reasonerRadio = document.getElementById('model-reasoner'); const chatRadio = document.getElementById('model-chat'); let selectedModel = ''; if (reasonerRadio && reasonerRadio.checked) { selectedModel = 'deepseek-reasoner'; } else if (chatRadio && chatRadio.checked) { selectedModel = 'deepseek-chat'; } if (selectedModel) { localStorage.setItem('ollamaModel', selectedModel); const saveBtn = document.getElementById('saveModelBtn'); if (saveBtn) { saveBtn.style.display = 'none'; } hideAllMessages(); const successDiv = document.getElementById('modelSaveSuccess'); if (successDiv) { successDiv.style.display = 'block'; setTimeout(() => { successDiv.style.display = 'none'; }, 3000); } console.log('Ollama 模型已設定為:', selectedModel); } } export function loadOllamaModel() { const savedModel = localStorage.getItem('ollamaModel') || 'deepseek-reasoner'; const reasonerRadio = document.getElementById('model-reasoner'); const chatRadio = document.getElementById('model-chat'); const gptossRadio = document.getElementById('model-gptoss'); if (savedModel === 'deepseek-reasoner' && reasonerRadio) { reasonerRadio.checked = true; } else if (savedModel === 'deepseek-chat' && chatRadio) { chatRadio.checked = true; } else if (savedModel === 'gpt-oss:120b' && gptossRadio) { gptossRadio.checked = true; } console.log('已載入 Ollama 模型設定:', savedModel); } export function getOllamaModel() { return localStorage.getItem('ollamaModel') || 'deepseek-reasoner'; } export function hideAllMessages() { const messages = ['connectionSuccess', 'connectionError', 'modelSaveSuccess']; messages.forEach(id => { const elem = document.getElementById(id); if (elem) elem.style.display = 'none'; }); } export async function testOllamaConnection() { const testBtn = document.getElementById('testConnectionBtn'); const reasonerRadio = document.getElementById('model-reasoner'); const chatRadio = document.getElementById('model-chat'); let selectedModel = ''; if (reasonerRadio && reasonerRadio.checked) { selectedModel = 'deepseek-reasoner'; } else if (chatRadio && chatRadio.checked) { selectedModel = 'deepseek-chat'; } if (!selectedModel) { if (typeof showToast === 'function') showToast('請先選擇一個模型'); return; } if (testBtn) { testBtn.disabled = true; testBtn.textContent = '測試中...'; } hideAllMessages(); try { const response = await fetch('/api/llm/generate', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ api: 'ollama', model: selectedModel, prompt: '請用一句話說明你是誰', max_tokens: 100 }), timeout: 30000 }); const data = await response.json(); if (data.success) { const successDiv = document.getElementById('connectionSuccess'); if (successDiv) { successDiv.style.display = 'block'; setTimeout(() => { successDiv.style.display = 'none'; }, 5000); } console.log('測試回應:', data.text); } else { throw new Error(data.error || '未知錯誤'); } } catch (error) { const errorDiv = document.getElementById('connectionError'); const errorMsg = document.getElementById('connectionErrorMessage'); if (errorDiv && errorMsg) { errorMsg.textContent = error.message; errorDiv.style.display = 'block'; setTimeout(() => { errorDiv.style.display = 'none'; }, 10000); } console.error('連線測試失敗:', error); } finally { if (testBtn) { testBtn.disabled = false; testBtn.innerHTML = ` 測試連線 `; } } } // ==================== 崗位資料管理功能 ==================== export async function exportCompletePositionData() { try { if (typeof showToast === 'function') showToast('正在準備匯出資料...'); window.location.href = API_BASE_URL + '/position-list/export'; setTimeout(() => { if (typeof showToast === 'function') showToast('崗位資料匯出成功!'); }, 1000); } catch (error) { console.error('匯出錯誤:', error); alert('匯出失敗: ' + error.message); } } export async function refreshPositionStats() { try { if (typeof showToast === 'function') showToast('正在更新統計資料...'); const response = await fetch(API_BASE_URL + '/position-list'); const result = await response.json(); if (result.success) { const positions = result.data; const total = positions.length; const described = positions.filter(p => p.hasDescription).length; const undescribed = total - described; const totalEl = document.getElementById('totalPositionsCount'); const describedEl = document.getElementById('describedPositionsCount'); const undescribedEl = document.getElementById('undescribedPositionsCount'); if (totalEl) totalEl.textContent = total; if (describedEl) describedEl.textContent = described; if (undescribedEl) undescribedEl.textContent = undescribed; if (typeof showToast === 'function') showToast('統計資料已更新'); } else { alert('更新統計失敗: ' + (result.error || '未知錯誤')); } } catch (error) { console.error('更新統計錯誤:', error); alert('更新統計失敗: ' + error.message); } } // ==================== 工具函數 ==================== function sanitizeHTML(str) { if (str === null || str === undefined) return ''; const temp = document.createElement('div'); temp.textContent = str; return temp.innerHTML; } // 暴露到全域 if (typeof window !== 'undefined') { window.showAddUserModal = showAddUserModal; window.editUser = editUser; window.closeUserModal = closeUserModal; window.saveUser = saveUser; window.deleteUser = deleteUser; window.renderUserList = renderUserList; window.exportUsersCSV = exportUsersCSV; window.logout = logout; window.testOllamaConnection = testOllamaConnection; window.saveOllamaModelWithConfirmation = saveOllamaModelWithConfirmation; window.exportCompletePositionData = exportCompletePositionData; window.refreshPositionStats = refreshPositionStats; }