// 全局变量 let currentResults = null; let currentIdenticalPage = 1; let identicalPageSize = 10; let filteredIdenticalResults = []; let currentDifferencePage = 1; let differencePageSize = 10; let filteredDifferenceResults = []; // 页面加载完成后初始化 document.addEventListener('DOMContentLoaded', function() { loadDefaultConfig(); loadConfigGroups(); // 加载配置组列表 }); // 加载配置组列表 async function loadConfigGroups() { try { const response = await fetch('/api/config-groups'); const result = await response.json(); if (result.success) { const select = document.getElementById('configGroupSelect'); select.innerHTML = ''; result.data.forEach(group => { const option = document.createElement('option'); option.value = group.id; option.textContent = `${group.name} (${group.description || '无描述'})`; select.appendChild(option); }); if (result.data.length === 0) { console.log('暂无配置组,数据库已就绪'); } } else { console.error('加载配置组失败:', result.error); // 如果是数据库问题,显示友好提示 if (result.error && result.error.includes('table')) { showAlert('info', '正在初始化数据库,请稍后再试'); } } } catch (error) { console.error('加载配置组失败:', error); showAlert('warning', '配置组功能暂时不可用,但不影响基本查询功能'); } } // 加载选中的配置组 async function loadSelectedConfigGroup() { const groupId = document.getElementById('configGroupSelect').value; if (!groupId) { showAlert('warning', '请先选择一个配置组'); return; } try { const response = await fetch(`/api/config-groups/${groupId}`); const result = await response.json(); if (result.success) { const config = result.data; // 填充生产环境配置 document.getElementById('pro_cluster_name').value = config.pro_config.cluster_name || ''; document.getElementById('pro_datacenter').value = config.pro_config.datacenter || ''; document.getElementById('pro_hosts').value = (config.pro_config.hosts || []).join(','); document.getElementById('pro_port').value = config.pro_config.port || 9042; document.getElementById('pro_username').value = config.pro_config.username || ''; document.getElementById('pro_password').value = config.pro_config.password || ''; document.getElementById('pro_keyspace').value = config.pro_config.keyspace || ''; document.getElementById('pro_table').value = config.pro_config.table || ''; // 填充测试环境配置 document.getElementById('test_cluster_name').value = config.test_config.cluster_name || ''; document.getElementById('test_datacenter').value = config.test_config.datacenter || ''; document.getElementById('test_hosts').value = (config.test_config.hosts || []).join(','); document.getElementById('test_port').value = config.test_config.port || 9042; document.getElementById('test_username').value = config.test_config.username || ''; document.getElementById('test_password').value = config.test_config.password || ''; document.getElementById('test_keyspace').value = config.test_config.keyspace || ''; document.getElementById('test_table').value = config.test_config.table || ''; // 填充查询配置 document.getElementById('keys').value = (config.query_config.keys || []).join(','); document.getElementById('fields_to_compare').value = (config.query_config.fields_to_compare || []).join(','); document.getElementById('exclude_fields').value = (config.query_config.exclude_fields || []).join(','); showAlert('success', `配置组 "${config.name}" 加载成功`); } else { showAlert('danger', result.error || '加载配置组失败'); } } catch (error) { showAlert('danger', '加载配置组失败: ' + error.message); } } // 显示保存配置组对话框 function showSaveConfigDialog() { const modalContent = ` `; // 移除现有modal const existingModal = document.getElementById('saveConfigModal'); if (existingModal) { existingModal.remove(); } // 添加新modal到body document.body.insertAdjacentHTML('beforeend', modalContent); // 显示modal const modal = new bootstrap.Modal(document.getElementById('saveConfigModal')); modal.show(); } // 保存配置组 async function saveConfigGroup() { const name = document.getElementById('configGroupName').value.trim(); const description = document.getElementById('configGroupDesc').value.trim(); if (!name) { showAlert('warning', '请输入配置组名称'); return; } const config = getCurrentConfig(); try { const response = await fetch('/api/config-groups', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ name: name, description: description, ...config }) }); const result = await response.json(); if (result.success) { // 关闭modal const modal = bootstrap.Modal.getInstance(document.getElementById('saveConfigModal')); modal.hide(); // 重新加载配置组列表 await loadConfigGroups(); showAlert('success', result.message); } else { showAlert('danger', result.error || '保存配置组失败'); } } catch (error) { showAlert('danger', '保存配置组失败: ' + error.message); } } // 显示管理配置组对话框 async function showManageConfigDialog() { try { const response = await fetch('/api/config-groups'); const result = await response.json(); if (result.success) { let configGroupsList = ''; if (result.data.length === 0) { configGroupsList = '

暂无配置组

'; } else { result.data.forEach(group => { const createdDate = new Date(group.created_at).toLocaleString(); const updatedDate = new Date(group.updated_at).toLocaleString(); configGroupsList += `
${group.name}

${group.description || '无描述'}

