前端重构:实现HTML5语义化标签、BEM命名规范、模块化CSS架构和响应式设计 #28

Merged
hnu202326010131 merged 1 commits from xingyuanxin_branch into develop 2 months ago

@ -0,0 +1,190 @@
# 集群管理系统前端项目
基于 Hadoop 的集群管理系统前端界面,提供集群状态监控、日志查询、故障诊断和自动修复功能。
## 项目结构
```
src/fronted/
├── index.html # 主页面文件(重构后)
├── 原型图.html # 原始原型文件(已优化)
├── assets/ # 静态资源目录
│ ├── images/ # 图片资源
│ ├── fonts/ # 字体文件
│ └── icons/ # 图标文件
├── components/ # 可复用组件
│ ├── common/ # 通用组件
│ ├── layout/ # 布局组件
│ └── ui/ # UI 组件
├── styles/ # 样式文件
│ ├── base/ # 基础样式
│ │ ├── reset.css # CSS 重置
│ │ └── variables.css # CSS 变量
│ ├── components/ # 组件样式
│ │ ├── header.css # 头部样式
│ │ ├── sidebar.css # 侧边栏样式
│ │ ├── dashboard.css # 仪表板样式
│ │ └── buttons.css # 按钮样式
│ ├── layouts/ # 布局样式
│ │ ├── main.css # 主布局
│ │ └── responsive.css # 响应式布局
│ ├── utils/ # 工具样式
│ │ └── utilities.css # 原子类
│ └── main.css # 主样式文件
├── utils/ # 工具函数
│ ├── charts.js # 图表管理
│ ├── navigation.js # 导航管理
│ └── responsive.js # 响应式交互
└── views/ # 页面级组件
```
## 功能特性
### 🎯 核心功能
- **集群状态监控**: 实时显示节点状态、CPU、内存使用情况
- **日志查询**: 支持多条件筛选的日志查询功能
- **故障诊断**: 自动检测和诊断系统故障
- **自动修复**: 提供故障修复建议和自动修复功能
### 🎨 设计规范
- **HTML5 语义化**: 使用 `<header>`, `<nav>`, `<main>`, `<aside>`, `<section>`, `<article>` 等语义化标签
- **BEM 命名规范**: 所有 CSS 类名遵循 BEM (Block Element Modifier) 命名规范
- **CSS 模块化**: 样式按功能模块拆分,便于维护和复用
- **响应式设计**: 支持桌面端、平板端和移动端适配
### 📱 响应式特性
- **移动端优先**: 采用移动端优先的响应式设计策略
- **断点设计**:
- 移动端: ≤ 768px
- 平板端: 769px - 1024px
- 桌面端: ≥ 1025px
- **触摸手势**: 支持滑动手势操作侧边栏
- **自适应布局**: 图表、表格、卡片等组件自动适配不同屏幕尺寸
### ♿ 无障碍访问
- **ARIA 标签**: 完整的 ARIA 属性支持
- **键盘导航**: 支持 Tab 键和 ESC 键操作
- **屏幕阅读器**: 兼容主流屏幕阅读器
- **焦点管理**: 清晰的焦点指示和管理
## 技术栈
- **HTML5**: 语义化标签和现代 Web 标准
- **CSS3**: Flexbox、Grid、CSS Variables、媒体查询
- **JavaScript ES6+**: 模块化、类、箭头函数等现代语法
- **ECharts**: 数据可视化图表库
- **Font Awesome**: 图标库
- **Tailwind CSS**: 原子化 CSS 框架(临时保留)
## 开发规范
### CSS 规范
- **选择器特异性**: 不超过 3 级嵌套
- **BEM 命名**: 严格遵循 BEM 命名规范
- **CSS 变量**: 使用 CSS 自定义属性管理设计系统
- **模块化**: 按功能拆分 CSS 文件
### JavaScript 规范
- **ES6+ 语法**: 使用现代 JavaScript 语法
- **模块化**: 功能按模块拆分
- **类设计**: 使用 ES6 类组织代码
- **事件管理**: 统一的事件绑定和解绑
### HTML 规范
- **语义化**: 使用合适的 HTML5 语义化标签
- **无障碍**: 完整的 ARIA 属性和无障碍支持
- **SEO 友好**: 合理的 meta 标签和结构化数据
## 快速开始
### 1. 启动开发服务器
```bash
cd /home/devbox/project/src/fronted
python3 -m http.server 8080
```
### 2. 访问应用
打开浏览器访问: `http://localhost:8080/index.html`
### 3. 功能测试
- **导航切换**: 点击顶部导航项切换不同页面
- **响应式测试**: 调整浏览器窗口大小测试响应式布局
- **移动端测试**: 使用浏览器开发者工具模拟移动设备
- **无障碍测试**: 使用 Tab 键测试键盘导航
## 浏览器兼容性
- **现代浏览器**: Chrome 60+, Firefox 60+, Safari 12+, Edge 79+
- **移动浏览器**: iOS Safari 12+, Chrome Mobile 60+
- **特性支持**: CSS Grid, Flexbox, CSS Variables, ES6+
## 性能优化
- **CSS 优化**: 模块化加载,减少重复样式
- **JavaScript 优化**: 按需加载,事件委托
- **图片优化**: 使用 SVG 矢量图标
- **缓存策略**: 合理的资源缓存设置
## 维护说明
### 添加新页面
1. 在 `views/` 目录创建页面组件
2. 在 `styles/components/` 添加对应样式
3. 在 `utils/navigation.js` 中添加路由逻辑
### 添加新组件
1. 在 `components/` 对应目录创建组件
2. 在 `styles/components/` 添加组件样式
3. 遵循 BEM 命名规范
### 样式修改
1. 优先修改 CSS 变量 (`styles/base/variables.css`)
2. 组件样式修改对应的组件 CSS 文件
3. 确保响应式适配正常
## 部署说明
### 生产环境部署
1. 移除 Tailwind CSS CDN 引用
2. 压缩 CSS 和 JavaScript 文件
3. 配置适当的 HTTP 缓存头
4. 启用 Gzip 压缩
### 静态资源优化
- 图片压缩和格式优化
- CSS 和 JavaScript 文件合并压缩
- 字体文件子集化
- CDN 资源配置
## 更新日志
### v2.0.0 (2023-10-15)
- ✨ 完全重构项目结构
- ✨ 实现 HTML5 语义化标签
- ✨ 采用 BEM 命名规范
- ✨ 模块化 CSS 架构
- ✨ 响应式布局支持
- ✨ 无障碍访问优化
- ✨ 现代 JavaScript 重构
### v1.0.0 (2023-10-01)
- 🎉 初始版本发布
- 📊 基础仪表板功能
- 🔍 日志查询功能
- 🔧 故障诊断功能
- 🛠️ 自动修复功能
## 贡献指南
1. Fork 项目
2. 创建功能分支
3. 提交代码变更
4. 推送到分支
5. 创建 Pull Request
## 许可证
MIT License - 详见 LICENSE 文件

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

@ -0,0 +1,549 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<!-- 页面基础配置 -->
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="基于Hadoop的集群管理系统 - 提供集群状态监控、日志查询、故障诊断和自动修复功能">
<meta name="keywords" content="Hadoop, 集群管理, 故障检测, 自动修复, 日志查询">
<meta name="author" content="集群管理系统开发团队">
<!-- 页面标题 -->
<title>集群管理系统 - Hadoop 故障检测与自动恢复平台</title>
<!-- 网站图标 -->
<link rel="icon" type="image/x-icon" href="./favicon.ico">
<link rel="shortcut icon" type="image/x-icon" href="./favicon.ico">
<!-- 外部字体和图标库 -->
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Pacifico&display=swap" rel="stylesheet">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<!-- 主样式表 -->
<link rel="stylesheet" href="./styles/main.css">
<!-- Tailwind CSS (临时保留,后续可移除) -->
<script src="https://cdn.tailwindcss.com"></script>
<!-- ECharts 图表库 -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/echarts/5.5.0/echarts.min.js"></script>
</head>
<body class="layout">
<!-- ==================== 顶部导航栏 ==================== -->
<header class="header" role="banner">
<!-- 左侧Logo 和主导航菜单 -->
<div class="header__left">
<!-- 系统 Logo -->
<h1 class="header__logo">ClusterManager</h1>
<!-- 主导航菜单 -->
<nav class="header__nav" role="navigation" aria-label="主导航">
<!-- 集群状态页面 - 对应用例图中的 UC_Status -->
<a href="#dashboard" class="header__nav-item header__nav-item--active"
data-page="dashboard" aria-current="page">
集群状态
</a>
<!-- 日志查询页面 - 对应用例图中的 UC_QueryLogs -->
<a href="#logs" class="header__nav-item" data-page="logs">
日志查询
</a>
<!-- 故障诊断页面 - 对应用例图中的 UC_Diagnose -->
<a href="#diagnosis" class="header__nav-item" data-page="diagnosis">
故障诊断
</a>
<!-- 自动修复页面 - 对应用例图中的 UC_Repair -->
<a href="#repair" class="header__nav-item" data-page="repair">
自动修复
</a>
<!-- 系统配置下拉菜单 -->
<div class="header__dropdown">
<button class="header__nav-item header__dropdown-trigger"
aria-haspopup="true" aria-expanded="false">
系统配置
<i class="fas fa-chevron-down header__dropdown-icon" aria-hidden="true"></i>
</button>
<!-- 下拉菜单内容 -->
<div class="header__dropdown-menu" role="menu">
<!-- Flume 配置 - 对应用例图中的 UC_ConfigFlume -->
<a href="#flume-config" class="header__dropdown-item" role="menuitem">
Flume 配置
</a>
<!-- 告警配置 - 对应用例图中的 UC_ConfigAlert -->
<a href="#alert-config" class="header__dropdown-item" role="menuitem">
告警配置
</a>
</div>
</div>
</nav>
</div>
<!-- 右侧:搜索框和用户菜单 -->
<div class="header__right">
<!-- 全局搜索框 -->
<div class="header__search">
<label for="global-search" class="u-sr-only">全局搜索</label>
<input type="search"
id="global-search"
class="header__search-input"
placeholder="搜索节点、日志或配置..."
aria-label="全局搜索">
<i class="fas fa-search header__search-icon" aria-hidden="true"></i>
</div>
<!-- 用户头像和下拉菜单 -->
<div class="header__user-menu">
<button class="header__user-avatar"
aria-haspopup="true"
aria-expanded="false"
aria-label="用户菜单">
<i class="fas fa-user header__user-avatar-icon" aria-hidden="true"></i>
</button>
<!-- 用户菜单下拉内容 -->
<div class="header__user-dropdown" role="menu">
<a href="#profile" class="header__user-dropdown-item" role="menuitem">
个人主页
</a>
<a href="#account" class="header__user-dropdown-item" role="menuitem">
账号管理
</a>
<a href="#clusters" class="header__user-dropdown-item" role="menuitem">
集群管理
</a>
</div>
</div>
</div>
</header>
<!-- ==================== 主体内容区域 ==================== -->
<div class="layout__container">
<!-- 左侧边栏 - 角色权限控制模块 -->
<aside class="sidebar" role="complementary" aria-label="权限管理导航">
<!-- 侧边栏标题 -->
<div class="sidebar__header">
<h2 class="sidebar__title">角色权限控制</h2>
</div>
<!-- 侧边栏导航菜单 -->
<nav class="sidebar__nav" role="navigation" aria-label="权限管理">
<a href="#user-management" class="sidebar__link">
<i class="fas fa-users sidebar__icon" aria-hidden="true"></i>
用户管理
</a>
<a href="#role-assignment" class="sidebar__link">
<i class="fas fa-user-tag sidebar__icon" aria-hidden="true"></i>
角色分配
</a>
<a href="#permission-policy" class="sidebar__link">
<i class="fas fa-shield-alt sidebar__icon" aria-hidden="true"></i>
权限策略
</a>
<a href="#audit-logs" class="sidebar__link">
<i class="fas fa-clipboard-list sidebar__icon" aria-hidden="true"></i>
审计日志
</a>
</nav>
</aside>
<!-- 主内容区域 -->
<main class="layout__main" role="main">
<div class="layout__content">
<!-- ==================== 仪表板部分 ==================== -->
<section id="dashboard" class="dashboard" aria-labelledby="dashboard-title">
<!-- 页面标题和更新时间 -->
<header class="dashboard__header">
<h2 id="dashboard-title" class="dashboard__title">仪表板 - 集群概览</h2>
<time class="dashboard__update-time" datetime="2023-10-15T14:30:00">
更新时间: 2023年10月15日 14:30
</time>
</header>
<!-- 集群状态统计卡片 -->
<div class="dashboard__stats-grid" role="region" aria-label="集群状态统计">
<!-- 总节点数统计卡片 -->
<article class="dashboard__stat-card">
<div class="dashboard__stat-card-content">
<div class="dashboard__stat-card-info">
<h3 class="dashboard__stat-card-label">总节点数</h3>
<p class="dashboard__stat-card-value">24</p>
</div>
<div class="dashboard__stat-card-icon dashboard__stat-card-icon--primary">
<i class="fas fa-server" aria-hidden="true"></i>
</div>
</div>
</article>
<!-- 健康节点统计卡片 -->
<article class="dashboard__stat-card">
<div class="dashboard__stat-card-content">
<div class="dashboard__stat-card-info">
<h3 class="dashboard__stat-card-label">健康节点</h3>
<p class="dashboard__stat-card-value dashboard__stat-card-value--success">22</p>
</div>
<div class="dashboard__stat-card-icon dashboard__stat-card-icon--success">
<i class="fas fa-heartbeat" aria-hidden="true"></i>
</div>
</div>
</article>
<!-- 警告节点统计卡片 -->
<article class="dashboard__stat-card">
<div class="dashboard__stat-card-content">
<div class="dashboard__stat-card-info">
<h3 class="dashboard__stat-card-label">警告节点</h3>
<p class="dashboard__stat-card-value dashboard__stat-card-value--warning">1</p>
</div>
<div class="dashboard__stat-card-icon dashboard__stat-card-icon--warning">
<i class="fas fa-exclamation-triangle" aria-hidden="true"></i>
</div>
</div>
</article>
<!-- 异常节点统计卡片 -->
<article class="dashboard__stat-card">
<div class="dashboard__stat-card-content">
<div class="dashboard__stat-card-info">
<h3 class="dashboard__stat-card-label">异常节点</h3>
<p class="dashboard__stat-card-value dashboard__stat-card-value--error">1</p>
</div>
<div class="dashboard__stat-card-icon dashboard__stat-card-icon--error">
<i class="fas fa-times-circle" aria-hidden="true"></i>
</div>
</div>
</article>
</div>
<!-- 监控图表区域 -->
<div class="dashboard__charts-grid" role="region" aria-label="监控图表">
<!-- CPU 使用率趋势图 -->
<article class="dashboard__chart-card">
<h3 class="dashboard__chart-title">CPU 使用率趋势</h3>
<!-- ECharts 图表容器 -->
<div id="cpuChart" class="dashboard__chart-container"
role="img" aria-label="CPU使用率趋势图表"></div>
</article>
<!-- 内存使用情况图 -->
<article class="dashboard__chart-card">
<h3 class="dashboard__chart-title">内存使用情况</h3>
<!-- ECharts 图表容器 -->
<div id="memoryChart" class="dashboard__chart-container"
role="img" aria-label="内存使用情况图表"></div>
</article>
</div>
<!-- 节点状态详情表格 -->
<article class="dashboard__table-container" role="region" aria-labelledby="nodes-table-title">
<!-- 表格标题 -->
<header class="dashboard__table-header">
<h3 id="nodes-table-title" class="dashboard__table-title">节点状态详情</h3>
</header>
<!-- 表格内容 -->
<div class="u-overflow-x-auto">
<table class="dashboard__table" role="table">
<!-- 表格头部 -->
<thead class="dashboard__table-head">
<tr>
<th class="dashboard__table-th" scope="col">节点名称</th>
<th class="dashboard__table-th" scope="col">IP 地址</th>
<th class="dashboard__table-th" scope="col">状态</th>
<th class="dashboard__table-th" scope="col">CPU 使用率</th>
<th class="dashboard__table-th" scope="col">内存使用</th>
<th class="dashboard__table-th" scope="col">最近更新</th>
</tr>
</thead>
<!-- 表格数据行 -->
<tbody>
<!-- 节点 001 - 运行正常 -->
<tr class="dashboard__table-row">
<td class="dashboard__table-td">
<strong>node-001</strong>
</td>
<td class="dashboard__table-td">192.168.1.101</td>
<td class="dashboard__table-td">
<span class="dashboard__status-indicator">
<span class="dashboard__status-dot dashboard__status-dot--running"
aria-hidden="true"></span>
<span class="dashboard__status-text">运行中</span>
</span>
</td>
<td class="dashboard__table-td">45%</td>
<td class="dashboard__table-td">2.1 GB / 8 GB</td>
<td class="dashboard__table-td">
<time datetime="2023-10-15T14:28:00">2分钟前</time>
</td>
</tr>
<!-- 节点 002 - 警告状态 -->
<tr class="dashboard__table-row">
<td class="dashboard__table-td">
<strong>node-002</strong>
</td>
<td class="dashboard__table-td">192.168.1.102</td>
<td class="dashboard__table-td">
<span class="dashboard__status-indicator">
<span class="dashboard__status-dot dashboard__status-dot--warning"
aria-hidden="true"></span>
<span class="dashboard__status-text">警告</span>
</span>
</td>
<td class="dashboard__table-td">82%</td>
<td class="dashboard__table-td">6.4 GB / 8 GB</td>
<td class="dashboard__table-td">
<time datetime="2023-10-15T14:25:00">5分钟前</time>
</td>
</tr>
<!-- 节点 003 - 异常状态 -->
<tr class="dashboard__table-row">
<td class="dashboard__table-td">
<strong>node-003</strong>
</td>
<td class="dashboard__table-td">192.168.1.103</td>
<td class="dashboard__table-td">
<span class="dashboard__status-indicator">
<span class="dashboard__status-dot dashboard__status-dot--error"
aria-hidden="true"></span>
<span class="dashboard__status-text">异常</span>
</span>
</td>
<td class="dashboard__table-td">-</td>
<td class="dashboard__table-td">-</td>
<td class="dashboard__table-td">
<time datetime="2023-10-15T13:30:00">1小时前</time>
</td>
</tr>
</tbody>
</table>
</div>
</article>
</section>
<!-- ==================== 日志查询部分 ==================== -->
<section id="logs" class="u-hidden layout__section" aria-labelledby="logs-title">
<!-- 页面标题和导出按钮 -->
<header class="layout__page-header">
<div>
<h2 id="logs-title" class="layout__page-title">日志查询</h2>
<p class="layout__page-subtitle">查询和分析系统日志信息</p>
</div>
<div class="layout__page-actions">
<button class="btn btn--primary">
<i class="fas fa-download btn__icon--left" aria-hidden="true"></i>
导出日志
</button>
</div>
</header>
<!-- 搜索和筛选条件区域 -->
<article class="layout__card">
<div class="layout__card-header">
<h3 class="layout__card-title">搜索条件</h3>
</div>
<div class="layout__card-body">
<form class="layout__grid layout__grid--3" role="search">
<!-- 日志级别选择 -->
<div>
<label for="log-level" class="u-text-sm u-font-medium u-text-gray-700">
日志级别
</label>
<select id="log-level" class="u-w-full u-p-2 u-border u-rounded u-mt-1">
<option value="">全部级别</option>
<option value="debug">DEBUG</option>
<option value="info">INFO</option>
<option value="warn">WARN</option>
<option value="error">ERROR</option>
</select>
</div>
<!-- 来源节点选择 -->
<div>
<label for="source-node" class="u-text-sm u-font-medium u-text-gray-700">
来源节点
</label>
<select id="source-node" class="u-w-full u-p-2 u-border u-rounded u-mt-1">
<option value="">全部节点</option>
<option value="node-001">node-001</option>
<option value="node-002">node-002</option>
<option value="node-003">node-003</option>
</select>
</div>
<!-- 时间范围选择 -->
<div>
<label for="time-range" class="u-text-sm u-font-medium u-text-gray-700">
时间范围
</label>
<select id="time-range" class="u-w-full u-p-2 u-border u-rounded u-mt-1">
<option value="1h">最近1小时</option>
<option value="6h">最近6小时</option>
<option value="24h">最近24小时</option>
<option value="7d">最近7天</option>
<option value="custom">自定义</option>
</select>
</div>
<!-- 搜索按钮 -->
<div class="u-flex u-items-end">
<button type="submit" class="btn btn--primary u-w-full">
<i class="fas fa-search btn__icon--left" aria-hidden="true"></i>
搜索日志
</button>
</div>
</form>
</div>
</article>
<!-- 日志列表 -->
<article class="layout__card">
<div class="layout__card-header">
<h3 class="layout__card-title">日志记录</h3>
</div>
<div class="layout__card-body u-p-0">
<div class="u-overflow-x-auto">
<table class="dashboard__table">
<thead class="dashboard__table-head">
<tr>
<th class="dashboard__table-th" scope="col">时间</th>
<th class="dashboard__table-th" scope="col">级别</th>
<th class="dashboard__table-th" scope="col">来源</th>
<th class="dashboard__table-th" scope="col">消息</th>
</tr>
</thead>
<tbody>
<tr class="dashboard__table-row">
<td class="dashboard__table-td">
<time datetime="2023-10-15T14:30:15">14:30:15</time>
</td>
<td class="dashboard__table-td">
<span class="u-text-error u-font-medium">ERROR</span>
</td>
<td class="dashboard__table-td">node-003</td>
<td class="dashboard__table-td">连接超时:无法连接到 DataNode</td>
</tr>
<tr class="dashboard__table-row">
<td class="dashboard__table-td">
<time datetime="2023-10-15T14:29:42">14:29:42</time>
</td>
<td class="dashboard__table-td">
<span class="u-text-warning u-font-medium">WARN</span>
</td>
<td class="dashboard__table-td">node-002</td>
<td class="dashboard__table-td">CPU 使用率过高82%</td>
</tr>
<tr class="dashboard__table-row">
<td class="dashboard__table-td">
<time datetime="2023-10-15T14:28:33">14:28:33</time>
</td>
<td class="dashboard__table-td">
<span class="u-text-primary u-font-medium">INFO</span>
</td>
<td class="dashboard__table-td">node-001</td>
<td class="dashboard__table-td">心跳检测正常</td>
</tr>
</tbody>
</table>
</div>
</div>
</article>
</section>
<!-- ==================== 故障诊断部分 ==================== -->
<section id="diagnosis" class="u-hidden layout__section" aria-labelledby="diagnosis-title">
<header class="layout__page-header">
<div>
<h2 id="diagnosis-title" class="layout__page-title">故障诊断</h2>
<p class="layout__page-subtitle">自动检测和诊断系统故障</p>
</div>
<div class="layout__page-actions">
<button class="btn btn--primary">
<i class="fas fa-play btn__icon--left" aria-hidden="true"></i>
开始诊断
</button>
</div>
</header>
<!-- 诊断进度 -->
<article class="layout__card">
<div class="layout__card-header">
<h3 class="layout__card-title">诊断进度</h3>
</div>
<div class="layout__card-body">
<div class="u-space-y-4">
<div class="u-flex u-items-center u-gap-3">
<i class="fas fa-check-circle u-text-success" aria-hidden="true"></i>
<span>网络连接检查</span>
<span class="u-text-success u-text-sm">已完成</span>
</div>
<div class="u-flex u-items-center u-gap-3">
<i class="fas fa-spinner fa-spin u-text-primary" aria-hidden="true"></i>
<span>服务状态检查</span>
<span class="u-text-primary u-text-sm">进行中...</span>
</div>
<div class="u-flex u-items-center u-gap-3">
<i class="fas fa-clock u-text-gray-400" aria-hidden="true"></i>
<span>资源使用分析</span>
<span class="u-text-gray-500 u-text-sm">等待中</span>
</div>
</div>
</div>
</article>
</section>
<!-- ==================== 自动修复部分 ==================== -->
<section id="repair" class="u-hidden layout__section" aria-labelledby="repair-title">
<header class="layout__page-header">
<div>
<h2 id="repair-title" class="layout__page-title">自动修复</h2>
<p class="layout__page-subtitle">自动修复检测到的系统故障</p>
</div>
<div class="layout__page-actions">
<button class="btn btn--danger">
<i class="fas fa-tools btn__icon--left" aria-hidden="true"></i>
执行修复
</button>
</div>
</header>
<!-- 修复建议 -->
<article class="layout__card">
<div class="layout__card-header">
<h3 class="layout__card-title">修复建议</h3>
</div>
<div class="layout__card-body">
<div class="u-space-y-4">
<div class="u-p-4 u-bg-error u-bg-opacity-10 u-border u-border-error u-rounded">
<h4 class="u-font-semibold u-text-error">严重node-003 连接失败</h4>
<p class="u-text-sm u-text-gray-600 u-mt-1">
建议重启 DataNode 服务并检查网络配置
</p>
</div>
<div class="u-p-4 u-bg-warning u-bg-opacity-10 u-border u-border-warning u-rounded">
<h4 class="u-font-semibold u-text-warning">警告node-002 CPU 使用率过高</h4>
<p class="u-text-sm u-text-gray-600 u-mt-1">
建议优化任务调度或增加计算资源
</p>
</div>
</div>
</div>
</article>
</section>
</div>
</main>
</div>
<!-- JavaScript 脚本 -->
<script src="./utils/charts.js"></script>
<script src="./utils/navigation.js"></script>
<script src="./utils/responsive.js"></script>
</body>
</html>

