邹佳轩2025年秋季学期第六周周计划及第五周周总结;以及对于前端的尝试 #22
Merged
hnu202326010131
merged 1 commits from xingyuanxin_branch into develop 3 months ago
@ -0,0 +1,275 @@
|
||||
<!-- 侧边栏导航组件 -->
|
||||
<div class="app-sidebar" id="appSidebar">
|
||||
<!-- 侧边栏头部 -->
|
||||
<div class="sidebar-header">
|
||||
<div class="sidebar-logo">
|
||||
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect width="32" height="32" rx="8" fill="#409EFF"/>
|
||||
<path d="M8 12H24V14H8V12Z" fill="white"/>
|
||||
<path d="M8 16H20V18H8V16Z" fill="white"/>
|
||||
<path d="M8 20H16V22H8V20Z" fill="white"/>
|
||||
<circle cx="22" cy="10" r="3" fill="#F56C6C"/>
|
||||
</svg>
|
||||
<span class="sidebar-title">错误检测</span>
|
||||
</div>
|
||||
<button class="sidebar-collapse" id="sidebarCollapse" title="收起侧边栏">
|
||||
<i class="icon-chevron-left"></i>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- 导航菜单 -->
|
||||
<nav class="sidebar-nav">
|
||||
<ul class="nav-menu">
|
||||
<!-- 概览仪表板 -->
|
||||
<li class="nav-item">
|
||||
<a href="#dashboard" class="nav-link active" data-view="dashboard">
|
||||
<i class="nav-icon icon-dashboard"></i>
|
||||
<span class="nav-text">概览仪表板</span>
|
||||
<span class="nav-badge">实时</span>
|
||||
</a>
|
||||
</li>
|
||||
|
||||
<!-- 集群监控 -->
|
||||
<li class="nav-item">
|
||||
<a href="#cluster-monitor" class="nav-link" data-view="cluster-monitor">
|
||||
<i class="nav-icon icon-cluster"></i>
|
||||
<span class="nav-text">集群监控</span>
|
||||
<span class="nav-count">12</span>
|
||||
</a>
|
||||
<ul class="nav-submenu">
|
||||
<li class="nav-subitem">
|
||||
<a href="#cluster-nodes" class="nav-sublink" data-view="cluster-nodes">
|
||||
<span class="nav-subtext">节点管理</span>
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-subitem">
|
||||
<a href="#cluster-resources" class="nav-sublink" data-view="cluster-resources">
|
||||
<span class="nav-subtext">资源监控</span>
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-subitem">
|
||||
<a href="#cluster-performance" class="nav-sublink" data-view="cluster-performance">
|
||||
<span class="nav-subtext">性能分析</span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
|
||||
<!-- 故障管理 -->
|
||||
<li class="nav-item">
|
||||
<a href="#fault-manage" class="nav-link" data-view="fault-manage">
|
||||
<i class="nav-icon icon-fault"></i>
|
||||
<span class="nav-text">故障管理</span>
|
||||
<span class="nav-alert">5</span>
|
||||
</a>
|
||||
<ul class="nav-submenu">
|
||||
<li class="nav-subitem">
|
||||
<a href="#fault-list" class="nav-sublink" data-view="fault-list">
|
||||
<span class="nav-subtext">故障列表</span>
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-subitem">
|
||||
<a href="#fault-analysis" class="nav-sublink" data-view="fault-analysis">
|
||||
<span class="nav-subtext">故障分析</span>
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-subitem">
|
||||
<a href="#fault-history" class="nav-sublink" data-view="fault-history">
|
||||
<span class="nav-subtext">历史记录</span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
|
||||
<!-- 日志分析 -->
|
||||
<li class="nav-item">
|
||||
<a href="#log-analysis" class="nav-link" data-view="log-analysis">
|
||||
<i class="nav-icon icon-log"></i>
|
||||
<span class="nav-text">日志分析</span>
|
||||
</a>
|
||||
<ul class="nav-submenu">
|
||||
<li class="nav-subitem">
|
||||
<a href="#log-search" class="nav-sublink" data-view="log-search">
|
||||
<span class="nav-subtext">日志搜索</span>
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-subitem">
|
||||
<a href="#log-realtime" class="nav-sublink" data-view="log-realtime">
|
||||
<span class="nav-subtext">实时日志</span>
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-subitem">
|
||||
<a href="#log-statistics" class="nav-sublink" data-view="log-statistics">
|
||||
<span class="nav-subtext">统计分析</span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
|
||||
<!-- 分割线 -->
|
||||
<li class="nav-divider"></li>
|
||||
|
||||
<!-- 系统配置 -->
|
||||
<li class="nav-item">
|
||||
<a href="#system-config" class="nav-link" data-view="system-config">
|
||||
<i class="nav-icon icon-config"></i>
|
||||
<span class="nav-text">系统配置</span>
|
||||
</a>
|
||||
<ul class="nav-submenu">
|
||||
<li class="nav-subitem">
|
||||
<a href="#config-general" class="nav-sublink" data-view="config-general">
|
||||
<span class="nav-subtext">基础配置</span>
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-subitem">
|
||||
<a href="#config-alert" class="nav-sublink" data-view="config-alert">
|
||||
<span class="nav-subtext">告警配置</span>
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-subitem">
|
||||
<a href="#config-notification" class="nav-sublink" data-view="config-notification">
|
||||
<span class="nav-subtext">通知配置</span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
|
||||
<!-- 用户管理 -->
|
||||
<li class="nav-item">
|
||||
<a href="#user-manage" class="nav-link" data-view="user-manage">
|
||||
<i class="nav-icon icon-users"></i>
|
||||
<span class="nav-text">用户管理</span>
|
||||
</a>
|
||||
<ul class="nav-submenu">
|
||||
<li class="nav-subitem">
|
||||
<a href="#user-list" class="nav-sublink" data-view="user-list">
|
||||
<span class="nav-subtext">用户列表</span>
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-subitem">
|
||||
<a href="#role-manage" class="nav-sublink" data-view="role-manage">
|
||||
<span class="nav-subtext">角色管理</span>
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-subitem">
|
||||
<a href="#permission-manage" class="nav-sublink" data-view="permission-manage">
|
||||
<span class="nav-subtext">权限管理</span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
|
||||
<!-- 系统监控 -->
|
||||
<li class="nav-item">
|
||||
<a href="#system-monitor" class="nav-link" data-view="system-monitor">
|
||||
<i class="nav-icon icon-monitor"></i>
|
||||
<span class="nav-text">系统监控</span>
|
||||
</a>
|
||||
</li>
|
||||
|
||||
<!-- 分割线 -->
|
||||
<li class="nav-divider"></li>
|
||||
|
||||
<!-- 帮助中心 -->
|
||||
<li class="nav-item">
|
||||
<a href="#help-center" class="nav-link" data-view="help-center">
|
||||
<i class="nav-icon icon-help-circle"></i>
|
||||
<span class="nav-text">帮助中心</span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
|
||||
<!-- 侧边栏底部 -->
|
||||
<div class="sidebar-footer">
|
||||
<div class="system-info">
|
||||
<div class="system-version">
|
||||
<span class="version-label">版本</span>
|
||||
<span class="version-number">v1.0.0</span>
|
||||
</div>
|
||||
<div class="system-status">
|
||||
<span class="status-dot online"></span>
|
||||
<span class="status-text">系统正常</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 侧边栏图标样式 -->
|
||||
<style>
|
||||
/* 侧边栏图标 */
|
||||
.icon-dashboard::before {
|
||||
content: "📊";
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.icon-cluster::before {
|
||||
content: "🖥️";
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.icon-fault::before {
|
||||
content: "⚠️";
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.icon-log::before {
|
||||
content: "📋";
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.icon-config::before {
|
||||
content: "⚙️";
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.icon-users::before {
|
||||
content: "👥";
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.icon-monitor::before {
|
||||
content: "📈";
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.icon-help-circle::before {
|
||||
content: "❓";
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.icon-chevron-left::before {
|
||||
content: "◀";
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
/* 收起状态下的图标调整 */
|
||||
.app-sidebar.collapsed .nav-icon {
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
.app-sidebar.collapsed .nav-text,
|
||||
.app-sidebar.collapsed .nav-badge,
|
||||
.app-sidebar.collapsed .nav-count,
|
||||
.app-sidebar.collapsed .nav-alert,
|
||||
.app-sidebar.collapsed .sidebar-title {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.app-sidebar.collapsed .nav-submenu {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.app-sidebar.collapsed {
|
||||
width: 60px;
|
||||
}
|
||||
|
||||
.app-sidebar.collapsed .sidebar-header {
|
||||
padding: 0 12px;
|
||||
}
|
||||
|
||||
.app-sidebar.collapsed .nav-link {
|
||||
justify-content: center;
|
||||
padding: 12px;
|
||||
}
|
||||
</style>
|
||||
@ -0,0 +1,339 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Hadoop故障检测与自动恢复系统</title>
|
||||
<!-- 样式文件 -->
|
||||
<link rel="stylesheet" href="styles/main.css">
|
||||
<link rel="stylesheet" href="styles/dark.css">
|
||||
<link rel="stylesheet" href="styles/main.css">
|
||||
<link rel="stylesheet" href="styles/login.css">
|
||||
<link rel="stylesheet" href="styles/cluster-monitor.css">
|
||||
<link rel="stylesheet" href="styles/fault-manage.css">
|
||||
<link rel="stylesheet" href="styles/log-analysis.css">
|
||||
<link rel="stylesheet" href="styles/components.css">
|
||||
<link rel="stylesheet" href="styles/responsive.css">
|
||||
|
||||
<!-- Element Plus CSS -->
|
||||
<link rel="stylesheet" href="https://unpkg.com/element-plus/dist/index.css">
|
||||
|
||||
<!-- ECharts -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/echarts@5.4.3/dist/echarts.min.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<!-- 应用根容器 -->
|
||||
<div id="app">
|
||||
<!-- 登录页面容器 -->
|
||||
<div id="loginContainer" class="login-container">
|
||||
<!-- 登录页面内容 -->
|
||||
<div id="loginPage" class="page-view">
|
||||
<!-- 引入登录页面HTML -->
|
||||
<div class="login-page-wrapper">
|
||||
<!-- 这里将通过JavaScript动态加载Login.html的内容 -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 主应用布局 -->
|
||||
<div id="appLayout" class="app-layout" style="display: none;">
|
||||
<!-- 顶部导航栏 -->
|
||||
<div id="header" class="header-container">
|
||||
<!-- 这里将通过JavaScript动态加载Header.html的内容 -->
|
||||
</div>
|
||||
|
||||
<!-- 侧边栏 -->
|
||||
<div id="sidebar" class="sidebar-container">
|
||||
<!-- 这里将通过JavaScript动态加载Sidebar.html的内容 -->
|
||||
</div>
|
||||
|
||||
<!-- 主内容区域 -->
|
||||
<div id="mainContent" class="main-content">
|
||||
<!-- 概览仪表板页面 -->
|
||||
<div id="dashboardPage" class="page-view" style="display: none;">
|
||||
<div class="dashboard-page">
|
||||
<div class="page-header">
|
||||
<div class="header-content">
|
||||
<h1 class="page-title">概览仪表板</h1>
|
||||
<p class="page-desc">系统整体运行状态概览</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 系统状态卡片 -->
|
||||
<div class="dashboard-cards">
|
||||
<div class="dashboard-card">
|
||||
<div class="card-icon">
|
||||
<i class="icon-server"></i>
|
||||
</div>
|
||||
<div class="card-content">
|
||||
<div class="card-value">12</div>
|
||||
<div class="card-label">在线节点</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="dashboard-card">
|
||||
<div class="card-icon">
|
||||
<i class="icon-alert"></i>
|
||||
</div>
|
||||
<div class="card-content">
|
||||
<div class="card-value">3</div>
|
||||
<div class="card-label">活跃告警</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="dashboard-card">
|
||||
<div class="card-icon">
|
||||
<i class="icon-cpu"></i>
|
||||
</div>
|
||||
<div class="card-content">
|
||||
<div class="card-value">68%</div>
|
||||
<div class="card-label">平均CPU使用率</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="dashboard-card">
|
||||
<div class="card-icon">
|
||||
<i class="icon-memory"></i>
|
||||
</div>
|
||||
<div class="card-content">
|
||||
<div class="card-value">45%</div>
|
||||
<div class="card-label">平均内存使用率</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 图表区域 -->
|
||||
<div class="dashboard-charts">
|
||||
<div class="chart-container">
|
||||
<h3>系统性能趋势</h3>
|
||||
<div id="performanceChart" class="chart-placeholder">
|
||||
<div class="chart-loading">
|
||||
<div class="loading-spinner"></div>
|
||||
<p>加载图表数据中...</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 集群监控页面 -->
|
||||
<div id="clusterMonitorPage" class="page-view" style="display: none;">
|
||||
<!-- 这里将通过JavaScript动态加载ClusterMonitor.html的内容 -->
|
||||
</div>
|
||||
|
||||
<!-- 故障管理页面 -->
|
||||
<div id="faultManagePage" class="page-view" style="display: none;">
|
||||
<!-- 这里将通过JavaScript动态加载FaultManage.html的内容 -->
|
||||
</div>
|
||||
|
||||
<!-- 日志分析页面 -->
|
||||
<div id="logAnalysisPage" class="page-view" style="display: none;">
|
||||
<!-- 这里将通过JavaScript动态加载LogAnalysis.html的内容 -->
|
||||
</div>
|
||||
|
||||
<!-- 系统配置页面 -->
|
||||
<div id="systemConfigPage" class="page-view" style="display: none;">
|
||||
<div class="config-page">
|
||||
<div class="page-header">
|
||||
<div class="header-content">
|
||||
<h1 class="page-title">系统配置</h1>
|
||||
<p class="page-desc">管理系统参数和配置选项</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="config-content">
|
||||
<div class="config-section">
|
||||
<h3>监控配置</h3>
|
||||
<div class="config-form">
|
||||
<div class="form-group">
|
||||
<label>监控间隔 (秒)</label>
|
||||
<input type="number" class="form-control" value="30">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>告警阈值</label>
|
||||
<input type="number" class="form-control" value="80">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="config-section">
|
||||
<h3>通知配置</h3>
|
||||
<div class="config-form">
|
||||
<div class="form-group">
|
||||
<label>邮件通知</label>
|
||||
<input type="email" class="form-control" placeholder="admin@example.com">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>短信通知</label>
|
||||
<input type="tel" class="form-control" placeholder="手机号码">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 用户管理页面 -->
|
||||
<div id="userManagePage" class="page-view" style="display: none;">
|
||||
<div class="user-manage-page">
|
||||
<div class="page-header">
|
||||
<div class="header-content">
|
||||
<h1 class="page-title">用户管理</h1>
|
||||
<p class="page-desc">管理系统用户和权限</p>
|
||||
</div>
|
||||
<div class="header-actions">
|
||||
<button class="btn btn-primary">
|
||||
<i class="icon-plus"></i>
|
||||
<span>添加用户</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="user-table-container">
|
||||
<table class="data-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>用户名</th>
|
||||
<th>邮箱</th>
|
||||
<th>角色</th>
|
||||
<th>状态</th>
|
||||
<th>最后登录</th>
|
||||
<th>操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>admin</td>
|
||||
<td>admin@example.com</td>
|
||||
<td><span class="role-badge admin">管理员</span></td>
|
||||
<td><span class="status-badge active">活跃</span></td>
|
||||
<td>2024-01-15 14:30:00</td>
|
||||
<td>
|
||||
<button class="action-btn">编辑</button>
|
||||
<button class="action-btn">删除</button>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 系统监控页面 -->
|
||||
<div id="systemMonitorPage" class="page-view" style="display: none;">
|
||||
<div class="system-monitor-page">
|
||||
<div class="page-header">
|
||||
<div class="header-content">
|
||||
<h1 class="page-title">系统监控</h1>
|
||||
<p class="page-desc">实时监控系统资源使用情况</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="monitor-content">
|
||||
<div class="monitor-charts">
|
||||
<div class="chart-container">
|
||||
<h3>CPU使用率</h3>
|
||||
<div id="cpuChart" class="chart-placeholder">
|
||||
<div class="chart-loading">
|
||||
<div class="loading-spinner"></div>
|
||||
<p>加载图表数据中...</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="chart-container">
|
||||
<h3>内存使用率</h3>
|
||||
<div id="memoryChart" class="chart-placeholder">
|
||||
<div class="chart-loading">
|
||||
<div class="loading-spinner"></div>
|
||||
<p>加载图表数据中...</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 帮助中心页面 -->
|
||||
<div id="helpPage" class="page-view" style="display: none;">
|
||||
<div class="help-page">
|
||||
<div class="page-header">
|
||||
<div class="header-content">
|
||||
<h1 class="page-title">帮助中心</h1>
|
||||
<p class="page-desc">系统使用指南和常见问题</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="help-content">
|
||||
<div class="help-section">
|
||||
<h3>快速开始</h3>
|
||||
<ul>
|
||||
<li>登录系统后,首先查看概览仪表板了解系统整体状态</li>
|
||||
<li>通过集群监控页面查看各节点的详细信息</li>
|
||||
<li>在故障管理页面处理系统告警和故障</li>
|
||||
<li>使用日志分析功能定位问题根因</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="help-section">
|
||||
<h3>常见问题</h3>
|
||||
<div class="faq-item">
|
||||
<h4>如何添加新的监控节点?</h4>
|
||||
<p>在集群监控页面点击"添加节点"按钮,填写节点信息即可。</p>
|
||||
</div>
|
||||
|
||||
<div class="faq-item">
|
||||
<h4>告警通知如何配置?</h4>
|
||||
<p>在系统配置页面的通知配置部分设置邮件和短信通知方式。</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 通用组件容器 -->
|
||||
<div id="modal-container">
|
||||
<!-- 弹窗、加载动画等通用组件将在这里渲染 -->
|
||||
</div>
|
||||
|
||||
<!-- JavaScript 文件 -->
|
||||
<!-- Vue 3 CDN -->
|
||||
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
|
||||
<!-- Element Plus JS CDN -->
|
||||
<script src="https://unpkg.com/element-plus/dist/index.full.js"></script>
|
||||
<!-- Axios CDN -->
|
||||
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
|
||||
|
||||
<!-- Chart.js 图表库 -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
||||
|
||||
<!-- 应用脚本 -->
|
||||
<script src="utils/request.js"></script>
|
||||
<script src="utils/auth.js"></script>
|
||||
<script src="utils/format.js"></script>
|
||||
<script src="router/index.js"></script>
|
||||
<script src="components/Layout/Header.js"></script>
|
||||
<script src="components/Layout/Sidebar.js"></script>
|
||||
<script src="components/Common/Loading.js"></script>
|
||||
<script src="components/Common/ErrorTip.js"></script>
|
||||
<script src="components/Common/Confirm.js"></script>
|
||||
<script src="components/Chart/ResourceTrend.js"></script>
|
||||
<script src="views/Login/Index.js"></script>
|
||||
<script src="views/ClusterMonitor/Index.js"></script>
|
||||
<script src="views/FaultManage/List.js"></script>
|
||||
<script src="views/FaultManage/Detail.js"></script>
|
||||
<script src="views/LogAnalysis/Index.js"></script>
|
||||
<script src="api/user.js"></script>
|
||||
<script src="api/cluster.js"></script>
|
||||
<script src="api/fault.js"></script>
|
||||
<script src="api/log.js"></script>
|
||||
<script src="js/charts.js"></script>
|
||||
<script src="js/demo-data.js"></script>
|
||||
<script src="app.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
@ -0,0 +1,435 @@
|
||||
/**
|
||||
* 图表组件库
|
||||
* 基于Chart.js实现各种数据可视化图表
|
||||
*/
|
||||
|
||||
// 图表基础配置
|
||||
const ChartConfig = {
|
||||
// 默认颜色主题
|
||||
colors: {
|
||||
primary: '#3b82f6',
|
||||
success: '#10b981',
|
||||
warning: '#f59e0b',
|
||||
danger: '#ef4444',
|
||||
info: '#06b6d4',
|
||||
secondary: '#6b7280'
|
||||
},
|
||||
|
||||
// 图表默认配置
|
||||
defaultOptions: {
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
plugins: {
|
||||
legend: {
|
||||
position: 'top',
|
||||
labels: {
|
||||
usePointStyle: true,
|
||||
padding: 20
|
||||
}
|
||||
},
|
||||
tooltip: {
|
||||
backgroundColor: 'rgba(0, 0, 0, 0.8)',
|
||||
titleColor: '#fff',
|
||||
bodyColor: '#fff',
|
||||
borderColor: '#374151',
|
||||
borderWidth: 1,
|
||||
cornerRadius: 8,
|
||||
displayColors: true
|
||||
}
|
||||
},
|
||||
scales: {
|
||||
x: {
|
||||
grid: {
|
||||
color: 'rgba(156, 163, 175, 0.1)'
|
||||
},
|
||||
ticks: {
|
||||
color: '#6b7280'
|
||||
}
|
||||
},
|
||||
y: {
|
||||
grid: {
|
||||
color: 'rgba(156, 163, 175, 0.1)'
|
||||
},
|
||||
ticks: {
|
||||
color: '#6b7280'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// 线性图表组件
|
||||
class LineChart {
|
||||
constructor(canvasId, options = {}) {
|
||||
this.canvas = document.getElementById(canvasId);
|
||||
this.ctx = this.canvas.getContext('2d');
|
||||
this.options = { ...ChartConfig.defaultOptions, ...options };
|
||||
this.chart = null;
|
||||
this.data = {
|
||||
labels: [],
|
||||
datasets: []
|
||||
};
|
||||
}
|
||||
|
||||
// 初始化图表
|
||||
init(data, options = {}) {
|
||||
this.data = data;
|
||||
const config = {
|
||||
type: 'line',
|
||||
data: this.data,
|
||||
options: { ...this.options, ...options }
|
||||
};
|
||||
|
||||
if (this.chart) {
|
||||
this.chart.destroy();
|
||||
}
|
||||
|
||||
this.chart = new Chart(this.ctx, config);
|
||||
return this;
|
||||
}
|
||||
|
||||
// 更新数据
|
||||
updateData(newData) {
|
||||
if (this.chart) {
|
||||
this.chart.data = newData;
|
||||
this.chart.update();
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
// 添加数据点
|
||||
addDataPoint(label, values) {
|
||||
if (this.chart) {
|
||||
this.chart.data.labels.push(label);
|
||||
values.forEach((value, index) => {
|
||||
if (this.chart.data.datasets[index]) {
|
||||
this.chart.data.datasets[index].data.push(value);
|
||||
}
|
||||
});
|
||||
this.chart.update();
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
// 销毁图表
|
||||
destroy() {
|
||||
if (this.chart) {
|
||||
this.chart.destroy();
|
||||
this.chart = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 柱状图组件
|
||||
class BarChart {
|
||||
constructor(canvasId, options = {}) {
|
||||
this.canvas = document.getElementById(canvasId);
|
||||
this.ctx = this.canvas.getContext('2d');
|
||||
this.options = { ...ChartConfig.defaultOptions, ...options };
|
||||
this.chart = null;
|
||||
}
|
||||
|
||||
init(data, options = {}) {
|
||||
const config = {
|
||||
type: 'bar',
|
||||
data: data,
|
||||
options: { ...this.options, ...options }
|
||||
};
|
||||
|
||||
if (this.chart) {
|
||||
this.chart.destroy();
|
||||
}
|
||||
|
||||
this.chart = new Chart(this.ctx, config);
|
||||
return this;
|
||||
}
|
||||
|
||||
updateData(newData) {
|
||||
if (this.chart) {
|
||||
this.chart.data = newData;
|
||||
this.chart.update();
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
destroy() {
|
||||
if (this.chart) {
|
||||
this.chart.destroy();
|
||||
this.chart = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 饼图组件
|
||||
class PieChart {
|
||||
constructor(canvasId, options = {}) {
|
||||
this.canvas = document.getElementById(canvasId);
|
||||
this.ctx = this.canvas.getContext('2d');
|
||||
this.options = {
|
||||
...ChartConfig.defaultOptions,
|
||||
...options,
|
||||
scales: undefined // 饼图不需要坐标轴
|
||||
};
|
||||
this.chart = null;
|
||||
}
|
||||
|
||||
init(data, options = {}) {
|
||||
const config = {
|
||||
type: 'pie',
|
||||
data: data,
|
||||
options: { ...this.options, ...options }
|
||||
};
|
||||
|
||||
if (this.chart) {
|
||||
this.chart.destroy();
|
||||
}
|
||||
|
||||
this.chart = new Chart(this.ctx, config);
|
||||
return this;
|
||||
}
|
||||
|
||||
updateData(newData) {
|
||||
if (this.chart) {
|
||||
this.chart.data = newData;
|
||||
this.chart.update();
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
destroy() {
|
||||
if (this.chart) {
|
||||
this.chart.destroy();
|
||||
this.chart = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 环形图组件
|
||||
class DoughnutChart {
|
||||
constructor(canvasId, options = {}) {
|
||||
this.canvas = document.getElementById(canvasId);
|
||||
this.ctx = this.canvas.getContext('2d');
|
||||
this.options = {
|
||||
...ChartConfig.defaultOptions,
|
||||
...options,
|
||||
scales: undefined // 环形图不需要坐标轴
|
||||
};
|
||||
this.chart = null;
|
||||
}
|
||||
|
||||
init(data, options = {}) {
|
||||
const config = {
|
||||
type: 'doughnut',
|
||||
data: data,
|
||||
options: { ...this.options, ...options }
|
||||
};
|
||||
|
||||
if (this.chart) {
|
||||
this.chart.destroy();
|
||||
}
|
||||
|
||||
this.chart = new Chart(this.ctx, config);
|
||||
return this;
|
||||
}
|
||||
|
||||
updateData(newData) {
|
||||
if (this.chart) {
|
||||
this.chart.data = newData;
|
||||
this.chart.update();
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
destroy() {
|
||||
if (this.chart) {
|
||||
this.chart.destroy();
|
||||
this.chart = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 实时图表组件(用于监控数据)
|
||||
class RealTimeChart extends LineChart {
|
||||
constructor(canvasId, options = {}) {
|
||||
super(canvasId, options);
|
||||
this.maxDataPoints = options.maxDataPoints || 50;
|
||||
this.updateInterval = options.updateInterval || 5000;
|
||||
this.isRunning = false;
|
||||
this.intervalId = null;
|
||||
}
|
||||
|
||||
// 开始实时更新
|
||||
start(dataCallback) {
|
||||
if (this.isRunning) return;
|
||||
|
||||
this.isRunning = true;
|
||||
this.intervalId = setInterval(() => {
|
||||
if (typeof dataCallback === 'function') {
|
||||
const newData = dataCallback();
|
||||
this.addRealTimeData(newData);
|
||||
}
|
||||
}, this.updateInterval);
|
||||
}
|
||||
|
||||
// 停止实时更新
|
||||
stop() {
|
||||
if (this.intervalId) {
|
||||
clearInterval(this.intervalId);
|
||||
this.intervalId = null;
|
||||
}
|
||||
this.isRunning = false;
|
||||
}
|
||||
|
||||
// 添加实时数据
|
||||
addRealTimeData(data) {
|
||||
if (!this.chart) return;
|
||||
|
||||
const { label, values } = data;
|
||||
|
||||
// 添加新数据点
|
||||
this.chart.data.labels.push(label);
|
||||
values.forEach((value, index) => {
|
||||
if (this.chart.data.datasets[index]) {
|
||||
this.chart.data.datasets[index].data.push(value);
|
||||
}
|
||||
});
|
||||
|
||||
// 限制数据点数量
|
||||
if (this.chart.data.labels.length > this.maxDataPoints) {
|
||||
this.chart.data.labels.shift();
|
||||
this.chart.data.datasets.forEach(dataset => {
|
||||
dataset.data.shift();
|
||||
});
|
||||
}
|
||||
|
||||
this.chart.update('none'); // 无动画更新,提高性能
|
||||
}
|
||||
|
||||
destroy() {
|
||||
this.stop();
|
||||
super.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
// 图表工厂类
|
||||
class ChartFactory {
|
||||
static createLineChart(canvasId, data, options = {}) {
|
||||
const chart = new LineChart(canvasId, options);
|
||||
return chart.init(data, options);
|
||||
}
|
||||
|
||||
static createBarChart(canvasId, data, options = {}) {
|
||||
const chart = new BarChart(canvasId, options);
|
||||
return chart.init(data, options);
|
||||
}
|
||||
|
||||
static createPieChart(canvasId, data, options = {}) {
|
||||
const chart = new PieChart(canvasId, options);
|
||||
return chart.init(data, options);
|
||||
}
|
||||
|
||||
static createDoughnutChart(canvasId, data, options = {}) {
|
||||
const chart = new DoughnutChart(canvasId, options);
|
||||
return chart.init(data, options);
|
||||
}
|
||||
|
||||
static createRealTimeChart(canvasId, data, options = {}) {
|
||||
const chart = new RealTimeChart(canvasId, options);
|
||||
return chart.init(data, options);
|
||||
}
|
||||
}
|
||||
|
||||
// 数据生成器(用于演示)
|
||||
class DataGenerator {
|
||||
// 生成时间序列数据
|
||||
static generateTimeSeriesData(hours = 24, datasets = 1) {
|
||||
const labels = [];
|
||||
const now = new Date();
|
||||
|
||||
for (let i = hours - 1; i >= 0; i--) {
|
||||
const time = new Date(now.getTime() - i * 60 * 60 * 1000);
|
||||
labels.push(time.toLocaleTimeString('zh-CN', { hour: '2-digit', minute: '2-digit' }));
|
||||
}
|
||||
|
||||
const datasetsArray = [];
|
||||
const colors = [
|
||||
ChartConfig.colors.primary,
|
||||
ChartConfig.colors.success,
|
||||
ChartConfig.colors.warning,
|
||||
ChartConfig.colors.danger
|
||||
];
|
||||
|
||||
for (let i = 0; i < datasets; i++) {
|
||||
datasetsArray.push({
|
||||
label: `数据集 ${i + 1}`,
|
||||
data: Array.from({ length: hours }, () => Math.floor(Math.random() * 100)),
|
||||
borderColor: colors[i % colors.length],
|
||||
backgroundColor: colors[i % colors.length] + '20',
|
||||
fill: false,
|
||||
tension: 0.4
|
||||
});
|
||||
}
|
||||
|
||||
return { labels, datasets: datasetsArray };
|
||||
}
|
||||
|
||||
// 生成饼图数据
|
||||
static generatePieData(categories) {
|
||||
const colors = [
|
||||
ChartConfig.colors.primary,
|
||||
ChartConfig.colors.success,
|
||||
ChartConfig.colors.warning,
|
||||
ChartConfig.colors.danger,
|
||||
ChartConfig.colors.info,
|
||||
ChartConfig.colors.secondary
|
||||
];
|
||||
|
||||
return {
|
||||
labels: categories,
|
||||
datasets: [{
|
||||
data: categories.map(() => Math.floor(Math.random() * 100) + 10),
|
||||
backgroundColor: colors.slice(0, categories.length),
|
||||
borderWidth: 2,
|
||||
borderColor: '#fff'
|
||||
}]
|
||||
};
|
||||
}
|
||||
|
||||
// 生成柱状图数据
|
||||
static generateBarData(categories, datasets = 1) {
|
||||
const colors = [
|
||||
ChartConfig.colors.primary,
|
||||
ChartConfig.colors.success,
|
||||
ChartConfig.colors.warning,
|
||||
ChartConfig.colors.danger
|
||||
];
|
||||
|
||||
const datasetsArray = [];
|
||||
for (let i = 0; i < datasets; i++) {
|
||||
datasetsArray.push({
|
||||
label: `数据集 ${i + 1}`,
|
||||
data: categories.map(() => Math.floor(Math.random() * 100)),
|
||||
backgroundColor: colors[i % colors.length],
|
||||
borderColor: colors[i % colors.length],
|
||||
borderWidth: 1
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
labels: categories,
|
||||
datasets: datasetsArray
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// 导出到全局
|
||||
window.ChartConfig = ChartConfig;
|
||||
window.LineChart = LineChart;
|
||||
window.BarChart = BarChart;
|
||||
window.PieChart = PieChart;
|
||||
window.DoughnutChart = DoughnutChart;
|
||||
window.RealTimeChart = RealTimeChart;
|
||||
window.ChartFactory = ChartFactory;
|
||||
window.DataGenerator = DataGenerator;
|
||||
|
||||
console.log('图表组件库已加载');
|
||||
@ -0,0 +1,690 @@
|
||||
// 通用组件库
|
||||
|
||||
// 模态框组件
|
||||
class Modal {
|
||||
constructor(options = {}) {
|
||||
this.options = {
|
||||
title: '提示',
|
||||
content: '',
|
||||
width: '500px',
|
||||
height: 'auto',
|
||||
closable: true,
|
||||
maskClosable: true,
|
||||
showFooter: true,
|
||||
confirmText: '确定',
|
||||
cancelText: '取消',
|
||||
onConfirm: null,
|
||||
onCancel: null,
|
||||
onClose: null,
|
||||
...options
|
||||
};
|
||||
|
||||
this.element = null;
|
||||
this.isVisible = false;
|
||||
this.create();
|
||||
}
|
||||
|
||||
create() {
|
||||
this.element = document.createElement('div');
|
||||
this.element.className = 'modal-overlay';
|
||||
this.element.innerHTML = `
|
||||
<div class="modal-container" style="width: ${this.options.width}; height: ${this.options.height};">
|
||||
<div class="modal-header">
|
||||
<h3 class="modal-title">${this.options.title}</h3>
|
||||
${this.options.closable ? '<button class="modal-close"><i class="icon-x"></i></button>' : ''}
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
${this.options.content}
|
||||
</div>
|
||||
${this.options.showFooter ? `
|
||||
<div class="modal-footer">
|
||||
<button class="btn btn-secondary modal-cancel">${this.options.cancelText}</button>
|
||||
<button class="btn btn-primary modal-confirm">${this.options.confirmText}</button>
|
||||
</div>
|
||||
` : ''}
|
||||
</div>
|
||||
`;
|
||||
|
||||
this.bindEvents();
|
||||
}
|
||||
|
||||
bindEvents() {
|
||||
// 关闭按钮
|
||||
const closeBtn = this.element.querySelector('.modal-close');
|
||||
if (closeBtn) {
|
||||
closeBtn.addEventListener('click', () => this.close());
|
||||
}
|
||||
|
||||
// 遮罩点击关闭
|
||||
if (this.options.maskClosable) {
|
||||
this.element.addEventListener('click', (e) => {
|
||||
if (e.target === this.element) {
|
||||
this.close();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 确定按钮
|
||||
const confirmBtn = this.element.querySelector('.modal-confirm');
|
||||
if (confirmBtn) {
|
||||
confirmBtn.addEventListener('click', () => {
|
||||
if (this.options.onConfirm) {
|
||||
const result = this.options.onConfirm();
|
||||
if (result !== false) {
|
||||
this.close();
|
||||
}
|
||||
} else {
|
||||
this.close();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 取消按钮
|
||||
const cancelBtn = this.element.querySelector('.modal-cancel');
|
||||
if (cancelBtn) {
|
||||
cancelBtn.addEventListener('click', () => {
|
||||
if (this.options.onCancel) {
|
||||
this.options.onCancel();
|
||||
}
|
||||
this.close();
|
||||
});
|
||||
}
|
||||
|
||||
// ESC键关闭
|
||||
document.addEventListener('keydown', (e) => {
|
||||
if (e.key === 'Escape' && this.isVisible) {
|
||||
this.close();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
show() {
|
||||
if (!this.isVisible) {
|
||||
document.body.appendChild(this.element);
|
||||
this.isVisible = true;
|
||||
|
||||
// 添加动画
|
||||
setTimeout(() => {
|
||||
this.element.classList.add('show');
|
||||
}, 10);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
close() {
|
||||
if (this.isVisible) {
|
||||
this.element.classList.remove('show');
|
||||
|
||||
setTimeout(() => {
|
||||
if (this.element.parentElement) {
|
||||
document.body.removeChild(this.element);
|
||||
}
|
||||
this.isVisible = false;
|
||||
|
||||
if (this.options.onClose) {
|
||||
this.options.onClose();
|
||||
}
|
||||
}, 300);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
setContent(content) {
|
||||
const body = this.element.querySelector('.modal-body');
|
||||
if (body) {
|
||||
body.innerHTML = content;
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
setTitle(title) {
|
||||
const titleElement = this.element.querySelector('.modal-title');
|
||||
if (titleElement) {
|
||||
titleElement.textContent = title;
|
||||
}
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
// 加载组件
|
||||
class Loading {
|
||||
constructor(container = document.body, options = {}) {
|
||||
this.container = container;
|
||||
this.options = {
|
||||
text: '加载中...',
|
||||
size: 'medium',
|
||||
overlay: true,
|
||||
...options
|
||||
};
|
||||
|
||||
this.element = null;
|
||||
this.isVisible = false;
|
||||
this.create();
|
||||
}
|
||||
|
||||
create() {
|
||||
this.element = document.createElement('div');
|
||||
this.element.className = `loading-container ${this.options.size}`;
|
||||
|
||||
if (this.options.overlay) {
|
||||
this.element.classList.add('loading-overlay');
|
||||
}
|
||||
|
||||
this.element.innerHTML = `
|
||||
<div class="loading-content">
|
||||
<div class="loading-spinner">
|
||||
<div class="spinner-ring"></div>
|
||||
<div class="spinner-ring"></div>
|
||||
<div class="spinner-ring"></div>
|
||||
<div class="spinner-ring"></div>
|
||||
</div>
|
||||
${this.options.text ? `<div class="loading-text">${this.options.text}</div>` : ''}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
show() {
|
||||
if (!this.isVisible) {
|
||||
this.container.appendChild(this.element);
|
||||
this.isVisible = true;
|
||||
|
||||
setTimeout(() => {
|
||||
this.element.classList.add('show');
|
||||
}, 10);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
hide() {
|
||||
if (this.isVisible) {
|
||||
this.element.classList.remove('show');
|
||||
|
||||
setTimeout(() => {
|
||||
if (this.element.parentElement) {
|
||||
this.container.removeChild(this.element);
|
||||
}
|
||||
this.isVisible = false;
|
||||
}, 300);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
setText(text) {
|
||||
const textElement = this.element.querySelector('.loading-text');
|
||||
if (textElement) {
|
||||
textElement.textContent = text;
|
||||
}
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
// 通知组件
|
||||
class Notification {
|
||||
static container = null;
|
||||
static notifications = [];
|
||||
|
||||
static init() {
|
||||
if (!this.container) {
|
||||
this.container = document.createElement('div');
|
||||
this.container.className = 'notification-container';
|
||||
document.body.appendChild(this.container);
|
||||
}
|
||||
}
|
||||
|
||||
static show(message, type = 'info', options = {}) {
|
||||
this.init();
|
||||
|
||||
const config = {
|
||||
duration: 3000,
|
||||
closable: true,
|
||||
...options
|
||||
};
|
||||
|
||||
const notification = document.createElement('div');
|
||||
notification.className = `notification notification-${type}`;
|
||||
|
||||
const id = Utils.generateId();
|
||||
notification.setAttribute('data-id', id);
|
||||
|
||||
notification.innerHTML = `
|
||||
<div class="notification-content">
|
||||
<i class="notification-icon icon-${this.getIcon(type)}"></i>
|
||||
<span class="notification-message">${message}</span>
|
||||
${config.closable ? '<button class="notification-close"><i class="icon-x"></i></button>' : ''}
|
||||
</div>
|
||||
`;
|
||||
|
||||
// 绑定关闭事件
|
||||
if (config.closable) {
|
||||
const closeBtn = notification.querySelector('.notification-close');
|
||||
closeBtn.addEventListener('click', () => {
|
||||
this.remove(id);
|
||||
});
|
||||
}
|
||||
|
||||
this.container.appendChild(notification);
|
||||
this.notifications.push({ id, element: notification });
|
||||
|
||||
// 添加显示动画
|
||||
setTimeout(() => {
|
||||
notification.classList.add('show');
|
||||
}, 10);
|
||||
|
||||
// 自动移除
|
||||
if (config.duration > 0) {
|
||||
setTimeout(() => {
|
||||
this.remove(id);
|
||||
}, config.duration);
|
||||
}
|
||||
|
||||
return id;
|
||||
}
|
||||
|
||||
static remove(id) {
|
||||
const index = this.notifications.findIndex(n => n.id === id);
|
||||
if (index !== -1) {
|
||||
const notification = this.notifications[index];
|
||||
notification.element.classList.remove('show');
|
||||
|
||||
setTimeout(() => {
|
||||
if (notification.element.parentElement) {
|
||||
this.container.removeChild(notification.element);
|
||||
}
|
||||
this.notifications.splice(index, 1);
|
||||
}, 300);
|
||||
}
|
||||
}
|
||||
|
||||
static getIcon(type) {
|
||||
const icons = {
|
||||
success: 'check',
|
||||
error: 'x',
|
||||
warning: 'warning',
|
||||
info: 'info'
|
||||
};
|
||||
return icons[type] || 'info';
|
||||
}
|
||||
|
||||
static success(message, options) {
|
||||
return this.show(message, 'success', options);
|
||||
}
|
||||
|
||||
static error(message, options) {
|
||||
return this.show(message, 'error', options);
|
||||
}
|
||||
|
||||
static warning(message, options) {
|
||||
return this.show(message, 'warning', options);
|
||||
}
|
||||
|
||||
static info(message, options) {
|
||||
return this.show(message, 'info', options);
|
||||
}
|
||||
}
|
||||
|
||||
// 确认对话框
|
||||
class Confirm {
|
||||
static show(message, options = {}) {
|
||||
return new Promise((resolve) => {
|
||||
const config = {
|
||||
title: '确认',
|
||||
confirmText: '确定',
|
||||
cancelText: '取消',
|
||||
type: 'warning',
|
||||
...options
|
||||
};
|
||||
|
||||
const modal = new Modal({
|
||||
title: config.title,
|
||||
content: `
|
||||
<div class="confirm-content">
|
||||
<i class="confirm-icon icon-${config.type}"></i>
|
||||
<div class="confirm-message">${message}</div>
|
||||
</div>
|
||||
`,
|
||||
confirmText: config.confirmText,
|
||||
cancelText: config.cancelText,
|
||||
onConfirm: () => {
|
||||
resolve(true);
|
||||
},
|
||||
onCancel: () => {
|
||||
resolve(false);
|
||||
},
|
||||
onClose: () => {
|
||||
resolve(false);
|
||||
}
|
||||
});
|
||||
|
||||
modal.show();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 提示框
|
||||
class Alert {
|
||||
static show(message, type = 'info', options = {}) {
|
||||
return new Promise((resolve) => {
|
||||
const config = {
|
||||
title: '提示',
|
||||
confirmText: '确定',
|
||||
...options
|
||||
};
|
||||
|
||||
const modal = new Modal({
|
||||
title: config.title,
|
||||
content: `
|
||||
<div class="alert-content">
|
||||
<i class="alert-icon icon-${type}"></i>
|
||||
<div class="alert-message">${message}</div>
|
||||
</div>
|
||||
`,
|
||||
showFooter: true,
|
||||
confirmText: config.confirmText,
|
||||
cancelText: '',
|
||||
onConfirm: () => {
|
||||
resolve(true);
|
||||
},
|
||||
onClose: () => {
|
||||
resolve(true);
|
||||
}
|
||||
});
|
||||
|
||||
// 隐藏取消按钮
|
||||
modal.show();
|
||||
const cancelBtn = modal.element.querySelector('.modal-cancel');
|
||||
if (cancelBtn) {
|
||||
cancelBtn.style.display = 'none';
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
static success(message, options) {
|
||||
return this.show(message, 'success', options);
|
||||
}
|
||||
|
||||
static error(message, options) {
|
||||
return this.show(message, 'error', options);
|
||||
}
|
||||
|
||||
static warning(message, options) {
|
||||
return this.show(message, 'warning', options);
|
||||
}
|
||||
|
||||
static info(message, options) {
|
||||
return this.show(message, 'info', options);
|
||||
}
|
||||
}
|
||||
|
||||
// 工具提示组件
|
||||
class Tooltip {
|
||||
constructor(element, options = {}) {
|
||||
this.element = element;
|
||||
this.options = {
|
||||
content: '',
|
||||
placement: 'top',
|
||||
trigger: 'hover',
|
||||
delay: 100,
|
||||
...options
|
||||
};
|
||||
|
||||
this.tooltip = null;
|
||||
this.isVisible = false;
|
||||
this.init();
|
||||
}
|
||||
|
||||
init() {
|
||||
this.create();
|
||||
this.bindEvents();
|
||||
}
|
||||
|
||||
create() {
|
||||
this.tooltip = document.createElement('div');
|
||||
this.tooltip.className = `tooltip tooltip-${this.options.placement}`;
|
||||
this.tooltip.innerHTML = `
|
||||
<div class="tooltip-content">${this.options.content}</div>
|
||||
<div class="tooltip-arrow"></div>
|
||||
`;
|
||||
document.body.appendChild(this.tooltip);
|
||||
}
|
||||
|
||||
bindEvents() {
|
||||
if (this.options.trigger === 'hover') {
|
||||
this.element.addEventListener('mouseenter', () => {
|
||||
this.showDelay();
|
||||
});
|
||||
|
||||
this.element.addEventListener('mouseleave', () => {
|
||||
this.hide();
|
||||
});
|
||||
} else if (this.options.trigger === 'click') {
|
||||
this.element.addEventListener('click', () => {
|
||||
this.toggle();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
showDelay() {
|
||||
setTimeout(() => {
|
||||
this.show();
|
||||
}, this.options.delay);
|
||||
}
|
||||
|
||||
show() {
|
||||
if (!this.isVisible) {
|
||||
this.updatePosition();
|
||||
this.tooltip.classList.add('show');
|
||||
this.isVisible = true;
|
||||
}
|
||||
}
|
||||
|
||||
hide() {
|
||||
if (this.isVisible) {
|
||||
this.tooltip.classList.remove('show');
|
||||
this.isVisible = false;
|
||||
}
|
||||
}
|
||||
|
||||
toggle() {
|
||||
if (this.isVisible) {
|
||||
this.hide();
|
||||
} else {
|
||||
this.show();
|
||||
}
|
||||
}
|
||||
|
||||
updatePosition() {
|
||||
const rect = this.element.getBoundingClientRect();
|
||||
const tooltipRect = this.tooltip.getBoundingClientRect();
|
||||
|
||||
let top, left;
|
||||
|
||||
switch (this.options.placement) {
|
||||
case 'top':
|
||||
top = rect.top - tooltipRect.height - 8;
|
||||
left = rect.left + (rect.width - tooltipRect.width) / 2;
|
||||
break;
|
||||
case 'bottom':
|
||||
top = rect.bottom + 8;
|
||||
left = rect.left + (rect.width - tooltipRect.width) / 2;
|
||||
break;
|
||||
case 'left':
|
||||
top = rect.top + (rect.height - tooltipRect.height) / 2;
|
||||
left = rect.left - tooltipRect.width - 8;
|
||||
break;
|
||||
case 'right':
|
||||
top = rect.top + (rect.height - tooltipRect.height) / 2;
|
||||
left = rect.right + 8;
|
||||
break;
|
||||
}
|
||||
|
||||
this.tooltip.style.top = `${top}px`;
|
||||
this.tooltip.style.left = `${left}px`;
|
||||
}
|
||||
|
||||
setContent(content) {
|
||||
const contentElement = this.tooltip.querySelector('.tooltip-content');
|
||||
if (contentElement) {
|
||||
contentElement.innerHTML = content;
|
||||
}
|
||||
}
|
||||
|
||||
destroy() {
|
||||
if (this.tooltip && this.tooltip.parentElement) {
|
||||
document.body.removeChild(this.tooltip);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 下拉菜单组件
|
||||
class Dropdown {
|
||||
constructor(trigger, options = {}) {
|
||||
this.trigger = trigger;
|
||||
this.options = {
|
||||
items: [],
|
||||
placement: 'bottom-start',
|
||||
trigger: 'click',
|
||||
...options
|
||||
};
|
||||
|
||||
this.dropdown = null;
|
||||
this.isVisible = false;
|
||||
this.init();
|
||||
}
|
||||
|
||||
init() {
|
||||
this.create();
|
||||
this.bindEvents();
|
||||
}
|
||||
|
||||
create() {
|
||||
this.dropdown = document.createElement('div');
|
||||
this.dropdown.className = 'dropdown-menu';
|
||||
|
||||
const itemsHtml = this.options.items.map(item => {
|
||||
if (item.divider) {
|
||||
return '<div class="dropdown-divider"></div>';
|
||||
}
|
||||
|
||||
return `
|
||||
<div class="dropdown-item ${item.disabled ? 'disabled' : ''}" data-value="${item.value || ''}">
|
||||
${item.icon ? `<i class="dropdown-icon ${item.icon}"></i>` : ''}
|
||||
<span class="dropdown-text">${item.text}</span>
|
||||
</div>
|
||||
`;
|
||||
}).join('');
|
||||
|
||||
this.dropdown.innerHTML = itemsHtml;
|
||||
document.body.appendChild(this.dropdown);
|
||||
}
|
||||
|
||||
bindEvents() {
|
||||
// 触发器事件
|
||||
if (this.options.trigger === 'click') {
|
||||
this.trigger.addEventListener('click', (e) => {
|
||||
e.stopPropagation();
|
||||
this.toggle();
|
||||
});
|
||||
} else if (this.options.trigger === 'hover') {
|
||||
this.trigger.addEventListener('mouseenter', () => {
|
||||
this.show();
|
||||
});
|
||||
|
||||
this.trigger.addEventListener('mouseleave', () => {
|
||||
setTimeout(() => {
|
||||
if (!this.dropdown.matches(':hover')) {
|
||||
this.hide();
|
||||
}
|
||||
}, 100);
|
||||
});
|
||||
|
||||
this.dropdown.addEventListener('mouseleave', () => {
|
||||
this.hide();
|
||||
});
|
||||
}
|
||||
|
||||
// 点击菜单项
|
||||
this.dropdown.addEventListener('click', (e) => {
|
||||
const item = e.target.closest('.dropdown-item');
|
||||
if (item && !item.classList.contains('disabled')) {
|
||||
const value = item.getAttribute('data-value');
|
||||
if (this.options.onSelect) {
|
||||
this.options.onSelect(value, item);
|
||||
}
|
||||
this.hide();
|
||||
}
|
||||
});
|
||||
|
||||
// 点击外部关闭
|
||||
document.addEventListener('click', (e) => {
|
||||
if (!this.trigger.contains(e.target) && !this.dropdown.contains(e.target)) {
|
||||
this.hide();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
show() {
|
||||
if (!this.isVisible) {
|
||||
this.updatePosition();
|
||||
this.dropdown.classList.add('show');
|
||||
this.isVisible = true;
|
||||
}
|
||||
}
|
||||
|
||||
hide() {
|
||||
if (this.isVisible) {
|
||||
this.dropdown.classList.remove('show');
|
||||
this.isVisible = false;
|
||||
}
|
||||
}
|
||||
|
||||
toggle() {
|
||||
if (this.isVisible) {
|
||||
this.hide();
|
||||
} else {
|
||||
this.show();
|
||||
}
|
||||
}
|
||||
|
||||
updatePosition() {
|
||||
const rect = this.trigger.getBoundingClientRect();
|
||||
|
||||
let top, left;
|
||||
|
||||
switch (this.options.placement) {
|
||||
case 'bottom-start':
|
||||
top = rect.bottom + 4;
|
||||
left = rect.left;
|
||||
break;
|
||||
case 'bottom-end':
|
||||
top = rect.bottom + 4;
|
||||
left = rect.right - this.dropdown.offsetWidth;
|
||||
break;
|
||||
case 'top-start':
|
||||
top = rect.top - this.dropdown.offsetHeight - 4;
|
||||
left = rect.left;
|
||||
break;
|
||||
case 'top-end':
|
||||
top = rect.top - this.dropdown.offsetHeight - 4;
|
||||
left = rect.right - this.dropdown.offsetWidth;
|
||||
break;
|
||||
}
|
||||
|
||||
this.dropdown.style.top = `${top}px`;
|
||||
this.dropdown.style.left = `${left}px`;
|
||||
}
|
||||
|
||||
destroy() {
|
||||
if (this.dropdown && this.dropdown.parentElement) {
|
||||
document.body.removeChild(this.dropdown);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 导出组件
|
||||
window.Modal = Modal;
|
||||
window.Loading = Loading;
|
||||
window.Notification = Notification;
|
||||
window.Confirm = Confirm;
|
||||
window.Alert = Alert;
|
||||
window.Tooltip = Tooltip;
|
||||
window.Dropdown = Dropdown;
|
||||
@ -0,0 +1,380 @@
|
||||
/**
|
||||
* 演示数据文件
|
||||
* 为各个页面提供模拟数据以展示功能
|
||||
*/
|
||||
|
||||
// 演示数据管理器
|
||||
class DemoDataManager {
|
||||
constructor() {
|
||||
this.initData();
|
||||
}
|
||||
|
||||
// 初始化所有演示数据
|
||||
initData() {
|
||||
this.dashboardData = this.generateDashboardData();
|
||||
this.clusterData = this.generateClusterData();
|
||||
this.faultData = this.generateFaultData();
|
||||
this.logData = this.generateLogData();
|
||||
this.chartData = this.generateChartData();
|
||||
}
|
||||
|
||||
// 生成仪表板数据
|
||||
generateDashboardData() {
|
||||
return {
|
||||
stats: {
|
||||
onlineNodes: 12,
|
||||
activeAlerts: 3,
|
||||
avgCpuUsage: 68,
|
||||
avgMemoryUsage: 45
|
||||
},
|
||||
recentAlerts: [
|
||||
{
|
||||
id: 1,
|
||||
title: 'CPU使用率过高',
|
||||
node: 'node-01',
|
||||
level: 'warning',
|
||||
time: '2024-01-15 14:30:00'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
title: '磁盘空间不足',
|
||||
node: 'node-03',
|
||||
level: 'error',
|
||||
time: '2024-01-15 14:25:00'
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
title: '网络连接异常',
|
||||
node: 'node-07',
|
||||
level: 'warning',
|
||||
time: '2024-01-15 14:20:00'
|
||||
}
|
||||
]
|
||||
};
|
||||
}
|
||||
|
||||
// 生成集群数据
|
||||
generateClusterData() {
|
||||
const nodes = [];
|
||||
const statuses = ['online', 'warning', 'offline'];
|
||||
const nodeTypes = ['master', 'worker', 'storage'];
|
||||
|
||||
for (let i = 1; i <= 12; i++) {
|
||||
const nodeId = `node-${i.toString().padStart(2, '0')}`;
|
||||
const status = statuses[Math.floor(Math.random() * statuses.length)];
|
||||
|
||||
nodes.push({
|
||||
id: nodeId,
|
||||
name: `节点 ${i}`,
|
||||
ip: `192.168.1.${100 + i}`,
|
||||
type: nodeTypes[Math.floor(Math.random() * nodeTypes.length)],
|
||||
status: status,
|
||||
cpu: Math.floor(Math.random() * 100),
|
||||
memory: Math.floor(Math.random() * 100),
|
||||
disk: Math.floor(Math.random() * 100),
|
||||
network: Math.floor(Math.random() * 1000),
|
||||
uptime: Math.floor(Math.random() * 30) + 1,
|
||||
lastUpdate: new Date(Date.now() - Math.random() * 300000).toLocaleString('zh-CN')
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
overview: {
|
||||
total: nodes.length,
|
||||
online: nodes.filter(n => n.status === 'online').length,
|
||||
warning: nodes.filter(n => n.status === 'warning').length,
|
||||
offline: nodes.filter(n => n.status === 'offline').length,
|
||||
avgCpu: Math.floor(nodes.reduce((sum, n) => sum + n.cpu, 0) / nodes.length),
|
||||
cpuTrend: '+2.3%'
|
||||
},
|
||||
nodes: nodes
|
||||
};
|
||||
}
|
||||
|
||||
// 生成故障数据
|
||||
generateFaultData() {
|
||||
const faults = [];
|
||||
const levels = ['critical', 'high', 'medium', 'low'];
|
||||
const types = ['hardware', 'software', 'network', 'security'];
|
||||
const statuses = ['open', 'in_progress', 'resolved', 'closed'];
|
||||
const assignees = ['张三', '李四', '王五', '赵六', '钱七'];
|
||||
|
||||
for (let i = 1; i <= 50; i++) {
|
||||
const faultId = `F${Date.now().toString().slice(-6)}${i.toString().padStart(2, '0')}`;
|
||||
const level = levels[Math.floor(Math.random() * levels.length)];
|
||||
const type = types[Math.floor(Math.random() * types.length)];
|
||||
const status = statuses[Math.floor(Math.random() * statuses.length)];
|
||||
|
||||
faults.push({
|
||||
id: faultId,
|
||||
title: this.generateFaultTitle(type),
|
||||
description: this.generateFaultDescription(type),
|
||||
level: level,
|
||||
type: type,
|
||||
status: status,
|
||||
assignee: assignees[Math.floor(Math.random() * assignees.length)],
|
||||
reporter: assignees[Math.floor(Math.random() * assignees.length)],
|
||||
node: `node-${Math.floor(Math.random() * 12) + 1}`,
|
||||
createdAt: new Date(Date.now() - Math.random() * 7 * 24 * 60 * 60 * 1000).toLocaleString('zh-CN'),
|
||||
updatedAt: new Date(Date.now() - Math.random() * 24 * 60 * 60 * 1000).toLocaleString('zh-CN'),
|
||||
resolvedAt: status === 'resolved' ? new Date(Date.now() - Math.random() * 12 * 60 * 60 * 1000).toLocaleString('zh-CN') : null
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
overview: {
|
||||
total: faults.length,
|
||||
critical: faults.filter(f => f.level === 'critical').length,
|
||||
high: faults.filter(f => f.level === 'high').length,
|
||||
medium: faults.filter(f => f.level === 'medium').length,
|
||||
resolved: faults.filter(f => f.status === 'resolved').length
|
||||
},
|
||||
faults: faults
|
||||
};
|
||||
}
|
||||
|
||||
// 生成日志数据
|
||||
generateLogData() {
|
||||
const logs = [];
|
||||
const levels = ['ERROR', 'WARN', 'INFO', 'DEBUG'];
|
||||
const services = ['api-gateway', 'user-service', 'order-service', 'payment-service', 'notification-service'];
|
||||
const hosts = ['host-01', 'host-02', 'host-03', 'host-04', 'host-05'];
|
||||
|
||||
for (let i = 1; i <= 200; i++) {
|
||||
const level = levels[Math.floor(Math.random() * levels.length)];
|
||||
const service = services[Math.floor(Math.random() * services.length)];
|
||||
const host = hosts[Math.floor(Math.random() * hosts.length)];
|
||||
|
||||
logs.push({
|
||||
id: i,
|
||||
timestamp: new Date(Date.now() - Math.random() * 24 * 60 * 60 * 1000).toISOString(),
|
||||
level: level,
|
||||
service: service,
|
||||
host: host,
|
||||
message: this.generateLogMessage(level, service),
|
||||
details: this.generateLogDetails(level, service),
|
||||
userId: Math.random() > 0.7 ? `user_${Math.floor(Math.random() * 1000)}` : null,
|
||||
requestId: `req_${Math.random().toString(36).substr(2, 9)}`,
|
||||
ip: `192.168.1.${Math.floor(Math.random() * 255)}`
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
overview: {
|
||||
total: logs.length,
|
||||
error: logs.filter(l => l.level === 'ERROR').length,
|
||||
warn: logs.filter(l => l.level === 'WARN').length,
|
||||
info: logs.filter(l => l.level === 'INFO').length,
|
||||
availability: '99.8%'
|
||||
},
|
||||
logs: logs.sort((a, b) => new Date(b.timestamp) - new Date(a.timestamp))
|
||||
};
|
||||
}
|
||||
|
||||
// 生成图表数据
|
||||
generateChartData() {
|
||||
return {
|
||||
// 性能趋势数据
|
||||
performanceTrend: DataGenerator.generateTimeSeriesData(24, 3),
|
||||
|
||||
// CPU使用率数据
|
||||
cpuUsage: DataGenerator.generateTimeSeriesData(12, 1),
|
||||
|
||||
// 内存使用率数据
|
||||
memoryUsage: DataGenerator.generateTimeSeriesData(12, 1),
|
||||
|
||||
// 故障分布数据
|
||||
faultDistribution: DataGenerator.generatePieData(['硬件故障', '软件故障', '网络故障', '安全故障']),
|
||||
|
||||
// 日志级别分布
|
||||
logLevelDistribution: DataGenerator.generatePieData(['ERROR', 'WARN', 'INFO', 'DEBUG']),
|
||||
|
||||
// 服务日志分布
|
||||
serviceLogDistribution: DataGenerator.generateBarData(['api-gateway', 'user-service', 'order-service', 'payment-service'])
|
||||
};
|
||||
}
|
||||
|
||||
// 生成故障标题
|
||||
generateFaultTitle(type) {
|
||||
const titles = {
|
||||
hardware: ['CPU温度过高', '内存故障', '硬盘损坏', '网卡故障', '电源异常'],
|
||||
software: ['应用程序崩溃', '服务无响应', '数据库连接失败', '配置错误', '版本冲突'],
|
||||
network: ['网络连接中断', '带宽不足', 'DNS解析失败', '路由异常', '防火墙阻断'],
|
||||
security: ['异常登录尝试', '权限提升攻击', '恶意文件检测', 'SQL注入尝试', '暴力破解']
|
||||
};
|
||||
|
||||
const typeTitle = titles[type] || titles.software;
|
||||
return typeTitle[Math.floor(Math.random() * typeTitle.length)];
|
||||
}
|
||||
|
||||
// 生成故障描述
|
||||
generateFaultDescription(type) {
|
||||
const descriptions = {
|
||||
hardware: '硬件设备出现异常,可能影响系统正常运行',
|
||||
software: '软件服务出现问题,需要及时处理以避免业务中断',
|
||||
network: '网络连接出现异常,可能影响服务间通信',
|
||||
security: '检测到安全威胁,需要立即采取防护措施'
|
||||
};
|
||||
|
||||
return descriptions[type] || descriptions.software;
|
||||
}
|
||||
|
||||
// 生成日志消息
|
||||
generateLogMessage(level, service) {
|
||||
const messages = {
|
||||
ERROR: [
|
||||
'Database connection failed',
|
||||
'Authentication failed for user',
|
||||
'Service timeout occurred',
|
||||
'Invalid request format',
|
||||
'Internal server error'
|
||||
],
|
||||
WARN: [
|
||||
'High memory usage detected',
|
||||
'Slow query performance',
|
||||
'Rate limit approaching',
|
||||
'Cache miss ratio high',
|
||||
'Deprecated API usage'
|
||||
],
|
||||
INFO: [
|
||||
'User login successful',
|
||||
'Request processed successfully',
|
||||
'Service started',
|
||||
'Configuration updated',
|
||||
'Backup completed'
|
||||
],
|
||||
DEBUG: [
|
||||
'Processing request',
|
||||
'Cache hit',
|
||||
'Validation passed',
|
||||
'Query executed',
|
||||
'Response sent'
|
||||
]
|
||||
};
|
||||
|
||||
const levelMessages = messages[level] || messages.INFO;
|
||||
return `[${service}] ${levelMessages[Math.floor(Math.random() * levelMessages.length)]}`;
|
||||
}
|
||||
|
||||
// 生成日志详情
|
||||
generateLogDetails(level, service) {
|
||||
return `详细信息: ${service} 服务在处理请求时出现 ${level} 级别的事件。请查看相关日志以获取更多信息。`;
|
||||
}
|
||||
|
||||
// 获取仪表板数据
|
||||
getDashboardData() {
|
||||
return this.dashboardData;
|
||||
}
|
||||
|
||||
// 获取集群数据
|
||||
getClusterData() {
|
||||
return this.clusterData;
|
||||
}
|
||||
|
||||
// 获取故障数据
|
||||
getFaultData() {
|
||||
return this.faultData;
|
||||
}
|
||||
|
||||
// 获取日志数据
|
||||
getLogData() {
|
||||
return this.logData;
|
||||
}
|
||||
|
||||
// 获取图表数据
|
||||
getChartData() {
|
||||
return this.chartData;
|
||||
}
|
||||
|
||||
// 模拟实时数据更新
|
||||
getRealtimeData() {
|
||||
const now = new Date();
|
||||
return {
|
||||
timestamp: now.toLocaleTimeString('zh-CN'),
|
||||
cpu: Math.floor(Math.random() * 100),
|
||||
memory: Math.floor(Math.random() * 100),
|
||||
network: Math.floor(Math.random() * 1000),
|
||||
activeConnections: Math.floor(Math.random() * 500)
|
||||
};
|
||||
}
|
||||
|
||||
// 搜索故障
|
||||
searchFaults(query, filters = {}) {
|
||||
let results = [...this.faultData.faults];
|
||||
|
||||
// 文本搜索
|
||||
if (query) {
|
||||
results = results.filter(fault =>
|
||||
fault.title.toLowerCase().includes(query.toLowerCase()) ||
|
||||
fault.description.toLowerCase().includes(query.toLowerCase()) ||
|
||||
fault.id.toLowerCase().includes(query.toLowerCase())
|
||||
);
|
||||
}
|
||||
|
||||
// 级别筛选
|
||||
if (filters.level) {
|
||||
results = results.filter(fault => fault.level === filters.level);
|
||||
}
|
||||
|
||||
// 状态筛选
|
||||
if (filters.status) {
|
||||
results = results.filter(fault => fault.status === filters.status);
|
||||
}
|
||||
|
||||
// 类型筛选
|
||||
if (filters.type) {
|
||||
results = results.filter(fault => fault.type === filters.type);
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
// 搜索日志
|
||||
searchLogs(query, filters = {}) {
|
||||
let results = [...this.logData.logs];
|
||||
|
||||
// 文本搜索
|
||||
if (query) {
|
||||
results = results.filter(log =>
|
||||
log.message.toLowerCase().includes(query.toLowerCase()) ||
|
||||
log.service.toLowerCase().includes(query.toLowerCase())
|
||||
);
|
||||
}
|
||||
|
||||
// 级别筛选
|
||||
if (filters.level) {
|
||||
results = results.filter(log => log.level === filters.level);
|
||||
}
|
||||
|
||||
// 服务筛选
|
||||
if (filters.service) {
|
||||
results = results.filter(log => log.service === filters.service);
|
||||
}
|
||||
|
||||
// 主机筛选
|
||||
if (filters.host) {
|
||||
results = results.filter(log => log.host === filters.host);
|
||||
}
|
||||
|
||||
// 时间范围筛选
|
||||
if (filters.startTime && filters.endTime) {
|
||||
const start = new Date(filters.startTime);
|
||||
const end = new Date(filters.endTime);
|
||||
results = results.filter(log => {
|
||||
const logTime = new Date(log.timestamp);
|
||||
return logTime >= start && logTime <= end;
|
||||
});
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
}
|
||||
|
||||
// 创建全局实例
|
||||
const demoData = new DemoDataManager();
|
||||
|
||||
// 导出到全局
|
||||
window.DemoDataManager = DemoDataManager;
|
||||
window.demoData = demoData;
|
||||
|
||||
console.log('演示数据管理器已加载');
|
||||
@ -0,0 +1,634 @@
|
||||
/* 集群监控页面样式 */
|
||||
.cluster-monitor-page {
|
||||
padding: 20px;
|
||||
background: var(--bg-color);
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
/* 页面头部 */
|
||||
.page-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: flex-start;
|
||||
margin-bottom: 24px;
|
||||
padding-bottom: 16px;
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
}
|
||||
|
||||
.header-content .page-title {
|
||||
font-size: 28px;
|
||||
font-weight: 600;
|
||||
color: var(--text-primary);
|
||||
margin: 0 0 8px 0;
|
||||
}
|
||||
|
||||
.header-content .page-desc {
|
||||
font-size: 14px;
|
||||
color: var(--text-secondary);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.header-actions {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
/* 集群概览统计 */
|
||||
.cluster-overview {
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.overview-cards {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.overview-card {
|
||||
background: var(--card-bg);
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 8px;
|
||||
padding: 20px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.overview-card:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.card-icon {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
border-radius: 8px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
.card-icon.online {
|
||||
background: rgba(34, 197, 94, 0.1);
|
||||
color: var(--success-color);
|
||||
}
|
||||
|
||||
.card-icon.warning {
|
||||
background: rgba(251, 191, 36, 0.1);
|
||||
color: var(--warning-color);
|
||||
}
|
||||
|
||||
.card-icon.danger {
|
||||
background: rgba(239, 68, 68, 0.1);
|
||||
color: var(--danger-color);
|
||||
}
|
||||
|
||||
.card-icon.info {
|
||||
background: rgba(59, 130, 246, 0.1);
|
||||
color: var(--primary-color);
|
||||
}
|
||||
|
||||
.card-content {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.card-value {
|
||||
font-size: 24px;
|
||||
font-weight: 600;
|
||||
color: var(--text-primary);
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.card-label {
|
||||
font-size: 14px;
|
||||
color: var(--text-secondary);
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.card-trend {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.card-trend.up {
|
||||
color: var(--success-color);
|
||||
}
|
||||
|
||||
.card-trend.down {
|
||||
color: var(--danger-color);
|
||||
}
|
||||
|
||||
/* 主要内容区域 */
|
||||
.cluster-content {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 24px;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
/* 节点列表 */
|
||||
.cluster-nodes {
|
||||
background: var(--card-bg);
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.section-header {
|
||||
padding: 20px;
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: var(--text-primary);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.section-actions {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.filter-group select {
|
||||
min-width: 120px;
|
||||
}
|
||||
|
||||
.search-group {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.search-group input {
|
||||
padding-right: 36px;
|
||||
min-width: 200px;
|
||||
}
|
||||
|
||||
.search-group .icon-search {
|
||||
position: absolute;
|
||||
right: 12px;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
color: var(--text-secondary);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.nodes-container {
|
||||
max-height: 600px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
/* 节点卡片 */
|
||||
.node-card {
|
||||
padding: 16px 20px;
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
transition: background-color 0.2s ease;
|
||||
}
|
||||
|
||||
.node-card:hover {
|
||||
background: var(--hover-bg);
|
||||
}
|
||||
|
||||
.node-card:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.node-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.node-info .node-name {
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
color: var(--text-primary);
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.node-info .node-ip {
|
||||
font-size: 14px;
|
||||
color: var(--text-secondary);
|
||||
font-family: 'Courier New', monospace;
|
||||
}
|
||||
|
||||
.status-badge {
|
||||
padding: 4px 8px;
|
||||
border-radius: 4px;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.status-badge.online {
|
||||
background: rgba(34, 197, 94, 0.1);
|
||||
color: var(--success-color);
|
||||
}
|
||||
|
||||
.status-badge.warning {
|
||||
background: rgba(251, 191, 36, 0.1);
|
||||
color: var(--warning-color);
|
||||
}
|
||||
|
||||
.status-badge.offline {
|
||||
background: rgba(239, 68, 68, 0.1);
|
||||
color: var(--danger-color);
|
||||
}
|
||||
|
||||
/* 节点指标 */
|
||||
.node-metrics {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.metric-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.metric-item:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.metric-label {
|
||||
width: 40px;
|
||||
font-size: 12px;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
.metric-value {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.progress-bar {
|
||||
flex: 1;
|
||||
height: 6px;
|
||||
background: var(--border-color);
|
||||
border-radius: 3px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.progress-fill {
|
||||
height: 100%;
|
||||
background: var(--success-color);
|
||||
border-radius: 3px;
|
||||
transition: width 0.3s ease;
|
||||
}
|
||||
|
||||
.progress-fill.warning {
|
||||
background: var(--warning-color);
|
||||
}
|
||||
|
||||
.progress-fill.danger {
|
||||
background: var(--danger-color);
|
||||
}
|
||||
|
||||
.metric-text {
|
||||
font-size: 12px;
|
||||
color: var(--text-secondary);
|
||||
min-width: 32px;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
/* 节点操作 */
|
||||
.node-actions {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.btn-icon {
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
border: 1px solid var(--border-color);
|
||||
background: transparent;
|
||||
border-radius: 4px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.btn-icon:hover {
|
||||
background: var(--hover-bg);
|
||||
border-color: var(--primary-color);
|
||||
}
|
||||
|
||||
/* 监控图表 */
|
||||
.cluster-charts {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.chart-section {
|
||||
background: var(--card-bg);
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.chart-header {
|
||||
padding: 16px 20px;
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.chart-title {
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
color: var(--text-primary);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.chart-controls select {
|
||||
min-width: 120px;
|
||||
}
|
||||
|
||||
.chart-legend {
|
||||
display: flex;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.legend-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
font-size: 12px;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
.legend-color {
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
.legend-color.cpu {
|
||||
background: #3b82f6;
|
||||
}
|
||||
|
||||
.legend-color.memory {
|
||||
background: #10b981;
|
||||
}
|
||||
|
||||
.legend-color.disk {
|
||||
background: #f59e0b;
|
||||
}
|
||||
|
||||
.traffic-stats {
|
||||
display: flex;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.stat-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
}
|
||||
|
||||
.stat-label {
|
||||
font-size: 12px;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
.stat-value {
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.chart-container {
|
||||
padding: 20px;
|
||||
height: 300px;
|
||||
}
|
||||
|
||||
.chart-placeholder {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: var(--bg-color);
|
||||
border: 2px dashed var(--border-color);
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.chart-loading {
|
||||
text-align: center;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
.chart-loading p {
|
||||
margin: 8px 0 0 0;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
/* 实时告警面板 */
|
||||
.alert-panel {
|
||||
background: var(--card-bg);
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.alert-header {
|
||||
padding: 16px 20px;
|
||||
background: rgba(239, 68, 68, 0.05);
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.alert-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
color: var(--text-primary);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.alert-count {
|
||||
background: var(--danger-color);
|
||||
color: white;
|
||||
padding: 2px 6px;
|
||||
border-radius: 10px;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.alert-toggle {
|
||||
background: transparent;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
padding: 4px;
|
||||
border-radius: 4px;
|
||||
transition: background-color 0.2s ease;
|
||||
}
|
||||
|
||||
.alert-toggle:hover {
|
||||
background: var(--hover-bg);
|
||||
}
|
||||
|
||||
.alert-content {
|
||||
max-height: 300px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.alert-item {
|
||||
padding: 16px 20px;
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.alert-item:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.alert-item.high {
|
||||
border-left: 4px solid var(--danger-color);
|
||||
}
|
||||
|
||||
.alert-item.medium {
|
||||
border-left: 4px solid var(--warning-color);
|
||||
}
|
||||
|
||||
.alert-item.low {
|
||||
border-left: 4px solid var(--info-color);
|
||||
}
|
||||
|
||||
.alert-icon {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.alert-info {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.alert-message {
|
||||
font-size: 14px;
|
||||
color: var(--text-primary);
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.alert-time {
|
||||
font-size: 12px;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
.alert-actions {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.btn-small {
|
||||
padding: 4px 12px;
|
||||
font-size: 12px;
|
||||
border-radius: 4px;
|
||||
border: 1px solid var(--border-color);
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.btn-small.btn-primary {
|
||||
background: var(--primary-color);
|
||||
color: white;
|
||||
border-color: var(--primary-color);
|
||||
}
|
||||
|
||||
.btn-small.btn-primary:hover {
|
||||
background: var(--primary-hover);
|
||||
}
|
||||
|
||||
.btn-small.btn-secondary {
|
||||
background: transparent;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
.btn-small.btn-secondary:hover {
|
||||
background: var(--hover-bg);
|
||||
}
|
||||
|
||||
/* 响应式设计 */
|
||||
@media (max-width: 1200px) {
|
||||
.cluster-content {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.overview-cards {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.cluster-monitor-page {
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.page-header {
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.header-actions {
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.overview-cards {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.section-header {
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.section-actions {
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.search-group input {
|
||||
min-width: auto;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.chart-header {
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.chart-legend {
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.traffic-stats {
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,519 @@
|
||||
/* 通用组件样式 */
|
||||
|
||||
/* 模态框样式 */
|
||||
.modal-overlay {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 1000;
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.modal-overlay.show {
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
.modal-container {
|
||||
background: var(--card-bg);
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
|
||||
max-width: 90vw;
|
||||
max-height: 90vh;
|
||||
overflow: hidden;
|
||||
transform: scale(0.9) translateY(-20px);
|
||||
transition: transform 0.3s ease;
|
||||
}
|
||||
|
||||
.modal-overlay.show .modal-container {
|
||||
transform: scale(1) translateY(0);
|
||||
}
|
||||
|
||||
.modal-header {
|
||||
padding: 20px 24px;
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
background: var(--bg-secondary);
|
||||
}
|
||||
|
||||
.modal-title {
|
||||
margin: 0;
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.modal-close {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border: none;
|
||||
background: none;
|
||||
border-radius: 4px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
color: var(--text-secondary);
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.modal-close:hover {
|
||||
background: var(--bg-color);
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.modal-body {
|
||||
padding: 24px;
|
||||
max-height: 60vh;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.modal-footer {
|
||||
padding: 16px 24px;
|
||||
border-top: 1px solid var(--border-color);
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 12px;
|
||||
background: var(--bg-secondary);
|
||||
}
|
||||
|
||||
/* 确认对话框样式 */
|
||||
.confirm-content {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
padding: 20px 0;
|
||||
}
|
||||
|
||||
.confirm-icon {
|
||||
font-size: 32px;
|
||||
color: #f59e0b;
|
||||
}
|
||||
|
||||
.confirm-message {
|
||||
font-size: 16px;
|
||||
color: var(--text-primary);
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
/* 提示框样式 */
|
||||
.alert-content {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
padding: 20px 0;
|
||||
}
|
||||
|
||||
.alert-icon {
|
||||
font-size: 32px;
|
||||
}
|
||||
|
||||
.alert-icon.icon-success {
|
||||
color: #10b981;
|
||||
}
|
||||
|
||||
.alert-icon.icon-error {
|
||||
color: #ef4444;
|
||||
}
|
||||
|
||||
.alert-icon.icon-warning {
|
||||
color: #f59e0b;
|
||||
}
|
||||
|
||||
.alert-icon.icon-info {
|
||||
color: #3b82f6;
|
||||
}
|
||||
|
||||
.alert-message {
|
||||
font-size: 16px;
|
||||
color: var(--text-primary);
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
/* 加载组件样式 */
|
||||
.loading-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.loading-container.show {
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
.loading-overlay {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: rgba(255, 255, 255, 0.8);
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
.dark-theme .loading-overlay {
|
||||
background: rgba(0, 0, 0, 0.8);
|
||||
}
|
||||
|
||||
.loading-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.loading-spinner {
|
||||
position: relative;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
}
|
||||
|
||||
.loading-container.small .loading-spinner {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
}
|
||||
|
||||
.loading-container.large .loading-spinner {
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
}
|
||||
|
||||
.spinner-ring {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border: 3px solid transparent;
|
||||
border-top-color: var(--primary-color);
|
||||
border-radius: 50%;
|
||||
animation: spin 1.2s linear infinite;
|
||||
}
|
||||
|
||||
.spinner-ring:nth-child(2) {
|
||||
animation-delay: -0.3s;
|
||||
border-top-color: rgba(var(--primary-color-rgb), 0.7);
|
||||
}
|
||||
|
||||
.spinner-ring:nth-child(3) {
|
||||
animation-delay: -0.6s;
|
||||
border-top-color: rgba(var(--primary-color-rgb), 0.4);
|
||||
}
|
||||
|
||||
.spinner-ring:nth-child(4) {
|
||||
animation-delay: -0.9s;
|
||||
border-top-color: rgba(var(--primary-color-rgb), 0.2);
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
0% {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
100% {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
.loading-text {
|
||||
font-size: 14px;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
.loading-container.small .loading-text {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.loading-container.large .loading-text {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
/* 通知组件样式 */
|
||||
.notification-container {
|
||||
position: fixed;
|
||||
top: 20px;
|
||||
right: 20px;
|
||||
z-index: 2000;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
max-width: 400px;
|
||||
}
|
||||
|
||||
.notification {
|
||||
background: var(--card-bg);
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
||||
overflow: hidden;
|
||||
transform: translateX(100%);
|
||||
opacity: 0;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.notification.show {
|
||||
transform: translateX(0);
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.notification-success {
|
||||
border-left: 4px solid #10b981;
|
||||
}
|
||||
|
||||
.notification-error {
|
||||
border-left: 4px solid #ef4444;
|
||||
}
|
||||
|
||||
.notification-warning {
|
||||
border-left: 4px solid #f59e0b;
|
||||
}
|
||||
|
||||
.notification-info {
|
||||
border-left: 4px solid #3b82f6;
|
||||
}
|
||||
|
||||
.notification-content {
|
||||
padding: 16px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.notification-icon {
|
||||
font-size: 18px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.notification-success .notification-icon {
|
||||
color: #10b981;
|
||||
}
|
||||
|
||||
.notification-error .notification-icon {
|
||||
color: #ef4444;
|
||||
}
|
||||
|
||||
.notification-warning .notification-icon {
|
||||
color: #f59e0b;
|
||||
}
|
||||
|
||||
.notification-info .notification-icon {
|
||||
color: #3b82f6;
|
||||
}
|
||||
|
||||
.notification-message {
|
||||
flex: 1;
|
||||
font-size: 14px;
|
||||
color: var(--text-primary);
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.notification-close {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
border: none;
|
||||
background: none;
|
||||
border-radius: 4px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
color: var(--text-secondary);
|
||||
transition: all 0.2s ease;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.notification-close:hover {
|
||||
background: var(--bg-secondary);
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
/* 工具提示样式 */
|
||||
.tooltip {
|
||||
position: absolute;
|
||||
z-index: 1500;
|
||||
background: rgba(0, 0, 0, 0.9);
|
||||
color: white;
|
||||
border-radius: 4px;
|
||||
font-size: 12px;
|
||||
padding: 8px 12px;
|
||||
max-width: 200px;
|
||||
word-wrap: break-word;
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
transition: all 0.2s ease;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.tooltip.show {
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
.tooltip-content {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.tooltip-arrow {
|
||||
position: absolute;
|
||||
width: 0;
|
||||
height: 0;
|
||||
border: 4px solid transparent;
|
||||
}
|
||||
|
||||
.tooltip-top .tooltip-arrow {
|
||||
bottom: -8px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
border-top-color: rgba(0, 0, 0, 0.9);
|
||||
}
|
||||
|
||||
.tooltip-bottom .tooltip-arrow {
|
||||
top: -8px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
border-bottom-color: rgba(0, 0, 0, 0.9);
|
||||
}
|
||||
|
||||
.tooltip-left .tooltip-arrow {
|
||||
right: -8px;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
border-left-color: rgba(0, 0, 0, 0.9);
|
||||
}
|
||||
|
||||
.tooltip-right .tooltip-arrow {
|
||||
left: -8px;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
border-right-color: rgba(0, 0, 0, 0.9);
|
||||
}
|
||||
|
||||
/* 下拉菜单样式 */
|
||||
.dropdown-menu {
|
||||
position: absolute;
|
||||
z-index: 1200;
|
||||
background: var(--card-bg);
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 6px;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
||||
min-width: 160px;
|
||||
max-width: 300px;
|
||||
padding: 4px 0;
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
transform: translateY(-8px);
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.dropdown-menu.show {
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
.dropdown-item {
|
||||
padding: 8px 16px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
color: var(--text-primary);
|
||||
transition: background-color 0.2s ease;
|
||||
}
|
||||
|
||||
.dropdown-item:hover:not(.disabled) {
|
||||
background: var(--bg-secondary);
|
||||
}
|
||||
|
||||
.dropdown-item.disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.dropdown-icon {
|
||||
font-size: 16px;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
.dropdown-text {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.dropdown-divider {
|
||||
height: 1px;
|
||||
background: var(--border-color);
|
||||
margin: 4px 0;
|
||||
}
|
||||
|
||||
/* 响应式设计 */
|
||||
@media (max-width: 768px) {
|
||||
.modal-container {
|
||||
width: 95vw !important;
|
||||
max-height: 95vh;
|
||||
}
|
||||
|
||||
.modal-header,
|
||||
.modal-body,
|
||||
.modal-footer {
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.notification-container {
|
||||
left: 20px;
|
||||
right: 20px;
|
||||
max-width: none;
|
||||
}
|
||||
|
||||
.notification {
|
||||
transform: translateY(-100%);
|
||||
}
|
||||
|
||||
.notification.show {
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
.modal-header,
|
||||
.modal-body,
|
||||
.modal-footer {
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
.modal-footer {
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.modal-footer .btn {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.notification-container {
|
||||
top: 10px;
|
||||
left: 10px;
|
||||
right: 10px;
|
||||
}
|
||||
|
||||
.confirm-content,
|
||||
.alert-content {
|
||||
flex-direction: column;
|
||||
text-align: center;
|
||||
gap: 12px;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,133 @@
|
||||
/* 深色主题样式 */
|
||||
.dark {
|
||||
/* 主色调 */
|
||||
--primary-color: #409EFF;
|
||||
--primary-light: #79BBFF;
|
||||
--primary-dark: #337ECC;
|
||||
|
||||
/* 背景色 - 深色主题 */
|
||||
--bg-color: #1A1A1A;
|
||||
--bg-secondary: #2D2D2D;
|
||||
--bg-tertiary: #3A3A3A;
|
||||
|
||||
/* 文字颜色 - 深色主题 */
|
||||
--text-primary: #E5E5E5;
|
||||
--text-regular: #CCCCCC;
|
||||
--text-secondary: #999999;
|
||||
--text-placeholder: #666666;
|
||||
|
||||
/* 边框颜色 - 深色主题 */
|
||||
--border-color: #404040;
|
||||
--border-light: #4A4A4A;
|
||||
--border-lighter: #555555;
|
||||
|
||||
/* 状态颜色 - 深色主题适配 */
|
||||
--success-color: #67C23A;
|
||||
--warning-color: #E6A23C;
|
||||
--danger-color: #F56C6C;
|
||||
--info-color: #909399;
|
||||
|
||||
/* 阴影 - 深色主题 */
|
||||
--box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.3);
|
||||
--box-shadow-light: 0 2px 4px rgba(0, 0, 0, 0.2), 0 0 6px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
/* 深色主题下的特殊样式调整 */
|
||||
.dark body {
|
||||
background-color: var(--bg-color);
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
/* 深色主题下的表格行悬停效果 */
|
||||
.dark .data-table tr:hover {
|
||||
background-color: var(--bg-tertiary);
|
||||
}
|
||||
|
||||
/* 深色主题下的状态标签 */
|
||||
.dark .status-tag.online {
|
||||
background-color: rgba(103, 194, 58, 0.2);
|
||||
color: #81C784;
|
||||
border: 1px solid rgba(103, 194, 58, 0.3);
|
||||
}
|
||||
|
||||
.dark .status-tag.offline {
|
||||
background-color: rgba(245, 108, 108, 0.2);
|
||||
color: #EF5350;
|
||||
border: 1px solid rgba(245, 108, 108, 0.3);
|
||||
}
|
||||
|
||||
.dark .status-tag.warning {
|
||||
background-color: rgba(230, 162, 60, 0.2);
|
||||
color: #FFB74D;
|
||||
border: 1px solid rgba(230, 162, 60, 0.3);
|
||||
}
|
||||
|
||||
/* 深色主题下的表单控件 */
|
||||
.dark .form-control {
|
||||
background-color: var(--bg-secondary);
|
||||
border-color: var(--border-color);
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.dark .form-control:focus {
|
||||
background-color: var(--bg-secondary);
|
||||
border-color: var(--primary-color);
|
||||
box-shadow: 0 0 0 2px rgba(64, 158, 255, 0.3);
|
||||
}
|
||||
|
||||
/* 深色主题下的按钮样式调整 */
|
||||
.dark .btn-secondary {
|
||||
background-color: var(--bg-secondary);
|
||||
color: var(--text-regular);
|
||||
border-color: var(--border-color);
|
||||
}
|
||||
|
||||
.dark .btn-secondary:hover {
|
||||
background-color: var(--bg-tertiary);
|
||||
border-color: var(--border-light);
|
||||
}
|
||||
|
||||
/* 深色主题下的加载动画 */
|
||||
.dark .loading-spinner {
|
||||
border-color: var(--border-color);
|
||||
border-top-color: var(--primary-color);
|
||||
}
|
||||
|
||||
/* 深色主题下的空状态 */
|
||||
.dark .empty-state {
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
.dark .empty-state-desc {
|
||||
color: var(--text-placeholder);
|
||||
}
|
||||
|
||||
/* 深色主题切换动画 */
|
||||
* {
|
||||
transition: background-color 0.3s ease, color 0.3s ease, border-color 0.3s ease;
|
||||
}
|
||||
|
||||
/* 深色主题下的滚动条样式 */
|
||||
.dark ::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
}
|
||||
|
||||
.dark ::-webkit-scrollbar-track {
|
||||
background-color: var(--bg-secondary);
|
||||
}
|
||||
|
||||
.dark ::-webkit-scrollbar-thumb {
|
||||
background-color: var(--border-color);
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.dark ::-webkit-scrollbar-thumb:hover {
|
||||
background-color: var(--border-light);
|
||||
}
|
||||
|
||||
/* 深色主题下的选中文本 */
|
||||
.dark ::selection {
|
||||
background-color: rgba(64, 158, 255, 0.3);
|
||||
color: var(--text-primary);
|
||||
}
|
||||
@ -0,0 +1,735 @@
|
||||
/* 故障管理页面样式 */
|
||||
.fault-manage-page {
|
||||
padding: 20px;
|
||||
background: var(--bg-color);
|
||||
min-height: 100vh;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
/* 页面头部 */
|
||||
.page-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: flex-start;
|
||||
margin-bottom: 24px;
|
||||
padding-bottom: 16px;
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
}
|
||||
|
||||
.header-content .page-title {
|
||||
font-size: 28px;
|
||||
font-weight: 600;
|
||||
color: var(--text-primary);
|
||||
margin: 0 0 8px 0;
|
||||
}
|
||||
|
||||
.header-content .page-desc {
|
||||
font-size: 14px;
|
||||
color: var(--text-secondary);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.header-actions {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
/* 故障统计概览 */
|
||||
.fault-overview {
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.overview-cards {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.overview-card {
|
||||
background: var(--card-bg);
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 8px;
|
||||
padding: 20px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.overview-card:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.overview-card.critical .card-icon {
|
||||
background: rgba(239, 68, 68, 0.1);
|
||||
color: var(--danger-color);
|
||||
}
|
||||
|
||||
.overview-card.high .card-icon {
|
||||
background: rgba(251, 191, 36, 0.1);
|
||||
color: var(--warning-color);
|
||||
}
|
||||
|
||||
.overview-card.medium .card-icon {
|
||||
background: rgba(59, 130, 246, 0.1);
|
||||
color: var(--primary-color);
|
||||
}
|
||||
|
||||
.overview-card.resolved .card-icon {
|
||||
background: rgba(34, 197, 94, 0.1);
|
||||
color: var(--success-color);
|
||||
}
|
||||
|
||||
.card-icon {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
border-radius: 8px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
.card-content {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.card-value {
|
||||
font-size: 24px;
|
||||
font-weight: 600;
|
||||
color: var(--text-primary);
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.card-label {
|
||||
font-size: 14px;
|
||||
color: var(--text-secondary);
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.card-trend {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.card-trend.up {
|
||||
color: var(--success-color);
|
||||
}
|
||||
|
||||
.card-trend.down {
|
||||
color: var(--danger-color);
|
||||
}
|
||||
|
||||
/* 故障筛选 */
|
||||
.fault-filters {
|
||||
background: var(--card-bg);
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 8px;
|
||||
padding: 20px;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.filter-section {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||||
gap: 16px;
|
||||
align-items: end;
|
||||
}
|
||||
|
||||
.filter-group {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.filter-label {
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.search-group {
|
||||
grid-column: span 2;
|
||||
}
|
||||
|
||||
.search-input {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.search-input input {
|
||||
padding-right: 36px;
|
||||
}
|
||||
|
||||
.search-input .icon-search {
|
||||
position: absolute;
|
||||
right: 12px;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
color: var(--text-secondary);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.filter-actions {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
/* 故障列表容器 */
|
||||
.fault-list-container {
|
||||
background: var(--card-bg);
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.list-header {
|
||||
padding: 20px;
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.list-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.list-title h2 {
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: var(--text-primary);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.fault-count {
|
||||
font-size: 14px;
|
||||
color: var(--text-secondary);
|
||||
background: var(--bg-color);
|
||||
padding: 4px 8px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.list-controls {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.view-toggle {
|
||||
display: flex;
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 4px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.view-btn {
|
||||
padding: 8px 12px;
|
||||
background: transparent;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.view-btn.active {
|
||||
background: var(--primary-color);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.view-btn:not(.active):hover {
|
||||
background: var(--hover-bg);
|
||||
}
|
||||
|
||||
.sort-group select {
|
||||
min-width: 150px;
|
||||
}
|
||||
|
||||
/* 故障表格 */
|
||||
.fault-table-view {
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.fault-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
.fault-table th {
|
||||
background: var(--bg-color);
|
||||
padding: 12px 16px;
|
||||
text-align: left;
|
||||
font-weight: 500;
|
||||
color: var(--text-primary);
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.fault-table th.sortable {
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.fault-table th.sortable:hover {
|
||||
background: var(--hover-bg);
|
||||
}
|
||||
|
||||
.fault-table th span {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.fault-table td {
|
||||
padding: 16px;
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
.fault-row:hover {
|
||||
background: var(--hover-bg);
|
||||
}
|
||||
|
||||
/* 故障行样式 */
|
||||
.fault-row.critical {
|
||||
border-left: 4px solid var(--danger-color);
|
||||
}
|
||||
|
||||
.fault-row.high {
|
||||
border-left: 4px solid var(--warning-color);
|
||||
}
|
||||
|
||||
.fault-row.resolved {
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
/* 故障ID */
|
||||
.fault-id .id-text {
|
||||
font-family: 'Courier New', monospace;
|
||||
font-size: 14px;
|
||||
color: var(--primary-color);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* 故障标题 */
|
||||
.fault-title {
|
||||
max-width: 300px;
|
||||
}
|
||||
|
||||
.title-content .title-text {
|
||||
font-size: 14px;
|
||||
color: var(--text-primary);
|
||||
font-weight: 500;
|
||||
display: block;
|
||||
margin-bottom: 4px;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.title-meta .affected-systems {
|
||||
font-size: 12px;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
/* 严重程度标签 */
|
||||
.severity-badge {
|
||||
padding: 4px 8px;
|
||||
border-radius: 4px;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.severity-badge.critical {
|
||||
background: rgba(239, 68, 68, 0.1);
|
||||
color: var(--danger-color);
|
||||
}
|
||||
|
||||
.severity-badge.high {
|
||||
background: rgba(251, 191, 36, 0.1);
|
||||
color: var(--warning-color);
|
||||
}
|
||||
|
||||
.severity-badge.medium {
|
||||
background: rgba(59, 130, 246, 0.1);
|
||||
color: var(--primary-color);
|
||||
}
|
||||
|
||||
.severity-badge.low {
|
||||
background: rgba(34, 197, 94, 0.1);
|
||||
color: var(--success-color);
|
||||
}
|
||||
|
||||
/* 故障类型标签 */
|
||||
.type-badge {
|
||||
padding: 4px 8px;
|
||||
border-radius: 4px;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
background: var(--bg-color);
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
.type-badge.hardware {
|
||||
background: rgba(168, 85, 247, 0.1);
|
||||
color: #8b5cf6;
|
||||
}
|
||||
|
||||
.type-badge.software {
|
||||
background: rgba(59, 130, 246, 0.1);
|
||||
color: var(--primary-color);
|
||||
}
|
||||
|
||||
.type-badge.network {
|
||||
background: rgba(34, 197, 94, 0.1);
|
||||
color: var(--success-color);
|
||||
}
|
||||
|
||||
.type-badge.performance {
|
||||
background: rgba(251, 191, 36, 0.1);
|
||||
color: var(--warning-color);
|
||||
}
|
||||
|
||||
.type-badge.security {
|
||||
background: rgba(239, 68, 68, 0.1);
|
||||
color: var(--danger-color);
|
||||
}
|
||||
|
||||
/* 状态标签 */
|
||||
.status-badge {
|
||||
padding: 4px 8px;
|
||||
border-radius: 4px;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.status-badge.open {
|
||||
background: rgba(239, 68, 68, 0.1);
|
||||
color: var(--danger-color);
|
||||
}
|
||||
|
||||
.status-badge.in-progress {
|
||||
background: rgba(251, 191, 36, 0.1);
|
||||
color: var(--warning-color);
|
||||
}
|
||||
|
||||
.status-badge.resolved {
|
||||
background: rgba(34, 197, 94, 0.1);
|
||||
color: var(--success-color);
|
||||
}
|
||||
|
||||
.status-badge.closed {
|
||||
background: var(--bg-color);
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
/* 负责人信息 */
|
||||
.assignee-info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.assignee-info .avatar {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border-radius: 50%;
|
||||
background: var(--primary-color);
|
||||
color: white;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.assignee-info .name {
|
||||
font-size: 14px;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
/* 时间信息 */
|
||||
.time-info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
}
|
||||
|
||||
.time-info .date {
|
||||
font-size: 14px;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.time-info .time {
|
||||
font-size: 12px;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
/* 故障操作 */
|
||||
.fault-actions {
|
||||
display: flex;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.action-btn {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border: 1px solid var(--border-color);
|
||||
background: transparent;
|
||||
border-radius: 4px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.action-btn:hover {
|
||||
background: var(--hover-bg);
|
||||
border-color: var(--primary-color);
|
||||
}
|
||||
|
||||
/* 分页控件 */
|
||||
.pagination-container {
|
||||
padding: 16px 20px;
|
||||
border-top: 1px solid var(--border-color);
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.pagination-info {
|
||||
font-size: 14px;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
.pagination-controls {
|
||||
display: flex;
|
||||
gap: 4px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.page-btn {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border: 1px solid var(--border-color);
|
||||
background: transparent;
|
||||
border-radius: 4px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.page-btn:hover:not(:disabled) {
|
||||
background: var(--hover-bg);
|
||||
}
|
||||
|
||||
.page-btn.active {
|
||||
background: var(--primary-color);
|
||||
color: white;
|
||||
border-color: var(--primary-color);
|
||||
}
|
||||
|
||||
.page-btn:disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.page-dots {
|
||||
padding: 0 8px;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
.page-size-selector select {
|
||||
min-width: 100px;
|
||||
}
|
||||
|
||||
/* 故障详情侧边栏 */
|
||||
.fault-detail-sidebar {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
right: -500px;
|
||||
width: 500px;
|
||||
height: 100vh;
|
||||
background: var(--card-bg);
|
||||
border-left: 1px solid var(--border-color);
|
||||
z-index: 1000;
|
||||
transition: right 0.3s ease;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.fault-detail-sidebar.active {
|
||||
right: 0;
|
||||
}
|
||||
|
||||
.sidebar-header {
|
||||
padding: 20px;
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
background: var(--bg-color);
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.sidebar-title {
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: var(--text-primary);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.close-btn {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border: 1px solid var(--border-color);
|
||||
background: transparent;
|
||||
border-radius: 4px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.close-btn:hover {
|
||||
background: var(--hover-bg);
|
||||
}
|
||||
|
||||
.sidebar-content {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.fault-detail-loading {
|
||||
text-align: center;
|
||||
padding: 40px 20px;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
.fault-detail-loading p {
|
||||
margin: 12px 0 0 0;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
/* 遮罩层 */
|
||||
.sidebar-overlay {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
z-index: 999;
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.sidebar-overlay.active {
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
/* 响应式设计 */
|
||||
@media (max-width: 1200px) {
|
||||
.filter-section {
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
}
|
||||
|
||||
.search-group {
|
||||
grid-column: span 3;
|
||||
}
|
||||
|
||||
.fault-detail-sidebar {
|
||||
width: 400px;
|
||||
right: -400px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.fault-manage-page {
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.page-header {
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.header-actions {
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.overview-cards {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
}
|
||||
|
||||
.filter-section {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.search-group {
|
||||
grid-column: span 1;
|
||||
}
|
||||
|
||||
.list-header {
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.list-controls {
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.fault-table {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.fault-table th,
|
||||
.fault-table td {
|
||||
padding: 8px 12px;
|
||||
}
|
||||
|
||||
.fault-title {
|
||||
max-width: 200px;
|
||||
}
|
||||
|
||||
.pagination-container {
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.fault-detail-sidebar {
|
||||
width: 100%;
|
||||
right: -100%;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
.overview-cards {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.fault-table-view {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.fault-actions {
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
}
|
||||
|
||||
.action-btn {
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,735 @@
|
||||
/* 布局组件样式 */
|
||||
|
||||
/* ==================== 顶部导航栏样式 ==================== */
|
||||
.app-header {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 60px;
|
||||
background-color: var(--bg-color);
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 0 20px;
|
||||
z-index: 1000;
|
||||
box-shadow: var(--box-shadow-light);
|
||||
}
|
||||
|
||||
/* 头部左侧 */
|
||||
.header-left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.menu-toggle {
|
||||
display: none;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border: none;
|
||||
background: none;
|
||||
cursor: pointer;
|
||||
border-radius: 6px;
|
||||
color: var(--text-regular);
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.menu-toggle:hover {
|
||||
background-color: var(--bg-secondary);
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.system-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.system-title h1 {
|
||||
font-size: 20px;
|
||||
font-weight: 600;
|
||||
color: var(--text-primary);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.system-title .version {
|
||||
font-size: 12px;
|
||||
color: var(--text-secondary);
|
||||
background-color: var(--bg-secondary);
|
||||
padding: 2px 6px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
/* 头部中间 - 面包屑 */
|
||||
.header-center {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
max-width: 600px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.breadcrumb {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.breadcrumb-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
color: var(--text-secondary);
|
||||
cursor: pointer;
|
||||
transition: color 0.2s ease;
|
||||
}
|
||||
|
||||
.breadcrumb-item:hover {
|
||||
color: var(--primary-color);
|
||||
}
|
||||
|
||||
.breadcrumb-item.active {
|
||||
color: var(--text-primary);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.breadcrumb-separator {
|
||||
color: var(--text-placeholder);
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
/* 头部右侧 */
|
||||
.header-right {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
/* 系统状态指示器 */
|
||||
.system-status {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
font-size: 12px;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
.status-dot {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
border-radius: 50%;
|
||||
background-color: var(--success-color);
|
||||
animation: pulse 2s infinite;
|
||||
}
|
||||
|
||||
.status-dot.offline {
|
||||
background-color: var(--danger-color);
|
||||
}
|
||||
|
||||
.status-dot.warning {
|
||||
background-color: var(--warning-color);
|
||||
}
|
||||
|
||||
@keyframes pulse {
|
||||
0% { opacity: 1; }
|
||||
50% { opacity: 0.5; }
|
||||
100% { opacity: 1; }
|
||||
}
|
||||
|
||||
/* 通知中心 */
|
||||
.notification-center {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.notification-btn {
|
||||
position: relative;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border: none;
|
||||
background: none;
|
||||
cursor: pointer;
|
||||
border-radius: 6px;
|
||||
color: var(--text-regular);
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.notification-btn:hover {
|
||||
background-color: var(--bg-secondary);
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.notification-badge {
|
||||
position: absolute;
|
||||
top: 8px;
|
||||
right: 8px;
|
||||
background-color: var(--danger-color);
|
||||
color: white;
|
||||
font-size: 10px;
|
||||
padding: 2px 5px;
|
||||
border-radius: 8px;
|
||||
min-width: 16px;
|
||||
text-align: center;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
.notification-dropdown {
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
right: 0;
|
||||
width: 320px;
|
||||
background-color: var(--bg-color);
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 8px;
|
||||
box-shadow: var(--box-shadow);
|
||||
display: none;
|
||||
z-index: 1001;
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.notification-dropdown.show {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.notification-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 16px;
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
}
|
||||
|
||||
.notification-header h4 {
|
||||
margin: 0;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.mark-all-read {
|
||||
background: none;
|
||||
border: none;
|
||||
color: var(--primary-color);
|
||||
font-size: 12px;
|
||||
cursor: pointer;
|
||||
padding: 4px 8px;
|
||||
border-radius: 4px;
|
||||
transition: background-color 0.2s ease;
|
||||
}
|
||||
|
||||
.mark-all-read:hover {
|
||||
background-color: var(--bg-secondary);
|
||||
}
|
||||
|
||||
.notification-list {
|
||||
max-height: 300px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.notification-item {
|
||||
display: flex;
|
||||
padding: 12px 16px;
|
||||
border-bottom: 1px solid var(--border-lighter);
|
||||
transition: background-color 0.2s ease;
|
||||
}
|
||||
|
||||
.notification-item:hover {
|
||||
background-color: var(--bg-secondary);
|
||||
}
|
||||
|
||||
.notification-item.unread {
|
||||
background-color: rgba(64, 158, 255, 0.05);
|
||||
}
|
||||
|
||||
.notification-icon {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-right: 12px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.notification-icon.warning {
|
||||
background-color: rgba(230, 162, 60, 0.2);
|
||||
color: var(--warning-color);
|
||||
}
|
||||
|
||||
.notification-icon.danger {
|
||||
background-color: rgba(245, 108, 108, 0.2);
|
||||
color: var(--danger-color);
|
||||
}
|
||||
|
||||
.notification-icon.success {
|
||||
background-color: rgba(103, 194, 58, 0.2);
|
||||
color: var(--success-color);
|
||||
}
|
||||
|
||||
.notification-content {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.notification-title {
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
color: var(--text-primary);
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.notification-desc {
|
||||
font-size: 12px;
|
||||
color: var(--text-secondary);
|
||||
margin-bottom: 4px;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.notification-time {
|
||||
font-size: 11px;
|
||||
color: var(--text-placeholder);
|
||||
}
|
||||
|
||||
.notification-footer {
|
||||
padding: 12px 16px;
|
||||
text-align: center;
|
||||
border-top: 1px solid var(--border-color);
|
||||
}
|
||||
|
||||
.view-all {
|
||||
color: var(--primary-color);
|
||||
text-decoration: none;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.view-all:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
/* 主题切换 */
|
||||
.theme-toggle {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border: none;
|
||||
background: none;
|
||||
cursor: pointer;
|
||||
border-radius: 6px;
|
||||
color: var(--text-regular);
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.theme-toggle:hover {
|
||||
background-color: var(--bg-secondary);
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
/* 用户信息 */
|
||||
.user-info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.user-avatar img {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border-radius: 50%;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.user-dropdown {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.user-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
background: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
padding: 6px 12px;
|
||||
border-radius: 6px;
|
||||
color: var(--text-primary);
|
||||
transition: background-color 0.2s ease;
|
||||
}
|
||||
|
||||
.user-btn:hover {
|
||||
background-color: var(--bg-secondary);
|
||||
}
|
||||
|
||||
.user-name {
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.user-menu {
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
right: 0;
|
||||
width: 240px;
|
||||
background-color: var(--bg-color);
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 8px;
|
||||
box-shadow: var(--box-shadow);
|
||||
display: none;
|
||||
z-index: 1001;
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.user-menu.show {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.user-menu-header {
|
||||
padding: 16px;
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.user-avatar-large img {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
border-radius: 50%;
|
||||
object-fit: cover;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.user-name-large {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: var(--text-primary);
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.user-role {
|
||||
font-size: 12px;
|
||||
color: var(--text-secondary);
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
.user-email {
|
||||
font-size: 11px;
|
||||
color: var(--text-placeholder);
|
||||
}
|
||||
|
||||
.user-menu-body {
|
||||
padding: 8px 0;
|
||||
}
|
||||
|
||||
.user-menu-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
padding: 10px 16px;
|
||||
color: var(--text-regular);
|
||||
text-decoration: none;
|
||||
font-size: 14px;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.user-menu-item:hover {
|
||||
background-color: var(--bg-secondary);
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.user-menu-item.logout {
|
||||
color: var(--danger-color);
|
||||
}
|
||||
|
||||
.user-menu-item.logout:hover {
|
||||
background-color: rgba(245, 108, 108, 0.1);
|
||||
}
|
||||
|
||||
.user-menu-divider {
|
||||
height: 1px;
|
||||
background-color: var(--border-color);
|
||||
margin: 8px 0;
|
||||
}
|
||||
|
||||
/* ==================== 侧边栏样式 ==================== */
|
||||
.app-sidebar {
|
||||
position: fixed;
|
||||
top: 60px;
|
||||
left: 0;
|
||||
width: 240px;
|
||||
height: calc(100vh - 60px);
|
||||
background-color: var(--bg-color);
|
||||
border-right: 1px solid var(--border-color);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
z-index: 999;
|
||||
transition: width 0.3s ease;
|
||||
}
|
||||
|
||||
/* 侧边栏头部 */
|
||||
.sidebar-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 16px 20px;
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
}
|
||||
|
||||
.sidebar-logo {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.sidebar-title {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.sidebar-collapse {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border: none;
|
||||
background: none;
|
||||
cursor: pointer;
|
||||
border-radius: 4px;
|
||||
color: var(--text-secondary);
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.sidebar-collapse:hover {
|
||||
background-color: var(--bg-secondary);
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
/* 导航菜单 */
|
||||
.sidebar-nav {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
padding: 8px 0;
|
||||
}
|
||||
|
||||
.nav-menu {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.nav-item {
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
.nav-link {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
padding: 12px 20px;
|
||||
color: var(--text-regular);
|
||||
text-decoration: none;
|
||||
font-size: 14px;
|
||||
transition: all 0.2s ease;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.nav-link:hover {
|
||||
background-color: var(--bg-secondary);
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.nav-link.active {
|
||||
background-color: rgba(64, 158, 255, 0.1);
|
||||
color: var(--primary-color);
|
||||
border-right: 3px solid var(--primary-color);
|
||||
}
|
||||
|
||||
.nav-icon {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.nav-text {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.nav-badge {
|
||||
background-color: var(--success-color);
|
||||
color: white;
|
||||
font-size: 10px;
|
||||
padding: 2px 6px;
|
||||
border-radius: 8px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.nav-count {
|
||||
background-color: var(--bg-secondary);
|
||||
color: var(--text-secondary);
|
||||
font-size: 11px;
|
||||
padding: 2px 6px;
|
||||
border-radius: 8px;
|
||||
min-width: 18px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.nav-alert {
|
||||
background-color: var(--danger-color);
|
||||
color: white;
|
||||
font-size: 10px;
|
||||
padding: 2px 6px;
|
||||
border-radius: 8px;
|
||||
min-width: 16px;
|
||||
text-align: center;
|
||||
animation: pulse 2s infinite;
|
||||
}
|
||||
|
||||
/* 子菜单 */
|
||||
.nav-submenu {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
background-color: var(--bg-secondary);
|
||||
display: none;
|
||||
}
|
||||
|
||||
.nav-item:hover .nav-submenu,
|
||||
.nav-item.active .nav-submenu {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.nav-subitem {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.nav-sublink {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 8px 20px 8px 52px;
|
||||
color: var(--text-secondary);
|
||||
text-decoration: none;
|
||||
font-size: 13px;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.nav-sublink:hover {
|
||||
background-color: var(--bg-tertiary);
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.nav-sublink.active {
|
||||
color: var(--primary-color);
|
||||
background-color: rgba(64, 158, 255, 0.05);
|
||||
}
|
||||
|
||||
/* 分割线 */
|
||||
.nav-divider {
|
||||
height: 1px;
|
||||
background-color: var(--border-color);
|
||||
margin: 8px 20px;
|
||||
}
|
||||
|
||||
/* 侧边栏底部 */
|
||||
.sidebar-footer {
|
||||
padding: 16px 20px;
|
||||
border-top: 1px solid var(--border-color);
|
||||
}
|
||||
|
||||
.system-info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.system-version {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.version-label {
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
.version-number {
|
||||
color: var(--text-primary);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.system-status {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
font-size: 11px;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
/* 响应式设计 */
|
||||
@media (max-width: 768px) {
|
||||
.menu-toggle {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.app-sidebar {
|
||||
transform: translateX(-100%);
|
||||
transition: transform 0.3s ease;
|
||||
}
|
||||
|
||||
.app-sidebar.show {
|
||||
transform: translateX(0);
|
||||
}
|
||||
|
||||
.header-center {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.system-title h1 {
|
||||
font-size: 18px;
|
||||
}
|
||||
}
|
||||
|
||||
/* 收起状态 */
|
||||
.app-sidebar.collapsed {
|
||||
width: 60px;
|
||||
}
|
||||
|
||||
.app-sidebar.collapsed .sidebar-title,
|
||||
.app-sidebar.collapsed .nav-text,
|
||||
.app-sidebar.collapsed .nav-badge,
|
||||
.app-sidebar.collapsed .nav-count,
|
||||
.app-sidebar.collapsed .nav-alert {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.app-sidebar.collapsed .nav-submenu {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.app-sidebar.collapsed .nav-link {
|
||||
justify-content: center;
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
.app-sidebar.collapsed .sidebar-header {
|
||||
padding: 16px 12px;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.app-sidebar.collapsed .sidebar-collapse {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.app-sidebar.collapsed .sidebar-footer {
|
||||
padding: 16px 12px;
|
||||
}
|
||||
|
||||
.app-sidebar.collapsed .system-info {
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.app-sidebar.collapsed .system-version,
|
||||
.app-sidebar.collapsed .system-status {
|
||||
display: none;
|
||||
}
|
||||
@ -0,0 +1,759 @@
|
||||
/* 日志分析页面样式 */
|
||||
.log-analysis-page {
|
||||
padding: 20px;
|
||||
background: var(--bg-color);
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
/* 页面头部 */
|
||||
.page-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: flex-start;
|
||||
margin-bottom: 24px;
|
||||
padding-bottom: 16px;
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
}
|
||||
|
||||
.header-content h1 {
|
||||
margin: 0 0 8px 0;
|
||||
color: var(--text-primary);
|
||||
font-size: 28px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.header-content p {
|
||||
margin: 0;
|
||||
color: var(--text-secondary);
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.header-actions {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
/* 日志统计概览 */
|
||||
.log-overview {
|
||||
margin-bottom: 32px;
|
||||
}
|
||||
|
||||
.overview-cards {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.overview-card {
|
||||
background: var(--card-bg);
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 8px;
|
||||
padding: 20px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.overview-card:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.overview-card.total {
|
||||
border-left: 4px solid #3b82f6;
|
||||
}
|
||||
|
||||
.overview-card.error {
|
||||
border-left: 4px solid #ef4444;
|
||||
}
|
||||
|
||||
.overview-card.warning {
|
||||
border-left: 4px solid #f59e0b;
|
||||
}
|
||||
|
||||
.overview-card.info {
|
||||
border-left: 4px solid #10b981;
|
||||
}
|
||||
|
||||
.card-icon {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
border-radius: 8px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
.total .card-icon {
|
||||
background: rgba(59, 130, 246, 0.1);
|
||||
}
|
||||
|
||||
.error .card-icon {
|
||||
background: rgba(239, 68, 68, 0.1);
|
||||
}
|
||||
|
||||
.warning .card-icon {
|
||||
background: rgba(245, 158, 11, 0.1);
|
||||
}
|
||||
|
||||
.info .card-icon {
|
||||
background: rgba(16, 185, 129, 0.1);
|
||||
}
|
||||
|
||||
.card-content {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.card-value {
|
||||
font-size: 24px;
|
||||
font-weight: 700;
|
||||
color: var(--text-primary);
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.card-label {
|
||||
font-size: 14px;
|
||||
color: var(--text-secondary);
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.card-trend {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.card-trend.up {
|
||||
color: #10b981;
|
||||
}
|
||||
|
||||
.card-trend.down {
|
||||
color: #ef4444;
|
||||
}
|
||||
|
||||
/* 日志查询区域 */
|
||||
.log-query-section {
|
||||
background: var(--card-bg);
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 8px;
|
||||
padding: 24px;
|
||||
margin-bottom: 32px;
|
||||
}
|
||||
|
||||
.query-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
margin: 0;
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.query-actions {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.query-form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.query-row {
|
||||
display: flex;
|
||||
gap: 20px;
|
||||
align-items: flex-end;
|
||||
}
|
||||
|
||||
.query-group {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.query-group.full-width {
|
||||
flex: 1 1 100%;
|
||||
}
|
||||
|
||||
.query-label {
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.time-range-selector {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.absolute-time {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.time-separator {
|
||||
color: var(--text-secondary);
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.level-selector {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.checkbox-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.checkbox-item input[type="checkbox"] {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.level-badge {
|
||||
padding: 4px 8px;
|
||||
border-radius: 4px;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.level-badge.error {
|
||||
background: rgba(239, 68, 68, 0.1);
|
||||
color: #ef4444;
|
||||
border: 1px solid rgba(239, 68, 68, 0.2);
|
||||
}
|
||||
|
||||
.level-badge.warning {
|
||||
background: rgba(245, 158, 11, 0.1);
|
||||
color: #f59e0b;
|
||||
border: 1px solid rgba(245, 158, 11, 0.2);
|
||||
}
|
||||
|
||||
.level-badge.info {
|
||||
background: rgba(59, 130, 246, 0.1);
|
||||
color: #3b82f6;
|
||||
border: 1px solid rgba(59, 130, 246, 0.2);
|
||||
}
|
||||
|
||||
.level-badge.debug {
|
||||
background: rgba(107, 114, 128, 0.1);
|
||||
color: #6b7280;
|
||||
border: 1px solid rgba(107, 114, 128, 0.2);
|
||||
}
|
||||
|
||||
.search-input-group {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.search-options {
|
||||
display: flex;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.advanced-query {
|
||||
border-top: 1px solid var(--border-color);
|
||||
padding-top: 20px;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.query-controls {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding-top: 20px;
|
||||
border-top: 1px solid var(--border-color);
|
||||
}
|
||||
|
||||
.query-controls .btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
/* 日志分析结果区域 */
|
||||
.log-results-section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 32px;
|
||||
}
|
||||
|
||||
/* 日志图表 */
|
||||
.log-charts {
|
||||
display: grid;
|
||||
grid-template-columns: 2fr 1fr;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.chart-container {
|
||||
background: var(--card-bg);
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 8px;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.chart-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.chart-title {
|
||||
margin: 0;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.chart-legend {
|
||||
display: flex;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.legend-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
font-size: 12px;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
.legend-color {
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
.legend-color.error {
|
||||
background: #ef4444;
|
||||
}
|
||||
|
||||
.legend-color.warning {
|
||||
background: #f59e0b;
|
||||
}
|
||||
|
||||
.legend-color.info {
|
||||
background: #3b82f6;
|
||||
}
|
||||
|
||||
.chart-content {
|
||||
height: 300px;
|
||||
}
|
||||
|
||||
.chart-placeholder {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: var(--bg-secondary);
|
||||
border-radius: 4px;
|
||||
border: 2px dashed var(--border-color);
|
||||
}
|
||||
|
||||
.chart-loading {
|
||||
text-align: center;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
.chart-loading p {
|
||||
margin: 8px 0 0 0;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
/* 日志列表 */
|
||||
.log-list-container {
|
||||
background: var(--card-bg);
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.list-header {
|
||||
padding: 20px;
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
background: var(--bg-secondary);
|
||||
}
|
||||
|
||||
.list-info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.list-title {
|
||||
margin: 0;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.log-count {
|
||||
font-size: 14px;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
.search-status {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.status-text {
|
||||
color: #10b981;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.search-time {
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
.list-controls {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.display-options {
|
||||
display: flex;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.view-controls {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
/* 日志条目 */
|
||||
.log-list {
|
||||
max-height: 600px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.log-entry {
|
||||
padding: 16px 20px;
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
transition: background-color 0.2s ease;
|
||||
}
|
||||
|
||||
.log-entry:hover {
|
||||
background: var(--bg-secondary);
|
||||
}
|
||||
|
||||
.log-entry:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.log-entry.error {
|
||||
border-left: 3px solid #ef4444;
|
||||
}
|
||||
|
||||
.log-entry.warning {
|
||||
border-left: 3px solid #f59e0b;
|
||||
}
|
||||
|
||||
.log-entry.info {
|
||||
border-left: 3px solid #3b82f6;
|
||||
}
|
||||
|
||||
.log-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.log-timestamp {
|
||||
color: var(--text-secondary);
|
||||
font-family: 'Courier New', monospace;
|
||||
min-width: 180px;
|
||||
}
|
||||
|
||||
.log-level {
|
||||
padding: 2px 6px;
|
||||
border-radius: 3px;
|
||||
font-weight: 500;
|
||||
text-transform: uppercase;
|
||||
min-width: 50px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.log-level.error {
|
||||
background: rgba(239, 68, 68, 0.1);
|
||||
color: #ef4444;
|
||||
}
|
||||
|
||||
.log-level.warning {
|
||||
background: rgba(245, 158, 11, 0.1);
|
||||
color: #f59e0b;
|
||||
}
|
||||
|
||||
.log-level.info {
|
||||
background: rgba(59, 130, 246, 0.1);
|
||||
color: #3b82f6;
|
||||
}
|
||||
|
||||
.log-source {
|
||||
color: var(--text-primary);
|
||||
font-weight: 500;
|
||||
min-width: 100px;
|
||||
}
|
||||
|
||||
.log-host {
|
||||
color: var(--text-secondary);
|
||||
min-width: 120px;
|
||||
}
|
||||
|
||||
.log-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
margin-left: 12px;
|
||||
}
|
||||
|
||||
.log-message {
|
||||
color: var(--text-primary);
|
||||
font-size: 14px;
|
||||
line-height: 1.5;
|
||||
font-family: 'Courier New', monospace;
|
||||
}
|
||||
|
||||
.log-details {
|
||||
display: flex;
|
||||
gap: 16px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.log-detail-item {
|
||||
font-size: 12px;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
.log-detail-item strong {
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.log-actions {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
margin-left: auto;
|
||||
align-self: flex-start;
|
||||
}
|
||||
|
||||
.action-btn {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border: 1px solid var(--border-color);
|
||||
background: var(--card-bg);
|
||||
border-radius: 4px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
.action-btn:hover {
|
||||
background: var(--bg-secondary);
|
||||
color: var(--text-primary);
|
||||
border-color: var(--primary-color);
|
||||
}
|
||||
|
||||
/* 分页控件 */
|
||||
.pagination-container {
|
||||
padding: 16px 20px;
|
||||
border-top: 1px solid var(--border-color);
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
background: var(--bg-secondary);
|
||||
}
|
||||
|
||||
.pagination-info {
|
||||
font-size: 14px;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
.pagination-controls {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.page-btn {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border: 1px solid var(--border-color);
|
||||
background: var(--card-bg);
|
||||
border-radius: 4px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
font-size: 14px;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.page-btn:hover:not(:disabled) {
|
||||
background: var(--primary-color);
|
||||
color: white;
|
||||
border-color: var(--primary-color);
|
||||
}
|
||||
|
||||
.page-btn.active {
|
||||
background: var(--primary-color);
|
||||
color: white;
|
||||
border-color: var(--primary-color);
|
||||
}
|
||||
|
||||
.page-btn:disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.page-dots {
|
||||
padding: 0 8px;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
.jump-to-page {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
font-size: 14px;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
.jump-to-page input {
|
||||
width: 60px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* 响应式设计 */
|
||||
@media (max-width: 1200px) {
|
||||
.log-charts {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.chart-content {
|
||||
height: 250px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.log-analysis-page {
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.page-header {
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.overview-cards {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.query-row {
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.list-header {
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.list-info {
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.log-header {
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.log-details {
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.pagination-container {
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
.header-actions {
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.time-range-selector {
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.level-selector {
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.search-options {
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.query-controls {
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.display-options {
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.view-controls {
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
align-items: stretch;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,639 @@
|
||||
/* 登录页面样式 */
|
||||
|
||||
.login-page {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* 背景装饰 */
|
||||
.login-background {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.bg-pattern {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-image:
|
||||
radial-gradient(circle at 25% 25%, rgba(255, 255, 255, 0.1) 0%, transparent 50%),
|
||||
radial-gradient(circle at 75% 75%, rgba(255, 255, 255, 0.1) 0%, transparent 50%);
|
||||
background-size: 400px 400px;
|
||||
animation: patternMove 20s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes patternMove {
|
||||
0% { transform: translate(0, 0); }
|
||||
100% { transform: translate(-400px, -400px); }
|
||||
}
|
||||
|
||||
.bg-particles {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
/* 登录容器 */
|
||||
.login-container {
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
display: flex;
|
||||
width: 90%;
|
||||
max-width: 1000px;
|
||||
height: 600px;
|
||||
background-color: rgba(255, 255, 255, 0.95);
|
||||
border-radius: 16px;
|
||||
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.1);
|
||||
overflow: hidden;
|
||||
backdrop-filter: blur(10px);
|
||||
}
|
||||
|
||||
/* 左侧系统介绍 */
|
||||
.login-intro {
|
||||
flex: 1;
|
||||
background: linear-gradient(135deg, #409EFF 0%, #36CFC9 100%);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 40px;
|
||||
color: white;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.login-intro::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"><defs><pattern id="grid" width="10" height="10" patternUnits="userSpaceOnUse"><path d="M 10 0 L 0 0 0 10" fill="none" stroke="rgba(255,255,255,0.1)" stroke-width="0.5"/></pattern></defs><rect width="100" height="100" fill="url(%23grid)"/></svg>');
|
||||
opacity: 0.3;
|
||||
}
|
||||
|
||||
.intro-content {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.system-logo {
|
||||
margin-bottom: 24px;
|
||||
animation: logoFloat 3s ease-in-out infinite;
|
||||
}
|
||||
|
||||
@keyframes logoFloat {
|
||||
0%, 100% { transform: translateY(0px); }
|
||||
50% { transform: translateY(-10px); }
|
||||
}
|
||||
|
||||
.system-name {
|
||||
font-size: 32px;
|
||||
font-weight: 700;
|
||||
margin-bottom: 16px;
|
||||
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.system-desc {
|
||||
font-size: 16px;
|
||||
line-height: 1.6;
|
||||
margin-bottom: 40px;
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
.feature-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 20px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.feature-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
padding: 16px;
|
||||
background-color: rgba(255, 255, 255, 0.1);
|
||||
border-radius: 12px;
|
||||
backdrop-filter: blur(5px);
|
||||
transition: transform 0.2s ease;
|
||||
}
|
||||
|
||||
.feature-item:hover {
|
||||
transform: translateX(5px);
|
||||
}
|
||||
|
||||
.feature-icon {
|
||||
font-size: 24px;
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background-color: rgba(255, 255, 255, 0.2);
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.feature-text h4 {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
margin: 0 0 4px 0;
|
||||
}
|
||||
|
||||
.feature-text p {
|
||||
font-size: 14px;
|
||||
margin: 0;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
/* 右侧登录表单 */
|
||||
.login-form-container {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 40px;
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
.login-form-wrapper {
|
||||
width: 100%;
|
||||
max-width: 360px;
|
||||
}
|
||||
|
||||
.login-header {
|
||||
text-align: center;
|
||||
margin-bottom: 32px;
|
||||
}
|
||||
|
||||
.login-header h2 {
|
||||
font-size: 28px;
|
||||
font-weight: 700;
|
||||
color: var(--text-primary);
|
||||
margin: 0 0 8px 0;
|
||||
}
|
||||
|
||||
.login-header p {
|
||||
font-size: 14px;
|
||||
color: var(--text-secondary);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/* 表单样式 */
|
||||
.login-form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.form-group {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.form-label {
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.input-wrapper {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.input-icon {
|
||||
position: absolute;
|
||||
left: 12px;
|
||||
z-index: 1;
|
||||
color: var(--text-placeholder);
|
||||
}
|
||||
|
||||
.form-control {
|
||||
width: 100%;
|
||||
height: 44px;
|
||||
padding: 0 44px 0 44px;
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 8px;
|
||||
font-size: 14px;
|
||||
color: var(--text-primary);
|
||||
background-color: var(--bg-color);
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.form-control:focus {
|
||||
outline: none;
|
||||
border-color: var(--primary-color);
|
||||
box-shadow: 0 0 0 3px rgba(64, 158, 255, 0.1);
|
||||
}
|
||||
|
||||
.form-control::placeholder {
|
||||
color: var(--text-placeholder);
|
||||
}
|
||||
|
||||
.password-toggle {
|
||||
position: absolute;
|
||||
right: 12px;
|
||||
background: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
color: var(--text-placeholder);
|
||||
padding: 4px;
|
||||
border-radius: 4px;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.password-toggle:hover {
|
||||
color: var(--text-secondary);
|
||||
background-color: var(--bg-secondary);
|
||||
}
|
||||
|
||||
/* 验证码样式 */
|
||||
.captcha-wrapper {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.captcha-input {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.captcha-image {
|
||||
position: relative;
|
||||
width: 100px;
|
||||
height: 40px;
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 6px;
|
||||
overflow: hidden;
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
#captchaCanvas {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.captcha-refresh {
|
||||
position: absolute;
|
||||
top: 2px;
|
||||
right: 2px;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
border: none;
|
||||
border-radius: 50%;
|
||||
color: white;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 10px;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.captcha-refresh:hover {
|
||||
background-color: rgba(0, 0, 0, 0.7);
|
||||
}
|
||||
|
||||
/* 表单选项 */
|
||||
.form-options {
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin: 8px 0;
|
||||
}
|
||||
|
||||
.checkbox-wrapper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
color: var(--text-regular);
|
||||
}
|
||||
|
||||
.checkbox-wrapper input[type="checkbox"] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.checkbox-custom {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
border: 2px solid var(--border-color);
|
||||
border-radius: 3px;
|
||||
position: relative;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.checkbox-wrapper input[type="checkbox"]:checked + .checkbox-custom {
|
||||
background-color: var(--primary-color);
|
||||
border-color: var(--primary-color);
|
||||
}
|
||||
|
||||
.checkbox-wrapper input[type="checkbox"]:checked + .checkbox-custom::after {
|
||||
content: '✓';
|
||||
position: absolute;
|
||||
top: -2px;
|
||||
left: 2px;
|
||||
color: white;
|
||||
font-size: 12px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.forgot-password {
|
||||
font-size: 14px;
|
||||
color: var(--primary-color);
|
||||
text-decoration: none;
|
||||
transition: color 0.2s ease;
|
||||
}
|
||||
|
||||
.forgot-password:hover {
|
||||
color: var(--primary-dark);
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
/* 登录按钮 */
|
||||
.login-btn {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 48px;
|
||||
background: linear-gradient(135deg, var(--primary-color) 0%, var(--primary-dark) 100%);
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
color: white;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
overflow: hidden;
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.login-btn:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 8px 20px rgba(64, 158, 255, 0.3);
|
||||
}
|
||||
|
||||
.login-btn:active {
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
.login-btn.loading {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.btn-text {
|
||||
transition: opacity 0.2s ease;
|
||||
}
|
||||
|
||||
.login-btn.loading .btn-text {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.btn-loading {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
opacity: 0;
|
||||
transition: opacity 0.2s ease;
|
||||
}
|
||||
|
||||
.login-btn.loading .btn-loading {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
/* 分割线 */
|
||||
.login-divider {
|
||||
position: relative;
|
||||
text-align: center;
|
||||
margin: 24px 0;
|
||||
color: var(--text-placeholder);
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.login-divider::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 1px;
|
||||
background-color: var(--border-lighter);
|
||||
}
|
||||
|
||||
.login-divider span {
|
||||
background-color: white;
|
||||
padding: 0 16px;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
/* 社交登录 */
|
||||
.social-login {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.social-btn {
|
||||
flex: 1;
|
||||
height: 44px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 8px;
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 8px;
|
||||
background-color: white;
|
||||
color: var(--text-regular);
|
||||
font-size: 14px;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.social-btn:hover {
|
||||
border-color: var(--primary-color);
|
||||
color: var(--primary-color);
|
||||
background-color: rgba(64, 158, 255, 0.05);
|
||||
}
|
||||
|
||||
/* 表单错误提示 */
|
||||
.form-error {
|
||||
font-size: 12px;
|
||||
color: var(--danger-color);
|
||||
margin-top: 4px;
|
||||
min-height: 16px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.form-error:empty {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.form-error::before {
|
||||
content: '⚠️';
|
||||
font-size: 10px;
|
||||
}
|
||||
|
||||
/* 登录页面底部 */
|
||||
.login-footer {
|
||||
margin-top: 32px;
|
||||
padding-top: 20px;
|
||||
border-top: 1px solid var(--border-lighter);
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
font-size: 12px;
|
||||
color: var(--text-placeholder);
|
||||
}
|
||||
|
||||
.system-info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.separator {
|
||||
color: var(--border-color);
|
||||
}
|
||||
|
||||
.help-links {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.help-link {
|
||||
color: var(--text-placeholder);
|
||||
text-decoration: none;
|
||||
transition: color 0.2s ease;
|
||||
}
|
||||
|
||||
.help-link:hover {
|
||||
color: var(--primary-color);
|
||||
}
|
||||
|
||||
/* 系统状态指示器 */
|
||||
.system-status-indicator {
|
||||
position: fixed;
|
||||
bottom: 20px;
|
||||
right: 20px;
|
||||
z-index: 3;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.status-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
padding: 6px 12px;
|
||||
background-color: rgba(255, 255, 255, 0.9);
|
||||
border-radius: 20px;
|
||||
font-size: 12px;
|
||||
color: var(--text-secondary);
|
||||
backdrop-filter: blur(5px);
|
||||
}
|
||||
|
||||
/* 响应式设计 */
|
||||
@media (max-width: 768px) {
|
||||
.login-container {
|
||||
flex-direction: column;
|
||||
width: 95%;
|
||||
height: auto;
|
||||
max-height: 90vh;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.login-intro {
|
||||
padding: 30px 20px;
|
||||
}
|
||||
|
||||
.system-name {
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
.feature-list {
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.feature-item {
|
||||
flex: 1;
|
||||
min-width: 140px;
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
.feature-icon {
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.login-form-container {
|
||||
padding: 30px 20px;
|
||||
}
|
||||
|
||||
.login-form-wrapper {
|
||||
max-width: none;
|
||||
}
|
||||
|
||||
.system-status-indicator {
|
||||
position: relative;
|
||||
bottom: auto;
|
||||
right: auto;
|
||||
margin-top: 20px;
|
||||
align-self: center;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
.login-container {
|
||||
width: 100%;
|
||||
height: 100vh;
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
.captcha-wrapper {
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.captcha-image {
|
||||
width: 100%;
|
||||
height: 50px;
|
||||
}
|
||||
|
||||
.social-login {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.login-footer {
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,529 @@
|
||||
/* 全局样式重置 */
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
/* CSS 变量定义 - 浅色主题 */
|
||||
:root {
|
||||
/* 主色调 */
|
||||
--primary-color: #409EFF;
|
||||
--primary-light: #79BBFF;
|
||||
--primary-dark: #337ECC;
|
||||
|
||||
/* 背景色 */
|
||||
--bg-color: #FFFFFF;
|
||||
--bg-secondary: #F5F7FA;
|
||||
--bg-tertiary: #EBEEF5;
|
||||
|
||||
/* 文字颜色 */
|
||||
--text-primary: #303133;
|
||||
--text-regular: #606266;
|
||||
--text-secondary: #909399;
|
||||
--text-placeholder: #C0C4CC;
|
||||
|
||||
/* 边框颜色 */
|
||||
--border-color: #DCDFE6;
|
||||
--border-light: #E4E7ED;
|
||||
--border-lighter: #EBEEF5;
|
||||
|
||||
/* 状态颜色 */
|
||||
--success-color: #67C23A;
|
||||
--warning-color: #E6A23C;
|
||||
--danger-color: #F56C6C;
|
||||
--info-color: #909399;
|
||||
|
||||
/* 阴影 */
|
||||
--box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
|
||||
--box-shadow-light: 0 2px 4px rgba(0, 0, 0, 0.12), 0 0 6px rgba(0, 0, 0, 0.04);
|
||||
|
||||
/* 布局尺寸 */
|
||||
--header-height: 60px;
|
||||
--sidebar-width: 240px;
|
||||
--sidebar-collapsed-width: 64px;
|
||||
}
|
||||
|
||||
/* 基础样式 */
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
|
||||
font-size: 14px;
|
||||
line-height: 1.5;
|
||||
color: var(--text-primary);
|
||||
background-color: var(--bg-color);
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
/* 应用根容器 */
|
||||
#app {
|
||||
width: 100%;
|
||||
height: 100vh;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
/* 页面容器 */
|
||||
.page-container {
|
||||
width: 100%;
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
/* 主布局 */
|
||||
.main-layout {
|
||||
width: 100%;
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
/* 顶部导航栏 */
|
||||
.header {
|
||||
height: var(--header-height);
|
||||
background-color: var(--bg-color);
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
box-shadow: var(--box-shadow-light);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 0 20px;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
.header-left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.header-logo {
|
||||
font-size: 18px;
|
||||
font-weight: bold;
|
||||
color: var(--primary-color);
|
||||
margin-right: 20px;
|
||||
}
|
||||
|
||||
.header-title {
|
||||
font-size: 16px;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.header-right {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 15px;
|
||||
}
|
||||
|
||||
.header-user {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
color: var(--text-regular);
|
||||
}
|
||||
|
||||
.header-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
/* 侧边栏 */
|
||||
.sidebar {
|
||||
width: var(--sidebar-width);
|
||||
height: calc(100vh - var(--header-height));
|
||||
background-color: var(--bg-color);
|
||||
border-right: 1px solid var(--border-color);
|
||||
position: fixed;
|
||||
top: var(--header-height);
|
||||
left: 0;
|
||||
z-index: 999;
|
||||
overflow-y: auto;
|
||||
transition: width 0.3s ease;
|
||||
}
|
||||
|
||||
.sidebar.collapsed {
|
||||
width: var(--sidebar-collapsed-width);
|
||||
}
|
||||
|
||||
.sidebar-menu {
|
||||
padding: 20px 0;
|
||||
}
|
||||
|
||||
.menu-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 12px 20px;
|
||||
color: var(--text-regular);
|
||||
text-decoration: none;
|
||||
transition: all 0.3s ease;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.menu-item:hover {
|
||||
background-color: var(--bg-secondary);
|
||||
color: var(--primary-color);
|
||||
}
|
||||
|
||||
.menu-item.active {
|
||||
background-color: var(--primary-color);
|
||||
color: #FFFFFF;
|
||||
}
|
||||
|
||||
.menu-item-icon {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
margin-right: 12px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.menu-item-text {
|
||||
flex: 1;
|
||||
transition: opacity 0.3s ease;
|
||||
}
|
||||
|
||||
.sidebar.collapsed .menu-item-text {
|
||||
opacity: 0;
|
||||
width: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* 主内容区域 */
|
||||
.main-content {
|
||||
flex: 1;
|
||||
margin-top: var(--header-height);
|
||||
margin-left: var(--sidebar-width);
|
||||
padding: 20px;
|
||||
background-color: var(--bg-secondary);
|
||||
overflow-y: auto;
|
||||
transition: margin-left 0.3s ease;
|
||||
}
|
||||
|
||||
.sidebar.collapsed + .main-content {
|
||||
margin-left: var(--sidebar-collapsed-width);
|
||||
}
|
||||
|
||||
/* 页面视图 */
|
||||
.page-view {
|
||||
background-color: var(--bg-color);
|
||||
border-radius: 8px;
|
||||
box-shadow: var(--box-shadow);
|
||||
padding: 24px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.page-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 24px;
|
||||
padding-bottom: 16px;
|
||||
border-bottom: 1px solid var(--border-lighter);
|
||||
}
|
||||
|
||||
.page-title {
|
||||
font-size: 20px;
|
||||
font-weight: 600;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.page-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
/* 卡片样式 */
|
||||
.card {
|
||||
background-color: var(--bg-color);
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 8px;
|
||||
box-shadow: var(--box-shadow-light);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.card-header {
|
||||
padding: 16px 20px;
|
||||
background-color: var(--bg-tertiary);
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
font-weight: 600;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.card-body {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
/* 表格样式 */
|
||||
.table-container {
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.data-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
background-color: var(--bg-color);
|
||||
}
|
||||
|
||||
.data-table th,
|
||||
.data-table td {
|
||||
padding: 12px 16px;
|
||||
text-align: left;
|
||||
border-bottom: 1px solid var(--border-lighter);
|
||||
}
|
||||
|
||||
.data-table th {
|
||||
background-color: var(--bg-tertiary);
|
||||
font-weight: 600;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.data-table tr:hover {
|
||||
background-color: var(--bg-secondary);
|
||||
}
|
||||
|
||||
/* 状态标签 */
|
||||
.status-tag {
|
||||
display: inline-block;
|
||||
padding: 4px 8px;
|
||||
border-radius: 4px;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.status-tag.online {
|
||||
background-color: #F0F9FF;
|
||||
color: var(--success-color);
|
||||
border: 1px solid #B3E5FC;
|
||||
}
|
||||
|
||||
.status-tag.offline {
|
||||
background-color: #FFF5F5;
|
||||
color: var(--danger-color);
|
||||
border: 1px solid #FFCDD2;
|
||||
}
|
||||
|
||||
.status-tag.warning {
|
||||
background-color: #FFFBF0;
|
||||
color: var(--warning-color);
|
||||
border: 1px solid #FFE0B2;
|
||||
}
|
||||
|
||||
/* 按钮样式 */
|
||||
.btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 8px 16px;
|
||||
border: 1px solid transparent;
|
||||
border-radius: 4px;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
text-decoration: none;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background-color: var(--primary-color);
|
||||
color: #FFFFFF;
|
||||
border-color: var(--primary-color);
|
||||
}
|
||||
|
||||
.btn-primary:hover {
|
||||
background-color: var(--primary-light);
|
||||
border-color: var(--primary-light);
|
||||
}
|
||||
|
||||
.btn-success {
|
||||
background-color: var(--success-color);
|
||||
color: #FFFFFF;
|
||||
border-color: var(--success-color);
|
||||
}
|
||||
|
||||
.btn-warning {
|
||||
background-color: var(--warning-color);
|
||||
color: #FFFFFF;
|
||||
border-color: var(--warning-color);
|
||||
}
|
||||
|
||||
.btn-danger {
|
||||
background-color: var(--danger-color);
|
||||
color: #FFFFFF;
|
||||
border-color: var(--danger-color);
|
||||
}
|
||||
|
||||
.btn-secondary {
|
||||
background-color: var(--bg-color);
|
||||
color: var(--text-regular);
|
||||
border-color: var(--border-color);
|
||||
}
|
||||
|
||||
.btn-secondary:hover {
|
||||
background-color: var(--bg-secondary);
|
||||
border-color: var(--border-light);
|
||||
}
|
||||
|
||||
/* 表单样式 */
|
||||
.form-group {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.form-label {
|
||||
display: block;
|
||||
margin-bottom: 8px;
|
||||
font-weight: 500;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.form-control {
|
||||
width: 100%;
|
||||
padding: 10px 12px;
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 4px;
|
||||
font-size: 14px;
|
||||
color: var(--text-primary);
|
||||
background-color: var(--bg-color);
|
||||
transition: border-color 0.3s ease;
|
||||
}
|
||||
|
||||
.form-control:focus {
|
||||
outline: none;
|
||||
border-color: var(--primary-color);
|
||||
box-shadow: 0 0 0 2px rgba(64, 158, 255, 0.2);
|
||||
}
|
||||
|
||||
/* 图表容器 */
|
||||
.chart-container {
|
||||
width: 100%;
|
||||
height: 400px;
|
||||
margin: 20px 0;
|
||||
}
|
||||
|
||||
/* 加载动画 */
|
||||
.loading {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 40px;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
.loading-spinner {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
border: 2px solid var(--border-color);
|
||||
border-top: 2px solid var(--primary-color);
|
||||
border-radius: 50%;
|
||||
animation: spin 1s linear infinite;
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
0% { transform: rotate(0deg); }
|
||||
100% { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
/* 空状态 */
|
||||
.empty-state {
|
||||
text-align: center;
|
||||
padding: 60px 20px;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
.empty-state-icon {
|
||||
font-size: 48px;
|
||||
margin-bottom: 16px;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.empty-state-text {
|
||||
font-size: 16px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.empty-state-desc {
|
||||
font-size: 14px;
|
||||
color: var(--text-placeholder);
|
||||
}
|
||||
|
||||
/* 响应式设计 */
|
||||
@media (max-width: 1366px) {
|
||||
.main-content {
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.page-view {
|
||||
padding: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.sidebar {
|
||||
transform: translateX(-100%);
|
||||
transition: transform 0.3s ease;
|
||||
}
|
||||
|
||||
.sidebar.mobile-open {
|
||||
transform: translateX(0);
|
||||
}
|
||||
|
||||
.main-content {
|
||||
margin-left: 0;
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
.header {
|
||||
padding: 0 16px;
|
||||
}
|
||||
|
||||
.page-view {
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.page-header {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.page-actions {
|
||||
width: 100%;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
}
|
||||
|
||||
/* 工具类 */
|
||||
.text-center { text-align: center; }
|
||||
.text-left { text-align: left; }
|
||||
.text-right { text-align: right; }
|
||||
|
||||
.mb-0 { margin-bottom: 0; }
|
||||
.mb-1 { margin-bottom: 8px; }
|
||||
.mb-2 { margin-bottom: 16px; }
|
||||
.mb-3 { margin-bottom: 24px; }
|
||||
.mb-4 { margin-bottom: 32px; }
|
||||
|
||||
.mt-0 { margin-top: 0; }
|
||||
.mt-1 { margin-top: 8px; }
|
||||
.mt-2 { margin-top: 16px; }
|
||||
.mt-3 { margin-top: 24px; }
|
||||
.mt-4 { margin-top: 32px; }
|
||||
|
||||
.d-flex { display: flex; }
|
||||
.d-block { display: block; }
|
||||
.d-none { display: none; }
|
||||
|
||||
.flex-1 { flex: 1; }
|
||||
.align-center { align-items: center; }
|
||||
.justify-center { justify-content: center; }
|
||||
.justify-between { justify-content: space-between; }
|
||||
|
||||
.w-100 { width: 100%; }
|
||||
.h-100 { height: 100%; }
|
||||
@ -0,0 +1,486 @@
|
||||
/**
|
||||
* 响应式设计样式
|
||||
* 确保在不同屏幕尺寸下的良好显示效果
|
||||
*/
|
||||
|
||||
/* 基础响应式断点 */
|
||||
:root {
|
||||
--breakpoint-xs: 480px;
|
||||
--breakpoint-sm: 768px;
|
||||
--breakpoint-md: 1024px;
|
||||
--breakpoint-lg: 1200px;
|
||||
--breakpoint-xl: 1440px;
|
||||
}
|
||||
|
||||
/* 通用响应式工具类 */
|
||||
.container {
|
||||
width: 100%;
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
padding: 0 20px;
|
||||
}
|
||||
|
||||
.row {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
margin: 0 -10px;
|
||||
}
|
||||
|
||||
.col {
|
||||
flex: 1;
|
||||
padding: 0 10px;
|
||||
}
|
||||
|
||||
/* 隐藏/显示工具类 */
|
||||
.hidden-xs { display: block; }
|
||||
.hidden-sm { display: block; }
|
||||
.hidden-md { display: block; }
|
||||
.hidden-lg { display: block; }
|
||||
|
||||
.visible-xs { display: none; }
|
||||
.visible-sm { display: none; }
|
||||
.visible-md { display: none; }
|
||||
.visible-lg { display: none; }
|
||||
|
||||
/* 超小屏幕 (手机) */
|
||||
@media (max-width: 479px) {
|
||||
.hidden-xs { display: none !important; }
|
||||
.visible-xs { display: block !important; }
|
||||
|
||||
/* 容器调整 */
|
||||
.container {
|
||||
padding: 0 15px;
|
||||
}
|
||||
|
||||
/* 应用布局调整 */
|
||||
.app-layout {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.sidebar-container {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: -280px;
|
||||
width: 280px;
|
||||
height: 100vh;
|
||||
z-index: 1000;
|
||||
transition: left 0.3s ease;
|
||||
}
|
||||
|
||||
.sidebar-container.active {
|
||||
left: 0;
|
||||
}
|
||||
|
||||
.main-content {
|
||||
margin-left: 0;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
/* 头部调整 */
|
||||
.header-container {
|
||||
padding: 10px 15px;
|
||||
}
|
||||
|
||||
.page-header {
|
||||
flex-direction: column;
|
||||
gap: 15px;
|
||||
}
|
||||
|
||||
.header-actions {
|
||||
width: 100%;
|
||||
justify-content: stretch;
|
||||
}
|
||||
|
||||
.header-actions .btn {
|
||||
flex: 1;
|
||||
margin: 0 5px;
|
||||
}
|
||||
|
||||
/* 卡片布局调整 */
|
||||
.dashboard-cards,
|
||||
.stats-overview {
|
||||
grid-template-columns: 1fr;
|
||||
gap: 15px;
|
||||
}
|
||||
|
||||
.dashboard-card,
|
||||
.stat-card {
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
/* 表格响应式 */
|
||||
.data-table {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.data-table th,
|
||||
.data-table td {
|
||||
padding: 8px 4px;
|
||||
}
|
||||
|
||||
/* 隐藏不重要的列 */
|
||||
.data-table .hidden-xs {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* 表单调整 */
|
||||
.form-row {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.form-group {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
/* 按钮调整 */
|
||||
.btn-group {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.btn-group .btn {
|
||||
margin: 2px 0;
|
||||
}
|
||||
|
||||
/* 模态框调整 */
|
||||
.modal-content {
|
||||
width: 95%;
|
||||
margin: 20px auto;
|
||||
max-height: 90vh;
|
||||
}
|
||||
|
||||
/* 图表容器调整 */
|
||||
.chart-container {
|
||||
height: 250px;
|
||||
}
|
||||
|
||||
/* 节点卡片调整 */
|
||||
.node-card {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.node-metrics {
|
||||
grid-template-columns: 1fr 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
/* 小屏幕 (平板竖屏) */
|
||||
@media (min-width: 480px) and (max-width: 767px) {
|
||||
.hidden-sm { display: none !important; }
|
||||
.visible-sm { display: block !important; }
|
||||
|
||||
/* 容器调整 */
|
||||
.container {
|
||||
padding: 0 20px;
|
||||
}
|
||||
|
||||
/* 侧边栏调整 */
|
||||
.sidebar-container {
|
||||
width: 240px;
|
||||
}
|
||||
|
||||
.main-content {
|
||||
margin-left: 240px;
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
/* 卡片布局调整 */
|
||||
.dashboard-cards,
|
||||
.stats-overview {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
/* 表格调整 */
|
||||
.data-table {
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
/* 图表容器调整 */
|
||||
.chart-container {
|
||||
height: 300px;
|
||||
}
|
||||
|
||||
/* 节点指标调整 */
|
||||
.node-metrics {
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
}
|
||||
}
|
||||
|
||||
/* 中等屏幕 (平板横屏/小笔记本) */
|
||||
@media (min-width: 768px) and (max-width: 1023px) {
|
||||
.hidden-md { display: none !important; }
|
||||
.visible-md { display: block !important; }
|
||||
|
||||
/* 卡片布局调整 */
|
||||
.dashboard-cards,
|
||||
.stats-overview {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: 25px;
|
||||
}
|
||||
|
||||
/* 图表容器调整 */
|
||||
.chart-container {
|
||||
height: 350px;
|
||||
}
|
||||
|
||||
/* 表单布局调整 */
|
||||
.form-row {
|
||||
display: flex;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.form-row .form-group {
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* 大屏幕 (桌面) */
|
||||
@media (min-width: 1024px) and (max-width: 1199px) {
|
||||
.hidden-lg { display: none !important; }
|
||||
.visible-lg { display: block !important; }
|
||||
|
||||
/* 卡片布局调整 */
|
||||
.dashboard-cards,
|
||||
.stats-overview {
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
}
|
||||
|
||||
/* 图表容器调整 */
|
||||
.chart-container {
|
||||
height: 400px;
|
||||
}
|
||||
}
|
||||
|
||||
/* 超大屏幕 (大桌面) */
|
||||
@media (min-width: 1200px) {
|
||||
/* 保持默认布局 */
|
||||
.dashboard-cards,
|
||||
.stats-overview {
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
}
|
||||
|
||||
.chart-container {
|
||||
height: 450px;
|
||||
}
|
||||
}
|
||||
|
||||
/* 移动设备横屏优化 */
|
||||
@media (max-width: 767px) and (orientation: landscape) {
|
||||
.dashboard-cards,
|
||||
.stats-overview {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
}
|
||||
|
||||
.chart-container {
|
||||
height: 200px;
|
||||
}
|
||||
|
||||
.modal-content {
|
||||
max-height: 80vh;
|
||||
}
|
||||
}
|
||||
|
||||
/* 打印样式 */
|
||||
@media print {
|
||||
.sidebar-container,
|
||||
.header-actions,
|
||||
.action-btn,
|
||||
.btn {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.main-content {
|
||||
margin-left: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.page-header {
|
||||
border-bottom: 2px solid #000;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.dashboard-cards,
|
||||
.stats-overview {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.chart-container {
|
||||
height: 300px;
|
||||
border: 1px solid #ccc;
|
||||
}
|
||||
|
||||
.data-table {
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
.data-table th,
|
||||
.data-table td {
|
||||
border: 1px solid #000;
|
||||
padding: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
/* 高对比度模式支持 */
|
||||
@media (prefers-contrast: high) {
|
||||
.dashboard-card,
|
||||
.stat-card,
|
||||
.node-card {
|
||||
border: 2px solid #000;
|
||||
}
|
||||
|
||||
.btn {
|
||||
border: 2px solid #000;
|
||||
}
|
||||
|
||||
.form-control {
|
||||
border: 2px solid #000;
|
||||
}
|
||||
}
|
||||
|
||||
/* 减少动画模式支持 */
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
animation-duration: 0.01ms !important;
|
||||
animation-iteration-count: 1 !important;
|
||||
transition-duration: 0.01ms !important;
|
||||
}
|
||||
}
|
||||
|
||||
/* 深色模式响应式调整 */
|
||||
@media (prefers-color-scheme: dark) {
|
||||
.chart-container {
|
||||
background-color: #1f2937;
|
||||
border-color: #374151;
|
||||
}
|
||||
|
||||
.data-table {
|
||||
background-color: #1f2937;
|
||||
color: #f9fafb;
|
||||
}
|
||||
|
||||
.data-table th {
|
||||
background-color: #374151;
|
||||
}
|
||||
|
||||
.data-table tr:nth-child(even) {
|
||||
background-color: #374151;
|
||||
}
|
||||
}
|
||||
|
||||
/* 触摸设备优化 */
|
||||
@media (hover: none) and (pointer: coarse) {
|
||||
.btn,
|
||||
.action-btn {
|
||||
min-height: 44px;
|
||||
min-width: 44px;
|
||||
}
|
||||
|
||||
.form-control {
|
||||
min-height: 44px;
|
||||
}
|
||||
|
||||
.data-table td {
|
||||
padding: 12px 8px;
|
||||
}
|
||||
|
||||
/* 增大点击区域 */
|
||||
.sidebar-nav a {
|
||||
padding: 15px 20px;
|
||||
}
|
||||
|
||||
.dropdown-item {
|
||||
padding: 12px 16px;
|
||||
}
|
||||
}
|
||||
|
||||
/* 特定组件的响应式调整 */
|
||||
|
||||
/* 登录页面响应式 */
|
||||
@media (max-width: 767px) {
|
||||
.login-container {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.login-form {
|
||||
padding: 30px 20px;
|
||||
}
|
||||
|
||||
.login-header h1 {
|
||||
font-size: 24px;
|
||||
}
|
||||
}
|
||||
|
||||
/* 故障管理页面响应式 */
|
||||
@media (max-width: 767px) {
|
||||
.fault-filters {
|
||||
flex-direction: column;
|
||||
gap: 15px;
|
||||
}
|
||||
|
||||
.filter-group {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.fault-table-container {
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.fault-table {
|
||||
min-width: 600px;
|
||||
}
|
||||
}
|
||||
|
||||
/* 日志分析页面响应式 */
|
||||
@media (max-width: 767px) {
|
||||
.log-query-form {
|
||||
grid-template-columns: 1fr;
|
||||
gap: 15px;
|
||||
}
|
||||
|
||||
.log-charts {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.log-entry {
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
.log-content {
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
/* 集群监控页面响应式 */
|
||||
@media (max-width: 767px) {
|
||||
.cluster-overview {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
}
|
||||
|
||||
.node-list {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.monitoring-charts {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
/* 滚动条优化 */
|
||||
@media (max-width: 767px) {
|
||||
/* 隐藏滚动条但保持功能 */
|
||||
.data-table-container::-webkit-scrollbar {
|
||||
height: 4px;
|
||||
}
|
||||
|
||||
.data-table-container::-webkit-scrollbar-track {
|
||||
background: #f1f5f9;
|
||||
}
|
||||
|
||||
.data-table-container::-webkit-scrollbar-thumb {
|
||||
background: #cbd5e1;
|
||||
border-radius: 2px;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,317 @@
|
||||
<!-- 登录页面 -->
|
||||
<div class="login-page" id="loginPage">
|
||||
<!-- 背景装饰 -->
|
||||
<div class="login-background">
|
||||
<div class="bg-pattern"></div>
|
||||
<div class="bg-particles">
|
||||
<div class="particle"></div>
|
||||
<div class="particle"></div>
|
||||
<div class="particle"></div>
|
||||
<div class="particle"></div>
|
||||
<div class="particle"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 登录容器 -->
|
||||
<div class="login-container">
|
||||
<!-- 左侧:系统介绍 -->
|
||||
<div class="login-intro">
|
||||
<div class="intro-content">
|
||||
<div class="system-logo">
|
||||
<svg width="80" height="80" viewBox="0 0 80 80" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect width="80" height="80" rx="20" fill="#409EFF"/>
|
||||
<path d="M20 30H60V35H20V30Z" fill="white"/>
|
||||
<path d="M20 40H50V45H20V40Z" fill="white"/>
|
||||
<path d="M20 50H40V55H20V50Z" fill="white"/>
|
||||
<circle cx="55" cy="25" r="8" fill="#F56C6C"/>
|
||||
<path d="M51 21L55 25L59 21" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M51 29L55 25L59 29" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
</div>
|
||||
<h1 class="system-name">错误检测系统</h1>
|
||||
<p class="system-desc">智能化运维监控平台,实时检测系统异常,保障业务稳定运行</p>
|
||||
|
||||
<div class="feature-list">
|
||||
<div class="feature-item">
|
||||
<div class="feature-icon">🔍</div>
|
||||
<div class="feature-text">
|
||||
<h4>智能检测</h4>
|
||||
<p>AI驱动的异常检测算法</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="feature-item">
|
||||
<div class="feature-icon">📊</div>
|
||||
<div class="feature-text">
|
||||
<h4>实时监控</h4>
|
||||
<p>7x24小时系统状态监控</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="feature-item">
|
||||
<div class="feature-icon">⚡</div>
|
||||
<div class="feature-text">
|
||||
<h4>快速响应</h4>
|
||||
<p>秒级故障告警与处理</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 右侧:登录表单 -->
|
||||
<div class="login-form-container">
|
||||
<div class="login-form-wrapper">
|
||||
<!-- 登录表单头部 -->
|
||||
<div class="login-header">
|
||||
<h2>欢迎登录</h2>
|
||||
<p>请输入您的账号信息</p>
|
||||
</div>
|
||||
|
||||
<!-- 登录表单 -->
|
||||
<form class="login-form" id="loginForm">
|
||||
<!-- 用户名输入 -->
|
||||
<div class="form-group">
|
||||
<label for="username" class="form-label">用户名</label>
|
||||
<div class="input-wrapper">
|
||||
<div class="input-icon">
|
||||
<i class="icon-user-input"></i>
|
||||
</div>
|
||||
<input
|
||||
type="text"
|
||||
id="username"
|
||||
name="username"
|
||||
class="form-control"
|
||||
placeholder="请输入用户名"
|
||||
autocomplete="username"
|
||||
required
|
||||
>
|
||||
</div>
|
||||
<div class="form-error" id="usernameError"></div>
|
||||
</div>
|
||||
|
||||
<!-- 密码输入 -->
|
||||
<div class="form-group">
|
||||
<label for="password" class="form-label">密码</label>
|
||||
<div class="input-wrapper">
|
||||
<div class="input-icon">
|
||||
<i class="icon-lock"></i>
|
||||
</div>
|
||||
<input
|
||||
type="password"
|
||||
id="password"
|
||||
name="password"
|
||||
class="form-control"
|
||||
placeholder="请输入密码"
|
||||
autocomplete="current-password"
|
||||
required
|
||||
>
|
||||
<button type="button" class="password-toggle" id="passwordToggle">
|
||||
<i class="icon-eye"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div class="form-error" id="passwordError"></div>
|
||||
</div>
|
||||
|
||||
<!-- 验证码 -->
|
||||
<div class="form-group">
|
||||
<label for="captcha" class="form-label">验证码</label>
|
||||
<div class="captcha-wrapper">
|
||||
<div class="input-wrapper captcha-input">
|
||||
<div class="input-icon">
|
||||
<i class="icon-shield"></i>
|
||||
</div>
|
||||
<input
|
||||
type="text"
|
||||
id="captcha"
|
||||
name="captcha"
|
||||
class="form-control"
|
||||
placeholder="请输入验证码"
|
||||
maxlength="4"
|
||||
required
|
||||
>
|
||||
</div>
|
||||
<div class="captcha-image" id="captchaImage">
|
||||
<canvas width="100" height="40" id="captchaCanvas"></canvas>
|
||||
<button type="button" class="captcha-refresh" id="captchaRefresh" title="刷新验证码">
|
||||
<i class="icon-refresh"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-error" id="captchaError"></div>
|
||||
</div>
|
||||
|
||||
<!-- 记住登录 -->
|
||||
<div class="form-group form-options">
|
||||
<label class="checkbox-wrapper">
|
||||
<input type="checkbox" id="rememberMe" name="rememberMe">
|
||||
<span class="checkbox-custom"></span>
|
||||
<span class="checkbox-text">记住登录状态</span>
|
||||
</label>
|
||||
<a href="#" class="forgot-password">忘记密码?</a>
|
||||
</div>
|
||||
|
||||
<!-- 登录按钮 -->
|
||||
<button type="submit" class="login-btn" id="loginBtn">
|
||||
<span class="btn-text">登录</span>
|
||||
<div class="btn-loading" id="loginLoading">
|
||||
<div class="loading-spinner"></div>
|
||||
</div>
|
||||
</button>
|
||||
|
||||
<!-- 其他登录方式 -->
|
||||
<div class="login-divider">
|
||||
<span>或</span>
|
||||
</div>
|
||||
|
||||
<div class="social-login">
|
||||
<button type="button" class="social-btn ldap-btn">
|
||||
<i class="icon-ldap"></i>
|
||||
<span>LDAP登录</span>
|
||||
</button>
|
||||
<button type="button" class="social-btn sso-btn">
|
||||
<i class="icon-sso"></i>
|
||||
<span>SSO登录</span>
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<!-- 登录页面底部信息 -->
|
||||
<div class="login-footer">
|
||||
<div class="system-info">
|
||||
<span class="version">版本 v1.0.0</span>
|
||||
<span class="separator">|</span>
|
||||
<span class="copyright">© 2024 错误检测系统</span>
|
||||
</div>
|
||||
<div class="help-links">
|
||||
<a href="#" class="help-link">使用帮助</a>
|
||||
<a href="#" class="help-link">技术支持</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 系统状态指示器 -->
|
||||
<div class="system-status-indicator">
|
||||
<div class="status-item">
|
||||
<div class="status-dot online"></div>
|
||||
<span class="status-text">系统正常</span>
|
||||
</div>
|
||||
<div class="status-item">
|
||||
<div class="status-dot"></div>
|
||||
<span class="status-text">数据库连接正常</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 登录页面图标样式 -->
|
||||
<style>
|
||||
/* 登录页面图标 */
|
||||
.icon-user-input::before {
|
||||
content: "👤";
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.icon-lock::before {
|
||||
content: "🔒";
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.icon-shield::before {
|
||||
content: "🛡️";
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.icon-eye::before {
|
||||
content: "👁️";
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.icon-eye-off::before {
|
||||
content: "🙈";
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.icon-refresh::before {
|
||||
content: "🔄";
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.icon-ldap::before {
|
||||
content: "🏢";
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.icon-sso::before {
|
||||
content: "🔑";
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
/* 密码显示切换动画 */
|
||||
.password-toggle {
|
||||
transition: transform 0.2s ease;
|
||||
}
|
||||
|
||||
.password-toggle:hover {
|
||||
transform: scale(1.1);
|
||||
}
|
||||
|
||||
/* 验证码刷新动画 */
|
||||
.captcha-refresh {
|
||||
transition: transform 0.3s ease;
|
||||
}
|
||||
|
||||
.captcha-refresh:hover {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
|
||||
/* 背景粒子动画 */
|
||||
.particle {
|
||||
position: absolute;
|
||||
width: 4px;
|
||||
height: 4px;
|
||||
background-color: rgba(64, 158, 255, 0.3);
|
||||
border-radius: 50%;
|
||||
animation: float 6s ease-in-out infinite;
|
||||
}
|
||||
|
||||
.particle:nth-child(1) {
|
||||
top: 20%;
|
||||
left: 20%;
|
||||
animation-delay: 0s;
|
||||
}
|
||||
|
||||
.particle:nth-child(2) {
|
||||
top: 60%;
|
||||
left: 80%;
|
||||
animation-delay: 1s;
|
||||
}
|
||||
|
||||
.particle:nth-child(3) {
|
||||
top: 80%;
|
||||
left: 40%;
|
||||
animation-delay: 2s;
|
||||
}
|
||||
|
||||
.particle:nth-child(4) {
|
||||
top: 40%;
|
||||
left: 70%;
|
||||
animation-delay: 3s;
|
||||
}
|
||||
|
||||
.particle:nth-child(5) {
|
||||
top: 10%;
|
||||
left: 90%;
|
||||
animation-delay: 4s;
|
||||
}
|
||||
|
||||
@keyframes float {
|
||||
0%, 100% {
|
||||
transform: translateY(0px) rotate(0deg);
|
||||
opacity: 0.3;
|
||||
}
|
||||
50% {
|
||||
transform: translateY(-20px) rotate(180deg);
|
||||
opacity: 0.8;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Loading…
Reference in new issue