backup: 完成 HR_position_ 表格前綴重命名與欄位對照表整理
變更內容: - 所有資料表加上 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>
This commit is contained in:
571
js/forms.js
Normal file
571
js/forms.js
Normal file
@@ -0,0 +1,571 @@
|
||||
/**
|
||||
* 表單邏輯模組
|
||||
* 處理各表單的資料操作、驗證和提交
|
||||
*/
|
||||
|
||||
const API_BASE_URL = '/api';
|
||||
|
||||
// ==================== 常數映射 ====================
|
||||
|
||||
export const categoryMap = { '01': '技術職', '02': '管理職', '03': '業務職', '04': '行政職' };
|
||||
export const natureMap = { 'FT': '全職', 'PT': '兼職', 'CT': '約聘', 'IN': '實習' };
|
||||
export const jobCategoryMap = { 'MGR': '管理職', 'TECH': '技術職', 'SALE': '業務職', 'ADMIN': '行政職', 'RD': '研發職', 'PROD': '生產職' };
|
||||
|
||||
// ==================== 崗位清單全域變數 ====================
|
||||
export let positionListData = [];
|
||||
export let currentSortColumn = '';
|
||||
export let currentSortDirection = 'asc';
|
||||
|
||||
// ==================== 崗位基礎資料表單 ====================
|
||||
|
||||
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 changePositionCode() {
|
||||
const currentCode = document.getElementById('positionCode').value;
|
||||
const newCode = prompt('請輸入新的崗位編號:', currentCode);
|
||||
if (newCode && newCode !== currentCode) {
|
||||
document.getElementById('positionCode').value = newCode;
|
||||
showToast('崗位編號已更改!');
|
||||
updatePreview();
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
export function savePositionAndExit() {
|
||||
const form = document.getElementById('positionForm');
|
||||
if (!form.checkValidity()) { form.reportValidity(); return; }
|
||||
console.log('Save Position:', getPositionFormData());
|
||||
showToast('崗位資料已保存!');
|
||||
}
|
||||
|
||||
export function savePositionAndNew() {
|
||||
const form = document.getElementById('positionForm');
|
||||
if (!form.checkValidity()) { form.reportValidity(); return; }
|
||||
console.log('Save Position:', getPositionFormData());
|
||||
showToast('崗位資料已保存,請繼續新增!');
|
||||
form.reset();
|
||||
document.getElementById('effectiveDate').value = new Date().toISOString().split('T')[0];
|
||||
updatePreview();
|
||||
}
|
||||
|
||||
export async function saveToPositionList() {
|
||||
const form = document.getElementById('positionForm');
|
||||
if (!form.checkValidity()) {
|
||||
form.reportValidity();
|
||||
return;
|
||||
}
|
||||
|
||||
const formData = getPositionFormData();
|
||||
|
||||
if (!formData.basicInfo.positionCode) {
|
||||
alert('請輸入崗位編號');
|
||||
return;
|
||||
}
|
||||
if (!formData.basicInfo.positionName) {
|
||||
alert('請輸入崗位名稱');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch(API_BASE_URL + '/positions', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(formData)
|
||||
});
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
if (result.success) {
|
||||
showToast(result.message || '崗位已成功儲存至崗位清單!');
|
||||
setTimeout(() => {
|
||||
switchModule('positionlist');
|
||||
}, 1500);
|
||||
} else {
|
||||
alert('儲存失敗: ' + (result.error || '未知錯誤'));
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('儲存錯誤:', error);
|
||||
alert('儲存失敗: ' + error.message);
|
||||
}
|
||||
}
|
||||
|
||||
export function cancelPositionForm() {
|
||||
if (confirm('確定要取消嗎?未保存的資料將會遺失。')) {
|
||||
document.getElementById('positionForm').reset();
|
||||
updatePreview();
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== 職務基礎資料表單 ====================
|
||||
|
||||
export function updateJobCategoryName() {
|
||||
const category = document.getElementById('jobCategoryCode').value;
|
||||
document.getElementById('jobCategoryName').value = jobCategoryMap[category] || '';
|
||||
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 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('job_hasAttBonus')?.checked || false;
|
||||
data.hasHousingAllowance = document.getElementById('job_hasHouseAllow')?.checked || false;
|
||||
return data;
|
||||
}
|
||||
|
||||
export async function saveJobToPositionList() {
|
||||
const form = document.getElementById('jobForm');
|
||||
if (!form.checkValidity()) {
|
||||
form.reportValidity();
|
||||
return;
|
||||
}
|
||||
|
||||
const formData = getJobFormData();
|
||||
|
||||
if (!formData.jobCode) {
|
||||
alert('請輸入職務代碼');
|
||||
return;
|
||||
}
|
||||
if (!formData.jobName) {
|
||||
alert('請輸入職務名稱');
|
||||
return;
|
||||
}
|
||||
|
||||
const positionData = {
|
||||
basicInfo: {
|
||||
positionCode: formData.jobCode,
|
||||
positionName: formData.jobName,
|
||||
positionCategory: formData.jobCategoryCode || '',
|
||||
effectiveDate: formData.jobEffectiveDate || new Date().toISOString().split('T')[0],
|
||||
headcount: formData.jobHeadcount || 1,
|
||||
positionLevel: formData.jobLevel || '',
|
||||
positionRemark: formData.jobRemark || ''
|
||||
},
|
||||
recruitInfo: {}
|
||||
};
|
||||
|
||||
try {
|
||||
const response = await fetch(API_BASE_URL + '/positions', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(positionData)
|
||||
});
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
if (result.success) {
|
||||
showToast(result.message || '職務已成功儲存至崗位清單!');
|
||||
setTimeout(() => {
|
||||
switchModule('positionlist');
|
||||
}, 1500);
|
||||
} else {
|
||||
alert('儲存失敗: ' + (result.error || '未知錯誤'));
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('儲存錯誤:', error);
|
||||
alert('儲存失敗: ' + error.message);
|
||||
}
|
||||
}
|
||||
|
||||
export function saveJobAndExit() {
|
||||
const form = document.getElementById('jobForm');
|
||||
if (!form.checkValidity()) { form.reportValidity(); return; }
|
||||
console.log('Save Job:', getJobFormData());
|
||||
showToast('職務資料已保存!');
|
||||
}
|
||||
|
||||
export function saveJobAndNew() {
|
||||
const form = document.getElementById('jobForm');
|
||||
if (!form.checkValidity()) { form.reportValidity(); return; }
|
||||
console.log('Save Job:', getJobFormData());
|
||||
showToast('職務資料已保存,請繼續新增!');
|
||||
form.reset();
|
||||
document.getElementById('attendanceLabel').textContent = '否';
|
||||
document.getElementById('housingLabel').textContent = '否';
|
||||
updatePreview();
|
||||
}
|
||||
|
||||
export function cancelJobForm() {
|
||||
if (confirm('確定要取消嗎?未保存的資料將會遺失。')) {
|
||||
document.getElementById('jobForm').reset();
|
||||
document.getElementById('attendanceLabel').textContent = '否';
|
||||
document.getElementById('housingLabel').textContent = '否';
|
||||
updatePreview();
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== 崗位描述表單 ====================
|
||||
|
||||
export function getJobDescFormData() {
|
||||
const form = document.getElementById('jobDescForm');
|
||||
const formData = new FormData(form);
|
||||
const data = { basicInfo: {}, positionInfo: {}, responsibilities: {}, requirements: {} };
|
||||
|
||||
['empNo', 'empName', 'positionCode', 'versionDate'].forEach(field => {
|
||||
const el = document.getElementById('jd_' + field);
|
||||
if (el && el.value) data.basicInfo[field] = el.value;
|
||||
});
|
||||
|
||||
['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;
|
||||
});
|
||||
|
||||
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;
|
||||
|
||||
['education', 'basicSkills', 'professionalKnowledge', 'workExperienceReq', 'otherRequirements'].forEach(field => {
|
||||
const el = document.getElementById('jd_' + field);
|
||||
if (el && el.value) data.requirements[field] = el.value;
|
||||
});
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
export async function saveJobDescAndExit() {
|
||||
const formData = getJobDescFormData();
|
||||
console.log('Save JobDesc:', formData);
|
||||
|
||||
if (!formData.basicInfo.positionCode) {
|
||||
alert('請輸入崗位代碼');
|
||||
return;
|
||||
}
|
||||
|
||||
const requestData = {
|
||||
positionCode: formData.basicInfo.positionCode,
|
||||
positionName: formData.positionInfo.positionName || '',
|
||||
effectiveDate: formData.positionInfo.positionEffectiveDate || new Date().toISOString().split('T')[0],
|
||||
jobDuties: formData.responsibilities.mainResponsibilities || '',
|
||||
requiredSkills: formData.requirements.basicSkills || '',
|
||||
workEnvironment: formData.positionInfo.workLocation || ''
|
||||
};
|
||||
|
||||
try {
|
||||
const response = await fetch(API_BASE_URL + '/position-descriptions', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(requestData)
|
||||
});
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
if (result.success) {
|
||||
showToast(result.message || '崗位描述已保存!');
|
||||
setTimeout(() => {
|
||||
switchModule('positionlist');
|
||||
}, 1000);
|
||||
} else {
|
||||
alert('保存失敗: ' + (result.error || '未知錯誤'));
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('保存錯誤:', error);
|
||||
alert('保存失敗: ' + error.message);
|
||||
}
|
||||
}
|
||||
|
||||
export async function saveJobDescAndNew() {
|
||||
const formData = getJobDescFormData();
|
||||
console.log('Save JobDesc:', formData);
|
||||
|
||||
if (!formData.basicInfo.positionCode) {
|
||||
alert('請輸入崗位代碼');
|
||||
return;
|
||||
}
|
||||
|
||||
const requestData = {
|
||||
positionCode: formData.basicInfo.positionCode,
|
||||
positionName: formData.positionInfo.positionName || '',
|
||||
effectiveDate: formData.positionInfo.positionEffectiveDate || new Date().toISOString().split('T')[0],
|
||||
jobDuties: formData.responsibilities.mainResponsibilities || '',
|
||||
requiredSkills: formData.requirements.basicSkills || '',
|
||||
workEnvironment: formData.positionInfo.workLocation || ''
|
||||
};
|
||||
|
||||
try {
|
||||
const response = await fetch(API_BASE_URL + '/position-descriptions', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(requestData)
|
||||
});
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
if (result.success) {
|
||||
showToast(result.message || '崗位描述已保存,請繼續新增!');
|
||||
document.getElementById('jobDescForm').reset();
|
||||
document.getElementById('jd_mainResponsibilities').value = '1、\n2、\n3、\n4、';
|
||||
updatePreview();
|
||||
} else {
|
||||
alert('保存失敗: ' + (result.error || '未知錯誤'));
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('保存錯誤:', error);
|
||||
alert('保存失敗: ' + error.message);
|
||||
}
|
||||
}
|
||||
|
||||
export async function saveJobDescToPositionList() {
|
||||
const formData = getJobDescFormData();
|
||||
|
||||
if (!formData.basicInfo.positionCode) {
|
||||
alert('請輸入崗位代碼');
|
||||
return;
|
||||
}
|
||||
|
||||
const positionData = {
|
||||
basicInfo: {
|
||||
positionCode: formData.basicInfo.positionCode,
|
||||
positionName: formData.positionInfo.positionName || '',
|
||||
effectiveDate: formData.positionInfo.positionEffectiveDate || new Date().toISOString().split('T')[0],
|
||||
positionDesc: formData.responsibilities.mainResponsibilities || '',
|
||||
positionRemark: formData.responsibilities.positionPurpose || ''
|
||||
},
|
||||
recruitInfo: {
|
||||
minEducation: formData.requirements.education || '',
|
||||
skillReq: formData.requirements.basicSkills || '',
|
||||
workExperience: formData.requirements.workExperienceReq || '',
|
||||
otherReq: formData.requirements.otherRequirements || ''
|
||||
}
|
||||
};
|
||||
|
||||
try {
|
||||
const response = await fetch(API_BASE_URL + '/positions', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(positionData)
|
||||
});
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
if (result.success) {
|
||||
showToast(result.message || '崗位描述已成功儲存至崗位清單!');
|
||||
setTimeout(() => {
|
||||
switchModule('positionlist');
|
||||
}, 1500);
|
||||
} else {
|
||||
alert('儲存失敗: ' + (result.error || '未知錯誤'));
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('儲存錯誤:', error);
|
||||
alert('儲存失敗: ' + error.message);
|
||||
}
|
||||
}
|
||||
|
||||
export function cancelJobDescForm() {
|
||||
if (confirm('確定要取消嗎?未保存的資料將會遺失。')) {
|
||||
document.getElementById('jobDescForm').reset();
|
||||
document.getElementById('jd_mainResponsibilities').value = '1、\n2、\n3、\n4、';
|
||||
updatePreview();
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== 預覽更新 ====================
|
||||
|
||||
export function updatePreview() {
|
||||
const activeModuleBtn = document.querySelector('.module-btn.active');
|
||||
if (!activeModuleBtn) return;
|
||||
|
||||
const activeModule = activeModuleBtn.dataset.module;
|
||||
let data;
|
||||
if (activeModule === 'position') {
|
||||
data = { module: '崗位基礎資料', ...getPositionFormData() };
|
||||
} else if (activeModule === 'job') {
|
||||
data = { module: '職務基礎資料', ...getJobFormData() };
|
||||
} else {
|
||||
data = { module: '崗位描述', ...getJobDescFormData() };
|
||||
}
|
||||
const previewEl = document.getElementById('jsonPreview');
|
||||
if (previewEl) {
|
||||
previewEl.textContent = JSON.stringify(data, null, 2);
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== Toast 訊息 ====================
|
||||
|
||||
export function showToast(message) {
|
||||
const toast = document.getElementById('toast');
|
||||
const toastMessage = document.getElementById('toastMessage');
|
||||
if (!toast || !toastMessage) {
|
||||
console.warn('Toast elements not found, creating dynamic toast');
|
||||
const existingToast = document.querySelector('.toast.dynamic-toast');
|
||||
if (existingToast) existingToast.remove();
|
||||
|
||||
const dynamicToast = document.createElement('div');
|
||||
dynamicToast.className = 'toast dynamic-toast show';
|
||||
dynamicToast.innerHTML = `
|
||||
<svg viewBox="0 0 24 24"><path d="M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z"/></svg>
|
||||
<span>${message}</span>
|
||||
`;
|
||||
document.body.appendChild(dynamicToast);
|
||||
setTimeout(() => {
|
||||
dynamicToast.classList.remove('show');
|
||||
setTimeout(() => dynamicToast.remove(), 300);
|
||||
}, 3000);
|
||||
return;
|
||||
}
|
||||
toastMessage.textContent = message;
|
||||
toast.classList.add('show');
|
||||
setTimeout(() => toast.classList.remove('show'), 3000);
|
||||
}
|
||||
|
||||
// ==================== 模組切換 ====================
|
||||
|
||||
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') {
|
||||
loadPositionList();
|
||||
}
|
||||
|
||||
updatePreview();
|
||||
}
|
||||
|
||||
// ==================== 崗位清單功能 ====================
|
||||
|
||||
export async function loadPositionList() {
|
||||
try {
|
||||
showToast('正在載入崗位清單...');
|
||||
|
||||
const response = await fetch(API_BASE_URL + '/position-list');
|
||||
const result = await response.json();
|
||||
|
||||
if (result.success) {
|
||||
positionListData = result.data;
|
||||
renderPositionList();
|
||||
showToast('已載入 ' + positionListData.length + ' 筆崗位資料');
|
||||
} else {
|
||||
alert('載入失敗: ' + (result.error || '未知錯誤'));
|
||||
positionListData = [];
|
||||
renderPositionList();
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('載入錯誤:', error);
|
||||
alert('載入失敗: ' + error.message);
|
||||
positionListData = [];
|
||||
renderPositionList();
|
||||
}
|
||||
}
|
||||
|
||||
export function renderPositionList() {
|
||||
const tbody = document.getElementById('positionListBody');
|
||||
if (!tbody) return;
|
||||
|
||||
if (positionListData.length === 0) {
|
||||
tbody.innerHTML = '<tr><td colspan="8" style="padding: 40px; text-align: center; color: var(--text-secondary);">沒有資料,請先建立崗位或點擊「載入清單」</td></tr>';
|
||||
return;
|
||||
}
|
||||
|
||||
tbody.innerHTML = positionListData.map(item => `
|
||||
<tr style="border-bottom: 1px solid #eee;">
|
||||
<td style="padding: 12px;">${sanitizeHTML(item.positionCode)}</td>
|
||||
<td style="padding: 12px;">${sanitizeHTML(item.positionName)}</td>
|
||||
<td style="padding: 12px;">${sanitizeHTML(item.positionCategory || '')}</td>
|
||||
<td style="padding: 12px;">${sanitizeHTML(item.positionNature || '')}</td>
|
||||
<td style="padding: 12px;">${sanitizeHTML(String(item.headcount || ''))}</td>
|
||||
<td style="padding: 12px;">${sanitizeHTML(item.positionLevel || '')}</td>
|
||||
<td style="padding: 12px;">${sanitizeHTML(item.effectiveDate || '')}</td>
|
||||
<td style="padding: 12px; text-align: center;">
|
||||
<button class="btn btn-secondary" style="padding: 4px 8px; font-size: 12px;" onclick="viewPositionDesc('${sanitizeHTML(item.positionCode)}')">檢視</button>
|
||||
</td>
|
||||
</tr>
|
||||
`).join('');
|
||||
}
|
||||
|
||||
export function sortPositionList(column) {
|
||||
if (currentSortColumn === column) {
|
||||
currentSortDirection = currentSortDirection === 'asc' ? 'desc' : 'asc';
|
||||
} else {
|
||||
currentSortColumn = column;
|
||||
currentSortDirection = 'asc';
|
||||
}
|
||||
|
||||
positionListData.sort((a, b) => {
|
||||
let valA = a[column];
|
||||
let valB = b[column];
|
||||
|
||||
if (typeof valA === 'string') {
|
||||
valA = valA.toLowerCase();
|
||||
valB = valB.toLowerCase();
|
||||
}
|
||||
|
||||
if (valA < valB) return currentSortDirection === 'asc' ? -1 : 1;
|
||||
if (valA > valB) return currentSortDirection === 'asc' ? 1 : -1;
|
||||
return 0;
|
||||
});
|
||||
|
||||
document.querySelectorAll('.sort-icon').forEach(icon => icon.textContent = '');
|
||||
const currentHeader = document.querySelector(`th[data-sort="${column}"] .sort-icon`);
|
||||
if (currentHeader) {
|
||||
currentHeader.textContent = currentSortDirection === 'asc' ? ' ^' : ' v';
|
||||
}
|
||||
|
||||
renderPositionList();
|
||||
}
|
||||
|
||||
// ==================== 工具函數 ====================
|
||||
|
||||
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.showToast = showToast;
|
||||
window.updatePreview = updatePreview;
|
||||
window.switchModule = switchModule;
|
||||
window.updateCategoryName = updateCategoryName;
|
||||
window.updateNatureName = updateNatureName;
|
||||
window.updateJobCategoryName = updateJobCategoryName;
|
||||
}
|
||||
Reference in New Issue
Block a user