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
|
|
});
|
|
} |