Files
BigDataTool/docs/frontend-architecture.md
2025-08-05 23:27:25 +08:00

1742 lines
50 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# DataTools Pro 前端模块化设计文档
## 1. 前端架构概述
### 1.1 设计理念
DataTools Pro前端采用现代化的模块化架构基于ES6模块系统构建追求高内聚、低耦合的设计原则提供可维护、可扩展的前端解决方案。
### 1.2 技术栈
```
前端技术栈:
├── 核心技术
│ ├── 原生JavaScript (ES6+)
│ ├── ES6 Modules (import/export)
│ └── 现代浏览器API (Fetch, Promise, async/await)
├── UI框架
│ ├── Bootstrap 5.1.3 (响应式UI框架)
│ ├── Font Awesome 6.0.0 (图标库)
│ └── 原生CSS3 (自定义样式和动画)
└── 开发模式
├── 模块化开发
├── 组件化设计
└── 事件驱动架构
```
### 1.3 架构优势
- **无构建依赖**: 直接在浏览器中运行,无需复杂的构建工具
- **模块化设计**: 清晰的职责分离,便于维护和测试
- **轻量级**: 没有框架包袱,性能优异
- **现代化**: 使用最新的JavaScript特性和API
- **兼容性**: 支持现代浏览器(Chrome 61+, Firefox 60+, Safari 10.1+)
## 2. 模块架构图
```
DataTools Pro 前端模块架构
┌─────────────────────────────────────────────────────────────┐
│ 应用层 (App Layer) │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ app-main.js │ │
│ │ 主应用入口和协调器 │ │
│ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────────────┐ │ │
│ │ │ 应用初始化 │ │ 生命周期 │ │ 全局事件管理 │ │ │
│ │ │ 管理 │ │ 管理 │ │ │ │ │
│ │ └─────────────┘ └─────────────┘ └─────────────────────┘ │ │
│ └─────────────────────────────────────────────────────────┘ │
├─────────────────────────────────────────────────────────────┤
│ 核心模块层 (Core Modules) │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────────────────┐ │
│ │ config.js │ │ utils.js │ │ api.js │ │
│ │ 配置管理 │ │ 工具函数库 │ │ HTTP客户端 │ │
│ │ ·APP_INFO │ │ ·escapeHtml │ │ ·CassandraAPI │ │
│ │ ·API端点 │ │ ·formatDate │ │ ·RedisAPI │ │
│ │ ·UI配置 │ │ ·debounce │ │ ·统一错误处理 │ │
│ └─────────────┘ └─────────────┘ └─────────────────────────┘ │
│ ┌─────────────┐ ┌─────────────────────────────────────────┐ │
│ │ ui.js │ │ navigation.js │ │
│ │ UI组件库 │ │ 导航管理模块 │ │
│ │ ·AlertMgr │ │ ·面包屑导航 ·键盘快捷键 │ │
│ │ ·ModalMgr │ │ ·活跃状态管理 ·程序化导航 │ │
│ │ ·Pagination │ │ ·页面标题管理 │ │
│ └─────────────┘ └─────────────────────────────────────────┘ │
├─────────────────────────────────────────────────────────────┤
│ 页面层 (Page Layer) │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────────────────┐ │
│ │ index.html │ │db_compare. │ │ redis_compare.html │ │
│ │ 首页模块 │ │html │ │ Redis比对模块 │ │
│ │ ·工具展示 │ │Cassandra │ │ ·集群配置管理 │ │
│ │ ·统计信息 │ │比对模块 │ │ ·随机采样查询 │ │
│ │ ·导航入口 │ │·查询配置 │ │ ·指定Key查询 │ │
│ └─────────────┘ │·分表查询 │ │ ·数据类型比对 │ │
│ │·多主键查询 │ └─────────────────────────┘ │
│ └─────────────┘ │
└─────────────────────────────────────────────────────────────┘
```
## 3. 核心模块详解
### 3.1 主应用模块 (app-main.js)
#### 3.1.1 模块职责
- **应用初始化**: 统一管理各模块启动和配置
- **生命周期管理**: 处理应用启动、运行、销毁周期
- **全局事件管理**: 统一处理全局事件和错误
- **模块协调**: 协调各个功能模块之间的交互
#### 3.1.2 核心类设计
```javascript
/**
* 主应用程序类
*/
class DataToolsApp {
constructor() {
this.config = config; // 配置管理
this.utils = utils; // 工具函数
this.api = apiManager; // API管理
this.navigation = navigationManager; // 导航管理
this.ui = { // UI组件集合
alert: alertManager,
modal: modalManager,
loading: loadingIndicator,
confirm: confirmDialog,
prompt: promptDialog
};
this.isInitialized = false;
this.currentTool = null;
}
// 核心方法
async init() // 应用初始化
checkDependencies() // 依赖检查
initUI() // UI初始化
bindGlobalEvents() // 全局事件绑定
detectCurrentTool() // 当前工具检测
destroy() // 应用销毁
}
```
#### 3.1.3 初始化流程
```javascript
// 应用初始化流程
async function initApplication() {
try {
console.log('DataTools Pro 正在初始化...');
// 1. 检查浏览器兼容性和依赖
checkDependencies();
// 2. 初始化UI组件
initUI();
// 3. 启用导航和快捷键
navigation.enableKeyboardShortcuts();
// 4. 绑定全局事件
bindGlobalEvents();
// 5. 检测当前工具类型
detectCurrentTool();
console.log('应用程序初始化完成');
// 6. 显示欢迎消息
showWelcomeMessage();
} catch (error) {
console.error('应用程序初始化失败:', error);
ui.alert.error('应用程序初始化失败,请刷新页面重试');
}
}
```
### 3.2 配置管理模块 (config.js)
#### 3.2.1 模块结构
```javascript
// 应用基础信息
export const APP_INFO = {
name: 'DataTools Pro',
version: '2.0.0',
description: '企业级数据处理与比对工具平台'
};
// API端点配置
export const API_ENDPOINTS = {
// Cassandra相关API
CASSANDRA: {
DEFAULT_CONFIG: '/api/default-config',
QUERY: '/api/query',
SHARDING_QUERY: '/api/sharding-query'
},
// Redis相关API
REDIS: {
COMPARE: '/api/redis/compare',
CONFIG: '/api/redis/config'
},
// 配置管理API
CONFIG_GROUPS: {
LIST: '/api/config-groups',
CREATE: '/api/config-groups',
GET: (id) => `/api/config-groups/${id}`,
DELETE: (id) => `/api/config-groups/${id}`
},
// 历史记录API
QUERY_HISTORY: {
LIST: '/api/query-history',
CREATE: '/api/query-history',
GET: (id) => `/api/query-history/${id}`,
RESULTS: (id) => `/api/query-history/${id}/results`,
DELETE: (id) => `/api/query-history/${id}`
},
// 日志管理API
QUERY_LOGS: {
LIST: '/api/query-logs',
HISTORY: (id) => `/api/query-logs/history/${id}`,
CLEAR: '/api/query-logs'
}
};
// UI配置
export const UI_CONFIG = {
PAGINATION: {
DEFAULT_PAGE_SIZE: 10,
MAX_PAGE_SIZE: 100,
SHOW_SIZE_CHANGER: true
},
ALERT: {
AUTO_HIDE_DELAY: 3000,
MAX_ALERTS: 5
},
LOADING: {
MIN_DISPLAY_TIME: 500,
TIMEOUT: 30000
},
ANIMATION: {
DURATION: 300,
EASING: 'ease-in-out'
}
};
// 工具配置
export const TOOLS_CONFIG = {
CASSANDRA: {
name: 'Cassandra数据比对工具',
url: '/cassandra-compare',
icon: 'fas fa-database',
description: '企业级Cassandra数据库比对分析'
},
REDIS: {
name: 'Redis集群比对工具',
url: '/redis-compare',
icon: 'fab fa-redis',
description: '专业的Redis集群数据比对分析'
}
};
```
#### 3.2.2 配置管理类
```javascript
class ConfigManager {
constructor() {
this.cache = new Map();
this.listeners = new Map();
}
// 获取配置值
get(key, defaultValue = null) {
const keys = key.split('.');
let value = this;
for (const k of keys) {
value = value[k];
if (value === undefined) {
return defaultValue;
}
}
return value;
}
// 设置配置值
set(key, value) {
this.cache.set(key, value);
this.notifyListeners(key, value);
}
// 监听配置变更
onChange(key, callback) {
if (!this.listeners.has(key)) {
this.listeners.set(key, new Set());
}
this.listeners.get(key).add(callback);
}
// 通知监听器
notifyListeners(key, value) {
if (this.listeners.has(key)) {
this.listeners.get(key).forEach(callback => {
callback(value, key);
});
}
}
}
```
### 3.3 工具函数模块 (utils.js)
#### 3.3.1 核心工具函数
```javascript
/**
* HTML转义防止XSS攻击
*/
export function escapeHtml(text) {
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}
/**
* 日期格式化
*/
export function formatDate(date, format = 'YYYY-MM-DD HH:mm:ss') {
const d = new Date(date);
const formatMap = {
'YYYY': d.getFullYear(),
'MM': String(d.getMonth() + 1).padStart(2, '0'),
'DD': String(d.getDate()).padStart(2, '0'),
'HH': String(d.getHours()).padStart(2, '0'),
'mm': String(d.getMinutes()).padStart(2, '0'),
'ss': String(d.getSeconds()).padStart(2, '0')
};
return format.replace(/YYYY|MM|DD|HH|mm|ss/g, match => formatMap[match]);
}
/**
* 防抖函数
*/
export function debounce(func, wait, immediate = false) {
let timeout;
return function executedFunction(...args) {
const later = () => {
timeout = null;
if (!immediate) func.apply(this, args);
};
const callNow = immediate && !timeout;
clearTimeout(timeout);
timeout = setTimeout(later, wait);
if (callNow) func.apply(this, args);
};
}
/**
* 节流函数
*/
export function throttle(func, limit) {
let inThrottle;
return function(...args) {
if (!inThrottle) {
func.apply(this, args);
inThrottle = true;
setTimeout(() => inThrottle = false, limit);
}
};
}
/**
* 深度克隆对象
*/
export function 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 => deepClone(item));
}
if (typeof obj === "object") {
const clonedObj = {};
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
clonedObj[key] = deepClone(obj[key]);
}
}
return clonedObj;
}
}
/**
* 剪贴板操作
*/
export const clipboard = {
async copy(text) {
try {
await navigator.clipboard.writeText(text);
return true;
} catch (err) {
// 降级方案
const textArea = document.createElement('textarea');
textArea.value = text;
document.body.appendChild(textArea);
textArea.select();
document.execCommand('copy');
document.body.removeChild(textArea);
return true;
}
},
async paste() {
try {
return await navigator.clipboard.readText();
} catch (err) {
console.warn('无法访问剪贴板');
return '';
}
}
};
/**
* 文件操作工具
*/
export const fileUtils = {
// 下载文件
download(data, filename, type = 'text/plain') {
const blob = new Blob([data], { type });
const url = URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = url;
link.download = filename;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
URL.revokeObjectURL(url);
},
// 读取文件
async readFile(file) {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = e => resolve(e.target.result);
reader.onerror = reject;
reader.readAsText(file);
});
}
};
```
#### 3.3.2 数据处理工具
```javascript
/**
* 数据验证工具
*/
export const validator = {
// 非空验证
required(value, message = '此字段不能为空') {
if (!value || value.toString().trim() === '') {
throw new Error(message);
}
return true;
},
// 邮箱验证
email(value, message = '请输入有效的邮箱地址') {
const pattern = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!pattern.test(value)) {
throw new Error(message);
}
return true;
},
// 数字验证
number(value, min = null, max = null, message = '请输入有效的数字') {
const num = Number(value);
if (isNaN(num)) {
throw new Error(message);
}
if (min !== null && num < min) {
throw new Error(`数值不能小于${min}`);
}
if (max !== null && num > max) {
throw new Error(`数值不能大于${max}`);
}
return true;
},
// JSON验证
json(value, message = '请输入有效的JSON格式') {
try {
JSON.parse(value);
return true;
} catch (e) {
throw new Error(message);
}
}
};
/**
* 字符串处理工具
*/
export const stringUtils = {
// 首字母大写
capitalize(str) {
return str.charAt(0).toUpperCase() + str.slice(1);
},
// 驼峰命名转换
camelCase(str) {
return str.replace(/-([a-z])/g, (match, letter) => letter.toUpperCase());
},
// 截断字符串
truncate(str, maxLength, suffix = '...') {
if (str.length <= maxLength) return str;
return str.substring(0, maxLength - suffix.length) + suffix;
},
// 格式化文件大小
formatBytes(bytes, decimals = 2) {
if (bytes === 0) return '0 Bytes';
const k = 1024;
const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return parseFloat((bytes / Math.pow(k, i)).toFixed(decimals)) + ' ' + sizes[i];
}
};
```
### 3.4 API管理模块 (api.js)
#### 3.4.1 HTTP客户端封装
```javascript
/**
* HTTP客户端类
*/
class HttpClient {
constructor(baseURL = '', options = {}) {
this.baseURL = baseURL;
this.defaultOptions = {
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json'
},
credentials: 'same-origin',
...options
};
this.interceptors = {
request: [],
response: []
};
}
// 添加请求拦截器
addRequestInterceptor(interceptor) {
this.interceptors.request.push(interceptor);
}
// 添加响应拦截器
addResponseInterceptor(interceptor) {
this.interceptors.response.push(interceptor);
}
// 执行请求
async request(url, options = {}) {
const fullUrl = this.baseURL + url;
let config = { ...this.defaultOptions, ...options };
// 执行请求拦截器
for (const interceptor of this.interceptors.request) {
config = await interceptor(config);
}
try {
let response = await fetch(fullUrl, config);
// 执行响应拦截器
for (const interceptor of this.interceptors.response) {
response = await interceptor(response);
}
// 处理HTTP错误状态
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
// 解析JSON响应
const data = await response.json();
return data;
} catch (error) {
console.error('HTTP请求失败:', error);
throw error;
}
}
// HTTP方法快捷方式
get(url, params = {}) {
const queryString = new URLSearchParams(params).toString();
const fullUrl = queryString ? `${url}?${queryString}` : url;
return this.request(fullUrl, { method: 'GET' });
}
post(url, data = {}) {
return this.request(url, {
method: 'POST',
body: JSON.stringify(data)
});
}
put(url, data = {}) {
return this.request(url, {
method: 'PUT',
body: JSON.stringify(data)
});
}
delete(url) {
return this.request(url, { method: 'DELETE' });
}
}
```
#### 3.4.2 API管理器
```javascript
/**
* API管理器类
*/
class APIManager {
constructor() {
this.httpClient = new HttpClient();
this.setupInterceptors();
}
// 设置拦截器
setupInterceptors() {
// 请求拦截器 - 添加时间戳和请求ID
this.httpClient.addRequestInterceptor(async (config) => {
config.headers['X-Request-ID'] = this.generateRequestId();
config.headers['X-Timestamp'] = Date.now();
return config;
});
// 响应拦截器 - 统一错误处理
this.httpClient.addResponseInterceptor(async (response) => {
const data = await response.json();
if (!data.success) {
throw new APIError(data.error?.message || '请求失败', data.error?.code);
}
return data;
});
}
// 生成请求ID
generateRequestId() {
return 'req_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9);
}
// Cassandra API
get cassandra() {
return {
getDefaultConfig: () =>
this.httpClient.get(API_ENDPOINTS.CASSANDRA.DEFAULT_CONFIG),
executeQuery: (config) =>
this.httpClient.post(API_ENDPOINTS.CASSANDRA.QUERY, config),
executeShardingQuery: (config) =>
this.httpClient.post(API_ENDPOINTS.CASSANDRA.SHARDING_QUERY, config)
};
}
// Redis API
get redis() {
return {
compareData: (config) =>
this.httpClient.post(API_ENDPOINTS.REDIS.COMPARE, config)
};
}
// 配置组API
get configGroups() {
return {
list: () =>
this.httpClient.get(API_ENDPOINTS.CONFIG_GROUPS.LIST),
create: (config) =>
this.httpClient.post(API_ENDPOINTS.CONFIG_GROUPS.CREATE, config),
get: (id) =>
this.httpClient.get(API_ENDPOINTS.CONFIG_GROUPS.GET(id)),
delete: (id) =>
this.httpClient.delete(API_ENDPOINTS.CONFIG_GROUPS.DELETE(id))
};
}
// 查询历史API
get queryHistory() {
return {
list: (params = {}) =>
this.httpClient.get(API_ENDPOINTS.QUERY_HISTORY.LIST, params),
create: (history) =>
this.httpClient.post(API_ENDPOINTS.QUERY_HISTORY.CREATE, history),
get: (id) =>
this.httpClient.get(API_ENDPOINTS.QUERY_HISTORY.GET(id)),
getResults: (id) =>
this.httpClient.get(API_ENDPOINTS.QUERY_HISTORY.RESULTS(id)),
delete: (id) =>
this.httpClient.delete(API_ENDPOINTS.QUERY_HISTORY.DELETE(id))
};
}
// 日志API
get queryLogs() {
return {
list: (params = {}) =>
this.httpClient.get(API_ENDPOINTS.QUERY_LOGS.LIST, params),
getHistory: (id) =>
this.httpClient.get(API_ENDPOINTS.QUERY_LOGS.HISTORY(id)),
clear: () =>
this.httpClient.delete(API_ENDPOINTS.QUERY_LOGS.CLEAR)
};
}
}
/**
* API错误类
*/
class APIError extends Error {
constructor(message, code = 'UNKNOWN_ERROR') {
super(message);
this.name = 'APIError';
this.code = code;
}
}
```
### 3.5 UI组件模块 (ui.js)
#### 3.5.1 提示管理器
```javascript
/**
* 提示信息管理器
*/
class AlertManager {
constructor() {
this.alerts = [];
this.container = null;
this.init();
}
init() {
// 创建提示容器
this.container = document.createElement('div');
this.container.className = 'alert-container';
this.container.style.cssText = `
position: fixed;
top: 20px;
right: 20px;
z-index: 9999;
max-width: 400px;
`;
document.body.appendChild(this.container);
}
show(type, message, autoHide = UI_CONFIG.ALERT.AUTO_HIDE_DELAY) {
const alertId = 'alert_' + Date.now();
const alertElement = this.createAlertElement(alertId, type, message);
this.container.appendChild(alertElement);
this.alerts.push({ id: alertId, element: alertElement });
// 限制提示数量
if (this.alerts.length > UI_CONFIG.ALERT.MAX_ALERTS) {
this.removeOldest();
}
// 自动隐藏
if (autoHide > 0) {
setTimeout(() => this.hide(alertId), autoHide);
}
return alertId;
}
createAlertElement(id, type, message) {
const alert = document.createElement('div');
alert.id = id;
alert.className = `alert alert-${type} alert-dismissible fade show`;
alert.innerHTML = `
<i class="fas fa-${this.getIcon(type)} me-2"></i>
<span>${escapeHtml(message)}</span>
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
`;
// 绑定关闭事件
alert.querySelector('.btn-close').addEventListener('click', () => {
this.hide(id);
});
return alert;
}
getIcon(type) {
const icons = {
success: 'check-circle',
error: 'exclamation-circle',
warning: 'exclamation-triangle',
info: 'info-circle'
};
return icons[type] || 'info-circle';
}
hide(alertId) {
const alertIndex = this.alerts.findIndex(alert => alert.id === alertId);
if (alertIndex !== -1) {
const alert = this.alerts[alertIndex];
alert.element.remove();
this.alerts.splice(alertIndex, 1);
}
}
// 便捷方法
success(message, autoHide) { return this.show('success', message, autoHide); }
error(message, autoHide) { return this.show('error', message, autoHide); }
warning(message, autoHide) { return this.show('warning', message, autoHide); }
info(message, autoHide) { return this.show('info', message, autoHide); }
}
```
#### 3.5.2 分页组件
```javascript
/**
* 分页组件
*/
class PaginationComponent {
constructor(container, options = {}) {
this.container = typeof container === 'string' ?
document.querySelector(container) : container;
this.options = {
pageSize: UI_CONFIG.PAGINATION.DEFAULT_PAGE_SIZE,
showSizeChanger: UI_CONFIG.PAGINATION.SHOW_SIZE_CHANGER,
showTotal: true,
showQuickJumper: true,
...options
};
this.currentPage = 1;
this.totalItems = 0;
this.totalPages = 0;
this.onPageChange = options.onPageChange || (() => {});
this.onSizeChange = options.onSizeChange || (() => {});
}
// 更新分页数据
update(totalItems, currentPage = 1) {
this.totalItems = totalItems;
this.currentPage = currentPage;
this.totalPages = Math.ceil(totalItems / this.options.pageSize);
this.render();
}
// 渲染分页组件
render() {
if (!this.container) return;
const pagination = this.createPaginationElement();
this.container.innerHTML = '';
this.container.appendChild(pagination);
}
createPaginationElement() {
const nav = document.createElement('nav');
nav.innerHTML = `
<div class="d-flex justify-content-between align-items-center">
<div class="pagination-info">
${this.renderTotalInfo()}
</div>
<ul class="pagination mb-0">
${this.renderPaginationItems()}
</ul>
<div class="pagination-controls">
${this.renderSizeChanger()}
${this.renderQuickJumper()}
</div>
</div>
`;
this.bindEvents(nav);
return nav;
}
renderTotalInfo() {
if (!this.options.showTotal) return '';
const start = (this.currentPage - 1) * this.options.pageSize + 1;
const end = Math.min(this.currentPage * this.options.pageSize, this.totalItems);
return `
<small class="text-muted">
显示 ${start}-${end} 项,共 ${this.totalItems}
</small>
`;
}
renderPaginationItems() {
let items = [];
// 上一页
items.push(`
<li class="page-item ${this.currentPage === 1 ? 'disabled' : ''}">
<a class="page-link" href="#" data-page="${this.currentPage - 1}">
<i class="fas fa-chevron-left"></i>
</a>
</li>
`);
// 页码
const pageNumbers = this.generatePageNumbers();
pageNumbers.forEach(page => {
if (page === '...') {
items.push('<li class="page-item disabled"><span class="page-link">...</span></li>');
} else {
items.push(`
<li class="page-item ${page === this.currentPage ? 'active' : ''}">
<a class="page-link" href="#" data-page="${page}">${page}</a>
</li>
`);
}
});
// 下一页
items.push(`
<li class="page-item ${this.currentPage === this.totalPages ? 'disabled' : ''}">
<a class="page-link" href="#" data-page="${this.currentPage + 1}">
<i class="fas fa-chevron-right"></i>
</a>
</li>
`);
return items.join('');
}
generatePageNumbers() {
const total = this.totalPages;
const current = this.currentPage;
const pages = [];
if (total <= 7) {
for (let i = 1; i <= total; i++) {
pages.push(i);
}
} else {
if (current <= 4) {
pages.push(1, 2, 3, 4, 5, '...', total);
} else if (current >= total - 3) {
pages.push(1, '...', total - 4, total - 3, total - 2, total - 1, total);
} else {
pages.push(1, '...', current - 1, current, current + 1, '...', total);
}
}
return pages;
}
bindEvents(nav) {
// 分页点击事件
nav.addEventListener('click', (e) => {
if (e.target.matches('.page-link[data-page]')) {
e.preventDefault();
const page = parseInt(e.target.dataset.page);
if (page > 0 && page <= this.totalPages) {
this.goToPage(page);
}
}
});
// 页面大小变更事件
const sizeSelect = nav.querySelector('.page-size-select');
if (sizeSelect) {
sizeSelect.addEventListener('change', (e) => {
this.changePageSize(parseInt(e.target.value));
});
}
// 快速跳转事件
const jumpInput = nav.querySelector('.page-jump-input');
if (jumpInput) {
jumpInput.addEventListener('keypress', (e) => {
if (e.key === 'Enter') {
const page = parseInt(e.target.value);
if (page > 0 && page <= this.totalPages) {
this.goToPage(page);
}
}
});
}
}
goToPage(page) {
if (page !== this.currentPage) {
this.currentPage = page;
this.render();
this.onPageChange(page, this.options.pageSize);
}
}
changePageSize(pageSize) {
this.options.pageSize = pageSize;
this.currentPage = 1;
this.totalPages = Math.ceil(this.totalItems / pageSize);
this.render();
this.onSizeChange(pageSize, this.currentPage);
}
}
```
### 3.6 导航管理模块 (navigation.js)
#### 3.6.1 导航管理器
```javascript
/**
* 导航管理器类
*/
class NavigationManager {
constructor() {
this.currentPath = window.location.pathname;
this.breadcrumbContainer = null;
this.shortcuts = new Map();
this.init();
}
init() {
this.updateActiveNavigation();
this.createBreadcrumb();
this.bindNavigationEvents();
}
// 更新活跃导航状态
updateActiveNavigation() {
document.querySelectorAll('.nav-link').forEach(link => {
link.classList.remove('active');
});
// 根据当前路径设置活跃状态
const currentNavLink = this.findActiveNavLink();
if (currentNavLink) {
currentNavLink.classList.add('active');
}
}
findActiveNavLink() {
// 直接匹配
let link = document.querySelector(`a[href="${this.currentPath}"]`);
if (link && link.classList.contains('nav-link')) {
return link;
}
// 路由别名匹配
if (this.currentPath.includes('db-compare') ||
this.currentPath.includes('cassandra-compare')) {
return document.querySelector('a[href="/cassandra-compare"]');
}
if (this.currentPath.includes('redis-compare')) {
return document.querySelector('a[href="/redis-compare"]');
}
if (this.currentPath === '/') {
return document.querySelector('a[href="/"]');
}
return null;
}
// 创建面包屑导航
createBreadcrumb() {
const breadcrumbItems = this.getBreadcrumbItems();
if (breadcrumbItems.length <= 1) return;
this.breadcrumbContainer = this.findOrCreateBreadcrumbContainer();
this.renderBreadcrumb(breadcrumbItems);
}
getBreadcrumbItems() {
const items = [
{ title: '首页', url: '/', icon: 'fas fa-home' }
];
if (this.currentPath.includes('cassandra-compare') ||
this.currentPath.includes('db-compare')) {
items.push({
title: 'Cassandra数据比对',
url: '/cassandra-compare',
icon: 'fas fa-database'
});
} else if (this.currentPath.includes('redis-compare')) {
items.push({
title: 'Redis集群比对',
url: '/redis-compare',
icon: 'fab fa-redis'
});
}
return items;
}
// 启用键盘快捷键
enableKeyboardShortcuts() {
// 定义快捷键
this.shortcuts.set('ctrl+h', {
action: () => this.navigateTo('/'),
description: '返回首页'
});
this.shortcuts.set('ctrl+1', {
action: () => this.navigateTo('/cassandra-compare'),
description: 'Cassandra工具'
});
this.shortcuts.set('ctrl+2', {
action: () => this.navigateTo('/redis-compare'),
description: 'Redis工具'
});
// 绑定键盘事件
document.addEventListener('keydown', (e) => {
const key = this.getKeyString(e);
const shortcut = this.shortcuts.get(key);
if (shortcut) {
e.preventDefault();
shortcut.action();
}
});
// 显示快捷键提示
this.createShortcutsTooltip();
}
getKeyString(event) {
const keys = [];
if (event.ctrlKey || event.metaKey) keys.push('ctrl');
if (event.altKey) keys.push('alt');
if (event.shiftKey) keys.push('shift');
keys.push(event.key.toLowerCase());
return keys.join('+');
}
createShortcutsTooltip() {
const tooltip = document.createElement('div');
tooltip.className = 'keyboard-shortcuts-tooltip';
tooltip.style.cssText = `
position: fixed;
bottom: 100px;
right: 30px;
z-index: 1000;
background: rgba(0, 0, 0, 0.8);
color: white;
padding: 10px;
border-radius: 5px;
font-size: 12px;
display: none;
`;
let html = '<div class="fw-bold mb-2">键盘快捷键:</div>';
this.shortcuts.forEach((shortcut, key) => {
const displayKey = key.replace('ctrl', 'Ctrl').replace('+', ' + ');
html += `<div><kbd>${displayKey}</kbd> ${shortcut.description}</div>`;
});
tooltip.innerHTML = html;
document.body.appendChild(tooltip);
// 按?键显示/隐藏
document.addEventListener('keydown', (e) => {
if (e.key === '?' && !e.ctrlKey && !e.metaKey && !e.altKey) {
e.preventDefault();
tooltip.style.display = tooltip.style.display === 'none' ? 'block' : 'none';
}
});
}
// 程序化导航
navigateTo(url, replace = false) {
if (replace) {
window.history.replaceState(null, '', url);
} else {
window.location.href = url;
}
}
// 更新页面标题
updatePageTitle(title) {
const toolInfo = this.getCurrentToolInfo();
if (toolInfo) {
document.title = `${title} - ${toolInfo.name} - DataTools Pro`;
} else {
document.title = `${title} - DataTools Pro`;
}
}
}
```
## 4. 模块间通信
### 4.1 事件驱动架构
```javascript
/**
* 事件总线 - 模块间通信
*/
class EventBus {
constructor() {
this.events = new Map();
}
// 监听事件
on(eventName, callback) {
if (!this.events.has(eventName)) {
this.events.set(eventName, new Set());
}
this.events.get(eventName).add(callback);
}
// 移除监听
off(eventName, callback) {
if (this.events.has(eventName)) {
this.events.get(eventName).delete(callback);
}
}
// 触发事件
emit(eventName, ...args) {
if (this.events.has(eventName)) {
this.events.get(eventName).forEach(callback => {
try {
callback(...args);
} catch (error) {
console.error(`事件处理错误 [${eventName}]:`, error);
}
});
}
}
// 一次性监听
once(eventName, callback) {
const onceCallback = (...args) => {
callback(...args);
this.off(eventName, onceCallback);
};
this.on(eventName, onceCallback);
}
}
// 全局事件总线实例
const eventBus = new EventBus();
```
### 4.2 模块依赖注入
```javascript
/**
* 依赖注入容器
*/
class DIContainer {
constructor() {
this.services = new Map();
this.instances = new Map();
}
// 注册服务
register(name, factory, singleton = true) {
this.services.set(name, { factory, singleton });
}
// 获取服务实例
get(name) {
if (!this.services.has(name)) {
throw new Error(`Service '${name}' not found`);
}
const service = this.services.get(name);
if (service.singleton) {
if (!this.instances.has(name)) {
this.instances.set(name, service.factory());
}
return this.instances.get(name);
} else {
return service.factory();
}
}
// 清除实例缓存
clear(name) {
if (name) {
this.instances.delete(name);
} else {
this.instances.clear();
}
}
}
// 全局DI容器
const container = new DIContainer();
// 注册核心服务
container.register('config', () => config);
container.register('utils', () => utils);
container.register('api', () => apiManager);
container.register('navigation', () => navigationManager);
container.register('eventBus', () => eventBus);
```
## 5. 性能优化
### 5.1 模块懒加载
```javascript
/**
* 模块懒加载管理器
*/
class LazyLoader {
constructor() {
this.loadedModules = new Set();
this.loadingPromises = new Map();
}
async loadModule(modulePath) {
if (this.loadedModules.has(modulePath)) {
return Promise.resolve();
}
if (this.loadingPromises.has(modulePath)) {
return this.loadingPromises.get(modulePath);
}
const loadPromise = import(modulePath).then(module => {
this.loadedModules.add(modulePath);
this.loadingPromises.delete(modulePath);
return module;
});
this.loadingPromises.set(modulePath, loadPromise);
return loadPromise;
}
async loadModules(modulePaths) {
return Promise.all(modulePaths.map(path => this.loadModule(path)));
}
}
```
### 5.2 缓存策略
```javascript
/**
* 缓存管理器
*/
class CacheManager {
constructor(maxSize = 100) {
this.cache = new Map();
this.maxSize = maxSize;
this.accessOrder = [];
}
set(key, value, ttl = 0) {
const item = {
value,
timestamp: Date.now(),
ttl
};
if (this.cache.has(key)) {
this.updateAccessOrder(key);
} else {
if (this.cache.size >= this.maxSize) {
this.evictLRU();
}
this.accessOrder.push(key);
}
this.cache.set(key, item);
}
get(key) {
const item = this.cache.get(key);
if (!item) return null;
// 检查过期时间
if (item.ttl > 0 && Date.now() - item.timestamp > item.ttl) {
this.delete(key);
return null;
}
this.updateAccessOrder(key);
return item.value;
}
delete(key) {
this.cache.delete(key);
const index = this.accessOrder.indexOf(key);
if (index > -1) {
this.accessOrder.splice(index, 1);
}
}
updateAccessOrder(key) {
const index = this.accessOrder.indexOf(key);
if (index > -1) {
this.accessOrder.splice(index, 1);
}
this.accessOrder.push(key);
}
evictLRU() {
const oldestKey = this.accessOrder.shift();
this.cache.delete(oldestKey);
}
clear() {
this.cache.clear();
this.accessOrder = [];
}
}
```
## 6. 错误处理和调试
### 6.1 全局错误处理
```javascript
/**
* 全局错误处理器
*/
class ErrorHandler {
constructor() {
this.setupGlobalHandlers();
this.errorReporters = [];
}
setupGlobalHandlers() {
// JavaScript错误
window.addEventListener('error', (e) => {
this.handleError(e.error, 'javascript');
});
// Promise拒绝错误
window.addEventListener('unhandledrejection', (e) => {
this.handleError(e.reason, 'promise');
e.preventDefault();
});
// 资源加载错误
window.addEventListener('error', (e) => {
if (e.target !== window) {
this.handleError(new Error(`资源加载失败: ${e.target.src || e.target.href}`), 'resource');
}
}, true);
}
handleError(error, type = 'unknown') {
const errorInfo = {
message: error.message || error.toString(),
stack: error.stack,
type,
timestamp: new Date().toISOString(),
url: window.location.href,
userAgent: navigator.userAgent
};
console.error('Global Error:', errorInfo);
// 通知错误报告器
this.errorReporters.forEach(reporter => {
try {
reporter(errorInfo);
} catch (reportError) {
console.error('Error reporter failed:', reportError);
}
});
// 显示用户友好的错误信息
if (type !== 'resource') {
this.showUserError(error);
}
}
showUserError(error) {
if (window.DataToolsApp && window.DataToolsApp.ui) {
window.DataToolsApp.ui.alert.error(
'系统出现错误,请刷新页面后重试'
);
}
}
addErrorReporter(reporter) {
this.errorReporters.push(reporter);
}
}
```
### 6.2 调试工具
```javascript
/**
* 开发调试工具
*/
class DebugTool {
constructor() {
this.isEnabled = this.checkDebugMode();
this.logs = [];
this.maxLogs = 1000;
if (this.isEnabled) {
this.setupDebugConsole();
}
}
checkDebugMode() {
return window.location.search.includes('debug=true') ||
localStorage.getItem('datatools_debug') === 'true';
}
log(message, data = null) {
if (!this.isEnabled) return;
const logEntry = {
timestamp: Date.now(),
message,
data,
stack: new Error().stack
};
this.logs.push(logEntry);
if (this.logs.length > this.maxLogs) {
this.logs.shift();
}
console.log(`[DEBUG] ${message}`, data);
}
setupDebugConsole() {
// 添加全局调试命令
window.DataToolsDebug = {
getLogs: () => this.logs,
clearLogs: () => { this.logs = []; },
exportLogs: () => {
const data = JSON.stringify(this.logs, null, 2);
fileUtils.download(data, 'datatools-debug.json', 'application/json');
},
getAppState: () => window.DataToolsApp,
getModules: () => ({
config,
utils,
apiManager,
navigationManager
})
};
console.log('DataTools Pro Debug Mode Enabled');
console.log('Use DataToolsDebug object for debugging');
}
}
```
## 7. 测试策略
### 7.1 单元测试框架
```javascript
/**
* 轻量级测试框架
*/
class TestFramework {
constructor() {
this.tests = [];
this.results = {
passed: 0,
failed: 0,
errors: []
};
}
describe(description, testFn) {
console.group(`📋 ${description}`);
testFn();
console.groupEnd();
}
it(description, testFn) {
try {
testFn();
this.results.passed++;
console.log(`✅ ${description}`);
} catch (error) {
this.results.failed++;
this.results.errors.push({ description, error });
console.error(`❌ ${description}:`, error);
}
}
expect(actual) {
return {
toBe: (expected) => {
if (actual !== expected) {
throw new Error(`Expected ${expected}, got ${actual}`);
}
},
toEqual: (expected) => {
if (JSON.stringify(actual) !== JSON.stringify(expected)) {
throw new Error(`Expected ${JSON.stringify(expected)}, got ${JSON.stringify(actual)}`);
}
},
toBeTruthy: () => {
if (!actual) {
throw new Error(`Expected truthy value, got ${actual}`);
}
},
toBeFalsy: () => {
if (actual) {
throw new Error(`Expected falsy value, got ${actual}`);
}
}
};
}
async runTests() {
console.log('🚀 Running DataTools Pro Tests...');
// 运行所有测试
this.testUtils();
this.testAPI();
this.testUI();
// 输出结果
console.log(`\n📊 Test Results:`);
console.log(`✅ Passed: ${this.results.passed}`);
console.log(`❌ Failed: ${this.results.failed}`);
if (this.results.errors.length > 0) {
console.log('\n❌ Errors:');
this.results.errors.forEach(({ description, error }) => {
console.error(` - ${description}: ${error.message}`);
});
}
}
testUtils() {
this.describe('Utils Module', () => {
this.it('should escape HTML correctly', () => {
const result = utils.escapeHtml('<script>alert("xss")</script>');
this.expect(result).toBe('&lt;script&gt;alert("xss")&lt;/script&gt;');
});
this.it('should format date correctly', () => {
const date = new Date('2024-08-05T10:30:00Z');
const result = utils.formatDate(date, 'YYYY-MM-DD');
this.expect(result).toBe('2024-08-05');
});
this.it('should debounce function calls', (done) => {
let callCount = 0;
const debouncedFn = utils.debounce(() => callCount++, 100);
debouncedFn();
debouncedFn();
debouncedFn();
setTimeout(() => {
this.expect(callCount).toBe(1);
done();
}, 150);
});
});
}
}
```
## 8. 构建和部署
### 8.1 开发环境
```javascript
/**
* 开发环境配置
*/
const devConfig = {
// 热重载
enableHotReload: true,
// 调试模式
debugMode: true,
// 本地服务器配置
devServer: {
port: 3000,
host: 'localhost',
open: true
},
// 代理配置
proxy: {
'/api': {
target: 'http://localhost:5000',
changeOrigin: true
}
}
};
```
### 8.2 生产环境优化
```javascript
/**
* 生产环境优化
*/
const prodOptimizations = {
// 代码压缩
minification: true,
// 资源合并
bundling: false, // 保持模块化
// 缓存策略
caching: {
staticAssets: '1y',
api: '5m'
},
// CDN配置
cdn: {
bootstrap: 'https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css',
fontawesome: 'https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css'
}
};
```
---
**版本**: v2.0
**更新日期**: 2024-08-05
**维护者**: DataTools Pro Team