// 屏蔽管理页面功能实现 // 初始化屏蔽管理页面 async function initShieldPage() { // 并行加载所有数据 await Promise.all([ loadShieldStats(), loadLocalRules(), loadRemoteBlacklists() ]); // 设置事件监听器 setupShieldEventListeners(); } // 加载屏蔽规则统计信息 async function loadShieldStats() { try { const response = await fetch('/api/shield'); if (!response.ok) { throw new Error(`加载失败: ${response.status}`); } const stats = await response.json(); // 更新统计信息 const elements = [ { id: 'domain-rules-count', value: stats.domainRulesCount }, { id: 'domain-exceptions-count', value: stats.domainExceptionsCount }, { id: 'regex-rules-count', value: stats.regexRulesCount }, { id: 'regex-exceptions-count', value: stats.regexExceptionsCount }, { id: 'hosts-rules-count', value: stats.hostsRulesCount }, { id: 'blacklist-count', value: stats.blacklistCount } ]; elements.forEach(item => { const element = document.getElementById(item.id); if (element) { element.textContent = item.value || 0; } }); } catch (error) { console.error('加载屏蔽规则统计信息失败:', error); showErrorMessage('加载屏蔽规则统计信息失败'); } } // 加载本地规则 async function loadLocalRules() { try { const response = await fetch('/api/shield/localrules'); if (!response.ok) { throw new Error(`加载失败: ${response.status}`); } const data = await response.json(); // 更新本地规则数量显示 if (document.getElementById('local-rules-count')) { document.getElementById('local-rules-count').textContent = data.localRulesCount || 0; } // 设置当前规则类型 currentRulesType = 'local'; // 合并所有本地规则 let rules = []; // 添加域名规则 if (Array.isArray(data.domainRules)) { rules = rules.concat(data.domainRules); } // 添加域名排除规则 if (Array.isArray(data.domainExceptions)) { rules = rules.concat(data.domainExceptions); } // 添加正则规则 if (Array.isArray(data.regexRules)) { rules = rules.concat(data.regexRules); } // 添加正则排除规则 if (Array.isArray(data.regexExceptions)) { rules = rules.concat(data.regexExceptions); } updateRulesTable(rules); } catch (error) { console.error('加载本地规则失败:', error); showErrorMessage('加载本地规则失败'); } } // 加载远程规则 async function loadRemoteRules() { try { // 设置当前规则类型 currentRulesType = 'remote'; const response = await fetch('/api/shield/remoterules'); if (!response.ok) { throw new Error(`加载失败: ${response.status}`); } const data = await response.json(); // 更新远程规则数量显示 if (document.getElementById('remote-rules-count')) { document.getElementById('remote-rules-count').textContent = data.remoteRulesCount || 0; } // 合并所有远程规则 let rules = []; // 添加域名规则 if (Array.isArray(data.domainRules)) { rules = rules.concat(data.domainRules); } // 添加域名排除规则 if (Array.isArray(data.domainExceptions)) { rules = rules.concat(data.domainExceptions); } // 添加正则规则 if (Array.isArray(data.regexRules)) { rules = rules.concat(data.regexRules); } // 添加正则排除规则 if (Array.isArray(data.regexExceptions)) { rules = rules.concat(data.regexExceptions); } updateRulesTable(rules); } catch (error) { console.error('加载远程规则失败:', error); showErrorMessage('加载远程规则失败'); } } // 更新规则表格 function updateRulesTable(rules) { const tbody = document.getElementById('rules-table-body'); // 清空表格 tbody.innerHTML = ''; if (rules.length === 0) { const emptyRow = document.createElement('tr'); emptyRow.innerHTML = '暂无规则'; tbody.appendChild(emptyRow); return; } // 对于大量规则,限制显示数量 const maxRulesToShow = 1000; // 限制最大显示数量 const rulesToShow = rules.length > maxRulesToShow ? rules.slice(0, maxRulesToShow) : rules; // 使用DocumentFragment提高性能 const fragment = document.createDocumentFragment(); rulesToShow.forEach(rule => { const tr = document.createElement('tr'); tr.className = 'border-b border-gray-200'; const tdRule = document.createElement('td'); tdRule.className = 'py-3 px-4'; tdRule.textContent = rule; const tdAction = document.createElement('td'); tdAction.className = 'py-3 px-4 text-right'; const deleteBtn = document.createElement('button'); deleteBtn.className = 'delete-rule-btn px-3 py-1 bg-danger text-white rounded-md hover:bg-danger/90 transition-colors text-sm'; deleteBtn.dataset.rule = rule; // 创建删除图标 const deleteIcon = document.createElement('i'); deleteIcon.className = 'fa fa-trash'; deleteIcon.style.pointerEvents = 'none'; // 防止图标拦截点击事件 deleteBtn.appendChild(deleteIcon); // 使用普通函数,确保this指向按钮元素 deleteBtn.onclick = function(e) { e.stopPropagation(); // 阻止事件冒泡 handleDeleteRule(e); }; tdAction.appendChild(deleteBtn); tr.appendChild(tdRule); tr.appendChild(tdAction); fragment.appendChild(tr); }); // 一次性添加所有行到DOM tbody.appendChild(fragment); // 如果有更多规则,添加提示 if (rules.length > maxRulesToShow) { const infoRow = document.createElement('tr'); infoRow.innerHTML = `显示前 ${maxRulesToShow} 条规则,共 ${rules.length} 条`; tbody.appendChild(infoRow); } } // 处理删除规则 async function handleDeleteRule(e) { console.log('Delete button clicked'); let deleteBtn; // 尝试从事件目标获取按钮元素 deleteBtn = e.target.closest('.delete-rule-btn'); console.log('Delete button from event target:', deleteBtn); // 尝试从this获取按钮元素(备用方案) if (!deleteBtn && this && typeof this.classList === 'object' && this.classList) { if (this.classList.contains('delete-rule-btn')) { deleteBtn = this; console.log('Delete button from this:', deleteBtn); } } if (!deleteBtn) { console.error('Delete button not found'); return; } const rule = deleteBtn.dataset.rule; console.log('Rule to delete:', rule); if (!rule) { console.error('Rule not found in data-rule attribute'); return; } try { console.log('Sending DELETE request to /api/shield'); const response = await fetch('/api/shield', { method: 'DELETE', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ rule }) }); console.log('Response status:', response.status); console.log('Response ok:', response.ok); if (!response.ok) { const errorText = await response.text(); throw new Error(`Failed to delete rule: ${response.status} ${errorText}`); } const responseData = await response.json(); console.log('Response data:', responseData); showSuccessMessage('规则删除成功'); console.log('Current rules type:', currentRulesType); // 根据当前显示的规则类型重新加载对应的规则列表 if (currentRulesType === 'local') { console.log('Reloading local rules'); loadLocalRules(); } else { console.log('Reloading remote rules'); loadRemoteRules(); } // 重新加载统计信息 loadShieldStats(); } catch (error) { console.error('Error deleting rule:', error); showErrorMessage('删除规则失败: ' + error.message); } } // 添加新规则 async function handleAddRule() { const rule = document.getElementById('new-rule').value.trim(); if (!rule) { showErrorMessage('规则不能为空'); return; } try { const response = await fetch('/api/shield', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ rule }) }); if (!response.ok) { throw new Error('Failed to add rule'); } showSuccessMessage('规则添加成功'); // 清空输入框 document.getElementById('new-rule').value = ''; // 重新加载规则 loadLocalRules(); // 重新加载统计信息 loadShieldStats(); } catch (error) { console.error('Error adding rule:', error); showErrorMessage('添加规则失败'); } } // 加载远程黑名单 async function loadRemoteBlacklists() { try { const response = await fetch('/api/shield/blacklists'); if (!response.ok) { throw new Error(`加载失败: ${response.status}`); } const blacklists = await response.json(); // 确保blacklists是数组 const blacklistArray = Array.isArray(blacklists) ? blacklists : []; updateBlacklistsTable(blacklistArray); } catch (error) { console.error('加载远程黑名单失败:', error); showErrorMessage('加载远程黑名单失败'); } } // 判断黑名单是否过期(超过24小时未更新视为过期) function isBlacklistExpired(lastUpdateTime) { if (!lastUpdateTime) { return true; // 从未更新过,视为过期 } const lastUpdate = new Date(lastUpdateTime); const now = new Date(); const hoursDiff = (now - lastUpdate) / (1000 * 60 * 60); return hoursDiff > 24; // 超过24小时视为过期 } // 更新黑名单表格 function updateBlacklistsTable(blacklists) { const tbody = document.getElementById('blacklists-table-body'); // 清空表格 tbody.innerHTML = ''; // 检查黑名单数据是否为空 if (!blacklists || blacklists.length === 0) { const emptyRow = document.createElement('tr'); emptyRow.innerHTML = '暂无黑名单'; tbody.appendChild(emptyRow); return; } // 对于大量黑名单,限制显示数量 const maxBlacklistsToShow = 100; // 限制最大显示数量 const blacklistsToShow = blacklists.length > maxBlacklistsToShow ? blacklists.slice(0, maxBlacklistsToShow) : blacklists; // 使用DocumentFragment提高性能 const fragment = document.createDocumentFragment(); blacklistsToShow.forEach(blacklist => { const tr = document.createElement('tr'); tr.className = 'border-b border-gray-200 hover:bg-gray-50'; // 名称单元格 const tdName = document.createElement('td'); tdName.className = 'py-3 px-4'; tdName.textContent = blacklist.Name || '未命名'; // URL单元格 const tdUrl = document.createElement('td'); tdUrl.className = 'py-3 px-4 truncate max-w-xs'; tdUrl.textContent = blacklist.URL; // 状态单元格 const tdStatus = document.createElement('td'); tdStatus.className = 'py-3 px-4 text-center'; // 判断状态颜色:绿色(正常)、黄色(过期)、灰色(禁用) let statusColor = 'bg-gray-300'; // 默认禁用 let statusText = '禁用'; if (blacklist.Enabled) { const expired = isBlacklistExpired(blacklist.lastUpdateTime || blacklist.LastUpdateTime); if (expired) { statusColor = 'bg-warning'; // 黄色表示过期 statusText = '过期'; } else { statusColor = 'bg-success'; // 绿色表示正常 statusText = '正常'; } } const statusContainer = document.createElement('div'); statusContainer.className = 'flex items-center justify-center'; const statusDot = document.createElement('span'); statusDot.className = `inline-block w-3 h-3 rounded-full ${statusColor}`; statusDot.title = statusText; const statusTextSpan = document.createElement('span'); statusTextSpan.className = 'text-sm ml-2'; statusTextSpan.textContent = statusText; statusContainer.appendChild(statusDot); statusContainer.appendChild(statusTextSpan); tdStatus.appendChild(statusContainer); // 操作单元格 const tdActions = document.createElement('td'); tdActions.className = 'py-3 px-4 text-right space-x-2'; // 刷新按钮 const refreshBtn = document.createElement('button'); refreshBtn.className = 'update-blacklist-btn px-3 py-1 bg-primary text-white rounded-md hover:bg-primary/90 transition-colors text-sm'; refreshBtn.dataset.url = blacklist.URL; refreshBtn.innerHTML = ''; refreshBtn.title = '刷新黑名单'; refreshBtn.addEventListener('click', handleUpdateBlacklist); // 删除按钮 const deleteBtn = document.createElement('button'); deleteBtn.className = 'delete-blacklist-btn px-3 py-1 bg-danger text-white rounded-md hover:bg-danger/90 transition-colors text-sm'; deleteBtn.dataset.url = blacklist.URL; deleteBtn.innerHTML = ''; deleteBtn.title = '删除黑名单'; deleteBtn.addEventListener('click', handleDeleteBlacklist); tdActions.appendChild(refreshBtn); tdActions.appendChild(deleteBtn); tr.appendChild(tdName); tr.appendChild(tdUrl); tr.appendChild(tdStatus); tr.appendChild(tdActions); fragment.appendChild(tr); }); // 一次性添加所有行到DOM tbody.appendChild(fragment); // 如果有更多黑名单,添加提示 if (blacklists.length > maxBlacklistsToShow) { const infoRow = document.createElement('tr'); infoRow.innerHTML = `显示前 ${maxBlacklistsToShow} 个黑名单,共 ${blacklists.length} 个`; tbody.appendChild(infoRow); } } // 处理更新单个黑名单 async function handleUpdateBlacklist(e) { const url = e.target.closest('.update-blacklist-btn').dataset.url; if (!url) { showToast('无效的黑名单URL', 'error'); return; } try { const response = await fetch(`/api/shield/blacklists/${encodeURIComponent(url)}/update`, { method: 'POST', headers: { 'Content-Type': 'application/json' } }); if (!response.ok) { const errorData = await response.json().catch(() => ({})); throw new Error(errorData.error || 'Failed to update blacklist'); } // 重新加载黑名单 loadRemoteBlacklists(); // 重新加载统计信息 loadShieldStats(); showToast('黑名单更新成功'); } catch (error) { console.error('更新黑名单失败:', error); showToast('更新黑名单失败: ' + error.message, 'error'); } } // 处理删除黑名单 async function handleDeleteBlacklist(e) { const url = e.target.closest('.delete-blacklist-btn').dataset.url; if (!url) { showToast('无效的黑名单URL', 'error'); return; } // 确认删除 if (!confirm('确定要删除这个黑名单吗?删除后将无法恢复。')) { return; } try { const response = await fetch(`/api/shield/blacklists/${encodeURIComponent(url)}`, { method: 'DELETE', headers: { 'Content-Type': 'application/json' } }); if (!response.ok) { const errorData = await response.json().catch(() => ({})); throw new Error(errorData.error || 'Failed to delete blacklist'); } // 重新加载黑名单 loadRemoteBlacklists(); // 重新加载统计信息 loadShieldStats(); showToast('黑名单删除成功'); } catch (error) { console.error('删除黑名单失败:', error); showToast('删除黑名单失败: ' + error.message, 'error'); } } // 处理添加黑名单 async function handleAddBlacklist(event) { // 如果存在event参数,则调用preventDefault()防止表单默认提交 if (event && typeof event.preventDefault === 'function') { event.preventDefault(); } const nameInput = document.getElementById('blacklist-name'); const urlInput = document.getElementById('blacklist-url'); const name = nameInput ? nameInput.value.trim() : ''; const url = urlInput ? urlInput.value.trim() : ''; // 简单验证 if (!name || !url) { showErrorMessage('名称和URL不能为空'); return; } // 验证URL格式 try { new URL(url); } catch (e) { showErrorMessage('URL格式不正确'); return; } try { const response = await fetch('/api/shield/blacklists', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ name, url }) }); if (!response.ok) { // 尝试从响应中获取更详细的错误信息 let errorMessage = '添加黑名单失败'; try { const errorData = await response.json(); if (errorData.error) { errorMessage = errorData.error; } } catch (jsonError) { // 忽略JSON解析错误 } throw new Error(errorMessage); } showSuccessMessage('黑名单添加成功'); // 清空输入框 if (nameInput) nameInput.value = ''; if (urlInput) urlInput.value = ''; // 重新加载黑名单 loadRemoteBlacklists(); // 重新加载统计信息 loadShieldStats(); } catch (error) { console.error('Error adding blacklist:', error); showErrorMessage(error.message || '添加黑名单失败'); } } // 当前显示的规则类型:'local' 或 'remote' let currentRulesType = 'local'; // 设置事件监听器 function setupShieldEventListeners() { // 本地规则管理事件 const saveRuleBtn = document.getElementById('save-rule-btn'); if (saveRuleBtn) { saveRuleBtn.addEventListener('click', handleAddRule); } // 远程黑名单管理事件 const saveBlacklistBtn = document.getElementById('save-blacklist-btn'); if (saveBlacklistBtn) { saveBlacklistBtn.addEventListener('click', handleAddBlacklist); } // 添加切换查看本地规则和远程规则的事件监听 const viewLocalRulesBtn = document.getElementById('view-local-rules-btn'); if (viewLocalRulesBtn) { viewLocalRulesBtn.addEventListener('click', loadLocalRules); } const viewRemoteRulesBtn = document.getElementById('view-remote-rules-btn'); if (viewRemoteRulesBtn) { viewRemoteRulesBtn.addEventListener('click', loadRemoteRules); } } // 显示成功消息 function showSuccessMessage(message) { showNotification(message, 'success'); } // 显示错误消息 function showErrorMessage(message) { showNotification(message, 'error'); } // 显示通知 function showNotification(message, type = 'info') { // 移除现有通知 const existingNotification = document.querySelector('.notification'); if (existingNotification) { existingNotification.remove(); } // 创建新通知 const notification = document.createElement('div'); notification.className = `notification fixed bottom-4 right-4 px-6 py-3 rounded-lg shadow-lg z-50 transform transition-all duration-300 ease-in-out translate-y-0 opacity-0`; // 设置通知样式 if (type === 'success') { notification.classList.add('bg-green-500', 'text-white'); } else if (type === 'error') { notification.classList.add('bg-red-500', 'text-white'); } else { notification.classList.add('bg-blue-500', 'text-white'); } notification.innerHTML = `
${message}
`; document.body.appendChild(notification); // 显示通知 setTimeout(() => { notification.classList.remove('opacity-0'); notification.classList.add('opacity-100'); }, 10); // 3秒后隐藏通知 setTimeout(() => { notification.classList.remove('opacity-100'); notification.classList.add('opacity-0'); setTimeout(() => { notification.remove(); }, 300); }, 3000); } // 页面加载完成后初始化 if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', initShieldPage); } else { initShieldPage(); } // 当切换到屏蔽管理页面时重新加载数据 document.addEventListener('DOMContentLoaded', () => { // 监听hash变化,当切换到屏蔽管理页面时重新加载数据 window.addEventListener('hashchange', () => { if (window.location.hash === '#shield') { initShieldPage(); } }); });