根据xpath标识广告油猴脚本|屏蔽广告
作者:YXN-js 阅读量:346 发布日期:2025-02-26
localStorage版
// ==UserScript==
// @name XPath元素阻止程序_localStorage版
// @namespace http://tampermonkey.net/
// @version 2.0
// @description localStorage版元素屏蔽工具,支持XPath规则管理
// @author YXN
// @match *://*/*
// @grant none
// ==/UserScript==
(function() {
'use strict';
// 生成网站专属存储键名
const storageKey = `xpath_blocker_${location.hostname}`;
// 样式增强
const css = `
#blocker-btn {
position: fixed;
bottom: 30px;
right: 30px;
z-index: 2147483647;
width: 45px;
height: 45px;
border-radius: 50%;
background: #2196F3;
color: white;
border: none;
cursor: pointer;
box-shadow: 0 3px 6px rgba(0,0,0,0.16);
font-size: 20px;
transition: transform 0.2s;
}
.blocker-dialog {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background: white;
padding: 20px;
border-radius: 8px;
box-shadow: 0 8px 24px rgba(0,0,0,0.2);
z-index: 2147483646;
min-width: 320px;
}
.rule-item {
display: flex;
align-items: center;
padding: 8px;
border-bottom: 1px solid #eee;
}
.rule-index {
width: 30px;
color: #666;
}
.rule-text {
flex: 1;
font-family: monospace;
overflow-x: auto;
}
.delete-btn {
color: #f44336;
cursor: pointer;
margin-left: 10px;
}
`;
// 注入全局样式
const style = document.createElement('style');
style.textContent = css;
document.head.appendChild(style);
class BlockerSystem {
constructor() {
this.rules = this.loadRules();
this.initUI();
this.setupObserver();
}
// 从localStorage加载规则
loadRules() {
try {
return JSON.parse(localStorage.getItem(storageKey)) || [];
} catch {
return [];
}
}
// 保存规则到localStorage
saveRules() {
localStorage.setItem(storageKey, JSON.stringify(this.rules));
}
// 初始化界面
initUI() {
this.createButton();
this.applyRules();
}
// 创建悬浮按钮
createButton() {
this.btn = document.createElement('button');
this.btn.id = 'blocker-btn';
this.btn.textContent = '✖';
this.btn.addEventListener('click', () => this.showMainDialog());
document.body.appendChild(this.btn);
}
// 显示主对话框
showMainDialog() {
const dialog = document.createElement('div');
dialog.className = 'blocker-dialog';
dialog.innerHTML = `
<h3>元素屏蔽系统</h3>
<div style="margin:15px 0">
<button id="add-rule">➕ 添加新规则</button>
<button id="manage-rules">???? 管理规则</button>
</div>
`;
dialog.querySelector('#add-rule').addEventListener('click', () => this.showAddDialog());
dialog.querySelector('#manage-rules').addEventListener('click', () => this.showRuleManager());
this.showModal(dialog);
}
// 显示添加规则对话框
showAddDialog() {
const dialog = document.createElement('div');
dialog.className = 'blocker-dialog';
dialog.innerHTML = `
<h3>添加XPath规则</h3>
<textarea
id="xpath-input"
placeholder="输入XPath表达式(每行一个规则)"
style="width:100%; height:100px; margin:10px 0"
></textarea>
<div style="text-align:right">
<button class="cancel">取消</button>
<button class="confirm">保存</button>
</div>
`;
dialog.querySelector('.cancel').addEventListener('click', () => dialog.remove());
dialog.querySelector('.confirm').addEventListener('click', () => {
const input = dialog.querySelector('textarea').value;
this.addRules(input.split('\n').map(x => x.trim()).filter(x => x));
dialog.remove();
this.applyRules();
});
this.showModal(dialog);
}
// 显示规则管理器
showRuleManager() {
const dialog = document.createElement('div');
dialog.className = 'blocker-dialog';
dialog.innerHTML = `
<h3>已保存规则(共${this.rules.length}条)</h3>
<div id="rule-list" style="max-height:300px;overflow-y:auto;margin:10px 0"></div>
<div style="text-align:right">
<button class="cancel">关闭</button>
</div>
`;
const listContainer = dialog.querySelector('#rule-list');
this.rules.forEach((rule, index) => {
const item = document.createElement('div');
item.className = 'rule-item';
item.innerHTML = `
<div class="rule-index">${index + 1}.</div>
<div class="rule-text">${rule}</div>
<div class="delete-btn" data-index="${index}">????️</div>
`;
item.querySelector('.delete-btn').addEventListener('click', (e) => {
const index = parseInt(e.target.dataset.index);
this.deleteRule(index);
item.remove();
});
listContainer.appendChild(item);
});
dialog.querySelector('.cancel').addEventListener('click', () => dialog.remove());
this.showModal(dialog);
}
// 显示模态框
showModal(content) {
const overlay = document.createElement('div');
overlay.style = `
position:fixed;
top:0;
left:0;
width:100%;
height:100%;
background:rgba(0,0,0,0.5);
z-index:2147483645;
`;
overlay.appendChild(content);
overlay.addEventListener('click', (e) => {
if (e.target === overlay) overlay.remove();
});
document.body.appendChild(overlay);
}
// 添加新规则
addRules(newRules) {
newRules.forEach(rule => {
if (!this.rules.includes(rule)) {
this.rules.push(rule);
}
});
this.saveRules();
}
// 删除单条规则
deleteRule(index) {
if (index >= 0 && index < this.rules.length) {
this.rules.splice(index, 1);
this.saveRules();
this.applyRules();
}
}
// 应用所有规则
applyRules() {
this.rules.forEach(rule => {
try {
const nodes = document.evaluate(
rule,
document,
null,
XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE,
null
);
for (let i = 0; i < nodes.snapshotLength; i++) {
const node = nodes.snapshotItem(i);
node.style.display = 'none';
}
} catch (e) {
console.warn(`无效的XPath规则: ${rule}`, e);
}
});
}
// 设置DOM监听
setupObserver() {
new MutationObserver(() => {
this.applyRules();
}).observe(document, {
childList: true,
subtree: true
});
}
}
// 初始化系统
new BlockerSystem();
})();
GM存储版
// ==UserScript==
// @name 元素隐藏
// @namespace http://tampermonkey.net/
// @version 2.2
// @description 支持GM存储和规则管理的元素屏蔽工具,支持XPath、CSS选择器和正则域名匹配
// @author yxn
// @match *://*/*
// @grant GM_setValue
// @grant GM_getValue
// @grant GM_registerMenuCommand
// @grant GM_addStyle
// ==/UserScript==
(function() {
'use strict';
// 存储键名
const RULES_KEY = 'element_blocker_rules';
const SETTINGS_KEY = 'element_blocker_settings';
// 样式配置
GM_addStyle(`
#blocker-btn {
position: fixed;
bottom: 30px;
right: 30px;
z-index: 2147483647;
width: 45px;
height: 45px;
border-radius: 50%;
background: #2196F3;
color: white;
border: none;
cursor: pointer;
box-shadow: 0 3px 6px rgba(0,0,0,0.16);
font-size: 20px;
transition: transform 0.2s;
}
#blocker-btn:hover {
transform: scale(1.1);
}
.blocker-dialog {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background: white;
padding: 20px;
border-radius: 8px;
box-shadow: 0 8px 24px rgba(0,0,0,0.2);
z-index: 2147483646;
min-width: 500px;
max-width: 90vw;
max-height: 90vh;
overflow-y: auto;
}
.blocker-textarea {
width: 100%;
height: 120px;
margin: 12px 0;
padding: 8px;
border: 1px solid #ddd;
border-radius: 4px;
resize: vertical;
font-family: monospace;
}
.blocker-input {
width: 100%;
margin: 8px 0;
padding: 8px;
border: 1px solid #ddd;
border-radius: 4px;
font-family: monospace;
}
.blocker-btns {
display: flex;
gap: 8px;
justify-content: flex-end;
margin-top: 15px;
}
.blocker-btn {
padding: 8px 16px;
border: none;
border-radius: 4px;
cursor: pointer;
}
.blocker-confirm {
background: #4CAF50;
color: white;
}
.blocker-cancel {
background: #f44336;
color: white;
}
.blocker-selector-type {
margin: 10px 0;
display: flex;
gap: 15px;
align-items: center;
}
.blocker-rule-item {
padding: 10px;
border-bottom: 1px solid #eee;
display: flex;
justify-content: space-between;
align-items: flex-start;
}
.blocker-rule-content {
flex: 1;
word-break: break-all;
}
.blocker-rule-type {
background: #e0e0e0;
padding: 2px 6px;
border-radius: 3px;
font-size: 0.8em;
margin-right: 8px;
}
.blocker-rule-domain {
background: #bbdefb;
padding: 2px 6px;
border-radius: 3px;
font-size: 0.8em;
margin-right: 8px;
}
.blocker-rule-regex {
background: #c8e6c9;
padding: 2px 6px;
border-radius: 3px;
font-size: 0.8em;
margin-right: 8px;
}
.blocker-rule-actions {
display: flex;
gap: 5px;
margin-left: 10px;
}
.blocker-delete-btn {
background: #ff4444;
color: white;
border: none;
border-radius: 3px;
padding: 2px 6px;
cursor: pointer;
font-size: 0.8em;
}
.blocker-edit-btn {
background: #ff9800;
color: white;
border: none;
border-radius: 3px;
padding: 2px 6px;
cursor: pointer;
font-size: 0.8em;
}
.blocker-help {
font-size: 0.9em;
color: #666;
margin: 5px 0;
}
.blocker-tabs {
display: flex;
border-bottom: 1px solid #ddd;
margin-bottom: 15px;
}
.blocker-tab {
padding: 8px 16px;
cursor: pointer;
border: none;
background: none;
border-bottom: 2px solid transparent;
}
.blocker-tab.active {
border-bottom-color: #2196F3;
color: #2196F3;
}
.blocker-tab-content {
display: none;
}
.blocker-tab-content.active {
display: block;
}
.blocker-domain-type {
margin: 10px 0;
display: flex;
gap: 15px;
align-items: center;
}
.blocker-examples {
background: #f5f5f5;
padding: 10px;
border-radius: 4px;
margin: 10px 0;
font-size: 0.85em;
}
.blocker-example-item {
margin: 5px 0;
font-family: monospace;
}
.blocker-test-regex {
background: #e3f2fd;
padding: 10px;
border-radius: 4px;
margin: 10px 0;
}
.blocker-test-result {
margin-top: 5px;
padding: 5px;
border-radius: 3px;
font-weight: bold;
}
.blocker-test-valid {
background: #c8e6c9;
color: #2e7d32;
}
.blocker-test-invalid {
background: #ffcdd2;
color: #c62828;
}
`);
class ElementBlocker {
constructor() {
this.rules = GM_getValue(RULES_KEY, []);
this.settings = GM_getValue(SETTINGS_KEY, {
enableGlobalRules: true
});
this.observer = null;
this.currentHostname = location.hostname;
this.init();
}
init() {
// 初始化界面
this.createButton();
// 应用现有规则
this.applyRules();
// 监听动态内容
this.setupMutationObserver();
// 注册管理命令
GM_registerMenuCommand('管理屏蔽规则', () => this.showRuleManager());
GM_registerMenuCommand('设置', () => this.showSettings());
}
createButton() {
this.btn = document.createElement('button');
this.btn.id = 'blocker-btn';
this.btn.textContent = '⛔';
this.btn.title = '元素屏蔽器';
this.btn.addEventListener('click', () => this.showDialog());
document.body.appendChild(this.btn);
}
showDialog() {
if (this.dialog) return;
this.dialog = document.createElement('div');
this.dialog.className = 'blocker-dialog';
this.dialog.innerHTML = `
<h3 style="margin:0 0 15px">元素屏蔽器</h3>
<div class="blocker-tabs">
<button class="blocker-tab active" data-tab="add-rule">添加规则</button>
<button class="blocker-tab" data-tab="quick-rule">快速添加</button>
</div>
<div class="blocker-tab-content active" id="add-rule">
<div class="blocker-selector-type">
<label>
<input type="radio" name="selectorType" value="xpath" checked> XPath
</label>
<label>
<input type="radio" name="selectorType" value="css"> CSS选择器
</label>
</div>
<textarea class="blocker-textarea"
placeholder="输入要屏蔽的元素选择器(每行一个) 例如XPath: //div[@class='ad'] 例如CSS: .ad-banner, #sidebar-ad"></textarea>
<div class="blocker-domain-type">
<label>
<input type="radio" name="domainType" value="normal" checked> 普通域名
</label>
<label>
<input type="radio" name="domainType" value="regex"> 正则表达式
</label>
</div>
<input type="text" class="blocker-input" id="domains-input"
placeholder="适用域名(多个用逗号分隔,留空表示当前域名)"
value="${this.currentHostname}">
<div class="blocker-test-regex" id="regex-test-area" style="display: none;">
<div>正则表达式测试:</div>
<input type="text" class="blocker-input" id="test-domain-input"
placeholder="输入要测试的域名" value="${this.currentHostname}">
<button class="blocker-btn" id="test-regex-btn" style="padding: 4px 8px; margin-top: 5px;">测试正则表达式</button>
<div id="regex-test-result"></div>
</div>
<div class="blocker-help" id="domain-help">
提示:每行输入一个选择器,支持XPath和CSS两种格式
</div>
<div class="blocker-examples" id="regex-examples" style="display: none;">
<strong>正则表达式示例:</strong>
<div class="blocker-example-item">^example\\.com$ - 精确匹配example.com</div>
<div class="blocker-example-item">.*\\.example\\.com$ - 匹配所有example.com的子域名</div>
<div class="blocker-example-item">(example\\.com|test\\.com) - 匹配example.com或test.com</div>
<div class="blocker-example-item">.*\\.(com|net)$ - 匹配所有.com或.net域名</div>
<div class="blocker-example-item">\\d+ - 匹配包含数字的域名</div>
</div>
<div class="blocker-btns">
<button class="blocker-btn blocker-cancel">取消</button>
<button class="blocker-btn blocker-confirm">确定</button>
</div>
</div>
<div class="blocker-tab-content" id="quick-rule">
<div class="blocker-selector-type">
<label>
<input type="radio" name="quickSelectorType" value="css" checked> CSS选择器
</label>
</div>
<input type="text" class="blocker-input" id="quick-rule-input"
placeholder="输入CSS选择器,如:.ad, #banner">
<div class="blocker-domain-type">
<label>
<input type="radio" name="quickDomainType" value="normal" checked> 普通域名
</label>
<label>
<input type="radio" name="quickDomainType" value="regex"> 正则表达式
</label>
</div>
<input type="text" class="blocker-input" id="quick-domains-input"
placeholder="适用域名(多个用逗号分隔,留空表示当前域名)"
value="${this.currentHostname}">
<div class="blocker-help">提示:快速添加单个CSS规则</div>
<div class="blocker-btns">
<button class="blocker-btn blocker-cancel">取消</button>
<button class="blocker-btn blocker-confirm" id="quick-confirm">确定</button>
</div>
</div>
`;
// 域名类型切换
const updateDomainHelp = () => {
const domainType = this.dialog.querySelector('input[name="domainType"]:checked').value;
const helpText = this.dialog.querySelector('#domain-help');
const examples = this.dialog.querySelector('#regex-examples');
const testArea = this.dialog.querySelector('#regex-test-area');
if (domainType === 'regex') {
helpText.textContent = '提示:使用正则表达式匹配域名,支持复杂的匹配规则';
examples.style.display = 'block';
testArea.style.display = 'block';
} else {
helpText.textContent = '提示:每行输入一个选择器,支持XPath和CSS两种格式';
examples.style.display = 'none';
testArea.style.display = 'none';
}
};
this.dialog.querySelectorAll('input[name="domainType"]').forEach(radio => {
radio.addEventListener('change', updateDomainHelp);
});
// 初始化帮助文本
updateDomainHelp();
// 正则表达式测试功能
this.dialog.querySelector('#test-regex-btn').addEventListener('click', () => {
this.testRegexPattern();
});
// 标签页切换
this.dialog.querySelectorAll('.blocker-tab').forEach(tab => {
tab.addEventListener('click', (e) => {
const tabName = e.target.getAttribute('data-tab');
this.dialog.querySelectorAll('.blocker-tab').forEach(t => t.classList.remove('active'));
this.dialog.querySelectorAll('.blocker-tab-content').forEach(c => c.classList.remove('active'));
e.target.classList.add('active');
this.dialog.querySelector(`#${tabName}`).classList.add('active');
});
});
// 事件绑定
this.dialog.querySelector('.blocker-cancel').addEventListener('click', () => this.closeDialog());
this.dialog.querySelector('.blocker-confirm').addEventListener('click', () => {
const textarea = this.dialog.querySelector('textarea');
const selectorType = this.dialog.querySelector('input[name="selectorType"]:checked').value;
const domainType = this.dialog.querySelector('input[name="domainType"]:checked').value;
const domains = this.dialog.querySelector('#domains-input').value;
this.processInput(textarea.value, selectorType, domains, domainType);
this.closeDialog();
});
this.dialog.querySelector('#quick-confirm').addEventListener('click', () => {
const ruleInput = this.dialog.querySelector('#quick-rule-input');
const selectorType = 'css'; // 快速添加只支持CSS
const domainType = this.dialog.querySelector('input[name="quickDomainType"]:checked').value;
const domains = this.dialog.querySelector('#quick-domains-input').value;
this.processInput(ruleInput.value, selectorType, domains, domainType);
this.closeDialog();
});
document.body.appendChild(this.dialog);
}
testRegexPattern() {
const regexInput = this.dialog.querySelector('#domains-input');
const testDomainInput = this.dialog.querySelector('#test-domain-input');
const resultDiv = this.dialog.querySelector('#regex-test-result');
const regexPattern = regexInput.value.trim();
const testDomain = testDomainInput.value.trim();
if (!regexPattern) {
resultDiv.innerHTML = '<div class="blocker-test-result blocker-test-invalid">请输入正则表达式</div>';
return;
}
if (!testDomain) {
resultDiv.innerHTML = '<div class="blocker-test-result blocker-test-invalid">请输入测试域名</div>';
return;
}
try {
const regex = new RegExp(regexPattern);
const matches = regex.test(testDomain);
if (matches) {
resultDiv.innerHTML = `<div class="blocker-test-result blocker-test-valid">✓ 正则表达式有效,匹配测试域名 "${testDomain}"</div>`;
} else {
resultDiv.innerHTML = `<div class="blocker-test-result blocker-test-invalid">✗ 正则表达式有效,但不匹配测试域名 "${testDomain}"</div>`;
}
} catch (e) {
resultDiv.innerHTML = `<div class="blocker-test-result blocker-test-invalid">✗ 无效的正则表达式: ${e.message}</div>`;
}
}
closeDialog() {
this.dialog.remove();
this.dialog = null;
}
processInput(input, selectorType, domainsInput, domainType) {
const domains = this.parseDomains(domainsInput, domainType);
const newRules = input.split('\n')
.map(rule => rule.trim())
.filter(rule => rule.length > 0)
.map(rule => ({
type: selectorType,
rule: rule,
domains: domains,
domainType: domainType,
id: Date.now() + Math.random().toString(36).substr(2, 9)
}));
if (newRules.length > 0) {
this.rules = [...this.rules, ...newRules];
this.saveRules();
this.applyRules();
}
}
parseDomains(domainsInput, domainType) {
if (!domainsInput || domainsInput.trim() === '') {
return [{
value: this.currentHostname,
type: 'normal'
}];
}
return domainsInput.split(',')
.map(domain => domain.trim())
.filter(domain => domain.length > 0)
.map(domain => ({
value: domain,
type: domainType
}));
}
isRuleApplicable(rule) {
if (!rule.domains || rule.domains.length === 0) {
return true;
}
return rule.domains.some(domainObj => {
if (domainObj.type === 'normal') {
// 普通域名匹配
const domain = domainObj.value;
// 处理通配符域名
if (domain.startsWith('*.')) {
const baseDomain = domain.substring(2);
return this.currentHostname === baseDomain || this.currentHostname.endsWith('.' + baseDomain);
}
// 精确匹配
if (this.currentHostname === domain) {
return true;
}
// 子域名匹配(如规则是 example.com,当前是 sub.example.com)
if (this.currentHostname.endsWith('.' + domain)) {
return true;
}
return false;
} else if (domainObj.type === 'regex') {
// 正则表达式匹配
try {
const regex = new RegExp(domainObj.value);
return regex.test(this.currentHostname);
} catch (e) {
console.warn(`无效的正则表达式: ${domainObj.value}`, e);
return false;
}
}
return false;
});
}
applyRules() {
const applicableRules = this.rules.filter(rule => this.isRuleApplicable(rule));
applicableRules.forEach(ruleObj => {
try {
if (ruleObj.type === 'xpath') {
// XPath选择器
const nodes = document.evaluate(
ruleObj.rule,
document,
null,
XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE,
null
);
for (let i = 0; i < nodes.snapshotLength; i++) {
const node = nodes.snapshotItem(i);
if (node && node.style) {
node.style.display = 'none';
}
}
} else if (ruleObj.type === 'css') {
// CSS选择器
const elements = document.querySelectorAll(ruleObj.rule);
elements.forEach(element => {
if (element.style) {
element.style.display = 'none';
}
});
}
} catch (e) {
console.warn(`无效的${ruleObj.type.toUpperCase()}规则: ${ruleObj.rule}`, e);
}
});
}
saveRules() {
GM_setValue(RULES_KEY, this.rules);
}
saveSettings() {
GM_setValue(SETTINGS_KEY, this.settings);
}
setupMutationObserver() {
this.observer = new MutationObserver(mutations => {
let shouldUpdate = false;
mutations.forEach(mutation => {
if (mutation.addedNodes.length > 0) shouldUpdate = true;
});
if (shouldUpdate) this.applyRules();
});
this.observer.observe(document, {
childList: true,
subtree: true
});
}
showRuleManager() {
const applicableRules = this.rules.filter(rule => this.isRuleApplicable(rule));
const allRules = this.rules;
const manager = document.createElement('div');
manager.className = 'blocker-dialog';
manager.innerHTML = `
<h3 style="margin:0 0 15px">屏蔽规则管理</h3>
<div class="blocker-tabs">
<button class="blocker-tab active" data-tab="current-rules">当前网站规则</button>
<button class="blocker-tab" data-tab="all-rules">所有规则</button>
</div>
<div class="blocker-tab-content active" id="current-rules">
<h4>对 ${this.currentHostname} 有效的规则 (${applicableRules.length}条)</h4>
<div style="max-height: 300px; overflow-y: auto; margin-bottom: 15px; border: 1px solid #ddd; border-radius: 4px;">
${applicableRules.length > 0 ?
applicableRules.map((rule, index) => `
<div class="blocker-rule-item">
<div class="blocker-rule-content">
<span class="blocker-rule-type">${rule.type.toUpperCase()}</span>
${rule.domains.map(domain => `
<span class="${domain.type === 'regex' ? 'blocker-rule-regex' : 'blocker-rule-domain'}">
${domain.value} ${domain.type === 'regex' ? '(正则)' : ''}
</span>
`).join('')}
<code>${rule.rule}</code>
</div>
<div class="blocker-rule-actions">
<button class="blocker-delete-btn" data-id="${rule.id}">删除</button>
</div>
</div>
`).join('') :
'<div style="padding: 20px; text-align: center; color: #666;">暂无规则</div>'
}
</div>
</div>
<div class="blocker-tab-content" id="all-rules">
<h4>所有规则 (${allRules.length}条)</h4>
<div style="max-height: 300px; overflow-y: auto; margin-bottom: 15px; border: 1px solid #ddd; border-radius: 4px;">
${allRules.length > 0 ?
allRules.map((rule, index) => `
<div class="blocker-rule-item">
<div class="blocker-rule-content">
<span class="blocker-rule-type">${rule.type.toUpperCase()}</span>
${rule.domains.map(domain => `
<span class="${domain.type === 'regex' ? 'blocker-rule-regex' : 'blocker-rule-domain'}">
${domain.value} ${domain.type === 'regex' ? '(正则)' : ''}
</span>
`).join('')}
<code>${rule.rule}</code>
</div>
<div class="blocker-rule-actions">
<button class="blocker-delete-btn" data-id="${rule.id}">删除</button>
</div>
</div>
`).join('') :
'<div style="padding: 20px; text-align: center; color: #666;">暂无规则</div>'
}
</div>
</div>
<div class="blocker-btns">
<button class="blocker-btn blocker-cancel">关闭</button>
<button class="blocker-btn" style="background:#ff9800" id="clear-all">清空所有规则</button>
</div>
`;
// 标签页切换
manager.querySelectorAll('.blocker-tab').forEach(tab => {
tab.addEventListener('click', (e) => {
const tabName = e.target.getAttribute('data-tab');
manager.querySelectorAll('.blocker-tab').forEach(t => t.classList.remove('active'));
manager.querySelectorAll('.blocker-tab-content').forEach(c => c.classList.remove('active'));
e.target.classList.add('active');
manager.querySelector(`#${tabName}`).classList.add('active');
});
});
// 事件绑定
manager.querySelector('.blocker-cancel').addEventListener('click', () => manager.remove());
// 删除单个规则
manager.querySelectorAll('.blocker-delete-btn').forEach(btn => {
btn.addEventListener('click', (e) => {
const ruleId = e.target.getAttribute('data-id');
this.rules = this.rules.filter(rule => rule.id !== ruleId);
this.saveRules();
this.applyRules();
manager.remove();
this.showRuleManager(); // 刷新管理界面
});
});
// 清空所有规则
manager.querySelector('#clear-all').addEventListener('click', () => {
if (confirm('确定要清空所有屏蔽规则吗?')) {
this.rules = [];
this.saveRules();
this.applyRules();
manager.remove();
}
});
document.body.appendChild(manager);
}
showSettings() {
const settingsDialog = document.createElement('div');
settingsDialog.className = 'blocker-dialog';
settingsDialog.innerHTML = `
<h3 style="margin:0 0 15px">设置</h3>
<div>
<label>
<input type="checkbox" id="enable-global-rules" ${this.settings.enableGlobalRules ? 'checked' : ''}>
启用全局规则(无域名限制的规则)
</label>
</div>
<div class="blocker-help" style="margin-top: 10px;">
全局规则会对所有网站生效,请谨慎使用
</div>
<div class="blocker-btns">
<button class="blocker-btn blocker-cancel">取消</button>
<button class="blocker-btn blocker-confirm">保存</button>
</div>
`;
// 事件绑定
settingsDialog.querySelector('.blocker-cancel').addEventListener('click', () => settingsDialog.remove());
settingsDialog.querySelector('.blocker-confirm').addEventListener('click', () => {
this.settings.enableGlobalRules = settingsDialog.querySelector('#enable-global-rules').checked;
this.saveSettings();
this.applyRules();
settingsDialog.remove();
});
document.body.appendChild(settingsDialog);
}
}
// 启动脚本
new ElementBlocker();
})();
YXN-js
2025-02-26