完善查询
This commit is contained in:
362
static/js/app.js
362
static/js/app.js
@@ -718,6 +718,13 @@ function getShardingConfig() {
|
||||
enabled: document.getElementById('enableSharding').checked,
|
||||
use_sharding_for_pro: document.getElementById('use_sharding_for_pro').checked,
|
||||
use_sharding_for_test: document.getElementById('use_sharding_for_test').checked,
|
||||
// 生产环境分表参数
|
||||
pro_interval_seconds: parseInt(document.getElementById('pro_interval_seconds').value) || 604800,
|
||||
pro_table_count: parseInt(document.getElementById('pro_table_count').value) || 14,
|
||||
// 测试环境分表参数
|
||||
test_interval_seconds: parseInt(document.getElementById('test_interval_seconds').value) || 604800,
|
||||
test_table_count: parseInt(document.getElementById('test_table_count').value) || 14,
|
||||
// 保持向后兼容的通用参数
|
||||
interval_seconds: parseInt(document.getElementById('pro_interval_seconds').value) || 604800,
|
||||
table_count: parseInt(document.getElementById('pro_table_count').value) || 14
|
||||
}
|
||||
@@ -736,6 +743,11 @@ function displayResults(results) {
|
||||
document.getElementById('diff-count').textContent = results.differences.length;
|
||||
document.getElementById('identical-count').textContent = results.identical_results.length;
|
||||
|
||||
// 计算原始数据总数
|
||||
const totalRawData = (results.raw_pro_data ? results.raw_pro_data.length : 0) +
|
||||
(results.raw_test_data ? results.raw_test_data.length : 0);
|
||||
document.getElementById('rawdata-count').textContent = totalRawData;
|
||||
|
||||
// 初始化相同结果分页数据
|
||||
filteredIdenticalResults = results.identical_results;
|
||||
currentIdenticalPage = 1;
|
||||
@@ -747,6 +759,7 @@ function displayResults(results) {
|
||||
// 显示各个面板内容
|
||||
displayDifferences();
|
||||
displayIdenticalResults();
|
||||
displayRawData(results);
|
||||
displayComparisonSummary(results.summary);
|
||||
|
||||
// 显示结果区域
|
||||
@@ -3202,4 +3215,353 @@ function autoRefreshLogsAfterQuery() {
|
||||
setTimeout(() => {
|
||||
refreshQueryLogs();
|
||||
}, 500);
|
||||
}
|
||||
|
||||
// 原始数据相关变量
|
||||
let currentRawData = null;
|
||||
let filteredRawData = [];
|
||||
let currentRawDataPage = 1;
|
||||
let rawDataPageSize = 20;
|
||||
|
||||
// 显示原始数据
|
||||
function displayRawData(results) {
|
||||
currentRawData = results;
|
||||
|
||||
// 添加调试信息
|
||||
console.log('displayRawData - results.query_config:', results.query_config);
|
||||
console.log('displayRawData - 当前数据键:', Object.keys(results));
|
||||
|
||||
// 合并生产和测试环境数据
|
||||
const combinedData = [];
|
||||
|
||||
if (results.raw_pro_data) {
|
||||
results.raw_pro_data.forEach(record => {
|
||||
combinedData.push({
|
||||
environment: 'production',
|
||||
data: record,
|
||||
displayName: '生产环境'
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
if (results.raw_test_data) {
|
||||
results.raw_test_data.forEach(record => {
|
||||
combinedData.push({
|
||||
environment: 'test',
|
||||
data: record,
|
||||
displayName: '测试环境'
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// 按环境排序,生产环境在前
|
||||
combinedData.sort((a, b) => {
|
||||
if (a.environment === 'production' && b.environment === 'test') return -1;
|
||||
if (a.environment === 'test' && b.environment === 'production') return 1;
|
||||
return 0;
|
||||
});
|
||||
|
||||
filteredRawData = combinedData;
|
||||
currentRawDataPage = 1;
|
||||
renderRawDataContent();
|
||||
}
|
||||
|
||||
// 渲染原始数据内容
|
||||
function renderRawDataContent() {
|
||||
const container = document.getElementById('raw-data-content');
|
||||
|
||||
if (!filteredRawData.length) {
|
||||
container.innerHTML = '<div class="alert alert-info">没有原始数据可显示</div>';
|
||||
return;
|
||||
}
|
||||
|
||||
// 分页计算
|
||||
const totalPages = Math.ceil(filteredRawData.length / rawDataPageSize);
|
||||
const startIndex = (currentRawDataPage - 1) * rawDataPageSize;
|
||||
const endIndex = startIndex + rawDataPageSize;
|
||||
const currentPageData = filteredRawData.slice(startIndex, endIndex);
|
||||
|
||||
let html = `
|
||||
<!-- 分页控制 -->
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-6">
|
||||
<div class="d-flex align-items-center">
|
||||
<label class="form-label me-2 mb-0">每页显示:</label>
|
||||
<select class="form-select form-select-sm me-2" style="width: auto;" onchange="changeRawDataPageSize(this.value)">
|
||||
<option value="10" ${rawDataPageSize == 10 ? 'selected' : ''}>10条</option>
|
||||
<option value="20" ${rawDataPageSize == 20 ? 'selected' : ''}>20条</option>
|
||||
<option value="50" ${rawDataPageSize == 50 ? 'selected' : ''}>50条</option>
|
||||
<option value="100" ${rawDataPageSize == 100 ? 'selected' : ''}>100条</option>
|
||||
</select>
|
||||
<span class="ms-2 text-muted">共 ${filteredRawData.length} 条记录</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="d-flex justify-content-end align-items-center">
|
||||
${generateRawDataPagination(currentRawDataPage, totalPages)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
// 显示数据记录
|
||||
currentPageData.forEach((item, index) => {
|
||||
const globalIndex = startIndex + index + 1;
|
||||
const envBadgeClass = item.environment === 'production' ? 'bg-primary' : 'bg-success';
|
||||
|
||||
// 获取主键值用于标识 - 修复主键解析问题
|
||||
let keyValue = 'N/A';
|
||||
|
||||
// 从当前配置获取keys,因为query_config可能不在results中
|
||||
const currentConfig = getCurrentConfig();
|
||||
console.log('渲染原始数据 - currentConfig.query_config.keys:', currentConfig.query_config.keys);
|
||||
console.log('渲染原始数据 - item.data keys:', Object.keys(item.data));
|
||||
|
||||
if (currentConfig && currentConfig.query_config && currentConfig.query_config.keys && currentConfig.query_config.keys.length > 0) {
|
||||
const keyFields = currentConfig.query_config.keys;
|
||||
const keyValues = keyFields.map(key => {
|
||||
const value = item.data[key];
|
||||
console.log(`查找主键字段 ${key}: `, value);
|
||||
return value || 'N/A';
|
||||
}).filter(val => val !== 'N/A');
|
||||
|
||||
if (keyValues.length > 0) {
|
||||
keyValue = keyValues.join(', ');
|
||||
}
|
||||
} else {
|
||||
// 如果没有配置主键,尝试常见的主键字段名
|
||||
const commonKeyFields = ['id', 'statusid', 'key', 'uuid'];
|
||||
for (const field of commonKeyFields) {
|
||||
if (item.data[field] !== undefined) {
|
||||
keyValue = item.data[field];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 格式化JSON数据
|
||||
const jsonData = JSON.stringify(item.data, null, 2);
|
||||
|
||||
html += `
|
||||
<div class="card mb-3 border-${item.environment === 'production' ? 'primary' : 'success'}">
|
||||
<div class="card-header bg-light">
|
||||
<div class="row align-items-center">
|
||||
<div class="col">
|
||||
<strong>记录 #${globalIndex}</strong>
|
||||
<span class="badge ${envBadgeClass} ms-2">${item.displayName}</span>
|
||||
</div>
|
||||
<div class="col-auto">
|
||||
<button class="btn btn-sm btn-outline-secondary me-2" type="button"
|
||||
data-bs-toggle="collapse" data-bs-target="#rawCollapse${globalIndex}">
|
||||
<i class="fas fa-eye"></i> 查看详情
|
||||
</button>
|
||||
<button class="btn btn-sm btn-outline-info" onclick='copyRawRecord(${JSON.stringify(JSON.stringify(item.data))})'>
|
||||
<i class="fas fa-copy"></i> 复制
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<p class="mb-0 mt-2"><strong>主键:</strong> ${keyValue}</p>
|
||||
</div>
|
||||
<div class="collapse" id="rawCollapse${globalIndex}">
|
||||
<div class="card-body">
|
||||
<pre class="bg-light p-3 rounded" style="max-height: 500px; overflow-y: auto; font-size: 0.9em; line-height: 1.4;">${escapeHtml(jsonData)}</pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
});
|
||||
|
||||
// 底部分页
|
||||
if (totalPages > 1) {
|
||||
html += `
|
||||
<div class="row mt-3">
|
||||
<div class="col-12">
|
||||
<div class="d-flex justify-content-center">
|
||||
${generateRawDataPagination(currentRawDataPage, totalPages)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
container.innerHTML = html;
|
||||
}
|
||||
|
||||
// 生成原始数据分页导航
|
||||
function generateRawDataPagination(currentPage, totalPages) {
|
||||
if (totalPages <= 1) return '';
|
||||
|
||||
let html = '<nav><ul class="pagination pagination-sm mb-0">';
|
||||
|
||||
// 上一页
|
||||
html += `
|
||||
<li class="page-item ${currentPage === 1 ? 'disabled' : ''}">
|
||||
<a class="page-link" href="#" onclick="goToRawDataPage(${currentPage - 1})">
|
||||
<i class="fas fa-chevron-left"></i>
|
||||
</a>
|
||||
</li>
|
||||
`;
|
||||
|
||||
// 页码
|
||||
const startPage = Math.max(1, currentPage - 2);
|
||||
const endPage = Math.min(totalPages, currentPage + 2);
|
||||
|
||||
if (startPage > 1) {
|
||||
html += '<li class="page-item"><a class="page-link" href="#" onclick="goToRawDataPage(1)">1</a></li>';
|
||||
if (startPage > 2) {
|
||||
html += '<li class="page-item disabled"><span class="page-link">...</span></li>';
|
||||
}
|
||||
}
|
||||
|
||||
for (let i = startPage; i <= endPage; i++) {
|
||||
html += `
|
||||
<li class="page-item ${i === currentPage ? 'active' : ''}">
|
||||
<a class="page-link" href="#" onclick="goToRawDataPage(${i})">${i}</a>
|
||||
</li>
|
||||
`;
|
||||
}
|
||||
|
||||
if (endPage < totalPages) {
|
||||
if (endPage < totalPages - 1) {
|
||||
html += '<li class="page-item disabled"><span class="page-link">...</span></li>';
|
||||
}
|
||||
html += `<li class="page-item"><a class="page-link" href="#" onclick="goToRawDataPage(${totalPages})">${totalPages}</a></li>`;
|
||||
}
|
||||
|
||||
// 下一页
|
||||
html += `
|
||||
<li class="page-item ${currentPage === totalPages ? 'disabled' : ''}">
|
||||
<a class="page-link" href="#" onclick="goToRawDataPage(${currentPage + 1})">
|
||||
<i class="fas fa-chevron-right"></i>
|
||||
</a>
|
||||
</li>
|
||||
`;
|
||||
|
||||
html += '</ul></nav>';
|
||||
return html;
|
||||
}
|
||||
|
||||
// 跳转到指定原始数据页面
|
||||
function goToRawDataPage(page) {
|
||||
const totalPages = Math.ceil(filteredRawData.length / rawDataPageSize);
|
||||
if (page < 1 || page > totalPages) return;
|
||||
|
||||
currentRawDataPage = page;
|
||||
renderRawDataContent();
|
||||
}
|
||||
|
||||
// 改变原始数据每页显示数量
|
||||
function changeRawDataPageSize(newSize) {
|
||||
rawDataPageSize = parseInt(newSize);
|
||||
currentRawDataPage = 1;
|
||||
renderRawDataContent();
|
||||
}
|
||||
|
||||
// 过滤原始数据(按环境)
|
||||
function filterRawData() {
|
||||
if (!currentRawData) return;
|
||||
|
||||
const showPro = document.getElementById('showProData').checked;
|
||||
const showTest = document.getElementById('showTestData').checked;
|
||||
|
||||
let combinedData = [];
|
||||
|
||||
if (showPro && currentRawData.raw_pro_data) {
|
||||
currentRawData.raw_pro_data.forEach(record => {
|
||||
combinedData.push({
|
||||
environment: 'production',
|
||||
data: record,
|
||||
displayName: '生产环境'
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
if (showTest && currentRawData.raw_test_data) {
|
||||
currentRawData.raw_test_data.forEach(record => {
|
||||
combinedData.push({
|
||||
environment: 'test',
|
||||
data: record,
|
||||
displayName: '测试环境'
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// 按环境排序
|
||||
combinedData.sort((a, b) => {
|
||||
if (a.environment === 'production' && b.environment === 'test') return -1;
|
||||
if (a.environment === 'test' && b.environment === 'production') return 1;
|
||||
return 0;
|
||||
});
|
||||
|
||||
filteredRawData = combinedData;
|
||||
currentRawDataPage = 1;
|
||||
renderRawDataContent();
|
||||
}
|
||||
|
||||
// 搜索原始数据
|
||||
function searchRawData(searchTerm) {
|
||||
if (!currentRawData) return;
|
||||
|
||||
const showPro = document.getElementById('showProData').checked;
|
||||
const showTest = document.getElementById('showTestData').checked;
|
||||
|
||||
let combinedData = [];
|
||||
|
||||
if (showPro && currentRawData.raw_pro_data) {
|
||||
currentRawData.raw_pro_data.forEach(record => {
|
||||
combinedData.push({
|
||||
environment: 'production',
|
||||
data: record,
|
||||
displayName: '生产环境'
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
if (showTest && currentRawData.raw_test_data) {
|
||||
currentRawData.raw_test_data.forEach(record => {
|
||||
combinedData.push({
|
||||
environment: 'test',
|
||||
data: record,
|
||||
displayName: '测试环境'
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
if (searchTerm.trim()) {
|
||||
const term = searchTerm.toLowerCase();
|
||||
combinedData = combinedData.filter(item => {
|
||||
// 搜索所有字段值
|
||||
return Object.values(item.data).some(value =>
|
||||
String(value).toLowerCase().includes(term)
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
// 按环境排序
|
||||
combinedData.sort((a, b) => {
|
||||
if (a.environment === 'production' && b.environment === 'test') return -1;
|
||||
if (a.environment === 'test' && b.environment === 'production') return 1;
|
||||
return 0;
|
||||
});
|
||||
|
||||
filteredRawData = combinedData;
|
||||
currentRawDataPage = 1;
|
||||
renderRawDataContent();
|
||||
}
|
||||
|
||||
// 复制原始记录
|
||||
function copyRawRecord(recordStr) {
|
||||
try {
|
||||
const record = JSON.parse(recordStr);
|
||||
const formattedData = JSON.stringify(record, null, 2);
|
||||
|
||||
navigator.clipboard.writeText(formattedData).then(() => {
|
||||
showAlert('success', '原始记录已复制到剪贴板');
|
||||
}).catch(err => {
|
||||
console.error('复制失败:', err);
|
||||
showAlert('danger', '复制失败,请手动选择复制');
|
||||
});
|
||||
} catch (error) {
|
||||
showAlert('danger', '复制失败: ' + error.message);
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user