refactor: 新增 ui.js 和 main.js 模組,啟用 ES6 Modules

新增檔案:
- js/ui.js - UI 操作、模組切換、預覽更新、表單資料收集
- js/main.js - 主程式初始化、事件監聽器設置、快捷鍵

更新檔案:
- index.html - 引用 ES6 模組 (type="module")

功能:
 模組切換功能
 標籤頁切換
 表單欄位監聽
 JSON 預覽更新
 快捷鍵支援 (Ctrl+S, Ctrl+N)
 用戶信息載入
 登出功能

注意:
- 大部分 JavaScript 代碼仍在 HTML 中(約 2400 行)
- 已建立核心模組架構,便於後續逐步遷移
- 使用 ES6 Modules,需要通過 HTTP Server 運行

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-12-05 17:18:28 +08:00
parent ee3db29c32
commit 12ceccc3d3
27 changed files with 9712 additions and 19 deletions

291
js/ui.js Normal file
View File

@@ -0,0 +1,291 @@
/**
* UI - UI 操作函式
* 包含模組切換、預覽更新、表單資料收集
*/
import { showToast } from './utils.js';
import { categoryMap, natureMap, jobCategoryMap } from './config.js';
// ==================== 模組切換 ====================
/**
* 切換頁面模組
* @param {string} moduleName - 模組名稱position/job/jobdesc/positionlist/deptfunction/admin
*/
export function switchModule(moduleName) {
document.querySelectorAll('.module-btn').forEach(b => {
b.classList.remove('active', 'job-active', 'desc-active');
});
document.querySelectorAll('.module-content').forEach(c => c.classList.remove('active'));
const targetBtn = document.querySelector(`.module-btn[data-module="${moduleName}"]`);
if (targetBtn) {
targetBtn.classList.add('active');
if (moduleName === 'job') targetBtn.classList.add('job-active');
if (moduleName === 'jobdesc') targetBtn.classList.add('desc-active');
}
const targetModule = document.getElementById('module-' + moduleName);
if (targetModule) {
targetModule.classList.add('active');
}
// 自動刷新崗位清單(需要從其他模組匯入)
if (moduleName === 'positionlist' && typeof window.loadPositionList === 'function') {
window.loadPositionList();
}
updatePreview();
}
// ==================== 表單資料收集 ====================
/**
* 收集崗位表單資料
* @returns {Object} - 崗位資料(分為 basicInfo 和 recruitInfo
*/
export function getPositionFormData() {
const form = document.getElementById('positionForm');
const formData = new FormData(form);
const data = { basicInfo: {}, recruitInfo: {} };
const basicFields = ['positionCode', 'positionName', 'positionCategory', 'positionCategoryName',
'positionNature', 'positionNatureName', 'headcount', 'positionLevel',
'effectiveDate', 'positionDesc', 'positionRemark'];
const recruitFields = ['minEducation', 'requiredGender', 'salaryRange', 'workExperience',
'minAge', 'maxAge', 'jobType', 'recruitPosition', 'jobTitle', 'jobDesc',
'positionReq', 'titleReq', 'majorReq', 'skillReq', 'langReq', 'otherReq',
'superiorPosition', 'recruitRemark'];
basicFields.forEach(field => {
const value = formData.get(field);
if (value) data.basicInfo[field] = value;
});
recruitFields.forEach(field => {
const value = formData.get(field);
if (value) data.recruitInfo[field] = value;
});
return data;
}
/**
* 收集職務表單資料
* @returns {Object} - 職務資料
*/
export function getJobFormData() {
const form = document.getElementById('jobForm');
const formData = new FormData(form);
const data = {};
const fields = ['jobCategoryCode', 'jobCategoryName', 'jobCode', 'jobName', 'jobNameEn',
'jobEffectiveDate', 'jobHeadcount', 'jobSortOrder', 'jobRemark', 'jobLevel'];
fields.forEach(field => {
const value = formData.get(field);
if (value) data[field] = value;
});
data.hasAttendanceBonus = document.getElementById('hasAttendanceBonus').checked;
data.hasHousingAllowance = document.getElementById('hasHousingAllowance').checked;
return data;
}
/**
* 收集崗位描述表單資料
* @returns {Object} - 崗位描述資料
*/
export function getJobDescFormData() {
const form = document.getElementById('jobDescForm');
if (!form) return {};
const formData = new FormData(form);
const data = { basicInfo: {}, positionInfo: {}, responsibilities: {}, requirements: {} };
// Basic Info
['empNo', 'empName', 'positionCode', 'versionDate'].forEach(field => {
const el = document.getElementById('jd_' + field);
if (el && el.value) data.basicInfo[field] = el.value;
});
// Position Info
['positionName', 'department', 'positionEffectiveDate', 'directSupervisor',
'positionGradeJob', 'reportTo', 'directReports', 'workLocation', 'empAttribute'].forEach(field => {
const el = document.getElementById('jd_' + field);
if (el && el.value) data.positionInfo[field] = el.value;
});
// Purpose & Responsibilities
const purpose = document.getElementById('jd_positionPurpose');
if (purpose && purpose.value) data.responsibilities.positionPurpose = purpose.value;
const mainResp = document.getElementById('jd_mainResponsibilities');
if (mainResp && mainResp.value) data.responsibilities.mainResponsibilities = mainResp.value;
// Requirements
['education', 'basicSkills', 'professionalKnowledge', 'workExperienceReq', 'otherRequirements'].forEach(field => {
const el = document.getElementById('jd_' + field);
if (el && el.value) data.requirements[field] = el.value;
});
return data;
}
/**
* 收集部門職責表單資料
* @returns {Object} - 部門職責資料
*/
export function getDeptFunctionFormData() {
const form = document.getElementById('deptFunctionForm');
if (!form) return {};
const formData = new FormData(form);
const data = {};
const fields = ['deptFunctionCode', 'deptFunctionName', 'deptFunctionBU',
'deptFunctionDept', 'deptManager', 'deptMission', 'deptVision',
'deptCoreFunctions', 'deptKPIs'];
fields.forEach(field => {
const value = formData.get(field);
if (value) data[field] = value;
});
return data;
}
// ==================== 預覽更新 ====================
/**
* 更新 JSON 預覽
*/
export function updatePreview() {
const activeBtn = document.querySelector('.module-btn.active');
if (!activeBtn) return;
const activeModule = activeBtn.dataset.module;
let data;
if (activeModule === 'position') {
data = { module: '崗位基礎資料', ...getPositionFormData() };
} else if (activeModule === 'job') {
data = { module: '職務基礎資料', ...getJobFormData() };
} else if (activeModule === 'jobdesc') {
data = { module: '崗位描述', ...getJobDescFormData() };
} else if (activeModule === 'deptfunction') {
data = { module: '部門職責', ...getDeptFunctionFormData() };
} else {
return; // 其他模組不顯示預覽
}
const previewEl = document.getElementById('jsonPreview');
if (previewEl) {
previewEl.textContent = JSON.stringify(data, null, 2);
}
}
// ==================== 表單邏輯輔助函式 ====================
/**
* 更新崗位類別中文名稱
*/
export function updateCategoryName() {
const category = document.getElementById('positionCategory').value;
document.getElementById('positionCategoryName').value = categoryMap[category] || '';
updatePreview();
}
/**
* 更新崗位性質中文名稱
*/
export function updateNatureName() {
const nature = document.getElementById('positionNature').value;
document.getElementById('positionNatureName').value = natureMap[nature] || '';
updatePreview();
}
/**
* 更新職務類別中文名稱
*/
export function updateJobCategoryName() {
const category = document.getElementById('jobCategoryCode').value;
document.getElementById('jobCategoryName').value = jobCategoryMap[category] || '';
updatePreview();
}
/**
* 修改崗位編號
*/
export function changePositionCode() {
const currentCode = document.getElementById('positionCode').value;
const newCode = prompt('請輸入新的崗位編號:', currentCode);
if (newCode && newCode !== currentCode) {
document.getElementById('positionCode').value = newCode;
showToast('崗位編號已更改!');
updatePreview();
}
}
/**
* 修改職務編號
*/
export function changeJobCode() {
const currentCode = document.getElementById('jobCode').value;
const newCode = prompt('請輸入新的職務編號:', currentCode);
if (newCode && newCode !== currentCode) {
document.getElementById('jobCode').value = newCode;
showToast('職務編號已更改!');
updatePreview();
}
}
// ==================== 模態框函式(待整合)====================
/**
* 開啟專業科目選擇模態框
*/
export function openMajorModal() {
const modal = document.getElementById('majorModal');
if (modal) {
modal.classList.add('show');
}
}
/**
* 關閉專業科目選擇模態框
*/
export function closeMajorModal() {
const modal = document.getElementById('majorModal');
if (modal) {
modal.classList.remove('show');
}
}
/**
* 確認選擇專業科目
*/
export function confirmMajor() {
const selected = [];
document.querySelectorAll('#majorModal input[type="checkbox"]:checked').forEach(cb => {
selected.push(cb.value);
});
document.getElementById('majorReq').value = selected.join(', ');
closeMajorModal();
updatePreview();
}
// 將函式掛載到 window 上以便內聯事件處理器使用
if (typeof window !== 'undefined') {
window.switchModule = switchModule;
window.updateCategoryName = updateCategoryName;
window.updateNatureName = updateNatureName;
window.updateJobCategoryName = updateJobCategoryName;
window.changePositionCode = changePositionCode;
window.changeJobCode = changeJobCode;
window.openMajorModal = openMajorModal;
window.closeMajorModal = closeMajorModal;
window.confirmMajor = confirmMajor;
window.updatePreview = updatePreview;
}