创建: ${createdDate} | 更新: ${updatedDate}
`; }); } const modalContent = ` `; // 移除现有modal const existingModal = document.getElementById('manageConfigModal'); if (existingModal) { existingModal.remove(); } // 添加新modal到body document.body.insertAdjacentHTML('beforeend', modalContent); // 显示modal const modal = new bootstrap.Modal(document.getElementById('manageConfigModal')); modal.show(); } else { showAlert('danger', '加载配置组列表失败'); } } catch (error) { showAlert('danger', '加载配置组列表失败: ' + error.message); } } // 通过ID加载配置组 async function loadConfigGroupById(groupId) { try { const response = await fetch(`/api/config-groups/${groupId}`); const result = await response.json(); if (result.success) { const config = result.data; // 填充表单数据(与loadSelectedConfigGroup相同逻辑) document.getElementById('pro_cluster_name').value = config.pro_config.cluster_name || ''; document.getElementById('pro_datacenter').value = config.pro_config.datacenter || ''; document.getElementById('pro_hosts').value = (config.pro_config.hosts || []).join(','); document.getElementById('pro_port').value = config.pro_config.port || 9042; document.getElementById('pro_username').value = config.pro_config.username || ''; document.getElementById('pro_password').value = config.pro_config.password || ''; document.getElementById('pro_keyspace').value = config.pro_config.keyspace || ''; document.getElementById('pro_table').value = config.pro_config.table || ''; document.getElementById('test_cluster_name').value = config.test_config.cluster_name || ''; document.getElementById('test_datacenter').value = config.test_config.datacenter || ''; document.getElementById('test_hosts').value = (config.test_config.hosts || []).join(','); document.getElementById('test_port').value = config.test_config.port || 9042; document.getElementById('test_username').value = config.test_config.username || ''; document.getElementById('test_password').value = config.test_config.password || ''; document.getElementById('test_keyspace').value = config.test_config.keyspace || ''; document.getElementById('test_table').value = config.test_config.table || ''; document.getElementById('keys').value = (config.query_config.keys || []).join(','); document.getElementById('fields_to_compare').value = (config.query_config.fields_to_compare || []).join(','); document.getElementById('exclude_fields').value = (config.query_config.exclude_fields || []).join(','); // 更新下拉框选中状态 document.getElementById('configGroupSelect').value = groupId; // 关闭管理modal const modal = bootstrap.Modal.getInstance(document.getElementById('manageConfigModal')); modal.hide(); showAlert('success', `配置组 "${config.name}" 加载成功`); } else { showAlert('danger', result.error || '加载配置组失败'); } } catch (error) { showAlert('danger', '加载配置组失败: ' + error.message); } } // 删除配置组 async function deleteConfigGroup(groupId, groupName) { if (!confirm(`确定要删除配置组 "${groupName}" 吗?此操作不可撤销。`)) { return; } try { const response = await fetch(`/api/config-groups/${groupId}`, { method: 'DELETE' }); const result = await response.json(); if (result.success) { showAlert('success', result.message); // 重新加载配置组列表 await loadConfigGroups(); // 重新显示管理对话框 setTimeout(() => { showManageConfigDialog(); }, 500); } else { showAlert('danger', result.error || '删除配置组失败'); } } catch (error) { showAlert('danger', '删除配置组失败: ' + error.message); } } // 加载默认配置 async function loadDefaultConfig() { try { const response = await fetch('/api/default-config'); const config = await response.json(); // 填充生产环境配置 document.getElementById('pro_cluster_name').value = config.pro_config.cluster_name || ''; document.getElementById('pro_datacenter').value = config.pro_config.datacenter || ''; document.getElementById('pro_hosts').value = config.pro_config.hosts.join(','); document.getElementById('pro_port').value = config.pro_config.port; document.getElementById('pro_username').value = config.pro_config.username; document.getElementById('pro_password').value = config.pro_config.password; document.getElementById('pro_keyspace').value = config.pro_config.keyspace; document.getElementById('pro_table').value = config.pro_config.table; // 填充测试环境配置 document.getElementById('test_cluster_name').value = config.test_config.cluster_name || ''; document.getElementById('test_datacenter').value = config.test_config.datacenter || ''; document.getElementById('test_hosts').value = config.test_config.hosts.join(','); document.getElementById('test_port').value = config.test_config.port; document.getElementById('test_username').value = config.test_config.username; document.getElementById('test_password').value = config.test_config.password; document.getElementById('test_keyspace').value = config.test_config.keyspace; document.getElementById('test_table').value = config.test_config.table; // 填充查询配置 document.getElementById('keys').value = config.keys.join(','); document.getElementById('fields_to_compare').value = config.fields_to_compare.join(','); document.getElementById('exclude_fields').value = config.exclude_fields.join(','); showAlert('success', '默认配置加载成功'); } catch (error) { showAlert('danger', '加载默认配置失败: ' + error.message); } } // 获取当前配置 function getCurrentConfig() { return { pro_config: { cluster_name: document.getElementById('pro_cluster_name').value, datacenter: document.getElementById('pro_datacenter').value, hosts: document.getElementById('pro_hosts').value.split(',').map(h => h.trim()).filter(h => h), port: parseInt(document.getElementById('pro_port').value) || 9042, username: document.getElementById('pro_username').value, password: document.getElementById('pro_password').value, keyspace: document.getElementById('pro_keyspace').value, table: document.getElementById('pro_table').value }, test_config: { cluster_name: document.getElementById('test_cluster_name').value, datacenter: document.getElementById('test_datacenter').value, hosts: document.getElementById('test_hosts').value.split(',').map(h => h.trim()).filter(h => h), port: parseInt(document.getElementById('test_port').value) || 9042, username: document.getElementById('test_username').value, password: document.getElementById('test_password').value, keyspace: document.getElementById('test_keyspace').value, table: document.getElementById('test_table').value }, keys: document.getElementById('keys').value.split(',').map(k => k.trim()).filter(k => k), fields_to_compare: document.getElementById('fields_to_compare').value .split(',').map(f => f.trim()).filter(f => f), exclude_fields: document.getElementById('exclude_fields').value .split(',').map(f => f.trim()).filter(f => f), values: document.getElementById('query_values').value .split('\n').map(v => v.trim()).filter(v => v) }; } // 执行查询比对 async function executeQuery() { const config = getCurrentConfig(); // 验证配置 if (!config.values.length) { showAlert('warning', '请输入查询Key值'); return; } if (!config.keys.length) { showAlert('warning', '请输入主键字段'); return; } // 显示加载动画 document.getElementById('loading').style.display = 'block'; document.getElementById('results').style.display = 'none'; try { const response = await fetch('/api/query', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(config) }); if (!response.ok) { const error = await response.json(); throw new Error(error.error || '查询失败'); } const results = await response.json(); currentResults = results; displayResults(results); } catch (error) { showAlert('danger', '查询失败: ' + error.message); } finally { document.getElementById('loading').style.display = 'none'; } } // 显示查询结果 function displayResults(results) { // 显示统计信息 displayStats(results); // 更新选项卡计数 document.getElementById('diff-count').textContent = results.differences.length; document.getElementById('identical-count').textContent = results.identical_results.length; // 初始化相同结果分页数据 filteredIdenticalResults = results.identical_results; currentIdenticalPage = 1; // 初始化差异结果分页数据 filteredDifferenceResults = results.differences; currentDifferencePage = 1; // 显示各个面板内容 displayDifferences(); displayIdenticalResults(); displayComparisonSummary(results.summary); // 显示结果区域 document.getElementById('results').style.display = 'block'; showAlert('success', `查询完成!共处理${results.total_keys}个Key,发现${results.differences.length}处差异,${results.identical_results.length}条记录完全相同`); } // 显示统计信息 function displayStats(results) { const statsHtml = `

