Files
hr-position-system/csv_utils.js
DonaldFang 方士碩 d17af39bf4 feat: 新增多項功能 v2.1
- 新增 CSV 匯入匯出功能(所有頁籤)
- 新增崗位清單頁籤(含欄位排序)
- 新增管理者頁面(使用者 CRUD)
- 新增事業體選項(SBU/MBU/HQBU/ITBU/HRBU/ACCBU)
- 新增組織單位欄位(處級/部級/課級)
- 崗位描述/備注改為條列式說明
- 新增 README.md 文件
- 新增開發指令記錄檔

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-04 10:06:50 +08:00

247 lines
6.8 KiB
JavaScript

/**
* CSV 匯入匯出工具
* 提供 CSV 文件的匯入和匯出功能
*/
const CSVUtils = {
/**
* 將數據匯出為 CSV 文件
* @param {Array} data - 數據陣列
* @param {String} filename - 文件名稱
* @param {Array} headers - CSV 標題行(可選)
*/
exportToCSV(data, filename, headers = null) {
if (!data || data.length === 0) {
alert('沒有資料可以匯出');
return;
}
// 如果沒有提供標題,從第一筆資料取得所有鍵
if (!headers) {
headers = Object.keys(data[0]);
}
// 構建 CSV 內容
let csvContent = '\uFEFF'; // BOM for UTF-8
// 添加標題行
csvContent += headers.join(',') + '\n';
// 添加數據行
data.forEach(row => {
const values = headers.map(header => {
let value = this.getNestedValue(row, header);
// 處理空值
if (value === null || value === undefined) {
return '';
}
// 轉換為字符串
value = String(value);
// 如果包含逗號、引號或換行符,需要用引號包圍
if (value.includes(',') || value.includes('"') || value.includes('\n')) {
value = '"' + value.replace(/"/g, '""') + '"';
}
return value;
});
csvContent += values.join(',') + '\n';
});
// 創建 Blob 並下載
const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' });
const link = document.createElement('a');
if (link.download !== undefined) {
const url = URL.createObjectURL(blob);
link.setAttribute('href', url);
link.setAttribute('download', filename);
link.style.visibility = 'hidden';
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
}
},
/**
* 從 CSV 文件匯入數據
* @param {File} file - CSV 文件
* @param {Function} callback - 回調函數,接收解析後的數據
*/
importFromCSV(file, callback) {
if (!file) {
alert('請選擇文件');
return;
}
if (!file.name.endsWith('.csv')) {
alert('請選擇 CSV 文件');
return;
}
const reader = new FileReader();
reader.onload = (e) => {
try {
const text = e.target.result;
const data = this.parseCSV(text);
if (data && data.length > 0) {
callback(data);
} else {
alert('CSV 文件為空或格式錯誤');
}
} catch (error) {
console.error('CSV 解析錯誤:', error);
alert('CSV 文件解析失敗: ' + error.message);
}
};
reader.onerror = () => {
alert('文件讀取失敗');
};
reader.readAsText(file, 'UTF-8');
},
/**
* 解析 CSV 文本
* @param {String} text - CSV 文本內容
* @returns {Array} 解析後的數據陣列
*/
parseCSV(text) {
// 移除 BOM
if (text.charCodeAt(0) === 0xFEFF) {
text = text.substr(1);
}
const lines = text.split('\n').filter(line => line.trim());
if (lines.length === 0) {
return [];
}
// 第一行是標題
const headers = this.parseCSVLine(lines[0]);
const data = [];
// 解析數據行
for (let i = 1; i < lines.length; i++) {
const values = this.parseCSVLine(lines[i]);
if (values.length === headers.length) {
const row = {};
headers.forEach((header, index) => {
row[header] = values[index];
});
data.push(row);
}
}
return data;
},
/**
* 解析單行 CSV
* @param {String} line - CSV 行
* @returns {Array} 值陣列
*/
parseCSVLine(line) {
const values = [];
let current = '';
let inQuotes = false;
for (let i = 0; i < line.length; i++) {
const char = line[i];
const nextChar = line[i + 1];
if (char === '"') {
if (inQuotes && nextChar === '"') {
current += '"';
i++;
} else {
inQuotes = !inQuotes;
}
} else if (char === ',' && !inQuotes) {
values.push(current);
current = '';
} else {
current += char;
}
}
values.push(current);
return values;
},
/**
* 獲取嵌套對象的值
* @param {Object} obj - 對象
* @param {String} path - 路徑(支援 a.b.c 格式)
* @returns {*} 值
*/
getNestedValue(obj, path) {
return path.split('.').reduce((current, key) => {
return current ? current[key] : undefined;
}, obj);
},
/**
* 創建 CSV 匯入按鈕
* @param {Function} onImport - 匯入成功的回調函數
* @returns {HTMLElement} 按鈕元素
*/
createImportButton(onImport) {
const input = document.createElement('input');
input.type = 'file';
input.accept = '.csv';
input.style.display = 'none';
input.addEventListener('change', (e) => {
const file = e.target.files[0];
if (file) {
this.importFromCSV(file, onImport);
}
});
const button = document.createElement('button');
button.className = 'btn btn-secondary';
button.innerHTML = '📥 匯入 CSV';
button.onclick = () => input.click();
const container = document.createElement('div');
container.style.display = 'inline-block';
container.appendChild(input);
container.appendChild(button);
return container;
},
/**
* 創建 CSV 匯出按鈕
* @param {Function} getData - 獲取數據的函數
* @param {String} filename - 文件名稱
* @param {Array} headers - CSV 標題
* @returns {HTMLElement} 按鈕元素
*/
createExportButton(getData, filename, headers = null) {
const button = document.createElement('button');
button.className = 'btn btn-secondary';
button.innerHTML = '📤 匯出 CSV';
button.onclick = () => {
const data = getData();
this.exportToCSV(data, filename, headers);
};
return button;
}
};
// 導出為全局變量
if (typeof window !== 'undefined') {
window.CSVUtils = CSVUtils;
}