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:
2025-12-04 10:06:50 +08:00
parent 293d64bc65
commit d17af39bf4
8 changed files with 2302 additions and 4 deletions

View File

@@ -0,0 +1,499 @@
"""
新增崗位清單頁籤(含排序功能)和管理者頁面
"""
import sys
import codecs
# 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_list_admin', 'w', encoding='utf-8') as f:
f.write(content)
# 1. 在模組選擇區加入兩個新按鈕
new_module_buttons = ''' <button class="module-btn" data-module="jobdesc">
<svg viewBox="0 0 24 24"><path d="M14 2H6c-1.1 0-1.99.9-1.99 2L4 20c0 1.1.89 2 1.99 2H18c1.1 0 2-.9 2-2V8l-6-6zm2 16H8v-2h8v2zm0-4H8v-2h8v2zm-3-5V3.5L18.5 9H13z"/></svg>
崗位描述
</button>
<button class="module-btn" data-module="positionlist">
<svg viewBox="0 0 24 24"><path d="M3 13h2v-2H3v2zm0 4h2v-2H3v2zm0-8h2V7H3v2zm4 4h14v-2H7v2zm0 4h14v-2H7v2zM7 7v2h14V7H7z"/></svg>
崗位清單
</button>
<button class="module-btn" data-module="admin">
<svg viewBox="0 0 24 24"><path d="M19.14,12.94c0.04-0.31,0.06-0.63,0.06-0.94c0-0.32-0.02-0.64-0.07-0.94l2.03-1.58c0.18-0.14,0.23-0.41,0.12-0.61 l-1.92-3.32c-0.12-0.22-0.37-0.29-0.59-0.22l-2.39,0.96c-0.5-0.38-1.03-0.7-1.62-0.94L14.4,2.81c-0.04-0.24-0.24-0.41-0.48-0.41 h-3.84c-0.24,0-0.43,0.17-0.47,0.41L9.25,5.35C8.66,5.59,8.12,5.92,7.63,6.29L5.24,5.33c-0.22-0.08-0.47,0-0.59,0.22L2.74,8.87 C2.62,9.08,2.66,9.34,2.86,9.48l2.03,1.58C4.84,11.36,4.8,11.69,4.8,12s0.02,0.64,0.07,0.94l-2.03,1.58 c-0.18,0.14-0.23,0.41-0.12,0.61l1.92,3.32c0.12,0.22,0.37,0.29,0.59,0.22l2.39-0.96c0.5,0.38,1.03,0.7,1.62,0.94l0.36,2.54 c0.05,0.24,0.24,0.41,0.48,0.41h3.84c0.24,0,0.44-0.17,0.47-0.41l0.36-2.54c0.59-0.24,1.13-0.56,1.62-0.94l2.39,0.96 c0.22,0.08,0.47,0,0.59-0.22l1.92-3.32c0.12-0.22,0.07-0.47-0.12-0.61L19.14,12.94z M12,15.6c-1.98,0-3.6-1.62-3.6-3.6 s1.62-3.6,3.6-3.6s3.6,1.62,3.6,3.6S13.98,15.6,12,15.6z"/></svg>
管理者頁面
</button>
</div>'''
old_module_end = ''' <button class="module-btn" data-module="jobdesc">
<svg viewBox="0 0 24 24"><path d="M14 2H6c-1.1 0-1.99.9-1.99 2L4 20c0 1.1.89 2 1.99 2H18c1.1 0 2-.9 2-2V8l-6-6zm2 16H8v-2h8v2zm0-4H8v-2h8v2zm-3-5V3.5L18.5 9H13z"/></svg>
崗位描述
</button>
</div>'''
if old_module_end in content and 'data-module="positionlist"' not in content:
content = content.replace(old_module_end, new_module_buttons)
print("[OK] Added Position List and Admin module buttons")
# 2. 找到插入新模組內容的位置(在 </body> 前,<script> 前)
# 先找到最後一個模組結束的位置
# 崗位清單模組 HTML
position_list_module = '''
<!-- ==================== 崗位清單模組 ==================== -->
<div class="module-content" id="module-positionlist">
<header class="app-header" style="background: linear-gradient(135deg, #8e44ad 0%, #9b59b6 100%);">
<div class="icon">
<svg viewBox="0 0 24 24" style="fill: white;"><path d="M3 13h2v-2H3v2zm0 4h2v-2H3v2zm0-8h2V7H3v2zm4 4h14v-2H7v2zm0 4h14v-2H7v2zM7 7v2h14V7H7z"/></svg>
</div>
<div>
<h1 style="color: white;">崗位清單</h1>
<div class="subtitle" style="color: rgba(255,255,255,0.8);">Position List with Sorting</div>
</div>
</header>
<div class="form-card">
<div style="margin-bottom: 20px; display: flex; justify-content: space-between; align-items: center;">
<div>
<button type="button" class="btn btn-primary" onclick="loadPositionList()">
<svg viewBox="0 0 24 24" style="width: 18px; height: 18px; fill: currentColor;"><path d="M17.65 6.35C16.2 4.9 14.21 4 12 4c-4.42 0-7.99 3.58-7.99 8s3.57 8 7.99 8c3.73 0 6.84-2.55 7.73-6h-2.08c-.82 2.33-3.04 4-5.65 4-3.31 0-6-2.69-6-6s2.69-6 6-6c1.66 0 3.14.69 4.22 1.78L13 11h7V4l-2.35 2.35z"/></svg>
載入清單
</button>
<button type="button" class="btn btn-secondary" onclick="exportPositionListCSV()">
<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>
</div>
<div style="color: var(--text-secondary);">
點擊欄位標題進行排序
</div>
</div>
<div style="overflow-x: auto;">
<table id="positionListTable" style="width: 100%; border-collapse: collapse; background: white;">
<thead>
<tr style="background: var(--primary); color: white;">
<th class="sortable" data-sort="positionCode" onclick="sortPositionList('positionCode')" style="padding: 12px; cursor: pointer; text-align: left;">
崗位編號 <span class="sort-icon"></span>
</th>
<th class="sortable" data-sort="positionName" onclick="sortPositionList('positionName')" style="padding: 12px; cursor: pointer; text-align: left;">
崗位名稱 <span class="sort-icon"></span>
</th>
<th class="sortable" data-sort="businessUnit" onclick="sortPositionList('businessUnit')" style="padding: 12px; cursor: pointer; text-align: left;">
事業體 <span class="sort-icon"></span>
</th>
<th class="sortable" data-sort="department" onclick="sortPositionList('department')" style="padding: 12px; cursor: pointer; text-align: left;">
部門 <span class="sort-icon"></span>
</th>
<th class="sortable" data-sort="positionCategory" onclick="sortPositionList('positionCategory')" style="padding: 12px; cursor: pointer; text-align: left;">
崗位類別 <span class="sort-icon"></span>
</th>
<th class="sortable" data-sort="headcount" onclick="sortPositionList('headcount')" style="padding: 12px; cursor: pointer; text-align: left;">
編制人數 <span class="sort-icon"></span>
</th>
<th class="sortable" data-sort="effectiveDate" onclick="sortPositionList('effectiveDate')" style="padding: 12px; cursor: pointer; text-align: left;">
生效日期 <span class="sort-icon"></span>
</th>
<th style="padding: 12px; text-align: center;">操作</th>
</tr>
</thead>
<tbody id="positionListBody">
<tr>
<td colspan="8" style="padding: 40px; text-align: center; color: var(--text-secondary);">
點擊「載入清單」按鈕以顯示崗位資料
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
<!-- ==================== 管理者頁面模組 ==================== -->
<div class="module-content" id="module-admin">
<header class="app-header" style="background: linear-gradient(135deg, #c0392b 0%, #e74c3c 100%);">
<div class="icon">
<svg viewBox="0 0 24 24" style="fill: white;"><path d="M19.14,12.94c0.04-0.31,0.06-0.63,0.06-0.94c0-0.32-0.02-0.64-0.07-0.94l2.03-1.58c0.18-0.14,0.23-0.41,0.12-0.61 l-1.92-3.32c-0.12-0.22-0.37-0.29-0.59-0.22l-2.39,0.96c-0.5-0.38-1.03-0.7-1.62-0.94L14.4,2.81c-0.04-0.24-0.24-0.41-0.48-0.41 h-3.84c-0.24,0-0.43,0.17-0.47,0.41L9.25,5.35C8.66,5.59,8.12,5.92,7.63,6.29L5.24,5.33c-0.22-0.08-0.47,0-0.59,0.22L2.74,8.87 C2.62,9.08,2.66,9.34,2.86,9.48l2.03,1.58C4.84,11.36,4.8,11.69,4.8,12s0.02,0.64,0.07,0.94l-2.03,1.58 c-0.18,0.14-0.23,0.41-0.12,0.61l1.92,3.32c0.12,0.22,0.37,0.29,0.59,0.22l2.39-0.96c0.5,0.38,1.03,0.7,1.62,0.94l0.36,2.54 c0.05,0.24,0.24,0.41,0.48,0.41h3.84c0.24,0,0.44-0.17,0.47-0.41l0.36-2.54c0.59-0.24,1.13-0.56,1.62-0.94l2.39,0.96 c0.22,0.08,0.47,0,0.59-0.22l1.92-3.32c0.12-0.22,0.07-0.47-0.12-0.61L19.14,12.94z M12,15.6c-1.98,0-3.6-1.62-3.6-3.6 s1.62-3.6,3.6-3.6s3.6,1.62,3.6,3.6S13.98,15.6,12,15.6z"/></svg>
</div>
<div>
<h1 style="color: white;">管理者頁面</h1>
<div class="subtitle" style="color: rgba(255,255,255,0.8);">User Administration</div>
</div>
</header>
<div class="form-card">
<div style="margin-bottom: 20px; display: flex; justify-content: space-between; align-items: center; flex-wrap: wrap; gap: 10px;">
<h2 style="color: var(--primary); margin: 0;">使用者清單</h2>
<div style="display: flex; gap: 10px;">
<button type="button" class="btn btn-primary" onclick="showAddUserModal()">
<svg viewBox="0 0 24 24" style="width: 18px; height: 18px; fill: currentColor;"><path d="M15 12c2.21 0 4-1.79 4-4s-1.79-4-4-4-4 1.79-4 4 1.79 4 4 4zm-9-2V7H4v3H1v2h3v3h2v-3h3v-2H6zm9 4c-2.67 0-8 1.34-8 4v2h16v-2c0-2.66-5.33-4-8-4z"/></svg>
新增使用者
</button>
<button type="button" class="btn btn-secondary" onclick="exportUsersCSV()">
<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>
</div>
</div>
<div style="overflow-x: auto;">
<table id="userListTable" style="width: 100%; border-collapse: collapse; background: white;">
<thead>
<tr style="background: #c0392b; color: white;">
<th style="padding: 12px; text-align: left;">工號</th>
<th style="padding: 12px; text-align: left;">使用者姓名</th>
<th style="padding: 12px; text-align: left;">Email 信箱</th>
<th style="padding: 12px; text-align: left;">權限等級</th>
<th style="padding: 12px; text-align: left;">建立日期</th>
<th style="padding: 12px; text-align: center;">操作</th>
</tr>
</thead>
<tbody id="userListBody">
<tr>
<td style="padding: 12px; border-bottom: 1px solid #eee;">A001</td>
<td style="padding: 12px; border-bottom: 1px solid #eee;">系統管理員</td>
<td style="padding: 12px; border-bottom: 1px solid #eee;">admin@company.com</td>
<td style="padding: 12px; border-bottom: 1px solid #eee;">
<span style="background: #e74c3c; color: white; padding: 4px 8px; border-radius: 4px; font-size: 12px;">最高權限管理者</span>
</td>
<td style="padding: 12px; border-bottom: 1px solid #eee;">2024-01-01</td>
<td style="padding: 12px; border-bottom: 1px solid #eee; text-align: center;">
<button class="btn btn-secondary" style="padding: 4px 8px; font-size: 12px;" onclick="editUser('A001')">編輯</button>
</td>
</tr>
<tr>
<td style="padding: 12px; border-bottom: 1px solid #eee;">A002</td>
<td style="padding: 12px; border-bottom: 1px solid #eee;">人資主管</td>
<td style="padding: 12px; border-bottom: 1px solid #eee;">hr_manager@company.com</td>
<td style="padding: 12px; border-bottom: 1px solid #eee;">
<span style="background: #f39c12; color: white; padding: 4px 8px; border-radius: 4px; font-size: 12px;">管理者</span>
</td>
<td style="padding: 12px; border-bottom: 1px solid #eee;">2024-01-15</td>
<td style="padding: 12px; border-bottom: 1px solid #eee; text-align: center;">
<button class="btn btn-secondary" style="padding: 4px 8px; font-size: 12px;" onclick="editUser('A002')">編輯</button>
<button class="btn btn-cancel" style="padding: 4px 8px; font-size: 12px;" onclick="deleteUser('A002')">刪除</button>
</td>
</tr>
<tr>
<td style="padding: 12px; border-bottom: 1px solid #eee;">A003</td>
<td style="padding: 12px; border-bottom: 1px solid #eee;">一般員工</td>
<td style="padding: 12px; border-bottom: 1px solid #eee;">employee@company.com</td>
<td style="padding: 12px; border-bottom: 1px solid #eee;">
<span style="background: #27ae60; color: white; padding: 4px 8px; border-radius: 4px; font-size: 12px;">一般使用者</span>
</td>
<td style="padding: 12px; border-bottom: 1px solid #eee;">2024-02-01</td>
<td style="padding: 12px; border-bottom: 1px solid #eee; text-align: center;">
<button class="btn btn-secondary" style="padding: 4px 8px; font-size: 12px;" onclick="editUser('A003')">編輯</button>
<button class="btn btn-cancel" style="padding: 4px 8px; font-size: 12px;" onclick="deleteUser('A003')">刪除</button>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
<!-- 新增/編輯使用者彈窗 -->
<div id="userModal" style="display: none; position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.5); z-index: 1000; justify-content: center; align-items: center;">
<div style="background: white; border-radius: 8px; padding: 30px; max-width: 500px; width: 90%;">
<h3 id="userModalTitle" style="margin: 0 0 20px 0; color: var(--primary);">新增使用者</h3>
<form id="userForm">
<div style="margin-bottom: 15px;">
<label style="display: block; margin-bottom: 5px; font-weight: 500;">工號 <span style="color: red;">*</span></label>
<input type="text" id="userEmployeeId" required style="width: 100%; padding: 10px; border: 1px solid #ddd; border-radius: 4px;">
</div>
<div style="margin-bottom: 15px;">
<label style="display: block; margin-bottom: 5px; font-weight: 500;">使用者姓名 <span style="color: red;">*</span></label>
<input type="text" id="userName" required style="width: 100%; padding: 10px; border: 1px solid #ddd; border-radius: 4px;">
</div>
<div style="margin-bottom: 15px;">
<label style="display: block; margin-bottom: 5px; font-weight: 500;">Email 信箱 <span style="color: red;">*</span></label>
<input type="email" id="userEmail" required style="width: 100%; padding: 10px; border: 1px solid #ddd; border-radius: 4px;">
</div>
<div style="margin-bottom: 20px;">
<label style="display: block; margin-bottom: 5px; font-weight: 500;">權限等級 <span style="color: red;">*</span></label>
<select id="userRole" required style="width: 100%; padding: 10px; border: 1px solid #ddd; border-radius: 4px;">
<option value="">請選擇</option>
<option value="user">一般使用者</option>
<option value="admin">管理者</option>
<option value="superadmin">最高權限管理者</option>
</select>
</div>
<div style="display: flex; justify-content: flex-end; gap: 10px;">
<button type="button" class="btn btn-cancel" onclick="closeUserModal()">取消</button>
<button type="submit" class="btn btn-primary" onclick="saveUser(event)">儲存</button>
</div>
</form>
</div>
</div>
'''
# 找到 </body> 前插入新模組
if 'module-positionlist' not in content:
# 找到 <script> 標籤的位置
script_start = content.find(' <script>')
if script_start > 0:
content = content[:script_start] + position_list_module + '\n' + content[script_start:]
print("[OK] Added Position List and Admin module content")
# 3. 加入新功能的 JavaScript 函數
new_js_functions = '''
// ==================== 崗位清單功能 ====================
let positionListData = [];
let currentSortColumn = '';
let currentSortDirection = 'asc';
// 載入崗位清單(示範資料)
function loadPositionList() {
// 示範資料
positionListData = [
{ positionCode: 'POS001', positionName: '軟體工程師', businessUnit: 'ITBU', department: '研發部', positionCategory: '技術職', headcount: 5, effectiveDate: '2024-01-01' },
{ positionCode: 'POS002', positionName: '專案經理', businessUnit: 'ITBU', department: '專案管理部', positionCategory: '管理職', headcount: 2, effectiveDate: '2024-01-01' },
{ positionCode: 'POS003', positionName: '人資專員', businessUnit: 'HRBU', department: '人力資源部', positionCategory: '行政職', headcount: 3, effectiveDate: '2024-02-01' },
{ positionCode: 'POS004', positionName: '財務分析師', businessUnit: 'ACCBU', department: '財務部', positionCategory: '專業職', headcount: 2, effectiveDate: '2024-01-15' },
{ positionCode: 'POS005', positionName: '業務代表', businessUnit: 'SBU', department: '業務部', positionCategory: '業務職', headcount: 10, effectiveDate: '2024-03-01' },
{ positionCode: 'POS006', positionName: '生產線主管', businessUnit: 'MBU', department: '生產部', positionCategory: '管理職', headcount: 4, effectiveDate: '2024-01-01' },
];
renderPositionList();
showToast('已載入 ' + positionListData.length + ' 筆崗位資料');
}
// 渲染崗位清單
function renderPositionList() {
const tbody = document.getElementById('positionListBody');
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;">${item.positionCode}</td>
<td style="padding: 12px;">${item.positionName}</td>
<td style="padding: 12px;">${item.businessUnit}</td>
<td style="padding: 12px;">${item.department}</td>
<td style="padding: 12px;">${item.positionCategory}</td>
<td style="padding: 12px;">${item.headcount}</td>
<td style="padding: 12px;">${item.effectiveDate}</td>
<td style="padding: 12px; text-align: center;">
<button class="btn btn-secondary" style="padding: 4px 8px; font-size: 12px;" onclick="viewPosition('${item.positionCode}')">檢視</button>
</td>
</tr>
`).join('');
}
// 排序崗位清單
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 viewPosition(code) {
const position = positionListData.find(p => p.positionCode === code);
if (position) {
showToast('檢視崗位: ' + position.positionName);
}
}
// 匯出崗位清單 CSV
function exportPositionListCSV() {
if (positionListData.length === 0) {
showToast('請先載入清單資料');
return;
}
const headers = ['positionCode', 'positionName', 'businessUnit', 'department', 'positionCategory', 'headcount', 'effectiveDate'];
CSVUtils.exportToCSV(positionListData, 'position_list.csv', headers);
showToast('崗位清單已匯出!');
}
// ==================== 管理者頁面功能 ====================
let usersData = [
{ employeeId: 'A001', name: '系統管理員', email: 'admin@company.com', role: 'superadmin', createdAt: '2024-01-01' },
{ employeeId: 'A002', name: '人資主管', email: 'hr_manager@company.com', role: 'admin', createdAt: '2024-01-15' },
{ employeeId: 'A003', name: '一般員工', email: 'employee@company.com', role: 'user', createdAt: '2024-02-01' }
];
let editingUserId = null;
// 顯示新增使用者彈窗
function showAddUserModal() {
editingUserId = null;
document.getElementById('userModalTitle').textContent = '新增使用者';
document.getElementById('userEmployeeId').value = '';
document.getElementById('userName').value = '';
document.getElementById('userEmail').value = '';
document.getElementById('userRole').value = '';
document.getElementById('userEmployeeId').disabled = false;
document.getElementById('userModal').style.display = 'flex';
}
// 編輯使用者
function editUser(employeeId) {
const user = usersData.find(u => u.employeeId === employeeId);
if (!user) return;
editingUserId = employeeId;
document.getElementById('userModalTitle').textContent = '編輯使用者';
document.getElementById('userEmployeeId').value = user.employeeId;
document.getElementById('userEmployeeId').disabled = true;
document.getElementById('userName').value = user.name;
document.getElementById('userEmail').value = user.email;
document.getElementById('userRole').value = user.role;
document.getElementById('userModal').style.display = 'flex';
}
// 關閉使用者彈窗
function closeUserModal() {
document.getElementById('userModal').style.display = 'none';
editingUserId = null;
}
// 儲存使用者
function saveUser(event) {
event.preventDefault();
const employeeId = document.getElementById('userEmployeeId').value;
const name = document.getElementById('userName').value;
const email = document.getElementById('userEmail').value;
const role = document.getElementById('userRole').value;
if (!employeeId || !name || !email || !role) {
showToast('請填寫所有必填欄位');
return;
}
if (editingUserId) {
// 編輯模式
const index = usersData.findIndex(u => u.employeeId === editingUserId);
if (index > -1) {
usersData[index] = { ...usersData[index], name, email, role };
showToast('使用者已更新');
}
} else {
// 新增模式
if (usersData.some(u => u.employeeId === employeeId)) {
showToast('工號已存在');
return;
}
usersData.push({
employeeId,
name,
email,
role,
createdAt: new Date().toISOString().split('T')[0]
});
showToast('使用者已新增');
}
closeUserModal();
renderUserList();
}
// 刪除使用者
function deleteUser(employeeId) {
if (confirm('確定要刪除此使用者嗎?')) {
usersData = usersData.filter(u => u.employeeId !== employeeId);
renderUserList();
showToast('使用者已刪除');
}
}
// 渲染使用者清單
function renderUserList() {
const tbody = document.getElementById('userListBody');
const roleLabels = {
'superadmin': { text: '最高權限管理者', color: '#e74c3c' },
'admin': { text: '管理者', color: '#f39c12' },
'user': { text: '一般使用者', color: '#27ae60' }
};
tbody.innerHTML = usersData.map(user => {
const roleInfo = roleLabels[user.role] || { text: user.role, color: '#999' };
const isSuperAdmin = user.role === 'superadmin';
return `
<tr>
<td style="padding: 12px; border-bottom: 1px solid #eee;">${user.employeeId}</td>
<td style="padding: 12px; border-bottom: 1px solid #eee;">${user.name}</td>
<td style="padding: 12px; border-bottom: 1px solid #eee;">${user.email}</td>
<td style="padding: 12px; border-bottom: 1px solid #eee;">
<span style="background: ${roleInfo.color}; color: white; padding: 4px 8px; border-radius: 4px; font-size: 12px;">${roleInfo.text}</span>
</td>
<td style="padding: 12px; border-bottom: 1px solid #eee;">${user.createdAt}</td>
<td style="padding: 12px; border-bottom: 1px solid #eee; text-align: center;">
<button class="btn btn-secondary" style="padding: 4px 8px; font-size: 12px;" onclick="editUser('${user.employeeId}')">編輯</button>
${!isSuperAdmin ? `<button class="btn btn-cancel" style="padding: 4px 8px; font-size: 12px;" onclick="deleteUser('${user.employeeId}')">刪除</button>` : ''}
</td>
</tr>
`;
}).join('');
}
// 匯出使用者 CSV
function exportUsersCSV() {
const headers = ['employeeId', 'name', 'email', 'role', 'createdAt'];
CSVUtils.exportToCSV(usersData, 'users.csv', headers);
showToast('使用者清單已匯出!');
}
'''
# 在現有的 JavaScript 函數區塊末尾加入新函數
if 'function loadPositionList()' not in content:
# 找到 </script> 的位置
script_end = content.find(' </script>')
if script_end > 0:
content = content[:script_end] + new_js_functions + '\n' + content[script_end:]
print("[OK] Added Position List and Admin JavaScript functions")
# 寫回
with open('index.html', 'w', encoding='utf-8') as f:
f.write(content)
print("\n" + "="*60)
print("[OK] Position List and Admin Page Added!")
print("="*60)
print("\nNew features:")
print("1. Position List tab with sortable columns")
print(" - Click column headers to sort")
print(" - Export to CSV")
print("2. Admin page with user management")
print(" - Add/Edit/Delete users")
print(" - Three permission levels:")
print(" - Regular User")
print(" - Admin")
print(" - Super Admin")
print(" - Export users to CSV")
print("\nPlease reload the page (Ctrl+F5) to see the new features!")