您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
获取<table>标签的表格元素,根据表头形成筛选列表,本地对数据进行筛选
// ==UserScript== // @name 本地表格数据筛选 // @name:zh-CN 本地表格数据筛选 // @name:zh-TW 本地表格數據篩選 // @name:en Filter tabular data // @namespace http://tampermonkey.net/ // @version 2.0.0 // @license GPL-3.0 // @author ShineByPupil // @description 获取<table>标签的表格元素,根据表头形成筛选列表,本地对数据进行筛选 // @description:zh-CN 获取<table>标签的表格元素,根据表头形成筛选列表,本地对数据进行筛选 // @description:zh-TW 獲取<table>標簽的表格元素,根據表頭形成篩選列表,本地對數據進行篩選 // @description:en Obtain the table element of the <table> tag, form a filtering list based on the table header, and locally filter the data // @match *://*/* // @icon data:image/png;base64,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 // @noframes // @grant none // ==/UserScript== (function () { "use strict"; function findEmptyIndex(array) { // 寻找第一个空单元的索引 const index = array.findIndex((_, i) => !(i in array)); // 如果找到空单元,则将新元素插入 if (index !== -1) { return index; } else { // 如果数组中没有空单元,则将新元素追加到数组末尾 return array.length; } } // 渲染帧优化 const rafDebounce = function (fn) { let rafId = null; return function (...args) { rafId && cancelAnimationFrame(rafId); rafId = requestAnimationFrame(() => { fn.apply(this, args); rafId = null; }); }; }; class MessageBox extends HTMLElement { constructor() { super(); this.attachShadow({ mode: "open" }); this.shadowRoot.innerHTML = ` <div class="message"></div> <style> .message { position: fixed; z-index: 100; top: 20px; left: 50%; transform: translateX(-50%); padding: 10px 20px; background-color: #333; color: #fff; border-radius: 5px; box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2); display: none; /* 默认隐藏 */ } </style> `; this.message = this.shadowRoot.querySelector(".message"); } show(message, duration = 2500) { this.message.textContent = message; this.message.style.display = "block"; // 显示消息 // 设置一定时间后自动隐藏消息 setTimeout(() => { this.message.style.display = "none"; }, duration); } } class SearchDialog extends HTMLElement { /** * WeakMap 存储结构说明: * * 键:table 表格元素 * * 值:SearchTable 表格状态容器 */ weakMap = new Map(); form = null; startX = 0; startY = 0; initialX = 0; initialY = 0; x = 0; y = 0; constructor() { super(); this.attachShadow({ mode: "open" }); this.shadowRoot.innerHTML = ` <div class="searchDialog"> <div class="searchDialog__header"> <label class="searchDialog__title">搜索</label> <span class="closeBtn">✕</span> </div> <div class="searchDialog__content scroll-bar"></div> <div class="searchDialog__footer"> <label> <input class="searchDialog__notClose" type="checkbox" checked/> <span style="margin-left: 4px">查询后不关闭</span> </label> <input type="button" class="resetBtn" value="重置"/> <input type="button" class="closeBtn" value="取消"/> <input type="button" class="confirmBtn primary" value="确定"/> </div> </div> <style> .searchDialog { font-size: 12px; font-family: "Microsoft YaHei", sans-serif; display: none; z-index: 9999; flex-direction: column; width: 30vw; min-width: 600px; box-sizing: border-box; position: fixed; background-color: #fff; border: 1px solid #ddd; box-shadow: 0 0 10px rgba(0, 0, 0, 0.2); border-radius: 5px; user-select: none; } .searchDialog__header { display: flex; justify-content: space-between; align-items: center; padding: 10px 20px; font-size: 18px; cursor: move; } .searchDialog__header span { transition: color 0.3s; cursor: pointer; font-size: 20px; } .searchDialog__header span:hover { color: red; } .searchDialog__content { margin: 20px; padding: 0 10px; flex: 1; max-height: 400px; overflow-y: auto; overflow-x: hidden; } .searchDialog__footer { padding: 10px; display: flex; justify-content: flex-end; align-items: center; user-select: none; } .searchDialog__footer > label { display: flex; } .searchDialog__footer > label > * { cursor: pointer; } .fade-in { animation: fadeIn 0.3s; } .fade-out { animation: fadeOut 0.3s; } @keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } } @keyframes fadeOut { from { opacity: 1; } to { opacity: 0; } } input[type='button'] { display: inline-block; padding: 4px 10px; border-radius: 4px; background-color: #fff; border: 1px solid #dcdfe6; color: #606266; text-decoration: none; cursor: pointer; margin-left: 10px; } input[type='button']:hover { background-color: #f5f7fa; border-color: #409eff; color: #409eff; } input[type='button'].primary { background-color: #409eff; border-color: #409eff; color: #fff; } input[type='button'].primary:hover { background-color: #66b1ff; border-color: #66b1ff; } .scroll-bar { overflow-y: scroll; } .scroll-bar::-webkit-scrollbar { margin: 10px; height: 10px; width: 10px; border: 2px solid #333; /* 设置滚动条的边框颜色 */ } .scroll-bar::-webkit-scrollbar-track { background-color: #f1f1f1; } .scroll-bar::-webkit-scrollbar-thumb { background-color: #888; border-radius: 5px; } .scroll-bar::-webkit-scrollbar-thumb:hover { background-color: #555; } </style> `; this.dialog = this.shadowRoot.querySelector(".searchDialog"); this.content = this.shadowRoot.querySelector(".searchDialog__content"); this.notClose = this.dialog.querySelector(".searchDialog__notClose"); this.init(); } show(table) { if (!this.weakMap.has(table)) { this.weakMap.set(table, new SearchTable(table)); } const searchFrom = this.weakMap.get(table).searchFrom; if (this.form !== searchFrom) { this.form?.remove(); this.form = searchFrom; this.content.appendChild(searchFrom); } requestAnimationFrame(() => { this.dialog.style.display = "flex"; this.dialog.style.left = "calc(50% - 15vw)"; this.dialog.style.top = "10vh"; this.dialog.classList.remove("fade-out"); this.dialog.classList.add("fade-in"); }); } close() { this.dialog.classList.add("fade-out"); this.dialog.classList.remove("fade-in"); this.dialog.onanimationend = function () { this.style.display = "none"; this.onanimationend = null; }; } init() { this.initEvent(); this.initTable(); } initEvent() { // 点击事件 - footer 按钮组 this.dialog.addEventListener("click", async (event) => { const { target, target: { className, tagName }, } = event; if (className.includes("closeBtn")) { this.close(); // 关闭 } else if (className.includes("resetBtn")) { this.form.reset(); // 重置 } else if (className.includes("confirmBtn")) { await this.form.confirm(); // 确定 !this.notClose.checked && this.close(); } else if (tagName === "INPUT" && target.type === "checkbox") { target.parentElement.classList.toggle("active"); } }); // 键盘事件 - esc 关闭弹窗 document.addEventListener("keydown", (event) => { if (this.dialog.style.display === "flex" && event.key === "Escape") this.close(); }); const startDrag = (event) => { this.startX = event.clientX; this.startY = event.clientY; this.initialX = this.x; this.initialY = this.y; document.addEventListener("mousemove", onDrag); document.addEventListener("mouseup", endDrag); }; const onDrag = rafDebounce((event) => { event.preventDefault(); const dx = event.clientX - this.startX; const dy = event.clientY - this.startY; this.x = this.initialX + dx; this.y = this.initialY + dy; this.dialog.style.transform = `translate(${this.x}px, ${this.y}px)`; }); const endDrag = () => { document.removeEventListener("mousemove", onDrag); document.removeEventListener("mouseup", endDrag); }; // 拖动事件 this.dialog .querySelector(".searchDialog__header") .addEventListener("mousedown", startDrag); } initTable() { document.querySelectorAll("table").forEach((table) => { const thead = table.querySelector("thead"); const tbody = table.querySelector("tbody"); if (thead && tbody) { const parent = table.parentElement; parent.classList.add("filter-table"); const template_style = document.createElement("template"); table.classList.add("scroll-bar"); template_style.innerHTML = ` <style> table { position: relative; max-height: 50vh; overflow-y: auto !important; } .scroll-bar { overflow-y: scroll; } .scroll-bar::-webkit-scrollbar { margin: 10px; height: 10px; width: 10px; border: 2px solid #333; /* 设置滚动条的边框颜色 */ } .scroll-bar::-webkit-scrollbar-track { background-color: #f1f1f1; } .scroll-bar::-webkit-scrollbar-thumb { background-color: #888; border-radius: 5px; } .scroll-bar::-webkit-scrollbar-thumb:hover { background-color: #555; } </style> `; document.head.appendChild(template_style.content); const wrapper = document.createElement("div"); const shadow = wrapper.attachShadow({ mode: "open" }); shadow.innerHTML = ` <button class="open-filter-Dialog-btn" title="打开筛选弹窗">F</button> <style> button { position: absolute; top: 0; left: 0; width: 20px; height: 20px; line-height: 20px; padding: 0 4px; background-color: #fff; border: 1px solid #409eff; cursor: pointer; } </style> `; shadow.querySelector("button").onclick = () => this.show(table); thead.appendChild(wrapper); } }); } } class SearchFrom extends HTMLElement { table = null; filterMap = null; constructor() { super(); this.attachShadow({ mode: "open" }); this.shadowRoot.innerHTML = ` <article class="filter_form"> <span class="add">添加</span> <form></form> </article> <style> .filter_form { font-size: 12px; } .filter_form form { position: relative; margin-top: 4px; } .filter_form .add, .filter_form .del { user-select: none; cursor: pointer; } </style> `; this.article = this.shadowRoot.querySelector("article"); this.form = this.shadowRoot.querySelector("form"); this.init(); } init() { this.initEvent(); } initEvent() { this.article.addEventListener("wheel", (event) => { const innerElement = event.composedPath()[0]; if (innerElement.tagName === "SELECT") { event.preventDefault(); const length = innerElement.options.length; const index = innerElement.selectedIndex; const direction = event.wheelDeltaY > 0 ? "up" : "down"; innerElement.selectedIndex = direction === "up" ? index === 0 ? length - 1 : index - 1 : index === length - 1 ? 0 : index + 1; } }); this.article.addEventListener("click", (event) => { const innerElement = event.composedPath()[0]; if (innerElement.className.includes("add")) { this.addSearchInput(); } else if (innerElement.className.includes("del")) { // 删除规则 innerElement.parentNode.remove(); } }); this.article.addEventListener("input", (event) => { const innerElement = event.composedPath()[0]; if (innerElement.tagName === "INPUT") { innerElement.value ? innerElement.classList.remove("error") : innerElement.classList.add("error"); } }); } reset() { this.form.innerHTML = ""; } async confirm() { try { this.formTrim(); await this.validate(); this.filter(); } catch (e) { console.error(e); } } addSearchInput() { const searchInput = document.createElement("search-input"); searchInput.setAttribute( "options", JSON.stringify([...this.filterMap.keys()]), ); searchInput.filterMap = this.filterMap; this.form.appendChild(searchInput); } formTrim() { this.form.querySelectorAll("input[type=text]").forEach((input) => { input.value = input.value.includes(",") ? input.value .split(",") .map((n) => n.trim()) .filter((n) => n) .join(", ") : input.value.trim(); }); } validate() { let flag = true; this.form.childNodes.forEach((node) => { const { checkbox, input } = node; if (checkbox.checked && !input.value) { flag = false; input.classList.add("error", "shake"); } else { input.classList.remove("error"); } }); return new Promise((resolve, reject) => { if (flag) { resolve(); } else { messageBox.show("表单验证未通过"); reject(new Error("表单验证未通过")); } }); } getRules() { const rules_AND = []; const rules_OR = []; const rules_NOT = []; this.form.childNodes.forEach((node) => { const { checkbox, select1, select2, input } = node; if (checkbox.checked) { const rules = input.value.split(",").map((keyword) => ({ keyword: keyword.trim(), colIndexs: Array.from(this.filterMap.get(select2.value)), })); switch (select1.value) { case "AND": rules_AND.push(...rules); break; case "OR": rules_OR.push(...rules); break; case "NOT": rules_NOT.push(...rules); break; } } }); return { rules_AND, rules_OR, rules_NOT }; } filter() { const data = this.table.searchTable.data; const rules = this.getRules(); const trList = Array.from(this.table.querySelector("tbody").children); let count = 0; // 筛选 data.forEach((trData, i) => { if (this.isVisible(trData, rules)) { trList[i].style.display = ""; count++; } else { trList[i].style.display = "none"; } }); messageBox.show(`搜索成功,一共查询出 ${count} 数据`); } // 根据给定的规则确定表格行是否可见 isVisible(trData, rules) { const { rules_AND, rules_OR, rules_NOT } = rules; const isVisible_AND = rules_AND.length && rules_AND.every((rule) => { const { keyword, colIndexs } = rule; return colIndexs.some((index) => trData?.[index]?.includes(keyword)); }); const isVisible_OR = rules_OR.length && rules_OR.some((rule) => { const { keyword, colIndexs } = rule; return colIndexs.some((index) => trData?.[index]?.includes(keyword)); }); const isVisible_NOT = rules_NOT.length ? rules_NOT.every((rule) => { const { keyword, colIndexs } = rule; return !colIndexs.some((index) => trData?.[index]?.includes(keyword), ); }) : true; return ( isVisible_NOT && (rules_AND.length || rules_OR.length ? isVisible_AND || isVisible_OR : true) ); } } class SearchInput extends HTMLElement { static get observedAttributes() { return ["options"]; } options = []; filterMap = null; constructor() { super(); this.attachShadow({ mode: "open" }); this.shadowRoot.innerHTML = ` <div class="form-example active"> <input type="checkbox" checked> <select> <option label="AND" value="AND"></option> <option label="OR" value="OR"></option> <option label="NOT" value="NOT"></option> </select> <select></select> <input type="text"/> <span class="del">删除</span> </div> <style> .form-example { display: flex; align-items: center; margin: 0 -4px 6px; } .form-example > * { margin: 0 4px; } .form-example:not(.active) * { border-color: #d9d9d9; color: #d9d9d9; } input[type='text'] { flex: 1; } input[type='text'].error { border-color: red; } input[type='text'].shake { animation: shake 0.6s ease-in-out 1; } select, input[type='text'] { box-sizing: border-box; height: 32px; padding: 4px 11px; border-radius: 4px; border: 1px solid #d9d9d9; transition: all 0.3s; } select:focus, input:focus { outline: none; border-color: #4096ff; border-inline-end-width: 1px; } select:hover, input:hover { border-color: #4096ff; border-inline-end-width: 1px; } @keyframes shake { 0% { transform: translateX(0); } 25% { transform: translateX(-6px); } 50% { transform: translateX(4px); } 75% { transform: translateX(-2px); } 100% { transform: translateX(0); } } </style> `; this.example = this.shadowRoot.querySelector(".form-example"); this.checkbox = this.shadowRoot.querySelector("input[type='checkbox']"); this.select1 = this.shadowRoot.querySelector("select:nth-child(2)"); this.select2 = this.shadowRoot.querySelector("select:nth-child(3)"); this.input = this.shadowRoot.querySelector("input[type='text']"); this.del = this.shadowRoot.querySelector(".del"); this.init(); } attributeChangedCallback(attrName, oldVal, newVal) { if (attrName === "options") { this.options = JSON.parse(newVal); this.select2.innerHTML = this.options .map((n) => `<option label="${n}" value="${n}"></option>`) .join(""); } } init() { this.initEvent(); } initEvent() { this.checkbox.onclick = () => { this.example.classList.toggle("active"); }; this.example.onanimationend = function (event) { const innerElement = event.composedPath()[0]; if (innerElement.className.includes("shake")) { innerElement.classList.remove("shake"); } }; } } class SearchTable { constructor(table) { this.table = table; table.searchTable = this; const { header, filterMap } = this.parseTable(table); this.header = header; this.filterMap = filterMap; this.searchFrom = document.createElement("search-from"); this.searchFrom.table = table; this.searchFrom.filterMap = filterMap; } parseTable(table) { const thead = table.querySelector("thead"); if (!thead) { throw new Error("缺少表头"); } let header = []; // 表头数据 thead.querySelectorAll("tr").forEach((tr) => { header.push( Array.from(tr.querySelectorAll("th")).map((n) => { return { label: n.textContent, rowspan: n.rowSpan ?? 1, // 高度 colspan: n.colSpan ?? 1, // 宽度 }; }), ); }); let dp = new Array(header.length).fill(0).map(() => []); header.forEach((tr, i) => { tr.forEach((td) => { const index = findEmptyIndex(dp[i]); const { colspan, rowspan } = td; for (let k = i; k < i + rowspan; k++) { for (let l = index; l < index + colspan; l++) { dp[k][l] ??= td.label; } } }); }); let filterMap = new Map(); // 过滤器映射 for (let i = 0; i < dp.length; i++) { for (let j = 0; j < dp[i].length; j++) { if (dp[i][j]) { filterMap.has(dp[i][j]) || filterMap.set(dp[i][j], new Set()); filterMap.get(dp[i][j]).add(j); } } } return { header, filterMap }; } get data() { return Array.from(this.table.querySelectorAll("tbody > tr")).map((tr) => { return Array.from(tr.querySelectorAll("td")).map((n) => { return n.textContent; }); }); } } customElements.define("message-box", MessageBox); customElements.define("search-dialog", SearchDialog); customElements.define("search-from", SearchFrom); customElements.define("search-input", SearchInput); const messageBox = document.createElement("message-box"); document.body.appendChild(messageBox); window.addEventListener("load", function (event) { const searchDialog = document.createElement("search-dialog"); document.body.appendChild(searchDialog); }); })();
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址