@ -0,0 +1,60 @@
/* CSS Reset and Base Styles */
/* 基础重置样式 */
*,
*::before,
*::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
html {
line-height: 1.15;
-webkit-text-size-adjust: 100%;
}
body {
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
background-color: #f9fafb;
color: #1e293b;
line-height: 1.6;
}
/* 移除默认样式 */
h1, h2, h3, h4, h5, h6 {
margin: 0;
font-weight: inherit;
}
ul, ol {
list-style: none;
}
a {
text-decoration: none;
color: inherit;
}
button {
background: none;
border: none;
cursor: pointer;
font-family: inherit;
}
input, textarea, select {
font-family: inherit;
font-size: inherit;
}
img {
max-width: 100%;
height: auto;
}
table {
border-collapse: collapse;
border-spacing: 0;
}

@ -0,0 +1,88 @@
/* CSS Custom Properties (Variables) */
/* CSS 自定义属性(变量)定义 */
:root {
/* 颜色系统 - Color System */
--color-primary: #3b82f6;
--color-primary-light: #60a5fa;
--color-primary-dark: #2563eb;
--color-secondary: #64748b;
--color-secondary-light: #94a3b8;
--color-secondary-dark: #475569;
--color-success: #10b981;
--color-warning: #f59e0b;
--color-error: #ef4444;
--color-info: #06b6d4;
/* 中性色 - Neutral Colors */
--color-white: #ffffff;
--color-gray-50: #f9fafb;
--color-gray-100: #f3f4f6;
--color-gray-200: #e5e7eb;
--color-gray-300: #d1d5db;
--color-gray-400: #9ca3af;
--color-gray-500: #6b7280;
--color-gray-600: #4b5563;
--color-gray-700: #374151;
--color-gray-800: #1f2937;
--color-gray-900: #111827;
/* 字体系统 - Typography */
--font-family-base: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
--font-family-logo: "Pacifico", serif;
--font-size-xs: 0.75rem; /* 12px */
--font-size-sm: 0.875rem; /* 14px */
--font-size-base: 1rem; /* 16px */
--font-size-lg: 1.125rem; /* 18px */
--font-size-xl: 1.25rem; /* 20px */
--font-size-2xl: 1.5rem; /* 24px */
--font-size-3xl: 1.875rem; /* 30px */
--font-weight-normal: 400;
--font-weight-medium: 500;
--font-weight-semibold: 600;
--font-weight-bold: 700;
/* 间距系统 - Spacing */
--spacing-1: 0.25rem; /* 4px */
--spacing-2: 0.5rem; /* 8px */
--spacing-3: 0.75rem; /* 12px */
--spacing-4: 1rem; /* 16px */
--spacing-5: 1.25rem; /* 20px */
--spacing-6: 1.5rem; /* 24px */
--spacing-8: 2rem; /* 32px */
--spacing-10: 2.5rem; /* 40px */
--spacing-12: 3rem; /* 48px */
/* 边框圆角 - Border Radius */
--border-radius-none: 0px;
--border-radius-sm: 2px;
--border-radius-base: 4px;
--border-radius-md: 8px;
--border-radius-lg: 12px;
--border-radius-xl: 16px;
--border-radius-2xl: 20px;
--border-radius-full: 9999px;
/* 阴影系统 - Shadow System */
--shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
--shadow-base: 0 4px 12px rgba(0, 0, 0, 0.05);
--shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
--shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.1);
--shadow-xl: 0 20px 25px -5px rgba(0, 0, 0, 0.1);
/* 过渡动画 - Transitions */
--transition-fast: 150ms ease-in-out;
--transition-base: 250ms ease-in-out;
--transition-slow: 350ms ease-in-out;
/* 断点 - Breakpoints */
--breakpoint-sm: 640px;
--breakpoint-md: 768px;
--breakpoint-lg: 1024px;
--breakpoint-xl: 1280px;
--breakpoint-2xl: 1536px;
}

@ -0,0 +1,299 @@
/* Button Component Styles */
/* 按钮组件样式 - 使用 BEM 命名规范 */
.btn {
display: inline-flex;
align-items: center;
justify-content: center;
gap: var(--spacing-2);
padding: var(--spacing-2) var(--spacing-4);
font-size: var(--font-size-sm);
font-weight: var(--font-weight-medium);
line-height: 1.5;
border: 1px solid transparent;
border-radius: var(--border-radius-base);
cursor: pointer;
transition: all var(--transition-fast);
text-decoration: none;
white-space: nowrap;
user-select: none;
}
.btn:focus {
outline: none;
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
}
.btn:disabled {
opacity: 0.6;
cursor: not-allowed;
pointer-events: none;
}
/* 按钮尺寸变体 */
.btn--sm {
padding: var(--spacing-1) var(--spacing-3);
font-size: var(--font-size-xs);
}
.btn--md {
padding: var(--spacing-2) var(--spacing-4);
font-size: var(--font-size-sm);
}
.btn--lg {
padding: var(--spacing-3) var(--spacing-6);
font-size: var(--font-size-base);
}
/* 主要按钮 */
.btn--primary {
background-color: var(--color-primary);
color: var(--color-white);
border-color: var(--color-primary);
}
.btn--primary:hover {
background-color: var(--color-primary-dark);
border-color: var(--color-primary-dark);
transform: translateY(-1px);
box-shadow: var(--shadow-md);
}
.btn--primary:active {
transform: translateY(0);
box-shadow: var(--shadow-sm);
}
/* 次要按钮 */
.btn--secondary {
background-color: var(--color-gray-100);
color: var(--color-gray-700);
border-color: var(--color-gray-300);
}
.btn--secondary:hover {
background-color: var(--color-gray-200);
border-color: var(--color-gray-400);
transform: translateY(-1px);
box-shadow: var(--shadow-md);
}
.btn--secondary:active {
transform: translateY(0);
box-shadow: var(--shadow-sm);
}
/* 成功按钮 */
.btn--success {
background-color: var(--color-success);
color: var(--color-white);
border-color: var(--color-success);
}
.btn--success:hover {
background-color: #059669;
border-color: #059669;
transform: translateY(-1px);
box-shadow: var(--shadow-md);
}
/* 警告按钮 */
.btn--warning {
background-color: var(--color-warning);
color: var(--color-white);
border-color: var(--color-warning);
}
.btn--warning:hover {
background-color: #d97706;
border-color: #d97706;
transform: translateY(-1px);
box-shadow: var(--shadow-md);
}
/* 危险按钮 */
.btn--danger {
background-color: var(--color-error);
color: var(--color-white);
border-color: var(--color-error);
}
.btn--danger:hover {
background-color: #dc2626;
border-color: #dc2626;
transform: translateY(-1px);
box-shadow: var(--shadow-md);
}
/* 轮廓按钮 */
.btn--outline {
background-color: transparent;
color: var(--color-primary);
border-color: var(--color-primary);
}
.btn--outline:hover {
background-color: var(--color-primary);
color: var(--color-white);
transform: translateY(-1px);
box-shadow: var(--shadow-md);
}
/* 幽灵按钮 */
.btn--ghost {
background-color: transparent;
color: var(--color-gray-700);
border-color: transparent;
}
.btn--ghost:hover {
background-color: var(--color-gray-100);
color: var(--color-gray-900);
}
/* 链接样式按钮 */
.btn--link {
background-color: transparent;
color: var(--color-primary);
border-color: transparent;
padding: 0;
text-decoration: underline;
}
.btn--link:hover {
color: var(--color-primary-dark);
text-decoration: none;
}
/* 圆形按钮 */
.btn--rounded {
border-radius: var(--border-radius-full);
}
/* 方形按钮 */
.btn--square {
border-radius: 0;
}
/* 图标按钮 */
.btn--icon-only {
padding: var(--spacing-2);
width: 2.5rem;
height: 2.5rem;
}
.btn--icon-only.btn--sm {
width: 2rem;
height: 2rem;
padding: var(--spacing-1);
}
.btn--icon-only.btn--lg {
width: 3rem;
height: 3rem;
padding: var(--spacing-3);
}
/* 按钮图标 */
.btn__icon {
font-size: 1em;
line-height: 1;
}
.btn__icon--left {
margin-right: var(--spacing-2);
}
.btn__icon--right {
margin-left: var(--spacing-2);
}
/* 加载状态 */
.btn--loading {
position: relative;
color: transparent;
pointer-events: none;
}
.btn--loading::after {
content: '';
position: absolute;
top: 50%;
left: 50%;
width: 1rem;
height: 1rem;
margin: -0.5rem 0 0 -0.5rem;
border: 2px solid currentColor;
border-radius: var(--border-radius-full);
border-top-color: transparent;
animation: btn-spin 0.8s linear infinite;
}
@keyframes btn-spin {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
/* 按钮组 */
.btn-group {
display: inline-flex;
border-radius: var(--border-radius-base);
overflow: hidden;
box-shadow: var(--shadow-sm);
}
.btn-group .btn {
border-radius: 0;
border-right-width: 0;
}
.btn-group .btn:first-child {
border-top-left-radius: var(--border-radius-base);
border-bottom-left-radius: var(--border-radius-base);
}
.btn-group .btn:last-child {
border-top-right-radius: var(--border-radius-base);
border-bottom-right-radius: var(--border-radius-base);
border-right-width: 1px;
}
.btn-group .btn:not(:first-child):not(:last-child) {
border-radius: 0;
}
/* 响应式设计 */
@media (max-width: 768px) {
.btn {
padding: var(--spacing-3) var(--spacing-4);
font-size: var(--font-size-base);
}
.btn--sm {
padding: var(--spacing-2) var(--spacing-3);
font-size: var(--font-size-sm);
}
.btn-group {
flex-direction: column;
}
.btn-group .btn {
border-right-width: 1px;
border-bottom-width: 0;
}
.btn-group .btn:first-child {
border-radius: var(--border-radius-base) var(--border-radius-base) 0 0;
}
.btn-group .btn:last-child {
border-radius: 0 0 var(--border-radius-base) var(--border-radius-base);
border-bottom-width: 1px;
}
}

@ -0,0 +1,263 @@
/* Dashboard Component Styles */
/* 仪表板组件样式 - 使用 BEM 命名规范 */
.dashboard {
padding: var(--spacing-6);
background-color: var(--color-gray-50);
min-height: 100vh;
}
/* 仪表板标题区域 */
.dashboard__header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: var(--spacing-6);
}
.dashboard__title {
font-size: var(--font-size-2xl);
font-weight: var(--font-weight-bold);
color: var(--color-gray-900);
}
.dashboard__update-time {
font-size: var(--font-size-sm);
color: var(--color-gray-500);
}
/* 统计卡片网格 */
.dashboard__stats-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: var(--spacing-6);
margin-bottom: var(--spacing-8);
}
/* 统计卡片样式 */
.dashboard__stat-card {
background-color: var(--color-white);
padding: var(--spacing-6);
border-radius: var(--border-radius-xl);
box-shadow: var(--shadow-base);
transition: transform var(--transition-fast), box-shadow var(--transition-fast);
}
.dashboard__stat-card:hover {
transform: translateY(-2px);
box-shadow: var(--shadow-lg);
}
.dashboard__stat-card-content {
display: flex;
justify-content: space-between;
align-items: flex-start;
}
.dashboard__stat-card-info {
flex: 1;
}
.dashboard__stat-card-label {
color: var(--color-gray-500);
font-size: var(--font-size-sm);
font-weight: var(--font-weight-medium);
margin-bottom: var(--spacing-2);
}
.dashboard__stat-card-value {
font-size: var(--font-size-3xl);
font-weight: var(--font-weight-bold);
color: var(--color-gray-900);
}
.dashboard__stat-card-value--success {
color: var(--color-success);
}
.dashboard__stat-card-value--warning {
color: var(--color-warning);
}
.dashboard__stat-card-value--error {
color: var(--color-error);
}
.dashboard__stat-card-icon {
width: 3rem;
height: 3rem;
border-radius: var(--border-radius-full);
display: flex;
align-items: center;
justify-content: center;
font-size: var(--font-size-xl);
}
.dashboard__stat-card-icon--primary {
background-color: rgba(59, 130, 246, 0.1);
color: var(--color-primary);
}
.dashboard__stat-card-icon--success {
background-color: rgba(16, 185, 129, 0.1);
color: var(--color-success);
}
.dashboard__stat-card-icon--warning {
background-color: rgba(245, 158, 11, 0.1);
color: var(--color-warning);
}
.dashboard__stat-card-icon--error {
background-color: rgba(239, 68, 68, 0.1);
color: var(--color-error);
}
/* 图表网格 */
.dashboard__charts-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(400px, 1fr));
gap: var(--spacing-6);
margin-bottom: var(--spacing-8);
}
/* 图表卡片 */
.dashboard__chart-card {
background-color: var(--color-white);
padding: var(--spacing-6);
border-radius: var(--border-radius-xl);
box-shadow: var(--shadow-base);
}
.dashboard__chart-title {
font-size: var(--font-size-lg);
font-weight: var(--font-weight-semibold);
color: var(--color-gray-900);
margin-bottom: var(--spacing-4);
}
.dashboard__chart-container {
height: 300px;
width: 100%;
}
/* 表格容器 */
.dashboard__table-container {
background-color: var(--color-white);
border-radius: var(--border-radius-xl);
box-shadow: var(--shadow-base);
overflow: hidden;
}
.dashboard__table-header {
padding: var(--spacing-6) var(--spacing-6) var(--spacing-4);
border-bottom: 1px solid var(--color-gray-200);
}
.dashboard__table-title {
font-size: var(--font-size-lg);
font-weight: var(--font-weight-semibold);
color: var(--color-gray-900);
}
/* 表格样式 */
.dashboard__table {
width: 100%;
border-collapse: collapse;
}
.dashboard__table-head {
background-color: var(--color-gray-50);
}
.dashboard__table-th {
padding: var(--spacing-3) var(--spacing-6);
text-align: left;
font-size: var(--font-size-xs);
font-weight: var(--font-weight-medium);
color: var(--color-gray-500);
text-transform: uppercase;
letter-spacing: 0.05em;
}
.dashboard__table-td {
padding: var(--spacing-4) var(--spacing-6);
border-top: 1px solid var(--color-gray-200);
font-size: var(--font-size-sm);
color: var(--color-gray-900);
}
.dashboard__table-row:hover {
background-color: var(--color-gray-50);
}
/* 状态指示器 */
.dashboard__status-indicator {
display: inline-flex;
align-items: center;
gap: var(--spacing-2);
}
.dashboard__status-dot {
width: 10px;
height: 10px;
border-radius: var(--border-radius-full);
}
.dashboard__status-dot--running {
background-color: var(--color-success);
}
.dashboard__status-dot--warning {
background-color: var(--color-warning);
}
.dashboard__status-dot--error {
background-color: var(--color-error);
}
.dashboard__status-text {
font-size: var(--font-size-sm);
color: var(--color-gray-700);
}
/* 响应式设计 */
@media (max-width: 1024px) {
.dashboard__stats-grid {
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: var(--spacing-4);
}
.dashboard__charts-grid {
grid-template-columns: 1fr;
gap: var(--spacing-4);
}
}
@media (max-width: 768px) {
.dashboard {
padding: var(--spacing-4);
}
.dashboard__header {
flex-direction: column;
align-items: flex-start;
gap: var(--spacing-2);
}
.dashboard__stats-grid {
grid-template-columns: 1fr;
}
.dashboard__stat-card {
padding: var(--spacing-4);
}
.dashboard__chart-card {
padding: var(--spacing-4);
}
.dashboard__table-container {
overflow-x: auto;
}
}

@ -0,0 +1,225 @@
/* Header Component Styles */
/* 头部组件样式 - 使用 BEM 命名规范 */
.header {
background-color: var(--color-white);
box-shadow: var(--shadow-sm);
padding: var(--spacing-4) var(--spacing-6);
display: flex;
align-items: center;
justify-content: space-between;
}
/* 头部左侧区域 */
.header__left {
display: flex;
align-items: center;
gap: var(--spacing-10);
}
/* Logo 样式 */
.header__logo {
font-size: var(--font-size-xl);
font-family: var(--font-family-logo);
color: var(--color-primary);
font-weight: var(--font-weight-normal);
}
/* 主导航容器 */
.header__nav {
display: flex;
gap: var(--spacing-6);
}
/* 导航项基础样式 */
.header__nav-item {
padding: var(--spacing-3) var(--spacing-3);
border-radius: var(--border-radius-md);
font-size: var(--font-size-sm);
font-weight: var(--font-weight-medium);
color: var(--color-gray-700);
transition: color var(--transition-fast);
cursor: pointer;
}
/* 导航项悬停和激活状态 */
.header__nav-item:hover,
.header__nav-item--active {
color: var(--color-primary);
}
/* 下拉菜单容器 */
.header__dropdown {
position: relative;
}
.header__dropdown-trigger {
display: flex;
align-items: center;
gap: var(--spacing-2);
}
.header__dropdown-icon {
font-size: var(--font-size-xs);
transition: transform var(--transition-fast);
}
.header__dropdown:hover .header__dropdown-icon {
transform: rotate(180deg);
}
/* 下拉菜单内容 */
.header__dropdown-menu {
position: absolute;
top: 100%;
left: 0;
margin-top: var(--spacing-1);
width: 12rem;
background-color: var(--color-white);
border-radius: var(--border-radius-md);
box-shadow: var(--shadow-lg);
z-index: 10;
padding: var(--spacing-1) 0;
opacity: 0;
visibility: hidden;
transform: translateY(-8px);
transition: all var(--transition-fast);
}
.header__dropdown:hover .header__dropdown-menu {
opacity: 1;
visibility: visible;
transform: translateY(0);
}
.header__dropdown-item {
display: block;
padding: var(--spacing-2) var(--spacing-4);
font-size: var(--font-size-sm);
color: var(--color-gray-700);
transition: background-color var(--transition-fast);
}
.header__dropdown-item:hover {
background-color: var(--color-gray-100);
}
/* 头部右侧区域 */
.header__right {
display: flex;
align-items: center;
gap: var(--spacing-4);
}
/* 搜索框容器 */
.header__search {
position: relative;
}
.header__search-input {
padding-left: var(--spacing-10);
padding-right: var(--spacing-4);
padding-top: var(--spacing-2);
padding-bottom: var(--spacing-2);
font-size: var(--font-size-sm);
border: 1px solid var(--color-gray-300);
border-radius: var(--border-radius-md);
background-color: var(--color-white);
transition: border-color var(--transition-fast), box-shadow var(--transition-fast);
}
.header__search-input:focus {
outline: none;
border-color: var(--color-primary);
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
}
.header__search-icon {
position: absolute;
left: var(--spacing-3);
top: 50%;
transform: translateY(-50%);
color: var(--color-gray-400);
font-size: var(--font-size-sm);
}
/* 用户菜单 */
.header__user-menu {
position: relative;
}
.header__user-avatar {
width: 2rem;
height: 2rem;
border-radius: var(--border-radius-full);
background-color: var(--color-gray-200);
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
transition: background-color var(--transition-fast);
}
.header__user-avatar:hover {
background-color: var(--color-gray-300);
}
.header__user-avatar-icon {
color: var(--color-gray-600);
font-size: var(--font-size-sm);
}
/* 用户下拉菜单 */
.header__user-dropdown {
position: absolute;
top: 100%;
right: 0;
margin-top: var(--spacing-2);
width: 12rem;
background-color: var(--color-white);
border-radius: var(--border-radius-md);
box-shadow: var(--shadow-lg);
z-index: 20;
padding: var(--spacing-1) 0;
opacity: 0;
visibility: hidden;
transform: translateY(-8px);
transition: all var(--transition-fast);
}
.header__user-menu:hover .header__user-dropdown {
opacity: 1;
visibility: visible;
transform: translateY(0);
}
.header__user-dropdown-item {
display: block;
padding: var(--spacing-2) var(--spacing-4);
font-size: var(--font-size-sm);
color: var(--color-gray-700);
transition: background-color var(--transition-fast);
}
.header__user-dropdown-item:hover {
background-color: var(--color-gray-100);
}
/* 响应式设计 */
@media (max-width: 768px) {
.header {
padding: var(--spacing-3) var(--spacing-4);
}
.header__left {
gap: var(--spacing-6);
}
.header__nav {
display: none;
}
.header__search {
display: none;
}
}

