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

50 KiB
Raw Blame History

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('&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 开发环境

/**
 * 开发环境配置
 */
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