${results.total_keys}

总Key数量

${results.pro_count}

生产表记录

${results.test_count}

测试表记录

${results.identical_results.length}

相同记录

${results.differences.length}

差异记录

${Math.round((results.identical_results.length / results.total_keys) * 100)}%

一致性比例

`; document.getElementById('stats').innerHTML = statsHtml; } // 显示差异详情 function displayDifferences() { const differencesContainer = document.getElementById('differences'); if (!filteredDifferenceResults.length) { differencesContainer.innerHTML = '

未发现差异

'; return; } // 计算分页 const totalPages = Math.ceil(filteredDifferenceResults.length / differencePageSize); const startIndex = (currentDifferencePage - 1) * differencePageSize; const endIndex = startIndex + differencePageSize; const currentPageData = filteredDifferenceResults.slice(startIndex, endIndex); let html = `
共 ${filteredDifferenceResults.length} 条差异记录
${generateDifferencePagination(currentDifferencePage, totalPages)}
`; // 显示当前页数据 currentPageData.forEach((diff, index) => { const globalIndex = startIndex + index + 1; if (diff.message) { // 记录不存在的情况 html += `
差异 #${globalIndex} 记录缺失

主键: ${JSON.stringify(diff.key)}

${diff.message}

`; } else { // 字段值差异的情况 const isJson = diff.is_json; const isArray = diff.is_array; const jsonClass = isJson ? 'json-field' : ''; html += `
差异 #${globalIndex} 字段差异 ${isJson ? 'JSON字段' : ''} ${isArray ? '数组字段' : ''}

主键: ${JSON.stringify(diff.key)}

差异字段: ${diff.field}

