1302 lines
42 KiB
JavaScript
1302 lines
42 KiB
JavaScript
// 屏蔽管理页面功能实现
|
||
|
||
// 初始化屏蔽管理页面
|
||
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;
|
||
|
||
// 清除之前的所有类
|
||
statusElement.classList.remove('status-transition', 'status-loading', 'status-success', 'status-error', 'status-fade-in', 'status-fade-out');
|
||
|
||
let statusHTML = '';
|
||
|
||
switch (status) {
|
||
case 'loading':
|
||
statusHTML = '<span class="text-blue-500"><i class="fa fa-spinner fa-spin"></i> 处理中...</span>';
|
||
break;
|
||
case 'success':
|
||
statusHTML = `<span class="text-green-500"><i class="fa fa-check"></i> ${message || '成功'}</span>`;
|
||
break;
|
||
case 'error':
|
||
statusHTML = `<span class="text-red-500"><i class="fa fa-times"></i> ${message || '失败'}</span>`;
|
||
break;
|
||
default:
|
||
statusHTML = '<span class="text-gray-400">-</span>';
|
||
}
|
||
|
||
// 强制重排,确保过渡效果生效
|
||
void statusElement.offsetWidth;
|
||
|
||
// 设置新的HTML内容
|
||
statusElement.innerHTML = statusHTML;
|
||
|
||
// 添加过渡类和对应状态类
|
||
statusElement.classList.add('status-transition');
|
||
|
||
// 如果不是默认状态,添加淡入动画和对应状态类
|
||
if (status !== 'default') {
|
||
statusElement.classList.add('status-fade-in');
|
||
statusElement.classList.add(`status-${status}`);
|
||
}
|
||
|
||
// 如果是成功或失败状态,3秒后渐变消失
|
||
if (status === 'success' || status === 'error') {
|
||
setTimeout(() => {
|
||
// 添加淡出类
|
||
statusElement.classList.add('status-fade-out');
|
||
|
||
// 等待淡出动画完成后切换到默认状态
|
||
setTimeout(() => {
|
||
// 清除所有类
|
||
statusElement.classList.remove('status-transition', 'status-loading', 'status-success', 'status-error', 'status-fade-in', 'status-fade-out');
|
||
// 设置默认状态
|
||
statusElement.innerHTML = '<span class="text-gray-400">-</span>';
|
||
}, 300); // 与CSS动画持续时间一致
|
||
}, 3000);
|
||
}
|
||
}
|
||
|
||
// 更新规则状态显示函数
|
||
function updateRuleStatus(rule, status, message) {
|
||
const statusElement = document.getElementById(`rule-status-${encodeURIComponent(rule)}`);
|
||
if (!statusElement) return;
|
||
|
||
// 清除之前的所有类
|
||
statusElement.classList.remove('status-transition', 'status-loading', 'status-success', 'status-error', 'status-fade-in', 'status-fade-out');
|
||
|
||
let statusHTML = '';
|
||
|
||
switch (status) {
|
||
case 'loading':
|
||
statusHTML = '<span class="text-blue-500"><i class="fa fa-spinner fa-spin"></i> 处理中...</span>';
|
||
break;
|
||
case 'success':
|
||
statusHTML = `<span class="text-green-500"><i class="fa fa-check"></i> ${message || '成功'}</span>`;
|
||
break;
|
||
case 'error':
|
||
statusHTML = `<span class="text-red-500"><i class="fa fa-times"></i> ${message || '失败'}</span>`;
|
||
break;
|
||
default:
|
||
statusHTML = '<span class="text-gray-400">-</span>';
|
||
}
|
||
|
||
// 强制重排,确保过渡效果生效
|
||
void statusElement.offsetWidth;
|
||
|
||
// 设置新的HTML内容
|
||
statusElement.innerHTML = statusHTML;
|
||
|
||
// 添加过渡类和对应状态类
|
||
statusElement.classList.add('status-transition');
|
||
|
||
// 如果不是默认状态,添加淡入动画和对应状态类
|
||
if (status !== 'default') {
|
||
statusElement.classList.add('status-fade-in');
|
||
statusElement.classList.add(`status-${status}`);
|
||
}
|
||
|
||
// 如果是成功或失败状态,3秒后渐变消失
|
||
if (status === 'success' || status === 'error') {
|
||
setTimeout(() => {
|
||
// 添加淡出类
|
||
statusElement.classList.add('status-fade-out');
|
||
|
||
// 等待淡出动画完成后切换到默认状态
|
||
setTimeout(() => {
|
||
// 清除所有类
|
||
statusElement.classList.remove('status-transition', 'status-loading', 'status-success', 'status-error', 'status-fade-in', 'status-fade-out');
|
||
// 设置默认状态
|
||
statusElement.innerHTML = '<span class="text-gray-400">-</span>';
|
||
}, 300); // 与CSS动画持续时间一致
|
||
}, 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 = '<td colspan="3" class="py-4 text-center text-gray-500">暂无规则</td>';
|
||
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 tdStatus = document.createElement('td');
|
||
tdStatus.className = 'py-3 px-4 text-center';
|
||
tdStatus.id = `rule-status-${encodeURIComponent(rule)}`;
|
||
tdStatus.innerHTML = '<span class="text-gray-400">-</span>';
|
||
|
||
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(tdStatus);
|
||
tr.appendChild(tdAction);
|
||
fragment.appendChild(tr);
|
||
});
|
||
|
||
// 一次性添加所有行到DOM
|
||
tbody.appendChild(fragment);
|
||
|
||
// 如果有更多规则,添加提示
|
||
if (rules.length > maxRulesToShow) {
|
||
const infoRow = document.createElement('tr');
|
||
infoRow.innerHTML = `<td colspan="3" class="py-4 text-center text-gray-500">显示前 ${maxRulesToShow} 条规则,共 ${rules.length} 条</td>`;
|
||
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 {
|
||
// 显示加载状态
|
||
updateRuleStatus(rule, 'loading');
|
||
|
||
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);
|
||
|
||
// 解析服务器响应
|
||
let responseData;
|
||
try {
|
||
responseData = await response.json();
|
||
} catch (jsonError) {
|
||
responseData = {};
|
||
}
|
||
|
||
console.log('Response data:', responseData);
|
||
|
||
// 根据服务器响应判断是否成功
|
||
if (response.ok && responseData.status === 'success') {
|
||
// 显示成功状态
|
||
updateRuleStatus(rule, 'success', '已删除');
|
||
|
||
showNotification('规则删除成功', 'success');
|
||
console.log('Current rules type:', currentRulesType);
|
||
|
||
// 延迟重新加载规则列表和统计信息,让用户能看到成功状态
|
||
setTimeout(() => {
|
||
// 根据当前显示的规则类型重新加载对应的规则列表
|
||
if (currentRulesType === 'local') {
|
||
console.log('Reloading local rules');
|
||
loadLocalRules();
|
||
} else {
|
||
console.log('Reloading remote rules');
|
||
loadRemoteRules();
|
||
}
|
||
// 重新加载统计信息
|
||
loadShieldStats();
|
||
}, 3000);
|
||
} else {
|
||
const errorMessage = responseData.error || responseData.message || `删除规则失败: ${response.status}`;
|
||
// 显示错误状态
|
||
updateRuleStatus(rule, 'error', errorMessage);
|
||
throw new Error(errorMessage);
|
||
}
|
||
} catch (error) {
|
||
console.error('Error deleting rule:', error);
|
||
// 显示错误状态
|
||
updateRuleStatus(rule, 'error', error.message);
|
||
showNotification('删除规则失败: ' + error.message, 'error');
|
||
}
|
||
}
|
||
|
||
// 添加新规则
|
||
async function handleAddRule() {
|
||
const rule = document.getElementById('new-rule').value.trim();
|
||
const statusElement = document.getElementById('save-rule-status');
|
||
|
||
if (!rule) {
|
||
showNotification('规则不能为空', 'error');
|
||
return;
|
||
}
|
||
|
||
try {
|
||
// 清除之前的所有类
|
||
statusElement.classList.remove('status-transition', 'status-loading', 'status-success', 'status-error', 'status-fade-in', 'status-fade-out');
|
||
|
||
// 显示加载状态
|
||
statusElement.innerHTML = '<i class="fa fa-spinner fa-spin text-blue-500"></i> 正在添加...';
|
||
|
||
// 强制重排,确保过渡效果生效
|
||
void statusElement.offsetWidth;
|
||
|
||
// 添加过渡类和加载状态类
|
||
statusElement.classList.add('status-transition', 'status-fade-in', 'status-loading');
|
||
|
||
const response = await fetch('/api/shield', {
|
||
method: 'POST',
|
||
headers: {
|
||
'Content-Type': 'application/json'
|
||
},
|
||
body: JSON.stringify({ rule })
|
||
});
|
||
|
||
// 解析服务器响应
|
||
let responseData;
|
||
try {
|
||
responseData = await response.json();
|
||
} catch (jsonError) {
|
||
responseData = {};
|
||
}
|
||
|
||
// 根据服务器响应判断是否成功
|
||
if (response.ok && responseData.status === 'success') {
|
||
// 清除之前的所有类
|
||
statusElement.classList.remove('status-transition', 'status-loading', 'status-success', 'status-error', 'status-fade-in', 'status-fade-out');
|
||
|
||
// 显示成功状态
|
||
statusElement.innerHTML = '<span class="text-green-500"><i class="fa fa-check"></i> 成功</span>';
|
||
|
||
// 强制重排,确保过渡效果生效
|
||
void statusElement.offsetWidth;
|
||
|
||
// 添加过渡类和成功状态类
|
||
statusElement.classList.add('status-transition', 'status-fade-in', 'status-success');
|
||
|
||
showNotification('规则添加成功', 'success');
|
||
// 清空输入框
|
||
document.getElementById('new-rule').value = '';
|
||
|
||
// 延迟重新加载规则和统计信息,让用户能看到成功状态
|
||
setTimeout(() => {
|
||
// 重新加载规则
|
||
loadLocalRules();
|
||
// 重新加载统计信息
|
||
loadShieldStats();
|
||
}, 3000);
|
||
} else {
|
||
// 清除之前的所有类
|
||
statusElement.classList.remove('status-transition', 'status-loading', 'status-success', 'status-error', 'status-fade-in', 'status-fade-out');
|
||
|
||
// 显示失败状态
|
||
const errorMessage = responseData.error || responseData.message || '添加规则失败';
|
||
statusElement.innerHTML = `<span class="text-red-500"><i class="fa fa-times"></i> ${errorMessage}</span>`;
|
||
|
||
// 强制重排,确保过渡效果生效
|
||
void statusElement.offsetWidth;
|
||
|
||
// 添加过渡类和错误状态类
|
||
statusElement.classList.add('status-transition', 'status-fade-in', 'status-error');
|
||
|
||
showNotification(errorMessage, 'error');
|
||
}
|
||
} catch (error) {
|
||
console.error('Error adding rule:', error);
|
||
|
||
// 清除之前的所有类
|
||
statusElement.classList.remove('status-transition', 'status-loading', 'status-success', 'status-error', 'status-fade-in', 'status-fade-out');
|
||
|
||
// 显示错误状态
|
||
const errorMessage = error.message || '添加规则失败';
|
||
statusElement.innerHTML = `<span class="text-red-500"><i class="fa fa-times"></i> ${errorMessage}</span>`;
|
||
|
||
// 强制重排,确保过渡效果生效
|
||
void statusElement.offsetWidth;
|
||
|
||
// 添加过渡类和错误状态类
|
||
statusElement.classList.add('status-transition', 'status-fade-in', 'status-error');
|
||
|
||
showNotification(errorMessage, 'error');
|
||
} finally {
|
||
// 3秒后渐变消失
|
||
setTimeout(() => {
|
||
// 添加淡出类
|
||
statusElement.classList.add('status-fade-out');
|
||
|
||
// 等待淡出动画完成后清除状态
|
||
setTimeout(() => {
|
||
// 清除所有类
|
||
statusElement.classList.remove('status-transition', 'status-loading', 'status-success', 'status-error', 'status-fade-in', 'status-fade-out');
|
||
// 清空状态显示
|
||
statusElement.innerHTML = '';
|
||
}, 300); // 与CSS动画持续时间一致
|
||
}, 3000);
|
||
}
|
||
}
|
||
|
||
// 加载远程黑名单
|
||
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 = '<tr><td colspan="4" class="py-4 text-center text-gray-500">暂无黑名单</td></tr>';
|
||
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 = '<span class="text-gray-400">-</span>';
|
||
|
||
// 操作单元格
|
||
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 = `<i class="fa fa-${blacklist.enabled ? 'toggle-on' : 'toggle-off'}"></i>`;
|
||
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 = '<i class="fa fa-refresh"></i>';
|
||
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 = '<i class="fa fa-trash"></i>';
|
||
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 = `<td colspan="4" class="py-4 text-center text-gray-500">显示前 ${maxBlacklistsToShow} 个黑名单,共 ${blacklists.length} 个</td>`;
|
||
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)
|
||
});
|
||
|
||
// 解析服务器响应
|
||
let responseData;
|
||
try {
|
||
responseData = await updateResponse.json();
|
||
} catch (jsonError) {
|
||
responseData = {};
|
||
}
|
||
|
||
// 根据服务器响应判断是否成功
|
||
if (updateResponse.ok && (responseData.status === 'success' || !responseData.status)) {
|
||
// 显示成功状态
|
||
updateStatus(url, 'success');
|
||
|
||
// 显示通知
|
||
showNotification('黑名单更新成功', 'success');
|
||
|
||
// 延迟重新加载黑名单和统计信息,让用户能看到成功状态
|
||
setTimeout(() => {
|
||
// 重新加载黑名单
|
||
loadRemoteBlacklists();
|
||
// 重新加载统计信息
|
||
loadShieldStats();
|
||
}, 3000);
|
||
} else {
|
||
// 显示失败状态
|
||
updateStatus(url, 'error', responseData.error || responseData.message || `更新失败: ${updateResponse.status}`);
|
||
showNotification(`黑名单更新失败: ${responseData.error || responseData.message || updateResponse.status}`, 'error');
|
||
|
||
// 延迟重新加载黑名单和统计信息
|
||
setTimeout(() => {
|
||
// 重新加载黑名单
|
||
loadRemoteBlacklists();
|
||
// 重新加载统计信息
|
||
loadShieldStats();
|
||
}, 3000);
|
||
}
|
||
} 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 {
|
||
// 获取当前行元素
|
||
const tr = btn.closest('tr');
|
||
if (!tr) {
|
||
console.error('未找到行元素');
|
||
return;
|
||
}
|
||
|
||
// 显示加载状态
|
||
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)
|
||
});
|
||
|
||
// 解析服务器响应
|
||
let responseData;
|
||
try {
|
||
responseData = await updateResponse.json();
|
||
} catch (jsonError) {
|
||
responseData = {};
|
||
}
|
||
|
||
// 根据服务器响应判断是否成功
|
||
if (updateResponse.ok && responseData.status === 'success') {
|
||
// 显示成功状态
|
||
updateStatus(url, 'success', '已删除');
|
||
|
||
// 显示通知
|
||
showNotification('黑名单删除成功', 'success');
|
||
|
||
// 延迟后渐变移除该行
|
||
setTimeout(() => {
|
||
// 添加渐变移除类
|
||
tr.style.transition = 'all 0.3s ease-in-out';
|
||
tr.style.opacity = '0';
|
||
tr.style.transform = 'translateX(-10px)';
|
||
tr.style.height = tr.offsetHeight + 'px';
|
||
tr.style.overflow = 'hidden';
|
||
|
||
// 等待过渡效果完成后,隐藏该行
|
||
setTimeout(() => {
|
||
tr.style.display = 'none';
|
||
|
||
// 延迟重新加载黑名单和统计信息,确保视觉效果完成
|
||
setTimeout(() => {
|
||
// 重新加载黑名单
|
||
loadRemoteBlacklists();
|
||
// 重新加载统计信息
|
||
loadShieldStats();
|
||
}, 100);
|
||
}, 300);
|
||
}, 3000);
|
||
} else {
|
||
// 显示失败状态
|
||
const errorMessage = responseData.error || responseData.message || `删除失败: ${updateResponse.status}`;
|
||
updateStatus(url, 'error', errorMessage);
|
||
showNotification(errorMessage, 'error');
|
||
|
||
// 延迟重新加载黑名单和统计信息
|
||
setTimeout(() => {
|
||
// 重新加载黑名单
|
||
loadRemoteBlacklists();
|
||
// 重新加载统计信息
|
||
loadShieldStats();
|
||
}, 3000);
|
||
}
|
||
} 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)
|
||
});
|
||
|
||
// 解析服务器响应
|
||
let responseData;
|
||
try {
|
||
responseData = await updateResponse.json();
|
||
} catch (jsonError) {
|
||
responseData = {};
|
||
}
|
||
|
||
// 根据服务器响应判断是否成功
|
||
if (updateResponse.ok && responseData.status === 'success') {
|
||
// 显示成功状态
|
||
updateStatus(url, 'success', currentEnabled ? '已禁用' : '已启用');
|
||
|
||
// 显示通知
|
||
showNotification(`黑名单已${currentEnabled ? '禁用' : '启用'}`, 'success');
|
||
|
||
// 延迟重新加载黑名单和统计信息,让用户能看到成功状态
|
||
setTimeout(() => {
|
||
// 重新加载黑名单
|
||
loadRemoteBlacklists();
|
||
// 重新加载统计信息
|
||
loadShieldStats();
|
||
}, 3000);
|
||
} else {
|
||
// 显示失败状态
|
||
const errorMessage = responseData.error || responseData.message || `更新状态失败: ${updateResponse.status}`;
|
||
updateStatus(url, 'error', errorMessage);
|
||
showNotification(errorMessage, 'error');
|
||
|
||
// 延迟重新加载黑名单和统计信息
|
||
setTimeout(() => {
|
||
// 重新加载黑名单
|
||
loadRemoteBlacklists();
|
||
// 重新加载统计信息
|
||
loadShieldStats();
|
||
}, 3000);
|
||
}
|
||
} 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.classList.remove('status-transition', 'status-loading', 'status-success', 'status-error', 'status-fade-in', 'status-fade-out');
|
||
|
||
// 显示加载状态
|
||
statusElement.innerHTML = '<i class="fa fa-spinner fa-spin text-blue-500"></i> 正在添加...';
|
||
|
||
// 强制重排,确保过渡效果生效
|
||
void statusElement.offsetWidth;
|
||
|
||
// 添加过渡类和加载状态类
|
||
statusElement.classList.add('status-transition', 'status-fade-in', 'status-loading');
|
||
|
||
// 发送添加请求
|
||
const response = await fetch('/api/shield/blacklists', {
|
||
method: 'POST',
|
||
headers: {
|
||
'Content-Type': 'application/json'
|
||
},
|
||
body: JSON.stringify({ name, url })
|
||
});
|
||
|
||
// 解析服务器响应
|
||
let responseData;
|
||
try {
|
||
responseData = await response.json();
|
||
} catch (jsonError) {
|
||
responseData = {};
|
||
}
|
||
|
||
// 根据服务器响应判断是否成功
|
||
if (response.ok && (responseData.status === 'success' || !responseData.status)) {
|
||
// 清除之前的所有类
|
||
statusElement.classList.remove('status-transition', 'status-loading', 'status-success', 'status-error', 'status-fade-in', 'status-fade-out');
|
||
|
||
// 显示成功状态
|
||
statusElement.innerHTML = '<span class="text-green-500"><i class="fa fa-check"></i> 成功</span>';
|
||
|
||
// 强制重排,确保过渡效果生效
|
||
void statusElement.offsetWidth;
|
||
|
||
// 添加过渡类和成功状态类
|
||
statusElement.classList.add('status-transition', 'status-fade-in', 'status-success');
|
||
|
||
showNotification('黑名单添加成功', 'success');
|
||
// 清空输入框
|
||
if (nameInput) nameInput.value = '';
|
||
if (urlInput) urlInput.value = '';
|
||
// 重新加载黑名单
|
||
loadRemoteBlacklists();
|
||
// 重新加载统计信息
|
||
loadShieldStats();
|
||
} else {
|
||
// 清除之前的所有类
|
||
statusElement.classList.remove('status-transition', 'status-loading', 'status-success', 'status-error', 'status-fade-in', 'status-fade-out');
|
||
|
||
// 显示失败状态
|
||
const errorMessage = responseData.error || responseData.message || `添加失败: ${response.status}`;
|
||
statusElement.innerHTML = `<span class="text-red-500"><i class="fa fa-times"></i> ${errorMessage}</span>`;
|
||
|
||
// 强制重排,确保过渡效果生效
|
||
void statusElement.offsetWidth;
|
||
|
||
// 添加过渡类和错误状态类
|
||
statusElement.classList.add('status-transition', 'status-fade-in', 'status-error');
|
||
|
||
showNotification(errorMessage, 'error');
|
||
}
|
||
} catch (error) {
|
||
console.error('Error adding blacklist:', error);
|
||
|
||
// 清除之前的所有类
|
||
statusElement.classList.remove('status-transition', 'status-loading', 'status-success', 'status-error', 'status-fade-in', 'status-fade-out');
|
||
|
||
// 显示错误状态
|
||
const errorMessage = error.message || '添加黑名单失败';
|
||
statusElement.innerHTML = `<span class="text-red-500"><i class="fa fa-times"></i> ${errorMessage}</span>`;
|
||
|
||
// 强制重排,确保过渡效果生效
|
||
void statusElement.offsetWidth;
|
||
|
||
// 添加过渡类和错误状态类
|
||
statusElement.classList.add('status-transition', 'status-fade-in', 'status-error');
|
||
|
||
showNotification(errorMessage, 'error');
|
||
} finally {
|
||
// 3秒后渐变消失
|
||
setTimeout(() => {
|
||
// 添加淡出类
|
||
statusElement.classList.add('status-fade-out');
|
||
|
||
// 等待淡出动画完成后清除状态
|
||
setTimeout(() => {
|
||
// 清除所有类
|
||
statusElement.classList.remove('status-transition', 'status-loading', 'status-success', 'status-error', 'status-fade-in', 'status-fade-out');
|
||
// 清空状态显示
|
||
statusElement.innerHTML = '';
|
||
}, 300); // 与CSS动画持续时间一致
|
||
}, 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 = `
|
||
<div class="flex items-center space-x-2">
|
||
<i class="fa ${type === 'success' ? 'fa-check-circle' : type === 'error' ? 'fa-exclamation-circle' : 'fa-info-circle'}"></i>
|
||
<span>${message}</span>
|
||
</div>
|
||
`;
|
||
|
||
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();
|
||
}
|
||
});
|
||
}); |