@ -0,0 +1,116 @@
/* Sidebar Component Styles */
/* 侧边栏组件样式 - 使用 BEM 命名规范 */
.sidebar {
width: 16rem;
background-color: var(--color-white);
box-shadow: inset -1px 0 0 var(--color-gray-200);
flex-shrink: 0;
display: flex;
flex-direction: column;
}
/* 侧边栏标题区域 */
.sidebar__header {
padding: var(--spacing-4);
border-bottom: 1px solid var(--color-gray-200);
}
.sidebar__title {
font-size: var(--font-size-sm);
font-weight: var(--font-weight-semibold);
color: var(--color-gray-500);
text-transform: uppercase;
letter-spacing: 0.05em;
}
/* 侧边栏导航区域 */
.sidebar__nav {
padding: var(--spacing-4) 0;
flex: 1;
}
/* 侧边栏链接样式 */
.sidebar__link {
display: block;
padding: var(--spacing-3) var(--spacing-6);
font-size: var(--font-size-sm);
font-weight: var(--font-weight-medium);
color: var(--color-gray-700);
transition: all var(--transition-fast);
border-left: 3px solid transparent;
}
.sidebar__link:hover {
background-color: var(--color-gray-50);
color: var(--color-primary);
border-left-color: var(--color-primary);
}
.sidebar__link--active {
background-color: rgba(59, 130, 246, 0.1);
color: var(--color-primary);
border-left-color: var(--color-primary);
}
/* 侧边栏图标 */
.sidebar__icon {
margin-right: var(--spacing-3);
font-size: var(--font-size-sm);
width: 1rem;
text-align: center;
}
/* 侧边栏分组 */
.sidebar__group {
margin-bottom: var(--spacing-6);
}
.sidebar__group-title {
padding: var(--spacing-2) var(--spacing-6);
font-size: var(--font-size-xs);
font-weight: var(--font-weight-semibold);
color: var(--color-gray-400);
text-transform: uppercase;
letter-spacing: 0.05em;
}
/* 侧边栏底部区域 */
.sidebar__footer {
padding: var(--spacing-4);
border-top: 1px solid var(--color-gray-200);
}
/* 响应式设计 */
@media (max-width: 768px) {
.sidebar {
position: fixed;
top: 0;
left: -16rem;
height: 100vh;
z-index: 50;
transition: left var(--transition-base);
}
.sidebar--open {
left: 0;
}
.sidebar__overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.5);
z-index: 40;
opacity: 0;
visibility: hidden;
transition: all var(--transition-base);
}
.sidebar__overlay--visible {
opacity: 1;
visibility: visible;
}
}

@ -0,0 +1,282 @@
/* Main Layout Styles */
/* 主布局样式 - 使用 BEM 命名规范 */
.layout {
min-height: 100vh;
display: flex;
flex-direction: column;
background-color: var(--color-gray-50);
}
/* 主容器 */
.layout__container {
display: flex;
flex: 1;
overflow: hidden;
}
/* 主内容区域 */
.layout__main {
flex: 1;
overflow: auto;
background-color: var(--color-gray-50);
}
/* 内容包装器 */
.layout__content {
padding: var(--spacing-6);
max-width: 100%;
}
/* 页面标题区域 */
.layout__page-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: var(--spacing-6);
padding-bottom: var(--spacing-4);
border-bottom: 1px solid var(--color-gray-200);
}
.layout__page-title {
font-size: var(--font-size-2xl);
font-weight: var(--font-weight-bold);
color: var(--color-gray-900);
margin: 0;
}
.layout__page-subtitle {
font-size: var(--font-size-sm);
color: var(--color-gray-500);
margin-top: var(--spacing-1);
}
.layout__page-actions {
display: flex;
gap: var(--spacing-3);
align-items: center;
}
/* 网格系统 */
.layout__grid {
display: grid;
gap: var(--spacing-6);
}
.layout__grid--1 {
grid-template-columns: 1fr;
}
.layout__grid--2 {
grid-template-columns: repeat(2, 1fr);
}
.layout__grid--3 {
grid-template-columns: repeat(3, 1fr);
}
.layout__grid--4 {
grid-template-columns: repeat(4, 1fr);
}
.layout__grid--auto {
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
}
/* 卡片布局 */
.layout__card {
background-color: var(--color-white);
border-radius: var(--border-radius-xl);
box-shadow: var(--shadow-base);
overflow: hidden;
}
.layout__card-header {
padding: var(--spacing-6) var(--spacing-6) var(--spacing-4);
border-bottom: 1px solid var(--color-gray-200);
}
.layout__card-title {
font-size: var(--font-size-lg);
font-weight: var(--font-weight-semibold);
color: var(--color-gray-900);
margin: 0;
}
.layout__card-description {
font-size: var(--font-size-sm);
color: var(--color-gray-500);
margin-top: var(--spacing-1);
}
.layout__card-body {
padding: var(--spacing-6);
}
.layout__card-footer {
padding: var(--spacing-4) var(--spacing-6);
background-color: var(--color-gray-50);
border-top: 1px solid var(--color-gray-200);
}
/* 分隔符 */
.layout__divider {
height: 1px;
background-color: var(--color-gray-200);
margin: var(--spacing-6) 0;
}
.layout__divider--vertical {
width: 1px;
height: auto;
margin: 0 var(--spacing-6);
}
/* 间距工具类 */
.layout__section {
margin-bottom: var(--spacing-10);
}
.layout__section:last-child {
margin-bottom: 0;
}
/* 滚动容器 */
.layout__scroll-container {
overflow: auto;
max-height: 100%;
}
.layout__scroll-container--horizontal {
overflow-x: auto;
overflow-y: hidden;
}
.layout__scroll-container--vertical {
overflow-x: hidden;
overflow-y: auto;
}
/* 粘性定位 */
.layout__sticky-top {
position: sticky;
top: 0;
z-index: 10;
background-color: var(--color-white);
}
.layout__sticky-bottom {
position: sticky;
bottom: 0;
z-index: 10;
background-color: var(--color-white);
}
/* 居中容器 */
.layout__centered {
display: flex;
align-items: center;
justify-content: center;
min-height: 50vh;
}
.layout__centered-content {
text-align: center;
max-width: 400px;
}
/* 响应式断点 */
@media (max-width: 1280px) {
.layout__grid--4 {
grid-template-columns: repeat(3, 1fr);
}
}
@media (max-width: 1024px) {
.layout__content {
padding: var(--spacing-4);
}
.layout__grid--3,
.layout__grid--4 {
grid-template-columns: repeat(2, 1fr);
}
.layout__page-header {
flex-direction: column;
align-items: flex-start;
gap: var(--spacing-4);
}
.layout__page-actions {
width: 100%;
justify-content: flex-start;
}
}
@media (max-width: 768px) {
.layout__container {
flex-direction: column;
}
.layout__content {
padding: var(--spacing-3);
}
.layout__grid--2,
.layout__grid--3,
.layout__grid--4 {
grid-template-columns: 1fr;
}
.layout__grid {
gap: var(--spacing-4);
}
.layout__card-header,
.layout__card-body,
.layout__card-footer {
padding: var(--spacing-4);
}
.layout__page-title {
font-size: var(--font-size-xl);
}
.layout__section {
margin-bottom: var(--spacing-6);
}
}
@media (max-width: 640px) {
.layout__content {
padding: var(--spacing-2);
}
.layout__page-header {
margin-bottom: var(--spacing-4);
padding-bottom: var(--spacing-3);
}
.layout__card-header,
.layout__card-body,
.layout__card-footer {
padding: var(--spacing-3);
}
}
/* 打印样式 */
@media print {
.layout {
background-color: white;
}
.layout__card {
box-shadow: none;
border: 1px solid var(--color-gray-300);
}
.layout__page-actions {
display: none;
}
}

@ -0,0 +1,444 @@
/* Responsive Layout Styles */
/* 响应式布局样式 - 移动端适配和响应式设计 */
/* ==================== 移动端优先的响应式设计 ==================== */
/* 基础布局响应式调整 */
.layout {
min-height: 100vh;
display: flex;
flex-direction: column;
}
.layout__container {
display: flex;
flex: 1;
min-height: 0; /* 防止 flex 子元素溢出 */
}
/* ==================== 头部响应式设计 ==================== */
/* 移动端头部调整 */
@media (max-width: 768px) {
.header {
padding: var(--spacing-3) var(--spacing-4);
flex-wrap: wrap;
gap: var(--spacing-3);
}
.header__left {
gap: var(--spacing-4);
flex: 1;
min-width: 0;
}
.header__logo {
font-size: var(--font-size-lg);
}
/* 移动端导航隐藏,使用汉堡菜单 */
.header__nav {
display: none;
position: absolute;
top: 100%;
left: 0;
right: 0;
background-color: var(--color-white);
box-shadow: var(--shadow-lg);
flex-direction: column;
padding: var(--spacing-4);
z-index: 50;
}
.header__nav--mobile-open {
display: flex;
}
.header__nav-item {
padding: var(--spacing-3) var(--spacing-4);
border-radius: var(--border-radius-md);
width: 100%;
text-align: left;
}
/* 移动端汉堡菜单按钮 */
.header__mobile-menu-btn {
display: block;
background: none;
border: none;
font-size: var(--font-size-lg);
color: var(--color-gray-700);
cursor: pointer;
padding: var(--spacing-2);
}
.header__right {
gap: var(--spacing-2);
}
.header__search {
display: none; /* 移动端隐藏搜索框 */
}
}
/* 桌面端汉堡菜单隐藏 */
@media (min-width: 769px) {
.header__mobile-menu-btn {
display: none;
}
}
/* ==================== 侧边栏响应式设计 ==================== */
.sidebar {
width: 250px;
flex-shrink: 0;
transition: transform var(--transition-normal);
}
/* 移动端侧边栏 */
@media (max-width: 768px) {
.sidebar {
position: fixed;
top: 0;
left: 0;
height: 100vh;
z-index: 40;
background-color: var(--color-white);
transform: translateX(-100%);
box-shadow: var(--shadow-xl);
}
.sidebar--mobile-open {
transform: translateX(0);
}
/* 移动端遮罩层 */
.sidebar-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.5);
z-index: 30;
opacity: 0;
visibility: hidden;
transition: opacity var(--transition-normal), visibility var(--transition-normal);
}
.sidebar-overlay--show {
opacity: 1;
visibility: visible;
}
/* 调整主内容区域 */
.layout__main {
width: 100%;
margin-left: 0;
}
}
/* 平板端侧边栏 */
@media (min-width: 769px) and (max-width: 1024px) {
.sidebar {
width: 200px;
}
}
/* ==================== 主内容区域响应式设计 ==================== */
.layout__main {
flex: 1;
min-width: 0;
overflow-x: auto;
}
.layout__content {
padding: var(--spacing-6);
max-width: 100%;
}
/* 移动端主内容调整 */
@media (max-width: 768px) {
.layout__content {
padding: var(--spacing-4);
}
.layout__page-header {
flex-direction: column;
gap: var(--spacing-4);
align-items: stretch;
}
.layout__page-actions {
width: 100%;
}
.layout__page-actions .btn {
width: 100%;
justify-content: center;
}
}
/* ==================== 仪表板响应式设计 ==================== */
/* 统计卡片网格 */
.dashboard__stats-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: var(--spacing-4);
}
/* 移动端统计卡片 */
@media (max-width: 640px) {
.dashboard__stats-grid {
grid-template-columns: repeat(2, 1fr);
gap: var(--spacing-3);
}
}
/* 超小屏幕单列显示 */
@media (max-width: 480px) {
.dashboard__stats-grid {
grid-template-columns: 1fr;
}
}
/* 图表网格响应式 */
.dashboard__charts-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(400px, 1fr));
gap: var(--spacing-6);
}
/* 移动端图表 */
@media (max-width: 768px) {
.dashboard__charts-grid {
grid-template-columns: 1fr;
gap: var(--spacing-4);
}
.dashboard__chart-container {
height: 250px; /* 移动端降低图表高度 */
}
}
/* 超小屏幕图表 */
@media (max-width: 480px) {
.dashboard__charts-grid {
grid-template-columns: 1fr;
}
.dashboard__chart-container {
height: 200px;
}
}
/* ==================== 表格响应式设计 ==================== */
/* 移动端表格滚动 */
@media (max-width: 768px) {
.dashboard__table-container {
overflow-x: auto;
-webkit-overflow-scrolling: touch;
}
.dashboard__table {
min-width: 600px; /* 确保表格有最小宽度 */
}
.dashboard__table-th,
.dashboard__table-td {
padding: var(--spacing-2) var(--spacing-3);
font-size: var(--font-size-sm);
}
}
/* 超小屏幕表格优化 */
@media (max-width: 480px) {
.dashboard__table {
min-width: 500px;
}
.dashboard__table-th,
.dashboard__table-td {
padding: var(--spacing-2);
font-size: var(--font-size-xs);
}
/* 隐藏不重要的列 */
.dashboard__table-th:nth-child(6),
.dashboard__table-td:nth-child(6) {
display: none;
}
}
/* ==================== 卡片响应式设计 ==================== */
.layout__card {
margin-bottom: var(--spacing-6);
}
/* 移动端卡片 */
@media (max-width: 768px) {
.layout__card {
margin-bottom: var(--spacing-4);
border-radius: var(--border-radius-lg);
}
.layout__card-body {
padding: var(--spacing-4);
}
}
/* ==================== 网格系统响应式 ==================== */
.layout__grid {
display: grid;
gap: var(--spacing-4);
}
.layout__grid--2 {
grid-template-columns: repeat(2, 1fr);
}
.layout__grid--3 {
grid-template-columns: repeat(3, 1fr);
}
.layout__grid--4 {
grid-template-columns: repeat(4, 1fr);
}
/* 移动端网格调整 */
@media (max-width: 768px) {
.layout__grid--2,
.layout__grid--3,
.layout__grid--4 {
grid-template-columns: 1fr;
gap: var(--spacing-3);
}
}
/* 平板端网格调整 */
@media (min-width: 769px) and (max-width: 1024px) {
.layout__grid--3,
.layout__grid--4 {
grid-template-columns: repeat(2, 1fr);
}
}
/* ==================== 按钮响应式设计 ==================== */
/* 移动端按钮 */
@media (max-width: 768px) {
.btn {
padding: var(--spacing-3) var(--spacing-4);
font-size: var(--font-size-sm);
}
.btn--large {
padding: var(--spacing-4) var(--spacing-6);
font-size: var(--font-size-base);
}
.btn--small {
padding: var(--spacing-2) var(--spacing-3);
font-size: var(--font-size-xs);
}
}
/* ==================== 下拉菜单响应式设计 ==================== */
/* 移动端下拉菜单 */
@media (max-width: 768px) {
.header__dropdown-menu,
.header__user-dropdown {
position: fixed;
top: auto;
bottom: 0;
left: 0;
right: 0;
transform: none;
border-radius: var(--border-radius-lg) var(--border-radius-lg) 0 0;
max-height: 50vh;
overflow-y: auto;
}
}
/* ==================== 工具类响应式扩展 ==================== */
/* 移动端显示/隐藏 */
@media (max-width: 640px) {
.u-hidden-mobile {
display: none !important;
}
.u-block-mobile {
display: block !important;
}
.u-flex-mobile {
display: flex !important;
}
}
/* 平板端显示/隐藏 */
@media (min-width: 641px) and (max-width: 1024px) {
.u-hidden-tablet {
display: none !important;
}
.u-block-tablet {
display: block !important;
}
.u-flex-tablet {
display: flex !important;
}
}
/* 桌面端显示/隐藏 */
@media (min-width: 1025px) {
.u-hidden-desktop {
display: none !important;
}
.u-block-desktop {
display: block !important;
}
.u-flex-desktop {
display: flex !important;
}
}
/* ==================== 打印样式 ==================== */
@media print {
.header,
.sidebar,
.layout__page-actions {
display: none !important;
}
.layout__main {
width: 100% !important;
margin: 0 !important;
}
.layout__content {
padding: 0 !important;
}
.dashboard__chart-container {
height: 300px !important;
break-inside: avoid;
}
.layout__card {
break-inside: avoid;
margin-bottom: 1rem !important;
}
}

