From d17af39bf4517e4831b8ee02c5e686a8822917c5 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?DonaldFang=20=E6=96=B9=E5=A3=AB=E7=A2=A9?=
Date: Thu, 4 Dec 2025 10:06:50 +0800
Subject: [PATCH] =?UTF-8?q?feat:=20=E6=96=B0=E5=A2=9E=E5=A4=9A=E9=A0=85?=
=?UTF-8?q?=E5=8A=9F=E8=83=BD=20v2.1?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- 新增 CSV 匯入匯出功能(所有頁籤)
- 新增崗位清單頁籤(含欄位排序)
- 新增管理者頁面(使用者 CRUD)
- 新增事業體選項(SBU/MBU/HQBU/ITBU/HRBU/ACCBU)
- 新增組織單位欄位(處級/部級/課級)
- 崗位描述/備注改為條列式說明
- 新增 README.md 文件
- 新增開發指令記錄檔
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude
---
README.md | 206 +++++++++++
USER_COMMANDS_LOG.md | 344 ++++++++++++++++++
add_csv_buttons.py | 281 ++++++++++++++
add_org_fields.py | 84 +++++
add_position_list_and_admin.py | 499 +++++++++++++++++++++++++
csv_utils.js | 246 +++++++++++++
index.html | 644 ++++++++++++++++++++++++++++++++-
requirements.txt | 2 +
8 files changed, 2302 insertions(+), 4 deletions(-)
create mode 100644 README.md
create mode 100644 USER_COMMANDS_LOG.md
create mode 100644 add_csv_buttons.py
create mode 100644 add_org_fields.py
create mode 100644 add_position_list_and_admin.py
create mode 100644 csv_utils.js
create mode 100644 requirements.txt
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..b578c02
--- /dev/null
+++ b/README.md
@@ -0,0 +1,206 @@
+# HR Position Management System
+
+人力資源崗位管理系統 v2.0
+
+## 功能特色
+
+### 1. 崗位基礎資料維護
+- **組織架構設定**
+ - 事業體選擇(SBU/MBU/HQBU/ITBU/HRBU/ACCBU)
+ - 處級單位(Division)
+ - 部級單位(Department)
+ - 課級單位(Section)
+- **崗位資訊**
+ - 崗位編號、名稱、級別
+ - 崗位類別與性質
+ - 編制人數、生效日期
+ - 條列式崗位描述與備注
+
+### 2. 職務基礎資料維護
+- 職務類別管理(管理職/技術職/業務職/行政職/研發職)
+- 職務編號與名稱(中英文)
+- 生效日期、編制人數
+- 職級與福利設定(全勤/住房補貼)
+
+### 3. 崗位描述維護
+- 工作職責說明
+- 技能要求
+- 工作環境描述
+- 職涯發展路徑
+
+### 4. 崗位清單(新功能)
+- 顯示所有崗位資料(表格形式)
+- 點擊欄位標題排序(升序/降序切換)
+- 支援匯出 CSV
+
+### 5. 管理者頁面(新功能)
+- 使用者管理(新增/編輯/刪除)
+- 三種權限等級:
+ - 一般使用者(綠色標籤)
+ - 管理者(橘色標籤)
+ - 最高權限管理者(紅色標籤)
+- 匯出使用者清單 CSV
+
+### 6. 通用功能
+- **CSV 匯入/匯出**:所有頁籤皆支援
+- **AI 自動填充**:「I'm feeling lucky」按鈕
+- **錯誤訊息處理**:可展開、可複製
+
+---
+
+## 技術架構
+
+| 層級 | 技術 |
+|------|------|
+| 前端 | HTML5, CSS3, JavaScript (Vanilla) |
+| 後端 | Python Flask |
+| 資料庫 | MySQL 5.7+ |
+| 版本控制 | Git / Gitea |
+| AI 整合 | Gemini API (gemini-2.5-flash) |
+
+---
+
+## 環境需求
+
+- Python 3.8+
+- MySQL 5.7+
+- 現代瀏覽器(Chrome, Firefox, Edge)
+
+---
+
+## 快速啟動
+
+### 方式一:純前端(無需後端)
+直接用瀏覽器開啟 `index.html` 即可使用基本功能。
+
+### 方式二:完整版(含 Flask API)
+
+```bash
+# 1. 複製專案
+git clone https://gitea.theaken.com/donald/hr-position-system.git
+cd hr-position-system
+
+# 2. 安裝 Python 套件
+pip install -r requirements.txt
+
+# 3. 設定環境變數
+# 編輯 .env 填入資料庫和 API 金鑰
+
+# 4. 初始化資料庫
+python init_database.py
+
+# 5. 啟動伺服器
+python start_server.py
+
+# 6. 開啟瀏覽器
+# http://127.0.0.1:5000
+```
+
+---
+
+## API 端點
+
+### 崗位資料 API
+| 方法 | 路徑 | 說明 |
+|------|------|------|
+| GET | `/api/positions` | 獲取所有崗位 |
+| GET | `/api/positions/` | 獲取單一崗位 |
+| POST | `/api/positions` | 新增崗位 |
+| PUT | `/api/positions/` | 更新崗位 |
+| DELETE | `/api/positions/` | 刪除崗位 |
+
+### 職務資料 API
+| 方法 | 路徑 | 說明 |
+|------|------|------|
+| GET | `/api/jobs` | 獲取所有職務 |
+| POST | `/api/jobs` | 新增職務 |
+
+### LLM API
+| 方法 | 路徑 | 說明 |
+|------|------|------|
+| GET | `/api/llm/config` | 取得 LLM 設定 |
+| GET | `/api/llm/test/` | 測試 API 連線 |
+| POST | `/api/llm/generate` | 生成文字 |
+
+---
+
+## 專案結構
+
+```
+hr-position-system/
+├── index.html # 主要應用頁面
+├── start_server.py # Flask 伺服器(Windows 相容)
+├── llm_config.py # LLM API 設定
+├── csv_utils.js # CSV 工具模組
+├── error_handler.js # 錯誤處理模組
+├── api_test.html # API 測試頁面
+├── database_schema.sql # 資料庫結構
+├── init_database.py # 資料庫初始化
+├── requirements.txt # Python 套件
+├── .env # 環境變數(不上傳)
+├── .gitignore # Git 忽略清單
+├── SDD.md # 系統設計文件
+├── USER_COMMANDS_LOG.md # 開發指令記錄
+└── README.md # 本文件
+```
+
+---
+
+## 快捷鍵
+
+| 快捷鍵 | 功能 |
+|--------|------|
+| `Ctrl+S` | 保存并退出 |
+| `Ctrl+N` | 保存并新增 |
+
+---
+
+## 版本歷史
+
+### v2.0 (2024-12-04)
+- 新增 CSV 匯入匯出功能(所有頁籤)
+- 新增崗位清單頁籤(含欄位排序)
+- 新增管理者頁面(使用者 CRUD)
+- 新增事業體與組織單位欄位
+- 崗位描述/備注改為條列式說明
+- 修正 CORS 錯誤
+- 改善錯誤訊息顯示(可複製)
+- 修正 Windows 編碼問題
+
+### v1.0 (2024-12-04)
+- 初始版本
+- 崗位基礎資料維護
+- 職務基礎資料維護
+- 崗位描述維護
+
+---
+
+## 環境變數設定(.env)
+
+```env
+# 資料庫設定
+DB_HOST=mysql.theaken.com
+DB_PORT=33306
+DB_NAME=db_A102
+DB_USER=A102
+DB_PASSWORD=your_password
+
+# LLM API 金鑰
+GEMINI_API_KEY=your_gemini_key
+DEEPSEEK_API_KEY=your_deepseek_key
+OPENAI_API_KEY=your_openai_key
+
+# Gitea 設定
+GITEA_URL=https://gitea.theaken.com
+GITEA_TOKEN=your_token
+```
+
+---
+
+## 授權
+
+此專案為內部使用系統。
+
+## 聯絡方式
+
+如有問題請聯繫系統管理員。
diff --git a/USER_COMMANDS_LOG.md b/USER_COMMANDS_LOG.md
new file mode 100644
index 0000000..c267be2
--- /dev/null
+++ b/USER_COMMANDS_LOG.md
@@ -0,0 +1,344 @@
+# 使用者指令記錄
+
+**專案**: HR Position Management System
+**日期**: 2024-12-04
+**版本**: 1.0
+
+---
+
+## 📋 指令時間軸
+
+### 初始設定階段
+
+#### 1. 系統基礎建置
+```
+提供 SDD.md 文件,要求建立:
+- .env 文件(包含 MySQL 資料庫設定)
+- .gitignore 文件
+- MySQL 資料庫連線設定(mysql.theaken.com:33306, db_A102)
+- Gitea 版本控制設定(https://gitea.theaken.com/)
+- LLM API 整合(Gemini, DeepSeek, OpenAI)
+- 所有頁面/功能的錯誤處理機制
+```
+
+**執行結果**: ✅ 完成
+- 建立 .env 文件
+- 建立 .gitignore 文件
+- 建立 database_schema.sql
+- 建立 llm_config.py
+- 建立 api_test.html
+- 建立 error_handler.js
+- 建立完整的設定文件
+
+---
+
+### CORS 錯誤修正階段
+
+#### 2. 回報 CORS 錯誤
+```
+回報錯誤:
+Access to fetch at 'https://api.anthropic.com/v1/messages' from origin
+'http://127.0.0.1:5000' has been blocked by CORS policy
+```
+
+**執行結果**: ✅ 修正完成
+- 修改 index.html 的 callClaudeAPI 函數
+- 建立 app_updated.py 增加 LLM endpoints
+- 建立 start_server.py 處理 Windows 編碼問題
+- 建立修正腳本(quick_fix.py, complete_fix.py)
+
+---
+
+### Gemini API 模型設定階段
+
+#### 3. 指定 Gemini 模型
+```
+指令: "我要用gemini-2.5-flash模型"
+```
+
+**執行結果**: ✅ 已更改
+
+#### 4. 強調不要亂改
+```
+指令: "你不要亂改!"
+```
+
+**背景**: 我建議使用 gemini-1.5-flash,但使用者堅持使用特定版本
+
+#### 5. 堅持使用指定模型
+```
+指令: "有責任我扛,你就是換成gemini-2.5-flash"
+```
+
+**執行結果**: ✅ 已修改 llm_config.py 使用 gemini-2.5-flash
+
+#### 6. 確認當前模型
+```
+指令: "你現在用哪個模型"
+```
+
+**回覆**: 確認使用 gemini-2.5-flash
+
+---
+
+### Git 版本控制階段
+
+#### 7. 推送到 Gitea
+```
+指令: "push to gitea"
+```
+
+**執行結果**: ✅ 成功推送
+- 初始化 git repository
+- 透過 Gitea API 建立 repository
+- 執行 git commit 和 git push
+- Repository URL: https://gitea.theaken.com/donald/hr-position-system
+
+#### 8. 更新 SDD 文件
+```
+指令: "更新一份SDD文件,進行版更"
+```
+
+**執行結果**: ✅ 完成
+- 更新 SDD.md 從 v1.0 到 v2.0
+- 新增變更歷史區塊
+- 記錄所有功能改進
+
+---
+
+### 新功能開發階段
+
+#### 9. CSV 匯入匯出功能
+```
+指令: "#在每個頁籤都加入csv匯入匯出功能"
+```
+
+**執行結果**: ✅ 完成
+- ✅ 建立 csv_utils.js 工具模組
+- ✅ 整合到崗位資料頁籤
+- ✅ 整合到職務資料頁籤
+- ✅ 整合到崗位描述頁籤
+
+**技術規格**:
+- exportToCSV(): 匯出資料為 CSV 檔案
+- importFromCSV(): 從 CSV 檔案匯入資料
+- parseCSV(): 解析 CSV 文字
+- 支援 UTF-8 BOM
+- 支援引號和逗號的正確處理
+
+---
+
+#### 10. 新增崗位清單頁籤
+```
+指令: "#增加一個崗位清單的頁籤,這個頁籤可以選擇欄位進行排序"
+```
+
+**執行結果**: ✅ 完成
+
+**實作內容**:
+- ✅ 新增獨立頁籤「崗位清單」
+- ✅ 顯示所有崗位資料(表格形式)
+- ✅ 支援點擊欄位標題進行排序
+- ✅ 支援升序/降序切換(顯示 ^ 和 v 符號)
+- ✅ 欄位包含:
+ - 崗位編號
+ - 崗位名稱
+ - 事業體
+ - 部門
+ - 崗位類別
+ - 編制人數
+ - 生效日期
+- ✅ 支援匯出 CSV
+
+---
+
+#### 11. 新增管理者頁面
+```
+指令: "#加入管理者頁面,建立使用者清單,清單的欄位包含工號,使用者姓名,
+email信箱,使用者權限設定(一般使用者/管理者/最高權限管理者)"
+```
+
+**執行結果**: ✅ 完成
+
+**實作內容**:
+- ✅ 建立新的管理者頁面/頁籤
+- ✅ 使用者清單欄位:
+ - 工號(Employee ID)
+ - 使用者姓名(User Name)
+ - Email 信箱
+ - 權限等級(三種權限層級)
+ - 建立日期
+- ✅ 權限標籤顏色區分:
+ - 一般使用者:綠色
+ - 管理者:橘色
+ - 最高權限管理者:紅色
+- ✅ CRUD 功能:
+ - 新增使用者(彈窗表單)
+ - 編輯使用者
+ - 刪除使用者(最高權限管理者不可刪除)
+- ✅ 匯出使用者 CSV
+
+---
+
+#### 14. 新增事業體選項和組織單位欄位
+```
+指令: "#在每個頁籤都加入事業體選項(SBU,MBU,HQBU,ITBU,HRBU,ACCBU)"
+指令: "#增加一個處級單位,一個部級單位,一個課級單位"
+```
+
+**執行結果**: ✅ 完成
+
+**實作內容**:
+- ✅ 事業體下拉選單(6個選項):
+ - SBU - 銷售事業體
+ - MBU - 製造事業體
+ - HQBU - 總部事業體
+ - ITBU - IT事業體
+ - HRBU - HR事業體
+ - ACCBU - 會計事業體
+- ✅ 處級單位欄位(Division)
+- ✅ 部級單位欄位(Department)
+- ✅ 課級單位欄位(Section)
+- 所有欄位為選填
+
+---
+
+### 測試階段
+
+#### 12. 測試所有服務功能
+```
+指令: "#測試服務的所有功能是否正常"
+```
+
+**執行結果**: ⏳ 待執行
+
+**測試範圍**:
+1. Flask 後端 API endpoints
+ - [ ] GET /api/positions
+ - [ ] GET /api/positions/
+ - [ ] POST /api/positions
+ - [ ] GET /api/jobs
+ - [ ] LLM API endpoints
+2. LLM API 連線測試
+ - [ ] Gemini API (gemini-2.5-flash)
+ - [ ] DeepSeek API
+ - [ ] OpenAI API
+3. 前端功能測試
+ - [ ] 崗位資料表單(新增/查詢/編輯)
+ - [ ] 職務資料表單
+ - [ ] AI 自動填充功能
+ - [ ] 錯誤訊息顯示與複製
+ - [ ] CSV 匯入匯出(新功能)
+4. 資料庫連線測試
+5. Gitea repository 訪問測試
+
+---
+
+#### 13. 建立指令記錄檔
+```
+指令: "你建立一個.md檔,記錄所有我在這個對話框裡下的指令"
+```
+
+**執行結果**: ✅ 正在建立(此文件)
+
+---
+
+## 📊 指令統計
+
+**總計**: 15 個指令
+**已完成**: 13 個
+**進行中**: 1 個(測試)
+**待執行**: 1 個(推送到 Gitea)
+
+---
+
+## 🔧 技術決策記錄
+
+### 1. CORS 問題解決方案
+- **決策**: 使用 Flask 後端作為代理,避免前端直接呼叫外部 API
+- **原因**: 瀏覽器 CORS 政策限制跨域請求
+- **實作**: 建立 /api/llm/generate endpoint
+
+### 2. Gemini 模型版本
+- **決策**: 使用 gemini-2.5-flash
+- **原因**: 使用者明確要求並願意承擔責任
+- **風險**: 該模型可能尚未正式發布
+
+### 3. 錯誤處理方式
+- **決策**: 建立可關閉、可複製的錯誤對話框
+- **原因**: 使用者需要完整查看和複製錯誤訊息
+- **實作**: showCopyableError() 函數
+
+### 4. CSV 功能實作
+- **決策**: 建立獨立的 csv_utils.js 模組
+- **原因**: 模組化設計,可重複使用於多個頁籤
+- **優點**: 維護容易,功能統一
+
+---
+
+## 🎯 下一步行動計畫
+
+### 優先順序 1: 完成 CSV 整合
+- [ ] 在崗位資料頁籤加入 CSV 按鈕
+- [ ] 在職務資料頁籤加入 CSV 按鈕
+- [ ] 在崗位描述頁籤加入 CSV 按鈕
+- [ ] 測試 CSV 匯入匯出功能
+
+### 優先順序 2: 建立崗位清單頁籤
+- [ ] 設計頁籤 UI
+- [ ] 實作欄位排序功能
+- [ ] 測試排序功能
+
+### 優先順序 3: 建立管理者頁面
+- [ ] 設計資料庫 schema(users 表)
+- [ ] 建立後端 API(/api/users)
+- [ ] 建立前端管理介面
+- [ ] 實作 CRUD 功能
+- [ ] 加入權限控制
+
+### 優先順序 4: 全面測試
+- [ ] 執行所有功能測試
+- [ ] 修正發現的問題
+- [ ] 更新文件
+
+### 優先順序 5: 版本控制
+- [ ] Commit 新功能
+- [ ] 更新 SDD 到 v3.0
+- [ ] Push to Gitea
+
+---
+
+## 📝 備註
+
+### 系統環境
+- **作業系統**: Windows
+- **Python 版本**: 3.x
+- **資料庫**: MySQL (mysql.theaken.com:33306)
+- **Git 服務**: Gitea (https://gitea.theaken.com/)
+- **Flask 端口**: 5000
+
+### 已知問題
+1. ✅ CORS 錯誤 - 已修正
+2. ✅ Windows 編碼錯誤 - 已修正
+3. ✅ 錯誤對話框無法關閉 - 已修正
+4. ⚠️ Gemini API Referrer 限制 - 需要使用者自行設定 API Key
+
+### 重要文件清單
+1. `.env` - 環境變數設定
+2. `SDD.md` - 系統設計文件(v2.0)
+3. `llm_config.py` - LLM API 設定(gemini-2.5-flash)
+4. `start_server.py` - Flask 伺服器啟動腳本
+5. `csv_utils.js` - CSV 工具模組
+6. `error_handler.js` - 錯誤處理模組
+7. `api_test.html` - API 測試頁面
+8. `SETUP.md` - 安裝指南
+9. `CORS_FIX_GUIDE.md` - CORS 修正指南
+10. `GEMINI_API_FIX.md` - Gemini API 修正指南
+11. `USER_COMMANDS_LOG.md` - 本文件
+
+---
+
+**文件建立時間**: 2024-12-04
+**最後更新**: 2024-12-04
+**維護者**: Claude Code
+**專案狀態**: 🚧 開發中
diff --git a/add_csv_buttons.py b/add_csv_buttons.py
new file mode 100644
index 0000000..825cd27
--- /dev/null
+++ b/add_csv_buttons.py
@@ -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. 在 中加入 csv_utils.js
+if '' not in content:
+ head_insertion = ' \n'
+ content = content.replace('', head_insertion)
+ print("[OK] Added csv_utils.js reference")
+
+# 2. 為崗位資料模組加入 CSV 按鈕
+# 在 action-buttons 區域前加入 CSV 按鈕
+position_csv_buttons = '''
+
+'''
+
+# 找到崗位資料模組的 action-buttons
+old_position_section = '''
@@ -623,6 +624,14 @@
崗位描述
+
+
@@ -650,6 +659,38 @@
✨ I'm feeling lucky
@@ -859,6 +900,18 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
', csv_functions + '\n \n')
+ 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
")
+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!")
diff --git a/add_org_fields.py b/add_org_fields.py
new file mode 100644
index 0000000..ef6b677
--- /dev/null
+++ b/add_org_fields.py
@@ -0,0 +1,84 @@
+"""
+新增事業體和組織單位欄位到崗位資料表單
+"""
+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_org', 'w', encoding='utf-8') as f:
+ f.write(content)
+
+# 找到基礎資料頁籤中的表單欄位區域,在 positionRemark 欄位前加入新欄位
+# 先找到 positionRemark 的 form-group
+org_fields_html = '''
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+'''
+
+# 在 positionRemark 前插入
+old_pattern = '''
+
+
+
'''
+
+new_pattern = org_fields_html + '''
+
+
+
'''
+
+if old_pattern in content and org_fields_html not in content:
+ content = content.replace(old_pattern, new_pattern)
+ print("[OK] Added organization fields to Position form")
+else:
+ print("[INFO] Organization fields may already exist or pattern not found")
+
+# 寫回
+with open('index.html', 'w', encoding='utf-8') as f:
+ f.write(content)
+
+print("\n" + "="*60)
+print("[OK] Organization Fields Added!")
+print("="*60)
+print("\nAdded fields:")
+print("1. Business Unit (SBU/MBU/HQBU/ITBU/HRBU/ACCBU)")
+print("2. Division (Division level, optional)")
+print("3. Department (Department level, optional)")
+print("4. Section (Section level, optional)")
+print("\nPlease reload the page (Ctrl+F5) to see the new fields!")
diff --git a/add_position_list_and_admin.py b/add_position_list_and_admin.py
new file mode 100644
index 0000000..9914953
--- /dev/null
+++ b/add_position_list_and_admin.py
@@ -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 = '''
+
+
+ '''
+
+old_module_end = '''
+ '''
+
+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. 找到插入新模組內容的位置(在 前, 的位置
+ script_end = content.find(' ')
+ 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!")
diff --git a/csv_utils.js b/csv_utils.js
new file mode 100644
index 0000000..fc1e805
--- /dev/null
+++ b/csv_utils.js
@@ -0,0 +1,246 @@
+/**
+ * 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;
+}
diff --git a/index.html b/index.html
index a10fe75..5bd6d14 100644
--- a/index.html
+++ b/index.html
@@ -606,6 +606,7 @@
.module-btn { min-width: 100%; }
}
+