// 全局变量 let currentResults = null; let currentIdenticalPage = 1; let identicalPageSize = 10; let filteredIdenticalResults = []; // 页面加载完成后初始化 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; // 显示各个面板内容 displayDifferences(results.differences); 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.identical_results.length}

相同记录

${results.differences.length}

差异记录

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

一致性比例

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

未发现差异

'; return; } let html = ''; differences.forEach((diff, index) => { if (diff.message) { // 记录不存在的情况 html += `
差异 #${index + 1}

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

${diff.message}

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

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

字段: ${diff.field}

${diff.field}
生产环境
${escapeHtml(diff.pro_value)}
测试环境
${escapeHtml(diff.test_value)}
`; } }); 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] || ''; const isJson = (proValue.includes('{') || proValue.includes('[')) || (testValue.includes('{') || testValue.includes('[')); html += `
${fieldName} ${isJson ? 'JSON' : ''}
生产环境
${escapeHtml(proValue)}
测试环境
${escapeHtml(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) { identicalPageSize = parseInt(newSize); currentIdenticalPage = 1; displayIdenticalResults(); } // 搜索相同结果 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 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) { return str.replace(/'/g, "\\'").replace(/"/g, '\\"').replace(/\n/g, '\\n').replace(/\r/g, '\\r'); } // 显示原生数据 function showRawData(keyStr) { if (!currentResults) return; try { const key = JSON.parse(keyStr); const keyValue = Object.values(key)[0]; // 在原生数据中查找匹配的记录 const proRecord = currentResults.raw_pro_data.find(record => Object.values(record).includes(keyValue) ); const testRecord = currentResults.raw_test_data.find(record => Object.values(record).includes(keyValue) ); let modalContent = ` `; // 移除现有modal const existingModal = document.getElementById('rawDataModal'); if (existingModal) { existingModal.remove(); } // 添加新modal到body document.body.insertAdjacentHTML('beforeend', modalContent); // 显示modal const modal = new bootstrap.Modal(document.getElementById('rawDataModal')); modal.show(); } catch (e) { showAlert('danger', '无法显示原生数据:' + e.message); } } // 显示差异数据的原生数据 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', '结果已清空'); } // 导出配置 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); }