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>
This commit is contained in:
281
add_csv_buttons.py
Normal file
281
add_csv_buttons.py
Normal file
@@ -0,0 +1,281 @@
|
||||
"""
|
||||
為每個模組加入 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!")
|
||||
Reference in New Issue
Block a user