处理集团OA待办页面,批量打开且高亮待办标签。
// ==UserScript==
// @name 批量打开新OA待办(多标签自动复选+高亮+全选+半选+对齐修复)
// @namespace http://tampermonkey.net/
// @version 5.4
// @description 高亮“省内待办(X)”,行复选框可选,表头复选框全选/反选/半选;防止表头触发排序;批量打开后恢复全选;切换标签页自动生效;修复表头与行复选框精确对齐。
// @match http://XXXX/backlog/cmit/web/index/*
// @grant none
// ==/UserScript==
(function () {
'use strict';
// --------------------------
// 样式:统一基准到 .cell
// --------------------------
const style = document.createElement('style');
style.textContent = `
.todo-tab-highlight {
color: #e60000 !important;
font-weight: bold !important;
background-color: #fff1f0 !important;
padding: 4px 10px !important;
border-radius: 6px !important;
border: 1px solid #ff4d4f !important;
display: inline-block !important;
}
.todo-num {
color: #e60000 !important;
font-weight: bold !important;
}
/* 统一把复选框基准设在 .cell(表头与每行的 cell)上 */
.el-table__header-wrapper thead tr th .cell.title,
.el-table__body tr.el-table__row td .cell {
position: relative !important;
/* 给复选框和文字留出相同的左侧空隙 */
padding-left: 36px !important;
}
/* 表头复选框与行复选框采用相同的绝对定位 */
.header-checkbox,
.todo-checkbox {
position: absolute !important;
left: 8px !important; /* 复选框距离 cell 左侧的偏移(可微调) */
top: 50% !important;
transform: translateY(-50%) !important;
margin: 0 !important;
cursor: pointer;
box-sizing: border-box !important;
}
/* 避免影响其它列,限制只针对第一列(可增强稳健性) */
.el-table__header-wrapper thead tr th.el-table_1_column_1 .cell.title,
.el-table__body tr.el-table__row td.el-table_1_column_1 .cell {
padding-left: 36px !important;
}
/* 按钮样式保持不变 */
#bulkOpenBtn {
position: fixed;
left: 10px;
top: 50%;
transform: translateY(-50%);
z-index: 9999;
padding: 10px 16px;
background-color: #409EFF;
color: #fff;
border: none;
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0,0,0,0.2);
font-size: 14px;
cursor: pointer;
}
#bulkOpenBtn:hover {
background-color: #66b1ff;
transform: translateY(-50%) scale(1.05);
}
`;
document.head.appendChild(style);
// --------------------------
// 基础工具函数(保持不变)
// --------------------------
function waitForElement(selector, timeout = 10000) {
return new Promise((resolve, reject) => {
const el = document.querySelector(selector);
if (el) return resolve(el);
const obs = new MutationObserver(() => {
const target = document.querySelector(selector);
if (target) {
obs.disconnect();
resolve(target);
}
});
obs.observe(document.body, { childList: true, subtree: true });
setTimeout(() => {
obs.disconnect();
reject(new Error(`Timeout waiting for element: ${selector}`));
}, timeout);
});
}
// 高亮标签(保持你之前的逻辑)
function highlightTodoTabs() {
document.querySelectorAll('.todo-num').forEach(numEl => {
const match = (numEl.textContent || '').trim().match(/\d+/);
const num = match ? parseInt(match[0], 10) : 0;
const tabText = numEl.closest('.text');
if (!tabText) return;
if (num > 0) {
tabText.classList.add('todo-tab-highlight');
numEl.classList.add('todo-num');
} else {
tabText.classList.remove('todo-tab-highlight');
tabText.removeAttribute('style');
numEl.classList.remove('todo-num');
numEl.removeAttribute('style');
}
});
}
// --------------------------
// 行复选框:插入到 .cell(与表头同级)以保证对齐
// --------------------------
function addCheckboxes() {
const rows = document.querySelectorAll('.el-table__body tr.el-table__Row, .el-table__body tr.el-table__row');
rows.forEach(row => {
// 找到行内的标题 span(原来的位置)
const titleSpan = row.querySelector('.itemTitle .item-title--click') || row.querySelector('.item-title--click');
if (!titleSpan) return;
// 找到对应的 .cell 容器(我们将复选框插到这个 cell 中)
const cellDiv = titleSpan.closest('.cell');
if (!cellDiv) return;
// 若 cell 已有复选框,则跳过
if (cellDiv.classList.contains('tm-has-checkbox')) return;
if (cellDiv.querySelector('.todo-checkbox')) {
cellDiv.classList.add('tm-has-checkbox');
return;
}
// 标记防重复
cellDiv.classList.add('tm-has-checkbox');
// 创建复选框并插入到 cell 的最前面(不移动原始 link 内容)
const checkbox = document.createElement('input');
checkbox.type = 'checkbox';
checkbox.className = 'todo-checkbox';
checkbox.checked = true;
// 阻止冒泡(避免触发行点击)
checkbox.addEventListener('click', e => e.stopPropagation());
checkbox.addEventListener('mousedown', e => e.stopPropagation());
checkbox.addEventListener('change', updateHeaderCheckbox);
// 插入 cell 的第一个子节点(保持 link 在原地,没有被移动)
cellDiv.insertBefore(checkbox, cellDiv.firstChild);
});
}
// --------------------------
// 表头复选框(保持原逻辑,但位置和定位一致)
// --------------------------
function addHeaderCheckbox() {
const headerCell = document.querySelector('.el-table__header-wrapper thead tr th .cell.title');
if (!headerCell) return;
if (headerCell.querySelector('.header-checkbox')) return;
const checkbox = document.createElement('input');
checkbox.type = 'checkbox';
checkbox.className = 'header-checkbox';
// 阻止冒泡,防止触发列排序
checkbox.addEventListener('click', e => e.stopPropagation());
checkbox.addEventListener('mousedown', e => e.stopPropagation());
checkbox.addEventListener('change', function () {
const rowCheckboxes = document.querySelectorAll('.todo-checkbox');
rowCheckboxes.forEach(cb => cb.checked = this.checked);
checkbox.indeterminate = false;
});
// 插入到 .cell.title 的最前面(与行的复选框插入点一致)
headerCell.insertBefore(checkbox, headerCell.firstChild);
// 默认全选并清除半选
const rowCheckboxes = document.querySelectorAll('.todo-checkbox');
rowCheckboxes.forEach(cb => cb.checked = true);
checkbox.checked = true;
checkbox.indeterminate = false;
}
function updateHeaderCheckbox() {
const header = document.querySelector('.header-checkbox');
const rowCheckboxes = document.querySelectorAll('.todo-checkbox');
const total = rowCheckboxes.length;
const checked = Array.from(rowCheckboxes).filter(cb => cb.checked).length;
if (!header) return;
if (checked === 0) {
header.checked = false;
header.indeterminate = false;
} else if (checked === total) {
header.checked = true;
header.indeterminate = false;
} else {
header.checked = false;
header.indeterminate = true;
}
}
// --------------------------
// 批量打开等核心功能(保持不变)
// --------------------------
function getTableLinks() {
const links = [];
const rows = document.querySelectorAll('.el-table__body tr.el-table__row');
rows.forEach(row => {
const cellDiv = row.querySelector('.itemTitle .item-title--click')?.closest('.cell') || row.querySelector('.cell');
const checkbox = cellDiv && cellDiv.querySelector('.todo-checkbox');
const link = row.querySelector('.itemTitle .link') || row.querySelector('.link');
if (cellDiv && checkbox && checkbox.checked && link) links.push(link);
});
return links;
}
function openLinks(links) {
if (links.length === 0) {
alert('当前未选中任何待办任务。');
return;
}
links.forEach(link => {
const ev = new MouseEvent('click', { ctrlKey: true, bubbles: true, cancelable: true });
link.dispatchEvent(ev);
});
}
function addFixedButton() {
if (document.getElementById('bulkOpenBtn')) return;
const button = document.createElement('button');
button.id = 'bulkOpenBtn';
button.innerText = '批量打开';
button.onclick = bulkOpenLinks;
document.body.appendChild(button);
}
document.addEventListener('keydown', event => {
if (event.ctrlKey && event.shiftKey && event.key === 'O') {
event.preventDefault();
bulkOpenLinks();
}
});
async function bulkOpenLinks() {
try {
await waitForElement('.el-table__body', 5000);
addCheckboxes();
addHeaderCheckbox();
const links = getTableLinks();
openLinks(links);
// 批量打开后恢复全选
const rowCheckboxes = document.querySelectorAll('.todo-checkbox');
rowCheckboxes.forEach(cb => cb.checked = true);
const header = document.querySelector('.header-checkbox');
if (header) { header.checked = true; header.indeterminate = false; }
} catch (error) {
console.error('打开失败:', error);
alert('加载表格失败,请稍后重试。');
}
}
// --------------------------
// 监听表格及标签变化(覆盖 SPA 渲染情况)
// --------------------------
function observeTableAndTabs() {
// 优先观察表格容器的父节点(如果存在),否则回退到 body
const mainContainer = document.querySelector('.el-table__body')?.parentElement || document.body;
const obs = new MutationObserver(() => {
addCheckboxes();
addHeaderCheckbox();
highlightTodoTabs();
});
obs.observe(mainContainer, { childList: true, subtree: true, characterData: true });
}
// 初始化
addFixedButton();
waitForElement('.el-menu--horizontal', 5000).then(observeTableAndTabs);
})();