${diff.field}
${escapeHtml(diff.pro_value)}
${escapeHtml(diff.test_value)}
`; } }); // 底部分页 if (totalPages > 1) { html += `
${generateDifferencePagination(currentDifferencePage, totalPages)}
`; } differencesContainer.innerHTML = html; } // HTML转义函数,防止XSS function escapeHtml(text) { const div = document.createElement('div'); div.textContent = text; return div.innerHTML; } // 显示相同结果 function displayIdenticalResults() { const identicalContainer = document.getElementById('identical-results'); if (!filteredIdenticalResults.length) { identicalContainer.innerHTML = '

没有完全相同的记录

'; return; } // 计算分页 const totalPages = Math.ceil(filteredIdenticalResults.length / identicalPageSize); const startIndex = (currentIdenticalPage - 1) * identicalPageSize; const endIndex = startIndex + identicalPageSize; const currentPageData = filteredIdenticalResults.slice(startIndex, endIndex); let html = `
共 ${filteredIdenticalResults.length} 条记录
${generatePagination(currentIdenticalPage, totalPages)}
`; // 显示当前页数据 currentPageData.forEach((result, index) => { const globalIndex = startIndex + index + 1; html += `
相同记录 #${globalIndex} 完全匹配

主键: ${JSON.stringify(result.key)}

