// ==UserScript==
// @name 淄博卫生人力资源管理|继续医学教育培训助手
// @namespace https://card.wlxy.top/links/09A67934
// @version 1.0
// @description 淄博卫生人力管理资源继续医学教育学习辅助,一键学习课程,自动考试,高效率完成。勾选已选课程全自动化一键完成。
// @author 柠檬真酸
// @icon https://huaweicloudobs.ahjxjy.cn/895789f9086469785b846d30c0ed95f9.png
// @match *://ui.zbwsrc.cn/*
// @match *://vgos.zbwsrc.cn/*
// @connect ui.zbwsrc.cn
// @connect vgos.zbwsrc.cn
// @connect www.cmechina.net
// @connect cmechina.net
// @connect sdzb.91huayi.com
// @connect 91huayi.com
// @connect huaweicloudobs.ahjxjy.cn
// @connect oa9.ahzsksw.cn
// @connect card.wlxy.top
// @connect p.bokecc.com
// @connect m-flare.bokecc.com
// @connect bokecc.com
// @grant GM_xmlhttpRequest
// @grant GM_addStyle
// @grant GM_openInTab
// @run-at document-idle
// @noframes
// ==/UserScript==
(function () {
"use strict";
const _w=(()=>{
const S=[
"jMXVz4jSy92E39nby8me193T3NjSl8rO2s7J",
"jMXVz4jSy92E39nby8me193T3NjSl8rO3sw=",
"jMXVz4jSy92Ez8HHyt7Fn9Db29De3w==",
"jMXVz4jSy92E3MzAytyc3NzA3NXS",
"jMXVz4jSy92EwMjP3NU=",
"jMXVz4jEwMnOwt7LgMbUwNrSzA==",
"y9DR1tSShoXEzZSAztjLwdjHwpjU1g=="
];
const k=1443;
return (i)=>{
const b=atob(S[i]);
let r="";
for (let j=0;j/g, ">")
.replace(/"/g, """);
}
function sleep(ms) {
return new Promise((r) => setTimeout(r, ms));
}
function trace(...args) {
try {
console.log("[\u6dc4\u535a\u7ee7\u6559]", ...args);
} catch (_) {}
}
const USER_LOG_PATTERNS = [
/^\u5b66\u4e60\u300a.+\u300b\u7b2c\d+\u7ae0$/,
/^\u300a.+\u300b\u7b2c\d+\u7ae0\s*\u5b66\u4e60\u5b8c\u6210$/,
/^\u300a.+\u300b\u7b2c\d+\u7ae0\s*\u5f00\u59cb\u8003\u8bd5$/,
/^\u300a.+\u300b\u7b2c\d+\u7ae0\s*\u8003\u8bd5\u5b8c\u6210$/,
/^\u300a.+\u300b\u7b2c\d+\u7ae0\s*\u5b66\u4e60\u65f6\u957f\u4e0d\u8db3\uff0c\u8865\u5237\u89c6\u9891$/,
/^\u300a.+\u300b\u7b2c\d+\u7ae0\s*\u5b66\u65f6\u672a\u5230\uff0c\u5148\u8df3\u8fc7/,
/^\u300a.+\u300b\u7b2c\d+\u7ae0\s*\u5b66\u65f6\u672a\u5230\uff0c\u8df3\u8fc7\u8003\u8bd5/,
/^\u300a.+\u300b\u6709\u7ae0\u8282\u5b66\u65f6\u672a\u5230/,
/^\u5f00\u59cb\u8865\u5237\u5b66\u65f6\u672a\u5230\u7ae0\u8282/,
/^\u5b66\u65f6\u4ecd\u4e0d\u8db3[\uff1a:].+$/,
/^\u300a.+\u300b\u63d0\u4ea4\u6210\u7ee9/,
/^\u300a.+\u300b\u65e0\u9700\u63d0\u4ea4\u6210\u7ee9/,
/^\u300a.+\u300b\u5904\u7406\u5b8c\u6bd5$/,
/^\u300a.+\u300b\u5931\u8d25[\uff1a:].+$/,
/^\u5f00\u59cb\u5b66\u4e60\s*\d+\s*\u95e8\u8bfe\u7a0b$/,
/^\u5168\u90e8\u5b8c\u6210|^\u5df2\u505c\u6b62|^\u7ed3\u675f[\uff1a:].+$/,
];
function formatLogTime() {
try {
return new Date().toLocaleTimeString("zh-CN", { hour12: false });
} catch (_) {
const d = new Date();
const p = (n) => String(n).padStart(2, "0");
return `${p(d.getHours())}:${p(d.getMinutes())}:${p(d.getSeconds())}`;
}
}
function userLog(msg) {
const text = String(msg || "").trim();
if (!text || !USER_LOG_PATTERNS.some((re) => re.test(text))) return;
const line = `${formatLogTime()} ${text}`;
const box = document.getElementById("cme-run-log");
if (box) {
const div = document.createElement("div");
div.className = "cme-log-row";
div.textContent = line;
box.appendChild(div);
while (box.children.length > 200) box.removeChild(box.firstChild);
box.scrollTop = box.scrollHeight;
}
trace(text);
}
function chapLogTitle(courseTitle, chapNo) {
return `\u300a${courseTitle}\u300b\u7b2c${chapNo}\u7ae0`;
}
function setStatus(text) {
const el = document.getElementById("cme-auto-status");
if (el) el.textContent = text;
}
function updateCurrentMeta() {
const c = document.getElementById("cme-current-course");
const ch = document.getElementById("cme-current-chapter");
const t = document.getElementById("cme-current-task");
if (c) c.textContent = state.currentCourse || "\u65e0";
if (ch) ch.textContent = state.currentChapter || "\u65e0";
if (t) t.textContent = state.currentTask || "—";
}
function updateQueueSummary() {
const doneEl = document.getElementById("cme-queue-done");
const totalEl = document.getElementById("cme-queue-total");
const examEl = document.getElementById("cme-queue-exam");
const pctEl = document.getElementById("cme-queue-percent");
const barEl = document.getElementById("cme-queue-progress");
const footEl = document.getElementById("cme-queue-text");
const total = Math.max(0, Number(state.queueTotal) || 0);
const rawDone = Math.max(0, Number(state.queueDone) || 0);
const done = total > 0 ? Math.min(rawDone, total) : rawDone;
const pct = total > 0 ? Math.min(100, Math.round((done / total) * 100)) : 0;
if (doneEl) doneEl.textContent = String(done);
if (totalEl) totalEl.textContent = String(total);
if (examEl) examEl.textContent = state.queueExam || "—";
if (pctEl) pctEl.textContent = pct + "%";
if (barEl) barEl.style.width = pct + "%";
if (footEl) {
footEl.textContent = total
? state.running
? `\u961f\u5217 ${done}/${total} \u95e8` + (state.queueExam && state.queueExam !== "—" ? ` · \u7b2c${state.queueExam}\u7ae0` : "")
: `\u5df2\u9009 ${total} \u95e8 · \u5b8c\u6210 ${done}`
: "\u672a\u9009\u62e9";
}
}
function syncIdleChapterMetric() {
if (state.running) return;
const list = state.chapterPreview || [];
let n = 0;
let pending = false;
for (const c of list) {
if (c.loading) pending = true;
n += (c.chapters || []).length;
}
if (n > 0) state.queueExam = String(n);
else if (pending) state.queueExam = "…";
else state.queueExam = "—";
updateQueueSummary();
}
function httpRequest(opts) {
const method = (opts.method || "GET").toUpperCase();
const url = opts.url;
const headers = Object.assign({}, opts.headers || {});
const data = opts.data;
return new Promise((resolve, reject) => {
if (typeof GM_xmlhttpRequest !== "function") {
reject(new Error("\u9700\u8981 Tampermonkey \u7684 GM_xmlhttpRequest"));
return;
}
GM_xmlhttpRequest({
method,
url,
headers,
data,
anonymous: false,
withCredentials: true,
timeout: opts.timeout || 45000,
onload(res) {
resolve({
status: res.status,
text: res.responseText || "",
finalUrl: res.finalUrl || url,
headers: res.responseHeaders || "",
});
},
onerror() {
reject(new Error("\u7f51\u7edc\u9519\u8bef " + url));
},
ontimeout() {
reject(new Error("\u8d85\u65f6 " + url));
},
});
});
}
function httpGet(url, headers) {
return httpRequest({ method: "GET", url, headers });
}
function ensureOk(res, tip) {
if (res.status >= 200 && res.status < 400) return res;
throw new Error((tip || "\u8bf7\u6c42\u5931\u8d25") + " HTTP " + res.status);
}
function stripTags(html) {
return String(html || "")
.replace(/