// 屏蔽管理页面功能实现 // 初始化屏蔽管理页面 async function initShieldPage() { // 并行加载所有数据 await Promise.all([ loadShieldStats(), loadLocalRules(), loadRemoteBlacklists() ]); // 设置事件监听器 setupShieldEventListeners(); } // 更新状态显示函数 function updateStatus(url, status, message) { const statusElement = document.getElementById(`update-status-${encodeURIComponent(url)}`); if (!statusElement) return; let statusHTML = ''; switch (status) { case 'loading': statusHTML = ' 处理中...'; break; case 'success': statusHTML = ` ${message || '成功'}`; break; case 'error': statusHTML = ` ${message || '失败'}`; break; default: statusHTML = '-'; } statusElement.innerHTML = statusHTML; // 如果是成功或失败状态,3秒后恢复默认状态 if (status === 'success' || status === 'error') { setTimeout(() => { updateStatus(url, 'default'); }, 3000); } } // 数字更新动画函数 function animateCounter(element, target, duration = 1000) { // 确保element存在 if (!element) return; // 清除元素上可能存在的现有定时器 if (element.animationTimer) { clearInterval(element.animationTimer); } // 确保target是数字 const targetNum = typeof target === 'number' ? target : parseInt(target) || 0; // 获取起始值,使用更安全的方法 const startText = element.textContent.replace(/[^0-9]/g, ''); const start = parseInt(startText) || 0; // 如果起始值和目标值相同,直接返回 if (start === targetNum) { element.textContent = targetNum; return; } let current = start; const increment = (targetNum - start) / (duration / 16); // 16ms per frame // 使用requestAnimationFrame实现更平滑的动画 let startTime = null; function updateCounter(timestamp) { if (!startTime) startTime = timestamp; const elapsed = timestamp - startTime; const progress = Math.min(elapsed / duration, 1); // 使用缓动函数使动画更自然 const easeOutQuad = progress * (2 - progress); current = start + (targetNum - start) * easeOutQuad; // 根据方向使用floor或ceil确保平滑过渡 const displayValue = targetNum > start ? Math.floor(current) : Math.ceil(current); element.textContent = displayValue; if (progress < 1) { // 继续动画 element.animationTimer = requestAnimationFrame(updateCounter); } else { // 动画结束,确保显示准确值 element.textContent = targetNum; // 清除定时器引用 element.animationTimer = null; } } // 开始动画 element.animationTimer = requestAnimationFrame(updateCounter); } // 加载屏蔽规则统计信息 async function loadShieldStats() { try { // 获取屏蔽规则统计信息 const shieldResponse = await fetch('/api/shield'); if (!shieldResponse.ok) { throw new Error(`加载屏蔽统计失败: ${shieldResponse.status}`); } const stats = await shieldResponse.json(); // 获取黑名单列表,计算禁用数量 const blacklistsResponse = await fetch('/api/shield/blacklists'); if (!blacklistsResponse.ok) { throw new Error(`加载黑名单列表失败: ${blacklistsResponse.status}`); } const blacklists = await blacklistsResponse.json(); const disabledBlacklistCount = blacklists.filter(blacklist => !blacklist.enabled).length; // 更新统计信息 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) { animateCounter(element, item.value || 0); } }); // 更新禁用黑名单数量 const disabledBlacklistElement = document.getElementById('blacklist-disabled-count'); if (disabledBlacklistElement) { animateCounter(disabledBlacklistElement, disabledBlacklistCount); } } catch (error) { console.error('加载屏蔽规则统计信息失败:', error); showNotification('加载屏蔽规则统计信息失败', 'error'); } } // 加载本地规则 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); showNotification('加载本地规则失败', 'error'); } } // 加载远程规则 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); showNotification('加载远程规则失败', 'error'); } } // 更新规则表格 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); showNotification('规则删除成功', 'success'); 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); showNotification('删除规则失败: ' + error.message, 'error'); } } // 添加新规则 async function handleAddRule() { const rule = document.getElementById('new-rule').value.trim(); if (!rule) { showNotification('规则不能为空', 'error'); 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'); } showNotification('规则添加成功', 'success'); // 清空输入框 document.getElementById('new-rule').value = ''; // 重新加载规则 loadLocalRules(); // 重新加载统计信息 loadShieldStats(); } catch (error) { console.error('Error adding rule:', error); showNotification('添加规则失败', 'error'); } } // 加载远程黑名单 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); showNotification('加载远程黑名单失败', 'error'); } } // 判断黑名单是否过期(超过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) { 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 tdUpdateStatus = document.createElement('td'); tdUpdateStatus.className = 'py-3 px-4 text-center'; tdUpdateStatus.id = `update-status-${encodeURIComponent(blacklist.url)}`; tdUpdateStatus.innerHTML = '-'; // 操作单元格 const tdActions = document.createElement('td'); tdActions.className = 'py-3 px-4 text-right space-x-2'; // 启用/禁用按钮 const toggleBtn = document.createElement('button'); toggleBtn.className = `toggle-blacklist-btn px-3 py-1 rounded-md transition-colors text-sm ${blacklist.enabled ? 'bg-warning text-white hover:bg-warning/90' : 'bg-success text-white hover:bg-success/90'}`; toggleBtn.dataset.url = blacklist.url; toggleBtn.dataset.enabled = blacklist.enabled; toggleBtn.innerHTML = ``; toggleBtn.title = blacklist.enabled ? '禁用黑名单' : '启用黑名单'; toggleBtn.addEventListener('click', handleToggleBlacklist); // 刷新按钮 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(toggleBtn); tdActions.appendChild(refreshBtn); tdActions.appendChild(deleteBtn); tr.appendChild(tdName); tr.appendChild(tdUrl); tr.appendChild(tdStatus); tr.appendChild(tdUpdateStatus); 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 btn = e.target.closest('.update-blacklist-btn'); if (!btn) { console.error('未找到更新按钮元素'); return; } const url = btn.dataset.url; if (!url) { showNotification('无效的黑名单URL', 'error'); return; } try { // 显示加载状态 updateStatus(url, 'loading'); // 获取当前所有黑名单 const response = await fetch('/api/shield/blacklists'); if (!response.ok) { throw new Error(`获取黑名单失败: ${response.status}`); } const blacklists = await response.json(); // 找到目标黑名单并更新其状态 const updatedBlacklists = blacklists.map(blacklist => { if (blacklist.url === url) { return { Name: blacklist.name, URL: blacklist.url, Enabled: blacklist.enabled, LastUpdateTime: new Date().toISOString() }; } return { Name: blacklist.name, URL: blacklist.url, Enabled: blacklist.enabled, LastUpdateTime: blacklist.lastUpdateTime || blacklist.LastUpdateTime }; }); // 发送更新请求 const updateResponse = await fetch('/api/shield/blacklists', { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(updatedBlacklists) }); if (!updateResponse.ok) { const errorText = await updateResponse.text(); throw new Error(`更新失败: ${updateResponse.status} ${errorText}`); } // 显示成功状态 updateStatus(url, 'success'); // 重新加载黑名单 loadRemoteBlacklists(); // 重新加载统计信息 loadShieldStats(); showNotification('黑名单更新成功', 'success'); } catch (error) { console.error('更新黑名单失败:', error); // 显示错误状态 updateStatus(url, 'error', error.message); showNotification('更新黑名单失败: ' + error.message, 'error'); } } // 处理删除黑名单 async function handleDeleteBlacklist(e) { // 确保获取到正确的按钮元素 const btn = e.target.closest('.delete-blacklist-btn'); if (!btn) { console.error('未找到删除按钮元素'); return; } const url = btn.dataset.url; if (!url) { showNotification('无效的黑名单URL', 'error'); return; } // 确认删除 if (!confirm('确定要删除这个黑名单吗?删除后将无法恢复。')) { return; } try { // 显示加载状态 updateStatus(url, 'loading'); // 获取当前所有黑名单 const response = await fetch('/api/shield/blacklists'); if (!response.ok) { throw new Error(`获取黑名单失败: ${response.status}`); } const blacklists = await response.json(); // 过滤掉要删除的黑名单 const updatedBlacklists = blacklists.filter(blacklist => blacklist.url !== url); // 发送更新请求 const updateResponse = await fetch('/api/shield/blacklists', { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(updatedBlacklists) }); if (!updateResponse.ok) { const errorText = await updateResponse.text(); throw new Error(`删除失败: ${updateResponse.status} ${errorText}`); } // 显示成功状态 updateStatus(url, 'success', '已删除'); // 重新加载黑名单 loadRemoteBlacklists(); // 重新加载统计信息 loadShieldStats(); showNotification('黑名单删除成功', 'success'); } catch (error) { console.error('删除黑名单失败:', error); // 显示错误状态 updateStatus(url, 'error', error.message); showNotification('删除黑名单失败: ' + error.message, 'error'); } } // 处理启用/禁用黑名单 async function handleToggleBlacklist(e) { // 确保获取到正确的按钮元素 const btn = e.target.closest('.toggle-blacklist-btn'); if (!btn) { console.error('未找到启用/禁用按钮元素'); return; } const url = btn.dataset.url; const currentEnabled = btn.dataset.enabled === 'true'; if (!url) { showNotification('无效的黑名单URL', 'error'); return; } try { // 显示加载状态 updateStatus(url, 'loading'); // 获取当前所有黑名单 const response = await fetch('/api/shield/blacklists'); if (!response.ok) { throw new Error(`获取黑名单失败: ${response.status}`); } const blacklists = await response.json(); // 找到目标黑名单并更新其状态 const updatedBlacklists = blacklists.map(blacklist => { if (blacklist.url === url) { return { Name: blacklist.name, URL: blacklist.url, Enabled: !currentEnabled, LastUpdateTime: blacklist.lastUpdateTime || blacklist.LastUpdateTime }; } return { Name: blacklist.name, URL: blacklist.url, Enabled: blacklist.enabled, LastUpdateTime: blacklist.lastUpdateTime || blacklist.LastUpdateTime }; }); // 发送更新请求 const updateResponse = await fetch('/api/shield/blacklists', { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(updatedBlacklists) }); if (!updateResponse.ok) { const errorText = await updateResponse.text(); throw new Error(`更新状态失败: ${updateResponse.status} ${errorText}`); } // 显示成功状态 updateStatus(url, 'success', currentEnabled ? '已禁用' : '已启用'); // 重新加载黑名单 loadRemoteBlacklists(); // 重新加载统计信息 loadShieldStats(); showNotification(`黑名单已${currentEnabled ? '禁用' : '启用'}`, 'success'); } catch (error) { console.error('启用/禁用黑名单失败:', error); // 显示错误状态 updateStatus(url, 'error', error.message); showNotification('启用/禁用黑名单失败: ' + 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 statusElement = document.getElementById('save-blacklist-status'); const name = nameInput ? nameInput.value.trim() : ''; const url = urlInput ? urlInput.value.trim() : ''; // 简单验证 if (!name || !url) { showNotification('名称和URL不能为空', 'error'); return; } // 验证URL格式 try { new URL(url); } catch (e) { showNotification('URL格式不正确', 'error'); return; } try { // 显示加载状态 statusElement.innerHTML = ' 正在添加...'; // 发送添加请求 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); } // 显示成功状态 statusElement.innerHTML = ' 成功'; showNotification('黑名单添加成功', 'success'); // 清空输入框 if (nameInput) nameInput.value = ''; if (urlInput) urlInput.value = ''; // 重新加载黑名单 loadRemoteBlacklists(); // 重新加载统计信息 loadShieldStats(); } catch (error) { console.error('Error adding blacklist:', error); // 显示错误状态 statusElement.innerHTML = ` 失败`; showNotification(error.message || '添加黑名单失败', 'error'); } finally { // 3秒后清除状态显示 setTimeout(() => { statusElement.innerHTML = ''; }, 3000); } } // 当前显示的规则类型:'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(); } }); });