将登陆页面和主页面合二为一

This commit is contained in:
jinchao 2025-05-09 16:29:50 +08:00
parent bd78b99b0a
commit 9fed446a6f
8 changed files with 875 additions and 3493 deletions

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,439 @@
class AuthComponent extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: 'open' });
}
connectedCallback() {
this.render();
this.setupEventListeners();
}
render() {
this.shadowRoot.innerHTML = `
<style>
:host {
display: block;
width: 100%;
height: 100vh;
display: flex;
align-items: center;
justify-content: center;
background: #f5f7fa;
}
.container {
width: 90%;
max-width: 1200px;
min-height: 600px;
background: white;
border-radius: 20px;
box-shadow: 0 15px 35px rgba(0, 0, 0, 0.1);
display: flex;
overflow: hidden;
}
.content {
flex: 1;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
padding: 60px;
display: flex;
align-items: center;
justify-content: center;
color: white;
}
.welcome-section {
text-align: center;
}
.welcome-section h1 {
font-size: 3.5rem;
margin-bottom: 2rem;
font-weight: 700;
text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.2);
}
.welcome-text {
font-size: 1.2rem;
line-height: 1.8;
opacity: 0.9;
}
.auth-container {
width: 500px;
background: white;
padding: 40px;
display: flex;
flex-direction: column;
}
.form-toggle {
display: flex;
margin-bottom: 30px;
border-radius: 8px;
overflow: hidden;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
}
.toggle-btn {
flex: 1;
padding: 15px;
border: none;
background: #f0f0f0;
cursor: pointer;
font-size: 1rem;
font-weight: 500;
transition: all 0.3s ease;
}
.toggle-btn.active {
background: #667eea;
color: white;
}
.form-panel {
display: none;
animation: fadeIn 0.3s ease;
margin-top: 10px;
}
@keyframes fadeIn {
from { opacity: 0; transform: translateY(10px); }
to { opacity: 1; transform: translateY(0); }
}
.form-panel.active {
display: block;
}
.form-group {
margin-bottom: 20px;
}
.form-row {
display: flex;
gap: 20px;
margin-bottom: 20px;
}
.form-row .form-group {
flex: 1;
margin-bottom: 0;
}
label {
display: block;
margin-bottom: 8px;
color: #555;
font-weight: 500;
}
input {
width: 100%;
padding: 12px 15px;
border: 2px solid #eee;
border-radius: 8px;
font-size: 1rem;
transition: all 0.3s ease;
}
input:focus {
border-color: #667eea;
outline: none;
box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);
}
.password-input {
position: relative;
display: flex;
align-items: center;
}
.password-input input {
padding-right: 40px;
}
.toggle-password {
position: absolute;
right: 10px;
top: 50%;
transform: translateY(-50%);
background: none;
border: none;
padding: 5px;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
transition: opacity 0.3s ease;
}
.toggle-password:hover {
opacity: 0.7;
}
.toggle-password:focus {
outline: none;
}
.visibility-icon {
width: 20px;
height: 20px;
object-fit: contain;
}
.remember-me {
display: flex;
align-items: center;
gap: 8px;
margin-bottom: 20px;
}
.remember-me input {
width: auto;
}
.submit-btn {
width: 100%;
padding: 15px;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
border: none;
border-radius: 8px;
color: white;
font-size: 1rem;
font-weight: 500;
cursor: pointer;
transition: all 0.3s ease;
}
.submit-btn:hover {
transform: translateY(-2px);
box-shadow: 0 5px 15px rgba(102, 126, 234, 0.4);
}
.toast {
position: fixed;
top: 20px;
right: 20px;
background: rgba(0, 0, 0, 0.8);
color: white;
padding: 12px 24px;
border-radius: 8px;
font-size: 14px;
z-index: 10000;
opacity: 0;
transition: opacity 0.3s ease;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
}
.toast.show {
opacity: 1;
}
@media (max-width: 1024px) {
.container {
flex-direction: column;
min-height: auto;
margin: 20px;
}
.content {
padding: 40px;
}
.auth-container {
width: 100%;
}
}
@media (max-width: 480px) {
.container {
margin: 0;
border-radius: 0;
}
.auth-container {
padding: 20px;
}
.welcome-section h1 {
font-size: 2.5rem;
}
.form-row {
flex-direction: column;
gap: 15px;
}
}
</style>
<div class="container">
<div class="content">
<div class="welcome-section">
<h1>XNSim</h1>
<div class="welcome-text">
<p>欢迎使用XNSim仿真平台</p>
<p>高效专业的仿真解决方案</p>
</div>
</div>
</div>
<div class="auth-container">
<div class="form-toggle">
<button id="loginToggle" class="toggle-btn active">登录</button>
<button id="registerToggle" class="toggle-btn">注册</button>
</div>
<div id="loginForm" class="form-panel active">
<form id="loginFormElement">
<div class="form-group">
<label for="loginUsername">用户名</label>
<input type="text" id="loginUsername" name="username" placeholder="请输入用户名" required>
</div>
<div class="form-group">
<label for="loginPassword">密码</label>
<div class="password-input">
<input type="password" id="loginPassword" name="password" placeholder="请输入密码" required>
<button type="button" class="toggle-password">
<img src="assets/icons/png/invisiable_b.png" alt="显示/隐藏密码" class="visibility-icon">
</button>
</div>
</div>
<div class="form-group remember-me">
<input type="checkbox" id="remember" name="remember">
<label for="remember">记住我</label>
</div>
<button type="submit" class="submit-btn">登录</button>
</form>
</div>
<div id="registerForm" class="form-panel">
<form id="registerFormElement">
<div class="form-row">
<div class="form-group">
<label for="regUsername">用户名 *</label>
<input type="text" id="regUsername" name="username" placeholder="请输入用户名" required>
</div>
<div class="form-group">
<label for="fullName">姓名选填</label>
<input type="text" id="fullName" name="fullName" placeholder="请输入姓名">
</div>
</div>
<div class="form-row">
<div class="form-group">
<label for="regPassword">密码 *</label>
<div class="password-input">
<input type="password" id="regPassword" name="password" placeholder="请输入密码" required>
<button type="button" class="toggle-password">
<img src="assets/icons/png/invisiable_b.png" alt="显示/隐藏密码" class="visibility-icon">
</button>
</div>
</div>
<div class="form-group">
<label for="regConfirmPassword">确认密码 *</label>
<div class="password-input">
<input type="password" id="regConfirmPassword" name="confirmPassword" placeholder="请再次输入密码" required>
<button type="button" class="toggle-password">
<img src="assets/icons/png/invisiable_b.png" alt="显示/隐藏密码" class="visibility-icon">
</button>
</div>
</div>
</div>
<div class="form-row">
<div class="form-group">
<label for="phone">电话选填</label>
<input type="tel" id="phone" name="phone" placeholder="请输入电话号码">
</div>
<div class="form-group">
<label for="email">邮箱选填</label>
<input type="email" id="email" name="email" placeholder="请输入邮箱">
</div>
</div>
<div class="form-row">
<div class="form-group">
<label for="department">部门选填</label>
<input type="text" id="department" name="department" placeholder="请输入所属部门">
</div>
<div class="form-group">
<label for="position">职位选填</label>
<input type="text" id="position" name="position" placeholder="请输入职位">
</div>
</div>
<button type="submit" class="submit-btn">注册</button>
</form>
</div>
</div>
</div>
`;
}
setupEventListeners() {
const loginToggle = this.shadowRoot.getElementById('loginToggle');
const registerToggle = this.shadowRoot.getElementById('registerToggle');
const loginForm = this.shadowRoot.getElementById('loginForm');
const registerForm = this.shadowRoot.getElementById('registerForm');
const loginFormElement = this.shadowRoot.getElementById('loginFormElement');
const registerFormElement = this.shadowRoot.getElementById('registerFormElement');
const togglePasswordButtons = this.shadowRoot.querySelectorAll('.toggle-password');
// 创建Toast提示函数
const showToast = (message) => {
const toast = document.createElement('div');
toast.className = 'toast';
toast.textContent = message;
this.shadowRoot.appendChild(toast);
setTimeout(() => toast.classList.add('show'), 10);
setTimeout(() => {
toast.classList.remove('show');
setTimeout(() => toast.remove(), 300);
}, 2000);
};
// 切换表单显示
loginToggle.addEventListener('click', () => {
loginToggle.classList.add('active');
registerToggle.classList.remove('active');
loginForm.classList.add('active');
registerForm.classList.remove('active');
});
registerToggle.addEventListener('click', () => {
registerToggle.classList.add('active');
loginToggle.classList.remove('active');
registerForm.classList.add('active');
loginForm.classList.remove('active');
});
// 密码显示/隐藏
togglePasswordButtons.forEach(button => {
button.addEventListener('click', () => {
const input = button.parentElement.querySelector('input');
const icon = button.querySelector('img');
if (input.type === 'password') {
input.type = 'text';
icon.src = 'assets/icons/png/visiable_b.png';
} else {
input.type = 'password';
icon.src = 'assets/icons/png/invisiable_b.png';
}
});
});
// 表单提交
loginFormElement.addEventListener('submit', (e) => {
e.preventDefault();
const username = this.shadowRoot.getElementById('loginUsername').value;
const password = this.shadowRoot.getElementById('loginPassword').value;
const remember = this.shadowRoot.getElementById('remember').checked;
if (!username || !password) {
showToast('请输入用户名和密码');
return;
}
this.dispatchEvent(new CustomEvent('login', {
detail: { username, password, remember },
bubbles: true,
composed: true
}));
});
registerFormElement.addEventListener('submit', (e) => {
e.preventDefault();
const username = this.shadowRoot.getElementById('regUsername').value;
const password = this.shadowRoot.getElementById('regPassword').value;
const confirmPassword = this.shadowRoot.getElementById('regConfirmPassword').value;
if (!username || !password) {
showToast('用户名和密码为必填项');
return;
}
if (password !== confirmPassword) {
showToast('两次输入的密码不一致');
return;
}
const userInfo = {
full_name: this.shadowRoot.getElementById('fullName').value.trim() || '',
phone: this.shadowRoot.getElementById('phone').value.trim() || '',
email: this.shadowRoot.getElementById('email').value.trim() || '',
department: this.shadowRoot.getElementById('department').value.trim() || '',
position: this.shadowRoot.getElementById('position').value.trim() || '',
access_level: 1
};
this.dispatchEvent(new CustomEvent('register', {
detail: { username, password, userInfo },
bubbles: true,
composed: true
}));
});
}
}
customElements.define('auth-component', AuthComponent);

