422 lines
13 KiB
JavaScript
422 lines
13 KiB
JavaScript
// 屏蔽规则管理模块
|
||
|
||
// 全局变量
|
||
let rules = [];
|
||
let currentPage = 1;
|
||
let itemsPerPage = 50; // 默认每页显示50条规则
|
||
let filteredRules = [];
|
||
|
||
// 初始化屏蔽规则面板
|
||
function initRulesPanel() {
|
||
// 加载规则列表
|
||
loadRules();
|
||
|
||
// 绑定添加规则按钮事件
|
||
document.getElementById('add-rule-btn').addEventListener('click', addNewRule);
|
||
|
||
// 绑定刷新规则按钮事件
|
||
document.getElementById('reload-rules-btn').addEventListener('click', reloadRules);
|
||
|
||
// 绑定搜索框事件
|
||
document.getElementById('rule-search').addEventListener('input', filterRules);
|
||
|
||
// 绑定每页显示数量变更事件
|
||
document.getElementById('items-per-page').addEventListener('change', () => {
|
||
itemsPerPage = parseInt(document.getElementById('items-per-page').value);
|
||
currentPage = 1; // 重置为第一页
|
||
renderRulesList();
|
||
});
|
||
|
||
// 绑定分页按钮事件
|
||
document.getElementById('prev-page-btn').addEventListener('click', goToPreviousPage);
|
||
document.getElementById('next-page-btn').addEventListener('click', goToNextPage);
|
||
document.getElementById('first-page-btn').addEventListener('click', goToFirstPage);
|
||
document.getElementById('last-page-btn').addEventListener('click', goToLastPage);
|
||
}
|
||
|
||
// 加载规则列表
|
||
async function loadRules() {
|
||
try {
|
||
const rulesPanel = document.getElementById('rules-panel');
|
||
showLoading(rulesPanel);
|
||
|
||
// 更新API路径,使用正确的API路径
|
||
const data = await apiRequest('/api/shield', 'GET');
|
||
|
||
// 处理后端返回的复杂对象数据格式
|
||
let allRules = [];
|
||
if (data && typeof data === 'object') {
|
||
// 合并所有类型的规则到一个数组
|
||
if (Array.isArray(data.domainRules)) allRules = allRules.concat(data.domainRules);
|
||
if (Array.isArray(data.domainExceptions)) allRules = allRules.concat(data.domainExceptions);
|
||
if (Array.isArray(data.regexRules)) allRules = allRules.concat(data.regexRules);
|
||
if (Array.isArray(data.regexExceptions)) allRules = allRules.concat(data.regexExceptions);
|
||
}
|
||
|
||
rules = allRules;
|
||
filteredRules = [...rules];
|
||
currentPage = 1; // 重置为第一页
|
||
renderRulesList();
|
||
|
||
// 更新规则数量统计卡片
|
||
if (window.updateRulesCount && typeof window.updateRulesCount === 'function') {
|
||
window.updateRulesCount(rules.length);
|
||
}
|
||
} catch (error) {
|
||
console.error('加载规则失败:', error);
|
||
if (typeof window.showNotification === 'function') {
|
||
window.showNotification('加载规则失败', 'danger');
|
||
}
|
||
} finally {
|
||
const rulesPanel = document.getElementById('rules-panel');
|
||
hideLoading(rulesPanel);
|
||
}
|
||
}
|
||
|
||
// 渲染规则列表
|
||
function renderRulesList() {
|
||
const rulesList = document.getElementById('rules-list');
|
||
const paginationInfo = document.getElementById('pagination-info');
|
||
|
||
// 清空列表
|
||
rulesList.innerHTML = '';
|
||
|
||
if (filteredRules.length === 0) {
|
||
// 使用更友好的空状态显示
|
||
rulesList.innerHTML = '<tr><td colspan="4" class="text-center py-4">' +
|
||
'<div class="empty-state">' +
|
||
'<div class="empty-icon"><i class="fas fa-shield-alt text-muted"></i></div>' +
|
||
'<div class="empty-title text-muted">暂无规则</div>' +
|
||
'<div class="empty-description text-muted">点击添加按钮或刷新规则来获取规则列表</div>' +
|
||
'</div>' +
|
||
'</td></tr>';
|
||
paginationInfo.textContent = '共0条规则';
|
||
updatePaginationButtons();
|
||
return;
|
||
}
|
||
|
||
// 计算分页数据
|
||
const totalPages = Math.ceil(filteredRules.length / itemsPerPage);
|
||
const startIndex = (currentPage - 1) * itemsPerPage;
|
||
const endIndex = Math.min(startIndex + itemsPerPage, filteredRules.length);
|
||
const currentRules = filteredRules.slice(startIndex, endIndex);
|
||
|
||
// 渲染当前页的规则
|
||
currentRules.forEach((rule, index) => {
|
||
const row = document.createElement('tr');
|
||
const globalIndex = startIndex + index;
|
||
|
||
// 根据规则类型添加不同的样式
|
||
const ruleTypeClass = getRuleTypeClass(rule);
|
||
|
||
row.innerHTML = `
|
||
<td class="rule-id">${globalIndex + 1}</td>
|
||
<td class="rule-content ${ruleTypeClass}"><pre>${escapeHtml(rule)}</pre></td>
|
||
<td class="rule-actions">
|
||
<button class="btn btn-danger btn-sm delete-rule" data-index="${globalIndex}">
|
||
<i class="fas fa-trash"></i> 删除
|
||
</button>
|
||
</td>
|
||
`;
|
||
|
||
// 添加行动画效果
|
||
row.style.opacity = '0';
|
||
row.style.transform = 'translateY(10px)';
|
||
rulesList.appendChild(row);
|
||
|
||
// 使用requestAnimationFrame确保动画平滑
|
||
requestAnimationFrame(() => {
|
||
row.style.transition = 'opacity 0.3s ease, transform 0.3s ease';
|
||
row.style.opacity = '1';
|
||
row.style.transform = 'translateY(0)';
|
||
});
|
||
});
|
||
|
||
// 绑定删除按钮事件
|
||
document.querySelectorAll('.delete-rule').forEach(button => {
|
||
button.addEventListener('click', (e) => {
|
||
const index = parseInt(e.currentTarget.dataset.index);
|
||
deleteRule(index);
|
||
});
|
||
});
|
||
|
||
// 更新分页信息
|
||
paginationInfo.textContent = `显示 ${startIndex + 1}-${endIndex} 条,共 ${filteredRules.length} 条规则,第 ${currentPage}/${totalPages} 页`;
|
||
|
||
// 更新分页按钮状态
|
||
updatePaginationButtons();
|
||
}
|
||
|
||
// 更新分页按钮状态
|
||
function updatePaginationButtons() {
|
||
const totalPages = Math.ceil(filteredRules.length / itemsPerPage);
|
||
const prevBtn = document.getElementById('prev-page-btn');
|
||
const nextBtn = document.getElementById('next-page-btn');
|
||
const firstBtn = document.getElementById('first-page-btn');
|
||
const lastBtn = document.getElementById('last-page-btn');
|
||
|
||
prevBtn.disabled = currentPage === 1;
|
||
nextBtn.disabled = currentPage === totalPages || totalPages === 0;
|
||
firstBtn.disabled = currentPage === 1;
|
||
lastBtn.disabled = currentPage === totalPages || totalPages === 0;
|
||
}
|
||
|
||
// 上一页
|
||
function goToPreviousPage() {
|
||
if (currentPage > 1) {
|
||
currentPage--;
|
||
renderRulesList();
|
||
}
|
||
}
|
||
|
||
// 下一页
|
||
function goToNextPage() {
|
||
const totalPages = Math.ceil(filteredRules.length / itemsPerPage);
|
||
if (currentPage < totalPages) {
|
||
currentPage++;
|
||
renderRulesList();
|
||
}
|
||
}
|
||
|
||
// 第一页
|
||
function goToFirstPage() {
|
||
currentPage = 1;
|
||
renderRulesList();
|
||
}
|
||
|
||
// 最后一页
|
||
function goToLastPage() {
|
||
currentPage = Math.ceil(filteredRules.length / itemsPerPage);
|
||
renderRulesList();
|
||
}
|
||
|
||
// 添加新规则
|
||
async function addNewRule() {
|
||
const ruleInput = document.getElementById('rule-input');
|
||
const rule = ruleInput.value.trim();
|
||
|
||
if (!rule) {
|
||
if (typeof window.showNotification === 'function') {
|
||
window.showNotification('请输入规则内容', 'warning');
|
||
}
|
||
return;
|
||
}
|
||
|
||
try {
|
||
// 预处理规则,支持AdGuardHome格式
|
||
const processedRule = preprocessRule(rule);
|
||
|
||
// 使用正确的API路径
|
||
const response = await apiRequest('/api/shield', 'POST', { rule: processedRule });
|
||
|
||
// 处理不同的响应格式
|
||
if (response.success || response.status === 'success') {
|
||
rules.push(processedRule);
|
||
filteredRules = [...rules];
|
||
ruleInput.value = '';
|
||
|
||
// 添加后跳转到最后一页,显示新添加的规则
|
||
currentPage = Math.ceil(filteredRules.length / itemsPerPage);
|
||
renderRulesList();
|
||
|
||
// 更新规则数量统计
|
||
if (window.updateRulesCount && typeof window.updateRulesCount === 'function') {
|
||
window.updateRulesCount(rules.length);
|
||
}
|
||
|
||
if (typeof window.showNotification === 'function') {
|
||
window.showNotification('规则添加成功', 'success');
|
||
}
|
||
} else {
|
||
if (typeof window.showNotification === 'function') {
|
||
window.showNotification('规则添加失败:' + (response.message || '未知错误'), 'danger');
|
||
}
|
||
}
|
||
} catch (error) {
|
||
console.error('添加规则失败:', error);
|
||
if (typeof window.showNotification === 'function') {
|
||
window.showNotification('添加规则失败', 'danger');
|
||
}
|
||
}
|
||
}
|
||
|
||
// 删除规则
|
||
async function deleteRule(index) {
|
||
if (!confirm('确定要删除这条规则吗?')) {
|
||
return;
|
||
}
|
||
|
||
try {
|
||
const rule = filteredRules[index];
|
||
const rowElement = document.querySelectorAll('#rules-list tr')[index];
|
||
|
||
// 添加删除动画
|
||
if (rowElement) {
|
||
rowElement.style.transition = 'opacity 0.3s ease, transform 0.3s ease';
|
||
rowElement.style.opacity = '0';
|
||
rowElement.style.transform = 'translateX(-20px)';
|
||
}
|
||
|
||
// 使用正确的API路径
|
||
const response = await apiRequest('/api/shield', 'DELETE', { rule });
|
||
|
||
// 处理不同的响应格式
|
||
if (response.success || response.status === 'success') {
|
||
// 在原规则列表中找到并删除
|
||
const originalIndex = rules.indexOf(rule);
|
||
if (originalIndex !== -1) {
|
||
rules.splice(originalIndex, 1);
|
||
}
|
||
|
||
// 在过滤后的列表中删除
|
||
filteredRules.splice(index, 1);
|
||
|
||
// 如果当前页没有数据了,回到上一页
|
||
const totalPages = Math.ceil(filteredRules.length / itemsPerPage);
|
||
if (currentPage > totalPages && totalPages > 0) {
|
||
currentPage = totalPages;
|
||
}
|
||
|
||
// 等待动画完成后重新渲染列表
|
||
setTimeout(() => {
|
||
renderRulesList();
|
||
|
||
// 更新规则数量统计
|
||
if (window.updateRulesCount && typeof window.updateRulesCount === 'function') {
|
||
window.updateRulesCount(rules.length);
|
||
}
|
||
|
||
if (typeof window.showNotification === 'function') {
|
||
window.showNotification('规则删除成功', 'success');
|
||
}
|
||
}, 300);
|
||
} else {
|
||
// 恢复行样式
|
||
if (rowElement) {
|
||
rowElement.style.opacity = '1';
|
||
rowElement.style.transform = 'translateX(0)';
|
||
}
|
||
|
||
if (typeof window.showNotification === 'function') {
|
||
window.showNotification('规则删除失败:' + (response.message || '未知错误'), 'danger');
|
||
}
|
||
}
|
||
} catch (error) {
|
||
console.error('删除规则失败:', error);
|
||
if (typeof window.showNotification === 'function') {
|
||
window.showNotification('删除规则失败', 'danger');
|
||
}
|
||
}
|
||
}
|
||
|
||
// 重新加载规则
|
||
async function reloadRules() {
|
||
if (!confirm('确定要重新加载所有规则吗?这将覆盖当前内存中的规则。')) {
|
||
return;
|
||
}
|
||
|
||
try {
|
||
const rulesPanel = document.getElementById('rules-panel');
|
||
showLoading(rulesPanel);
|
||
|
||
// 使用正确的API路径和方法 - PUT请求到/api/shield
|
||
await apiRequest('/api/shield', 'PUT');
|
||
|
||
// 重新加载规则列表
|
||
await loadRules();
|
||
|
||
// 触发数据刷新事件,通知其他模块数据已更新
|
||
if (typeof window.triggerDataRefresh === 'function') {
|
||
window.triggerDataRefresh('rules');
|
||
}
|
||
|
||
if (typeof window.showNotification === 'function') {
|
||
window.showNotification('规则重新加载成功', 'success');
|
||
}
|
||
} catch (error) {
|
||
console.error('重新加载规则失败:', error);
|
||
if (typeof window.showNotification === 'function') {
|
||
window.showNotification('重新加载规则失败', 'danger');
|
||
}
|
||
} finally {
|
||
const rulesPanel = document.getElementById('rules-panel');
|
||
hideLoading(rulesPanel);
|
||
}
|
||
}
|
||
|
||
// 过滤规则
|
||
function filterRules() {
|
||
const searchTerm = document.getElementById('rule-search').value.toLowerCase();
|
||
|
||
if (searchTerm) {
|
||
filteredRules = rules.filter(rule => rule.toLowerCase().includes(searchTerm));
|
||
} else {
|
||
filteredRules = [...rules];
|
||
}
|
||
|
||
currentPage = 1; // 重置为第一页
|
||
renderRulesList();
|
||
}
|
||
|
||
// HTML转义,防止XSS攻击
|
||
function escapeHtml(text) {
|
||
const map = {
|
||
'&': '&',
|
||
'<': '<',
|
||
'>': '>',
|
||
'"': '"',
|
||
"'": '''
|
||
};
|
||
return text.replace(/[&<>'"]/g, m => map[m]);
|
||
}
|
||
|
||
// 根据规则类型返回对应的CSS类名
|
||
function getRuleTypeClass(rule) {
|
||
// 简单的规则类型判断
|
||
if (rule.startsWith('||') || rule.startsWith('|http')) {
|
||
return 'rule-type-url';
|
||
} else if (rule.startsWith('@@')) {
|
||
return 'rule-type-exception';
|
||
} else if (rule.startsWith('#')) {
|
||
return 'rule-type-comment';
|
||
} else if (rule.includes('$')) {
|
||
return 'rule-type-filter';
|
||
}
|
||
return 'rule-type-standard';
|
||
}
|
||
|
||
// 预处理规则,支持多种规则格式
|
||
function preprocessRule(rule) {
|
||
// 移除首尾空白字符
|
||
let processed = rule.trim();
|
||
|
||
// 处理AdGuardHome格式的规则
|
||
if (processed.startsWith('0.0.0.0 ') || processed.startsWith('127.0.0.1 ')) {
|
||
const parts = processed.split(' ');
|
||
if (parts.length >= 2) {
|
||
// 转换为AdBlock Plus格式
|
||
processed = '||' + parts[1] + '^';
|
||
}
|
||
}
|
||
|
||
return processed;
|
||
}
|
||
|
||
// 导出函数,供其他模块调用
|
||
window.updateRulesCount = function(count) {
|
||
const rulesCountElement = document.getElementById('rules-count');
|
||
if (rulesCountElement) {
|
||
rulesCountElement.textContent = count;
|
||
}
|
||
}
|
||
|
||
// 导出初始化函数
|
||
window.initRulesPanel = initRulesPanel;
|
||
|
||
// 注册到面板导航系统
|
||
if (window.registerPanelModule) {
|
||
window.registerPanelModule('rules-panel', {
|
||
init: initRulesPanel,
|
||
refresh: loadRules
|
||
});
|
||
} |