// ==UserScript==
// @name 内蒙古高考招生计划添加彩色标签
// @namespace http://tampermonkey.net/
// @version 2026.6.22
// @description 【添加彩色标签后的页面仅供参考,本人不对其负责】脚本原理只是将某些文本变为彩色背景,不会改变文本内容
// @author AN drew
// @match https://www.nm.zsks.cn/**/jhzy.html*
// @grant GM_addStyle
// @run-at document-end
// ==/UserScript==
(function() {
'use strict';
// ====================== 配置区 ======================
const CONFIG = {
// 标签颜色
colors: {
direction: '#e74c3c', // 定向 - 红色
national: '#3498db', // 国家专项计划 - 蓝色
local: '#27ae60', // 地方专项计划 - 绿色
university: '#f39c12', // 高校专项计划 - 金色
freeMedical: '#e84393', // 国家免费医学生 - 粉红
nationalTeacher: '#ad1457',// 国家公费师范生 - 紫红
localTeacher: '#9b59b6', // 地方公费师范生 - 紫色
excellentTeacher: '#2e7d32',// 优师专项 - 深绿色
military: '#215e21', // 定向培养军士生 - 深蓝灰
planSuffix: '#8e44ad', // 专业名称中的(XXX专项计划) - 紫色
teacher: '#d35400', // 专业名称中的(师范类) - 棕色
location: '#2c3e50', // 专业名称中的(定向到XX) - 深灰蓝
project: '#e67e22', // 备注中的XXX项目 - 橙色
base: '#1abc9c', // 备注中的XXX基地 - 青色
language: '#167b67', // 外语语种 - 墨绿
subject: '#00b894', // 选科要求 - 薄荷绿
},
// 匹配关键词
keywords: {
direction: '定向',
national: '国家专项计划',
local: '地方专项计划',
university: '高校专项计划',
freeMedical: '国家免费医学生',
nationalTeacher: '国家公费师范生',
localTeacher: '地方公费师范生',
excellentTeacher: '优师专项',
military: '定向培养军士生',
planSuffix: /([^)]*专项计划)/g, // 匹配(XXX专项计划)
teacher: /(师范类)/g, // 匹配(师范类)
location: /(定向到[^)]*)|定向到[^,,。]*|([^)]*[市县旗])/g, // 匹配(定向到XX)
project: /[^,,。、\(;]{2,}(项目|计划)(?=[,,。]|$)/g, // 匹配XXX项目/计划
base: /[^,,。、\(;]*(? {
return `.tag-${key} { background-color: ${CONFIG.colors[key]}; }`;
}).join('\n');
GM_addStyle(`
/* 通用标签样式 */
.tag-label {
display: inline-block;
padding: 1px 8px;
border-radius: 12px;
font-size: 11px;
font-weight: 600;
line-height: 1.6;
letter-spacing: 0.3px;
white-space: nowrap;
color: #fff;
margin: 0 2px;
box-shadow: 0 1px 3px rgba(0,0,0,0.12);
}
${styleRules}
`);
GM_addStyle(`
/* 让单元格内容更整齐 */
#table tbody td {
vertical-align: middle;
}
/* 备注列允许换行,但标签不换行 */
#table tbody td:nth-child(11) .tag-label {
white-space: nowrap;
}
/* 专业名称列(第3列)整体加粗 */
#table tbody td:nth-child(3) {
font-weight: bold;
text-align: left !important;
}
/* 该列中的标签独占一行,并保持间距 */
#table tbody td:nth-child(3) .tag-label {
display: block;
width: fit-content;
max-width: 100%;
margin: 3px 0;
}
`);
// ====================== 工具函数 ======================
/** 获取单元格的纯文本(去除已有标签) */
function getCellText(td) {
if (!td) return '';
// 克隆节点,避免影响原DOM
const clone = td.cloneNode(true);
// 移除所有标签(带有 .tag-label 的span)
clone.querySelectorAll('.tag-label').forEach(el => el.remove());
return clone.textContent.trim();
}
/** 安全设置单元格HTML,保留原有结构 */
function setCellHTML(td, html) {
if (!td) return;
td.innerHTML = html;
}
/** 对文本中的匹配项进行替换,返回HTML字符串 */
function replaceWithTag(text, regex, tagClass, tagText) {
if (!text) return text;
// 如果tagText未提供,使用匹配到的完整文本
return text.replace(regex, function(match) {
const displayText = tagText || match;
return `${displayText}`;
});
}
/** 处理单个单元格:根据列索引应用不同规则 */
function processCell(td, colIndex) {
if (!td) return;
// 如果已经包含标签,直接返回,不再处理
if (td.querySelector('.tag-label')) {
return;
}
const rawText = getCellText(td);
if (!rawText) return;
let result = rawText;
// ---- 列2: 专业名称 ----
if (colIndex === 2) {
let modified = rawText;
// 匹配 (XXX专项计划)—— planSuffix
if (CONFIG.keywords.planSuffix.test(rawText)) {
CONFIG.keywords.planSuffix.lastIndex = 0;
modified = replaceWithTag(modified, CONFIG.keywords.planSuffix, 'tag-planSuffix');
}
// 匹配 (师范类)—— teacher
if (CONFIG.keywords.teacher.test(rawText)) {
CONFIG.keywords.teacher.lastIndex = 0;
modified = replaceWithTag(modified, CONFIG.keywords.teacher, 'tag-teacher');
}
// 匹配(定向到XX)—— location
if (CONFIG.keywords.location.test(rawText)) {
CONFIG.keywords.location.lastIndex = 0;
modified = replaceWithTag(modified, CONFIG.keywords.location, 'tag-location');
}
result = modified;
}
// ---- 列3: 计划性质 ----
else if (colIndex === 3) {
if (rawText === CONFIG.keywords.direction) {
result = `${rawText}`;
} else if (rawText === CONFIG.keywords.freeMedical) {
result = `${rawText}`;
} else if (rawText === CONFIG.keywords.localTeacher) {
result = `${rawText}`;
} else if (rawText === CONFIG.keywords.nationalTeacher) {
result = `${rawText}`;
} else if (rawText === CONFIG.keywords.excellentTeacher) {
result = `${rawText}`;
}
}
// ---- 列4: 计划类别 ----
else if (colIndex === 4) {
if (rawText === CONFIG.keywords.national) {
result = `${rawText}`;
} else if (rawText === CONFIG.keywords.local) {
result = `${rawText}`;
} else if (rawText === CONFIG.keywords.university) {
result = `${rawText}`;
} else if (rawText === CONFIG.keywords.military) {
result = `${rawText}`;
}
}
// ---- 列7: 外语语种 ----
else if (colIndex === 7) {
if (rawText && rawText !== '不限') {
const parts = rawText.match(/[^,,]+/g) || [];
const cleanParts = parts.map(s => s.trim()).filter(s => s !== '');
if (cleanParts.length === 0) {
result = rawText;
} else if (cleanParts.length === 1) {
result = `${cleanParts[0]}`;
} else {
const tagged = cleanParts.map(p => `${p}`).join('');
result = tagged;
}
}
}
// ---- 列9: 选科要求 ----
else if (colIndex === 9) {
if (rawText && rawText !== '-' && rawText !== '不限' && rawText !== '不提科目要求') {
// 使用正则匹配所有非逗号(中英文)的连续字符段
const parts = rawText.match(/[^,,]+/g) || [];
const cleanParts = parts.map(s => s.trim()).filter(s => s !== '');
console.log('选科拆分:', cleanParts);
if (cleanParts.length === 0) {
result = rawText;
} else if (cleanParts.length === 1) {
result = `${cleanParts[0]}`;
} else {
const tagged = cleanParts.map(p => `${p}`).join('');
result = tagged;
}
}
}
// ---- 列10: 专业备注 ----
else if (colIndex === 10) {
let modified = rawText;
// 匹配 XXX项目
if (CONFIG.keywords.project.test(rawText)) {
CONFIG.keywords.project.lastIndex = 0;
modified = replaceWithTag(modified, CONFIG.keywords.project, 'tag-project');
}
// 匹配 XXX基地
if (CONFIG.keywords.base.test(rawText)) {
CONFIG.keywords.base.lastIndex = 0;
modified = replaceWithTag(modified, CONFIG.keywords.base, 'tag-base');
}
// 匹配 定向到XX
if (CONFIG.keywords.location.test(rawText)) {
CONFIG.keywords.location.lastIndex = 0;
modified = replaceWithTag(modified, CONFIG.keywords.location, 'tag-location');
}
result = modified;
}
// 只有发生变化时才更新
if (result !== rawText) {
setCellHTML(td, result);
}
}
/** 处理表格中的所有行 */
function processTable() {
const table = document.querySelector('#table');
if (!table) return;
const tbody = table.querySelector('tbody');
if (!tbody) return;
const rows = tbody.querySelectorAll('tr');
if (rows.length === 0) return;
// 遍历每一行,处理特定列
rows.forEach(row => {
const cells = row.querySelectorAll('td');
if (cells.length < 13) return; // 确保列数足够
// 列索引:0专业组,1代号,2名称,3性质,4类别,5学制,6学费,7语种,8口试,9选科,10备注,11地点,12计划数
// 需要处理的列:2(专业名称), 3(计划性质), 4(计划类别), 9(选科要求),10(专业备注)
processCell(cells[2], 2);
processCell(cells[3], 3);
processCell(cells[4], 4);
processCell(cells[7], 7);
processCell(cells[9], 9);
processCell(cells[10], 10);
});
// 在所有行处理完成后,表头同步列宽(延迟确保 DOM 更新)
setTimeout(syncColumnWidth, 100);
}
//表头同步列宽
function syncColumnWidth() {
const dataTable = document.querySelector('#table');
if (!dataTable) return;
const tbody = dataTable.querySelector('tbody');
if (!tbody) return;
const firstRow = tbody.querySelector('tr');
if (!firstRow) return;
const dataCells = firstRow.querySelectorAll('td');
if (dataCells.length === 0) return;
const headerContainer = document.querySelector('.fixed-table-header');
if (!headerContainer) return;
const headerRow = headerContainer.querySelector('tr');
if (!headerRow) return;
const headerCells = headerRow.querySelectorAll('th');
if (headerCells.length !== dataCells.length) return;
// 同步表头表格整体宽度(防止滚动条/布局差异)
const headerTable = headerContainer.querySelector('table');
const dataTableRect = dataTable.getBoundingClientRect();
if (headerTable) {
headerTable.style.width = dataTableRect.width + 'px';
headerTable.style.tableLayout = 'fixed';
}
// 逐列设置宽度(使用 getBoundingClientRect 获取精确像素)
dataCells.forEach((td, index) => {
const width = td.getBoundingClientRect().width;
if (width > 0) {
const th = headerCells[index];
th.style.width = width + 'px';
th.style.minWidth = width + 'px';
th.style.maxWidth = width + 'px';
}
});
}
// ====================== 主逻辑 ======================
/** 初始化:设置MutationObserver监听表格变化 */
function init() {
// 先尝试立即处理一次(如果表格已存在)
const table = document.querySelector('#table');
if (table && table.querySelector('tbody tr')) {
processTable();
}
// 使用MutationObserver监听表格内容变化
const observer = new MutationObserver(function(mutations) {
// 检查是否有新增的行或内容变化
let shouldProcess = false;
for (const mutation of mutations) {
if (mutation.type === 'childList' && mutation.addedNodes.length > 0) {
shouldProcess = true;
break;
}
if (mutation.type === 'characterData') {
shouldProcess = true;
break;
}
}
if (shouldProcess) {
// 延迟执行,等待DOM完全更新
clearTimeout(window._tagTimer);
window._tagTimer = setTimeout(() => {
processTable();
}, 150);
}
});
// 观察目标:整个#table及其子树
const targetNode = document.querySelector('#table') || document.querySelector('#tablediv');
if (targetNode) {
observer.observe(targetNode, {
childList: true,
subtree: true,
characterData: true,
});
} else {
// 如果#table还没出现,观察整个文档
observer.observe(document.body, {
childList: true,
subtree: true,
});
}
// 额外:当搜索框输入时,延迟处理(因为搜索会重新渲染表格)
const searchInput = document.querySelector('.fixed-table-toolbar .search input');
if (searchInput) {
searchInput.addEventListener('input', function() {
clearTimeout(window._tagTimer);
window._tagTimer = setTimeout(() => {
processTable();
}, 300);
});
}
// 也监听页面上的"上一页"链接点击(页面切换)
document.addEventListener('click', function(e) {
const target = e.target.closest('a');
if (target && target.id === 'syhref') {
clearTimeout(window._tagTimer);
window._tagTimer = setTimeout(() => {
// 页面切换后,等待新表格加载
const checkInterval = setInterval(() => {
const tbody = document.querySelector('#table tbody');
if (tbody && tbody.querySelector('tr')) {
clearInterval(checkInterval);
processTable();
}
}, 200);
// 最多等待5秒
setTimeout(() => clearInterval(checkInterval), 5000);
}, 300);
}
});
// 保存observer到全局,便于调试
window.__tagObserver = observer;
}
// ====================== 启动 ======================
// 等待页面加载完成
if (document.readyState === 'complete' || document.readyState === 'interactive') {
init();
} else {
document.addEventListener('DOMContentLoaded', init);
}
// 额外:如果页面已经完全加载但表格还没出现,使用setInterval兜底检查
let fallbackCount = 0;
const fallbackTimer = setInterval(() => {
const table = document.querySelector('#table');
if (table && table.querySelector('tbody tr')) {
processTable();
clearInterval(fallbackTimer);
}
fallbackCount++;
if (fallbackCount > 30) { // 30次后停止(约15秒)
clearInterval(fallbackTimer);
}
}, 500);
console.log('[内蒙古高考招生计划添加彩色标签] 脚本已启动 ✅');
})();