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:
499
add_position_list_and_admin.py
Normal file
499
add_position_list_and_admin.py
Normal 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!")
|
||||
Reference in New Issue
Block a user