View File

@ -82,6 +82,10 @@ class UserInfo extends HTMLElement {
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
display: none;
z-index: 1000;
min-width: 150px;
max-width: 250px;
white-space: nowrap;
overflow: hidden;
}
.user-dropdown.active .user-dropdown-menu {
@ -95,6 +99,9 @@ class UserInfo extends HTMLElement {
cursor: pointer;
transition: background-color 0.3s;
gap: 8px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.dropdown-item:hover {
@ -159,6 +166,11 @@ class UserInfo extends HTMLElement {
border-style: solid;
border-color: transparent transparent rgba(0, 0, 0, 0.8) transparent;
}
.dropdown-item span {
overflow: hidden;
text-overflow: ellipsis;
}
</style>
<div class="user-dropdown">
<div class="user-dropdown-toggle">
@ -242,13 +254,15 @@ class UserInfo extends HTMLElement {
try {
const userInfoStr = localStorage.getItem('userInfo');
if (!userInfoStr) {
window.location.href = 'index.html';
document.getElementById('authContainer').style.display = 'block';
document.getElementById('mainContainer').style.display = 'none';
return;
}
userInfo = JSON.parse(userInfoStr);
} catch (error) {
console.error('解析用户信息失败:', error);
window.location.href = 'index.html';
document.getElementById('authContainer').style.display = 'block';
document.getElementById('mainContainer').style.display = 'none';
return;
}
@ -314,7 +328,8 @@ class UserInfo extends HTMLElement {
logout() {
localStorage.removeItem('userInfo');
window.location.href = 'index.html';
document.getElementById('authContainer').style.display = 'block';
document.getElementById('mainContainer').style.display = 'none';
}
}

View File

@ -1,111 +0,0 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>XNSim - 登录/注册</title>
<link rel="icon" type="image/png" href="assets/icons/XNSim.png">
<link rel="shortcut icon" type="image/png" href="assets/icons/XNSim.png">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
<link rel="stylesheet" href="style.css">
</head>
<body>
<div class="container">
<div class="content">
<div class="welcome-section">
<h1>XNSim</h1>
<div class="welcome-text">
<p>欢迎使用XNSim仿真平台</p>
<p>高效、专业的仿真解决方案</p>
</div>
</div>
</div>
<div class="auth-container">
<div class="form-toggle">
<button id="loginToggle" class="toggle-btn active">登录</button>
<button id="registerToggle" class="toggle-btn">注册</button>
</div>
<div id="loginForm" class="form-panel active">
<form>
<div class="form-group">
<label for="loginUsername">用户名</label>
<input type="text" id="loginUsername" name="username" placeholder="请输入用户名" required>
</div>
<div class="form-group">
<label for="loginPassword">密码</label>
<div class="password-input">
<input type="password" id="loginPassword" name="password" placeholder="请输入密码" required>
<button type="button" class="toggle-password">
<img src="assets/icons/png/invisiable_b.png" alt="显示/隐藏密码" class="visibility-icon">
</button>
</div>
</div>
<div class="form-group remember-me">
<input type="checkbox" id="remember" name="remember">
<label for="remember">记住我</label>
</div>
<button type="submit" class="submit-btn">登录</button>
</form>
</div>
<div id="registerForm" class="form-panel">
<form>
<div class="form-row">
<div class="form-group">
<label for="regUsername">用户名 *</label>
<input type="text" id="regUsername" name="username" placeholder="请输入用户名" required>
</div>
<div class="form-group">
<label for="fullName">姓名(选填)</label>
<input type="text" id="fullName" name="fullName" placeholder="请输入姓名">
</div>
</div>
<div class="form-row">
<div class="form-group">
<label for="regPassword">密码 *</label>
<div class="password-input">
<input type="password" id="regPassword" name="password" placeholder="请输入密码" required>
<button type="button" class="toggle-password">
<img src="assets/icons/png/invisiable_b.png" alt="显示/隐藏密码" class="visibility-icon">
</button>
</div>
</div>
<div class="form-group">
<label for="regConfirmPassword">确认密码 *</label>
<div class="password-input">
<input type="password" id="regConfirmPassword" name="confirmPassword" placeholder="请再次输入密码" required>
<button type="button" class="toggle-password">
<img src="assets/icons/png/invisiable_b.png" alt="显示/隐藏密码" class="visibility-icon">
</button>
</div>
</div>
</div>
<div class="form-row">
<div class="form-group">
<label for="phone">电话(选填)</label>
<input type="tel" id="phone" name="phone" placeholder="请输入电话号码">
</div>
<div class="form-group">
<label for="email">邮箱(选填)</label>
<input type="email" id="email" name="email" placeholder="请输入邮箱">
</div>
</div>
<div class="form-row">
<div class="form-group">
<label for="department">部门(选填)</label>
<input type="text" id="department" name="department" placeholder="请输入所属部门">
</div>
<div class="form-group">
<label for="position">职位(选填)</label>
<input type="text" id="position" name="position" placeholder="请输入职位">
</div>
</div>
<button type="submit" class="submit-btn">注册</button>
</form>
</div>
</div>
</div>
<script src="script.js"></script>
</body>
</html>

View File

@ -7,6 +7,7 @@
<link rel="icon" type="image/png" href="assets/icons/XNSim.png">
<link rel="shortcut icon" type="image/png" href="assets/icons/XNSim.png">
<link rel="stylesheet" href="style.css">
<script src="components/auth-component.js"></script>
<script src="components/main-toolbar.js"></script>
<script src="components/sub-toolbar.js"></script>
<script src="components/user-info.js"></script>
@ -183,10 +184,39 @@
flex: 1;
overflow: hidden;
}
/* Toast 提示样式 */
.toast {
position: fixed;
top: 20px;
right: 20px;
background: rgba(0, 0, 0, 0.8);
color: white;
padding: 12px 24px;
border-radius: 8px;
font-size: 14px;
z-index: 10000;
opacity: 0;
transition: opacity 0.3s ease;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
}
.toast.show {
opacity: 1;
}
#authContainer, #mainContainer {
display: none;
}
#authContainer.visible, #mainContainer.visible {
display: block;
}
</style>
</head>
<body>
<div class="main-container">
<div id="authContainer">
<auth-component></auth-component>
</div>
<div id="mainContainer" class="main-container">
<div class="content-wrapper">
<main-toolbar></main-toolbar>
<sub-toolbar></sub-toolbar>
@ -210,10 +240,44 @@
</div>
<script>
document.addEventListener('DOMContentLoaded', function() {
// Toast 提示函数
function showToast(message) {
const toast = document.createElement('div');
toast.className = 'toast';
toast.textContent = message;
document.body.appendChild(toast);
setTimeout(() => toast.classList.add('show'), 10);
setTimeout(() => {
toast.classList.remove('show');
setTimeout(() => toast.remove(), 300);
}, 2000);
}
document.addEventListener('DOMContentLoaded', () => {
const authComponent = document.querySelector('auth-component');
const authContainer = document.getElementById('authContainer');
const mainContainer = document.getElementById('mainContainer');
const tabsContainer = document.querySelector('tabs-container');
const contentArea = document.querySelector('content-area');
// 检查是否已登录
const checkAuth = () => {
const userInfo = localStorage.getItem('userInfo');
if (userInfo) {
authContainer.classList.remove('visible');
mainContainer.classList.add('visible');
// 初始化主页面
initializeMainPage();
} else {
authContainer.classList.add('visible');
mainContainer.classList.remove('visible');
}
};
// 初始化主页面
function initializeMainPage() {
// 初始化时创建概览标签页并加载概览内容
tabsContainer.createTab('overview', '概览', 'dashboard', '主页', 'home');
contentArea.loadContent('overview');
@ -269,7 +333,7 @@
// 清除关闭标签页的内容缓存
contentArea.clearCache(id);
});
});
}
function handleMenuAction(action) {
const contentArea = document.querySelector('content-area');
@ -283,6 +347,17 @@
tabsContainer.createTab('users', '用户管理', 'users', '系统', 'system');
contentArea.loadContent('users');
break;
case 'logout':
// 清除所有用户相关数据
localStorage.removeItem('userInfo');
localStorage.removeItem('authToken');
// 清除所有标签页
tabsContainer.clearAllTabs();
// 显示退出成功提示
showToast('已安全退出登录');
// 重新检查认证状态,这会触发返回登录页面
checkAuth();
break;
}
}
@ -488,6 +563,68 @@
return iconMap[title] || parentTool || 'con';
}
// 监听登录事件
authComponent.addEventListener('login', async (e) => {
const data = e.detail;
try {
const response = await fetch('/api/login', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(data)
});
const result = await response.json();
if (result.success) {
localStorage.setItem('userInfo', JSON.stringify(result.user));
showToast(`欢迎回来,${result.user.username}`);
authContainer.classList.remove('visible');
mainContainer.classList.add('visible');
// 初始化主页面
initializeMainPage();
} else {
showToast(result.message || '登录失败,请检查用户名和密码');
}
} catch (error) {
console.error('登录错误:', error);
showToast('登录过程中发生错误');
}
});
// 监听注册事件
authComponent.addEventListener('register', async (e) => {
const data = e.detail;
try {
const response = await fetch('/api/register', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(data)
});
const result = await response.json();
if (result.success) {
showToast('注册成功!请登录');
// 切换到登录表单
const loginToggle = authComponent.shadowRoot.getElementById('loginToggle');
loginToggle.click();
} else {
showToast(result.message || '注册失败,请稍后重试');
}
} catch (error) {
console.error('注册错误:', error);
showToast('注册过程中发生错误');
}
});
// 初始检查认证状态
checkAuth();
});
</script>
</body>
</html>