@ -0,0 +1,119 @@
/* Main Stylesheet */
/* 主样式表 - 导入所有模块化样式 */
/* 基础样式 */
@import './base/reset.css';
@import './base/variables.css';
/* 布局样式 */
@import './layouts/main.css';
@import './layouts/responsive.css';
/* 组件样式 */
@import './components/header.css';
@import './components/sidebar.css';
@import './components/dashboard.css';
@import './components/buttons.css';
/* 工具类 */
@import './utils/utilities.css';
/* 全局样式补充 */
html {
scroll-behavior: smooth;
}
body {
font-family: var(--font-family-base);
background-color: var(--color-gray-50);
color: var(--color-gray-900);
line-height: 1.6;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
/* 焦点样式 */
*:focus {
outline: 2px solid var(--color-primary);
outline-offset: 2px;
}
*:focus:not(:focus-visible) {
outline: none;
}
/* 选择文本样式 */
::selection {
background-color: rgba(59, 130, 246, 0.2);
color: var(--color-gray-900);
}
/* 滚动条样式 */
::-webkit-scrollbar {
width: 8px;
height: 8px;
}
::-webkit-scrollbar-track {
background-color: var(--color-gray-100);
}
::-webkit-scrollbar-thumb {
background-color: var(--color-gray-300);
border-radius: var(--border-radius-base);
}
::-webkit-scrollbar-thumb:hover {
background-color: var(--color-gray-400);
}
/* 打印样式 */
@media print {
* {
box-shadow: none !important;
text-shadow: none !important;
}
body {
background: white !important;
color: black !important;
}
.header,
.sidebar {
display: none !important;
}
.layout__main {
margin: 0 !important;
padding: 0 !important;
}
}
/* 高对比度模式支持 */
@media (prefers-contrast: high) {
:root {
--color-gray-50: #ffffff;
--color-gray-100: #f0f0f0;
--color-gray-200: #e0e0e0;
--color-gray-300: #c0c0c0;
--color-gray-400: #808080;
--color-gray-500: #606060;
--color-gray-600: #404040;
--color-gray-700: #202020;
--color-gray-800: #101010;
--color-gray-900: #000000;
}
}
/* 减少动画偏好 */
@media (prefers-reduced-motion: reduce) {
*,
*::before,
*::after {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
scroll-behavior: auto !important;
}
}

@ -0,0 +1,564 @@
/* Utility Classes */
/* 工具类样式 - 原子化 CSS */
/* 显示/隐藏 */
.u-hidden {
display: none !important;
}
.u-visible {
visibility: visible !important;
}
.u-invisible {
visibility: hidden !important;
}
.u-sr-only {
position: absolute !important;
width: 1px !important;
height: 1px !important;
padding: 0 !important;
margin: -1px !important;
overflow: hidden !important;
clip: rect(0, 0, 0, 0) !important;
white-space: nowrap !important;
border: 0 !important;
}
/* 文本对齐 */
.u-text-left {
text-align: left !important;
}
.u-text-center {
text-align: center !important;
}
.u-text-right {
text-align: right !important;
}
.u-text-justify {
text-align: justify !important;
}
/* 文本颜色 */
.u-text-primary {
color: var(--color-primary) !important;
}
.u-text-secondary {
color: var(--color-secondary) !important;
}
.u-text-success {
color: var(--color-success) !important;
}
.u-text-warning {
color: var(--color-warning) !important;
}
.u-text-error {
color: var(--color-error) !important;
}
.u-text-muted {
color: var(--color-gray-500) !important;
}
.u-text-white {
color: var(--color-white) !important;
}
/* 字体大小 */
.u-text-xs {
font-size: var(--font-size-xs) !important;
}
.u-text-sm {
font-size: var(--font-size-sm) !important;
}
.u-text-base {
font-size: var(--font-size-base) !important;
}
.u-text-lg {
font-size: var(--font-size-lg) !important;
}
.u-text-xl {
font-size: var(--font-size-xl) !important;
}
.u-text-2xl {
font-size: var(--font-size-2xl) !important;
}
.u-text-3xl {
font-size: var(--font-size-3xl) !important;
}
/* 字体粗细 */
.u-font-normal {
font-weight: var(--font-weight-normal) !important;
}
.u-font-medium {
font-weight: var(--font-weight-medium) !important;
}
.u-font-semibold {
font-weight: var(--font-weight-semibold) !important;
}
.u-font-bold {
font-weight: var(--font-weight-bold) !important;
}
/* 背景颜色 */
.u-bg-primary {
background-color: var(--color-primary) !important;
}
.u-bg-secondary {
background-color: var(--color-secondary) !important;
}
.u-bg-success {
background-color: var(--color-success) !important;
}
.u-bg-warning {
background-color: var(--color-warning) !important;
}
.u-bg-error {
background-color: var(--color-error) !important;
}
.u-bg-white {
background-color: var(--color-white) !important;
}
.u-bg-gray-50 {
background-color: var(--color-gray-50) !important;
}
.u-bg-gray-100 {
background-color: var(--color-gray-100) !important;
}
.u-bg-transparent {
background-color: transparent !important;
}
/* 边距 - Margin */
.u-m-0 {
margin: 0 !important;
}
.u-m-1 {
margin: var(--spacing-1) !important;
}
.u-m-2 {
margin: var(--spacing-2) !important;
}
.u-m-3 {
margin: var(--spacing-3) !important;
}
.u-m-4 {
margin: var(--spacing-4) !important;
}
.u-m-6 {
margin: var(--spacing-6) !important;
}
.u-m-8 {
margin: var(--spacing-8) !important;
}
/* 垂直边距 */
.u-my-0 {
margin-top: 0 !important;
margin-bottom: 0 !important;
}
.u-my-2 {
margin-top: var(--spacing-2) !important;
margin-bottom: var(--spacing-2) !important;
}
.u-my-4 {
margin-top: var(--spacing-4) !important;
margin-bottom: var(--spacing-4) !important;
}
.u-my-6 {
margin-top: var(--spacing-6) !important;
margin-bottom: var(--spacing-6) !important;
}
/* 水平边距 */
.u-mx-auto {
margin-left: auto !important;
margin-right: auto !important;
}
.u-mx-2 {
margin-left: var(--spacing-2) !important;
margin-right: var(--spacing-2) !important;
}
.u-mx-4 {
margin-left: var(--spacing-4) !important;
margin-right: var(--spacing-4) !important;
}
/* 内边距 - Padding */
.u-p-0 {
padding: 0 !important;
}
.u-p-2 {
padding: var(--spacing-2) !important;
}
.u-p-4 {
padding: var(--spacing-4) !important;
}
.u-p-6 {
padding: var(--spacing-6) !important;
}
/* 垂直内边距 */
.u-py-2 {
padding-top: var(--spacing-2) !important;
padding-bottom: var(--spacing-2) !important;
}
.u-py-4 {
padding-top: var(--spacing-4) !important;
padding-bottom: var(--spacing-4) !important;
}
/* 水平内边距 */
.u-px-2 {
padding-left: var(--spacing-2) !important;
padding-right: var(--spacing-2) !important;
}
.u-px-4 {
padding-left: var(--spacing-4) !important;
padding-right: var(--spacing-4) !important;
}
.u-px-6 {
padding-left: var(--spacing-6) !important;
padding-right: var(--spacing-6) !important;
}
/* 弹性布局 */
.u-flex {
display: flex !important;
}
.u-inline-flex {
display: inline-flex !important;
}
.u-flex-col {
flex-direction: column !important;
}
.u-flex-row {
flex-direction: row !important;
}
.u-items-center {
align-items: center !important;
}
.u-items-start {
align-items: flex-start !important;
}
.u-items-end {
align-items: flex-end !important;
}
.u-justify-center {
justify-content: center !important;
}
.u-justify-between {
justify-content: space-between !important;
}
.u-justify-start {
justify-content: flex-start !important;
}
.u-justify-end {
justify-content: flex-end !important;
}
.u-flex-1 {
flex: 1 1 0% !important;
}
.u-flex-shrink-0 {
flex-shrink: 0 !important;
}
/* 网格布局 */
.u-grid {
display: grid !important;
}
.u-grid-cols-1 {
grid-template-columns: repeat(1, minmax(0, 1fr)) !important;
}
.u-grid-cols-2 {
grid-template-columns: repeat(2, minmax(0, 1fr)) !important;
}
.u-grid-cols-3 {
grid-template-columns: repeat(3, minmax(0, 1fr)) !important;
}
.u-grid-cols-4 {
grid-template-columns: repeat(4, minmax(0, 1fr)) !important;
}
.u-gap-2 {
gap: var(--spacing-2) !important;
}
.u-gap-4 {
gap: var(--spacing-4) !important;
}
.u-gap-6 {
gap: var(--spacing-6) !important;
}
/* 宽度 */
.u-w-full {
width: 100% !important;
}
.u-w-auto {
width: auto !important;
}
.u-w-fit {
width: fit-content !important;
}
/* 高度 */
.u-h-full {
height: 100% !important;
}
.u-h-auto {
height: auto !important;
}
.u-min-h-screen {
min-height: 100vh !important;
}
/* 边框 */
.u-border {
border: 1px solid var(--color-gray-200) !important;
}
.u-border-0 {
border: 0 !important;
}
.u-border-t {
border-top: 1px solid var(--color-gray-200) !important;
}
.u-border-b {
border-bottom: 1px solid var(--color-gray-200) !important;
}
.u-border-l {
border-left: 1px solid var(--color-gray-200) !important;
}
.u-border-r {
border-right: 1px solid var(--color-gray-200) !important;
}
/* 圆角 */
.u-rounded {
border-radius: var(--border-radius-base) !important;
}
.u-rounded-md {
border-radius: var(--border-radius-md) !important;
}
.u-rounded-lg {
border-radius: var(--border-radius-lg) !important;
}
.u-rounded-xl {
border-radius: var(--border-radius-xl) !important;
}
.u-rounded-full {
border-radius: var(--border-radius-full) !important;
}
/* 阴影 */
.u-shadow {
box-shadow: var(--shadow-base) !important;
}
.u-shadow-sm {
box-shadow: var(--shadow-sm) !important;
}
.u-shadow-md {
box-shadow: var(--shadow-md) !important;
}
.u-shadow-lg {
box-shadow: var(--shadow-lg) !important;
}
.u-shadow-none {
box-shadow: none !important;
}
/* 溢出 */
.u-overflow-hidden {
overflow: hidden !important;
}
.u-overflow-auto {
overflow: auto !important;
}
.u-overflow-x-auto {
overflow-x: auto !important;
}
.u-overflow-y-auto {
overflow-y: auto !important;
}
/* 定位 */
.u-relative {
position: relative !important;
}
.u-absolute {
position: absolute !important;
}
.u-fixed {
position: fixed !important;
}
.u-sticky {
position: sticky !important;
}
/* z-index */
.u-z-10 {
z-index: 10 !important;
}
.u-z-20 {
z-index: 20 !important;
}
.u-z-50 {
z-index: 50 !important;
}
/* 过渡动画 */
.u-transition {
transition: all var(--transition-base) !important;
}
.u-transition-fast {
transition: all var(--transition-fast) !important;
}
.u-transition-slow {
transition: all var(--transition-slow) !important;
}
/* 光标 */
.u-cursor-pointer {
cursor: pointer !important;
}
.u-cursor-not-allowed {
cursor: not-allowed !important;
}
/* 用户选择 */
.u-select-none {
user-select: none !important;
}
.u-select-all {
user-select: all !important;
}
/* 响应式工具类 */
@media (max-width: 768px) {
.u-md-hidden {
display: none !important;
}
.u-md-block {
display: block !important;
}
.u-md-flex {
display: flex !important;
}
.u-md-grid-cols-1 {
grid-template-columns: repeat(1, minmax(0, 1fr)) !important;
}
.u-md-text-center {
text-align: center !important;
}
}
@media (max-width: 640px) {
.u-sm-hidden {
display: none !important;
}
.u-sm-block {
display: block !important;
}
.u-sm-text-sm {
font-size: var(--font-size-sm) !important;
}
.u-sm-p-2 {
padding: var(--spacing-2) !important;
}
}

@ -0,0 +1,284 @@
/**
* 图表管理工具类
* 负责初始化和管理 ECharts 图表实例
*/
class ChartManager {
constructor() {
this.charts = new Map(); // 存储图表实例
this.init();
}
/**
* 初始化所有图表
*/
init() {
// 等待 DOM 加载完成后初始化图表
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', () => {
this.initAllCharts();
});
} else {
this.initAllCharts();
}
}
/**
* 初始化所有图表
*/
initAllCharts() {
this.initCpuChart();
this.initMemoryChart();
// 监听窗口大小变化,自动调整图表大小
window.addEventListener('resize', () => {
this.resizeAllCharts();
});
}
/**
* 初始化 CPU 使用率趋势图
*/
initCpuChart() {
const chartElement = document.getElementById('cpuChart');
if (!chartElement) return;
const chart = echarts.init(chartElement);
// CPU 使用率图表配置
const option = {
title: {
text: 'CPU 使用率 (%)',
textStyle: {
fontSize: 14,
fontWeight: 'normal',
color: '#374151'
}
},
tooltip: {
trigger: 'axis',
formatter: function(params) {
const data = params[0];
return `${data.name}<br/>CPU 使用率: ${data.value}%`;
}
},
grid: {
left: '3%',
right: '4%',
bottom: '3%',
containLabel: true
},
xAxis: {
type: 'category',
boundaryGap: false,
data: ['00:00', '04:00', '08:00', '12:00', '16:00', '20:00', '24:00'],
axisLine: {
lineStyle: {
color: '#E5E7EB'
}
},
axisLabel: {
color: '#6B7280'
}
},
yAxis: {
type: 'value',
min: 0,
max: 100,
axisLine: {
lineStyle: {
color: '#E5E7EB'
}
},
axisLabel: {
color: '#6B7280',
formatter: '{value}%'
},
splitLine: {
lineStyle: {
color: '#F3F4F6'
}
}
},
series: [{
name: 'CPU 使用率',
type: 'line',
smooth: true,
data: [20, 35, 45, 60, 55, 40, 30],
lineStyle: {
color: '#3B82F6',
width: 2
},
areaStyle: {
color: {
type: 'linear',
x: 0,
y: 0,
x2: 0,
y2: 1,
colorStops: [{
offset: 0,
color: 'rgba(59, 130, 246, 0.3)'
}, {
offset: 1,
color: 'rgba(59, 130, 246, 0.05)'
}]
}
},
itemStyle: {
color: '#3B82F6'
}
}]
};
chart.setOption(option);
this.charts.set('cpu', chart);
}
/**
* 初始化内存使用情况图
*/
initMemoryChart() {
const chartElement = document.getElementById('memoryChart');
if (!chartElement) return;
const chart = echarts.init(chartElement);
// 内存使用情况图表配置
const option = {
title: {
text: '内存使用情况',
textStyle: {
fontSize: 14,
fontWeight: 'normal',
color: '#374151'
}
},
tooltip: {
trigger: 'item',
formatter: function(params) {
return `${params.name}<br/>使用量: ${params.value} GB (${params.percent}%)`;
}
},
legend: {
orient: 'horizontal',
bottom: '0%',
textStyle: {
color: '#6B7280'
}
},
series: [{
name: '内存使用',
type: 'pie',
radius: ['40%', '70%'],
center: ['50%', '45%'],
avoidLabelOverlap: false,
itemStyle: {
borderRadius: 4,
borderColor: '#fff',
borderWidth: 2
},
label: {
show: false,
position: 'center'
},
emphasis: {
label: {
show: true,
fontSize: '16',
fontWeight: 'bold',
color: '#374151'
}
},
labelLine: {
show: false
},
data: [
{
value: 8.5,
name: '已使用',
itemStyle: {
color: '#EF4444'
}
},
{
value: 15.5,
name: '可用',
itemStyle: {
color: '#10B981'
}
}
]
}]
};
chart.setOption(option);
this.charts.set('memory', chart);
}
/**
* 调整所有图表大小
*/
resizeAllCharts() {
this.charts.forEach(chart => {
chart.resize();
});
}
/**
* 更新 CPU 图表数据
* @param {Array} data - 新的数据数组
*/
updateCpuChart(data) {
const chart = this.charts.get('cpu');
if (chart && data) {
chart.setOption({
series: [{
data: data
}]
});
}
}
/**
* 更新内存图表数据
* @param {Object} data - 内存使用数据 {used: number, available: number}
*/
updateMemoryChart(data) {
const chart = this.charts.get('memory');
if (chart && data) {
chart.setOption({
series: [{
data: [
{
value: data.used,
name: '已使用',
itemStyle: {
color: '#EF4444'
}
},
{
value: data.available,
name: '可用',
itemStyle: {
color: '#10B981'
}
}
]
}]
});
}
}
/**
* 销毁所有图表实例
*/
dispose() {
this.charts.forEach(chart => {
chart.dispose();
});
this.charts.clear();
}
}
// 创建全局图表管理器实例
window.chartManager = new ChartManager();

@ -0,0 +1,357 @@
/**
* 导航管理工具类
* 负责页面导航下拉菜单和用户交互
*/
class NavigationManager {
constructor() {
this.currentPage = 'dashboard'; // 当前激活的页面
this.init();
}
/**
* 初始化导航功能
*/
init() {
// 等待 DOM 加载完成
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', () => {
this.bindEvents();
});
} else {
this.bindEvents();
}
}
/**
* 绑定所有事件监听器
*/
bindEvents() {
this.bindNavigationEvents();
this.bindDropdownEvents();
this.bindUserMenuEvents();
this.bindGlobalClickEvents();
this.bindSearchEvents();
}
/**
* 绑定主导航事件
*/
bindNavigationEvents() {
// 获取所有导航项
const navItems = document.querySelectorAll('.header__nav-item[data-page]');
navItems.forEach(item => {
item.addEventListener('click', (e) => {
e.preventDefault();
const targetPage = item.getAttribute('data-page');
this.switchPage(targetPage);
});
});
}
/**
* 页面切换功能
* @param {string} pageName - 目标页面名称
*/
switchPage(pageName) {
// 隐藏所有页面
const allSections = document.querySelectorAll('.layout__section, #dashboard');
allSections.forEach(section => {
section.classList.add('u-hidden');
});
// 显示目标页面
const targetSection = document.getElementById(pageName);
if (targetSection) {
targetSection.classList.remove('u-hidden');
}
// 更新导航项激活状态
this.updateActiveNavItem(pageName);
// 更新当前页面
this.currentPage = pageName;
// 触发页面切换事件
this.onPageSwitch(pageName);
}
/**
* 更新导航项激活状态
* @param {string} activePage - 激活的页面名称
*/
updateActiveNavItem(activePage) {
// 移除所有激活状态
const navItems = document.querySelectorAll('.header__nav-item');
navItems.forEach(item => {
item.classList.remove('header__nav-item--active');
item.removeAttribute('aria-current');
});
// 添加新的激活状态
const activeItem = document.querySelector(`[data-page="${activePage}"]`);
if (activeItem) {
activeItem.classList.add('header__nav-item--active');
activeItem.setAttribute('aria-current', 'page');
}
}
/**
* 页面切换回调
* @param {string} pageName - 切换到的页面名称
*/
onPageSwitch(pageName) {
// 根据页面执行特定操作
switch (pageName) {
case 'dashboard':
// 刷新图表数据
if (window.chartManager) {
window.chartManager.resizeAllCharts();
}
break;
case 'logs':
// 可以在这里加载日志数据
console.log('切换到日志查询页面');
break;
case 'diagnosis':
// 可以在这里初始化诊断功能
console.log('切换到故障诊断页面');
break;
case 'repair':
// 可以在这里加载修复建议
console.log('切换到自动修复页面');
break;
}
}
/**
* 绑定下拉菜单事件
*/
bindDropdownEvents() {
// 系统配置下拉菜单
const configDropdown = document.querySelector('.header__dropdown');
if (configDropdown) {
const trigger = configDropdown.querySelector('.header__dropdown-trigger');
const menu = configDropdown.querySelector('.header__dropdown-menu');
if (trigger && menu) {
trigger.addEventListener('click', (e) => {
e.preventDefault();
e.stopPropagation();
this.toggleDropdown(configDropdown, trigger, menu);
});
}
}
// 日志级别下拉菜单
this.bindSelectDropdown('log-level');
// 来源节点下拉菜单
this.bindSelectDropdown('source-node');
// 时间范围下拉菜单
this.bindSelectDropdown('time-range');
}
/**
* 切换下拉菜单显示状态
* @param {Element} dropdown - 下拉菜单容器
* @param {Element} trigger - 触发按钮
* @param {Element} menu - 菜单内容
*/
toggleDropdown(dropdown, trigger, menu) {
const isOpen = trigger.getAttribute('aria-expanded') === 'true';
// 关闭所有其他下拉菜单
this.closeAllDropdowns();
if (!isOpen) {
// 打开当前下拉菜单
trigger.setAttribute('aria-expanded', 'true');
menu.classList.add('header__dropdown-menu--show');
dropdown.classList.add('header__dropdown--active');
}
}
/**
* 绑定选择框下拉菜单
* @param {string} selectId - 选择框ID
*/
bindSelectDropdown(selectId) {
const select = document.getElementById(selectId);
if (select) {
select.addEventListener('change', (e) => {
console.log(`${selectId} 选择变更:`, e.target.value);
// 可以在这里添加选择变更的处理逻辑
});
}
}
/**
* 绑定用户菜单事件
*/
bindUserMenuEvents() {
const userMenu = document.querySelector('.header__user-menu');
if (userMenu) {
const avatar = userMenu.querySelector('.header__user-avatar');
const dropdown = userMenu.querySelector('.header__user-dropdown');
if (avatar && dropdown) {
avatar.addEventListener('click', (e) => {
e.preventDefault();
e.stopPropagation();
this.toggleUserMenu(avatar, dropdown);
});
}
}
}
/**
* 切换用户菜单显示状态
* @param {Element} avatar - 用户头像按钮
* @param {Element} dropdown - 下拉菜单
*/
toggleUserMenu(avatar, dropdown) {
const isOpen = avatar.getAttribute('aria-expanded') === 'true';
// 关闭所有其他下拉菜单
this.closeAllDropdowns();
if (!isOpen) {
// 打开用户菜单
avatar.setAttribute('aria-expanded', 'true');
dropdown.classList.add('header__user-dropdown--show');
}
}
/**
* 关闭所有下拉菜单
*/
closeAllDropdowns() {
// 关闭系统配置下拉菜单
const configTriggers = document.querySelectorAll('.header__dropdown-trigger');
const configMenus = document.querySelectorAll('.header__dropdown-menu');
const configDropdowns = document.querySelectorAll('.header__dropdown');
configTriggers.forEach(trigger => {
trigger.setAttribute('aria-expanded', 'false');
});
configMenus.forEach(menu => {
menu.classList.remove('header__dropdown-menu--show');
});
configDropdowns.forEach(dropdown => {
dropdown.classList.remove('header__dropdown--active');
});
// 关闭用户菜单
const userAvatars = document.querySelectorAll('.header__user-avatar');
const userDropdowns = document.querySelectorAll('.header__user-dropdown');
userAvatars.forEach(avatar => {
avatar.setAttribute('aria-expanded', 'false');
});
userDropdowns.forEach(dropdown => {
dropdown.classList.remove('header__user-dropdown--show');
});
}
/**
* 绑定全局点击事件
*/
bindGlobalClickEvents() {
document.addEventListener('click', (e) => {
// 如果点击的不是下拉菜单相关元素,则关闭所有下拉菜单
const isDropdownClick = e.target.closest('.header__dropdown') ||
e.target.closest('.header__user-menu');
if (!isDropdownClick) {
this.closeAllDropdowns();
}
});
// ESC 键关闭下拉菜单
document.addEventListener('keydown', (e) => {
if (e.key === 'Escape') {
this.closeAllDropdowns();
}
});
}
/**
* 绑定搜索事件
*/
bindSearchEvents() {
const searchInput = document.getElementById('global-search');
if (searchInput) {
// 搜索输入事件
searchInput.addEventListener('input', (e) => {
const query = e.target.value.trim();
if (query.length > 2) {
this.performSearch(query);
}
});
// 回车键搜索
searchInput.addEventListener('keydown', (e) => {
if (e.key === 'Enter') {
e.preventDefault();
const query = e.target.value.trim();
if (query) {
this.performSearch(query);
}
}
});
}
// 日志搜索表单
const logSearchForm = document.querySelector('form[role="search"]');
if (logSearchForm) {
logSearchForm.addEventListener('submit', (e) => {
e.preventDefault();
this.performLogSearch();
});
}
}
/**
* 执行全局搜索
* @param {string} query - 搜索查询
*/
performSearch(query) {
console.log('执行全局搜索:', query);
// 这里可以实现实际的搜索逻辑
// 例如:搜索节点、日志、配置等
}
/**
* 执行日志搜索
*/
performLogSearch() {
const logLevel = document.getElementById('log-level')?.value;
const sourceNode = document.getElementById('source-node')?.value;
const timeRange = document.getElementById('time-range')?.value;
console.log('执行日志搜索:', {
logLevel,
sourceNode,
timeRange
});
// 这里可以实现实际的日志搜索逻辑
// 例如:向后端发送搜索请求,更新日志表格等
}
/**
* 获取当前页面
* @returns {string} 当前页面名称
*/
getCurrentPage() {
return this.currentPage;
}
}
// 创建全局导航管理器实例
window.navigationManager = new NavigationManager();

