refactor: 新增 ui.js 和 main.js 模組,啟用 ES6 Modules

新增檔案:
- js/ui.js - UI 操作、模組切換、預覽更新、表單資料收集
- js/main.js - 主程式初始化、事件監聽器設置、快捷鍵

更新檔案:
- index.html - 引用 ES6 模組 (type="module")

功能:
 模組切換功能
 標籤頁切換
 表單欄位監聽
 JSON 預覽更新
 快捷鍵支援 (Ctrl+S, Ctrl+N)
 用戶信息載入
 登出功能

注意:
- 大部分 JavaScript 代碼仍在 HTML 中(約 2400 行)
- 已建立核心模組架構,便於後續逐步遷移
- 使用 ES6 Modules,需要通過 HTTP Server 運行

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

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-12-05 17:18:28 +08:00
parent ee3db29c32
commit 12ceccc3d3
27 changed files with 9712 additions and 19 deletions

471
login.html Normal file
View File

@@ -0,0 +1,471 @@
<!DOCTYPE html>
<html lang="zh-TW">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>那都AI寫的不要問我 - 登入</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Microsoft JhengHei', 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh;
display: flex;
justify-content: center;
align-items: center;
padding: 20px;
}
.login-container {
background: white;
border-radius: 20px;
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
overflow: hidden;
max-width: 450px;
width: 100%;
animation: slideIn 0.5s ease-out;
}
@keyframes slideIn {
from {
opacity: 0;
transform: translateY(-30px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.login-header {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
padding: 40px 30px;
text-align: center;
color: white;
}
.logo-container {
margin-bottom: 20px;
}
.logo-container img {
width: 150px;
height: 150px;
animation: float 3s ease-in-out infinite;
}
@keyframes float {
0%, 100% {
transform: translateY(0px);
}
50% {
transform: translateY(-10px);
}
}
.system-title {
font-size: 28px;
font-weight: bold;
margin-bottom: 10px;
text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.2);
}
.system-subtitle {
font-size: 14px;
opacity: 0.9;
font-style: italic;
}
.login-body {
padding: 40px 30px;
}
.form-group {
margin-bottom: 25px;
}
.form-group label {
display: block;
margin-bottom: 8px;
color: #333;
font-weight: 500;
font-size: 14px;
}
.form-group input {
width: 100%;
padding: 12px 15px;
border: 2px solid #e0e0e0;
border-radius: 8px;
font-size: 15px;
transition: all 0.3s;
}
.form-group input:focus {
outline: none;
border-color: #667eea;
box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);
}
.btn {
width: 100%;
padding: 14px;
border: none;
border-radius: 8px;
font-size: 16px;
font-weight: 600;
cursor: pointer;
transition: all 0.3s;
margin-bottom: 10px;
display: flex;
align-items: center;
justify-content: center;
gap: 10px;
}
.btn-primary {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
}
.btn-primary:hover {
transform: translateY(-2px);
box-shadow: 0 10px 20px rgba(102, 126, 234, 0.3);
}
.btn-primary:active {
transform: translateY(0);
}
.divider {
text-align: center;
margin: 30px 0 20px 0;
position: relative;
}
.divider::before {
content: '';
position: absolute;
top: 50%;
left: 0;
right: 0;
height: 1px;
background: #e0e0e0;
}
.divider span {
background: white;
padding: 0 15px;
color: #999;
font-size: 14px;
position: relative;
z-index: 1;
}
.quick-login {
margin-top: 20px;
}
.quick-login-title {
font-size: 14px;
color: #666;
margin-bottom: 15px;
text-align: center;
font-weight: 500;
}
.btn-test {
background: white;
color: #333;
border: 2px solid #e0e0e0;
font-size: 14px;
padding: 12px;
}
.btn-test:hover {
background: #f8f9fa;
border-color: #667eea;
color: #667eea;
}
.btn-test .role-badge {
display: inline-block;
padding: 3px 10px;
border-radius: 12px;
font-size: 12px;
font-weight: 600;
}
.btn-test.user .role-badge {
background: #e3f2fd;
color: #1976d2;
}
.btn-test.admin .role-badge {
background: #fff3e0;
color: #f57c00;
}
.btn-test.superadmin .role-badge {
background: #fce4ec;
color: #c2185b;
}
.footer-note {
margin-top: 25px;
text-align: center;
font-size: 12px;
color: #999;
line-height: 1.6;
}
.icon {
width: 20px;
height: 20px;
fill: currentColor;
}
@media (max-width: 480px) {
.login-container {
margin: 10px;
}
.system-title {
font-size: 24px;
}
.login-body {
padding: 30px 20px;
}
}
</style>
</head>
<body>
<div class="login-container">
<div class="login-header">
<div class="logo-container">
<img src="logo.svg" alt="System Logo">
</div>
<h1 class="system-title">那都AI寫的不要問我</h1>
<p class="system-subtitle">HR Position Management System</p>
</div>
<div class="login-body">
<form id="loginForm" onsubmit="handleLogin(event)">
<div class="form-group">
<label for="username">
<svg class="icon" viewBox="0 0 24 24" style="width: 16px; height: 16px; vertical-align: middle; margin-right: 5px;">
<path d="M12 12c2.21 0 4-1.79 4-4s-1.79-4-4-4-4 1.79-4 4 1.79 4 4 4zm0 2c-2.67 0-8 1.34-8 4v2h16v-2c0-2.66-5.33-4-8-4z"/>
</svg>
工號 / 帳號
</label>
<input type="text" id="username" name="username" placeholder="請輸入工號" required>
</div>
<div class="form-group">
<label for="password">
<svg class="icon" viewBox="0 0 24 24" style="width: 16px; height: 16px; vertical-align: middle; margin-right: 5px;">
<path d="M18 8h-1V6c0-2.76-2.24-5-5-5S7 3.24 7 6v2H6c-1.1 0-2 .9-2 2v10c0 1.1.9 2 2 2h12c1.1 0 2-.9 2-2V10c0-1.1-.9-2-2-2zm-6 9c-1.1 0-2-.9-2-2s.9-2 2-2 2 .9 2 2-.9 2-2 2zm3.1-9H8.9V6c0-1.71 1.39-3.1 3.1-3.1 1.71 0 3.1 1.39 3.1 3.1v2z"/>
</svg>
密碼
</label>
<input type="password" id="password" name="password" placeholder="請輸入密碼" required>
</div>
<button type="submit" class="btn btn-primary">
<svg class="icon" viewBox="0 0 24 24">
<path d="M10 17l5-5-5-5v10z" fill="currentColor"/>
</svg>
登入系統
</button>
</form>
<div class="divider">
<span>或使用測試帳號快速登入</span>
</div>
<div class="quick-login">
<p class="quick-login-title">選擇測試角色</p>
<button type="button" class="btn btn-test user" onclick="quickLogin('user')">
<svg class="icon" viewBox="0 0 24 24">
<path d="M12 12c2.21 0 4-1.79 4-4s-1.79-4-4-4-4 1.79-4 4 1.79 4 4 4zm0 2c-2.67 0-8 1.34-8 4v2h16v-2c0-2.66-5.33-4-8-4z"/>
</svg>
<span class="role-badge">一般使用者</span>
<span style="flex: 1; text-align: left; margin-left: 10px; font-size: 13px; color: #666;">A003 / employee</span>
</button>
<button type="button" class="btn btn-test admin" onclick="quickLogin('admin')">
<svg class="icon" viewBox="0 0 24 24">
<path d="M12 1L3 5v6c0 5.55 3.84 10.74 9 12 5.16-1.26 9-6.45 9-12V5l-9-4zm0 10.99h7c-.53 4.12-3.28 7.79-7 8.94V12H5V6.3l7-3.11v8.8z"/>
</svg>
<span class="role-badge">管理者</span>
<span style="flex: 1; text-align: left; margin-left: 10px; font-size: 13px; color: #666;">A002 / hr_manager</span>
</button>
<button type="button" class="btn btn-test superadmin" onclick="quickLogin('superadmin')">
<svg class="icon" viewBox="0 0 24 24">
<path d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z"/>
</svg>
<span class="role-badge">最高管理者</span>
<span style="flex: 1; text-align: left; margin-left: 10px; font-size: 13px; color: #666;">A001 / admin</span>
</button>
</div>
<div class="footer-note">
這個系統真的都是 AI 寫的<br>
如果有問題... 那就是 AI 的問題 ¯\_(ツ)_/¯
</div>
</div>
</div>
<script>
// 測試帳號資料
const testAccounts = {
'user': {
username: 'A003',
password: 'employee',
role: 'user',
name: '一般員工'
},
'admin': {
username: 'A002',
password: 'hr_manager',
role: 'admin',
name: '人資主管'
},
'superadmin': {
username: 'A001',
password: 'admin',
role: 'superadmin',
name: '系統管理員'
}
};
// 處理一般登入
function handleLogin(event) {
event.preventDefault();
const username = document.getElementById('username').value;
const password = document.getElementById('password').value;
// 簡單的驗證邏輯(實際應該調用後端 API
let validUser = null;
for (const [key, account] of Object.entries(testAccounts)) {
if (account.username === username && account.password === password) {
validUser = account;
break;
}
}
if (validUser) {
// 儲存登入資訊
localStorage.setItem('currentUser', JSON.stringify(validUser));
// 顯示登入成功訊息
showLoginSuccess(validUser.name, validUser.role);
// 延遲跳轉到主頁面
setTimeout(() => {
window.location.href = 'index.html';
}, 1500);
} else {
alert('帳號或密碼錯誤!\n\n提示您可以使用下方的測試帳號快速登入');
}
}
// 快速登入
function quickLogin(role) {
const account = testAccounts[role];
// 填入表單
document.getElementById('username').value = account.username;
document.getElementById('password').value = account.password;
// 儲存登入資訊
localStorage.setItem('currentUser', JSON.stringify(account));
// 顯示登入成功訊息
showLoginSuccess(account.name, account.role);
// 延遲跳轉到主頁面
setTimeout(() => {
window.location.href = 'index.html';
}, 1500);
}
// 顯示登入成功訊息
function showLoginSuccess(name, role) {
const roleNames = {
'user': '一般使用者',
'admin': '管理者',
'superadmin': '最高管理者'
};
// 創建成功訊息元素
const successDiv = document.createElement('div');
successDiv.style.cssText = `
position: fixed;
top: 20px;
right: 20px;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
padding: 20px 30px;
border-radius: 10px;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.3);
z-index: 9999;
animation: slideInRight 0.5s ease-out;
font-size: 16px;
`;
successDiv.innerHTML = `
<div style="display: flex; align-items: center; gap: 15px;">
<svg viewBox="0 0 24 24" style="width: 32px; height: 32px; fill: white;">
<path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-2 15l-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z"/>
</svg>
<div>
<div style="font-weight: 600; margin-bottom: 5px;">登入成功!</div>
<div style="font-size: 14px; opacity: 0.9;">${name} (${roleNames[role]})</div>
</div>
</div>
`;
// 添加動畫樣式
const style = document.createElement('style');
style.textContent = `
@keyframes slideInRight {
from {
transform: translateX(400px);
opacity: 0;
}
to {
transform: translateX(0);
opacity: 1;
}
}
`;
document.head.appendChild(style);
document.body.appendChild(successDiv);
}
// 頁面載入時檢查是否已登入
window.addEventListener('DOMContentLoaded', function() {
const currentUser = localStorage.getItem('currentUser');
if (currentUser) {
const user = JSON.parse(currentUser);
// 如果已經登入,詢問是否要繼續使用或重新登入
const confirm = window.confirm(`偵測到您已經以「${user.name}」身份登入。\n\n是否要繼續使用此帳號?\n(取消將重新登入)`);
if (confirm) {
window.location.href = 'index.html';
} else {
localStorage.removeItem('currentUser');
}
}
});
</script>
</body>
</html>