// 屏蔽管理页面功能实现
// 初始化屏蔽管理页面
function initShieldPage() {
// 加载屏蔽规则统计信息
loadShieldStats();
// 加载本地规则
loadLocalRules();
// 加载远程黑名单
loadRemoteBlacklists();
// 设置事件监听器
setupShieldEventListeners();
}
// 加载屏蔽规则统计信息
async function loadShieldStats() {
showLoading('加载屏蔽规则统计信息...');
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;
}
});
hideLoading();
} catch (error) {
console.error('加载屏蔽规则统计信息失败:', error);
showErrorMessage('加载屏蔽规则统计信息失败');
hideLoading();
}
}
// 加载本地规则
async function loadLocalRules() {
showLoading('加载本地规则...');
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);
hideLoading();
} catch (error) {
console.error('加载本地规则失败:', error);
showErrorMessage('加载本地规则失败');
hideLoading();
}
}
// 加载远程规则
async function loadRemoteRules() {
showLoading('加载远程规则...');
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);
hideLoading();
} catch (error) {
console.error('加载远程规则失败:', error);
showErrorMessage('加载远程规则失败');
hideLoading();
}
}
// 更新规则表格
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;
deleteBtn.innerHTML = '';
deleteBtn.addEventListener('click', handleDeleteRule);
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) {
const rule = e.target.closest('.delete-rule-btn').dataset.rule;
showLoading('删除规则中...');
try {
const response = await fetch('/api/shield', {
method: 'DELETE',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ rule })
});
if (!response.ok) {
throw new Error('Failed to delete rule');
}
showSuccessMessage('规则删除成功');
// 根据当前显示的规则类型重新加载对应的规则列表
if (currentRulesType === 'local') {
loadLocalRules();
} else {
loadRemoteRules();
}
// 重新加载统计信息
loadShieldStats();
hideLoading();
} catch (error) {
console.error('Error deleting rule:', error);
showErrorMessage('删除规则失败');
hideLoading();
}
}
// 添加新规则
async function handleAddRule() {
const rule = document.getElementById('new-rule').value.trim();
if (!rule) {
showErrorMessage('规则不能为空');
return;
}
showLoading('添加规则中...');
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();
hideLoading();
} catch (error) {
console.error('Error adding rule:', error);
showErrorMessage('添加规则失败');
hideLoading();
}
}
// 加载远程黑名单
async function loadRemoteBlacklists() {
showLoading('加载远程黑名单...');
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);
hideLoading();
} catch (error) {
console.error('加载远程黑名单失败:', error);
showErrorMessage('加载远程黑名单失败');
hideLoading();
}
}
// 判断黑名单是否过期(超过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;
}
showLoading('更新黑名单中...');
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');
} finally {
hideLoading();
}
}
// 处理删除黑名单
async function handleDeleteBlacklist(e) {
const url = e.target.closest('.delete-blacklist-btn').dataset.url;
if (!url) {
showToast('无效的黑名单URL', 'error');
return;
}
// 确认删除
if (!confirm('确定要删除这个黑名单吗?删除后将无法恢复。')) {
return;
}
showLoading('删除黑名单中...');
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');
} finally {
hideLoading();
}
}
// 处理添加黑名单
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;
}
showLoading('添加黑名单中...');
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();
hideLoading();
} catch (error) {
console.error('Error adding blacklist:', error);
showErrorMessage(error.message || '添加黑名单失败');
hideLoading();
}
}
// 当前显示的规则类型:'local' 或 'remote'
let currentRulesType = 'local';
// 设置事件监听器
function setupShieldEventListeners() {
// 本地规则管理事件
document.getElementById('save-rule-btn').addEventListener('click', handleAddRule);
// 远程黑名单管理事件
document.getElementById('save-blacklist-btn').addEventListener('click', handleAddBlacklist);
// 添加切换查看本地规则和远程规则的事件监听
if (document.getElementById('view-local-rules-btn')) {
document.getElementById('view-local-rules-btn').addEventListener('click', loadLocalRules);
}
if (document.getElementById('view-remote-rules-btn')) {
document.getElementById('view-remote-rules-btn').addEventListener('click', loadRemoteRules);
}
}
// 显示成功消息
function showSuccessMessage(message) {
showNotification(message, 'success');
}
// 显示错误消息
function showErrorMessage(message) {
showNotification(message, 'error');
}
// 显示加载状态
function showLoading(message = '加载中...') {
// 移除现有加载状态
hideLoading();
// 创建加载状态元素
const loading = document.createElement('div');
loading.className = 'loading-overlay fixed inset-0 bg-black/50 flex items-center justify-center z-50';
loading.innerHTML = `
`;
document.body.appendChild(loading);
}
// 隐藏加载状态
function hideLoading() {
const loading = document.querySelector('.loading-overlay');
if (loading) {
loading.remove();
}
}
// 显示通知
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();
}
});
});