@ -0,0 +1,355 @@
/**
* 响应式交互管理工具类
* 负责移动端菜单侧边栏切换和响应式行为
*/
class ResponsiveManager {
constructor() {
this.isMobile = false;
this.isTablet = false;
this.sidebarOpen = false;
this.mobileMenuOpen = false;
this.init();
}
/**
* 初始化响应式功能
*/
init() {
// 等待 DOM 加载完成
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', () => {
this.setup();
});
} else {
this.setup();
}
}
/**
* 设置响应式功能
*/
setup() {
this.createMobileElements();
this.bindEvents();
this.checkScreenSize();
// 监听窗口大小变化
window.addEventListener('resize', () => {
this.handleResize();
});
}
/**
* 创建移动端需要的元素
*/
createMobileElements() {
this.createMobileMenuButton();
this.createSidebarOverlay();
}
/**
* 创建移动端菜单按钮
*/
createMobileMenuButton() {
const headerLeft = document.querySelector('.header__left');
if (!headerLeft) return;
// 检查是否已存在
if (document.querySelector('.header__mobile-menu-btn')) return;
const mobileMenuBtn = document.createElement('button');
mobileMenuBtn.className = 'header__mobile-menu-btn';
mobileMenuBtn.innerHTML = '<i class="fas fa-bars" aria-hidden="true"></i>';
mobileMenuBtn.setAttribute('aria-label', '打开导航菜单');
mobileMenuBtn.setAttribute('aria-expanded', 'false');
// 插入到 logo 之后
const logo = headerLeft.querySelector('.header__logo');
if (logo && logo.nextSibling) {
headerLeft.insertBefore(mobileMenuBtn, logo.nextSibling);
} else {
headerLeft.appendChild(mobileMenuBtn);
}
}
/**
* 创建侧边栏遮罩层
*/
createSidebarOverlay() {
const container = document.querySelector('.layout__container');
if (!container) return;
// 检查是否已存在
if (document.querySelector('.sidebar-overlay')) return;
const overlay = document.createElement('div');
overlay.className = 'sidebar-overlay';
overlay.setAttribute('aria-hidden', 'true');
container.appendChild(overlay);
}
/**
* 绑定事件监听器
*/
bindEvents() {
// 移动端菜单按钮
const mobileMenuBtn = document.querySelector('.header__mobile-menu-btn');
if (mobileMenuBtn) {
mobileMenuBtn.addEventListener('click', (e) => {
e.preventDefault();
this.toggleMobileMenu();
});
}
// 侧边栏遮罩层点击
const overlay = document.querySelector('.sidebar-overlay');
if (overlay) {
overlay.addEventListener('click', () => {
this.closeSidebar();
});
}
// ESC 键关闭菜单
document.addEventListener('keydown', (e) => {
if (e.key === 'Escape') {
this.closeMobileMenu();
this.closeSidebar();
}
});
// 导航项点击时关闭移动端菜单
const navItems = document.querySelectorAll('.header__nav-item');
navItems.forEach(item => {
item.addEventListener('click', () => {
if (this.isMobile) {
this.closeMobileMenu();
}
});
});
}
/**
* 检查屏幕尺寸
*/
checkScreenSize() {
const width = window.innerWidth;
this.isMobile = width <= 768;
this.isTablet = width > 768 && width <= 1024;
// 根据屏幕尺寸调整布局
this.adjustLayout();
}
/**
* 处理窗口大小变化
*/
handleResize() {
const wasMobile = this.isMobile;
this.checkScreenSize();
// 如果从移动端切换到桌面端,关闭移动端菜单
if (wasMobile && !this.isMobile) {
this.closeMobileMenu();
this.closeSidebar();
}
// 通知图表管理器调整大小
if (window.chartManager) {
setTimeout(() => {
window.chartManager.resizeAllCharts();
}, 300);
}
}
/**
* 调整布局
*/
adjustLayout() {
const body = document.body;
// 添加屏幕尺寸类名
body.classList.remove('is-mobile', 'is-tablet', 'is-desktop');
if (this.isMobile) {
body.classList.add('is-mobile');
} else if (this.isTablet) {
body.classList.add('is-tablet');
} else {
body.classList.add('is-desktop');
}
}
/**
* 切换移动端菜单
*/
toggleMobileMenu() {
if (this.mobileMenuOpen) {
this.closeMobileMenu();
} else {
this.openMobileMenu();
}
}
/**
* 打开移动端菜单
*/
openMobileMenu() {
const nav = document.querySelector('.header__nav');
const btn = document.querySelector('.header__mobile-menu-btn');
if (nav && btn) {
nav.classList.add('header__nav--mobile-open');
btn.setAttribute('aria-expanded', 'true');
btn.innerHTML = '<i class="fas fa-times" aria-hidden="true"></i>';
this.mobileMenuOpen = true;
// 阻止背景滚动
document.body.style.overflow = 'hidden';
}
}
/**
* 关闭移动端菜单
*/
closeMobileMenu() {
const nav = document.querySelector('.header__nav');
const btn = document.querySelector('.header__mobile-menu-btn');
if (nav && btn) {
nav.classList.remove('header__nav--mobile-open');
btn.setAttribute('aria-expanded', 'false');
btn.innerHTML = '<i class="fas fa-bars" aria-hidden="true"></i>';
this.mobileMenuOpen = false;
// 恢复背景滚动
document.body.style.overflow = '';
}
}
/**
* 切换侧边栏
*/
toggleSidebar() {
if (this.sidebarOpen) {
this.closeSidebar();
} else {
this.openSidebar();
}
}
/**
* 打开侧边栏
*/
openSidebar() {
const sidebar = document.querySelector('.sidebar');
const overlay = document.querySelector('.sidebar-overlay');
if (sidebar && overlay) {
sidebar.classList.add('sidebar--mobile-open');
overlay.classList.add('sidebar-overlay--show');
this.sidebarOpen = true;
// 阻止背景滚动
document.body.style.overflow = 'hidden';
}
}
/**
* 关闭侧边栏
*/
closeSidebar() {
const sidebar = document.querySelector('.sidebar');
const overlay = document.querySelector('.sidebar-overlay');
if (sidebar && overlay) {
sidebar.classList.remove('sidebar--mobile-open');
overlay.classList.remove('sidebar-overlay--show');
this.sidebarOpen = false;
// 恢复背景滚动
document.body.style.overflow = '';
}
}
/**
* 获取当前屏幕类型
* @returns {string} 屏幕类型'mobile', 'tablet', 'desktop'
*/
getScreenType() {
if (this.isMobile) return 'mobile';
if (this.isTablet) return 'tablet';
return 'desktop';
}
/**
* 检查是否为移动端
* @returns {boolean}
*/
isMobileDevice() {
return this.isMobile;
}
/**
* 检查是否为平板端
* @returns {boolean}
*/
isTabletDevice() {
return this.isTablet;
}
/**
* 检查是否为桌面端
* @returns {boolean}
*/
isDesktopDevice() {
return !this.isMobile && !this.isTablet;
}
/**
* 添加触摸手势支持
*/
addTouchSupport() {
let startX = 0;
let startY = 0;
document.addEventListener('touchstart', (e) => {
startX = e.touches[0].clientX;
startY = e.touches[0].clientY;
}, { passive: true });
document.addEventListener('touchend', (e) => {
if (!this.isMobile) return;
const endX = e.changedTouches[0].clientX;
const endY = e.changedTouches[0].clientY;
const deltaX = endX - startX;
const deltaY = endY - startY;
// 水平滑动距离大于垂直滑动距离,且滑动距离足够
if (Math.abs(deltaX) > Math.abs(deltaY) && Math.abs(deltaX) > 50) {
// 从左边缘向右滑动,打开侧边栏
if (startX < 50 && deltaX > 0 && !this.sidebarOpen) {
this.openSidebar();
}
// 向左滑动,关闭侧边栏
else if (deltaX < -50 && this.sidebarOpen) {
this.closeSidebar();
}
}
}, { passive: true });
}
}
// 创建全局响应式管理器实例
window.responsiveManager = new ResponsiveManager();
// 添加触摸手势支持
window.responsiveManager.addTouchSupport();

