50 KiB
50 KiB
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 核心类设计
/**
* 主应用程序类
*/
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 初始化流程
// 应用初始化流程
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 模块结构
// 应用基础信息
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 配置管理类
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 核心工具函数
/**
* 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 数据处理工具
/**
* 数据验证工具
*/
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客户端封装
/**
* 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管理器
/**
* 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 提示管理器
/**
* 提示信息管理器
*/
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 分页组件
/**
* 分页组件
*/
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 导航管理器
/**
* 导航管理器类
*/
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 事件驱动架构
/**
* 事件总线 - 模块间通信
*/
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 模块依赖注入
/**
* 依赖注入容器
*/
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 模块懒加载
/**
* 模块懒加载管理器
*/
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 缓存策略
/**
* 缓存管理器
*/
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 全局错误处理
/**
* 全局错误处理器
*/
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 调试工具
/**
* 开发调试工具
*/
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 单元测试框架
/**
* 轻量级测试框架
*/
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('<script>alert("xss")</script>');
});
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 开发环境
/**
* 开发环境配置
*/
const devConfig = {
// 热重载
enableHotReload: true,
// 调试模式
debugMode: true,
// 本地服务器配置
devServer: {
port: 3000,
host: 'localhost',
open: true
},
// 代理配置
proxy: {
'/api': {
target: 'http://localhost:5000',
changeOrigin: true
}
}
};
8.2 生产环境优化
/**
* 生产环境优化
*/
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