- 新增 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>
282 lines
13 KiB
Python
282 lines
13 KiB
Python
"""
|
||
為每個模組加入 CSV 匯入匯出按鈕
|
||
"""
|
||
import sys
|
||
import codecs
|
||
|
||
# 設置 UTF-8 編碼(Windows 編碼修正)
|
||
if sys.platform == 'win32':
|
||
sys.stdout = codecs.getwriter('utf-8')(sys.stdout.buffer, 'strict')
|
||
sys.stderr = codecs.getwriter('utf-8')(sys.stderr.buffer, 'strict')
|
||
|
||
with open('index.html', 'r', encoding='utf-8') as f:
|
||
content = f.read()
|
||
|
||
# 備份
|
||
with open('index.html.backup_csv', 'w', encoding='utf-8') as f:
|
||
f.write(content)
|
||
|
||
# 1. 在 <head> 中加入 csv_utils.js
|
||
if '<script src="csv_utils.js"></script>' not in content:
|
||
head_insertion = ' <script src="csv_utils.js"></script>\n</head>'
|
||
content = content.replace('</head>', head_insertion)
|
||
print("[OK] Added csv_utils.js reference")
|
||
|
||
# 2. 為崗位資料模組加入 CSV 按鈕
|
||
# 在 action-buttons 區域前加入 CSV 按鈕
|
||
position_csv_buttons = ''' <!-- CSV 匯入匯出按鈕 -->
|
||
<div class="csv-buttons" style="margin-bottom: 15px; display: flex; gap: 10px;">
|
||
<button type="button" class="btn btn-secondary" onclick="exportPositionsCSV()">
|
||
<svg viewBox="0 0 24 24" style="width: 18px; height: 18px; fill: currentColor;"><path d="M14 2H6c-1.1 0-2 .9-2 2v16c0 1.1.9 2 2 2h12c1.1 0 2-.9 2-2V8l-6-6zm4 18H6V4h7v5h5v11z"/></svg>
|
||
匯出 CSV
|
||
</button>
|
||
<button type="button" class="btn btn-secondary" onclick="importPositionsCSV()">
|
||
<svg viewBox="0 0 24 24" style="width: 18px; height: 18px; fill: currentColor;"><path d="M9 16h6v-6h4l-7-7-7 7h4zm-4 2h14v2H5z"/></svg>
|
||
匯入 CSV
|
||
</button>
|
||
<input type="file" id="positionCSVInput" accept=".csv" style="display: none;" onchange="handlePositionCSVImport(event)">
|
||
</div>
|
||
'''
|
||
|
||
# 找到崗位資料模組的 action-buttons
|
||
old_position_section = ''' <div class="action-buttons">
|
||
<button type="button" class="btn btn-primary" onclick="savePositionAndExit()">'''
|
||
|
||
new_position_section = position_csv_buttons + ''' <div class="action-buttons">
|
||
<button type="button" class="btn btn-primary" onclick="savePositionAndExit()">'''
|
||
|
||
if old_position_section in content and position_csv_buttons not in content:
|
||
content = content.replace(old_position_section, new_position_section)
|
||
print("[OK] Added CSV buttons to Position module")
|
||
|
||
# 3. 為職務資料模組加入 CSV 按鈕
|
||
job_csv_buttons = ''' <!-- CSV 匯入匯出按鈕 -->
|
||
<div class="csv-buttons" style="margin-bottom: 15px; display: flex; gap: 10px;">
|
||
<button type="button" class="btn btn-secondary" onclick="exportJobsCSV()">
|
||
<svg viewBox="0 0 24 24" style="width: 18px; height: 18px; fill: currentColor;"><path d="M14 2H6c-1.1 0-2 .9-2 2v16c0 1.1.9 2 2 2h12c1.1 0 2-.9 2-2V8l-6-6zm4 18H6V4h7v5h5v11z"/></svg>
|
||
匯出 CSV
|
||
</button>
|
||
<button type="button" class="btn btn-secondary" onclick="importJobsCSV()">
|
||
<svg viewBox="0 0 24 24" style="width: 18px; height: 18px; fill: currentColor;"><path d="M9 16h6v-6h4l-7-7-7 7h4zm-4 2h14v2H5z"/></svg>
|
||
匯入 CSV
|
||
</button>
|
||
<input type="file" id="jobCSVInput" accept=".csv" style="display: none;" onchange="handleJobCSVImport(event)">
|
||
</div>
|
||
'''
|
||
|
||
# 找到職務資料模組的 action-buttons(注意有不同的函數名)
|
||
old_job_section = ''' <div class="action-buttons">
|
||
<button type="button" class="btn btn-primary" onclick="saveJobAndExit()">'''
|
||
|
||
new_job_section = job_csv_buttons + ''' <div class="action-buttons">
|
||
<button type="button" class="btn btn-primary" onclick="saveJobAndExit()">'''
|
||
|
||
if old_job_section in content and job_csv_buttons not in content:
|
||
content = content.replace(old_job_section, new_job_section)
|
||
print("[OK] Added CSV buttons to Job module")
|
||
|
||
# 4. 為崗位描述模組加入 CSV 按鈕
|
||
desc_csv_buttons = ''' <!-- CSV 匯入匯出按鈕 -->
|
||
<div class="csv-buttons" style="margin-bottom: 15px; display: flex; gap: 10px;">
|
||
<button type="button" class="btn btn-secondary" onclick="exportDescriptionsCSV()">
|
||
<svg viewBox="0 0 24 24" style="width: 18px; height: 18px; fill: currentColor;"><path d="M14 2H6c-1.1 0-2 .9-2 2v16c0 1.1.9 2 2 2h12c1.1 0 2-.9 2-2V8l-6-6zm4 18H6V4h7v5h5v11z"/></svg>
|
||
匯出 CSV
|
||
</button>
|
||
<button type="button" class="btn btn-secondary" onclick="importDescriptionsCSV()">
|
||
<svg viewBox="0 0 24 24" style="width: 18px; height: 18px; fill: currentColor;"><path d="M9 16h6v-6h4l-7-7-7 7h4zm-4 2h14v2H5z"/></svg>
|
||
匯入 CSV
|
||
</button>
|
||
<input type="file" id="descCSVInput" accept=".csv" style="display: none;" onchange="handleDescCSVImport(event)">
|
||
</div>
|
||
'''
|
||
|
||
# 找到崗位描述模組的 action-buttons
|
||
old_desc_section = ''' <div class="action-buttons">
|
||
<button type="button" class="btn btn-primary" onclick="saveDescAndExit()">'''
|
||
|
||
new_desc_section = desc_csv_buttons + ''' <div class="action-buttons">
|
||
<button type="button" class="btn btn-primary" onclick="saveDescAndExit()">'''
|
||
|
||
if old_desc_section in content and desc_csv_buttons not in content:
|
||
content = content.replace(old_desc_section, new_desc_section)
|
||
print("[OK] Added CSV buttons to Description module")
|
||
|
||
# 5. 加入 CSV 處理函數
|
||
csv_functions = '''
|
||
// ==================== CSV 匯入匯出函數 ====================
|
||
|
||
// 崗位資料 CSV 匯出
|
||
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);
|
||
showToast('崗位資料已匯出!');
|
||
}
|
||
|
||
// 崗位資料 CSV 匯入觸發
|
||
function importPositionsCSV() {
|
||
document.getElementById('positionCSVInput').click();
|
||
}
|
||
|
||
// 處理崗位 CSV 匯入
|
||
function handlePositionCSVImport(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];
|
||
}
|
||
});
|
||
showToast(`已匯入 ${data.length} 筆崗位資料(顯示第一筆)`);
|
||
}
|
||
});
|
||
// 重置 input
|
||
event.target.value = '';
|
||
}
|
||
|
||
// 職務資料 CSV 匯出
|
||
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('hasAttendanceBonus')?.checked,
|
||
hasHousingAllowance: document.getElementById('hasHousingAllowance')?.checked
|
||
}];
|
||
|
||
const headers = ['jobCategoryCode', 'jobCategoryName', 'jobCode', 'jobName', 'jobNameEn',
|
||
'jobEffectiveDate', 'jobHeadcount', 'jobSortOrder', 'jobRemark', 'jobLevel',
|
||
'hasAttendanceBonus', 'hasHousingAllowance'];
|
||
|
||
CSVUtils.exportToCSV(data, 'jobs.csv', headers);
|
||
showToast('職務資料已匯出!');
|
||
}
|
||
|
||
// 職務資料 CSV 匯入觸發
|
||
function importJobsCSV() {
|
||
document.getElementById('jobCSVInput').click();
|
||
}
|
||
|
||
// 處理職務 CSV 匯入
|
||
function handleJobCSVImport(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) {
|
||
if (element.type === 'checkbox') {
|
||
element.checked = firstRow[key] === 'true';
|
||
} else {
|
||
element.value = firstRow[key];
|
||
}
|
||
}
|
||
});
|
||
showToast(`已匯入 ${data.length} 筆職務資料(顯示第一筆)`);
|
||
}
|
||
});
|
||
event.target.value = '';
|
||
}
|
||
|
||
// 崗位描述 CSV 匯出
|
||
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);
|
||
showToast('崗位描述已匯出!');
|
||
}
|
||
|
||
// 崗位描述 CSV 匯入觸發
|
||
function importDescriptionsCSV() {
|
||
document.getElementById('descCSVInput').click();
|
||
}
|
||
|
||
// 處理崗位描述 CSV 匯入
|
||
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];
|
||
}
|
||
});
|
||
showToast(`已匯入 ${data.length} 筆崗位描述資料(顯示第一筆)`);
|
||
}
|
||
});
|
||
event.target.value = '';
|
||
}
|
||
'''
|
||
|
||
# 在 </script> 前加入函數
|
||
if 'function exportPositionsCSV()' not in content:
|
||
content = content.replace(' </script>\n</body>', csv_functions + '\n </script>\n</body>')
|
||
print("[OK] Added CSV handler functions")
|
||
|
||
# 寫回
|
||
with open('index.html', 'w', encoding='utf-8') as f:
|
||
f.write(content)
|
||
|
||
print("\n" + "="*60)
|
||
print("[OK] CSV Integration Complete!")
|
||
print("="*60)
|
||
print("\nCompleted tasks:")
|
||
print("1. Added csv_utils.js reference in <head>")
|
||
print("2. Added CSV buttons to Position module")
|
||
print("3. Added CSV buttons to Job module")
|
||
print("4. Added CSV buttons to Description module")
|
||
print("5. Added all CSV handler functions")
|
||
print("\nPlease reload the page (Ctrl+F5) to test CSV features!")
|