變更內容: - 所有資料表加上 HR_position_ 前綴 - 整理完整欄位顯示名稱與 ID 對照表 - 模組化 JS 檔案 (admin.js, ai.js, csv.js 等) - 專案結構優化 (docs/, scripts/, tests/ 等) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
392 lines
12 KiB
JavaScript
392 lines
12 KiB
JavaScript
/**
|
||
* 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: {} };
|
||
|
||
// 使用新的 pos_ prefix 欄位
|
||
const basicFieldMapping = {
|
||
'pos_code': 'positionCode',
|
||
'pos_name': 'positionName',
|
||
'pos_category': 'positionCategory',
|
||
'pos_categoryName': 'positionCategoryName',
|
||
'pos_type': 'positionType',
|
||
'pos_typeName': 'positionTypeName',
|
||
'pos_headcount': 'headcount',
|
||
'pos_level': 'positionLevel',
|
||
'pos_effectiveDate': 'effectiveDate',
|
||
'pos_desc': 'positionDesc',
|
||
'pos_remark': 'positionRemark'
|
||
};
|
||
|
||
// 使用新的 rec_ prefix 欄位
|
||
const recruitFieldMapping = {
|
||
'rec_eduLevel': 'minEducation',
|
||
'rec_gender': 'requiredGender',
|
||
'rec_salaryRange': 'salaryRange',
|
||
'rec_expYears': 'workExperience',
|
||
'rec_minAge': 'minAge',
|
||
'rec_maxAge': 'maxAge',
|
||
'rec_jobType': 'jobType',
|
||
'rec_position': 'recruitPosition',
|
||
'rec_jobTitle': 'jobTitle',
|
||
'rec_jobDesc': 'jobDesc',
|
||
'rec_positionReq': 'positionReq',
|
||
'rec_certReq': 'certReq',
|
||
'rec_majorReq': 'majorReq',
|
||
'rec_skillReq': 'skillReq',
|
||
'rec_langReq': 'langReq',
|
||
'rec_otherReq': 'otherReq',
|
||
'rec_superiorCode': 'superiorPosition',
|
||
'rec_remark': 'recruitRemark'
|
||
};
|
||
|
||
Object.entries(basicFieldMapping).forEach(([htmlId, dataKey]) => {
|
||
const el = document.getElementById(htmlId);
|
||
if (el && el.value) data.basicInfo[dataKey] = el.value;
|
||
});
|
||
|
||
Object.entries(recruitFieldMapping).forEach(([htmlId, dataKey]) => {
|
||
const el = document.getElementById(htmlId);
|
||
if (el && el.value) data.recruitInfo[dataKey] = el.value;
|
||
});
|
||
|
||
return data;
|
||
}
|
||
|
||
/**
|
||
* 收集職務表單資料
|
||
* @returns {Object} - 職務資料
|
||
*/
|
||
export function getJobFormData() {
|
||
const form = document.getElementById('jobForm');
|
||
const data = {};
|
||
|
||
// 使用新的 job_ prefix 欄位
|
||
const fieldMapping = {
|
||
'job_category': 'jobCategoryCode',
|
||
'job_categoryName': 'jobCategoryName',
|
||
'job_code': 'jobCode',
|
||
'job_name': 'jobName',
|
||
'job_nameEn': 'jobNameEn',
|
||
'job_level': 'jobLevel',
|
||
'job_effectiveDate': 'jobEffectiveDate',
|
||
'job_sortOrder': 'jobSortOrder',
|
||
'job_headcount': 'jobHeadcount',
|
||
'job_remark': 'jobRemark'
|
||
};
|
||
|
||
Object.entries(fieldMapping).forEach(([htmlId, dataKey]) => {
|
||
const el = document.getElementById(htmlId);
|
||
if (el && el.value) data[dataKey] = el.value;
|
||
});
|
||
|
||
const hasAttBonus = document.getElementById('job_hasAttBonus');
|
||
const hasHouseAllow = document.getElementById('job_hasHouseAllow');
|
||
data.hasAttendanceBonus = hasAttBonus ? hasAttBonus.checked : false;
|
||
data.hasHousingAllowance = hasHouseAllow ? hasHouseAllow.checked : false;
|
||
|
||
return data;
|
||
}
|
||
|
||
/**
|
||
* 收集崗位描述表單資料
|
||
* @returns {Object} - 崗位描述資料
|
||
*/
|
||
export function getJobDescFormData() {
|
||
const form = document.getElementById('jobDescForm');
|
||
if (!form) return {};
|
||
|
||
const data = { basicInfo: {}, positionInfo: {}, responsibilities: {}, requirements: {} };
|
||
|
||
// Basic Info - 使用新的 jd_ prefix
|
||
const basicMapping = {
|
||
'jd_empNo': 'empNo',
|
||
'jd_empName': 'empName',
|
||
'jd_posCode': 'positionCode',
|
||
'jd_versionDate': 'versionDate'
|
||
};
|
||
|
||
Object.entries(basicMapping).forEach(([htmlId, dataKey]) => {
|
||
const el = document.getElementById(htmlId);
|
||
if (el && el.value) data.basicInfo[dataKey] = el.value;
|
||
});
|
||
|
||
// Position Info - 使用新的 jd_ prefix
|
||
const posInfoMapping = {
|
||
'jd_posName': 'positionName',
|
||
'jd_department': 'department',
|
||
'jd_posLevel': 'positionLevel',
|
||
'jd_posEffDate': 'positionEffectiveDate',
|
||
'jd_supervisor': 'directSupervisor',
|
||
'jd_gradeJob': 'positionGradeJob',
|
||
'jd_reportTo': 'reportTo',
|
||
'jd_directReports': 'directReports',
|
||
'jd_location': 'workLocation',
|
||
'jd_empAttr': 'empAttribute'
|
||
};
|
||
|
||
Object.entries(posInfoMapping).forEach(([htmlId, dataKey]) => {
|
||
const el = document.getElementById(htmlId);
|
||
if (el && el.value) data.positionInfo[dataKey] = el.value;
|
||
});
|
||
|
||
// Purpose & Responsibilities - 使用新的 jd_ prefix
|
||
const purpose = document.getElementById('jd_purpose');
|
||
if (purpose && purpose.value) data.responsibilities.positionPurpose = purpose.value;
|
||
|
||
const mainResp = document.getElementById('jd_mainResp');
|
||
if (mainResp && mainResp.value) data.responsibilities.mainResponsibilities = mainResp.value;
|
||
|
||
// Requirements - 使用新的 jd_ prefix
|
||
const reqMapping = {
|
||
'jd_eduLevel': 'education',
|
||
'jd_basicSkills': 'basicSkills',
|
||
'jd_proKnowledge': 'professionalKnowledge',
|
||
'jd_expReq': 'workExperienceReq',
|
||
'jd_otherReq': 'otherRequirements'
|
||
};
|
||
|
||
Object.entries(reqMapping).forEach(([htmlId, dataKey]) => {
|
||
const el = document.getElementById(htmlId);
|
||
if (el && el.value) data.requirements[dataKey] = el.value;
|
||
});
|
||
|
||
return data;
|
||
}
|
||
|
||
/**
|
||
* 收集部門職責表單資料
|
||
* @returns {Object} - 部門職責資料
|
||
*/
|
||
export function getDeptFunctionFormData() {
|
||
const form = document.getElementById('deptFunctionForm');
|
||
if (!form) return {};
|
||
|
||
const data = {};
|
||
|
||
// 使用新的 df_ prefix 欄位
|
||
const fieldMapping = {
|
||
'df_code': 'dfCode',
|
||
'df_name': 'dfName',
|
||
'df_businessUnit': 'businessUnit',
|
||
'df_division': 'division',
|
||
'df_department': 'department',
|
||
'df_section': 'section',
|
||
'df_posTitle': 'positionTitle',
|
||
'df_posLevel': 'positionLevel',
|
||
'df_managerTitle': 'managerTitle',
|
||
'df_effectiveDate': 'effectiveDate',
|
||
'df_headcountLimit': 'headcountLimit',
|
||
'df_status': 'status',
|
||
'df_mission': 'mission',
|
||
'df_vision': 'vision',
|
||
'df_coreFunc': 'coreFunctions',
|
||
'df_kpis': 'kpis',
|
||
'df_collab': 'collaboration',
|
||
'df_remark': 'remark'
|
||
};
|
||
|
||
Object.entries(fieldMapping).forEach(([htmlId, dataKey]) => {
|
||
const el = document.getElementById(htmlId);
|
||
if (el && el.value) data[dataKey] = el.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 categoryEl = document.getElementById('pos_category');
|
||
const categoryNameEl = document.getElementById('pos_categoryName');
|
||
if (categoryEl && categoryNameEl) {
|
||
categoryNameEl.value = categoryMap[categoryEl.value] || '';
|
||
}
|
||
updatePreview();
|
||
}
|
||
|
||
/**
|
||
* 更新崗位性質中文名稱
|
||
*/
|
||
export function updateNatureName() {
|
||
const typeEl = document.getElementById('pos_type');
|
||
const typeNameEl = document.getElementById('pos_typeName');
|
||
if (typeEl && typeNameEl) {
|
||
typeNameEl.value = natureMap[typeEl.value] || '';
|
||
}
|
||
updatePreview();
|
||
}
|
||
|
||
/**
|
||
* 更新職務類別中文名稱
|
||
*/
|
||
export function updateJobCategoryName() {
|
||
const categoryEl = document.getElementById('job_category');
|
||
const categoryNameEl = document.getElementById('job_categoryName');
|
||
if (categoryEl && categoryNameEl) {
|
||
categoryNameEl.value = jobCategoryMap[categoryEl.value] || '';
|
||
}
|
||
updatePreview();
|
||
}
|
||
|
||
/**
|
||
* 修改崗位編號
|
||
*/
|
||
export function changePositionCode() {
|
||
const codeEl = document.getElementById('pos_code');
|
||
if (!codeEl) return;
|
||
const currentCode = codeEl.value;
|
||
const newCode = prompt('請輸入新的崗位編號:', currentCode);
|
||
if (newCode && newCode !== currentCode) {
|
||
codeEl.value = newCode;
|
||
showToast('崗位編號已更改!');
|
||
updatePreview();
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 修改職務編號
|
||
*/
|
||
export function changeJobCode() {
|
||
const codeEl = document.getElementById('job_code');
|
||
if (!codeEl) return;
|
||
const currentCode = codeEl.value;
|
||
const newCode = prompt('請輸入新的職務編號:', currentCode);
|
||
if (newCode && newCode !== currentCode) {
|
||
codeEl.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);
|
||
});
|
||
const majorReqEl = document.getElementById('rec_majorReq');
|
||
if (majorReqEl) {
|
||
majorReqEl.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;
|
||
}
|