變更內容: - 所有資料表加上 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>
383 lines
11 KiB
Python
383 lines
11 KiB
Python
#!/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)} 筆組織架構資料")
|
||
|