@ -1,599 +0,0 @@
# ErrorDetecting 前端项目
## 项目简介
ErrorDetecting 是一个分布式系统错误检测与监控平台的前端应用,提供直观的用户界面来监控系统状态、管理故障和分析日志。
## 功能特性
- 🎯 **概览仪表板** - 系统整体运行状态概览
- 🖥️ **集群监控** - 实时监控集群节点状态和性能指标
- 🚨 **故障管理** - 故障报告、处理和跟踪管理
- 📊 **日志分析** - 日志查询、分析和可视化
- 👤 **用户管理** - 用户权限和角色管理
- ⚙️ **系统配置** - 系统参数和配置管理
- 📱 **响应式设计** - 支持多种设备和屏幕尺寸
## 技术栈
- **HTML5** - 页面结构
- **CSS3** - 样式设计,包含响应式布局
- **JavaScript (ES6+)** - 交互逻辑和数据处理
- **Chart.js** - 数据可视化图表
- **Font Awesome** - 图标库
## 项目结构
```
frontend/
├── index.html # 主页面入口
├── styles/ # 样式文件目录
│ ├── main.css # 主样式文件
│ ├── login.css # 登录页面样式
│ ├── cluster-monitor.css # 集群监控页面样式
│ ├── fault-manage.css # 故障管理页面样式
│ ├── log-analysis.css # 日志分析页面样式
│ ├── components.css # 通用组件样式
│ └── responsive.css # 响应式设计样式
├── js/ # JavaScript文件目录
│ ├── app.js # 主应用逻辑
│ ├── components.js # 通用组件库
│ ├── charts.js # 图表组件库
│ └── demo-data.js # 演示数据管理
├── views/ # 页面视图目录
├── components/ # 可复用组件
├── api/ # API接口封装
├── utils/ # 工具函数
├── router/ # 路由配置
└── README.md # 项目说明文档
```
## 快速开始
### 1. 环境要求
- 现代浏览器 (Chrome, Firefox, Safari, Edge)
- Python 3.x (用于本地开发服务器)
### 2. 启动项目
```bash
# 进入前端目录
cd src/frontend
# 启动本地HTTP服务器
python -m http.server 8080
# 或者使用Node.js
npx http-server -p 8080
```
### 3. 访问应用
打开浏览器访问: http://localhost:8080
### 4. 默认登录信息
- 用户名: admin
- 密码: admin123
## 页面功能说明
### 概览仪表板
- 显示系统整体运行状态
- 在线节点数量、活跃告警、资源使用率等关键指标
- 系统性能趋势图表
### 集群监控
- 实时监控所有节点状态
- 节点性能指标 (CPU、内存、磁盘使用率)
- 节点管理操作 (重启、停止、详情查看)
- 资源使用趋势分析
### 故障管理
- 故障列表查看和筛选
- 故障详情查看和处理
- 故障统计和分析
- 故障报告导出
### 日志分析
- 多条件日志查询
- 日志级别和服务筛选
- 日志趋势分析图表
- 实时日志监控
## 组件库
### 通用组件
- **Modal** - 模态框组件
- **Loading** - 加载动画组件
- **Notification** - 通知组件
- **Confirm** - 确认对话框
- **Alert** - 提示框
- **Tooltip** - 工具提示
- **Dropdown** - 下拉菜单
### 图表组件
- **LineChart** - 线性图表
- **BarChart** - 柱状图表
- **PieChart** - 饼图
- **DoughnutChart** - 环形图
- **RealTimeChart** - 实时图表
## 响应式设计
项目采用响应式设计,支持以下设备:
- 📱 **移动设备** (< 480px)
- 📱 **小屏平板** (480px - 767px)
- 💻 **中等屏幕** (768px - 1023px)
- 🖥️ **大屏幕** (1024px - 1199px)
- 🖥️ **超大屏幕** (≥ 1200px)
## 浏览器支持
- Chrome ≥ 60
- Firefox ≥ 55
- Safari ≥ 12
- Edge ≥ 79
## 开发指南
### 添加新页面
1. 在 `views/` 目录下创建页面文件夹
2. 创建对应的HTML和CSS文件
3. 在 `app.js` 中添加路由配置
4. 在侧边栏导航中添加菜单项
### 使用组件
```javascript
// 显示通知
Notification.show('操作成功', 'success');
// 显示确认对话框
const result = await Confirm.show('确定要删除吗?');
// 创建图表
const chart = ChartFactory.createLineChart('chartCanvas', data);
```
### 获取演示数据
```javascript
// 获取仪表板数据
const dashboardData = demoData.getDashboardData();
// 搜索故障
const faults = demoData.searchFaults('CPU', { level: 'critical' });
// 获取实时数据
const realtimeData = demoData.getRealtimeData();
```
## 部署说明
### 生产环境部署
1. 将所有文件上传到Web服务器
2. 配置Web服务器支持SPA路由
3. 确保所有静态资源可正常访问
4. 配置HTTPS (推荐)
### Nginx配置示例
```nginx
server {
listen 80;
server_name your-domain.com;
root /path/to/frontend;
index index.html;
location / {
try_files $uri $uri/ /index.html;
}
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg)$ {
expires 1y;
add_header Cache-Control "public, immutable";
}
}
```
## 性能优化
- 使用CDN加载第三方库
- 启用Gzip压缩
- 图片懒加载
- 代码分割和按需加载
- 缓存策略优化
## 故障排除
### 常见问题
1. **页面无法加载**
- 检查HTTP服务器是否正常启动
- 确认端口号是否被占用
2. **图表不显示**
- 检查Chart.js库是否正确加载
- 确认canvas元素是否存在
3. **样式异常**
- 检查CSS文件路径是否正确
- 确认浏览器是否支持CSS特性
## 贡献指南
1. Fork 项目
2. 创建功能分支
3. 提交更改
4. 推送到分支
5. 创建Pull Request
## 许可证
本项目采用 MIT 许可证。
## 联系方式
如有问题或建议,请联系开发团队。
---
## 原始开发文档
以下是项目的原始开发文档和需求分析:
### 项目背景
在基于 Hadoop 的故障检测与自动恢复项目中,前端开发者需围绕"运维人员可视化操作入口"核心目标,从需求对齐到上线维护全流程参与,具体工作可按**项目阶段**拆解为以下详细内容,同时紧密贴合项目文档(前景与范围、用例、核心任务)中的前端相关需求:
### 一、项目前期:需求对齐与技术准备(项目启动-基础搭建阶段)
此阶段核心是明确"做什么"和"用什么做",避免后期需求偏差,对应文档中"Web 应用功能范围""用例场景"等内容。
#### 1. 需求分析与边界确认
需与产品、后端、运维人员同步,明确前端核心功能边界,重点对齐以下内容:
- **功能范围确认**参考《前景与范围文档》4.1 功能范围):
- 必做:登录、集群监控(节点状态/资源趋势)、故障管理(列表/详情/修复)、日志分析(查询/AI 提交);
- 不做:非 Hadoop 组件(如 Spark的监控、跨集群管理、离线日志回溯仅实时日志
- **用例场景拆解**(参考《用例文档》核心用例):
- 梳理每个用例的前端交互细节,例如:
- 故障修复UC-006需按风险分级展示按钮低风险"自动修复"、中风险"确认修复"、高风险"申请审批"),并实时展示执行日志;
- AI 日志分析UC-008需限制最多勾选 10 条日志,分析超时后提示"后台继续处理,结果将推送"。
- **运维人员体验诉求**
- 深色主题适配(运维场景多在夜间使用,减少视觉疲劳);
- 操作简洁(核心功能如"修复""刷新"按钮突出,避免多层级跳转);
- 异常提示明确(如接口失败时需区分"集群连接中断""Token 过期"等场景)。
#### 2. 技术栈选型与版本确认
基于《核心任务说明文档》任务 5 明确的技术栈,进一步确认版本兼容性与工具链:
- **核心技术栈**(固定):
- 框架Vue 3.3+Composition API 风格,便于逻辑复用);
- 构建工具Vite 4.0+(比 Webpack 快,适配 Vue 3支持热更新
- UI 组件库Element Plus 2.3+(需支持表格、弹窗、表单,适配 Vue 3
- 图表库ECharts 5.4+需支持折线图CPU/磁盘趋势)、表格标注(异常节点标红));
- 网络请求Axios 1.4+处理接口拦截、Token 携带、跨域);
- **补充工具**
- 代码规范ESLintVue 3 规则)+ Prettier统一格式
- 样式方案CSS 变量(实现深色/浅色主题切换)+ SCSS嵌套样式提高可维护性
- 路由Vue Router 4.2+(需支持 history 模式,配合 Nginx 部署)。
### 二、项目初始化:前端架构搭建(应用开发阶段前期)
此阶段需搭建可扩展的项目架构,为后续开发提效,避免后期重构。
#### 1. 项目初始化与配置
- **创建项目**
```bash
npm create vite@latest hadoop-fault-front -- --template vue # 初始化 Vue 3 项目
cd hadoop-fault-front && npm install # 安装依赖
npm install element-plus echarts axios vue-router@4 # 安装核心依赖
```
- **核心配置vite.config.js**
- 跨域代理(开发环境调用后端 FastAPI 接口需解决跨域):
```javascript
export default defineConfig({
server: {
proxy: {
'/api': {
target: 'http://localhost:8000', // 后端开发环境地址
changeOrigin: true, // 允许跨域
rewrite: (path) => path.replace(/^\/api/, '') // 去掉请求路径中的 /api 前缀
}
}
},
resolve: {
alias: {
'@': path.resolve(__dirname, 'src') // 路径别名,@ 指向 src 目录
}
}
})
```
- 环境变量:创建 `.env.development`(开发环境)和 `.env.production`(生产环境),配置 API 基础地址:
```env
# .env.development
VITE_API_BASE_URL = '/api' # 开发环境(走代理)
# .env.production
VITE_API_BASE_URL = 'http://生产后端IP:8000' # 生产环境(直接调用后端)
```
### 2. 目录结构设计(按功能拆分,便于维护)
```
frontend/
├── api/ # 接口请求封装(按模块拆分)
│ ├── user.js # 登录、退出接口
│ ├── cluster.js# 集群状态接口
│ ├── fault.js # 故障管理接口
│ └── log.js # 日志查询、AI 分析接口
├── components/ # 公共组件(复用性强)
│ ├── Layout/ # 页面布局(侧边栏+顶部栏)
│ ├── Common/ # 通用组件(加载动画、错误弹窗、确认弹窗)
│ └── Chart/ # 图表组件CPU/磁盘趋势图、异常节点表格)
├── views/ # 核心页面(按功能模块拆分)
│ ├── Login/ # 登录页
│ ├── ClusterMonitor/ # 集群监控页
│ ├── FaultManage/ # 故障管理页(列表+详情)
│ └── LogAnalysis/ # 日志分析页
├── router/ # 路由配置
│ └── index.js # 路由规则(含权限守卫)
├── utils/ # 工具函数
│ ├── request.js# Axios 实例封装(拦截器、错误处理)
│ ├── format.js # 时间格式化、日志筛选等工具
│ └── auth.js # Token 存储/获取/删除
├── styles/ # 全局样式
│ ├── main.scss # 全局样式入口(引入 Element Plus 主题、CSS 变量)
│ └── dark.scss # 深色主题样式
└── App.vue # 根组件(路由出口)
```
## 三、核心开发:公共组件与页面实现(应用开发阶段中期)
此阶段需优先开发公共组件(复用),再实现核心页面,严格贴合用例场景与交互需求。
### 1. 公共组件开发(优先完成,减少重复代码)
#### 1基础布局组件src/components/Layout/Index.vue
- **功能**:包含侧边栏(导航菜单)、顶部栏(用户信息、刷新、退出),适配所有页面;
- **关键实现**
- 侧边栏菜单:按“集群监控→故障管理→日志分析”排序(运维高频操作优先),用 Element Plus 的 `ElMenu` 组件;
- 顶部栏:显示当前登录用户(从 localStorage 获取),“退出”按钮触发清除 Token 并跳转登录页;
- 深色主题切换:用 Element Plus 的 `ElSwitch` 控制 `document.documentElement.classList` 切换 `dark` 类,配合 CSS 变量生效。
#### 2通用工具组件
- **加载组件src/components/Common/Loading.vue**
- 场景接口请求时显示如集群状态加载、AI 分析中);
- 实现:用 Element Plus 的 `ElLoading` 或自定义全屏加载动画支持传入“加载文案”如“AI 分析中,约 30 秒”)。
- **错误弹窗组件src/components/Common/ErrorTip.vue**
- 场景接口请求失败如集群连接中断、Token 过期);
- 实现:封装 Element Plus 的 `ElMessage`,支持区分错误类型(网络错误→“请检查网络”;业务错误→后端返回的错误信息)。
- **确认弹窗组件src/components/Common/Confirm.vue**
- 场景:中风险故障修复、账号禁用等需确认的操作;
- 实现:封装 Element Plus 的 `ElMessageBox`,支持传入“标题”“内容”“确认按钮文案”,返回 Promise 便于后续处理。
#### 3图表组件src/components/Chart/ResourceTrend.vue
- **功能**:展示节点 CPU/磁盘使用率趋势(用 ECharts 折线图);
- **关键实现**
- 数据适配:接收后端返回的“时间轴+使用率数组”,格式化为 ECharts 所需的 `xAxis.data``series.data`
- 异常标注:当使用率超过阈值(如磁盘 85%)时,用 ECharts 的 `markLine` 画红色警戒线,并标注“告警阈值”;
- 懒加载:用 Vue 的 `v-intersect` 指令(需安装 `@vueuse/core`),滚动到图表区域才初始化,减少首屏加载时间。
### 2. 核心页面实现(按用例优先级开发,高优先级先做)
#### 1登录页src/views/Login/Index.vue用例 UC-001
- **功能**:账号密码验证,对接后端 `/api/user/login` 接口;
- **关键实现**
- 表单验证:用 Element Plus 的 `ElForm` 做规则校验账号不能为空、密码≥6 位且含特殊字符);
- Token 处理:登录成功后,将后端返回的 Token 存入 localStorage`localStorage.setItem('token', res.data.token)`),并跳转首页(`router.push('/cluster-monitor')`
- 异常处理:账号禁用→提示“账号已禁用,请联系管理员”;密码错误→提示“账号或密码错误”(后端返回对应的错误码,前端匹配处理)。
#### 2集群监控页src/views/ClusterMonitor/Index.vue用例 UC-002、UC-003
- **功能**展示节点列表状态、角色、资源使用率、CPU/磁盘趋势图,定时 5 分钟刷新;
- **关键实现**
- 节点列表:用 Element Plus 的 `ElTable`按“角色”分组NameNode、DataNode异常状态标注离线→标红、磁盘>90%→标橙);
- 资源趋势图:复用 `ResourceTrend` 组件支持切换“CPU/磁盘”指标,默认显示近 24 小时数据;
- 定时刷新:用 `setInterval` 每 5 分钟调用 `getClusterStatus` 接口,页面离开时用 `onUnmounted` 清除定时器(避免内存泄漏);
- 手动刷新:“刷新”按钮触发接口调用,加载期间显示 `Loading` 组件。
#### 3故障管理页分列表和详情用例 UC-004、UC-005、UC-006
##### ① 故障列表页src/views/FaultManage/List.vue
- **功能**:按“未修复/已修复”筛选故障,展示故障 ID、类型、发生时间、风险等级
- **关键实现**
- 筛选功能:用 Element Plus 的 `ElSelect` 实现状态筛选,筛选后重新调用 `/api/fault/list` 接口(携带筛选参数);
- 分页:用 Element Plus 的 `ElPagination`支持切换“每页条数”10/20/50分页参数同步到接口请求
- 跳转详情:点击表格行,携带故障 ID 跳转详情页(`router.push(/fault-detail/${faultId})`)。
##### ② 故障详情页src/views/FaultManage/Detail.vue
- **功能**:展示故障日志、诊断结果、修复脚本,按风险等级显示修复按钮,实时展示修复日志;
- **关键实现**
- 数据加载:页面初始化时,通过路由参数 `route.params.faultId` 调用 `/api/fault/detail` 接口,加载故障信息;
- 风险分级按钮:
- 低风险:直接显示“自动修复”按钮,点击调用 `/api/fault/execute` 接口;
- 中风险:显示“确认修复”按钮,点击触发 `Confirm` 组件,确认后执行修复;
- 高风险:显示“申请审批”按钮,点击后提示“已发送审批请求,等待管理员确认”(后续通过 WebSocket 接收审批结果);
- 实时日志:修复过程中,用 `ElTag``ElText` 实时渲染后端返回的执行日志(接口返回 `stream` 流或通过 WebSocket 推送);
- 状态同步:修复完成后,自动更新故障状态(“未修复”→“已修复”),并提示“修复成功”,提供“返回列表”按钮。
#### 4日志分析页src/views/LogAnalysis/Index.vue用例 UC-007、UC-008
- **功能**:按“时间范围/节点/日志级别”查询日志,支持勾选日志提交 AI 分析;
- **关键实现**
- 查询表单:用 Element Plus 的 `ElDatePicker`(时间范围)、`ElSelect`(节点/日志级别),“查询”按钮触发 `/api/log/query` 接口;
- 日志列表:用 `ElTable` 展示结构化日志(时间戳、级别、组件、内容),支持勾选(限制最多 10 条,超过提示“最多勾选 10 条日志”);
- AI 分析:
- 点击“提交 AI 分析”,携带勾选的日志 ID 调用 `/api/llm/diagnose` 接口;
- 分析中显示 `Loading` 组件,超时(>30 秒)提示“分析超时,已后台继续处理,结果将推送”;
- 分析成功后,弹窗展示诊断结果(故障类型、原因、修复脚本),提供“前往故障详情”按钮。
### 3. 接口请求封装src/utils/request.js + src/api/模块.js
- **第一步Axios 实例封装src/utils/request.js**
- 处理请求头:自动携带 Token从 localStorage 获取);
- 响应拦截:处理 401Token 过期→清除 Token 并跳转登录、500服务器错误→提示“服务器异常请重试”
```javascript
import axios from 'axios';
import { ElMessage } from 'element-plus';
import router from '@/router';
const service = axios.create({
baseURL: import.meta.env.VITE_API_BASE_URL,
timeout: 30000 // AI 分析接口超时设为 30 秒
});
// 请求拦截器:加 Token
service.interceptors.request.use(
(config) => {
const token = localStorage.getItem('token');
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
},
(error) => Promise.reject(error)
);
// 响应拦截器:处理错误
service.interceptors.response.use(
(response) => response.data, // 直接返回响应体
(error) => {
if (error.response?.status === 401) {
// Token 过期:清除数据并跳转登录
localStorage.removeItem('token');
router.push('/login');
ElMessage.error('登录已过期,请重新登录');
} else {
ElMessage.error(error.message || '请求失败,请重试');
}
return Promise.reject(error);
}
);
export default service;
```
- **第二步:按模块封装接口(如 src/api/fault.js**
```javascript
import request from '@/utils/request';
// 获取故障列表
export const getFaultList = (params) => {
return request({
url: '/fault/list',
method: 'get',
params // 筛选参数status: 'unfixed'/'fixed'
});
};
// 执行故障修复
export const executeFaultFix = (faultId) => {
return request({
url: `/fault/execute`,
method: 'post',
data: { faultId }
});
};
```
## 四、联调与优化:接口联调与性能优化(应用开发阶段后期)
此阶段需与后端紧密配合,解决接口问题,并优化页面性能与体验。
### 1. 接口联调(核心:对齐数据格式,解决交互问题)
- **联调准备**:与后端确认每个接口的“请求参数格式”“响应数据结构”(如故障列表接口返回 `{ code: 200, data: { list: [], total: 100 } }`
- **关键场景联调**
- 集群状态接口:确认返回的“节点状态”字段(如 `status: 'online'/'offline'`),确保表格标注正确;
- 故障修复接口确认“执行日志”的返回方式stream 流→前端用 `onDownloadProgress` 实时接收WebSocket→建立连接监听推送
- AI 分析接口:确认“超时处理”逻辑(后端是否支持异步推送,前端是否需要轮询查询结果);
- **问题记录**:用文档记录联调中的问题(如“故障详情接口缺少‘修复脚本’字段”),同步后端修复,避免遗漏。
### 2. 性能与体验优化(贴合任务 8 中的“前端优化”要求)
- **首屏加载优化**
- 路由懒加载:在 `src/router/index.js` 中用 `() => import('@/views/xxx')` 实现,减少首屏 JS 体积;
```javascript
const routes = [
{
path: '/cluster-monitor',
name: 'ClusterMonitor',
component: () => import('@/views/ClusterMonitor/Index.vue') // 懒加载
}
];
```
- 图表懒加载:非首屏图表(如日志分析页的趋势图)用 `v-intersect` 指令,滚动到可视区域才初始化;
- **接口请求优化**
- 缓存高频接口如集群状态接口5 分钟刷新),用 `localStorage` 缓存上次请求结果,避免重复调用;
- 合并重复请求:用 Axios 拦截器实现“同一接口未返回时,不重复发起请求”(如多次点击“刷新”按钮);
- **兼容性优化**
- 浏览器兼容:测试 Chrome 90+、Firefox 90+(项目要求),修复 CSS 兼容性问题(如 Flex 布局、CSS 变量);
- 响应式适配:用 Element Plus 的 `Layout` 组件和媒体查询,确保 1366px+ 屏幕正常显示(运维多使用台式机)。
## 五、部署与维护:构建部署与上线后迭代(落地保障阶段)
此阶段需配合运维完成部署,并处理上线后的问题与迭代需求。
### 1. 项目构建与部署(贴合任务 9 中的“容器化部署”)
#### 1构建生产包
- 执行 `npm run build` 生成 `dist` 目录(生产环境代码,已压缩混淆);
- 检查 `dist` 目录结构(确保 `index.html` 正确引用 JS/CSS 文件)。
#### 2编写 Docker 相关文件(配合运维容器化部署)
- **Dockerfile前端镜像构建**
```dockerfile
# 基础镜像Nginx轻量适合静态资源部署
FROM nginx:alpine
# 复制构建好的 dist 目录到 Nginx 的 html 目录
COPY dist /usr/share/nginx/html
# 复制 Nginx 配置文件(解决路由 history 模式和反向代理)
COPY nginx.conf /etc/nginx/conf.d/default.conf
# 暴露 80 端口(与 Nginx 配置一致)
EXPOSE 80
# 启动 Nginx前台运行
CMD ["nginx", "-g", "daemon off;"]
```
- **nginx.confNginx 配置)**
```nginx
server {
listen 80;
server_name localhost;
root /usr/share/nginx/html;
index index.html;
# 反向代理:将 /api 请求转发到后端服务(容器名+端口,由 docker-compose 管理)
location /api {
proxy_pass http://backend:8000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
# 解决 Vue Router history 模式的 404 问题(所有路由指向 index.html
location / {
try_files $uri $uri/ /index.html;
}
}
```
- **配合运维**:将 `dist`、`Dockerfile`、`nginx.conf` 交给运维,由运维通过 `docker-compose` 与后端、MySQL、Redis 等服务一起部署。
### 2. 上线后维护与迭代(贴合任务 10 中的“持续改进”)
- **问题排查**
- 生产环境 bug通过浏览器“开发者工具”查看控制台错误如接口 500、JS 报错),配合后端定位问题(如“日志分析页勾选超过 10 条未提示”→前端补充判断逻辑);
- 兼容性问题:收集运维反馈(如 Firefox 中图表不显示),修复 ECharts 初始化问题;
- **需求迭代**
- 低优先级需求:如“日志导出 Excel”任务 6 中未提及,属于迭代需求),用 Element Plus 的 `XLSX` 库实现;
- 体验优化:如“故障列表页增加‘最近 24 小时’筛选”,根据运维反馈补充筛选条件;
- **文档更新**
- 维护前端开发文档,记录“接口变更历史”“新增功能说明”,方便后续迭代;
- 向运维提供“前端部署手册”,说明如何更新前端镜像、重启容器。
## 六、关键注意事项(贯穿全流程)
1. **安全问题**
- Token 安全:避免用 `sessionStorage`(页面刷新丢失),用 `localStorage` 可配合简单加密(如 base64
- XSS 防护:渲染日志内容时用 `v-text` 而非 `v-html`,避免注入恶意脚本;
2. **可维护性**
- 代码规范:严格遵守 ESLint 规则,提交代码前用 `npm run lint` 修复格式问题;
- 注释清晰:组件和接口函数需加注释(如 `// 执行故障修复传入故障ID返回修复结果`
3. **运维体验**
- 操作反馈:所有用户操作(点击按钮、筛选)需有明确反馈(如按钮 loading、筛选后表格加载中
- 异常兜底:接口返回空数据时(如无故障记录),显示“暂无数据”而非空白页面,提升运维信心。
通过以上全流程工作,前端开发者可完成“从需求到上线”的闭环,最终交付一个符合运维需求、体验流畅、稳定可靠的可视化操作系统,助力 Hadoop 集群故障的自动化处理。

@ -1,226 +0,0 @@
<!-- 顶部导航栏组件 -->
<div class="app-header">
<!-- 左侧:系统标题和菜单切换 -->
<div class="header-left">
<button class="menu-toggle" id="menuToggle" title="切换菜单">
<i class="icon-menu"></i>
</button>
<div class="system-title">
<h1>错误检测系统</h1>
<span class="version">v1.0</span>
</div>
</div>
<!-- 中间:面包屑导航 -->
<div class="header-center">
<nav class="breadcrumb" id="breadcrumb">
<span class="breadcrumb-item">
<i class="icon-home"></i>
<span>首页</span>
</span>
<span class="breadcrumb-separator">/</span>
<span class="breadcrumb-item active">集群监控</span>
</nav>
</div>
<!-- 右侧:功能按钮和用户信息 -->
<div class="header-right">
<!-- 系统状态指示器 -->
<div class="system-status" title="系统状态">
<span class="status-dot online"></span>
<span class="status-text">系统正常</span>
</div>
<!-- 通知中心 -->
<div class="notification-center">
<button class="notification-btn" id="notificationBtn" title="通知中心">
<i class="icon-bell"></i>
<span class="notification-badge">3</span>
</button>
<!-- 通知下拉菜单 -->
<div class="notification-dropdown" id="notificationDropdown">
<div class="notification-header">
<h4>通知中心</h4>
<button class="mark-all-read">全部已读</button>
</div>
<div class="notification-list">
<div class="notification-item unread">
<div class="notification-icon warning">
<i class="icon-warning"></i>
</div>
<div class="notification-content">
<div class="notification-title">服务器CPU使用率过高</div>
<div class="notification-desc">服务器192.168.1.100 CPU使用率达到85%</div>
<div class="notification-time">5分钟前</div>
</div>
</div>
<div class="notification-item unread">
<div class="notification-icon danger">
<i class="icon-error"></i>
</div>
<div class="notification-content">
<div class="notification-title">数据库连接异常</div>
<div class="notification-desc">主数据库连接超时,请检查网络状态</div>
<div class="notification-time">10分钟前</div>
</div>
</div>
<div class="notification-item">
<div class="notification-icon success">
<i class="icon-success"></i>
</div>
<div class="notification-content">
<div class="notification-title">系统更新完成</div>
<div class="notification-desc">系统已成功更新到v1.0.1版本</div>
<div class="notification-time">1小时前</div>
</div>
</div>
</div>
<div class="notification-footer">
<a href="#" class="view-all">查看全部通知</a>
</div>
</div>
</div>
<!-- 主题切换 -->
<div class="theme-switcher">
<button class="theme-toggle" id="themeToggle" title="切换主题">
<i class="icon-sun" id="themeIcon"></i>
</button>
</div>
<!-- 用户信息 -->
<div class="user-info">
<div class="user-avatar">
<img src="data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMzIiIGhlaWdodD0iMzIiIHZpZXdCb3g9IjAgMCAzMiAzMiIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPGNpcmNsZSBjeD0iMTYiIGN5PSIxNiIgcj0iMTYiIGZpbGw9IiM0MDlFRkYiLz4KPHN2ZyB4PSI4IiB5PSI4IiB3aWR0aD0iMTYiIGhlaWdodD0iMTYiIHZpZXdCb3g9IjAgMCAxNiAxNiIgZmlsbD0ibm9uZSI+CjxwYXRoIGQ9Ik04IDhDOS42NTY4NSA4IDExIDYuNjU2ODUgMTEgNUMxMSAzLjM0MzE1IDkuNjU2ODUgMiA4IDJDNi4zNDMxNSAyIDUgMy4zNDMxNSA1IDVDNSA2LjY1Njg1IDYuMzQzMTUgOCA4IDhaIiBmaWxsPSJ3aGl0ZSIvPgo8cGF0aCBkPSJNOCAxMEMxMC4yMDkxIDEwIDEyIDExLjc5MDkgMTIgMTRIMTBDMTAgMTIuODk1NCA5LjEwNDU3IDEyIDggMTJDNi44OTU0MyAxMiA2IDEyLjg5NTQgNiAxNEg0QzQgMTEuNzkwOSA1Ljc5MDg2IDEwIDggMTBaIiBmaWxsPSJ3aGl0ZSIvPgo8L3N2Zz4KPC9zdmc+" alt="用户头像">
</div>
<div class="user-dropdown">
<button class="user-btn" id="userBtn">
<span class="user-name">管理员</span>
<i class="icon-chevron-down"></i>
</button>
<!-- 用户下拉菜单 -->
<div class="user-menu" id="userMenu">
<div class="user-menu-header">
<div class="user-avatar-large">
<img src="data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNDAiIGhlaWdodD0iNDAiIHZpZXdCb3g9IjAgMCA0MCA0MCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPGNpcmNsZSBjeD0iMjAiIGN5PSIyMCIgcj0iMjAiIGZpbGw9IiM0MDlFRkYiLz4KPHN2ZyB4PSIxMCIgeT0iMTAiIHdpZHRoPSIyMCIgaGVpZ2h0PSIyMCIgdmlld0JveD0iMCAwIDIwIDIwIiBmaWxsPSJub25lIj4KPHBhdGggZD0iTTEwIDEwQzEyLjIwOTEgMTAgMTQgOC4yMDkxNCAxNCA2QzE0IDMuNzkwODYgMTIuMjA5MSAyIDEwIDJDNy43OTA4NiAyIDYgMy43OTA4NiA2IDZDNiA4LjIwOTE0IDcuNzkwODYgMTAgMTAgMTBaIiBmaWxsPSJ3aGl0ZSIvPgo8cGF0aCBkPSJNMTAgMTJDMTMuMzEzNyAxMiAxNiAxNC42ODYzIDE2IDE4SDE0QzE0IDE1Ljc5MDkgMTIuMjA5MSAxNCAxMCAxNEM3Ljc5MDg2IDE0IDYgMTUuNzkwOSA2IDE4SDRDNCAxNC42ODYzIDYuNjg2MjkgMTIgMTAgMTJaIiBmaWxsPSJ3aGl0ZSIvPgo8L3N2Zz4KPC9zdmc+" alt="用户头像">
</div>
<div class="user-info-detail">
<div class="user-name-large">管理员</div>
<div class="user-role">系统管理员</div>
<div class="user-email">admin@errordetect.com</div>
</div>
</div>
<div class="user-menu-body">
<a href="#" class="user-menu-item">
<i class="icon-user"></i>
<span>个人资料</span>
</a>
<a href="#" class="user-menu-item">
<i class="icon-settings"></i>
<span>系统设置</span>
</a>
<a href="#" class="user-menu-item">
<i class="icon-help"></i>
<span>帮助中心</span>
</a>
<div class="user-menu-divider"></div>
<a href="#" class="user-menu-item logout">
<i class="icon-logout"></i>
<span>退出登录</span>
</a>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- 图标字体样式使用CSS伪元素实现简单图标 -->
<style>
/* 图标字体基础样式 */
[class^="icon-"], [class*=" icon-"] {
display: inline-block;
width: 16px;
height: 16px;
line-height: 1;
vertical-align: middle;
}
/* 菜单图标 */
.icon-menu::before {
content: "☰";
font-size: 16px;
}
/* 首页图标 */
.icon-home::before {
content: "🏠";
font-size: 14px;
}
/* 铃铛图标 */
.icon-bell::before {
content: "🔔";
font-size: 14px;
}
/* 太阳图标 */
.icon-sun::before {
content: "☀️";
font-size: 14px;
}
/* 月亮图标 */
.icon-moon::before {
content: "🌙";
font-size: 14px;
}
/* 下拉箭头 */
.icon-chevron-down::before {
content: "▼";
font-size: 10px;
}
/* 用户图标 */
.icon-user::before {
content: "👤";
font-size: 14px;
}
/* 设置图标 */
.icon-settings::before {
content: "⚙️";
font-size: 14px;
}
/* 帮助图标 */
.icon-help::before {
content: "❓";
font-size: 14px;
}
/* 退出图标 */
.icon-logout::before {
content: "🚪";
font-size: 14px;
}
/* 警告图标 */
.icon-warning::before {
content: "⚠️";
font-size: 14px;
}
/* 错误图标 */
.icon-error::before {
content: "❌";
font-size: 14px;
}
/* 成功图标 */
.icon-success::before {
content: "✅";
font-size: 14px;
}
</style>

@ -1,275 +0,0 @@
<!-- 侧边栏导航组件 -->
<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>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 89 KiB

@ -1,326 +0,0 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Hadoop故障检测与自动恢复系统</title>
<!-- 网站图标 -->
<link rel="icon" type="image/x-icon" href="favicon.ico">
<link rel="shortcut icon" type="image/x-icon" href="favicon.ico">
<!-- 样式文件 -->
<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="js/components.js"></script>
<script src="js/charts.js"></script>
<script src="js/demo-data.js"></script>
<script src="js/app.js"></script>
</body>
</html>

@ -1,540 +0,0 @@
// 主应用程序
class ErrorDetectingApp {
constructor() {
this.currentPage = 'login';
this.isLoggedIn = false;
this.sidebarCollapsed = false;
this.darkMode = false;
this.init();
}
// 初始化应用
init() {
this.loadStoredSettings();
this.bindEvents();
this.initializeRouter();
this.loadPageComponents();
this.checkAuthStatus();
}
// 加载存储的设置
loadStoredSettings() {
const darkMode = localStorage.getItem('darkMode');
const sidebarCollapsed = localStorage.getItem('sidebarCollapsed');
if (darkMode === 'true') {
this.toggleDarkMode(true);
}
if (sidebarCollapsed === 'true') {
this.toggleSidebar(true);
}
}
// 绑定事件
bindEvents() {
// 侧边栏切换
const sidebarToggle = document.getElementById('sidebarToggle');
if (sidebarToggle) {
sidebarToggle.addEventListener('click', () => this.toggleSidebar());
}
// 主题切换
const themeToggle = document.getElementById('themeToggle');
if (themeToggle) {
themeToggle.addEventListener('click', () => this.toggleDarkMode());
}
// 导航菜单点击
document.addEventListener('click', (e) => {
if (e.target.matches('[data-page]')) {
e.preventDefault();
const page = e.target.getAttribute('data-page');
this.navigateTo(page);
}
});
// 登录表单提交
const loginForm = document.getElementById('loginForm');
if (loginForm) {
loginForm.addEventListener('submit', (e) => this.handleLogin(e));
}
// 退出登录
const logoutBtn = document.getElementById('logoutBtn');
if (logoutBtn) {
logoutBtn.addEventListener('click', () => this.handleLogout());
}
// 窗口大小变化
window.addEventListener('resize', () => this.handleResize());
}
// 加载页面组件
async loadPageComponents() {
try {
// 加载登录页面内容
await this.loadPageContent('views/Login/Login.html', '.login-page-wrapper');
// 加载布局组件
await this.loadPageContent('components/Layout/Header.html', '#header');
await this.loadPageContent('components/Layout/Sidebar.html', '#sidebar');
// 加载其他页面内容
await this.loadPageContent('views/ClusterMonitor/ClusterMonitor.html', '#clusterMonitorPage');
await this.loadPageContent('views/FaultManage/FaultManage.html', '#faultManagePage');
await this.loadPageContent('views/LogAnalysis/LogAnalysis.html', '#logAnalysisPage');
} catch (error) {
console.error('加载页面组件失败:', error);
}
}
// 初始化路由
initializeRouter() {
// 监听浏览器前进后退
window.addEventListener('popstate', (e) => {
if (e.state && e.state.page) {
this.showPage(e.state.page, false);
}
});
// 初始页面路由
const hash = window.location.hash.slice(1);
if (hash) {
this.navigateTo(hash, false);
} else {
this.navigateTo('login', false);
}
}
// 检查认证状态
checkAuthStatus() {
const token = localStorage.getItem('authToken');
if (token) {
// 这里应该验证token的有效性
this.isLoggedIn = true;
if (this.currentPage === 'login') {
this.navigateTo('dashboard');
}
} else {
this.isLoggedIn = false;
if (this.currentPage !== 'login') {
this.navigateTo('login');
}
}
}
// 导航到指定页面
navigateTo(page, pushState = true) {
// 检查是否需要登录
if (!this.isLoggedIn && page !== 'login') {
this.navigateTo('login');
return;
}
this.showPage(page, pushState);
}
// 显示页面
showPage(page, pushState = true) {
// 隐藏所有页面
const pages = document.querySelectorAll('.page-view');
pages.forEach(p => p.style.display = 'none');
// 显示目标页面
const targetPage = document.getElementById(page + 'Page');
if (targetPage) {
targetPage.style.display = 'block';
this.currentPage = page;
// 更新导航状态
this.updateNavigation(page);
// 更新浏览器历史
if (pushState) {
const url = page === 'login' ? '/' : `/#${page}`;
history.pushState({ page }, '', url);
}
// 更新页面标题
this.updatePageTitle(page);
// 触发页面加载事件
this.onPageLoad(page);
}
// 控制布局显示
const appLayout = document.getElementById('appLayout');
const loginContainer = document.getElementById('loginContainer');
if (page === 'login') {
if (appLayout) appLayout.style.display = 'none';
if (loginContainer) loginContainer.style.display = 'flex';
} else {
if (appLayout) appLayout.style.display = 'flex';
if (loginContainer) loginContainer.style.display = 'none';
}
}
// 更新导航状态
updateNavigation(page) {
// 移除所有活动状态
const navItems = document.querySelectorAll('.nav-item');
navItems.forEach(item => item.classList.remove('active'));
// 添加当前页面的活动状态
const currentNavItem = document.querySelector(`[data-page="${page}"]`);
if (currentNavItem) {
currentNavItem.closest('.nav-item').classList.add('active');
}
// 更新面包屑
this.updateBreadcrumb(page);
}
// 更新面包屑导航
updateBreadcrumb(page) {
const breadcrumb = document.getElementById('breadcrumb');
if (!breadcrumb) return;
const pageNames = {
'dashboard': '概览仪表板',
'cluster-monitor': '集群监控',
'fault-manage': '故障管理',
'log-analysis': '日志分析',
'system-config': '系统配置',
'user-manage': '用户管理',
'system-monitor': '系统监控',
'help': '帮助中心'
};
const pageName = pageNames[page] || '未知页面';
breadcrumb.innerHTML = `
<span class="breadcrumb-item">
<i class="icon-home"></i>
<span>首页</span>
</span>
<span class="breadcrumb-separator">/</span>
<span class="breadcrumb-item active">${pageName}</span>
`;
}
// 更新页面标题
updatePageTitle(page) {
const titles = {
'login': '登录 - 错误检测系统',
'dashboard': '概览仪表板 - 错误检测系统',
'cluster-monitor': '集群监控 - 错误检测系统',
'fault-manage': '故障管理 - 错误检测系统',
'log-analysis': '日志分析 - 错误检测系统',
'system-config': '系统配置 - 错误检测系统',
'user-manage': '用户管理 - 错误检测系统',
'system-monitor': '系统监控 - 错误检测系统',
'help': '帮助中心 - 错误检测系统'
};
document.title = titles[page] || '错误检测系统';
}
// 页面加载事件
onPageLoad(page) {
switch (page) {
case 'cluster-monitor':
this.initClusterMonitor();
break;
case 'fault-manage':
this.initFaultManage();
break;
case 'log-analysis':
this.initLogAnalysis();
break;
case 'dashboard':
this.initDashboard();
break;
}
}
// 切换侧边栏
toggleSidebar(force = null) {
const sidebar = document.getElementById('sidebar');
const mainContent = document.getElementById('mainContent');
if (!sidebar || !mainContent) return;
if (force !== null) {
this.sidebarCollapsed = force;
} else {
this.sidebarCollapsed = !this.sidebarCollapsed;
}
if (this.sidebarCollapsed) {
sidebar.classList.add('collapsed');
mainContent.classList.add('sidebar-collapsed');
} else {
sidebar.classList.remove('collapsed');
mainContent.classList.remove('sidebar-collapsed');
}
localStorage.setItem('sidebarCollapsed', this.sidebarCollapsed);
}
// 切换深色模式
toggleDarkMode(force = null) {
if (force !== null) {
this.darkMode = force;
} else {
this.darkMode = !this.darkMode;
}
if (this.darkMode) {
document.body.classList.add('dark-theme');
} else {
document.body.classList.remove('dark-theme');
}
localStorage.setItem('darkMode', this.darkMode);
// 更新主题切换按钮图标
const themeToggle = document.getElementById('themeToggle');
if (themeToggle) {
const icon = themeToggle.querySelector('i');
if (icon) {
icon.className = this.darkMode ? 'icon-sun' : 'icon-moon';
}
}
}
// 处理登录
handleLogin(e) {
e.preventDefault();
const username = document.getElementById('username').value;
const password = document.getElementById('password').value;
const captcha = document.getElementById('captcha').value;
// 简单的登录验证实际项目中应该调用API
if (username && password && captcha) {
// 模拟登录成功
this.isLoggedIn = true;
localStorage.setItem('authToken', 'mock-token-' + Date.now());
localStorage.setItem('username', username);
// 显示登录成功消息
this.showNotification('登录成功!', 'success');
// 跳转到仪表板
setTimeout(() => {
this.navigateTo('dashboard');
}, 1000);
} else {
this.showNotification('请填写完整的登录信息', 'error');
}
}
// 处理退出登录
handleLogout() {
this.isLoggedIn = false;
localStorage.removeItem('authToken');
localStorage.removeItem('username');
this.showNotification('已退出登录', 'info');
this.navigateTo('login');
}
// 处理窗口大小变化
handleResize() {
const width = window.innerWidth;
// 在小屏幕上自动收起侧边栏
if (width < 768 && !this.sidebarCollapsed) {
this.toggleSidebar(true);
}
}
// 显示通知
showNotification(message, type = 'info', duration = 3000) {
// 创建通知元素
const notification = document.createElement('div');
notification.className = `notification notification-${type}`;
notification.innerHTML = `
<div class="notification-content">
<i class="notification-icon icon-${type === 'success' ? 'check' : type === 'error' ? 'x' : 'info'}"></i>
<span class="notification-message">${message}</span>
<button class="notification-close" onclick="this.parentElement.parentElement.remove()">
<i class="icon-x"></i>
</button>
</div>
`;
// 添加到页面
let container = document.getElementById('notificationContainer');
if (!container) {
container = document.createElement('div');
container.id = 'notificationContainer';
container.className = 'notification-container';
document.body.appendChild(container);
}
container.appendChild(notification);
// 自动移除
setTimeout(() => {
if (notification.parentElement) {
notification.remove();
}
}, duration);
}
// 初始化仪表板
initDashboard() {
// 仪表板页面已在HTML中定义这里可以添加动态数据加载
console.log('仪表板页面初始化完成');
}
// 初始化集群监控
initClusterMonitor() {
const container = document.getElementById('clusterMonitorPage');
if (container && !container.hasChildNodes()) {
// 动态加载集群监控页面内容
this.loadPageContent('views/ClusterMonitor/ClusterMonitor.html', container);
}
}
// 初始化故障管理
initFaultManage() {
const container = document.getElementById('faultManagePage');
if (container && !container.hasChildNodes()) {
// 动态加载故障管理页面内容
this.loadPageContent('views/FaultManage/FaultManage.html', container);
}
}
// 初始化日志分析
initLogAnalysis() {
const container = document.getElementById('logAnalysisPage');
if (container && !container.hasChildNodes()) {
// 动态加载日志分析页面内容
this.loadPageContent('views/LogAnalysis/LogAnalysis.html', container);
}
}
// 动态加载页面内容的辅助方法
async loadPageContent(url, containerSelector) {
try {
const container = document.querySelector(containerSelector);
if (!container) {
console.warn(`容器不存在: ${containerSelector}`);
return;
}
const response = await fetch(url);
if (response.ok) {
const html = await response.text();
container.innerHTML = html;
} else {
console.warn(`无法加载页面内容: ${url}`);
container.innerHTML = '<div class="error-message">页面加载失败</div>';
}
} catch (error) {
console.error(`加载页面内容时出错: ${url}`, error);
const container = document.querySelector(containerSelector);
if (container) {
container.innerHTML = '<div class="error-message">页面加载出错</div>';
}
}
}
}
// 工具函数
const Utils = {
// 格式化时间
formatTime(timestamp, format = 'YYYY-MM-DD HH:mm:ss') {
const date = new Date(timestamp);
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, '0');
const day = String(date.getDate()).padStart(2, '0');
const hours = String(date.getHours()).padStart(2, '0');
const minutes = String(date.getMinutes()).padStart(2, '0');
const seconds = String(date.getSeconds()).padStart(2, '0');
return format
.replace('YYYY', year)
.replace('MM', month)
.replace('DD', day)
.replace('HH', hours)
.replace('mm', minutes)
.replace('ss', seconds);
},
// 格式化文件大小
formatFileSize(bytes) {
if (bytes === 0) return '0 B';
const k = 1024;
const sizes = ['B', 'KB', 'MB', 'GB', 'TB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
},
// 防抖函数
debounce(func, wait) {
let timeout;
return function executedFunction(...args) {
const later = () => {
clearTimeout(timeout);
func(...args);
};
clearTimeout(timeout);
timeout = setTimeout(later, wait);
};
},
// 节流函数
throttle(func, limit) {
let inThrottle;
return function() {
const args = arguments;
const context = this;
if (!inThrottle) {
func.apply(context, args);
inThrottle = true;
setTimeout(() => inThrottle = false, limit);
}
};
},
// 生成随机ID
generateId(length = 8) {
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
let result = '';
for (let i = 0; i < length; i++) {
result += chars.charAt(Math.floor(Math.random() * chars.length));
}
return result;
},
// 深拷贝
deepClone(obj) {
if (obj === null || typeof obj !== 'object') return obj;
if (obj instanceof Date) return new Date(obj.getTime());
if (obj instanceof Array) return obj.map(item => this.deepClone(item));
if (typeof obj === 'object') {
const clonedObj = {};
for (const key in obj) {
if (obj.hasOwnProperty(key)) {
clonedObj[key] = this.deepClone(obj[key]);
}
}
return clonedObj;
}
}
};
// 页面加载完成后初始化应用
document.addEventListener('DOMContentLoaded', () => {
window.app = new ErrorDetectingApp();
});
// 导出到全局
window.ErrorDetectingApp = ErrorDetectingApp;
window.Utils = Utils;

@ -1,435 +0,0 @@
/**
* 图表组件库
* 基于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('图表组件库已加载');

@ -1,690 +0,0 @@
// 通用组件库
// 模态框组件
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;

@ -1,380 +0,0 @@
/**
* 演示数据文件
* 为各个页面提供模拟数据以展示功能
*/
// 演示数据管理器
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('演示数据管理器已加载');

@ -1,634 +0,0 @@
/* 集群监控页面样式 */
.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;
}
}

@ -1,519 +0,0 @@
/* 通用组件样式 */
/* 模态框样式 */
.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;
}
}

