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

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); box-shadow: 0 2px 8px rgba(0,0,0,0.1);
display: none; display: none;
z-index: 1000; z-index: 1000;
min-width: 150px;
max-width: 250px;
white-space: nowrap;
overflow: hidden;
} }
.user-dropdown.active .user-dropdown-menu { .user-dropdown.active .user-dropdown-menu {
@ -95,6 +99,9 @@ class UserInfo extends HTMLElement {
cursor: pointer; cursor: pointer;
transition: background-color 0.3s; transition: background-color 0.3s;
gap: 8px; gap: 8px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
} }
.dropdown-item:hover { .dropdown-item:hover {
@ -159,6 +166,11 @@ class UserInfo extends HTMLElement {
border-style: solid; border-style: solid;
border-color: transparent transparent rgba(0, 0, 0, 0.8) transparent; border-color: transparent transparent rgba(0, 0, 0, 0.8) transparent;
} }
.dropdown-item span {
overflow: hidden;
text-overflow: ellipsis;
}
</style> </style>
<div class="user-dropdown"> <div class="user-dropdown">
<div class="user-dropdown-toggle"> <div class="user-dropdown-toggle">
@ -242,13 +254,15 @@ class UserInfo extends HTMLElement {
try { try {
const userInfoStr = localStorage.getItem('userInfo'); const userInfoStr = localStorage.getItem('userInfo');
if (!userInfoStr) { if (!userInfoStr) {
window.location.href = 'index.html'; document.getElementById('authContainer').style.display = 'block';
document.getElementById('mainContainer').style.display = 'none';
return; return;
} }
userInfo = JSON.parse(userInfoStr); userInfo = JSON.parse(userInfoStr);
} catch (error) { } catch (error) {
console.error('解析用户信息失败:', error); console.error('解析用户信息失败:', error);
window.location.href = 'index.html'; document.getElementById('authContainer').style.display = 'block';
document.getElementById('mainContainer').style.display = 'none';
return; return;
} }
@ -314,7 +328,8 @@ class UserInfo extends HTMLElement {
logout() { logout() {
localStorage.removeItem('userInfo'); 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="icon" type="image/png" href="assets/icons/XNSim.png">
<link rel="shortcut 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"> <link rel="stylesheet" href="style.css">
<script src="components/auth-component.js"></script>
<script src="components/main-toolbar.js"></script> <script src="components/main-toolbar.js"></script>
<script src="components/sub-toolbar.js"></script> <script src="components/sub-toolbar.js"></script>
<script src="components/user-info.js"></script> <script src="components/user-info.js"></script>
@ -183,10 +184,39 @@
flex: 1; flex: 1;
overflow: hidden; 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> </style>
</head> </head>
<body> <body>
<div class="main-container"> <div id="authContainer">
<auth-component></auth-component>
</div>
<div id="mainContainer" class="main-container">
<div class="content-wrapper"> <div class="content-wrapper">
<main-toolbar></main-toolbar> <main-toolbar></main-toolbar>
<sub-toolbar></sub-toolbar> <sub-toolbar></sub-toolbar>
@ -210,284 +240,391 @@
</div> </div>
<script> <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 tabsContainer = document.querySelector('tabs-container');
const contentArea = document.querySelector('content-area'); const contentArea = document.querySelector('content-area');
// 初始化时创建概览标签页并加载概览内容 // 检查是否已登录
tabsContainer.createTab('overview', '概览', 'dashboard', '主页', 'home'); const checkAuth = () => {
contentArea.loadContent('overview'); const userInfo = localStorage.getItem('userInfo');
if (userInfo) {
// 监听主工具栏选择事件 authContainer.classList.remove('visible');
document.querySelector('main-toolbar').addEventListener('tool-selected', function(e) { mainContainer.classList.add('visible');
const { tool, text } = e.detail; // 初始化主页面
document.querySelector('sub-toolbar').setAttribute('current-tool', tool); initializeMainPage();
// 当选择主页时,显示概览 } else {
if (tool === 'home') { authContainer.classList.add('visible');
tabsContainer.createTab('overview', '概览', 'dashboard', '主页', 'home'); mainContainer.classList.remove('visible');
} }
});
// 监听子工具栏选择事件
document.querySelector('sub-toolbar').addEventListener('sub-item-selected', function(e) {
const { parent, text, icon } = e.detail;
const parentText = document.querySelector(`main-toolbar`).shadowRoot.querySelector(`[data-tool="${parent}"] span`).textContent;
updateBreadcrumb(parentText, text);
updateContent(text, icon, parentText, parent);
});
// 监听用户菜单动作
document.querySelector('user-info').addEventListener('menu-action', function(e) {
const { action } = e.detail;
handleMenuAction(action);
});
// 监听标签页激活事件
tabsContainer.addEventListener('tab-activated', function(e) {
const { id, parentText, title, parentTool, isTabSwitch } = e.detail;
// 更新面包屑导航
updateBreadcrumb(parentText, title);
// 只有在真正的标签切换时才加载内容
if (isTabSwitch) {
contentArea.loadContent(id);
}
// 同步更新面包屑导航
const breadcrumbIcon = document.querySelector('.breadcrumb .icon-small');
if (breadcrumbIcon) {
const iconName = getIconNameForTitle(title, parentTool);
breadcrumbIcon.src = `assets/icons/png/${iconName}_b.png`;
breadcrumbIcon.alt = `${parentText} / ${title}`;
}
});
// 监听标签页关闭事件
tabsContainer.addEventListener('tab-closed', function(e) {
const { id } = e.detail;
// 清除关闭标签页的内容缓存
contentArea.clearCache(id);
});
});
function handleMenuAction(action) {
const contentArea = document.querySelector('content-area');
const tabsContainer = document.querySelector('tabs-container');
switch(action) {
case 'profile':
tabsContainer.createTab('profile', '个人中心', 'user', '系统', 'system');
contentArea.loadContent('profile');
break;
case 'users':
tabsContainer.createTab('users', '用户管理', 'users', '系统', 'system');
contentArea.loadContent('users');
break;
}
}
function updateBreadcrumb(mainMenu, subMenu) {
const currentPath = document.getElementById('currentPath');
currentPath.textContent = subMenu ? `${mainMenu} / ${subMenu}` : mainMenu;
}
function updateContent(title, icon, parentText, parentTool) {
const tabsContainer = document.querySelector('tabs-container');
const contentArea = document.querySelector('content-area');
if (title === '概览') {
// 直接激活第一个标签页(概览标签页)
const firstTab = tabsContainer.shadowRoot.querySelector('.tab');
if (firstTab) {
tabsContainer.activateTab(firstTab.getAttribute('data-tab'));
}
return;
}
// 特殊处理更新记录标签页
if (title === '更新记录') {
const id = 'update-history';
tabsContainer.createTab(id, title, icon, parentText, parentTool);
return;
}
// 处理运行日志标签页
if (title === '运行日志') {
const id = 'run-log';
tabsContainer.createTab(id, title, icon, parentText, parentTool);
return;
}
// 处理资源监控标签页
if (title === '资源监控') {
const id = 'system-info';
tabsContainer.createTab(id, title, icon, parentText, parentTool);
contentArea.loadContent(id);
return;
}
// 处理系统日志标签页
if (title === '系统日志') {
const id = 'system-log';
tabsContainer.createTab(id, title, icon, parentText, parentTool);
contentArea.loadContent(id);
return;
}
// 处理待办事项标签页
if (title === '待办事项') {
const id = 'todo';
tabsContainer.createTab(id, title, icon, parentText, parentTool);
contentArea.loadContent(id);
return;
}
// 处理帮助标签页
if (title === '帮助') {
const id = 'help';
tabsContainer.createTab(id, title, icon, parentText, parentTool);
contentArea.loadContent(id);
return;
}
// 处理Q&A标签页
if (title === 'Q&A') {
const id = 'qa';
tabsContainer.createTab(id, title, icon, parentText, parentTool);
contentArea.loadContent(id);
return;
}
// 处理运行环境配置标签页
if (title === '运行环境配置') {
const id = 'run-env-config';
tabsContainer.createTab(id, title, icon, parentText, parentTool);
return;
}
// 处理模型配置标签页
if (title === '模型配置') {
const id = 'model-config';
tabsContainer.createTab(id, title, icon, parentText, parentTool);
return;
}
// 处理服务配置标签页
if (title === '服务配置') {
const id = 'service-config';
tabsContainer.createTab(id, title, icon, parentText, parentTool);
return;
}
// 处理接口配置标签页
if (title === '接口配置') {
const id = 'interface-config';
tabsContainer.createTab(id, title, icon, parentText, parentTool);
return;
}
// 处理模型开发标签页
if (title === '模型开发') {
const id = 'model-development';
tabsContainer.createTab(id, title, icon, parentText, parentTool);
return;
}
// 处理服务开发标签页
if (title === '服务开发') {
const id = 'service-development';
tabsContainer.createTab(id, title, icon, parentText, parentTool);
return;
}
// 处理运行测试标签页
if (title === '运行测试') {
const id = 'run-test';
tabsContainer.createTab(id, title, icon, parentText, parentTool);
return;
}
// 处理运行仿真标签页
if (title === '运行仿真') {
const id = 'run-simulation';
tabsContainer.createTab(id, title, icon, parentText, parentTool);
return;
}
// 处理仿真监控标签页
if (title === '仿真监控') {
const id = 'simulation-monitor';
tabsContainer.createTab(id, title, icon, parentText, parentTool);
return;
}
// 处理模型监控标签页
if (title === '模型监控') {
const id = 'model-monitor';
tabsContainer.createTab(id, title, icon, parentText, parentTool);
return;
}
// 处理数据监控标签页
if (title === '数据监控') {
const id = 'data-monitor';
tabsContainer.createTab(id, title, icon, parentText, parentTool);
return;
}
// 处理数据采集标签页
if (title === '数据采集') {
const id = 'data-collection';
tabsContainer.createTab(id, title, icon, parentText, parentTool);
return;
}
// 处理个人中心标签页
if (title === '个人中心') {
const id = 'profile';
tabsContainer.createTab(id, title, icon, parentText, parentTool);
return;
}
// 处理用户管理标签页
if (title === '用户管理') {
const id = 'users';
tabsContainer.createTab(id, title, icon, parentText, parentTool);
return;
}
// 默认情况下使用标题转换为ID
const id = title.toLowerCase().replace(/\s+/g, '-');
tabsContainer.createTab(id, title, icon, parentText, parentTool);
}
// 根据标题和父工具获取图标名称的辅助函数
function getIconNameForTitle(title, parentTool) {
const iconMap = {
'概览': 'dashboard',
'更新记录': 'clock',
'运行日志': 'file',
'资源监控': 'server',
'帮助': 'help',
'Q&A': 'question',
'运行环境配置': 'chip',
'模型配置': 'cube',
'服务配置': 'settings',
'接口配置': 'plug',
'运行测试': 'flask',
'运行仿真': 'rocket',
'仿真监控': 'desktop',
'模型监控': 'cubes',
'数据监控': 'chart-bar',
'数据采集': 'database',
'个人中心': 'user',
'用户管理': 'users',
'模型开发': 'cube',
'服务开发': 'settings'
}; };
return iconMap[title] || parentTool || 'con'; // 初始化主页面
} function initializeMainPage() {
// 初始化时创建概览标签页并加载概览内容
tabsContainer.createTab('overview', '概览', 'dashboard', '主页', 'home');
contentArea.loadContent('overview');
// 监听主工具栏选择事件
document.querySelector('main-toolbar').addEventListener('tool-selected', function(e) {
const { tool, text } = e.detail;
document.querySelector('sub-toolbar').setAttribute('current-tool', tool);
// 当选择主页时,显示概览
if (tool === 'home') {
tabsContainer.createTab('overview', '概览', 'dashboard', '主页', 'home');
}
});
// 监听子工具栏选择事件
document.querySelector('sub-toolbar').addEventListener('sub-item-selected', function(e) {
const { parent, text, icon } = e.detail;
const parentText = document.querySelector(`main-toolbar`).shadowRoot.querySelector(`[data-tool="${parent}"] span`).textContent;
updateBreadcrumb(parentText, text);
updateContent(text, icon, parentText, parent);
});
// 监听用户菜单动作
document.querySelector('user-info').addEventListener('menu-action', function(e) {
const { action } = e.detail;
handleMenuAction(action);
});
// 监听标签页激活事件
tabsContainer.addEventListener('tab-activated', function(e) {
const { id, parentText, title, parentTool, isTabSwitch } = e.detail;
// 更新面包屑导航
updateBreadcrumb(parentText, title);
// 只有在真正的标签切换时才加载内容
if (isTabSwitch) {
contentArea.loadContent(id);
}
// 同步更新面包屑导航
const breadcrumbIcon = document.querySelector('.breadcrumb .icon-small');
if (breadcrumbIcon) {
const iconName = getIconNameForTitle(title, parentTool);
breadcrumbIcon.src = `assets/icons/png/${iconName}_b.png`;
breadcrumbIcon.alt = `${parentText} / ${title}`;
}
});
// 监听标签页关闭事件
tabsContainer.addEventListener('tab-closed', function(e) {
const { id } = e.detail;
// 清除关闭标签页的内容缓存
contentArea.clearCache(id);
});
}
function handleMenuAction(action) {
const contentArea = document.querySelector('content-area');
const tabsContainer = document.querySelector('tabs-container');
switch(action) {
case 'profile':
tabsContainer.createTab('profile', '个人中心', 'user', '系统', 'system');
contentArea.loadContent('profile');
break;
case 'users':
tabsContainer.createTab('users', '用户管理', 'users', '系统', 'system');
contentArea.loadContent('users');
break;
case 'logout':
// 清除所有用户相关数据
localStorage.removeItem('userInfo');
localStorage.removeItem('authToken');
// 清除所有标签页
tabsContainer.clearAllTabs();
// 显示退出成功提示
showToast('已安全退出登录');
// 重新检查认证状态,这会触发返回登录页面
checkAuth();
break;
}
}
function updateBreadcrumb(mainMenu, subMenu) {
const currentPath = document.getElementById('currentPath');
currentPath.textContent = subMenu ? `${mainMenu} / ${subMenu}` : mainMenu;
}
function updateContent(title, icon, parentText, parentTool) {
const tabsContainer = document.querySelector('tabs-container');
const contentArea = document.querySelector('content-area');
if (title === '概览') {
// 直接激活第一个标签页(概览标签页)
const firstTab = tabsContainer.shadowRoot.querySelector('.tab');
if (firstTab) {
tabsContainer.activateTab(firstTab.getAttribute('data-tab'));
}
return;
}
// 特殊处理更新记录标签页
if (title === '更新记录') {
const id = 'update-history';
tabsContainer.createTab(id, title, icon, parentText, parentTool);
return;
}
// 处理运行日志标签页
if (title === '运行日志') {
const id = 'run-log';
tabsContainer.createTab(id, title, icon, parentText, parentTool);
return;
}
// 处理资源监控标签页
if (title === '资源监控') {
const id = 'system-info';
tabsContainer.createTab(id, title, icon, parentText, parentTool);
contentArea.loadContent(id);
return;
}
// 处理系统日志标签页
if (title === '系统日志') {
const id = 'system-log';
tabsContainer.createTab(id, title, icon, parentText, parentTool);
contentArea.loadContent(id);
return;
}
// 处理待办事项标签页
if (title === '待办事项') {
const id = 'todo';
tabsContainer.createTab(id, title, icon, parentText, parentTool);
contentArea.loadContent(id);
return;
}
// 处理帮助标签页
if (title === '帮助') {
const id = 'help';
tabsContainer.createTab(id, title, icon, parentText, parentTool);
contentArea.loadContent(id);
return;
}
// 处理Q&A标签页
if (title === 'Q&A') {
const id = 'qa';
tabsContainer.createTab(id, title, icon, parentText, parentTool);
contentArea.loadContent(id);
return;
}
// 处理运行环境配置标签页
if (title === '运行环境配置') {
const id = 'run-env-config';
tabsContainer.createTab(id, title, icon, parentText, parentTool);
return;
}
// 处理模型配置标签页
if (title === '模型配置') {
const id = 'model-config';
tabsContainer.createTab(id, title, icon, parentText, parentTool);
return;
}
// 处理服务配置标签页
if (title === '服务配置') {
const id = 'service-config';
tabsContainer.createTab(id, title, icon, parentText, parentTool);
return;
}
// 处理接口配置标签页
if (title === '接口配置') {
const id = 'interface-config';
tabsContainer.createTab(id, title, icon, parentText, parentTool);
return;
}
// 处理模型开发标签页
if (title === '模型开发') {
const id = 'model-development';
tabsContainer.createTab(id, title, icon, parentText, parentTool);
return;
}
// 处理服务开发标签页
if (title === '服务开发') {
const id = 'service-development';
tabsContainer.createTab(id, title, icon, parentText, parentTool);
return;
}
// 处理运行测试标签页
if (title === '运行测试') {
const id = 'run-test';
tabsContainer.createTab(id, title, icon, parentText, parentTool);
return;
}
// 处理运行仿真标签页
if (title === '运行仿真') {
const id = 'run-simulation';
tabsContainer.createTab(id, title, icon, parentText, parentTool);
return;
}
// 处理仿真监控标签页
if (title === '仿真监控') {
const id = 'simulation-monitor';
tabsContainer.createTab(id, title, icon, parentText, parentTool);
return;
}
// 处理模型监控标签页
if (title === '模型监控') {
const id = 'model-monitor';
tabsContainer.createTab(id, title, icon, parentText, parentTool);
return;
}
// 处理数据监控标签页
if (title === '数据监控') {
const id = 'data-monitor';
tabsContainer.createTab(id, title, icon, parentText, parentTool);
return;
}
// 处理数据采集标签页
if (title === '数据采集') {
const id = 'data-collection';
tabsContainer.createTab(id, title, icon, parentText, parentTool);
return;
}
// 处理个人中心标签页
if (title === '个人中心') {
const id = 'profile';
tabsContainer.createTab(id, title, icon, parentText, parentTool);
return;
}
// 处理用户管理标签页
if (title === '用户管理') {
const id = 'users';
tabsContainer.createTab(id, title, icon, parentText, parentTool);
return;
}
// 默认情况下使用标题转换为ID
const id = title.toLowerCase().replace(/\s+/g, '-');
tabsContainer.createTab(id, title, icon, parentText, parentTool);
}
// 根据标题和父工具获取图标名称的辅助函数
function getIconNameForTitle(title, parentTool) {
const iconMap = {
'概览': 'dashboard',
'更新记录': 'clock',
'运行日志': 'file',
'资源监控': 'server',
'帮助': 'help',
'Q&A': 'question',
'运行环境配置': 'chip',
'模型配置': 'cube',
'服务配置': 'settings',
'接口配置': 'plug',
'运行测试': 'flask',
'运行仿真': 'rocket',
'仿真监控': 'desktop',
'模型监控': 'cubes',
'数据监控': 'chart-bar',
'数据采集': 'database',
'个人中心': 'user',
'用户管理': 'users',
'模型开发': 'cube',
'服务开发': 'settings'
};
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> </script>
</body> </body>
</html> </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.json());
app.use(bodyParser.urlencoded({ extended: true })); app.use(bodyParser.urlencoded({ extended: true }));
// 根路径直接返回main.html
app.get('/', (req, res) => {
res.sendFile(path.join(__dirname, 'main.html'));
});
// 静态文件服务 - 放在根路径处理之后
app.use(express.static(__dirname)); app.use(express.static(__dirname));
// 监听进程退出事件 // 监听进程退出事件
@ -80,11 +87,6 @@ app.use('/api/qa', qaRoutes);
app.use('/api/todos', todoRoutes); app.use('/api/todos', todoRoutes);
app.use('/api', userRoutes); app.use('/api', userRoutes);
// 主页路由
app.get('/', (req, res) => {
res.sendFile(path.join(__dirname, 'main.html'));
});
// 接口配置页面路由 // 接口配置页面路由
app.get('/interface-config', (req, res) => { app.get('/interface-config', (req, res) => {
res.sendFile(path.join(__dirname, 'interface-config.html')); 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;
}