View File

@ -1,204 +0,0 @@
document.addEventListener('DOMContentLoaded', function() {
// 获取DOM元素
const loginForm = document.getElementById('loginForm');
const registerForm = document.getElementById('registerForm');
const loginToggle = document.getElementById('loginToggle');
const registerToggle = document.getElementById('registerToggle');
// 处理密码可见性切换
document.querySelectorAll('.toggle-password').forEach(button => {
button.addEventListener('click', function() {
const input = this.previousElementSibling;
const icon = this.querySelector('.visibility-icon');
// 切换密码可见性
if (input.type === 'password') {
input.type = 'text';
icon.src = 'assets/icons/png/visiable_b.png';
} else {
input.type = 'password';
icon.src = 'assets/icons/png/invisiable_b.png';
}
});
});
// 创建Toast提示函数
function showToast(message) {
const toast = document.createElement('div');
toast.className = 'toast';
toast.textContent = message;
document.body.appendChild(toast);
setTimeout(() => toast.classList.add('show'), 10);
setTimeout(() => {
toast.classList.remove('show');
setTimeout(() => toast.remove(), 300);
}, 2000);
}
// 添加Toast样式
const style = document.createElement('style');
style.textContent = `
.toast {
position: fixed;
top: 20px;
right: 20px;
background: rgba(0, 0, 0, 0.8);
color: white;
padding: 12px 24px;
border-radius: 8px;
font-size: 14px;
z-index: 10000;
opacity: 0;
transition: opacity 0.3s ease;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
}
.toast.show {
opacity: 1;
}
`;
document.head.appendChild(style);
// 处理表单切换
if (loginToggle && registerToggle) {
loginToggle.addEventListener('click', () => {
loginToggle.classList.add('active');
registerToggle.classList.remove('active');
loginForm.classList.add('active');
registerForm.classList.remove('active');
});
registerToggle.addEventListener('click', () => {
registerToggle.classList.add('active');
loginToggle.classList.remove('active');
registerForm.classList.add('active');
loginForm.classList.remove('active');
});
}
// 处理登录表单提交
if (loginForm) {
loginForm.querySelector('form').addEventListener('submit', async (e) => {
e.preventDefault();
const username = document.getElementById('loginUsername').value;
const password = document.getElementById('loginPassword').value;
const remember = document.getElementById('remember').checked;
if (!username || !password) {
showToast('请输入用户名和密码');
return;
}
try {
const response = await fetch('/api/login', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
username,
password,
remember
})
});
const data = await response.json();
if (data.success) {
localStorage.setItem('userInfo', JSON.stringify(data.user));
showToast(`欢迎回来,${data.user.username}`);
setTimeout(() => {
window.location.href = 'main.html';
}, 1000);
} else {
showToast(data.message || '登录失败,请检查用户名和密码');
}
} catch (error) {
console.error('登录请求错误:', error);
showToast('登录请求失败,请稍后再试');
}
});
}
// 处理注册表单提交
if (registerForm) {
registerForm.querySelector('form').addEventListener('submit', async (e) => {
e.preventDefault();
const username = document.getElementById('regUsername').value;
const password = document.getElementById('regPassword').value;
const confirmPassword = document.getElementById('regConfirmPassword').value;
if (!username || !password) {
showToast('用户名和密码为必填项');
return;
}
if (password !== confirmPassword) {
showToast('两次输入的密码不一致');
return;
}
const userInfo = {
full_name: document.getElementById('fullName').value.trim() || '',
phone: document.getElementById('phone').value.trim() || '',
email: document.getElementById('email').value.trim() || '',
department: document.getElementById('department').value.trim() || '',
position: document.getElementById('position').value.trim() || '',
access_level: 1
};
try {
const response = await fetch('/api/register', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
username,
password,
userInfo
})
});
const data = await response.json();
if (data.success) {
showToast('注册成功!请登录');
loginToggle.click();
e.target.reset();
} else {
let errorMessage;
switch (data.userId) {
case -1:
errorMessage = '注册失败:系统错误';
break;
case -2:
errorMessage = '注册失败:用户名已存在';
break;
case -3:
errorMessage = '注册失败:无效的用户信息格式';
break;
case -4:
errorMessage = '注册失败:用户名不能为空';
break;
case -5:
errorMessage = '注册失败:密码不能为空';
break;
case -6:
errorMessage = '注册失败:无效的权限级别';
break;
default:
errorMessage = data.message || '注册失败,请稍后重试';
}
showToast(errorMessage);
}
} catch (error) {
console.error('注册请求失败:', error);
showToast('注册请求失败,请稍后重试');
}
});
}
});

