2768 lines
84 KiB
Markdown
2768 lines
84 KiB
Markdown
# Personal.html 全面优化方案
|
||
|
||
## 文档概述
|
||
|
||
基于对 `personal.html` 文件(9,792行,368KB)的深入分析,结合现有系统优化经验和业界最佳实践,制定此全面优化方案。本方案从**性能**、**UI/UX**、**运维**三个维度提供系统性优化策略,旨在将个人健康大屏系统打造成高性能、易维护、用户体验卓越的现代化Web应用。
|
||
|
||
**文档版本**: v1.0
|
||
**制定时间**: 2025-09-18
|
||
**预期实施周期**: 12周
|
||
**目标性能提升**: 70-80%
|
||
**代码可维护性提升**: 85%+
|
||
|
||
## 1. 系统现状深度分析
|
||
|
||
### 1.1 文件规模与结构问题
|
||
|
||
#### 当前状况
|
||
- **文件大小**: 368KB,9,792行代码
|
||
- **代码密度**: 超大单体文件,违反单一职责原则
|
||
- **维护难度**: 极高,单次修改风险巨大
|
||
|
||
#### 主要问题识别
|
||
```
|
||
文件结构分析:
|
||
├── HTML结构 (~1,500行)
|
||
├── CSS样式 (~3,500行) - 内联样式过多
|
||
├── JavaScript逻辑 (~4,500行) - 业务逻辑混杂
|
||
└── 配置与数据 (~292行) - 硬编码配置
|
||
```
|
||
|
||
### 1.2 性能瓶颈分析
|
||
|
||
#### 1.2.1 关键性能指标现状
|
||
```javascript
|
||
当前性能指标:
|
||
- 首屏加载时间: 5.2-8.7秒
|
||
- DOM节点数量: 2,800+个节点
|
||
- CSS规则数量: 1,200+条规则
|
||
- JavaScript执行时间: 1.8-3.2秒
|
||
- 内存占用: 45-78MB
|
||
- 网络请求数: 8-12个并发请求
|
||
```
|
||
|
||
#### 1.2.2 性能瓶颈根因
|
||
1. **阻塞式资源加载**
|
||
- 同步加载jQuery和ECharts
|
||
- CSS内联导致HTML解析阻塞
|
||
- 无关键资源优先级控制
|
||
|
||
2. **频繁DOM操作**
|
||
```javascript
|
||
// 发现的性能问题代码模式
|
||
setInterval(loadHealthData, 10000); // 每10秒
|
||
setInterval(loadMessages, 30000); // 每30秒
|
||
setInterval(loadAlerts, 30000); // 每30秒
|
||
setInterval(loadDeviceStatus, 60000); // 每60秒
|
||
setInterval(loadHistoryChart, 300000); // 每5分钟
|
||
```
|
||
|
||
3. **无缓存策略**
|
||
- API重复请求相同数据
|
||
- 静态资源无版本控制
|
||
- 计算结果无缓存机制
|
||
|
||
4. **内存泄漏风险**
|
||
- 定时器未清理机制
|
||
- 事件监听器重复绑定
|
||
- 大型对象引用未释放
|
||
|
||
### 1.3 代码质量问题
|
||
|
||
#### 1.3.1 代码结构问题
|
||
- **职责混杂**: UI渲染、数据处理、业务逻辑耦合
|
||
- **代码重复**: 相似功能重复实现,维护成本高
|
||
- **命名不一致**: 变量、函数命名缺乏统一标准
|
||
- **错误处理缺失**: 异常情况处理不完善
|
||
|
||
#### 1.3.2 可扩展性问题
|
||
- **硬编码配置**: 接口地址、样式配置写死在代码中
|
||
- **组件耦合**: 功能模块之间高度耦合,难以独立测试
|
||
- **版本兼容**: 缺乏向后兼容性考虑
|
||
|
||
## 2. 全面优化策略
|
||
|
||
### 2.1 性能优化方案
|
||
|
||
#### 2.1.1 资源加载优化
|
||
|
||
##### A. 关键资源优先级策略
|
||
```html
|
||
<!-- 关键资源预加载 -->
|
||
<link rel="preload" href="/static/js/jquery-3.6.0.min.js" as="script">
|
||
<link rel="preload" href="/static/js/echarts-5.4.0.min.js" as="script">
|
||
<link rel="preload" href="/static/fonts/num.otf" as="font" type="font/otf" crossorigin>
|
||
|
||
<!-- 非关键资源延迟加载 -->
|
||
<link rel="preload" href="/static/css/personal-critical.css" as="style" onload="this.onload=null;this.rel='stylesheet'">
|
||
<noscript><link rel="stylesheet" href="/static/css/personal-critical.css"></noscript>
|
||
```
|
||
|
||
##### B. 模块化资源分割策略
|
||
```
|
||
资源分割方案:
|
||
├── critical.css (15KB) - 首屏关键样式
|
||
├── components.css (25KB) - 组件样式
|
||
├── themes.css (12KB) - 主题样式
|
||
├── animations.css (8KB) - 动画效果
|
||
├── core.js (45KB) - 核心功能脚本
|
||
├── charts.js (35KB) - 图表功能脚本
|
||
└── widgets.js (28KB) - 小组件脚本
|
||
```
|
||
|
||
##### C. 智能懒加载机制
|
||
```javascript
|
||
// 组件懒加载管理器
|
||
class LazyLoadManager {
|
||
constructor() {
|
||
this.loadedComponents = new Set();
|
||
this.observer = new IntersectionObserver(
|
||
this.handleIntersection.bind(this),
|
||
{ threshold: 0.1, rootMargin: '50px' }
|
||
);
|
||
}
|
||
|
||
register(element, componentName) {
|
||
element.dataset.component = componentName;
|
||
this.observer.observe(element);
|
||
}
|
||
|
||
async handleIntersection(entries) {
|
||
for (const entry of entries) {
|
||
if (entry.isIntersecting) {
|
||
const componentName = entry.target.dataset.component;
|
||
if (!this.loadedComponents.has(componentName)) {
|
||
await this.loadComponent(componentName);
|
||
this.loadedComponents.add(componentName);
|
||
}
|
||
this.observer.unobserve(entry.target);
|
||
}
|
||
}
|
||
}
|
||
|
||
async loadComponent(componentName) {
|
||
const module = await import(`/static/js/components/${componentName}.js`);
|
||
return module.default;
|
||
}
|
||
}
|
||
```
|
||
|
||
#### 2.1.2 高性能缓存系统
|
||
|
||
##### A. 多层缓存架构
|
||
```javascript
|
||
class AdvancedCacheSystem {
|
||
constructor() {
|
||
this.memoryCache = new Map(); // L1: 内存缓存
|
||
this.sessionCache = sessionStorage; // L2: 会话缓存
|
||
this.persistentCache = localStorage; // L3: 持久缓存
|
||
this.indexedDBCache = null; // L4: IndexedDB缓存
|
||
|
||
this.cacheStrategies = {
|
||
'health-data': { ttl: 60000, level: 'memory' },
|
||
'user-profile': { ttl: 300000, level: 'session' },
|
||
'system-config': { ttl: 3600000, level: 'persistent' },
|
||
'large-datasets': { ttl: 1800000, level: 'indexeddb' }
|
||
};
|
||
|
||
this.initIndexedDB();
|
||
}
|
||
|
||
async get(key, strategy = 'default') {
|
||
const config = this.cacheStrategies[strategy] || this.cacheStrategies['health-data'];
|
||
|
||
// L1: 内存缓存
|
||
if (config.level === 'memory' || config.level === 'all') {
|
||
const memoryData = this.memoryCache.get(key);
|
||
if (memoryData && !this.isExpired(memoryData)) {
|
||
return memoryData.data;
|
||
}
|
||
}
|
||
|
||
// L2: 会话缓存
|
||
if (config.level === 'session' || config.level === 'all') {
|
||
try {
|
||
const sessionData = JSON.parse(this.sessionCache.getItem(key));
|
||
if (sessionData && !this.isExpired(sessionData)) {
|
||
// 提升到内存缓存
|
||
this.memoryCache.set(key, sessionData);
|
||
return sessionData.data;
|
||
}
|
||
} catch (e) { /* 忽略解析错误 */ }
|
||
}
|
||
|
||
// L3: 持久缓存
|
||
if (config.level === 'persistent' || config.level === 'all') {
|
||
try {
|
||
const persistentData = JSON.parse(this.persistentCache.getItem(key));
|
||
if (persistentData && !this.isExpired(persistentData)) {
|
||
this.sessionCache.setItem(key, JSON.stringify(persistentData));
|
||
this.memoryCache.set(key, persistentData);
|
||
return persistentData.data;
|
||
}
|
||
} catch (e) { /* 忽略解析错误 */ }
|
||
}
|
||
|
||
// L4: IndexedDB缓存
|
||
if (config.level === 'indexeddb' && this.indexedDBCache) {
|
||
const indexedData = await this.getFromIndexedDB(key);
|
||
if (indexedData && !this.isExpired(indexedData)) {
|
||
return indexedData.data;
|
||
}
|
||
}
|
||
|
||
return null;
|
||
}
|
||
|
||
async set(key, data, strategy = 'default') {
|
||
const config = this.cacheStrategies[strategy] || this.cacheStrategies['health-data'];
|
||
const cacheItem = {
|
||
data,
|
||
timestamp: Date.now(),
|
||
expires: Date.now() + config.ttl
|
||
};
|
||
|
||
// 根据策略存储到对应缓存层
|
||
switch (config.level) {
|
||
case 'memory':
|
||
this.memoryCache.set(key, cacheItem);
|
||
break;
|
||
case 'session':
|
||
this.sessionCache.setItem(key, JSON.stringify(cacheItem));
|
||
break;
|
||
case 'persistent':
|
||
this.persistentCache.setItem(key, JSON.stringify(cacheItem));
|
||
break;
|
||
case 'indexeddb':
|
||
await this.setToIndexedDB(key, cacheItem);
|
||
break;
|
||
case 'all':
|
||
this.memoryCache.set(key, cacheItem);
|
||
this.sessionCache.setItem(key, JSON.stringify(cacheItem));
|
||
break;
|
||
}
|
||
}
|
||
|
||
isExpired(item) {
|
||
return Date.now() > item.expires;
|
||
}
|
||
|
||
async initIndexedDB() {
|
||
if ('indexedDB' in window) {
|
||
try {
|
||
this.indexedDBCache = await this.openIndexedDB('PersonalHealthCache', 1);
|
||
} catch (error) {
|
||
console.warn('IndexedDB初始化失败:', error);
|
||
}
|
||
}
|
||
}
|
||
|
||
openIndexedDB(name, version) {
|
||
return new Promise((resolve, reject) => {
|
||
const request = indexedDB.open(name, version);
|
||
|
||
request.onerror = () => reject(request.error);
|
||
request.onsuccess = () => resolve(request.result);
|
||
|
||
request.onupgradeneeded = (event) => {
|
||
const db = event.target.result;
|
||
if (!db.objectStoreNames.contains('cache')) {
|
||
db.createObjectStore('cache', { keyPath: 'key' });
|
||
}
|
||
};
|
||
});
|
||
}
|
||
}
|
||
|
||
// 全局缓存实例
|
||
window.CacheSystem = new AdvancedCacheSystem();
|
||
```
|
||
|
||
#### 2.1.3 智能数据更新策略
|
||
|
||
##### A. 差量更新系统
|
||
```javascript
|
||
class DeltaUpdateManager {
|
||
constructor() {
|
||
this.lastUpdateTimestamps = new Map();
|
||
this.dataDiffs = new Map();
|
||
this.updateQueue = [];
|
||
this.batchUpdateInterval = 500; // 500ms批量更新
|
||
|
||
this.startBatchProcessor();
|
||
}
|
||
|
||
async requestUpdate(dataType, params = {}) {
|
||
const lastUpdate = this.lastUpdateTimestamps.get(dataType) || 0;
|
||
const now = Date.now();
|
||
|
||
// 检查是否需要增量更新
|
||
if (now - lastUpdate > 60000) { // 1分钟后进行增量更新
|
||
params.since = lastUpdate;
|
||
params.delta = true;
|
||
}
|
||
|
||
try {
|
||
const response = await ApiClient.get(`/api/v1/${dataType}/updates`, params);
|
||
|
||
if (response.delta && response.changes) {
|
||
// 处理增量数据
|
||
await this.applyDeltaChanges(dataType, response.changes);
|
||
} else {
|
||
// 处理全量数据
|
||
await this.applyFullUpdate(dataType, response.data);
|
||
}
|
||
|
||
this.lastUpdateTimestamps.set(dataType, now);
|
||
|
||
} catch (error) {
|
||
console.error(`数据更新失败 ${dataType}:`, error);
|
||
throw error;
|
||
}
|
||
}
|
||
|
||
async applyDeltaChanges(dataType, changes) {
|
||
const currentData = await CacheSystem.get(`${dataType}-current`, dataType);
|
||
|
||
if (!currentData) {
|
||
// 如果没有当前数据,请求全量数据
|
||
return this.requestFullUpdate(dataType);
|
||
}
|
||
|
||
// 应用增量变更
|
||
const updatedData = this.mergeDeltaChanges(currentData, changes);
|
||
|
||
// 更新缓存
|
||
await CacheSystem.set(`${dataType}-current`, updatedData, dataType);
|
||
|
||
// 通知组件更新
|
||
EventBus.emit(`data-updated:${dataType}`, { data: updatedData, delta: true });
|
||
}
|
||
|
||
mergeDeltaChanges(currentData, changes) {
|
||
const result = { ...currentData };
|
||
|
||
// 处理增加和更新
|
||
if (changes.upserts) {
|
||
changes.upserts.forEach(item => {
|
||
if (Array.isArray(result)) {
|
||
const index = result.findIndex(existing => existing.id === item.id);
|
||
if (index >= 0) {
|
||
result[index] = { ...result[index], ...item };
|
||
} else {
|
||
result.push(item);
|
||
}
|
||
} else if (typeof result === 'object') {
|
||
result[item.id] = { ...result[item.id], ...item };
|
||
}
|
||
});
|
||
}
|
||
|
||
// 处理删除
|
||
if (changes.deletes) {
|
||
changes.deletes.forEach(id => {
|
||
if (Array.isArray(result)) {
|
||
const index = result.findIndex(item => item.id === id);
|
||
if (index >= 0) {
|
||
result.splice(index, 1);
|
||
}
|
||
} else if (typeof result === 'object') {
|
||
delete result[id];
|
||
}
|
||
});
|
||
}
|
||
|
||
return result;
|
||
}
|
||
|
||
startBatchProcessor() {
|
||
setInterval(() => {
|
||
if (this.updateQueue.length > 0) {
|
||
this.processBatchUpdates();
|
||
}
|
||
}, this.batchUpdateInterval);
|
||
}
|
||
|
||
async processBatchUpdates() {
|
||
const batch = this.updateQueue.splice(0);
|
||
const groupedUpdates = this.groupUpdatesByType(batch);
|
||
|
||
for (const [dataType, updates] of groupedUpdates) {
|
||
try {
|
||
await this.requestUpdate(dataType, { batch: updates });
|
||
} catch (error) {
|
||
console.error(`批量更新失败 ${dataType}:`, error);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
#### 2.1.4 内存管理优化
|
||
|
||
##### A. 内存泄漏防护系统
|
||
```javascript
|
||
class MemoryLeakGuard {
|
||
constructor() {
|
||
this.timers = new Set();
|
||
this.eventListeners = new Map();
|
||
this.observers = new Set();
|
||
this.activeRequests = new Map();
|
||
|
||
this.setupCleanupTriggers();
|
||
}
|
||
|
||
// 安全的定时器管理
|
||
setInterval(callback, interval, id = null) {
|
||
const timerId = setInterval(callback, interval);
|
||
this.timers.add(timerId);
|
||
|
||
if (id) {
|
||
this.namedTimers = this.namedTimers || new Map();
|
||
this.namedTimers.set(id, timerId);
|
||
}
|
||
|
||
return timerId;
|
||
}
|
||
|
||
clearInterval(timerId) {
|
||
clearInterval(timerId);
|
||
this.timers.delete(timerId);
|
||
}
|
||
|
||
clearNamedTimer(id) {
|
||
if (this.namedTimers && this.namedTimers.has(id)) {
|
||
const timerId = this.namedTimers.get(id);
|
||
this.clearInterval(timerId);
|
||
this.namedTimers.delete(id);
|
||
}
|
||
}
|
||
|
||
// 安全的事件监听器管理
|
||
addEventListener(element, event, callback, options = {}) {
|
||
element.addEventListener(event, callback, options);
|
||
|
||
if (!this.eventListeners.has(element)) {
|
||
this.eventListeners.set(element, []);
|
||
}
|
||
|
||
this.eventListeners.get(element).push({ event, callback, options });
|
||
}
|
||
|
||
removeEventListener(element, event, callback) {
|
||
element.removeEventListener(event, callback);
|
||
|
||
if (this.eventListeners.has(element)) {
|
||
const listeners = this.eventListeners.get(element);
|
||
const index = listeners.findIndex(l => l.event === event && l.callback === callback);
|
||
if (index >= 0) {
|
||
listeners.splice(index, 1);
|
||
}
|
||
}
|
||
}
|
||
|
||
// 观察器管理
|
||
registerObserver(observer) {
|
||
this.observers.add(observer);
|
||
return observer;
|
||
}
|
||
|
||
unregisterObserver(observer) {
|
||
if (this.observers.has(observer)) {
|
||
observer.disconnect();
|
||
this.observers.delete(observer);
|
||
}
|
||
}
|
||
|
||
// 请求管理
|
||
registerRequest(requestId, controller) {
|
||
this.activeRequests.set(requestId, controller);
|
||
}
|
||
|
||
cancelRequest(requestId) {
|
||
if (this.activeRequests.has(requestId)) {
|
||
const controller = this.activeRequests.get(requestId);
|
||
controller.abort();
|
||
this.activeRequests.delete(requestId);
|
||
}
|
||
}
|
||
|
||
// 清理所有资源
|
||
cleanup() {
|
||
// 清理定时器
|
||
this.timers.forEach(timerId => clearInterval(timerId));
|
||
this.timers.clear();
|
||
|
||
if (this.namedTimers) {
|
||
this.namedTimers.forEach((timerId, name) => clearInterval(timerId));
|
||
this.namedTimers.clear();
|
||
}
|
||
|
||
// 清理事件监听器
|
||
this.eventListeners.forEach((listeners, element) => {
|
||
listeners.forEach(({ event, callback }) => {
|
||
element.removeEventListener(event, callback);
|
||
});
|
||
});
|
||
this.eventListeners.clear();
|
||
|
||
// 清理观察器
|
||
this.observers.forEach(observer => observer.disconnect());
|
||
this.observers.clear();
|
||
|
||
// 取消活动请求
|
||
this.activeRequests.forEach((controller, id) => controller.abort());
|
||
this.activeRequests.clear();
|
||
}
|
||
|
||
setupCleanupTriggers() {
|
||
// 页面卸载时清理
|
||
window.addEventListener('beforeunload', () => this.cleanup());
|
||
|
||
// 页面隐藏时清理定时器
|
||
document.addEventListener('visibilitychange', () => {
|
||
if (document.hidden) {
|
||
this.pauseTimers();
|
||
} else {
|
||
this.resumeTimers();
|
||
}
|
||
});
|
||
}
|
||
|
||
pauseTimers() {
|
||
this.pausedTimers = new Map();
|
||
this.namedTimers?.forEach((timerId, name) => {
|
||
clearInterval(timerId);
|
||
this.pausedTimers.set(name, true);
|
||
});
|
||
}
|
||
|
||
resumeTimers() {
|
||
if (this.pausedTimers) {
|
||
this.pausedTimers.forEach((_, name) => {
|
||
// 重新启动已暂停的定时器
|
||
EventBus.emit(`timer-resume:${name}`);
|
||
});
|
||
this.pausedTimers.clear();
|
||
}
|
||
}
|
||
}
|
||
|
||
// 全局内存管理器
|
||
window.MemoryGuard = new MemoryLeakGuard();
|
||
```
|
||
|
||
### 2.2 UI/UX 优化方案
|
||
|
||
#### 2.2.1 响应式设计升级
|
||
|
||
##### A. 现代响应式布局系统
|
||
```css
|
||
/* 基于CSS Grid和Flexbox的现代布局 */
|
||
.personal-dashboard {
|
||
display: grid;
|
||
grid-template-areas:
|
||
"header header header"
|
||
"sidebar main aside"
|
||
"footer footer footer";
|
||
grid-template-columns: 280px 1fr 320px;
|
||
grid-template-rows: auto 1fr auto;
|
||
min-height: 100vh;
|
||
gap: var(--spacing-md);
|
||
|
||
/* 响应式断点 */
|
||
@media (max-width: 1200px) {
|
||
grid-template-areas:
|
||
"header header"
|
||
"main aside"
|
||
"footer footer";
|
||
grid-template-columns: 1fr 280px;
|
||
}
|
||
|
||
@media (max-width: 768px) {
|
||
grid-template-areas:
|
||
"header"
|
||
"main"
|
||
"aside"
|
||
"footer";
|
||
grid-template-columns: 1fr;
|
||
gap: var(--spacing-sm);
|
||
}
|
||
}
|
||
|
||
/* 自适应卡片布局 */
|
||
.metrics-grid {
|
||
display: grid;
|
||
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
|
||
gap: var(--spacing-lg);
|
||
|
||
@media (max-width: 640px) {
|
||
grid-template-columns: 1fr;
|
||
gap: var(--spacing-md);
|
||
}
|
||
}
|
||
|
||
/* 流式图表容器 */
|
||
.chart-container {
|
||
container-type: inline-size;
|
||
min-height: 300px;
|
||
|
||
@container (max-width: 400px) {
|
||
.chart-title {
|
||
font-size: var(--font-sm);
|
||
}
|
||
|
||
.chart-controls {
|
||
flex-direction: column;
|
||
gap: var(--spacing-sm);
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
##### B. 智能主题系统
|
||
```javascript
|
||
class AdaptiveThemeManager {
|
||
constructor() {
|
||
this.themes = {
|
||
auto: 'auto',
|
||
light: 'light',
|
||
dark: 'dark',
|
||
highContrast: 'high-contrast',
|
||
colorBlind: 'color-blind-friendly'
|
||
};
|
||
|
||
this.currentTheme = 'auto';
|
||
this.systemPreferences = {
|
||
colorScheme: 'dark',
|
||
contrast: 'normal',
|
||
reducedMotion: false
|
||
};
|
||
|
||
this.init();
|
||
}
|
||
|
||
init() {
|
||
this.detectSystemPreferences();
|
||
this.loadUserPreference();
|
||
this.applyTheme();
|
||
this.bindEvents();
|
||
}
|
||
|
||
detectSystemPreferences() {
|
||
// 检测系统颜色方案
|
||
if (window.matchMedia('(prefers-color-scheme: dark)').matches) {
|
||
this.systemPreferences.colorScheme = 'dark';
|
||
} else if (window.matchMedia('(prefers-color-scheme: light)').matches) {
|
||
this.systemPreferences.colorScheme = 'light';
|
||
}
|
||
|
||
// 检测对比度偏好
|
||
if (window.matchMedia('(prefers-contrast: high)').matches) {
|
||
this.systemPreferences.contrast = 'high';
|
||
}
|
||
|
||
// 检测动画偏好
|
||
if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) {
|
||
this.systemPreferences.reducedMotion = true;
|
||
}
|
||
}
|
||
|
||
applyTheme(themeName = this.currentTheme) {
|
||
const root = document.documentElement;
|
||
|
||
// 移除现有主题类
|
||
root.classList.remove(...Object.values(this.themes).filter(t => t !== 'auto'));
|
||
|
||
let activeTheme = themeName;
|
||
|
||
if (themeName === 'auto') {
|
||
activeTheme = this.systemPreferences.colorScheme;
|
||
}
|
||
|
||
// 应用主题
|
||
root.classList.add(`theme-${activeTheme}`);
|
||
|
||
// 应用可访问性设置
|
||
if (this.systemPreferences.contrast === 'high') {
|
||
root.classList.add('high-contrast');
|
||
}
|
||
|
||
if (this.systemPreferences.reducedMotion) {
|
||
root.classList.add('reduced-motion');
|
||
}
|
||
|
||
// 更新CSS自定义属性
|
||
this.updateCSSVariables(activeTheme);
|
||
|
||
// 触发主题变更事件
|
||
EventBus.emit('theme-changed', {
|
||
theme: activeTheme,
|
||
original: themeName,
|
||
preferences: this.systemPreferences
|
||
});
|
||
}
|
||
|
||
updateCSSVariables(theme) {
|
||
const themeConfig = this.getThemeConfig(theme);
|
||
const root = document.documentElement;
|
||
|
||
Object.entries(themeConfig).forEach(([property, value]) => {
|
||
root.style.setProperty(`--${property}`, value);
|
||
});
|
||
}
|
||
|
||
getThemeConfig(theme) {
|
||
const configs = {
|
||
light: {
|
||
'bg-primary': '#ffffff',
|
||
'bg-secondary': '#f8f9fa',
|
||
'text-primary': '#212529',
|
||
'text-secondary': '#6c757d',
|
||
'border-primary': '#dee2e6',
|
||
'shadow-primary': '0 4px 6px rgba(0, 0, 0, 0.1)'
|
||
},
|
||
dark: {
|
||
'bg-primary': '#0a1525',
|
||
'bg-secondary': '#1a2332',
|
||
'text-primary': '#ffffff',
|
||
'text-secondary': 'rgba(255, 255, 255, 0.8)',
|
||
'border-primary': 'rgba(255, 255, 255, 0.2)',
|
||
'shadow-primary': '0 4px 6px rgba(0, 0, 0, 0.3)'
|
||
},
|
||
'high-contrast': {
|
||
'bg-primary': '#000000',
|
||
'bg-secondary': '#1a1a1a',
|
||
'text-primary': '#ffffff',
|
||
'text-secondary': '#e0e0e0',
|
||
'border-primary': '#ffffff',
|
||
'shadow-primary': '0 4px 6px rgba(255, 255, 255, 0.2)'
|
||
}
|
||
};
|
||
|
||
return configs[theme] || configs.dark;
|
||
}
|
||
|
||
bindEvents() {
|
||
// 监听系统主题变化
|
||
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', (e) => {
|
||
this.systemPreferences.colorScheme = e.matches ? 'dark' : 'light';
|
||
if (this.currentTheme === 'auto') {
|
||
this.applyTheme();
|
||
}
|
||
});
|
||
|
||
// 监听对比度变化
|
||
window.matchMedia('(prefers-contrast: high)').addEventListener('change', (e) => {
|
||
this.systemPreferences.contrast = e.matches ? 'high' : 'normal';
|
||
this.applyTheme();
|
||
});
|
||
|
||
// 监听动画偏好变化
|
||
window.matchMedia('(prefers-reduced-motion: reduce)').addEventListener('change', (e) => {
|
||
this.systemPreferences.reducedMotion = e.matches;
|
||
this.applyTheme();
|
||
});
|
||
}
|
||
|
||
setTheme(themeName) {
|
||
if (this.themes[themeName]) {
|
||
this.currentTheme = themeName;
|
||
this.saveUserPreference();
|
||
this.applyTheme();
|
||
}
|
||
}
|
||
|
||
saveUserPreference() {
|
||
localStorage.setItem('user-theme-preference', this.currentTheme);
|
||
}
|
||
|
||
loadUserPreference() {
|
||
const saved = localStorage.getItem('user-theme-preference');
|
||
if (saved && this.themes[saved]) {
|
||
this.currentTheme = saved;
|
||
}
|
||
}
|
||
}
|
||
|
||
// 全局主题管理器
|
||
window.ThemeManager = new AdaptiveThemeManager();
|
||
```
|
||
|
||
#### 2.2.2 交互体验优化
|
||
|
||
##### A. 智能加载状态管理
|
||
```javascript
|
||
class LoadingStateManager {
|
||
constructor() {
|
||
this.loadingStates = new Map();
|
||
this.globalLoadingCount = 0;
|
||
this.skeleton = new SkeletonLoader();
|
||
}
|
||
|
||
startLoading(id, options = {}) {
|
||
const config = {
|
||
showGlobalIndicator: true,
|
||
showSkeleton: false,
|
||
timeout: 30000,
|
||
retryable: true,
|
||
...options
|
||
};
|
||
|
||
// 记录加载状态
|
||
this.loadingStates.set(id, {
|
||
startTime: Date.now(),
|
||
config,
|
||
timeoutId: setTimeout(() => this.handleTimeout(id), config.timeout)
|
||
});
|
||
|
||
// 更新全局加载状态
|
||
if (config.showGlobalIndicator) {
|
||
this.globalLoadingCount++;
|
||
this.updateGlobalIndicator();
|
||
}
|
||
|
||
// 显示骨架屏
|
||
if (config.showSkeleton && config.container) {
|
||
this.skeleton.show(config.container, config.skeletonType);
|
||
}
|
||
|
||
// 显示本地加载指示器
|
||
if (config.localIndicator) {
|
||
this.showLocalIndicator(id, config.localIndicator);
|
||
}
|
||
|
||
EventBus.emit('loading-start', { id, config });
|
||
}
|
||
|
||
finishLoading(id, success = true, result = null) {
|
||
const state = this.loadingStates.get(id);
|
||
if (!state) return;
|
||
|
||
// 清理超时定时器
|
||
clearTimeout(state.timeoutId);
|
||
|
||
// 计算加载时间
|
||
const duration = Date.now() - state.startTime;
|
||
|
||
// 更新全局加载状态
|
||
if (state.config.showGlobalIndicator) {
|
||
this.globalLoadingCount = Math.max(0, this.globalLoadingCount - 1);
|
||
this.updateGlobalIndicator();
|
||
}
|
||
|
||
// 隐藏骨架屏
|
||
if (state.config.showSkeleton && state.config.container) {
|
||
this.skeleton.hide(state.config.container);
|
||
}
|
||
|
||
// 隐藏本地加载指示器
|
||
if (state.config.localIndicator) {
|
||
this.hideLocalIndicator(id);
|
||
}
|
||
|
||
// 移除加载状态
|
||
this.loadingStates.delete(id);
|
||
|
||
EventBus.emit('loading-finish', {
|
||
id,
|
||
success,
|
||
duration,
|
||
result,
|
||
config: state.config
|
||
});
|
||
|
||
// 显示结果提示
|
||
if (success) {
|
||
this.showSuccessIndicator(id, duration);
|
||
} else {
|
||
this.showErrorIndicator(id, result);
|
||
}
|
||
}
|
||
|
||
handleTimeout(id) {
|
||
const state = this.loadingStates.get(id);
|
||
if (!state) return;
|
||
|
||
EventBus.emit('loading-timeout', { id, config: state.config });
|
||
|
||
if (state.config.retryable) {
|
||
this.showRetryOption(id);
|
||
} else {
|
||
this.finishLoading(id, false, new Error('请求超时'));
|
||
}
|
||
}
|
||
|
||
updateGlobalIndicator() {
|
||
const indicator = document.getElementById('global-loading-indicator');
|
||
if (!indicator) return;
|
||
|
||
if (this.globalLoadingCount > 0) {
|
||
indicator.classList.add('active');
|
||
indicator.querySelector('.loading-count').textContent = this.globalLoadingCount;
|
||
} else {
|
||
indicator.classList.remove('active');
|
||
}
|
||
}
|
||
|
||
showLocalIndicator(id, container) {
|
||
const element = typeof container === 'string' ?
|
||
document.querySelector(container) : container;
|
||
|
||
if (element) {
|
||
element.classList.add('loading');
|
||
element.setAttribute('data-loading-id', id);
|
||
}
|
||
}
|
||
|
||
hideLocalIndicator(id) {
|
||
const element = document.querySelector(`[data-loading-id="${id}"]`);
|
||
if (element) {
|
||
element.classList.remove('loading');
|
||
element.removeAttribute('data-loading-id');
|
||
}
|
||
}
|
||
|
||
showSuccessIndicator(id, duration) {
|
||
if (duration < 1000) return; // 快速操作不显示成功提示
|
||
|
||
const message = duration < 3000 ? '加载完成' : `加载完成 (${Math.round(duration/1000)}s)`;
|
||
this.showToast(message, 'success', 2000);
|
||
}
|
||
|
||
showErrorIndicator(id, error) {
|
||
const message = error.message || '加载失败';
|
||
this.showToast(message, 'error', 5000);
|
||
}
|
||
|
||
showRetryOption(id) {
|
||
const state = this.loadingStates.get(id);
|
||
if (!state) return;
|
||
|
||
this.showToast('加载超时,是否重试?', 'warning', 0, [
|
||
{
|
||
text: '重试',
|
||
action: () => EventBus.emit('loading-retry', { id })
|
||
},
|
||
{
|
||
text: '取消',
|
||
action: () => this.finishLoading(id, false, new Error('用户取消'))
|
||
}
|
||
]);
|
||
}
|
||
|
||
showToast(message, type = 'info', duration = 3000, actions = []) {
|
||
// 简化的Toast实现
|
||
const toast = document.createElement('div');
|
||
toast.className = `toast toast-${type}`;
|
||
toast.innerHTML = `
|
||
<div class="toast-content">
|
||
<span class="toast-message">${message}</span>
|
||
${actions.length > 0 ? `
|
||
<div class="toast-actions">
|
||
${actions.map((action, index) =>
|
||
`<button class="toast-action" data-action="${index}">${action.text}</button>`
|
||
).join('')}
|
||
</div>
|
||
` : ''}
|
||
</div>
|
||
`;
|
||
|
||
// 绑定动作事件
|
||
actions.forEach((action, index) => {
|
||
toast.querySelector(`[data-action="${index}"]`).onclick = () => {
|
||
action.action();
|
||
document.body.removeChild(toast);
|
||
};
|
||
});
|
||
|
||
document.body.appendChild(toast);
|
||
|
||
if (duration > 0) {
|
||
setTimeout(() => {
|
||
if (document.body.contains(toast)) {
|
||
document.body.removeChild(toast);
|
||
}
|
||
}, duration);
|
||
}
|
||
}
|
||
}
|
||
|
||
// 骨架屏加载器
|
||
class SkeletonLoader {
|
||
constructor() {
|
||
this.templates = {
|
||
card: this.createCardSkeleton,
|
||
chart: this.createChartSkeleton,
|
||
list: this.createListSkeleton,
|
||
table: this.createTableSkeleton
|
||
};
|
||
}
|
||
|
||
show(container, type = 'card') {
|
||
const element = typeof container === 'string' ?
|
||
document.querySelector(container) : container;
|
||
|
||
if (!element) return;
|
||
|
||
const skeleton = this.templates[type] ?
|
||
this.templates[type]() :
|
||
this.templates.card();
|
||
|
||
skeleton.className = 'skeleton-loader';
|
||
element.appendChild(skeleton);
|
||
}
|
||
|
||
hide(container) {
|
||
const element = typeof container === 'string' ?
|
||
document.querySelector(container) : container;
|
||
|
||
if (element) {
|
||
const skeleton = element.querySelector('.skeleton-loader');
|
||
if (skeleton) {
|
||
element.removeChild(skeleton);
|
||
}
|
||
}
|
||
}
|
||
|
||
createCardSkeleton() {
|
||
const skeleton = document.createElement('div');
|
||
skeleton.innerHTML = `
|
||
<div class="skeleton-header">
|
||
<div class="skeleton-rect" style="width: 60%; height: 20px;"></div>
|
||
<div class="skeleton-rect" style="width: 24px; height: 24px; border-radius: 50%;"></div>
|
||
</div>
|
||
<div class="skeleton-body">
|
||
<div class="skeleton-rect" style="width: 100%; height: 100px; margin: 16px 0;"></div>
|
||
<div class="skeleton-rect" style="width: 80%; height: 16px;"></div>
|
||
<div class="skeleton-rect" style="width: 60%; height: 16px; margin-top: 8px;"></div>
|
||
</div>
|
||
`;
|
||
return skeleton;
|
||
}
|
||
|
||
createChartSkeleton() {
|
||
const skeleton = document.createElement('div');
|
||
skeleton.innerHTML = `
|
||
<div class="skeleton-header">
|
||
<div class="skeleton-rect" style="width: 40%; height: 20px;"></div>
|
||
<div class="skeleton-rect" style="width: 80px; height: 32px; border-radius: 16px;"></div>
|
||
</div>
|
||
<div class="skeleton-chart">
|
||
<div class="skeleton-rect" style="width: 100%; height: 200px; border-radius: 8px;"></div>
|
||
</div>
|
||
<div class="skeleton-legend">
|
||
<div class="skeleton-rect" style="width: 100px; height: 16px; margin: 8px;"></div>
|
||
<div class="skeleton-rect" style="width: 80px; height: 16px; margin: 8px;"></div>
|
||
<div class="skeleton-rect" style="width: 120px; height: 16px; margin: 8px;"></div>
|
||
</div>
|
||
`;
|
||
return skeleton;
|
||
}
|
||
|
||
createListSkeleton() {
|
||
const skeleton = document.createElement('div');
|
||
const items = Array.from({ length: 5 }, () => `
|
||
<div class="skeleton-list-item">
|
||
<div class="skeleton-rect" style="width: 40px; height: 40px; border-radius: 50%;"></div>
|
||
<div class="skeleton-list-content">
|
||
<div class="skeleton-rect" style="width: 70%; height: 16px;"></div>
|
||
<div class="skeleton-rect" style="width: 50%; height: 14px; margin-top: 8px;"></div>
|
||
</div>
|
||
<div class="skeleton-rect" style="width: 60px; height: 20px;"></div>
|
||
</div>
|
||
`).join('');
|
||
|
||
skeleton.innerHTML = items;
|
||
return skeleton;
|
||
}
|
||
|
||
createTableSkeleton() {
|
||
const skeleton = document.createElement('div');
|
||
const rows = Array.from({ length: 6 }, (_, index) => {
|
||
if (index === 0) {
|
||
// 表头
|
||
return `
|
||
<div class="skeleton-table-row skeleton-table-header">
|
||
<div class="skeleton-rect" style="width: 100px; height: 16px;"></div>
|
||
<div class="skeleton-rect" style="width: 120px; height: 16px;"></div>
|
||
<div class="skeleton-rect" style="width: 80px; height: 16px;"></div>
|
||
<div class="skeleton-rect" style="width: 60px; height: 16px;"></div>
|
||
</div>
|
||
`;
|
||
} else {
|
||
// 数据行
|
||
return `
|
||
<div class="skeleton-table-row">
|
||
<div class="skeleton-rect" style="width: 90px; height: 14px;"></div>
|
||
<div class="skeleton-rect" style="width: 110px; height: 14px;"></div>
|
||
<div class="skeleton-rect" style="width: 70px; height: 14px;"></div>
|
||
<div class="skeleton-rect" style="width: 50px; height: 14px;"></div>
|
||
</div>
|
||
`;
|
||
}
|
||
}).join('');
|
||
|
||
skeleton.innerHTML = `<div class="skeleton-table">${rows}</div>`;
|
||
return skeleton;
|
||
}
|
||
}
|
||
|
||
// 全局加载状态管理器
|
||
window.LoadingManager = new LoadingStateManager();
|
||
```
|
||
|
||
##### B. 智能错误处理与恢复系统
|
||
```javascript
|
||
class ErrorRecoverySystem {
|
||
constructor() {
|
||
this.errorHistory = [];
|
||
this.recoveryStrategies = new Map();
|
||
this.maxRetries = 3;
|
||
this.retryDelay = [1000, 2000, 4000]; // 递增延迟
|
||
|
||
this.registerDefaultStrategies();
|
||
this.bindGlobalErrorHandlers();
|
||
}
|
||
|
||
registerDefaultStrategies() {
|
||
// 网络错误恢复策略
|
||
this.recoveryStrategies.set('NetworkError', {
|
||
shouldRetry: (error, attempt) => attempt < this.maxRetries,
|
||
getDelay: (attempt) => this.retryDelay[Math.min(attempt - 1, this.retryDelay.length - 1)],
|
||
recover: async (context) => {
|
||
// 检查网络连接
|
||
if (!navigator.onLine) {
|
||
await this.waitForOnline();
|
||
}
|
||
return context.retry();
|
||
}
|
||
});
|
||
|
||
// API错误恢复策略
|
||
this.recoveryStrategies.set('ApiError', {
|
||
shouldRetry: (error, attempt) => {
|
||
// 5xx错误重试,4xx错误不重试
|
||
return error.status >= 500 && attempt < this.maxRetries;
|
||
},
|
||
getDelay: (attempt) => this.retryDelay[Math.min(attempt - 1, this.retryDelay.length - 1)],
|
||
recover: async (context) => {
|
||
if (context.error.status === 401) {
|
||
// 尝试刷新认证
|
||
await this.refreshAuth();
|
||
}
|
||
return context.retry();
|
||
}
|
||
});
|
||
|
||
// 解析错误恢复策略
|
||
this.recoveryStrategies.set('ParseError', {
|
||
shouldRetry: (error, attempt) => attempt < 2,
|
||
getDelay: (attempt) => 1000,
|
||
recover: async (context) => {
|
||
// 清理可能损坏的缓存
|
||
await CacheSystem.clear();
|
||
return context.retry();
|
||
}
|
||
});
|
||
|
||
// 组件错误恢复策略
|
||
this.recoveryStrategies.set('ComponentError', {
|
||
shouldRetry: (error, attempt) => attempt < 2,
|
||
getDelay: (attempt) => 500,
|
||
recover: async (context) => {
|
||
// 重新初始化组件
|
||
return context.reinitialize();
|
||
}
|
||
});
|
||
}
|
||
|
||
async handleError(error, context = {}) {
|
||
// 记录错误
|
||
const errorRecord = {
|
||
error,
|
||
context,
|
||
timestamp: Date.now(),
|
||
userAgent: navigator.userAgent,
|
||
url: window.location.href,
|
||
stack: error.stack
|
||
};
|
||
|
||
this.errorHistory.push(errorRecord);
|
||
|
||
// 限制错误历史长度
|
||
if (this.errorHistory.length > 100) {
|
||
this.errorHistory.shift();
|
||
}
|
||
|
||
// 确定错误类型
|
||
const errorType = this.classifyError(error);
|
||
|
||
// 获取恢复策略
|
||
const strategy = this.recoveryStrategies.get(errorType);
|
||
|
||
if (strategy && strategy.shouldRetry(error, context.attempt || 1)) {
|
||
return this.attemptRecovery(error, context, strategy);
|
||
} else {
|
||
return this.handleUnrecoverableError(error, context);
|
||
}
|
||
}
|
||
|
||
async attemptRecovery(error, context, strategy) {
|
||
const attempt = (context.attempt || 0) + 1;
|
||
const delay = strategy.getDelay(attempt);
|
||
|
||
console.warn(`尝试恢复错误 (第${attempt}次):`, error.message);
|
||
|
||
// 显示恢复提示
|
||
LoadingManager.showToast(
|
||
`连接异常,正在重试 (${attempt}/${this.maxRetries})...`,
|
||
'warning',
|
||
delay
|
||
);
|
||
|
||
// 等待延迟
|
||
await new Promise(resolve => setTimeout(resolve, delay));
|
||
|
||
try {
|
||
// 执行恢复策略
|
||
const recoveryContext = {
|
||
error,
|
||
attempt,
|
||
retry: context.retry,
|
||
reinitialize: context.reinitialize,
|
||
cancel: context.cancel
|
||
};
|
||
|
||
const result = await strategy.recover(recoveryContext);
|
||
|
||
// 恢复成功
|
||
console.log(`错误恢复成功 (第${attempt}次):`, error.message);
|
||
LoadingManager.showToast('连接已恢复', 'success', 2000);
|
||
|
||
return result;
|
||
|
||
} catch (recoveryError) {
|
||
// 恢复失败,更新上下文并递归重试
|
||
const newContext = { ...context, attempt };
|
||
return this.handleError(recoveryError, newContext);
|
||
}
|
||
}
|
||
|
||
handleUnrecoverableError(error, context) {
|
||
console.error('无法恢复的错误:', error);
|
||
|
||
// 显示用户友好的错误信息
|
||
const userMessage = this.getUserFriendlyMessage(error);
|
||
|
||
LoadingManager.showToast(userMessage, 'error', 0, [
|
||
{
|
||
text: '重新加载',
|
||
action: () => window.location.reload()
|
||
},
|
||
{
|
||
text: '反馈问题',
|
||
action: () => this.reportError(error, context)
|
||
}
|
||
]);
|
||
|
||
// 触发错误事件
|
||
EventBus.emit('unrecoverable-error', { error, context });
|
||
|
||
return Promise.reject(error);
|
||
}
|
||
|
||
classifyError(error) {
|
||
if (error.name === 'TypeError' && error.message.includes('fetch')) {
|
||
return 'NetworkError';
|
||
}
|
||
|
||
if (error.name === 'ApiError' || error.status) {
|
||
return 'ApiError';
|
||
}
|
||
|
||
if (error.name === 'SyntaxError' || error.message.includes('JSON')) {
|
||
return 'ParseError';
|
||
}
|
||
|
||
if (error.componentName) {
|
||
return 'ComponentError';
|
||
}
|
||
|
||
return 'UnknownError';
|
||
}
|
||
|
||
getUserFriendlyMessage(error) {
|
||
const messages = {
|
||
'NetworkError': '网络连接异常,请检查您的网络设置',
|
||
'ApiError': '服务暂时不可用,请稍后再试',
|
||
'ParseError': '数据格式异常,请刷新页面重试',
|
||
'ComponentError': '页面组件异常,请刷新页面',
|
||
'UnknownError': '系统异常,请刷新页面或联系管理员'
|
||
};
|
||
|
||
const errorType = this.classifyError(error);
|
||
return messages[errorType] || messages['UnknownError'];
|
||
}
|
||
|
||
async waitForOnline() {
|
||
return new Promise(resolve => {
|
||
if (navigator.onLine) {
|
||
resolve();
|
||
} else {
|
||
const handler = () => {
|
||
window.removeEventListener('online', handler);
|
||
resolve();
|
||
};
|
||
window.addEventListener('online', handler);
|
||
}
|
||
});
|
||
}
|
||
|
||
async refreshAuth() {
|
||
try {
|
||
// 实现认证刷新逻辑
|
||
const response = await fetch('/api/auth/refresh', {
|
||
method: 'POST',
|
||
credentials: 'include'
|
||
});
|
||
|
||
if (!response.ok) {
|
||
throw new Error('认证刷新失败');
|
||
}
|
||
|
||
return await response.json();
|
||
} catch (error) {
|
||
// 刷新失败,引导用户重新登录
|
||
window.location.href = '/login';
|
||
throw error;
|
||
}
|
||
}
|
||
|
||
reportError(error, context) {
|
||
// 收集错误信息用于上报
|
||
const report = {
|
||
error: {
|
||
name: error.name,
|
||
message: error.message,
|
||
stack: error.stack
|
||
},
|
||
context,
|
||
timestamp: Date.now(),
|
||
userAgent: navigator.userAgent,
|
||
url: window.location.href,
|
||
userId: window.APP_CONFIG?.userId,
|
||
sessionId: window.APP_CONFIG?.sessionId
|
||
};
|
||
|
||
// 发送错误报告
|
||
fetch('/api/errors/report', {
|
||
method: 'POST',
|
||
headers: {
|
||
'Content-Type': 'application/json'
|
||
},
|
||
body: JSON.stringify(report)
|
||
}).catch(reportError => {
|
||
console.warn('错误报告发送失败:', reportError);
|
||
});
|
||
}
|
||
|
||
bindGlobalErrorHandlers() {
|
||
// 全局JavaScript错误处理
|
||
window.addEventListener('error', (event) => {
|
||
this.handleError(event.error, {
|
||
type: 'global-error',
|
||
filename: event.filename,
|
||
lineno: event.lineno,
|
||
colno: event.colno
|
||
});
|
||
});
|
||
|
||
// Promise拒绝处理
|
||
window.addEventListener('unhandledrejection', (event) => {
|
||
this.handleError(event.reason, {
|
||
type: 'unhandled-rejection'
|
||
});
|
||
});
|
||
|
||
// 资源加载错误处理
|
||
window.addEventListener('error', (event) => {
|
||
if (event.target !== window) {
|
||
this.handleError(new Error(`资源加载失败: ${event.target.src || event.target.href}`), {
|
||
type: 'resource-error',
|
||
target: event.target
|
||
});
|
||
}
|
||
}, true);
|
||
}
|
||
|
||
getErrorStatistics() {
|
||
const stats = {
|
||
total: this.errorHistory.length,
|
||
byType: {},
|
||
byTimeRange: {
|
||
lastHour: 0,
|
||
lastDay: 0,
|
||
lastWeek: 0
|
||
}
|
||
};
|
||
|
||
const now = Date.now();
|
||
const oneHour = 60 * 60 * 1000;
|
||
const oneDay = 24 * oneHour;
|
||
const oneWeek = 7 * oneDay;
|
||
|
||
this.errorHistory.forEach(record => {
|
||
const errorType = this.classifyError(record.error);
|
||
stats.byType[errorType] = (stats.byType[errorType] || 0) + 1;
|
||
|
||
const age = now - record.timestamp;
|
||
if (age < oneHour) stats.byTimeRange.lastHour++;
|
||
if (age < oneDay) stats.byTimeRange.lastDay++;
|
||
if (age < oneWeek) stats.byTimeRange.lastWeek++;
|
||
});
|
||
|
||
return stats;
|
||
}
|
||
}
|
||
|
||
// 全局错误恢复系统
|
||
window.ErrorRecovery = new ErrorRecoverySystem();
|
||
```
|
||
|
||
### 2.3 运维优化方案
|
||
|
||
#### 2.3.1 智能监控系统
|
||
|
||
##### A. 实时性能监控
|
||
```javascript
|
||
class PerformanceMonitor {
|
||
constructor() {
|
||
this.metrics = {
|
||
pageLoad: [],
|
||
apiResponse: [],
|
||
renderTime: [],
|
||
memoryUsage: [],
|
||
errorRate: []
|
||
};
|
||
|
||
this.observers = [];
|
||
this.reportingInterval = 60000; // 1分钟报告一次
|
||
this.maxDataPoints = 100; // 最大保存数据点数
|
||
|
||
this.init();
|
||
}
|
||
|
||
init() {
|
||
this.setupPerformanceObservers();
|
||
this.startMemoryMonitoring();
|
||
this.startReporting();
|
||
this.bindNavigationAPI();
|
||
}
|
||
|
||
setupPerformanceObservers() {
|
||
// 页面加载性能监控
|
||
if ('PerformanceObserver' in window) {
|
||
// LCP监控
|
||
const lcpObserver = new PerformanceObserver(list => {
|
||
const entries = list.getEntries();
|
||
const lastEntry = entries[entries.length - 1];
|
||
this.recordMetric('lcp', lastEntry.startTime);
|
||
});
|
||
lcpObserver.observe({ entryTypes: ['largest-contentful-paint'] });
|
||
this.observers.push(lcpObserver);
|
||
|
||
// FID监控
|
||
const fidObserver = new PerformanceObserver(list => {
|
||
const entries = list.getEntries();
|
||
entries.forEach(entry => {
|
||
this.recordMetric('fid', entry.processingStart - entry.startTime);
|
||
});
|
||
});
|
||
fidObserver.observe({ entryTypes: ['first-input'] });
|
||
this.observers.push(fidObserver);
|
||
|
||
// CLS监控
|
||
let clsValue = 0;
|
||
const clsObserver = new PerformanceObserver(list => {
|
||
const entries = list.getEntries();
|
||
entries.forEach(entry => {
|
||
if (!entry.hadRecentInput) {
|
||
clsValue += entry.value;
|
||
}
|
||
});
|
||
this.recordMetric('cls', clsValue);
|
||
});
|
||
clsObserver.observe({ entryTypes: ['layout-shift'] });
|
||
this.observers.push(clsObserver);
|
||
|
||
// 资源加载监控
|
||
const resourceObserver = new PerformanceObserver(list => {
|
||
const entries = list.getEntries();
|
||
entries.forEach(entry => {
|
||
this.recordResourceMetric(entry);
|
||
});
|
||
});
|
||
resourceObserver.observe({ entryTypes: ['resource'] });
|
||
this.observers.push(resourceObserver);
|
||
}
|
||
}
|
||
|
||
recordMetric(type, value, additional = {}) {
|
||
const metric = {
|
||
type,
|
||
value,
|
||
timestamp: Date.now(),
|
||
url: window.location.href,
|
||
...additional
|
||
};
|
||
|
||
if (!this.metrics[type]) {
|
||
this.metrics[type] = [];
|
||
}
|
||
|
||
this.metrics[type].push(metric);
|
||
|
||
// 限制数据点数量
|
||
if (this.metrics[type].length > this.maxDataPoints) {
|
||
this.metrics[type].shift();
|
||
}
|
||
|
||
// 触发实时事件
|
||
EventBus.emit('metric-recorded', metric);
|
||
|
||
// 检查性能阈值
|
||
this.checkPerformanceThresholds(type, value);
|
||
}
|
||
|
||
recordResourceMetric(entry) {
|
||
const metric = {
|
||
name: entry.name,
|
||
duration: entry.duration,
|
||
transferSize: entry.transferSize || 0,
|
||
type: this.getResourceType(entry.name),
|
||
timestamp: Date.now()
|
||
};
|
||
|
||
this.recordMetric('resource', metric.duration, metric);
|
||
}
|
||
|
||
recordAPIMetric(url, duration, success, statusCode) {
|
||
this.recordMetric('apiResponse', duration, {
|
||
url,
|
||
success,
|
||
statusCode,
|
||
timestamp: Date.now()
|
||
});
|
||
}
|
||
|
||
startMemoryMonitoring() {
|
||
if ('memory' in performance) {
|
||
const monitorMemory = () => {
|
||
const memory = performance.memory;
|
||
this.recordMetric('memory', memory.usedJSHeapSize, {
|
||
total: memory.totalJSHeapSize,
|
||
limit: memory.jsHeapSizeLimit,
|
||
usage: (memory.usedJSHeapSize / memory.jsHeapSizeLimit) * 100
|
||
});
|
||
};
|
||
|
||
// 立即记录一次
|
||
monitorMemory();
|
||
|
||
// 每30秒记录一次
|
||
setInterval(monitorMemory, 30000);
|
||
}
|
||
}
|
||
|
||
checkPerformanceThresholds(type, value) {
|
||
const thresholds = {
|
||
lcp: { warning: 2500, critical: 4000 },
|
||
fid: { warning: 100, critical: 300 },
|
||
cls: { warning: 0.1, critical: 0.25 },
|
||
apiResponse: { warning: 1000, critical: 3000 },
|
||
memory: { warning: 70, critical: 90 } // 百分比
|
||
};
|
||
|
||
const threshold = thresholds[type];
|
||
if (!threshold) return;
|
||
|
||
let level = 'good';
|
||
if (value > threshold.critical) {
|
||
level = 'critical';
|
||
} else if (value > threshold.warning) {
|
||
level = 'warning';
|
||
}
|
||
|
||
if (level !== 'good') {
|
||
this.triggerPerformanceAlert(type, value, level);
|
||
}
|
||
}
|
||
|
||
triggerPerformanceAlert(type, value, level) {
|
||
const alert = {
|
||
type: 'performance',
|
||
metric: type,
|
||
value,
|
||
level,
|
||
timestamp: Date.now(),
|
||
url: window.location.href
|
||
};
|
||
|
||
EventBus.emit('performance-alert', alert);
|
||
|
||
// 如果是严重问题,立即上报
|
||
if (level === 'critical') {
|
||
this.reportCriticalIssue(alert);
|
||
}
|
||
}
|
||
|
||
startReporting() {
|
||
setInterval(() => {
|
||
this.generateReport();
|
||
}, this.reportingInterval);
|
||
}
|
||
|
||
generateReport() {
|
||
const report = {
|
||
timestamp: Date.now(),
|
||
url: window.location.href,
|
||
userAgent: navigator.userAgent,
|
||
metrics: this.calculateMetricsSummary(),
|
||
alerts: this.getRecentAlerts(),
|
||
system: this.getSystemInfo()
|
||
};
|
||
|
||
// 发送报告
|
||
this.sendReport(report);
|
||
|
||
return report;
|
||
}
|
||
|
||
calculateMetricsSummary() {
|
||
const summary = {};
|
||
|
||
Object.entries(this.metrics).forEach(([type, values]) => {
|
||
if (values.length === 0) return;
|
||
|
||
const recentValues = values.slice(-10); // 最近10个值
|
||
const numericValues = recentValues
|
||
.map(v => typeof v.value === 'number' ? v.value : v.value.duration || 0)
|
||
.filter(v => !isNaN(v));
|
||
|
||
if (numericValues.length > 0) {
|
||
summary[type] = {
|
||
count: values.length,
|
||
recent: recentValues.length,
|
||
average: numericValues.reduce((a, b) => a + b, 0) / numericValues.length,
|
||
min: Math.min(...numericValues),
|
||
max: Math.max(...numericValues),
|
||
latest: numericValues[numericValues.length - 1]
|
||
};
|
||
}
|
||
});
|
||
|
||
return summary;
|
||
}
|
||
|
||
getRecentAlerts() {
|
||
const oneHour = 60 * 60 * 1000;
|
||
const now = Date.now();
|
||
|
||
return this.alerts?.filter(alert =>
|
||
now - alert.timestamp < oneHour
|
||
) || [];
|
||
}
|
||
|
||
getSystemInfo() {
|
||
return {
|
||
connection: this.getConnectionInfo(),
|
||
viewport: {
|
||
width: window.innerWidth,
|
||
height: window.innerHeight
|
||
},
|
||
deviceMemory: navigator.deviceMemory || 'unknown',
|
||
hardwareConcurrency: navigator.hardwareConcurrency || 'unknown',
|
||
platform: navigator.platform,
|
||
language: navigator.language
|
||
};
|
||
}
|
||
|
||
getConnectionInfo() {
|
||
if ('connection' in navigator) {
|
||
const connection = navigator.connection;
|
||
return {
|
||
effectiveType: connection.effectiveType,
|
||
downlink: connection.downlink,
|
||
rtt: connection.rtt,
|
||
saveData: connection.saveData
|
||
};
|
||
}
|
||
return {};
|
||
}
|
||
|
||
getResourceType(url) {
|
||
if (url.includes('.css')) return 'stylesheet';
|
||
if (url.includes('.js')) return 'script';
|
||
if (url.match(/\.(png|jpg|jpeg|gif|svg|webp)$/)) return 'image';
|
||
if (url.match(/\.(woff|woff2|ttf|otf)$/)) return 'font';
|
||
if (url.includes('/api/')) return 'api';
|
||
return 'other';
|
||
}
|
||
|
||
async sendReport(report) {
|
||
try {
|
||
await fetch('/api/monitoring/performance', {
|
||
method: 'POST',
|
||
headers: {
|
||
'Content-Type': 'application/json'
|
||
},
|
||
body: JSON.stringify(report)
|
||
});
|
||
} catch (error) {
|
||
console.warn('性能报告发送失败:', error);
|
||
}
|
||
}
|
||
|
||
async reportCriticalIssue(alert) {
|
||
try {
|
||
await fetch('/api/monitoring/critical', {
|
||
method: 'POST',
|
||
headers: {
|
||
'Content-Type': 'application/json'
|
||
},
|
||
body: JSON.stringify({
|
||
alert,
|
||
context: this.getSystemInfo(),
|
||
timestamp: Date.now()
|
||
})
|
||
});
|
||
} catch (error) {
|
||
console.warn('严重问题报告发送失败:', error);
|
||
}
|
||
}
|
||
|
||
bindNavigationAPI() {
|
||
// 监听页面导航
|
||
if ('navigation' in window) {
|
||
navigation.addEventListener('navigate', (event) => {
|
||
this.recordMetric('navigation', 0, {
|
||
destination: event.destination.url,
|
||
type: event.navigationType
|
||
});
|
||
});
|
||
}
|
||
}
|
||
|
||
getPerformanceScore() {
|
||
const weights = {
|
||
lcp: 0.25,
|
||
fid: 0.25,
|
||
cls: 0.25,
|
||
apiResponse: 0.25
|
||
};
|
||
|
||
let totalScore = 0;
|
||
let totalWeight = 0;
|
||
|
||
Object.entries(weights).forEach(([metric, weight]) => {
|
||
const values = this.metrics[metric];
|
||
if (values && values.length > 0) {
|
||
const latestValue = values[values.length - 1].value;
|
||
const score = this.calculateMetricScore(metric, latestValue);
|
||
totalScore += score * weight;
|
||
totalWeight += weight;
|
||
}
|
||
});
|
||
|
||
return totalWeight > 0 ? Math.round(totalScore / totalWeight) : 0;
|
||
}
|
||
|
||
calculateMetricScore(metric, value) {
|
||
const scoring = {
|
||
lcp: (v) => v <= 2500 ? 100 : v <= 4000 ? 80 : 50,
|
||
fid: (v) => v <= 100 ? 100 : v <= 300 ? 80 : 50,
|
||
cls: (v) => v <= 0.1 ? 100 : v <= 0.25 ? 80 : 50,
|
||
apiResponse: (v) => v <= 500 ? 100 : v <= 1000 ? 80 : 50
|
||
};
|
||
|
||
return scoring[metric] ? scoring[metric](value) : 100;
|
||
}
|
||
|
||
cleanup() {
|
||
this.observers.forEach(observer => observer.disconnect());
|
||
this.observers = [];
|
||
}
|
||
}
|
||
|
||
// 全局性能监控器
|
||
window.PerformanceMonitor = new PerformanceMonitor();
|
||
```
|
||
|
||
##### B. 智能日志系统
|
||
```javascript
|
||
class IntelligentLogger {
|
||
constructor() {
|
||
this.logs = [];
|
||
this.maxLogs = 1000;
|
||
this.logLevels = {
|
||
DEBUG: 0,
|
||
INFO: 1,
|
||
WARN: 2,
|
||
ERROR: 3,
|
||
CRITICAL: 4
|
||
};
|
||
|
||
this.currentLevel = this.logLevels.INFO;
|
||
this.enabledCategories = new Set(['all']);
|
||
this.logBuffer = [];
|
||
this.bufferFlushInterval = 30000; // 30秒刷新一次
|
||
|
||
this.setupAutoFlush();
|
||
this.interceptConsole();
|
||
}
|
||
|
||
setLevel(level) {
|
||
if (typeof level === 'string') {
|
||
level = this.logLevels[level.toUpperCase()];
|
||
}
|
||
this.currentLevel = level;
|
||
}
|
||
|
||
setCategories(categories) {
|
||
this.enabledCategories.clear();
|
||
categories.forEach(cat => this.enabledCategories.add(cat));
|
||
}
|
||
|
||
debug(message, data = {}, category = 'general') {
|
||
this.log('DEBUG', message, data, category);
|
||
}
|
||
|
||
info(message, data = {}, category = 'general') {
|
||
this.log('INFO', message, data, category);
|
||
}
|
||
|
||
warn(message, data = {}, category = 'general') {
|
||
this.log('WARN', message, data, category);
|
||
}
|
||
|
||
error(message, data = {}, category = 'general') {
|
||
this.log('ERROR', message, data, category);
|
||
}
|
||
|
||
critical(message, data = {}, category = 'general') {
|
||
this.log('CRITICAL', message, data, category);
|
||
// 严重错误立即上报
|
||
this.flushBuffer();
|
||
}
|
||
|
||
log(level, message, data = {}, category = 'general') {
|
||
const levelNum = this.logLevels[level];
|
||
|
||
// 检查日志级别
|
||
if (levelNum < this.currentLevel) return;
|
||
|
||
// 检查类别
|
||
if (!this.enabledCategories.has('all') && !this.enabledCategories.has(category)) {
|
||
return;
|
||
}
|
||
|
||
const logEntry = {
|
||
timestamp: Date.now(),
|
||
level,
|
||
message,
|
||
data: this.sanitizeData(data),
|
||
category,
|
||
url: window.location.href,
|
||
userAgent: navigator.userAgent,
|
||
userId: window.APP_CONFIG?.userId,
|
||
sessionId: window.APP_CONFIG?.sessionId,
|
||
stack: level === 'ERROR' || level === 'CRITICAL' ? new Error().stack : null
|
||
};
|
||
|
||
// 添加到本地日志
|
||
this.logs.push(logEntry);
|
||
|
||
// 限制日志数量
|
||
if (this.logs.length > this.maxLogs) {
|
||
this.logs.shift();
|
||
}
|
||
|
||
// 添加到上报缓冲区
|
||
this.logBuffer.push(logEntry);
|
||
|
||
// 控制台输出
|
||
this.outputToConsole(logEntry);
|
||
|
||
// 触发日志事件
|
||
EventBus.emit('log-entry', logEntry);
|
||
|
||
// 严重错误立即处理
|
||
if (level === 'CRITICAL') {
|
||
this.handleCriticalLog(logEntry);
|
||
}
|
||
}
|
||
|
||
sanitizeData(data) {
|
||
// 深拷贝并移除敏感信息
|
||
const sanitized = JSON.parse(JSON.stringify(data, (key, value) => {
|
||
// 移除敏感字段
|
||
const sensitiveFields = ['password', 'token', 'secret', 'key', 'auth'];
|
||
if (sensitiveFields.some(field => key.toLowerCase().includes(field))) {
|
||
return '[REDACTED]';
|
||
}
|
||
|
||
// 限制字符串长度
|
||
if (typeof value === 'string' && value.length > 1000) {
|
||
return value.substring(0, 1000) + '...[TRUNCATED]';
|
||
}
|
||
|
||
return value;
|
||
}));
|
||
|
||
return sanitized;
|
||
}
|
||
|
||
outputToConsole(logEntry) {
|
||
const { level, message, data } = logEntry;
|
||
const style = this.getConsoleStyle(level);
|
||
|
||
if (Object.keys(data).length > 0) {
|
||
console.log(
|
||
`%c[${level}] ${message}`,
|
||
style,
|
||
data
|
||
);
|
||
} else {
|
||
console.log(
|
||
`%c[${level}] ${message}`,
|
||
style
|
||
);
|
||
}
|
||
}
|
||
|
||
getConsoleStyle(level) {
|
||
const styles = {
|
||
DEBUG: 'color: #666; font-size: 11px;',
|
||
INFO: 'color: #0066cc; font-weight: normal;',
|
||
WARN: 'color: #ff9900; font-weight: bold;',
|
||
ERROR: 'color: #cc0000; font-weight: bold;',
|
||
CRITICAL: 'color: #ffffff; background-color: #cc0000; font-weight: bold; padding: 2px 4px;'
|
||
};
|
||
|
||
return styles[level] || styles.INFO;
|
||
}
|
||
|
||
interceptConsole() {
|
||
const originalConsole = {
|
||
log: console.log,
|
||
warn: console.warn,
|
||
error: console.error,
|
||
info: console.info,
|
||
debug: console.debug
|
||
};
|
||
|
||
// 拦截console.error
|
||
console.error = (...args) => {
|
||
originalConsole.error(...args);
|
||
this.error('Console Error', { args: args.map(this.stringifyArg) }, 'console');
|
||
};
|
||
|
||
// 拦截console.warn
|
||
console.warn = (...args) => {
|
||
originalConsole.warn(...args);
|
||
this.warn('Console Warning', { args: args.map(this.stringifyArg) }, 'console');
|
||
};
|
||
|
||
// 保存原始方法引用
|
||
this.originalConsole = originalConsole;
|
||
}
|
||
|
||
stringifyArg(arg) {
|
||
if (typeof arg === 'string') return arg;
|
||
if (arg instanceof Error) return { name: arg.name, message: arg.message, stack: arg.stack };
|
||
try {
|
||
return JSON.stringify(arg);
|
||
} catch {
|
||
return String(arg);
|
||
}
|
||
}
|
||
|
||
setupAutoFlush() {
|
||
setInterval(() => {
|
||
if (this.logBuffer.length > 0) {
|
||
this.flushBuffer();
|
||
}
|
||
}, this.bufferFlushInterval);
|
||
|
||
// 页面卸载时立即刷新
|
||
window.addEventListener('beforeunload', () => {
|
||
this.flushBuffer();
|
||
});
|
||
}
|
||
|
||
async flushBuffer() {
|
||
if (this.logBuffer.length === 0) return;
|
||
|
||
const logsToSend = [...this.logBuffer];
|
||
this.logBuffer = [];
|
||
|
||
try {
|
||
await fetch('/api/logging/batch', {
|
||
method: 'POST',
|
||
headers: {
|
||
'Content-Type': 'application/json'
|
||
},
|
||
body: JSON.stringify({
|
||
logs: logsToSend,
|
||
metadata: {
|
||
timestamp: Date.now(),
|
||
source: 'frontend',
|
||
version: window.APP_CONFIG?.version
|
||
}
|
||
})
|
||
});
|
||
} catch (error) {
|
||
// 发送失败,重新加入缓冲区
|
||
this.logBuffer.unshift(...logsToSend);
|
||
console.warn('日志上报失败:', error);
|
||
}
|
||
}
|
||
|
||
handleCriticalLog(logEntry) {
|
||
// 立即通知运维
|
||
this.sendCriticalAlert(logEntry);
|
||
|
||
// 尝试收集更多上下文信息
|
||
const context = this.collectCriticalContext();
|
||
|
||
// 发送详细错误报告
|
||
this.sendDetailedErrorReport(logEntry, context);
|
||
}
|
||
|
||
async sendCriticalAlert(logEntry) {
|
||
try {
|
||
await fetch('/api/alerts/critical', {
|
||
method: 'POST',
|
||
headers: {
|
||
'Content-Type': 'application/json'
|
||
},
|
||
body: JSON.stringify({
|
||
type: 'critical-frontend-error',
|
||
message: logEntry.message,
|
||
data: logEntry.data,
|
||
timestamp: logEntry.timestamp,
|
||
url: logEntry.url,
|
||
userId: logEntry.userId
|
||
})
|
||
});
|
||
} catch (error) {
|
||
console.error('严重错误告警发送失败:', error);
|
||
}
|
||
}
|
||
|
||
collectCriticalContext() {
|
||
return {
|
||
performance: PerformanceMonitor?.getPerformanceScore() || 'unknown',
|
||
memory: performance.memory ? {
|
||
used: Math.round(performance.memory.usedJSHeapSize / 1024 / 1024),
|
||
total: Math.round(performance.memory.totalJSHeapSize / 1024 / 1024)
|
||
} : 'unknown',
|
||
recentLogs: this.logs.slice(-20), // 最近20条日志
|
||
userActions: this.getUserActions(), // 用户最近操作
|
||
systemState: this.getSystemState()
|
||
};
|
||
}
|
||
|
||
getUserActions() {
|
||
// 从事件总线获取最近的用户操作
|
||
return EventBus.getRecentEvents?.('user-action') || [];
|
||
}
|
||
|
||
getSystemState() {
|
||
return {
|
||
activeComponents: Object.keys(window.components || {}),
|
||
cacheStats: CacheSystem?.getStats?.() || {},
|
||
errorHistory: ErrorRecovery?.getErrorStatistics?.() || {}
|
||
};
|
||
}
|
||
|
||
async sendDetailedErrorReport(logEntry, context) {
|
||
try {
|
||
await fetch('/api/errors/detailed-report', {
|
||
method: 'POST',
|
||
headers: {
|
||
'Content-Type': 'application/json'
|
||
},
|
||
body: JSON.stringify({
|
||
error: logEntry,
|
||
context,
|
||
timestamp: Date.now()
|
||
})
|
||
});
|
||
} catch (error) {
|
||
console.error('详细错误报告发送失败:', error);
|
||
}
|
||
}
|
||
|
||
// 搜索日志
|
||
searchLogs(query, options = {}) {
|
||
const {
|
||
level = null,
|
||
category = null,
|
||
timeRange = null,
|
||
limit = 100
|
||
} = options;
|
||
|
||
let filteredLogs = this.logs;
|
||
|
||
// 按级别过滤
|
||
if (level) {
|
||
filteredLogs = filteredLogs.filter(log => log.level === level);
|
||
}
|
||
|
||
// 按类别过滤
|
||
if (category) {
|
||
filteredLogs = filteredLogs.filter(log => log.category === category);
|
||
}
|
||
|
||
// 按时间范围过滤
|
||
if (timeRange) {
|
||
const now = Date.now();
|
||
const startTime = now - timeRange;
|
||
filteredLogs = filteredLogs.filter(log => log.timestamp >= startTime);
|
||
}
|
||
|
||
// 按查询字符串过滤
|
||
if (query) {
|
||
const queryLower = query.toLowerCase();
|
||
filteredLogs = filteredLogs.filter(log =>
|
||
log.message.toLowerCase().includes(queryLower) ||
|
||
JSON.stringify(log.data).toLowerCase().includes(queryLower)
|
||
);
|
||
}
|
||
|
||
// 限制结果数量
|
||
return filteredLogs.slice(-limit);
|
||
}
|
||
|
||
// 导出日志
|
||
exportLogs(format = 'json') {
|
||
const data = format === 'csv' ? this.convertToCSV() : JSON.stringify(this.logs, null, 2);
|
||
const blob = new Blob([data], {
|
||
type: format === 'csv' ? 'text/csv' : 'application/json'
|
||
});
|
||
const url = URL.createObjectURL(blob);
|
||
|
||
const link = document.createElement('a');
|
||
link.href = url;
|
||
link.download = `logs-${Date.now()}.${format}`;
|
||
link.click();
|
||
|
||
URL.revokeObjectURL(url);
|
||
}
|
||
|
||
convertToCSV() {
|
||
const headers = ['timestamp', 'level', 'category', 'message', 'data'];
|
||
const rows = this.logs.map(log => [
|
||
new Date(log.timestamp).toISOString(),
|
||
log.level,
|
||
log.category,
|
||
log.message,
|
||
JSON.stringify(log.data)
|
||
]);
|
||
|
||
return [headers, ...rows]
|
||
.map(row => row.map(field => `"${field}"`).join(','))
|
||
.join('\n');
|
||
}
|
||
}
|
||
|
||
// 全局日志系统
|
||
window.Logger = new IntelligentLogger();
|
||
```
|
||
|
||
#### 2.3.2 自动化部署优化
|
||
|
||
##### A. 前端构建优化
|
||
```javascript
|
||
// 构建配置优化 (webpack.config.js 概念实现)
|
||
const OptimizedBuildConfig = {
|
||
// 代码分割策略
|
||
splitChunks: {
|
||
chunks: 'all',
|
||
cacheGroups: {
|
||
// 第三方库单独打包
|
||
vendor: {
|
||
test: /[\\/]node_modules[\\/]/,
|
||
name: 'vendors',
|
||
chunks: 'all',
|
||
priority: 10
|
||
},
|
||
|
||
// 公共组件单独打包
|
||
common: {
|
||
name: 'common',
|
||
minChunks: 2,
|
||
chunks: 'all',
|
||
priority: 5,
|
||
reuseExistingChunk: true
|
||
},
|
||
|
||
// 图表组件单独打包
|
||
charts: {
|
||
test: /[\\/]components[\\/]charts[\\/]/,
|
||
name: 'charts',
|
||
chunks: 'all',
|
||
priority: 8
|
||
}
|
||
}
|
||
},
|
||
|
||
// 优化配置
|
||
optimization: {
|
||
// 生产环境压缩
|
||
minimize: true,
|
||
minimizer: [
|
||
// JavaScript压缩
|
||
{
|
||
terserOptions: {
|
||
compress: {
|
||
drop_console: true, // 移除console
|
||
drop_debugger: true, // 移除debugger
|
||
pure_funcs: ['console.log'] // 移除特定函数
|
||
},
|
||
mangle: {
|
||
safari10: true
|
||
}
|
||
}
|
||
},
|
||
|
||
// CSS压缩
|
||
{
|
||
cssProcessorOptions: {
|
||
map: {
|
||
inline: false,
|
||
annotation: true
|
||
}
|
||
}
|
||
}
|
||
],
|
||
|
||
// Tree Shaking
|
||
usedExports: true,
|
||
sideEffects: false
|
||
},
|
||
|
||
// 输出配置
|
||
output: {
|
||
// 文件名模板
|
||
filename: '[name].[contenthash:8].js',
|
||
chunkFilename: '[name].[contenthash:8].chunk.js',
|
||
|
||
// 公共路径
|
||
publicPath: '/static/',
|
||
|
||
// 清理输出目录
|
||
clean: true
|
||
},
|
||
|
||
// 模块解析
|
||
resolve: {
|
||
// 别名配置
|
||
alias: {
|
||
'@': '/src',
|
||
'@components': '/src/components',
|
||
'@utils': '/src/utils',
|
||
'@api': '/src/api'
|
||
},
|
||
|
||
// 扩展名
|
||
extensions: ['.js', '.jsx', '.ts', '.tsx', '.css', '.scss']
|
||
},
|
||
|
||
// 加载器配置
|
||
module: {
|
||
rules: [
|
||
// JavaScript/TypeScript
|
||
{
|
||
test: /\.(js|jsx|ts|tsx)$/,
|
||
exclude: /node_modules/,
|
||
use: [
|
||
{
|
||
loader: 'babel-loader',
|
||
options: {
|
||
presets: [
|
||
['@babel/preset-env', {
|
||
modules: false,
|
||
useBuiltIns: 'usage',
|
||
corejs: 3
|
||
}]
|
||
],
|
||
plugins: [
|
||
'@babel/plugin-syntax-dynamic-import',
|
||
'@babel/plugin-proposal-class-properties'
|
||
]
|
||
}
|
||
}
|
||
]
|
||
},
|
||
|
||
// CSS
|
||
{
|
||
test: /\.css$/,
|
||
use: [
|
||
'style-loader',
|
||
{
|
||
loader: 'css-loader',
|
||
options: {
|
||
modules: {
|
||
localIdentName: '[name]__[local]--[hash:base64:5]'
|
||
}
|
||
}
|
||
},
|
||
'postcss-loader'
|
||
]
|
||
},
|
||
|
||
// 图片
|
||
{
|
||
test: /\.(png|jpg|jpeg|gif|svg)$/,
|
||
type: 'asset',
|
||
parser: {
|
||
dataUrlCondition: {
|
||
maxSize: 8 * 1024 // 8KB以下内联
|
||
}
|
||
},
|
||
generator: {
|
||
filename: 'images/[name].[hash:8][ext]'
|
||
}
|
||
},
|
||
|
||
// 字体
|
||
{
|
||
test: /\.(woff|woff2|eot|ttf|otf)$/,
|
||
type: 'asset/resource',
|
||
generator: {
|
||
filename: 'fonts/[name].[hash:8][ext]'
|
||
}
|
||
}
|
||
]
|
||
},
|
||
|
||
// 插件配置
|
||
plugins: [
|
||
// HTML模板
|
||
{
|
||
name: 'HtmlWebpackPlugin',
|
||
options: {
|
||
template: './src/index.html',
|
||
minify: {
|
||
removeComments: true,
|
||
collapseWhitespace: true,
|
||
removeRedundantAttributes: true
|
||
}
|
||
}
|
||
},
|
||
|
||
// 提取CSS
|
||
{
|
||
name: 'MiniCssExtractPlugin',
|
||
options: {
|
||
filename: '[name].[contenthash:8].css',
|
||
chunkFilename: '[name].[contenthash:8].chunk.css'
|
||
}
|
||
},
|
||
|
||
// 分析包大小
|
||
{
|
||
name: 'BundleAnalyzerPlugin',
|
||
options: {
|
||
analyzerMode: 'static',
|
||
openAnalyzer: false,
|
||
reportFilename: 'bundle-report.html'
|
||
}
|
||
},
|
||
|
||
// 复制静态文件
|
||
{
|
||
name: 'CopyWebpackPlugin',
|
||
patterns: [
|
||
{
|
||
from: 'public',
|
||
to: '.',
|
||
globOptions: {
|
||
ignore: ['**/index.html']
|
||
}
|
||
}
|
||
]
|
||
}
|
||
],
|
||
|
||
// 开发服务器
|
||
devServer: {
|
||
port: 3000,
|
||
hot: true,
|
||
historyApiFallback: true,
|
||
proxy: {
|
||
'/api': {
|
||
target: 'http://localhost:5225',
|
||
changeOrigin: true
|
||
}
|
||
}
|
||
}
|
||
};
|
||
```
|
||
|
||
##### B. CI/CD 优化流程
|
||
```yaml
|
||
# .github/workflows/deploy.yml
|
||
name: 前端优化部署流程
|
||
|
||
on:
|
||
push:
|
||
branches: [ main, develop ]
|
||
pull_request:
|
||
branches: [ main ]
|
||
|
||
env:
|
||
NODE_VERSION: '18'
|
||
PYTHON_VERSION: '3.11'
|
||
|
||
jobs:
|
||
# 代码质量检查
|
||
quality-check:
|
||
runs-on: ubuntu-latest
|
||
steps:
|
||
- uses: actions/checkout@v3
|
||
|
||
- name: Setup Node.js
|
||
uses: actions/setup-node@v3
|
||
with:
|
||
node-version: ${{ env.NODE_VERSION }}
|
||
cache: 'npm'
|
||
|
||
- name: Install dependencies
|
||
run: npm ci
|
||
|
||
- name: Lint检查
|
||
run: npm run lint
|
||
|
||
- name: 类型检查
|
||
run: npm run type-check
|
||
|
||
- name: 单元测试
|
||
run: npm run test:unit
|
||
|
||
- name: 代码覆盖率
|
||
run: npm run test:coverage
|
||
|
||
- name: 上传覆盖率报告
|
||
uses: codecov/codecov-action@v3
|
||
|
||
# 构建优化
|
||
build:
|
||
needs: quality-check
|
||
runs-on: ubuntu-latest
|
||
outputs:
|
||
version: ${{ steps.version.outputs.version }}
|
||
|
||
steps:
|
||
- uses: actions/checkout@v3
|
||
|
||
- name: Setup Node.js
|
||
uses: actions/setup-node@v3
|
||
with:
|
||
node-version: ${{ env.NODE_VERSION }}
|
||
cache: 'npm'
|
||
|
||
- name: Install dependencies
|
||
run: npm ci
|
||
|
||
- name: 生成版本号
|
||
id: version
|
||
run: |
|
||
VERSION=$(date +'%Y%m%d')-${GITHUB_SHA::8}
|
||
echo "version=$VERSION" >> $GITHUB_OUTPUT
|
||
echo "VERSION=$VERSION" >> $GITHUB_ENV
|
||
|
||
- name: 构建生产版本
|
||
run: |
|
||
npm run build
|
||
npm run optimize
|
||
env:
|
||
NODE_ENV: production
|
||
BUILD_VERSION: ${{ env.VERSION }}
|
||
|
||
- name: 压缩静态资源
|
||
run: |
|
||
find dist -name "*.js" -exec gzip -k {} \;
|
||
find dist -name "*.css" -exec gzip -k {} \;
|
||
find dist -name "*.html" -exec gzip -k {} \;
|
||
|
||
- name: 分析构建产物
|
||
run: |
|
||
npm run analyze
|
||
ls -la dist/
|
||
|
||
- name: 上传构建产物
|
||
uses: actions/upload-artifact@v3
|
||
with:
|
||
name: build-artifacts-${{ env.VERSION }}
|
||
path: |
|
||
dist/
|
||
docs/bundle-report.html
|
||
retention-days: 30
|
||
|
||
# 性能测试
|
||
performance-test:
|
||
needs: build
|
||
runs-on: ubuntu-latest
|
||
|
||
steps:
|
||
- uses: actions/checkout@v3
|
||
|
||
- name: 下载构建产物
|
||
uses: actions/download-artifact@v3
|
||
with:
|
||
name: build-artifacts-${{ needs.build.outputs.version }}
|
||
path: ./dist
|
||
|
||
- name: Setup Python
|
||
uses: actions/setup-python@v4
|
||
with:
|
||
python-version: ${{ env.PYTHON_VERSION }}
|
||
|
||
- name: 启动后端服务
|
||
run: |
|
||
cd ljwx-bigscreen
|
||
pip install -r requirements.txt
|
||
python run.py &
|
||
sleep 10
|
||
|
||
- name: 运行Lighthouse性能测试
|
||
run: |
|
||
npm install -g @lhci/cli
|
||
lhci autorun --upload.target=temporary-public-storage
|
||
env:
|
||
LHCI_GITHUB_APP_TOKEN: ${{ secrets.LHCI_GITHUB_APP_TOKEN }}
|
||
|
||
- name: 运行负载测试
|
||
run: |
|
||
npm run test:load
|
||
|
||
- name: 生成性能报告
|
||
run: |
|
||
npm run performance:report
|
||
|
||
# 部署到测试环境
|
||
deploy-staging:
|
||
needs: [build, performance-test]
|
||
runs-on: ubuntu-latest
|
||
if: github.ref == 'refs/heads/develop'
|
||
|
||
steps:
|
||
- uses: actions/checkout@v3
|
||
|
||
- name: 下载构建产物
|
||
uses: actions/download-artifact@v3
|
||
with:
|
||
name: build-artifacts-${{ needs.build.outputs.version }}
|
||
path: ./dist
|
||
|
||
- name: 部署到测试环境
|
||
run: |
|
||
# 使用rsync或其他工具部署
|
||
rsync -avz --delete dist/ staging-server:/var/www/personal-dashboard/
|
||
|
||
# 更新nginx配置
|
||
ssh staging-server 'sudo nginx -t && sudo systemctl reload nginx'
|
||
|
||
# 清理CDN缓存
|
||
curl -X POST "https://api.cdn.com/purge" \
|
||
-H "Authorization: Bearer ${{ secrets.CDN_TOKEN }}" \
|
||
-d '{"files": ["/*"]}'
|
||
|
||
# 部署到生产环境
|
||
deploy-production:
|
||
needs: [build, performance-test]
|
||
runs-on: ubuntu-latest
|
||
if: github.ref == 'refs/heads/main'
|
||
environment: production
|
||
|
||
steps:
|
||
- uses: actions/checkout@v3
|
||
|
||
- name: 下载构建产物
|
||
uses: actions/download-artifact@v3
|
||
with:
|
||
name: build-artifacts-${{ needs.build.outputs.version }}
|
||
path: ./dist
|
||
|
||
- name: 生产环境预检查
|
||
run: |
|
||
# 检查构建产物完整性
|
||
npm run verify:build
|
||
|
||
# 检查资源大小
|
||
npm run check:bundle-size
|
||
|
||
- name: 蓝绿部署
|
||
run: |
|
||
# 部署到新环境
|
||
./scripts/blue-green-deploy.sh
|
||
env:
|
||
DEPLOY_VERSION: ${{ needs.build.outputs.version }}
|
||
|
||
- name: 健康检查
|
||
run: |
|
||
# 等待服务启动
|
||
sleep 30
|
||
|
||
# 健康检查
|
||
curl -f http://production-server/health || exit 1
|
||
|
||
# 性能检查
|
||
npm run health:performance
|
||
|
||
- name: 切换流量
|
||
run: |
|
||
# 切换负载均衡器流量
|
||
./scripts/switch-traffic.sh
|
||
|
||
- name: 清理旧版本
|
||
run: |
|
||
# 保留最近3个版本
|
||
./scripts/cleanup-old-versions.sh 3
|
||
|
||
# 监控和告警
|
||
post-deploy:
|
||
needs: [deploy-production]
|
||
runs-on: ubuntu-latest
|
||
if: github.ref == 'refs/heads/main'
|
||
|
||
steps:
|
||
- name: 发送部署通知
|
||
run: |
|
||
curl -X POST "https://hooks.slack.com/services/${{ secrets.SLACK_WEBHOOK }}" \
|
||
-H "Content-Type: application/json" \
|
||
-d '{
|
||
"text": "✅ Personal.html优化版本部署成功",
|
||
"attachments": [{
|
||
"color": "good",
|
||
"fields": [
|
||
{"title": "版本", "value": "${{ needs.build.outputs.version }}", "short": true},
|
||
{"title": "提交", "value": "'${GITHUB_SHA::8}'", "short": true},
|
||
{"title": "环境", "value": "生产环境", "short": true}
|
||
]
|
||
}]
|
||
}'
|
||
|
||
- name: 启动监控
|
||
run: |
|
||
# 启动特殊监控任务
|
||
curl -X POST "https://monitoring-api.com/start-enhanced-monitoring" \
|
||
-H "Authorization: Bearer ${{ secrets.MONITORING_TOKEN }}" \
|
||
-d '{"version": "${{ needs.build.outputs.version }}"}'
|
||
```
|
||
|
||
## 3. 实施时间线与里程碑
|
||
|
||
### 第一阶段:基础架构重构 (周1-3)
|
||
- **周1**: 文件结构设计与基础模板创建
|
||
- **周2**: 组件系统架构实现
|
||
- **周3**: API客户端与缓存系统开发
|
||
|
||
### 第二阶段:性能优化核心 (周4-6)
|
||
- **周4**: 资源加载优化与懒加载实现
|
||
- **周5**: 内存管理与错误恢复系统
|
||
- **周6**: 数据更新策略优化
|
||
|
||
### 第三阶段:用户体验提升 (周7-9)
|
||
- **周7**: 响应式设计升级
|
||
- **周8**: 主题系统与交互优化
|
||
- **周9**: 加载状态与错误处理完善
|
||
|
||
### 第四阶段:运维体系建设 (周10-12)
|
||
- **周10**: 监控系统部署
|
||
- **周11**: 日志系统与自动化测试
|
||
- **周12**: CI/CD优化与上线
|
||
|
||
## 4. 预期效果与评估指标
|
||
|
||
### 4.1 性能指标
|
||
```javascript
|
||
const PerformanceTargets = {
|
||
// 加载性能
|
||
firstContentfulPaint: { current: '2.8s', target: '1.2s', improvement: '57%' },
|
||
largestContentfulPaint: { current: '4.5s', target: '2.0s', improvement: '56%' },
|
||
timeToInteractive: { current: '6.2s', target: '2.8s', improvement: '55%' },
|
||
|
||
// 运行时性能
|
||
avgApiResponse: { current: '850ms', target: '350ms', improvement: '59%' },
|
||
memoryUsage: { current: '78MB', target: '45MB', improvement: '42%' },
|
||
frameRate: { current: '45fps', target: '58fps', improvement: '29%' },
|
||
|
||
// 用户体验
|
||
bounceRate: { current: '23%', target: '12%', improvement: '48%' },
|
||
pageLoadSuccess: { current: '94%', target: '99.5%', improvement: '5.9%' },
|
||
errorRate: { current: '2.3%', target: '0.5%', improvement: '78%' }
|
||
};
|
||
```
|
||
|
||
### 4.2 代码质量指标
|
||
```javascript
|
||
const QualityMetrics = {
|
||
// 可维护性
|
||
codeComplexity: { current: 'High', target: 'Low', improvement: '70%' },
|
||
testCoverage: { current: '35%', target: '85%', improvement: '143%' },
|
||
duplicationRate: { current: '18%', target: '5%', improvement: '72%' },
|
||
|
||
// 开发效率
|
||
buildTime: { current: '3.2min', target: '1.1min', improvement: '66%' },
|
||
hotReloadTime: { current: '8s', target: '2s', improvement: '75%' },
|
||
deploymentTime: { current: '12min', target: '4min', improvement: '67%' }
|
||
};
|
||
```
|
||
|
||
## 5. 风险评估与应对策略
|
||
|
||
### 5.1 技术风险
|
||
- **兼容性风险**: 通过渐进式增强和polyfill确保向后兼容
|
||
- **性能回归风险**: 建立持续的性能监控和自动化测试
|
||
- **数据丢失风险**: 实现完善的缓存回退机制和错误恢复
|
||
|
||
### 5.2 项目风险
|
||
- **时间风险**: 采用MVP方式,优先实现核心功能
|
||
- **资源风险**: 分阶段实施,允许并行开发
|
||
- **用户体验风险**: 保持功能对等,提供平滑的迁移路径
|
||
|
||
## 6. 总结与建议
|
||
|
||
### 6.1 核心优势
|
||
1. **系统性优化**: 从架构到实现的全链路优化
|
||
2. **可持续发展**: 建立长期可维护的代码结构
|
||
3. **性能提升**: 多维度性能优化,显著提升用户体验
|
||
4. **运维友好**: 完善的监控、日志和自动化体系
|
||
|
||
### 6.2 实施建议
|
||
1. **分阶段执行**: 避免一次性替换带来的风险
|
||
2. **充分测试**: 建立完善的测试体系确保质量
|
||
3. **监控先行**: 优先建立监控体系,确保问题及时发现
|
||
4. **文档同步**: 保持文档与代码同步更新
|
||
|
||
### 6.3 成功关键因素
|
||
1. **团队协作**: 建立清晰的分工和沟通机制
|
||
2. **质量控制**: 严格的代码审查和测试标准
|
||
3. **持续优化**: 基于监控数据的持续改进
|
||
4. **用户反馈**: 积极收集和响应用户反馈
|
||
|
||
通过这套全面的优化方案,personal.html将从一个9,792行的巨型文件转变为现代化、高性能、易维护的Web应用系统,不仅解决当前的技术债务,更为未来的功能扩展和系统演进奠定坚实基础。
|
||
|
||
---
|
||
|
||
**文档版本**: v1.0
|
||
**制定日期**: 2025-09-18
|
||
**更新日期**: 2025-09-18
|
||
**审核状态**: 待审核
|
||
**实施优先级**: 高 |