@ -1,133 +0,0 @@
/* 深色主题样式 */
.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);
}

@ -1,735 +0,0 @@
/* 故障管理页面样式 */
.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;
}
}

@ -1,735 +0,0 @@
/* 布局组件样式 */
/* ==================== 顶部导航栏样式 ==================== */
.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;
}

@ -1,759 +0,0 @@
/* 日志分析页面样式 */
.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;
}
}

@ -1,639 +0,0 @@
/* 登录页面样式 */
.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;
}
}

@ -1,529 +0,0 @@
/* 全局样式重置 */
* {
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%; }

@ -1,486 +0,0 @@
/**
*
*
*/
/* 基础响应式断点 */
: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;
}
}

@ -1,490 +0,0 @@
<!-- 集群监控页面 -->
<div class="cluster-monitor-page" id="clusterMonitorPage">
<!-- 页面头部 -->
<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-secondary" id="refreshBtn">
<i class="icon-refresh"></i>
<span>刷新</span>
</button>
<button class="btn btn-primary" id="addNodeBtn">
<i class="icon-plus"></i>
<span>添加节点</span>
</button>
</div>
</div>
<!-- 集群概览统计 -->
<div class="cluster-overview">
<div class="overview-cards">
<div class="overview-card">
<div class="card-icon online">
<i class="icon-server"></i>
</div>
<div class="card-content">
<div class="card-value">12</div>
<div class="card-label">在线节点</div>
<div class="card-trend up">
<i class="icon-arrow-up"></i>
<span>+2</span>
</div>
</div>
</div>
<div class="overview-card">
<div class="card-icon warning">
<i class="icon-warning-triangle"></i>
</div>
<div class="card-content">
<div class="card-value">3</div>
<div class="card-label">告警节点</div>
<div class="card-trend down">
<i class="icon-arrow-down"></i>
<span>-1</span>
</div>
</div>
</div>
<div class="overview-card">
<div class="card-icon danger">
<i class="icon-server-off"></i>
</div>
<div class="card-content">
<div class="card-value">1</div>
<div class="card-label">离线节点</div>
<div class="card-trend">
<span>无变化</span>
</div>
</div>
</div>
<div class="overview-card">
<div class="card-icon info">
<i class="icon-cpu"></i>
</div>
<div class="card-content">
<div class="card-value">68%</div>
<div class="card-label">平均CPU使用率</div>
<div class="card-trend up">
<i class="icon-arrow-up"></i>
<span>+5%</span>
</div>
</div>
</div>
</div>
</div>
<!-- 主要内容区域 -->
<div class="cluster-content">
<!-- 左侧:节点列表 -->
<div class="cluster-nodes">
<div class="section-header">
<h2 class="section-title">节点列表</h2>
<div class="section-actions">
<div class="filter-group">
<select class="form-control" id="statusFilter">
<option value="">全部状态</option>
<option value="online">在线</option>
<option value="warning">告警</option>
<option value="offline">离线</option>
</select>
</div>
<div class="search-group">
<input type="text" class="form-control" placeholder="搜索节点..." id="nodeSearch">
<i class="icon-search"></i>
</div>
</div>
</div>
<div class="nodes-container">
<!-- 节点卡片列表 -->
<div class="node-card online" data-node-id="node-001">
<div class="node-header">
<div class="node-info">
<div class="node-name">Web-Server-01</div>
<div class="node-ip">192.168.1.101</div>
</div>
<div class="node-status">
<span class="status-badge online">在线</span>
</div>
</div>
<div class="node-metrics">
<div class="metric-item">
<div class="metric-label">CPU</div>
<div class="metric-value">
<div class="progress-bar">
<div class="progress-fill" style="width: 65%"></div>
</div>
<span class="metric-text">65%</span>
</div>
</div>
<div class="metric-item">
<div class="metric-label">内存</div>
<div class="metric-value">
<div class="progress-bar">
<div class="progress-fill" style="width: 78%"></div>
</div>
<span class="metric-text">78%</span>
</div>
</div>
<div class="metric-item">
<div class="metric-label">磁盘</div>
<div class="metric-value">
<div class="progress-bar">
<div class="progress-fill" style="width: 45%"></div>
</div>
<span class="metric-text">45%</span>
</div>
</div>
</div>
<div class="node-actions">
<button class="btn-icon" title="查看详情">
<i class="icon-eye"></i>
</button>
<button class="btn-icon" title="重启节点">
<i class="icon-restart"></i>
</button>
<button class="btn-icon" title="更多操作">
<i class="icon-more"></i>
</button>
</div>
</div>
<div class="node-card warning" data-node-id="node-002">
<div class="node-header">
<div class="node-info">
<div class="node-name">DB-Server-01</div>
<div class="node-ip">192.168.1.102</div>
</div>
<div class="node-status">
<span class="status-badge warning">告警</span>
</div>
</div>
<div class="node-metrics">
<div class="metric-item">
<div class="metric-label">CPU</div>
<div class="metric-value">
<div class="progress-bar">
<div class="progress-fill warning" style="width: 85%"></div>
</div>
<span class="metric-text">85%</span>
</div>
</div>
<div class="metric-item">
<div class="metric-label">内存</div>
<div class="metric-value">
<div class="progress-bar">
<div class="progress-fill danger" style="width: 92%"></div>
</div>
<span class="metric-text">92%</span>
</div>
</div>
<div class="metric-item">
<div class="metric-label">磁盘</div>
<div class="metric-value">
<div class="progress-bar">
<div class="progress-fill" style="width: 67%"></div>
</div>
<span class="metric-text">67%</span>
</div>
</div>
</div>
<div class="node-actions">
<button class="btn-icon" title="查看详情">
<i class="icon-eye"></i>
</button>
<button class="btn-icon" title="重启节点">
<i class="icon-restart"></i>
</button>
<button class="btn-icon" title="更多操作">
<i class="icon-more"></i>
</button>
</div>
</div>
<div class="node-card offline" data-node-id="node-003">
<div class="node-header">
<div class="node-info">
<div class="node-name">Cache-Server-01</div>
<div class="node-ip">192.168.1.103</div>
</div>
<div class="node-status">
<span class="status-badge offline">离线</span>
</div>
</div>
<div class="node-metrics">
<div class="metric-item">
<div class="metric-label">CPU</div>
<div class="metric-value">
<div class="progress-bar">
<div class="progress-fill" style="width: 0%"></div>
</div>
<span class="metric-text">--</span>
</div>
</div>
<div class="metric-item">
<div class="metric-label">内存</div>
<div class="metric-value">
<div class="progress-bar">
<div class="progress-fill" style="width: 0%"></div>
</div>
<span class="metric-text">--</span>
</div>
</div>
<div class="metric-item">
<div class="metric-label">磁盘</div>
<div class="metric-value">
<div class="progress-bar">
<div class="progress-fill" style="width: 0%"></div>
</div>
<span class="metric-text">--</span>
</div>
</div>
</div>
<div class="node-actions">
<button class="btn-icon" title="查看详情">
<i class="icon-eye"></i>
</button>
<button class="btn-icon" title="启动节点">
<i class="icon-play"></i>
</button>
<button class="btn-icon" title="更多操作">
<i class="icon-more"></i>
</button>
</div>
</div>
</div>
</div>
<!-- 右侧:监控图表 -->
<div class="cluster-charts">
<!-- 资源使用趋势图 -->
<div class="chart-section">
<div class="chart-header">
<h3 class="chart-title">资源使用趋势</h3>
<div class="chart-controls">
<select class="form-control small" id="timeRange">
<option value="1h">最近1小时</option>
<option value="6h">最近6小时</option>
<option value="24h" selected>最近24小时</option>
<option value="7d">最近7天</option>
</select>
</div>
</div>
<div class="chart-container">
<div id="resourceTrendChart" class="chart-placeholder">
<!-- ECharts图表将在这里渲染 -->
<div class="chart-loading">
<div class="loading-spinner"></div>
<p>加载图表数据中...</p>
</div>
</div>
</div>
</div>
<!-- 节点性能对比 -->
<div class="chart-section">
<div class="chart-header">
<h3 class="chart-title">节点性能对比</h3>
<div class="chart-legend">
<div class="legend-item">
<span class="legend-color cpu"></span>
<span class="legend-text">CPU使用率</span>
</div>
<div class="legend-item">
<span class="legend-color memory"></span>
<span class="legend-text">内存使用率</span>
</div>
<div class="legend-item">
<span class="legend-color disk"></span>
<span class="legend-text">磁盘使用率</span>
</div>
</div>
</div>
<div class="chart-container">
<div id="nodeComparisonChart" class="chart-placeholder">
<!-- ECharts图表将在这里渲染 -->
<div class="chart-loading">
<div class="loading-spinner"></div>
<p>加载图表数据中...</p>
</div>
</div>
</div>
</div>
<!-- 网络流量监控 -->
<div class="chart-section">
<div class="chart-header">
<h3 class="chart-title">网络流量监控</h3>
<div class="traffic-stats">
<div class="stat-item">
<span class="stat-label">入站流量</span>
<span class="stat-value">1.2 GB/s</span>
</div>
<div class="stat-item">
<span class="stat-label">出站流量</span>
<span class="stat-value">0.8 GB/s</span>
</div>
</div>
</div>
<div class="chart-container">
<div id="networkTrafficChart" class="chart-placeholder">
<!-- ECharts图表将在这里渲染 -->
<div class="chart-loading">
<div class="loading-spinner"></div>
<p>加载图表数据中...</p>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- 实时告警面板 -->
<div class="alert-panel" id="alertPanel">
<div class="alert-header">
<h3 class="alert-title">
<i class="icon-alert"></i>
<span>实时告警</span>
<span class="alert-count">3</span>
</h3>
<button class="alert-toggle" id="alertToggle">
<i class="icon-chevron-up"></i>
</button>
</div>
<div class="alert-content">
<div class="alert-item high">
<div class="alert-icon">
<i class="icon-danger"></i>
</div>
<div class="alert-info">
<div class="alert-message">DB-Server-01 内存使用率超过90%</div>
<div class="alert-time">2分钟前</div>
</div>
<div class="alert-actions">
<button class="btn-small btn-primary">处理</button>
<button class="btn-small btn-secondary">忽略</button>
</div>
</div>
<div class="alert-item medium">
<div class="alert-icon">
<i class="icon-warning"></i>
</div>
<div class="alert-info">
<div class="alert-message">Web-Server-01 CPU使用率持续偏高</div>
<div class="alert-time">5分钟前</div>
</div>
<div class="alert-actions">
<button class="btn-small btn-primary">处理</button>
<button class="btn-small btn-secondary">忽略</button>
</div>
</div>
<div class="alert-item low">
<div class="alert-icon">
<i class="icon-info"></i>
</div>
<div class="alert-info">
<div class="alert-message">Cache-Server-01 连接超时</div>
<div class="alert-time">10分钟前</div>
</div>
<div class="alert-actions">
<button class="btn-small btn-primary">处理</button>
<button class="btn-small btn-secondary">忽略</button>
</div>
</div>
</div>
</div>
</div>
<!-- 集群监控页面图标样式 -->
<style>
/* 集群监控页面图标 */
.icon-server::before {
content: "🖥️";
font-size: 20px;
}
.icon-server-off::before {
content: "💻";
font-size: 20px;
}
.icon-warning-triangle::before {
content: "⚠️";
font-size: 20px;
}
.icon-cpu::before {
content: "⚙️";
font-size: 20px;
}
.icon-plus::before {
content: "";
font-size: 14px;
}
.icon-search::before {
content: "🔍";
font-size: 14px;
}
.icon-eye::before {
content: "👁️";
font-size: 14px;
}
.icon-restart::before {
content: "🔄";
font-size: 14px;
}
.icon-play::before {
content: "▶️";
font-size: 14px;
}
.icon-more::before {
content: "⋯";
font-size: 14px;
}
.icon-arrow-up::before {
content: "↗️";
font-size: 12px;
}
.icon-arrow-down::before {
content: "↘️";
font-size: 12px;
}
.icon-alert::before {
content: "🚨";
font-size: 16px;
}
.icon-danger::before {
content: "🔴";
font-size: 14px;
}
.icon-warning::before {
content: "🟡";
font-size: 14px;
}
.icon-info::before {
content: "🔵";
font-size: 14px;
}
.icon-chevron-up::before {
content: "▲";
font-size: 12px;
}
</style>

