## 重写DNS日志详情弹窗方案 ### 1. 问题分析 当前弹窗代码存在以下问题: - 代码结构复杂,大量嵌套条件和重复逻辑 - 样式不够现代,布局不够灵活 - 响应式设计不足,在大屏幕上显示效果不佳 - 代码冗余,重复的记录解析逻辑 - 可维护性差,HTML结构直接写在JavaScript字符串中 ### 2. 设计思路 - **组件化设计**:将弹窗拆分为更小的可复用组件 - **简化逻辑**:重构DNS解析记录处理逻辑,减少重复代码 - **现代样式**:使用更现代的CSS布局和样式 - **响应式设计**:优化在不同屏幕尺寸下的显示效果 - **更好的用户体验**:添加动画效果、改进交互体验 - **更清晰的代码结构**:提高可维护性 ### 3. 实现方案 #### 3.1 重构`showLogDetailModal`函数 - 简化函数结构,分离关注点 - 将DNS解析记录处理逻辑提取为独立函数 - 使用更现代的DOM API创建元素 #### 3.2 优化DNS解析记录处理 - 创建独立的`formatDNSRecords`函数 - 统一处理各种格式的DNS记录 - 减少重复代码,提高可维护性 #### 3.3 现代CSS样式 - 使用更现代的CSS类名和布局 - 优化响应式设计,支持不同屏幕尺寸 - 添加动画效果,提升用户体验 #### 3.4 改进HTML结构 - 使用更清晰的语义化HTML - 优化布局结构,提高可读性 - 支持更好的响应式设计 ### 4. 代码实现 ```javascript // 独立的DNS记录格式化函数 function formatDNSRecords(log, result) { if (result === 'blocked') return '无'; let records = ''; const sources = [ log.answers, log.answer, log.Records, log.records, log.response ]; for (const source of sources) { if (records) break; if (!source || source === '无') continue; // 处理数组类型 if (Array.isArray(source)) { records = source.map(answer => { const type = answer.type || answer.Type || '未知'; let value = answer.value || answer.Value || answer.data || answer.Data || '未知'; const ttl = answer.TTL || answer.ttl || answer.expires || '未知'; // 增强的记录值提取逻辑 if (typeof value === 'string') { value = value.trim(); // 处理制表符分隔的格式 if (value.includes('\t') || value.includes('\\t')) { const parts = value.replace(/\\t/g, '\t').split('\t'); if (parts.length >= 4) { value = parts[parts.length - 1].trim(); } } // 处理JSON格式 else if (value.startsWith('{') && value.endsWith('}')) { try { const parsed = JSON.parse(value); value = parsed.data || parsed.value || value; } catch (e) {} } } return `${type}: ${value} (ttl=${ttl})`; }).join('\n').trim(); } // 处理字符串类型 else if (typeof source === 'string') { // 尝试解析为JSON数组 if (source.startsWith('[') && source.endsWith(']')) { try { const parsed = JSON.parse(source); if (Array.isArray(parsed)) { records = parsed.map(answer => { const type = answer.type || answer.Type || '未知'; let value = answer.value || answer.Value || answer.data || answer.Data || '未知'; const ttl = answer.TTL || answer.ttl || answer.expires || '未知'; if (typeof value === 'string') { value = value.trim(); } return `${type}: ${value} (ttl=${ttl})`; }).join('\n').trim(); } } catch (e) { // 解析失败,尝试直接格式化 records = formatDNSString(source); } } else { // 直接格式化字符串 records = formatDNSString(source); } } } return records || '无解析记录'; } // 格式化DNS字符串记录 function formatDNSString(str) { const recordLines = str.split('\n').map(line => line.trim()).filter(line => line !== ''); return recordLines.map(line => { // 检查是否已经是标准格式 if (line.includes(':') && line.includes('(')) { return line; } // 尝试解析为标准DNS格式 const parts = line.split(/\s+/); if (parts.length >= 5) { const type = parts[3]; const value = parts.slice(4).join(' '); const ttl = parts[1]; return `${type}: ${value} (ttl=${ttl})`; } // 无法解析,返回原始行 return line; }).join('\n'); } // 重写后的showLogDetailModal函数 async function showLogDetailModal(log) { if (!log) { console.error('No log data provided!'); return; } try { // 安全获取log属性,提供默认值 const timestamp = log.timestamp ? new Date(log.timestamp) : null; const dateStr = timestamp ? timestamp.toLocaleDateString() : '未知'; const timeStr = timestamp ? timestamp.toLocaleTimeString() : '未知'; const domain = log.domain || '未知'; const queryType = log.queryType || '未知'; const result = log.result || '未知'; const responseTime = log.responseTime || '未知'; const clientIP = log.clientIP || '未知'; const location = log.location || '未知'; const fromCache = log.fromCache || false; const dnssec = log.dnssec || false; const edns = log.edns || false; const dnsServer = log.dnsServer || '无'; const dnssecServer = log.dnssecServer || '无'; const blockRule = log.blockRule || '无'; // 检查域名是否在跟踪器数据库中 const trackerInfo = await isDomainInTrackerDatabase(log.domain); const isTracker = trackerInfo !== null; // 格式化DNS解析记录 const dnsRecords = formatDNSRecords(log, result); // 创建模态框容器 const modalContainer = document.createElement('div'); modalContainer.className = 'fixed inset-0 bg-black bg-opacity-50 z-50 flex items-center justify-center p-4 animate-fade-in'; modalContainer.style.zIndex = '9999'; // 创建模态框内容 const modalContent = document.createElement('div'); modalContent.className = 'bg-white rounded-xl shadow-2xl w-full max-w-2xl max-h-[90vh] overflow-y-auto animate-slide-in'; // 创建标题栏 const header = document.createElement('div'); header.className = 'sticky top-0 bg-white border-b border-gray-200 px-6 py-4 flex justify-between items-center'; const title = document.createElement('h3'); title.className = 'text-xl font-semibold text-gray-900'; title.textContent = '日志详情'; const closeButton = document.createElement('button'); closeButton.innerHTML = ''; closeButton.className = 'text-gray-500 hover:text-gray-700 focus:outline-none transition-colors'; closeButton.onclick = () => closeModal(); header.appendChild(title); header.appendChild(closeButton); // 创建内容区域 const content = document.createElement('div'); content.className = 'p-6 space-y-6'; // 基本信息部分 const basicInfo = document.createElement('div'); basicInfo.className = 'space-y-4'; const basicInfoTitle = document.createElement('h4'); basicInfoTitle.className = 'text-sm font-medium text-gray-700 uppercase tracking-wider'; basicInfoTitle.textContent = '基本信息'; const basicInfoGrid = document.createElement('div'); basicInfoGrid.className = 'grid grid-cols-1 md:grid-cols-2 gap-4'; // 添加基本信息项 basicInfoGrid.innerHTML = `
日期
${dateStr}
时间
${timeStr}
状态
${result === 'blocked' ? '已拦截' : result === 'allowed' ? '允许' : result}
域名
${domain}
类型
${queryType}
`; // DNS特性 const dnsFeatures = document.createElement('div'); dnsFeatures.className = 'col-span-1 md:col-span-2 space-y-1'; dnsFeatures.innerHTML = `
DNS特性
${dnssec ? 'DNSSEC ' : ''} ${edns ? 'EDNS' : ''} ${!dnssec && !edns ? '无' : ''}
`; // 跟踪器信息 const trackerDiv = document.createElement('div'); trackerDiv.className = 'col-span-1 md:col-span-2 space-y-1'; trackerDiv.innerHTML = `
跟踪器信息
${isTracker ? `
${trackerInfo.name} (${trackersDatabase.categories[trackerInfo.categoryId] || '未知'})
` : '无'}
`; // 解析记录 const recordsDiv = document.createElement('div'); recordsDiv.className = 'col-span-1 md:col-span-2 space-y-1'; recordsDiv.innerHTML = `
解析记录
${dnsRecords}
`; // DNS服务器 const dnsServerDiv = document.createElement('div'); dnsServerDiv.className = 'col-span-1 md:col-span-2 space-y-1'; dnsServerDiv.innerHTML = `
DNS服务器
${dnsServer}
`; // DNSSEC专用服务器 const dnssecServerDiv = document.createElement('div'); dnssecServerDiv.className = 'col-span-1 md:col-span-2 space-y-1'; dnssecServerDiv.innerHTML = `
DNSSEC专用服务器
${dnssecServer}
`; basicInfoGrid.appendChild(dnsFeatures); basicInfoGrid.appendChild(trackerDiv); basicInfoGrid.appendChild(recordsDiv); basicInfoGrid.appendChild(dnsServerDiv); basicInfoGrid.appendChild(dnssecServerDiv); basicInfo.appendChild(basicInfoTitle); basicInfo.appendChild(basicInfoGrid); // 响应细节部分 const responseDetails = document.createElement('div'); responseDetails.className = 'space-y-4 pt-4 border-t border-gray-200'; const responseDetailsTitle = document.createElement('h4'); responseDetailsTitle.className = 'text-sm font-medium text-gray-700 uppercase tracking-wider'; responseDetailsTitle.textContent = '响应细节'; const responseGrid = document.createElement('div'); responseGrid.className = 'grid grid-cols-1 md:grid-cols-2 gap-4'; responseGrid.innerHTML = `
响应时间
${responseTime}毫秒
规则
${blockRule}
响应代码
${getResponseCodeText(log.responseCode)}
缓存状态
${fromCache ? '缓存' : '非缓存'}
`; responseDetails.appendChild(responseDetailsTitle); responseDetails.appendChild(responseGrid); // 客户端详情部分 const clientDetails = document.createElement('div'); clientDetails.className = 'space-y-4 pt-4 border-t border-gray-200'; const clientDetailsTitle = document.createElement('h4'); clientDetailsTitle.className = 'text-sm font-medium text-gray-700 uppercase tracking-wider'; clientDetailsTitle.textContent = '客户端详情'; const clientIPDiv = document.createElement('div'); clientIPDiv.className = 'space-y-1'; clientIPDiv.innerHTML = `
IP地址
${clientIP} (${location})
`; clientDetails.appendChild(clientDetailsTitle); clientDetails.appendChild(clientIPDiv); // 组装内容 content.appendChild(basicInfo); content.appendChild(responseDetails); content.appendChild(clientDetails); // 组装模态框 modalContent.appendChild(header); modalContent.appendChild(content); modalContainer.appendChild(modalContent); // 添加到页面 document.body.appendChild(modalContainer); // 关闭模态框函数 function closeModal() { modalContainer.classList.add('animate-fade-out'); modalContent.classList.add('animate-slide-out'); // 等待动画结束后移除元素 setTimeout(() => { document.body.removeChild(modalContainer); }, 300); } // 点击外部关闭 modalContainer.addEventListener('click', (e) => { if (e.target === modalContainer) { closeModal(); } }); // ESC键关闭 const handleEsc = (e) => { if (e.key === 'Escape') { closeModal(); document.removeEventListener('keydown', handleEsc); } }; document.addEventListener('keydown', handleEsc); } catch (error) { console.error('Error showing log detail modal:', error); } }