`; // 显示字段对比,左右布局 const proFields = result.pro_fields || {}; const testFields = result.test_fields || {}; const allFields = new Set([...Object.keys(proFields), ...Object.keys(testFields)]); html += '
字段对比 (生产环境 vs 测试环境):
'; allFields.forEach(fieldName => { const proValue = proFields[fieldName] || ''; const testValue = testFields[fieldName] || ''; // 更安全的JSON检测方法 let isJson = false; try { if (typeof proValue === 'string' && (proValue.startsWith('{') || proValue.startsWith('['))) { JSON.parse(proValue); isJson = true; } } catch (e1) { try { if (typeof testValue === 'string' && (testValue.startsWith('{') || testValue.startsWith('['))) { JSON.parse(testValue); isJson = true; } } catch (e2) { // 如果都不是有效的JSON,保持isJson为false } } html += `
${fieldName} ${isJson ? 'JSON' : ''}
${escapeHtml(String(proValue))}
${escapeHtml(String(testValue))}
`; }); html += `
`; }); // 底部分页 if (totalPages > 1) { html += `
${generatePagination(currentIdenticalPage, totalPages)}
`; } identicalContainer.innerHTML = html; } // 生成分页导航 function generatePagination(currentPage, totalPages) { if (totalPages <= 1) return ''; let html = ''; return html; } // 跳转到指定页面 function goToIdenticalPage(page) { const totalPages = Math.ceil(filteredIdenticalResults.length / identicalPageSize); if (page < 1 || page > totalPages) return; currentIdenticalPage = page; displayIdenticalResults(); } // 改变每页显示数量 function changePageSize(newSize) { if (newSize === 'custom') { // 显示自定义输入框 const customInput = document.getElementById('customPageSize'); const select = document.querySelector('select[onchange="changePageSize(this.value)"]'); customInput.style.display = 'inline-block'; customInput.focus(); return; } identicalPageSize = parseInt(newSize); currentIdenticalPage = 1; displayIdenticalResults(); } // 设置自定义页面大小 function setCustomPageSize(size) { const pageSize = parseInt(size); if (pageSize && pageSize > 0 && pageSize <= 1000) { identicalPageSize = pageSize; currentIdenticalPage = 1; // 隐藏输入框,更新下拉框显示 const customInput = document.getElementById('customPageSize'); const select = document.querySelector('select[onchange="changePageSize(this.value)"]'); customInput.style.display = 'none'; // 如果不是预设值,保持custom选中状态 const presetValues = [5, 10, 20, 50, 100]; if (!presetValues.includes(pageSize)) { select.value = 'custom'; } else { select.value = pageSize.toString(); } displayIdenticalResults(); } else { showAlert('warning', '请输入1-1000之间的有效数字'); } } // 处理自定义页面大小输入框回车事件 function handleCustomPageSizeEnter(event) { if (event.key === 'Enter') { setCustomPageSize(event.target.value); } } // 搜索相同结果 function searchIdenticalResults(searchTerm) { if (!currentResults) return; if (!searchTerm.trim()) { filteredIdenticalResults = currentResults.identical_results; } else { const term = searchTerm.toLowerCase(); filteredIdenticalResults = currentResults.identical_results.filter(result => { // 搜索主键 const keyStr = JSON.stringify(result.key).toLowerCase(); if (keyStr.includes(term)) return true; // 搜索字段内容 const proFields = result.pro_fields || {}; const testFields = result.test_fields || {}; const allValues = [...Object.values(proFields), ...Object.values(testFields)]; return allValues.some(value => String(value).toLowerCase().includes(term) ); }); } currentIdenticalPage = 1; displayIdenticalResults(); } // 生成差异分页导航 function generateDifferencePagination(currentPage, totalPages) { if (totalPages <= 1) return ''; let html = ''; return html; } // 跳转到指定差异页面 function goToDifferencePage(page) { const totalPages = Math.ceil(filteredDifferenceResults.length / differencePageSize); if (page < 1 || page > totalPages) return; currentDifferencePage = page; displayDifferences(); } // 改变差异每页显示数量 function changeDifferencePageSize(newSize) { if (newSize === 'custom_diff') { // 显示自定义输入框 const customInput = document.getElementById('customDiffPageSize'); const select = document.querySelector('select[onchange="changeDifferencePageSize(this.value)"]'); customInput.style.display = 'inline-block'; customInput.focus(); return; } differencePageSize = parseInt(newSize); currentDifferencePage = 1; displayDifferences(); } // 设置自定义差异页面大小 function setCustomDifferencePageSize(size) { const pageSize = parseInt(size); if (pageSize && pageSize > 0 && pageSize <= 1000) { differencePageSize = pageSize; currentDifferencePage = 1; // 隐藏输入框,更新下拉框显示 const customInput = document.getElementById('customDiffPageSize'); const select = document.querySelector('select[onchange="changeDifferencePageSize(this.value)"]'); customInput.style.display = 'none'; // 如果不是预设值,保持custom选中状态 const presetValues = [5, 10, 20, 50, 100]; if (!presetValues.includes(pageSize)) { select.value = 'custom_diff'; } else { select.value = pageSize.toString(); } displayDifferences(); } else { showAlert('warning', '请输入1-1000之间的有效数字'); } } // 处理自定义差异页面大小输入框回车事件 function handleCustomDiffPageSizeEnter(event) { if (event.key === 'Enter') { setCustomDifferencePageSize(event.target.value); } } // 搜索差异结果 function searchDifferenceResults(searchTerm) { if (!currentResults) return; if (!searchTerm.trim()) { filteredDifferenceResults = currentResults.differences; } else { const term = searchTerm.toLowerCase(); filteredDifferenceResults = currentResults.differences.filter(diff => { // 搜索主键 const keyStr = JSON.stringify(diff.key).toLowerCase(); if (keyStr.includes(term)) return true; // 搜索字段名 if (diff.field && diff.field.toLowerCase().includes(term)) return true; // 搜索字段值 if (diff.pro_value && String(diff.pro_value).toLowerCase().includes(term)) return true; if (diff.test_value && String(diff.test_value).toLowerCase().includes(term)) return true; // 搜索消息 if (diff.message && diff.message.toLowerCase().includes(term)) return true; return false; }); } currentDifferencePage = 1; displayDifferences(); } // 复制到剪贴板 function copyToClipboard(text, button) { navigator.clipboard.writeText(text).then(function() { // 显示复制成功反馈 const originalIcon = button.innerHTML; button.innerHTML = ''; button.classList.add('btn-success'); button.classList.remove('btn-outline-secondary'); setTimeout(() => { button.innerHTML = originalIcon; button.classList.remove('btn-success'); button.classList.add('btn-outline-secondary'); }, 1000); }).catch(function(err) { console.error('复制失败:', err); showAlert('danger', '复制失败,请手动复制'); }); } // JS字符串转义 function escapeForJs(str) { if (str === null || str === undefined) { return ''; } return String(str) .replace(/\\/g, '\\\\') // 反斜杠必须先处理 .replace(/'/g, "\\'") // 单引号 .replace(/"/g, '\\"') // 双引号 .replace(/\n/g, '\\n') // 换行符 .replace(/\r/g, '\\r') // 回车符 .replace(/\t/g, '\\t') // 制表符 .replace(/\f/g, '\\f') // 换页符 .replace(/\b/g, '\\b') // 退格符 .replace(/\0/g, '\\0'); // 空字符 } // 显示原生数据 function showRawData(keyStr) { console.log('showRawData 调用,参数:', keyStr); if (!currentResults) { console.error('没有查询结果数据'); showAlert('warning', '请先执行查询操作'); return; } try { // 解析主键 const key = JSON.parse(keyStr); const keyValue = Object.values(key)[0]; console.log('解析主键成功:', key, '键值:', keyValue); // 查找对应的原生数据记录 let proRecord = null; let testRecord = null; if (currentResults.raw_pro_data && currentResults.raw_pro_data.length > 0) { proRecord = currentResults.raw_pro_data.find(record => Object.values(record).includes(keyValue) ); } if (currentResults.raw_test_data && currentResults.raw_test_data.length > 0) { testRecord = currentResults.raw_test_data.find(record => Object.values(record).includes(keyValue) ); } console.log('查找记录结果 - 生产:', !!proRecord, '测试:', !!testRecord); // 安全处理数据 const proData = proRecord ? JSON.stringify(proRecord, null, 2) : '{}'; const testData = testRecord ? JSON.stringify(testRecord, null, 2) : '{}'; // 创建重构后的简化模态框 const modalHtml = ` `; // 移除现有模态框 const existingModal = document.getElementById('rawDataModal'); if (existingModal) { existingModal.remove(); } // 添加新模态框 document.body.insertAdjacentHTML('beforeend', modalHtml); // 设置预格式化内容,避免模板字符串中的转义问题 const rawDataContent = document.getElementById('rawDataContent'); if (rawDataContent) { rawDataContent.textContent = proData + '\n\n' + testData; } // 存储数据用于复制功能 window.currentRawData = { pro: proData, test: testData, combined: proData + '\n\n' + testData }; // 显示模态框 const modal = new bootstrap.Modal(document.getElementById('rawDataModal')); modal.show(); console.log('重构后的原生数据模态框已显示'); } catch (error) { console.error('显示原生数据失败:', error); showAlert('danger', '显示原生数据失败: ' + error.message); } } // 复制原生数据 function copyRawData() { if (window.currentRawData) { const combinedData = window.currentRawData.combined || (window.currentRawData.pro + '\n\n' + window.currentRawData.test); navigator.clipboard.writeText(combinedData).then(() => { showAlert('success', '原生数据已复制到剪贴板'); }).catch(err => { console.error('复制失败:', err); showAlert('danger', '复制失败,请手动选择复制'); }); } else { showAlert('warning', '无可复制的数据'); } } // 显示差异数据的原生数据 function showDifferenceRawData(keyStr) { showRawData(keyStr); // 复用相同的原生数据显示逻辑 } // 显示比较总结 function displayComparisonSummary(summary) { const summaryContainer = document.getElementById('comparison-summary'); if (!summary) { summaryContainer.innerHTML = '

无总结数据

'; return; } const qualityLevel = summary.data_quality.quality_level; const html = `
数据概览