@ -1,502 +0,0 @@
<!-- 故障管理页面 -->
<div class="fault-manage-page" id="faultManagePage">
<!-- 页面头部 -->
<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-secondary" id="exportBtn">
<i class="icon-download"></i>
<span>导出报告</span>
</button>
<button class="btn btn-primary" id="createFaultBtn">
<i class="icon-plus"></i>
<span>手动创建故障</span>
</button>
</div>
</div>
<!-- 故障统计概览 -->
<div class="fault-overview">
<div class="overview-cards">
<div class="overview-card critical">
<div class="card-icon">
<i class="icon-alert-circle"></i>
</div>
<div class="card-content">
<div class="card-value">5</div>
<div class="card-label">严重故障</div>
<div class="card-trend up">
<i class="icon-arrow-up"></i>
<span>+2</span>
</div>
</div>
</div>
<div class="overview-card high">
<div class="card-icon">
<i class="icon-warning"></i>
</div>
<div class="card-content">
<div class="card-value">12</div>
<div class="card-label">高级故障</div>
<div class="card-trend down">
<i class="icon-arrow-down"></i>
<span>-3</span>
</div>
</div>
</div>
<div class="overview-card medium">
<div class="card-icon">
<i class="icon-info"></i>
</div>
<div class="card-content">
<div class="card-value">28</div>
<div class="card-label">中级故障</div>
<div class="card-trend">
<span>无变化</span>
</div>
</div>
</div>
<div class="overview-card resolved">
<div class="card-icon">
<i class="icon-check-circle"></i>
</div>
<div class="card-content">
<div class="card-value">156</div>
<div class="card-label">已解决故障</div>
<div class="card-trend up">
<i class="icon-arrow-up"></i>
<span>+15</span>
</div>
</div>
</div>
</div>
</div>
<!-- 故障筛选和搜索 -->
<div class="fault-filters">
<div class="filter-section">
<div class="filter-group">
<label class="filter-label">故障级别</label>
<select class="form-control" id="severityFilter">
<option value="">全部级别</option>
<option value="critical">严重</option>
<option value="high">高级</option>
<option value="medium">中级</option>
<option value="low">低级</option>
</select>
</div>
<div class="filter-group">
<label class="filter-label">故障状态</label>
<select class="form-control" id="statusFilter">
<option value="">全部状态</option>
<option value="open">待处理</option>
<option value="in_progress">处理中</option>
<option value="resolved">已解决</option>
<option value="closed">已关闭</option>
</select>
</div>
<div class="filter-group">
<label class="filter-label">故障类型</label>
<select class="form-control" id="typeFilter">
<option value="">全部类型</option>
<option value="hardware">硬件故障</option>
<option value="software">软件故障</option>
<option value="network">网络故障</option>
<option value="performance">性能问题</option>
<option value="security">安全问题</option>
</select>
</div>
<div class="filter-group">
<label class="filter-label">时间范围</label>
<select class="form-control" id="timeFilter">
<option value="today">今天</option>
<option value="week">本周</option>
<option value="month" selected>本月</option>
<option value="quarter">本季度</option>
<option value="year">本年</option>
</select>
</div>
<div class="filter-group search-group">
<label class="filter-label">搜索</label>
<div class="search-input">
<input type="text" class="form-control" placeholder="搜索故障ID、标题或描述..." id="faultSearch">
<i class="icon-search"></i>
</div>
</div>
<div class="filter-actions">
<button class="btn btn-secondary" id="resetFilters">
<i class="icon-refresh"></i>
<span>重置</span>
</button>
<button class="btn btn-primary" id="applyFilters">
<i class="icon-filter"></i>
<span>应用筛选</span>
</button>
</div>
</div>
</div>
<!-- 故障列表 -->
<div class="fault-list-container">
<div class="list-header">
<div class="list-title">
<h2>故障列表</h2>
<span class="fault-count">共 45 个故障</span>
</div>
<div class="list-controls">
<div class="view-toggle">
<button class="view-btn active" data-view="list">
<i class="icon-list"></i>
</button>
<button class="view-btn" data-view="card">
<i class="icon-grid"></i>
</button>
</div>
<div class="sort-group">
<select class="form-control" id="sortBy">
<option value="created_desc">创建时间 ↓</option>
<option value="created_asc">创建时间 ↑</option>
<option value="severity_desc">严重程度 ↓</option>
<option value="severity_asc">严重程度 ↑</option>
<option value="updated_desc">更新时间 ↓</option>
</select>
</div>
</div>
</div>
<!-- 故障表格视图 -->
<div class="fault-table-view" id="faultTableView">
<table class="fault-table">
<thead>
<tr>
<th class="sortable" data-sort="id">
<span>故障ID</span>
<i class="icon-sort"></i>
</th>
<th class="sortable" data-sort="title">
<span>故障标题</span>
<i class="icon-sort"></i>
</th>
<th class="sortable" data-sort="severity">
<span>严重程度</span>
<i class="icon-sort"></i>
</th>
<th class="sortable" data-sort="type">
<span>故障类型</span>
<i class="icon-sort"></i>
</th>
<th class="sortable" data-sort="status">
<span>状态</span>
<i class="icon-sort"></i>
</th>
<th class="sortable" data-sort="assignee">
<span>负责人</span>
<i class="icon-sort"></i>
</th>
<th class="sortable" data-sort="created">
<span>创建时间</span>
<i class="icon-sort"></i>
</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<tr class="fault-row critical" data-fault-id="FLT-2024-001">
<td class="fault-id">
<span class="id-text">FLT-2024-001</span>
</td>
<td class="fault-title">
<div class="title-content">
<span class="title-text">数据库连接池耗尽导致服务不可用</span>
<div class="title-meta">
<span class="affected-systems">影响系统: 用户服务, 订单服务</span>
</div>
</div>
</td>
<td class="fault-severity">
<span class="severity-badge critical">严重</span>
</td>
<td class="fault-type">
<span class="type-badge software">软件故障</span>
</td>
<td class="fault-status">
<span class="status-badge in-progress">处理中</span>
</td>
<td class="fault-assignee">
<div class="assignee-info">
<div class="avatar"></div>
<span class="name">张工程师</span>
</div>
</td>
<td class="fault-created">
<div class="time-info">
<span class="date">2024-01-15</span>
<span class="time">14:30</span>
</div>
</td>
<td class="fault-actions">
<button class="action-btn" title="查看详情" onclick="viewFaultDetail('FLT-2024-001')">
<i class="icon-eye"></i>
</button>
<button class="action-btn" title="编辑故障" onclick="editFault('FLT-2024-001')">
<i class="icon-edit"></i>
</button>
<button class="action-btn" title="更多操作" onclick="showFaultMenu('FLT-2024-001')">
<i class="icon-more"></i>
</button>
</td>
</tr>
<tr class="fault-row high" data-fault-id="FLT-2024-002">
<td class="fault-id">
<span class="id-text">FLT-2024-002</span>
</td>
<td class="fault-title">
<div class="title-content">
<span class="title-text">Web服务器CPU使用率持续超过90%</span>
<div class="title-meta">
<span class="affected-systems">影响系统: Web前端</span>
</div>
</div>
</td>
<td class="fault-severity">
<span class="severity-badge high">高级</span>
</td>
<td class="fault-type">
<span class="type-badge performance">性能问题</span>
</td>
<td class="fault-status">
<span class="status-badge open">待处理</span>
</td>
<td class="fault-assignee">
<div class="assignee-info">
<div class="avatar"></div>
<span class="name">李运维</span>
</div>
</td>
<td class="fault-created">
<div class="time-info">
<span class="date">2024-01-15</span>
<span class="time">16:45</span>
</div>
</td>
<td class="fault-actions">
<button class="action-btn" title="查看详情" onclick="viewFaultDetail('FLT-2024-002')">
<i class="icon-eye"></i>
</button>
<button class="action-btn" title="编辑故障" onclick="editFault('FLT-2024-002')">
<i class="icon-edit"></i>
</button>
<button class="action-btn" title="更多操作" onclick="showFaultMenu('FLT-2024-002')">
<i class="icon-more"></i>
</button>
</td>
</tr>
<tr class="fault-row resolved" data-fault-id="FLT-2024-003">
<td class="fault-id">
<span class="id-text">FLT-2024-003</span>
</td>
<td class="fault-title">
<div class="title-content">
<span class="title-text">网络交换机端口故障导致部分节点离线</span>
<div class="title-meta">
<span class="affected-systems">影响系统: 集群节点3-5</span>
</div>
</div>
</td>
<td class="fault-severity">
<span class="severity-badge medium">中级</span>
</td>
<td class="fault-type">
<span class="type-badge network">网络故障</span>
</td>
<td class="fault-status">
<span class="status-badge resolved">已解决</span>
</td>
<td class="fault-assignee">
<div class="assignee-info">
<div class="avatar"></div>
<span class="name">王网管</span>
</div>
</td>
<td class="fault-created">
<div class="time-info">
<span class="date">2024-01-14</span>
<span class="time">09:20</span>
</div>
</td>
<td class="fault-actions">
<button class="action-btn" title="查看详情" onclick="viewFaultDetail('FLT-2024-003')">
<i class="icon-eye"></i>
</button>
<button class="action-btn" title="查看报告" onclick="viewFaultReport('FLT-2024-003')">
<i class="icon-file"></i>
</button>
<button class="action-btn" title="更多操作" onclick="showFaultMenu('FLT-2024-003')">
<i class="icon-more"></i>
</button>
</td>
</tr>
</tbody>
</table>
</div>
<!-- 分页控件 -->
<div class="pagination-container">
<div class="pagination-info">
<span>显示 1-10 条,共 45 条记录</span>
</div>
<div class="pagination-controls">
<button class="page-btn" disabled>
<i class="icon-chevron-left"></i>
</button>
<button class="page-btn active">1</button>
<button class="page-btn">2</button>
<button class="page-btn">3</button>
<span class="page-dots">...</span>
<button class="page-btn">5</button>
<button class="page-btn">
<i class="icon-chevron-right"></i>
</button>
</div>
<div class="page-size-selector">
<select class="form-control">
<option value="10">10条/页</option>
<option value="20">20条/页</option>
<option value="50">50条/页</option>
</select>
</div>
</div>
</div>
<!-- 故障详情侧边栏 -->
<div class="fault-detail-sidebar" id="faultDetailSidebar">
<div class="sidebar-header">
<h3 class="sidebar-title">故障详情</h3>
<button class="close-btn" id="closeSidebar">
<i class="icon-x"></i>
</button>
</div>
<div class="sidebar-content">
<div class="fault-detail-loading">
<div class="loading-spinner"></div>
<p>加载故障详情中...</p>
</div>
<!-- 故障详情内容将通过JavaScript动态加载 -->
</div>
</div>
<!-- 遮罩层 -->
<div class="sidebar-overlay" id="sidebarOverlay"></div>
</div>
<!-- 故障管理页面图标样式 -->
<style>
/* 故障管理页面图标 */
.icon-alert-circle::before {
content: "🚨";
font-size: 20px;
}
.icon-warning::before {
content: "⚠️";
font-size: 20px;
}
.icon-info::before {
content: "";
font-size: 20px;
}
.icon-check-circle::before {
content: "✅";
font-size: 20px;
}
.icon-download::before {
content: "⬇️";
font-size: 14px;
}
.icon-plus::before {
content: "";
font-size: 14px;
}
.icon-filter::before {
content: "🔽";
font-size: 14px;
}
.icon-list::before {
content: "☰";
font-size: 14px;
}
.icon-grid::before {
content: "⊞";
font-size: 14px;
}
.icon-sort::before {
content: "↕️";
font-size: 12px;
}
.icon-eye::before {
content: "👁️";
font-size: 14px;
}
.icon-edit::before {
content: "✏️";
font-size: 14px;
}
.icon-file::before {
content: "📄";
font-size: 14px;
}
.icon-more::before {
content: "⋯";
font-size: 14px;
}
.icon-chevron-left::before {
content: "◀";
font-size: 12px;
}
.icon-chevron-right::before {
content: "▶";
font-size: 12px;
}
.icon-x::before {
content: "✕";
font-size: 16px;
}
.icon-arrow-up::before {
content: "↗️";
font-size: 12px;
}
.icon-arrow-down::before {
content: "↘️";
font-size: 12px;
}
</style>

@ -1,541 +0,0 @@
<!-- 日志分析页面 -->
<div class="log-analysis-page" id="logAnalysisPage">
<!-- 页面头部 -->
<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-secondary" id="exportLogsBtn">
<i class="icon-download"></i>
<span>导出日志</span>
</button>
<button class="btn btn-primary" id="realTimeBtn">
<i class="icon-play"></i>
<span>实时监控</span>
</button>
</div>
</div>
<!-- 日志统计概览 -->
<div class="log-overview">
<div class="overview-cards">
<div class="overview-card total">
<div class="card-icon">
<i class="icon-file-text"></i>
</div>
<div class="card-content">
<div class="card-value">1,234,567</div>
<div class="card-label">总日志条数</div>
<div class="card-trend up">
<i class="icon-arrow-up"></i>
<span>+12.5%</span>
</div>
</div>
</div>
<div class="overview-card error">
<div class="card-icon">
<i class="icon-alert-triangle"></i>
</div>
<div class="card-content">
<div class="card-value">1,245</div>
<div class="card-label">错误日志</div>
<div class="card-trend up">
<i class="icon-arrow-up"></i>
<span>+8</span>
</div>
</div>
</div>
<div class="overview-card warning">
<div class="card-icon">
<i class="icon-warning"></i>
</div>
<div class="card-content">
<div class="card-value">3,456</div>
<div class="card-label">警告日志</div>
<div class="card-trend down">
<i class="icon-arrow-down"></i>
<span>-15</span>
</div>
</div>
</div>
<div class="overview-card info">
<div class="card-icon">
<i class="icon-info"></i>
</div>
<div class="card-content">
<div class="card-value">98.7%</div>
<div class="card-label">系统可用性</div>
<div class="card-trend up">
<i class="icon-arrow-up"></i>
<span>+0.2%</span>
</div>
</div>
</div>
</div>
</div>
<!-- 日志查询表单 -->
<div class="log-query-section">
<div class="query-header">
<h2 class="section-title">日志查询</h2>
<div class="query-actions">
<button class="btn btn-secondary" id="saveQueryBtn">
<i class="icon-save"></i>
<span>保存查询</span>
</button>
<button class="btn btn-secondary" id="loadQueryBtn">
<i class="icon-folder"></i>
<span>加载查询</span>
</button>
</div>
</div>
<div class="query-form">
<!-- 基础查询条件 -->
<div class="query-row">
<div class="query-group">
<label class="query-label">时间范围</label>
<div class="time-range-selector">
<select class="form-control" id="timeRangeType">
<option value="relative">相对时间</option>
<option value="absolute">绝对时间</option>
</select>
<select class="form-control" id="relativeTime">
<option value="15m">最近15分钟</option>
<option value="1h">最近1小时</option>
<option value="6h">最近6小时</option>
<option value="24h" selected>最近24小时</option>
<option value="7d">最近7天</option>
<option value="30d">最近30天</option>
</select>
<div class="absolute-time" id="absoluteTime" style="display: none;">
<input type="datetime-local" class="form-control" id="startTime">
<span class="time-separator"></span>
<input type="datetime-local" class="form-control" id="endTime">
</div>
</div>
</div>
<div class="query-group">
<label class="query-label">日志级别</label>
<div class="level-selector">
<label class="checkbox-item">
<input type="checkbox" value="ERROR" checked>
<span class="level-badge error">ERROR</span>
</label>
<label class="checkbox-item">
<input type="checkbox" value="WARN" checked>
<span class="level-badge warning">WARN</span>
</label>
<label class="checkbox-item">
<input type="checkbox" value="INFO" checked>
<span class="level-badge info">INFO</span>
</label>
<label class="checkbox-item">
<input type="checkbox" value="DEBUG">
<span class="level-badge debug">DEBUG</span>
</label>
</div>
</div>
</div>
<div class="query-row">
<div class="query-group">
<label class="query-label">服务/模块</label>
<select class="form-control" id="serviceFilter" multiple>
<option value="user-service">用户服务</option>
<option value="order-service">订单服务</option>
<option value="payment-service">支付服务</option>
<option value="notification-service">通知服务</option>
<option value="gateway">API网关</option>
<option value="database">数据库</option>
</select>
</div>
<div class="query-group">
<label class="query-label">主机/节点</label>
<select class="form-control" id="hostFilter" multiple>
<option value="web-01">Web-Server-01</option>
<option value="web-02">Web-Server-02</option>
<option value="db-01">DB-Server-01</option>
<option value="cache-01">Cache-Server-01</option>
<option value="gateway-01">Gateway-01</option>
</select>
</div>
</div>
<!-- 高级查询条件 -->
<div class="advanced-query" id="advancedQuery">
<div class="query-row">
<div class="query-group full-width">
<label class="query-label">关键词搜索</label>
<div class="search-input-group">
<input type="text" class="form-control" placeholder="输入关键词,支持正则表达式..." id="keywordSearch">
<div class="search-options">
<label class="checkbox-item">
<input type="checkbox" id="regexMode">
<span>正则表达式</span>
</label>
<label class="checkbox-item">
<input type="checkbox" id="caseSensitive">
<span>区分大小写</span>
</label>
</div>
</div>
</div>
</div>
<div class="query-row">
<div class="query-group">
<label class="query-label">用户ID</label>
<input type="text" class="form-control" placeholder="输入用户ID..." id="userIdFilter">
</div>
<div class="query-group">
<label class="query-label">请求ID</label>
<input type="text" class="form-control" placeholder="输入请求ID..." id="requestIdFilter">
</div>
<div class="query-group">
<label class="query-label">IP地址</label>
<input type="text" class="form-control" placeholder="输入IP地址..." id="ipFilter">
</div>
</div>
</div>
<div class="query-controls">
<button class="btn btn-secondary" id="toggleAdvanced">
<i class="icon-settings"></i>
<span>高级选项</span>
</button>
<button class="btn btn-secondary" id="clearQuery">
<i class="icon-refresh"></i>
<span>清空条件</span>
</button>
<button class="btn btn-primary" id="searchLogs">
<i class="icon-search"></i>
<span>搜索日志</span>
</button>
</div>
</div>
</div>
<!-- 日志分析结果 -->
<div class="log-results-section">
<!-- 日志统计图表 -->
<div class="log-charts">
<div class="chart-container">
<div class="chart-header">
<h3 class="chart-title">日志趋势分析</h3>
<div class="chart-legend">
<div class="legend-item">
<span class="legend-color error"></span>
<span class="legend-text">错误</span>
</div>
<div class="legend-item">
<span class="legend-color warning"></span>
<span class="legend-text">警告</span>
</div>
<div class="legend-item">
<span class="legend-color info"></span>
<span class="legend-text">信息</span>
</div>
</div>
</div>
<div class="chart-content">
<div id="logTrendChart" class="chart-placeholder">
<div class="chart-loading">
<div class="loading-spinner"></div>
<p>加载图表数据中...</p>
</div>
</div>
</div>
</div>
<div class="chart-container">
<div class="chart-header">
<h3 class="chart-title">服务日志分布</h3>
</div>
<div class="chart-content">
<div id="serviceDistributionChart" class="chart-placeholder">
<div class="chart-loading">
<div class="loading-spinner"></div>
<p>加载图表数据中...</p>
</div>
</div>
</div>
</div>
</div>
<!-- 日志列表 -->
<div class="log-list-container">
<div class="list-header">
<div class="list-info">
<h3 class="list-title">日志列表</h3>
<span class="log-count">找到 1,234 条日志</span>
<div class="search-status">
<span class="status-text">搜索完成</span>
<span class="search-time">耗时 0.5s</span>
</div>
</div>
<div class="list-controls">
<div class="display-options">
<label class="checkbox-item">
<input type="checkbox" id="showTimestamp" checked>
<span>显示时间戳</span>
</label>
<label class="checkbox-item">
<input type="checkbox" id="showLevel" checked>
<span>显示级别</span>
</label>
<label class="checkbox-item">
<input type="checkbox" id="showSource" checked>
<span>显示来源</span>
</label>
</div>
<div class="view-controls">
<select class="form-control" id="pageSize">
<option value="50">50条/页</option>
<option value="100" selected>100条/页</option>
<option value="200">200条/页</option>
</select>
<button class="btn btn-secondary" id="autoRefresh">
<i class="icon-refresh"></i>
<span>自动刷新</span>
</button>
</div>
</div>
</div>
<div class="log-list" id="logList">
<!-- 日志条目 -->
<div class="log-entry error" data-log-id="log-001">
<div class="log-header">
<span class="log-timestamp">2024-01-15 14:32:15.123</span>
<span class="log-level error">ERROR</span>
<span class="log-source">user-service</span>
<span class="log-host">web-server-01</span>
</div>
<div class="log-content">
<div class="log-message">
Database connection failed: Connection timeout after 30 seconds
</div>
<div class="log-details">
<span class="log-detail-item">
<strong>Request ID:</strong> req-12345-abcde
</span>
<span class="log-detail-item">
<strong>User ID:</strong> user-67890
</span>
<span class="log-detail-item">
<strong>IP:</strong> 192.168.1.100
</span>
</div>
</div>
<div class="log-actions">
<button class="action-btn" title="查看上下文" onclick="showLogContext('log-001')">
<i class="icon-context"></i>
</button>
<button class="action-btn" title="添加到故障" onclick="addToFault('log-001')">
<i class="icon-plus-circle"></i>
</button>
<button class="action-btn" title="复制日志" onclick="copyLog('log-001')">
<i class="icon-copy"></i>
</button>
</div>
</div>
<div class="log-entry warning" data-log-id="log-002">
<div class="log-header">
<span class="log-timestamp">2024-01-15 14:31:45.567</span>
<span class="log-level warning">WARN</span>
<span class="log-source">order-service</span>
<span class="log-host">web-server-02</span>
</div>
<div class="log-content">
<div class="log-message">
High memory usage detected: 85% of available memory in use
</div>
<div class="log-details">
<span class="log-detail-item">
<strong>Memory Usage:</strong> 6.8GB / 8GB
</span>
<span class="log-detail-item">
<strong>Process:</strong> order-processor
</span>
</div>
</div>
<div class="log-actions">
<button class="action-btn" title="查看上下文" onclick="showLogContext('log-002')">
<i class="icon-context"></i>
</button>
<button class="action-btn" title="添加到故障" onclick="addToFault('log-002')">
<i class="icon-plus-circle"></i>
</button>
<button class="action-btn" title="复制日志" onclick="copyLog('log-002')">
<i class="icon-copy"></i>
</button>
</div>
</div>
<div class="log-entry info" data-log-id="log-003">
<div class="log-header">
<span class="log-timestamp">2024-01-15 14:31:30.890</span>
<span class="log-level info">INFO</span>
<span class="log-source">gateway</span>
<span class="log-host">gateway-01</span>
</div>
<div class="log-content">
<div class="log-message">
User authentication successful
</div>
<div class="log-details">
<span class="log-detail-item">
<strong>User ID:</strong> user-12345
</span>
<span class="log-detail-item">
<strong>Session ID:</strong> sess-abcde-12345
</span>
<span class="log-detail-item">
<strong>IP:</strong> 192.168.1.105
</span>
</div>
</div>
<div class="log-actions">
<button class="action-btn" title="查看上下文" onclick="showLogContext('log-003')">
<i class="icon-context"></i>
</button>
<button class="action-btn" title="添加到故障" onclick="addToFault('log-003')">
<i class="icon-plus-circle"></i>
</button>
<button class="action-btn" title="复制日志" onclick="copyLog('log-003')">
<i class="icon-copy"></i>
</button>
</div>
</div>
</div>
<!-- 分页控件 -->
<div class="pagination-container">
<div class="pagination-info">
<span>显示 1-100 条,共 1,234 条日志</span>
</div>
<div class="pagination-controls">
<button class="page-btn" disabled>
<i class="icon-chevron-left"></i>
</button>
<button class="page-btn active">1</button>
<button class="page-btn">2</button>
<button class="page-btn">3</button>
<span class="page-dots">...</span>
<button class="page-btn">13</button>
<button class="page-btn">
<i class="icon-chevron-right"></i>
</button>
</div>
<div class="jump-to-page">
<span>跳转到</span>
<input type="number" class="form-control" min="1" max="13" value="1">
<span></span>
</div>
</div>
</div>
</div>
</div>
<!-- 日志分析页面图标样式 -->
<style>
/* 日志分析页面图标 */
.icon-file-text::before {
content: "📄";
font-size: 20px;
}
.icon-alert-triangle::before {
content: "⚠️";
font-size: 20px;
}
.icon-warning::before {
content: "🟡";
font-size: 20px;
}
.icon-info::before {
content: "";
font-size: 20px;
}
.icon-download::before {
content: "⬇️";
font-size: 14px;
}
.icon-play::before {
content: "▶️";
font-size: 14px;
}
.icon-save::before {
content: "💾";
font-size: 14px;
}
.icon-folder::before {
content: "📁";
font-size: 14px;
}
.icon-settings::before {
content: "⚙️";
font-size: 14px;
}
.icon-search::before {
content: "🔍";
font-size: 14px;
}
.icon-refresh::before {
content: "🔄";
font-size: 14px;
}
.icon-context::before {
content: "📋";
font-size: 14px;
}
.icon-plus-circle::before {
content: "";
font-size: 14px;
}
.icon-copy::before {
content: "📋";
font-size: 14px;
}
.icon-chevron-left::before {
content: "◀";
font-size: 12px;
}
.icon-chevron-right::before {
content: "▶";
font-size: 12px;
}
.icon-arrow-up::before {
content: "↗️";
font-size: 12px;
}
.icon-arrow-down::before {
content: "↘️";
font-size: 12px;
}
</style>

@ -1,317 +0,0 @@
<!-- 登录页面 -->
<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…
Cancel
Save