backup: 完成 HR_position_ 表格前綴重命名與欄位對照表整理

變更內容:
- 所有資料表加上 HR_position_ 前綴
- 整理完整欄位顯示名稱與 ID 對照表
- 模組化 JS 檔案 (admin.js, ai.js, csv.js 等)
- 專案結構優化 (docs/, scripts/, tests/ 等)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2025-12-09 12:05:20 +08:00
parent a068ef9704
commit a6af297623
82 changed files with 8685 additions and 4933 deletions

382
scripts/generate_review.py Normal file
View File

@@ -0,0 +1,382 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import json
# 讀取表格數據
with open('excel_table.md', 'r', encoding='utf-8') as f:
lines = f.readlines()
# 解析數據(跳過表頭和分隔線)
data = []
for line in lines[2:]: # 跳過表頭和分隔線
line = line.strip()
if not line or not line.startswith('|'):
continue
# 移除首尾的管道符號並分割
parts = [p.strip() for p in line[1:-1].split('|')]
if len(parts) >= 4:
data.append({
'事業體': parts[0],
'處級單位': parts[1],
'部級單位': parts[2],
'崗位名稱': parts[3]
})
# 生成 HTML
html_content = '''<!DOCTYPE html>
<html lang="zh-TW">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>組織架構預覽</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: "Microsoft JhengHei", "微軟正黑體", Arial, sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
padding: 20px;
min-height: 100vh;
}
/* Header using Float */
.header {
background: rgba(255, 255, 255, 0.95);
padding: 20px;
border-radius: 10px;
margin-bottom: 20px;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
overflow: hidden; /* Clear float */
}
.header h1 {
float: left;
color: #333;
font-size: 28px;
}
.header .stats {
float: right;
color: #666;
font-size: 14px;
padding-top: 8px;
}
.header::after {
content: "";
display: table;
clear: both;
}
/* Filter Section using Float */
.filters {
background: rgba(255, 255, 255, 0.95);
padding: 20px;
border-radius: 10px;
margin-bottom: 20px;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
overflow: hidden;
}
.filter-group {
float: left;
margin-right: 20px;
margin-bottom: 10px;
}
.filter-group label {
display: block;
margin-bottom: 5px;
color: #555;
font-weight: bold;
font-size: 14px;
}
.filter-group select,
.filter-group input {
padding: 8px 12px;
border: 2px solid #ddd;
border-radius: 5px;
font-size: 14px;
width: 200px;
}
.filter-group select:focus,
.filter-group input:focus {
outline: none;
border-color: #667eea;
}
.filters::after {
content: "";
display: table;
clear: both;
}
/* Table Container using Float */
.table-container {
background: rgba(255, 255, 255, 0.95);
border-radius: 10px;
padding: 20px;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
overflow-x: auto;
}
table {
width: 100%;
border-collapse: collapse;
font-size: 14px;
}
thead {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
}
th {
padding: 15px;
text-align: left;
font-weight: bold;
position: sticky;
top: 0;
z-index: 10;
}
tbody tr {
border-bottom: 1px solid #eee;
transition: background-color 0.2s;
}
tbody tr:hover {
background-color: #f5f5f5;
}
tbody tr:nth-child(even) {
background-color: #fafafa;
}
tbody tr:nth-child(even):hover {
background-color: #f0f0f0;
}
td {
padding: 12px 15px;
color: #333;
}
td:first-child {
font-weight: 600;
color: #667eea;
}
td:last-child {
color: #764ba2;
font-weight: 500;
}
/* Empty cells styling */
td:empty::before {
content: "";
color: #ccc;
}
/* Footer using Float */
.footer {
margin-top: 20px;
background: rgba(255, 255, 255, 0.95);
padding: 15px;
border-radius: 10px;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
text-align: center;
color: #666;
font-size: 14px;
}
/* Responsive Design */
@media (max-width: 768px) {
.filter-group {
float: none;
width: 100%;
margin-right: 0;
}
.filter-group select,
.filter-group input {
width: 100%;
}
.header h1 {
float: none;
margin-bottom: 10px;
}
.header .stats {
float: none;
}
}
</style>
</head>
<body>
<div class="header">
<h1>📊 公司組織架構預覽</h1>
<div class="stats">總計: <span id="totalCount">''' + str(len(data)) + '''</span> 筆資料</div>
</div>
<div class="filters">
<div class="filter-group">
<label for="filterBusiness">事業體篩選</label>
<select id="filterBusiness">
<option value="">全部</option>
</select>
</div>
<div class="filter-group">
<label for="filterDepartment">處級單位篩選</label>
<select id="filterDepartment">
<option value="">全部</option>
</select>
</div>
<div class="filter-group">
<label for="filterDivision">部級單位篩選</label>
<select id="filterDivision">
<option value="">全部</option>
</select>
</div>
<div class="filter-group">
<label for="searchPosition">崗位搜尋</label>
<input type="text" id="searchPosition" placeholder="輸入崗位名稱...">
</div>
</div>
<div class="table-container">
<table>
<thead>
<tr>
<th>事業體</th>
<th>處級單位</th>
<th>部級單位</th>
<th>崗位名稱</th>
</tr>
</thead>
<tbody id="tableBody">
'''
# 添加表格行
for row in data:
html_content += f''' <tr>
<td>{row['事業體']}</td>
<td>{row['處級單位'] if row['處級單位'] else ''}</td>
<td>{row['部級單位'] if row['部級單位'] else ''}</td>
<td>{row['崗位名稱']}</td>
</tr>
'''
html_content += ''' </tbody>
</table>
</div>
<div class="footer">
<p>組織架構資料預覽系統 | 使用 CSS Float Layout 設計</p>
</div>
<script>
// 獲取所有數據
const allData = ''' + json.dumps(data, ensure_ascii=False) + ''';
// 獲取唯一的選項值
function getUniqueValues(key) {
const values = new Set();
allData.forEach(row => {
if (row[key]) {
values.add(row[key]);
}
});
return Array.from(values).sort();
}
// 填充下拉選單
function populateSelects() {
const businessSelect = document.getElementById('filterBusiness');
const deptSelect = document.getElementById('filterDepartment');
const divSelect = document.getElementById('filterDivision');
getUniqueValues('事業體').forEach(value => {
const option = document.createElement('option');
option.value = value;
option.textContent = value;
businessSelect.appendChild(option);
});
getUniqueValues('處級單位').forEach(value => {
const option = document.createElement('option');
option.value = value;
option.textContent = value;
deptSelect.appendChild(option);
});
getUniqueValues('部級單位').forEach(value => {
const option = document.createElement('option');
option.value = value;
option.textContent = value;
divSelect.appendChild(option);
});
}
// 過濾數據
function filterData() {
const businessFilter = document.getElementById('filterBusiness').value;
const deptFilter = document.getElementById('filterDepartment').value;
const divFilter = document.getElementById('filterDivision').value;
const positionSearch = document.getElementById('searchPosition').value.toLowerCase();
const filtered = allData.filter(row => {
const matchBusiness = !businessFilter || row['事業體'] === businessFilter;
const matchDept = !deptFilter || row['處級單位'] === deptFilter;
const matchDiv = !divFilter || row['部級單位'] === divFilter;
const matchPosition = !positionSearch || row['崗位名稱'].toLowerCase().includes(positionSearch);
return matchBusiness && matchDept && matchDiv && matchPosition;
});
renderTable(filtered);
document.getElementById('totalCount').textContent = filtered.length;
}
// 渲染表格
function renderTable(data) {
const tbody = document.getElementById('tableBody');
tbody.innerHTML = '';
data.forEach(row => {
const tr = document.createElement('tr');
tr.innerHTML = `
<td>${row['事業體'] || ''}</td>
<td>${row['處級單位'] || ''}</td>
<td>${row['部級單位'] || ''}</td>
<td>${row['崗位名稱'] || ''}</td>
`;
tbody.appendChild(tr);
});
}
// 事件監聽
document.getElementById('filterBusiness').addEventListener('change', filterData);
document.getElementById('filterDepartment').addEventListener('change', filterData);
document.getElementById('filterDivision').addEventListener('change', filterData);
document.getElementById('searchPosition').addEventListener('input', filterData);
// 初始化
populateSelects();
</script>
</body>
</html>'''
# 寫入文件
with open('review.html', 'w', encoding='utf-8') as f:
f.write(html_content)
print(f"預覽頁面已生成review.html")
print(f"共包含 {len(data)} 筆組織架構資料")