View File

@ -37,6 +37,13 @@ if (!xnCorePath) {
// 中间件
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
// 根路径直接返回main.html
app.get('/', (req, res) => {
res.sendFile(path.join(__dirname, 'main.html'));
});
// 静态文件服务 - 放在根路径处理之后
app.use(express.static(__dirname));
// 监听进程退出事件
@ -80,11 +87,6 @@ app.use('/api/qa', qaRoutes);
app.use('/api/todos', todoRoutes);
app.use('/api', userRoutes);
// 主页路由
app.get('/', (req, res) => {
res.sendFile(path.join(__dirname, 'main.html'));
});
// 接口配置页面路由
app.get('/interface-config', (req, res) => {
res.sendFile(path.join(__dirname, 'interface-config.html'));

View File

@ -1,776 +0,0 @@
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
}
.container {
width: 90%;
max-width: 1200px;
min-height: 600px;
background: white;
border-radius: 20px;
box-shadow: 0 15px 35px rgba(0, 0, 0, 0.1);
display: flex;
overflow: hidden;
}
.content {
flex: 1;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
padding: 60px;
display: flex;
align-items: center;
justify-content: center;
color: white;
}
.welcome-section {
text-align: center;
}
.welcome-section h1 {
font-size: 3.5rem;
margin-bottom: 2rem;
font-weight: 700;
text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.2);
}
.welcome-text {
font-size: 1.2rem;
line-height: 1.8;
opacity: 0.9;
}
.auth-container {
width: 500px;
background: white;
padding: 40px;
display: flex;
flex-direction: column;
}
.form-toggle {
display: flex;
margin-bottom: 30px;
border-radius: 8px;
overflow: hidden;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
}
.toggle-btn {
flex: 1;
padding: 15px;
border: none;
background: #f0f0f0;
cursor: pointer;
font-size: 1rem;
font-weight: 500;
transition: all 0.3s ease;
}
.toggle-btn.active {
background: #667eea;
color: white;
}
.form-panel {
display: none;
animation: fadeIn 0.3s ease;
margin-top: 10px;
}
@keyframes fadeIn {
from { opacity: 0; transform: translateY(10px); }
to { opacity: 1; transform: translateY(0); }
}
.form-panel.active {
display: block;
}
.form-panel h2 {
font-size: 1.8rem;
color: #333;
margin-bottom: 25px;
text-align: center;
}
.form-row {
display: flex;
gap: 20px;
margin-bottom: 20px;
}
.form-row .form-group {
flex: 1;
margin-bottom: 0;
}
.form-group {
margin-bottom: 20px;
}
.form-group label {
display: block;
margin-bottom: 8px;
color: #555;
font-weight: 500;
}
.form-group input {
width: 100%;
padding: 12px 15px;
border: 2px solid #eee;
border-radius: 8px;
font-size: 1rem;
transition: all 0.3s ease;
}
.form-group input:focus {
border-color: #667eea;
outline: none;
box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);
}
.remember-me {
display: flex;
align-items: center;
gap: 8px;
margin-bottom: 20px;
}
.remember-me input {
width: auto;
}
.submit-btn {
width: 100%;
padding: 15px;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
border: none;
border-radius: 8px;
color: white;
font-size: 1rem;
font-weight: 500;
cursor: pointer;
transition: all 0.3s ease;
}
.submit-btn:hover {
transform: translateY(-2px);
box-shadow: 0 5px 15px rgba(102, 126, 234, 0.4);
}
@media (max-width: 1024px) {
.container {
flex-direction: column;
min-height: auto;
margin: 20px;
}
.content {
padding: 40px;
}
.auth-container {
width: 100%;
}
}
@media (max-width: 480px) {
.container {
margin: 0;
border-radius: 0;
}
.auth-container {
padding: 20px;
}
.welcome-section h1 {
font-size: 2.5rem;
}
}
.main-container {
width: 100%;
min-height: 100vh;
display: flex;
flex-direction: column;
}
.main-nav {
display: none;
}
.logo {
display: none;
}
.user-info {
display: none;
}
main {
flex: 1;
padding: 2rem;
background-color: #f5f5f5;
}
.content-section {
background-color: white;
border-radius: 8px;
padding: 2rem;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
}
.content-section h2 {
color: #333;
margin-bottom: 1.5rem;
padding-bottom: 0.5rem;
border-bottom: 2px solid #f0f0f0;
}
.admin-features,
.user-features {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 1rem;
margin-top: 1rem;
}
.feature-btn {
padding: 1rem;
background: linear-gradient(135deg, #6e8efb, #a777e3);
border: none;
border-radius: 8px;
color: white;
font-size: 1rem;
cursor: pointer;
transition: all 0.3s ease;
text-align: center;
}
.feature-btn:hover {
transform: translateY(-2px);
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.2);
}
@media (max-width: 768px) {
.main-nav {
padding: 1rem;
flex-direction: column;
gap: 1rem;
text-align: center;
}
.admin-features,
.user-features {
grid-template-columns: 1fr;
}
main {
padding: 1rem;
}
}
/* 内容包装器 */
.content-wrapper {
display: flex;
height: 100vh; /* 修改为全屏高度 */
}
/* 主工具栏样式 */
.main-toolbar {
width: 80px;
background-color: #2c3e50;
display: flex;
flex-direction: column;
align-items: center;
padding: 20px 0;
transition: width 0.3s ease;
}
.main-toolbar.collapsed {
width: 50px;
}
.main-toolbar.collapsed .tool-item span {
display: none;
}
.main-toolbar.collapsed .logo-image {
width: 30px;
height: 30px;
}
.tools-container {
flex: 1;
width: 100%;
display: flex;
flex-direction: column;
align-items: center;
}
.toolbar-toggle {
position: absolute;
right: -12px;
top: 50%;
transform: translateY(-50%);
width: 24px;
height: 24px;
background-color: #34495e;
border-radius: 50%;
display: flex;
justify-content: center;
align-items: center;
cursor: pointer;
color: #fff;
box-shadow: 0 2px 4px rgba(0,0,0,0.2);
z-index: 100;
}
.toolbar-toggle i {
transition: transform 0.3s ease;
}
.sub-toolbar.collapsed .toolbar-toggle i {
transform: rotate(180deg);
}
.toolbar-toggle:hover {
background-color: #2c3e50;
}
/* 调整子工具栏的过渡效果 */
.sub-toolbar {
width: 200px;
background-color: #34495e;
position: relative;
transition: all 0.3s ease;
display: flex;
}
.sub-toolbar.collapsed {
width: 0;
}
.sub-toolbar-content {
min-width: 200px;
padding: 20px 0;
opacity: 1;
transition: opacity 0.2s ease;
}
.sub-toolbar.collapsed .sub-toolbar-content {
opacity: 0;
}
.tool-item {
width: 100%;
display: flex;
flex-direction: column;
align-items: center;
padding: 15px 0;
color: #ecf0f1;
cursor: pointer;
transition: all 0.3s ease;
}
.tool-item i {
font-size: 24px;
margin-bottom: 5px;
}
.tool-item span {
font-size: 12px;
text-align: center;
}
.tool-item:hover {
background-color: #34495e;
}
.tool-item.active {
background-color: #3498db;
}
/* 子工具栏样式 */
.sub-menu {
display: none;
white-space: nowrap;
}
.sub-menu.active {
display: block;
}
.sub-item {
padding: 12px 20px;
color: #ecf0f1;
cursor: pointer;
transition: all 0.3s ease;
}
.sub-item:hover {
background-color: #2c3e50;
}
.sub-item.active {
background-color: #3498db;
}
/* 主要内容区域 */
.main-content {
flex: 1;
padding: 20px;
background-color: #f5f5f5;
overflow-y: auto;
}
#contentArea {
background-color: white;
border-radius: 8px;
padding: 20px;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
}
/* 响应式设计 */
@media (max-width: 768px) {
.content-wrapper {
flex-direction: column;
}
.main-toolbar {
width: 100%;
height: 60px;
flex-direction: row;
justify-content: space-around;
padding: 0;
}
.tool-item {
padding: 10px;
}
.sub-toolbar {
width: 100%;
height: auto;
max-height: 200px;
}
.form-row {
flex-direction: column;
gap: 15px;
}
.form-row .form-group {
margin-bottom: 0;
}
.auth-container {
padding: 20px;
}
}
.logo-container {
width: 100%;
padding: 8px;
margin-bottom: 10px;
display: flex;
justify-content: center;
align-items: center;
}
.logo-image {
width: 50px;
height: 50px;
object-fit: contain;
}
.content-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 12px 20px;
background-color: white;
border-bottom: 1px solid #e0e0e0;
}
.breadcrumb {
display: flex;
align-items: center;
gap: 8px;
color: #666;
}
.breadcrumb i {
font-size: 14px;
color: #999;
}
/* 用户下拉菜单 */
.user-dropdown {
position: relative;
}
.user-dropdown-toggle {
display: flex;
align-items: center;
gap: 8px;
cursor: pointer;
padding: 6px 12px;
border-radius: 4px;
transition: background-color 0.2s ease;
}
.user-dropdown-toggle:hover {
background-color: #f5f5f5;
}
.user-dropdown-toggle i {
font-size: 12px;
color: #666;
transition: transform 0.2s ease;
}
.user-dropdown.active .user-dropdown-toggle i {
transform: rotate(180deg);
}
.user-dropdown-menu {
position: absolute;
top: 100%;
right: 0;
margin-top: 8px;
background-color: white;
border-radius: 4px;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
min-width: 160px;
display: none;
z-index: 1000;
}
.user-dropdown.active .user-dropdown-menu {
display: block;
}
.dropdown-item {
padding: 10px 16px;
display: flex;
align-items: center;
gap: 8px;
color: #333;
cursor: pointer;
transition: background-color 0.2s ease;
}
.dropdown-item i {
font-size: 14px;
color: #666;
width: 16px;
text-align: center;
}
.dropdown-item:hover {
background-color: #f5f5f5;
}
.dropdown-divider {
height: 1px;
background-color: #e0e0e0;
margin: 4px 0;
}
.admin-only {
display: none;
}
/* 主内容区域调整 */
#contentArea {
padding: 20px;
}
/* 标签页容器 */
.tabs-container {
background-color: white;
border-bottom: 1px solid #e0e0e0;
}
.tabs-header {
display: flex;
padding: 0 20px;
overflow-x: auto;
scrollbar-width: thin;
scrollbar-color: #ccc transparent;
height: 40px;
}
.tabs-header::-webkit-scrollbar {
height: 4px;
}
.tabs-header::-webkit-scrollbar-track {
background: transparent;
}
.tabs-header::-webkit-scrollbar-thumb {
background-color: #ccc;
border-radius: 2px;
}
.tab {
display: flex;
align-items: center;
gap: 8px;
padding: 8px 16px;
background-color: #f5f5f5;
border: 1px solid #e0e0e0;
border-bottom: none;
border-radius: 4px 4px 0 0;
margin-right: 4px;
margin-top: 4px;
cursor: pointer;
position: relative;
user-select: none;
color: #666;
min-width: 120px;
max-width: 200px;
height: 36px;
transition: all 0.2s ease;
}
.tab i {
font-size: 14px;
}
.tab span {
flex: 1;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.tab .close-tab {
visibility: hidden;
width: 16px;
height: 16px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 50%;
font-size: 12px;
color: #999;
margin-left: 4px;
}
.tab:not([data-tab="overview"]):hover .close-tab {
visibility: visible;
}
.tab .close-tab:hover {
background-color: rgba(0, 0, 0, 0.1);
color: #666;
}
.tab.active {
background-color: white;
border: 1px solid #e0e0e0;
border-bottom: 1px solid white;
margin-bottom: -1px;
color: #2c3e50;
font-weight: 500;
box-shadow: 0 -2px 4px rgba(0,0,0,0.05);
}
.tab.active::before {
content: '';
position: absolute;
top: -1px;
left: -1px;
right: -1px;
height: 2px;
background: #3498db;
border-radius: 2px 2px 0 0;
}
.tab.active i {
color: #3498db;
}
.tab:hover:not(.active) {
background-color: #f0f0f0;
}
/* 标签页内容区域 */
.tabs-content {
flex: 1;
overflow: auto;
background-color: white;
position: relative;
}
.tab-pane {
display: none;
padding: 20px;
}
.tab-pane.active {
display: block;
}
/* 调整主内容区域的布局 */
.main-content {
display: flex;
flex-direction: column;
height: 100%;
}
.password-input {
position: relative;
display: flex;
align-items: center;
}
.password-input input {
padding-right: 40px;
}
.toggle-password {
position: absolute;
right: 10px;
top: 50%;
transform: translateY(-50%);
background: none;
border: none;
padding: 5px;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
transition: opacity 0.3s ease;
}
.toggle-password:hover {
opacity: 0.7;
}
.toggle-password:focus {
outline: none;
}
.visibility-icon {
width: 20px;
height: 20px;
object-fit: contain;
}