${summary.overview.total_keys_queried}

查询Key总数

${summary.overview.identical_records}

相同记录

${summary.overview.different_records}

差异记录
数据质量评估

${qualityLevel.level}

${qualityLevel.description}

${summary.data_quality.consistency_score}%
数据一致性评分
百分比统计
数据一致性: ${summary.percentages.data_consistency}%
数据差异率: ${summary.percentages.data_differences}%
数据缺失率: ${summary.percentages.missing_rate}%
字段差异TOP5
${summary.field_analysis.most_different_fields.length > 0 ? summary.field_analysis.most_different_fields.map(([field, count]) => `
${field} ${count}次
`).join('') : '

无字段差异统计

' }
改进建议
`; summaryContainer.innerHTML = html; } // 显示字段差异统计 function displayFieldStats(fieldStats) { const fieldStatsContainer = document.getElementById('field_stats'); if (!Object.keys(fieldStats).length) { fieldStatsContainer.innerHTML = '

无字段差异统计

'; return; } let html = '
'; html += ''; const totalDiffs = Object.values(fieldStats).reduce((sum, count) => sum + count, 0); Object.entries(fieldStats) .sort(([, a], [, b]) => b - a) .forEach(([field, count]) => { const percentage = ((count / totalDiffs) * 100).toFixed(1); html += ` `; }); html += '
字段名差异次数占比
${field} ${count} ${percentage}%
'; fieldStatsContainer.innerHTML = html; } // 清空结果 function clearResults() { document.getElementById('results').style.display = 'none'; document.getElementById('query_values').value = ''; currentResults = null; showAlert('info', '结果已清空'); } // 显示查询历史对话框 async function showQueryHistoryDialog() { try { const response = await fetch('/api/query-history'); const result = await response.json(); if (result.success) { let historyList = ''; if (result.data.length === 0) { historyList = '

暂无查询历史记录

'; } else { result.data.forEach(history => { const createdDate = new Date(history.created_at).toLocaleString(); const consistencyRate = history.total_keys > 0 ? Math.round((history.identical_count / history.total_keys) * 100) : 0; historyList += `
${history.name}

${history.description || '无描述'}

${createdDate}
查询: ${history.total_keys}个Key
一致性: ${consistencyRate}%
差异: ${history.differences_count}处
`; }); } const modalContent = ` `; // 移除现有modal const existingModal = document.getElementById('queryHistoryModal'); if (existingModal) { existingModal.remove(); } // 添加新modal到body document.body.insertAdjacentHTML('beforeend', modalContent); // 显示modal const modal = new bootstrap.Modal(document.getElementById('queryHistoryModal')); modal.show(); } else { showAlert('danger', '加载查询历史记录失败'); } } catch (error) { showAlert('danger', '加载查询历史记录失败: ' + error.message); } } // 加载历史记录配置 async function loadHistoryRecord(historyId) { try { const response = await fetch(`/api/query-history/${historyId}`); const result = await response.json(); if (result.success) { const history = result.data; // 填充生产环境配置 document.getElementById('pro_cluster_name').value = history.pro_config.cluster_name || ''; document.getElementById('pro_datacenter').value = history.pro_config.datacenter || ''; document.getElementById('pro_hosts').value = (history.pro_config.hosts || []).join(','); document.getElementById('pro_port').value = history.pro_config.port || 9042; document.getElementById('pro_username').value = history.pro_config.username || ''; document.getElementById('pro_password').value = history.pro_config.password || ''; document.getElementById('pro_keyspace').value = history.pro_config.keyspace || ''; document.getElementById('pro_table').value = history.pro_config.table || ''; // 填充测试环境配置 document.getElementById('test_cluster_name').value = history.test_config.cluster_name || ''; document.getElementById('test_datacenter').value = history.test_config.datacenter || ''; document.getElementById('test_hosts').value = (history.test_config.hosts || []).join(','); document.getElementById('test_port').value = history.test_config.port || 9042; document.getElementById('test_username').value = history.test_config.username || ''; document.getElementById('test_password').value = history.test_config.password || ''; document.getElementById('test_keyspace').value = history.test_config.keyspace || ''; document.getElementById('test_table').value = history.test_config.table || ''; // 填充查询配置 document.getElementById('keys').value = (history.query_config.keys || []).join(','); document.getElementById('fields_to_compare').value = (history.query_config.fields_to_compare || []).join(','); document.getElementById('exclude_fields').value = (history.query_config.exclude_fields || []).join(','); // 填充查询Key值 document.getElementById('query_values').value = (history.query_keys || []).join('\n'); // 关闭历史记录modal const modal = bootstrap.Modal.getInstance(document.getElementById('queryHistoryModal')); modal.hide(); showAlert('success', `历史记录 "${history.name}" 加载成功`); } else { showAlert('danger', result.error || '加载历史记录失败'); } } catch (error) { showAlert('danger', '加载历史记录失败: ' + error.message); } } // 查看历史记录详情 async function viewHistoryDetail(historyId) { try { const response = await fetch(`/api/query-history/${historyId}`); const result = await response.json(); if (result.success) { const history = result.data; const summary = history.results_summary; const modalContent = ` `; // 移除现有modal const existingModal = document.getElementById('historyDetailModal'); if (existingModal) { existingModal.remove(); } // 添加新modal到body document.body.insertAdjacentHTML('beforeend', modalContent); // 显示modal const modal = new bootstrap.Modal(document.getElementById('historyDetailModal')); modal.show(); } else { showAlert('danger', '加载历史记录详情失败'); } } catch (error) { showAlert('danger', '加载历史记录详情失败: ' + error.message); } } // 删除历史记录 async function deleteHistoryRecord(historyId, historyName) { if (!confirm(`确定要删除历史记录 "${historyName}" 吗?此操作不可撤销。`)) { return; } try { const response = await fetch(`/api/query-history/${historyId}`, { method: 'DELETE' }); const result = await response.json(); if (result.success) { showAlert('success', result.message); // 重新显示历史记录对话框 setTimeout(() => { showQueryHistoryDialog(); }, 500); } else { showAlert('danger', result.error || '删除历史记录失败'); } } catch (error) { showAlert('danger', '删除历史记录失败: ' + error.message); } } // 显示保存历史记录对话框 function showSaveHistoryDialog() { if (!currentResults) { showAlert('warning', '请先执行查询操作,然后再保存历史记录'); return; } const modalContent = ` `; // 移除现有modal const existingModal = document.getElementById('saveHistoryModal'); if (existingModal) { existingModal.remove(); } // 添加新modal到body document.body.insertAdjacentHTML('beforeend', modalContent); // 显示modal const modal = new bootstrap.Modal(document.getElementById('saveHistoryModal')); modal.show(); } // 保存历史记录 async function saveHistoryRecord() { const name = document.getElementById('historyRecordName').value.trim(); const description = document.getElementById('historyRecordDesc').value.trim(); if (!name) { showAlert('warning', '请输入历史记录名称'); return; } if (!currentResults) { showAlert('warning', '没有可保存的查询结果'); return; } const config = getCurrentConfig(); try { const response = await fetch('/api/query-history', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ name: name, description: description, pro_config: config.pro_config, test_config: config.test_config, query_config: { keys: config.keys, fields_to_compare: config.fields_to_compare, exclude_fields: config.exclude_fields }, query_keys: config.values, results_summary: currentResults.summary || {}, execution_time: 0.0, total_keys: currentResults.total_keys, differences_count: currentResults.differences.length, identical_count: currentResults.identical_results.length }) }); const result = await response.json(); if (result.success) { // 关闭modal const modal = bootstrap.Modal.getInstance(document.getElementById('saveHistoryModal')); modal.hide(); showAlert('success', result.message); } else { showAlert('danger', result.error || '保存历史记录失败'); } } catch (error) { showAlert('danger', '保存历史记录失败: ' + error.message); } } // 导出配置 function exportConfig() { const config = getCurrentConfig(); const dataStr = JSON.stringify(config, null, 2); const dataBlob = new Blob([dataStr], {type: 'application/json'}); const link = document.createElement('a'); link.href = URL.createObjectURL(dataBlob); link.download = 'db_compare_config.json'; link.click(); } // 导出结果 function exportResults() { if (!currentResults) { showAlert('warning', '没有可导出的结果'); return; } let output = `数据库查询比对结果\n`; output += `生成时间: ${new Date().toLocaleString()}\n`; output += `=`.repeat(50) + '\n\n'; output += `统计信息:\n`; output += `- 总Key数量: ${currentResults.total_keys}\n`; output += `- 生产表记录: ${currentResults.pro_count}\n`; output += `- 测试表记录: ${currentResults.test_count}\n`; output += `- 发现差异: ${currentResults.differences.length}\n\n`; if (currentResults.differences.length > 0) { output += `差异详情:\n`; output += `-`.repeat(30) + '\n'; currentResults.differences.forEach((diff, index) => { output += `差异 #${index + 1}:\n`; output += `主键: ${JSON.stringify(diff.key)}\n`; if (diff.message) { output += `消息: ${diff.message}\n`; } else { output += `字段: ${diff.field}\n`; output += `生产表值: ${diff.pro_value}\n`; output += `测试表值: ${diff.test_value}\n`; } output += '\n'; }); // 字段差异统计 if (Object.keys(currentResults.field_diff_count).length > 0) { output += `字段差异统计:\n`; output += `-`.repeat(30) + '\n'; Object.entries(currentResults.field_diff_count) .sort(([, a], [, b]) => b - a) .forEach(([field, count]) => { output += `${field}: ${count}次\n`; }); } } const dataBlob = new Blob([output], {type: 'text/plain;charset=utf-8'}); const link = document.createElement('a'); link.href = URL.createObjectURL(dataBlob); link.download = `db_compare_result_${new Date().toISOString().slice(0, 19).replace(/:/g, '-')}.txt`; link.click(); } // 显示一键导入对话框 function showImportDialog(env) { const envName = env === 'pro' ? '生产环境' : '测试环境'; const modalContent = ` `; // 移除现有modal const existingModal = document.getElementById('importModal'); if (existingModal) { existingModal.remove(); } // 添加新modal到body document.body.insertAdjacentHTML('beforeend', modalContent); // 显示modal const modal = new bootstrap.Modal(document.getElementById('importModal')); modal.show(); } // 导入配置数据 function importConfig(env) { const configData = document.getElementById('importConfigData').value.trim(); if (!configData) { showAlert('warning', '请输入配置数据'); return; } try { // 解析配置数据 const config = parseConfigData(configData); // 根据环境类型填充对应的表单字段 const prefix = env === 'pro' ? 'pro' : 'test'; if (config.clusterName) { document.getElementById(`${prefix}_cluster_name`).value = config.clusterName; } if (config.datacenter) { document.getElementById(`${prefix}_datacenter`).value = config.datacenter; } if (config.clusterNodes) { document.getElementById(`${prefix}_hosts`).value = config.clusterNodes; } if (config.port) { document.getElementById(`${prefix}_port`).value = config.port; } if (config.username) { document.getElementById(`${prefix}_username`).value = config.username; } if (config.password) { document.getElementById(`${prefix}_password`).value = config.password; } if (config.keyspace) { document.getElementById(`${prefix}_keyspace`).value = config.keyspace; } if (config.table) { document.getElementById(`${prefix}_table`).value = config.table; } // 关闭modal const modal = bootstrap.Modal.getInstance(document.getElementById('importModal')); modal.hide(); const envName = env === 'pro' ? '生产环境' : '测试环境'; showAlert('success', `${envName}配置导入成功!`); } catch (error) { showAlert('danger', '配置解析失败:' + error.message); } } // 解析配置数据 function parseConfigData(configText) { const config = {}; try { // 尝试按JSON格式解析 const jsonConfig = JSON.parse(configText); return jsonConfig; } catch (e) { // 如果不是JSON,按YAML格式解析 const lines = configText.split('\n'); for (const line of lines) { const trimmedLine = line.trim(); if (!trimmedLine || trimmedLine.startsWith('#')) { continue; // 跳过空行和注释 } const colonIndex = trimmedLine.indexOf(':'); if (colonIndex === -1) { continue; // 跳过无效行 } const key = trimmedLine.substring(0, colonIndex).trim(); let value = trimmedLine.substring(colonIndex + 1).trim(); // 处理带引号的值 if ((value.startsWith('"') && value.endsWith('"')) || (value.startsWith("'") && value.endsWith("'"))) { value = value.slice(1, -1); } // 尝试转换数字 if (!isNaN(value) && value !== '') { config[key] = parseInt(value); } else { config[key] = value; } } return config; } } // 显示提示信息 function showAlert(type, message) { // 移除已存在的提示 const existingAlert = document.querySelector('.alert'); if (existingAlert) { existingAlert.remove(); } const alertHtml = ` `; // 插入到页面顶部 const container = document.querySelector('.container-fluid'); container.insertAdjacentHTML('afterbegin', alertHtml); // 自动隐藏 setTimeout(() => { const alert = document.querySelector('.alert'); if (alert) { alert.remove(); } }, 5000); }