/**
* 管理功能模組
* 處理使用者管理、系統設定和統計功能
*/
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;
}