/** * CSV 匯入匯出模組 * 處理各表單的 CSV 資料交換 */ const API_BASE_URL = '/api'; // ==================== CSV 工具函數 ==================== export const CSVUtils = { /** * 匯出資料到 CSV */ exportToCSV(data, filename, headers) { if (!data || data.length === 0) { console.warn('No data to export'); return; } const csvHeaders = headers || Object.keys(data[0]); const csvRows = data.map(row => { return csvHeaders.map(header => { let value = row[header] !== undefined ? row[header] : ''; // 處理包含逗號或換行的值 if (typeof value === 'string' && (value.includes(',') || value.includes('\n') || value.includes('"'))) { value = '"' + value.replace(/"/g, '""') + '"'; } return value; }).join(','); }); const csvContent = '\uFEFF' + [csvHeaders.join(','), ...csvRows].join('\n'); // BOM for UTF-8 const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' }); const link = document.createElement('a'); link.href = URL.createObjectURL(blob); link.download = filename; link.click(); }, /** * 從 CSV 匯入資料 */ importFromCSV(file, callback) { const reader = new FileReader(); reader.onload = (e) => { const text = e.target.result; const lines = text.split('\n').filter(line => line.trim()); if (lines.length < 2) { callback([]); return; } const headers = this.parseCSVLine(lines[0]); const data = []; for (let i = 1; i < lines.length; i++) { const values = this.parseCSVLine(lines[i]); const row = {}; headers.forEach((header, index) => { row[header.trim()] = values[index] ? values[index].trim() : ''; }); data.push(row); } callback(data); }; reader.readAsText(file, 'UTF-8'); }, /** * 解析 CSV 行(處理引號內的逗號) */ parseCSVLine(line) { const result = []; let current = ''; let inQuotes = false; for (let i = 0; i < line.length; i++) { const char = line[i]; if (char === '"') { if (inQuotes && line[i + 1] === '"') { current += '"'; i++; } else { inQuotes = !inQuotes; } } else if (char === ',' && !inQuotes) { result.push(current); current = ''; } else { current += char; } } result.push(current); return result; } }; // ==================== 崗位資料 CSV ==================== export function downloadPositionCSVTemplate() { window.location.href = API_BASE_URL + '/positions/csv-template'; if (typeof showToast === 'function') showToast('正在下載崗位資料範本...'); } export function exportPositionsCSV() { const data = [{ positionCode: getFieldValue('positionCode'), positionName: getFieldValue('positionName'), positionCategory: getFieldValue('positionCategory'), positionNature: getFieldValue('positionNature'), headcount: getFieldValue('headcount'), positionLevel: getFieldValue('positionLevel'), effectiveDate: getFieldValue('effectiveDate'), positionDesc: getFieldValue('positionDesc'), positionRemark: getFieldValue('positionRemark'), minEducation: getFieldValue('minEducation'), salaryRange: getFieldValue('salaryRange'), workExperience: getFieldValue('workExperience'), minAge: getFieldValue('minAge'), maxAge: getFieldValue('maxAge') }]; const headers = ['positionCode', 'positionName', 'positionCategory', 'positionNature', 'headcount', 'positionLevel', 'effectiveDate', 'positionDesc', 'positionRemark', 'minEducation', 'salaryRange', 'workExperience', 'minAge', 'maxAge']; CSVUtils.exportToCSV(data, 'positions.csv', headers); if (typeof showToast === 'function') showToast('崗位資料已匯出!'); } export function importPositionsCSV() { document.getElementById('positionCSVInput').click(); } export function handlePositionCSVImport(event) { const file = event.target.files[0]; if (!file) return; const formData = new FormData(); formData.append('file', file); if (typeof showToast === 'function') showToast('正在匯入崗位資料...'); fetch(API_BASE_URL + '/positions/import-csv', { method: 'POST', body: formData }) .then(response => response.json()) .then(data => { if (data.success) { let message = data.message; if (data.errors && data.errors.length > 0) { message += '\n\n錯誤詳情:\n' + data.errors.join('\n'); } alert(message); } else { alert('匯入失敗: ' + (data.error || '未知錯誤')); } }) .catch(error => { console.error('匯入錯誤:', error); alert('匯入失敗: ' + error.message); }) .finally(() => { event.target.value = ''; }); } // ==================== 職務資料 CSV ==================== export function downloadJobCSVTemplate() { window.location.href = API_BASE_URL + '/jobs/csv-template'; if (typeof showToast === 'function') showToast('正在下載職務資料範本...'); } export function exportJobsCSV() { const data = [{ jobCategoryCode: getFieldValue('jobCategoryCode'), jobCategoryName: getFieldValue('jobCategoryName'), jobCode: getFieldValue('jobCode'), jobName: getFieldValue('jobName'), jobNameEn: getFieldValue('jobNameEn'), jobEffectiveDate: getFieldValue('jobEffectiveDate'), jobHeadcount: getFieldValue('jobHeadcount'), jobSortOrder: getFieldValue('jobSortOrder'), jobRemark: getFieldValue('jobRemark'), jobLevel: getFieldValue('jobLevel'), hasAttendanceBonus: document.getElementById('job_hasAttBonus')?.checked, hasHousingAllowance: document.getElementById('job_hasHouseAllow')?.checked }]; const headers = ['jobCategoryCode', 'jobCategoryName', 'jobCode', 'jobName', 'jobNameEn', 'jobEffectiveDate', 'jobHeadcount', 'jobSortOrder', 'jobRemark', 'jobLevel', 'hasAttendanceBonus', 'hasHousingAllowance']; CSVUtils.exportToCSV(data, 'jobs.csv', headers); if (typeof showToast === 'function') showToast('職務資料已匯出!'); } export function importJobsCSV() { document.getElementById('jobCSVInput').click(); } export function handleJobCSVImport(event) { const file = event.target.files[0]; if (!file) return; const formData = new FormData(); formData.append('file', file); if (typeof showToast === 'function') showToast('正在匯入職務資料...'); fetch(API_BASE_URL + '/jobs/import-csv', { method: 'POST', body: formData }) .then(response => response.json()) .then(data => { if (data.success) { let message = data.message; if (data.errors && data.errors.length > 0) { message += '\n\n錯誤詳情:\n' + data.errors.join('\n'); } alert(message); } else { alert('匯入失敗: ' + (data.error || '未知錯誤')); } }) .catch(error => { console.error('匯入錯誤:', error); alert('匯入失敗: ' + error.message); }) .finally(() => { event.target.value = ''; }); } // ==================== 崗位描述 CSV ==================== export function exportDescriptionsCSV() { const data = [{ descPositionCode: getFieldValue('descPositionCode'), descPositionName: getFieldValue('descPositionName'), descEffectiveDate: getFieldValue('descEffectiveDate'), jobDuties: getFieldValue('jobDuties'), requiredSkills: getFieldValue('requiredSkills'), workEnvironment: getFieldValue('workEnvironment'), careerPath: getFieldValue('careerPath'), descRemark: getFieldValue('descRemark') }]; const headers = ['descPositionCode', 'descPositionName', 'descEffectiveDate', 'jobDuties', 'requiredSkills', 'workEnvironment', 'careerPath', 'descRemark']; CSVUtils.exportToCSV(data, 'job_descriptions.csv', headers); if (typeof showToast === 'function') showToast('崗位描述已匯出!'); } export function importDescriptionsCSV() { document.getElementById('descCSVInput').click(); } export function handleDescCSVImport(event) { const file = event.target.files[0]; if (!file) return; CSVUtils.importFromCSV(file, (data) => { if (data && data.length > 0) { const firstRow = data[0]; Object.keys(firstRow).forEach(key => { const element = document.getElementById(key); if (element) { element.value = firstRow[key]; } }); if (typeof showToast === 'function') { showToast(`已匯入 ${data.length} 筆崗位描述資料(顯示第一筆)`); } } }); event.target.value = ''; } // ==================== 崗位清單 CSV ==================== export function exportPositionListCSV(positionListData) { if (!positionListData || positionListData.length === 0) { if (typeof showToast === 'function') showToast('請先載入清單資料'); return; } const headers = ['positionCode', 'positionName', 'businessUnit', 'department', 'positionCategory', 'headcount', 'effectiveDate']; CSVUtils.exportToCSV(positionListData, 'position_list.csv', headers); if (typeof showToast === 'function') showToast('崗位清單已匯出!'); } // ==================== 部門職責 CSV ==================== export function importDeptFunctionCSV() { document.getElementById('deptFunctionCsvInput').click(); } export function handleDeptFunctionCSVImport(event, callback) { const file = event.target.files[0]; if (!file) return; CSVUtils.importFromCSV(file, (data) => { if (data && data.length > 0) { const row = data[0]; Object.keys(row).forEach(key => { const el = document.getElementById(key); if (el) el.value = row[key]; }); if (typeof showToast === 'function') showToast('已匯入 CSV 資料!'); if (callback) callback(data); } }); event.target.value = ''; } export function exportDeptFunctionCSV(formData) { const headers = Object.keys(formData); CSVUtils.exportToCSV([formData], 'dept_function.csv', headers); if (typeof showToast === 'function') showToast('部門職責資料已匯出!'); } // ==================== 工具函數 ==================== function getFieldValue(elementId) { const el = document.getElementById(elementId); return el ? el.value.trim() : ''; } // 暴露到全域 if (typeof window !== 'undefined') { window.CSVUtils = CSVUtils; }