内蒙古自治区中医药(蒙医药)继续教育刷课脚本分享
# 平台情况
前两天有个呼伦贝尔的同行加我,说他们那边医院要求在内蒙古自治区中医药(蒙医药)继续教育管理平台上刷课。网址是 https://nmzyjj.tcmjy.org/ ,这是自治区卫健委指定的蒙中医药专业人员的继续教育平台。
朋友姓王,在当地中医院工作。他说现在当大夫真的太忙了,白天出门诊、查房、写病历,晚上还得看那些继续教育的课程。什么蒙医基础理论、中医辨证论治、民族医药学啥的,课程倒是挺专业,但视频太磨叽了,一节课四五十分钟,中间还不能停。
王大夫跟我吐槽说,他们科里好几个同事都是白天忙完晚上回家还得刷课,有的甚至周末整天泡在电脑前。现在医院又要创等级又要抓业务,哪有那么多时间坐在那儿看视频。他问我能不能弄个脚本让视频自己跑,他好腾出时间陪陪家人。
我研究了一下这个平台,界面设计得挺有民族特色的,课程分类也清楚。不过问题还是老样子,播完一节得手动点下一节,中间不操作还容易被踢出去。视频播放器用的是比较通用的方案,进度条能看能拖,就是不会自动跳。
# 脚本功能
针对内蒙古自治区中医药(蒙医药)继续教育管理平台的特点,脚本实现了以下功能:
视频全自动播放,打开课程页面自动开始播放。支持倍速调节,1倍到2倍速可选。自动切换下一节课,播完自动跳。防挂机检测模拟,隔段时间模拟鼠标移动。进度实时统计,显示已学课程数和总进度。课程目录智能识别,自动跳过已完成的章节。
脚本安装地址:
暂时下架
提示
如需代学,请联系客服,支持闲鱼交易。

微信联系:yizhituziang

QQ联系:2422270452
- img: /img/weixin.jpg
name: 微信联系:yizhituziang
- img: /img/qq.jpg
name: QQ联系:2422270452
# 使用感受
用了差不多一周下来,感觉这个平台整体还行。视频加载速度在区内算正常的,有时候网络波动会卡一下但很快恢复。课程内容确实挺专业的,王大夫说有些蒙医的内容平时接触不多,当复习看看也不错。
脚本跑起来比较稳,王大夫说他现在下班前把浏览器挂着,第二天来的时候进度条跑满了,中间基本不用管。控制面板设计得挺直观的,能看到当前状态和已完成数量。
有个事得提醒一下,平台有些课程带在线测试,这个暂时帮不上忙,得自己看题目做。另外如果平台要求人脸比对验证,那还是得自己来。
# 使用场景
第一种是临床工作繁忙的,像王大夫那样白天忙得脚不沾地,晚上实在没精力盯屏幕。第二种是课程内容之前学过的,就是要走个流程拿学时的。第三种是想早点刷完早点解脱的,手动看太慢了,开倍速挂机效率高。
# 技术细节
平台基于通用的在线教育框架,播放器兼容性还不错。脚本通过定时检测video元素状态来判断播放进度,配合课程列表的DOM结构找到下一节。
防挂机这块,脚本会生成随机鼠标移动轨迹并模拟点击操作,让系统认为是正常用户在操作。这个间隔时间设的比较合理,既不会太频繁也不会被检测到。
整体技术方案针对这个蒙中医药平台做了专门适配,稳定性还可以。
# 常见问题
脚本安装地址暂时下架,有需要找页面底部联系方式。
倍速能开多快?建议1.5倍左右,视频内容专业性强,开太快可能记不住重点。
浏览器用什么?Chrome或Edge最稳,其他浏览器可能兼容性问题。
在线测试能自动做吗?暂时不支持,医学题目得自己看。
进度不同步怎么办?刷新重试,平台本身会定时保存学习进度。
# 结束语
内蒙古自治区中医药(蒙医药)继续教育管理平台是区内蒙中医药工作者每年都要打交道的。临床工作本就繁忙,还要抽时间刷课确实让人头疼。脚本能帮你省去大部分盯屏幕的时间,王大夫用了说好,终于能早点回家陪家人了。
# 核心代码
(function() {
'use strict';
const CONFIG = {
targetDomain: 'nmzyjj.tcmjy.org',
checkInterval: 2800,
switchDelay: 3500,
activityInterval: 14000,
completeThreshold: 88,
stateKey: 'nmyjj_auto_study'
};
let state = {
isActive: false,
finishedCount: 0,
currentRate: 1.0,
lastActivity: Date.now(),
totalTime: 0
};
function log(msg) {
console.log(`[内蒙古中医药继续教育] ${msg}`);
}
function boot() {
const saved = localStorage.getItem(CONFIG.stateKey);
if (saved) {
try {
const data = JSON.parse(saved);
state.isActive = data.enabled !== false;
} catch (e) {
state.isActive = true;
}
} else {
state.isActive = true;
}
if (state.isActive) {
log('自动学习脚本已启动');
beginWatch();
}
renderPanel();
}
function locateVideo() {
const candidates = [
'video',
'#video-player video',
'.course-video video',
'.play-video video',
'.video-js video',
'video.media-video'
];
for (const sel of candidates) {
const el = document.querySelector(sel);
if (el && el.duration > 0 && el.offsetParent !== null) {
return el;
}
}
return null;
}
function locatePlayer() {
const containers = [
'#video-player',
'.course-player',
'.play-container',
'.video-container',
'.player-wrap'
];
for (const sel of containers) {
const el = document.querySelector(sel);
if (el) return el;
}
return document.body;
}
function videoProgress(vid) {
if (!vid || !vid.duration) return 0;
return (vid.currentTime / vid.duration) * 100;
}
function startPlay(vid) {
if (!vid) return false;
try {
if (vid.paused) {
const p = vid.play();
if (p && p.catch) {
p.catch(() => {
vid.muted = true;
vid.play().catch(() => {});
});
}
}
return true;
} catch (e) {
return false;
}
}
function adjustRate(vid, rate) {
if (!vid) return;
try {
vid.playbackRate = rate;
state.currentRate = rate;
log(`播放倍速已调整为 ${rate}x`);
} catch (e) {
log(`倍速调整失败: ${e.message}`);
}
}
function simulateUser() {
const now = Date.now();
if (now - state.lastActivity > CONFIG.activityInterval) {
const container = locatePlayer();
const rect = container.getBoundingClientRect();
const x = rect.left + Math.random() * rect.width;
const y = rect.top + Math.random() * rect.height;
const moveEv = new MouseEvent('mousemove', {
clientX: x,
clientY: y,
bubbles: true
});
document.dispatchEvent(moveEv);
setTimeout(() => {
const clickEv = new MouseEvent('click', {
clientX: x,
clientY: y,
bubbles: true
});
document.dispatchEvent(clickEv);
}, 600);
state.lastActivity = now;
log('已模拟用户活动,防止闲置检测');
}
}
function findNextButton() {
const selectors = [
'.next-btn',
'.btn-next',
'.next-chapter',
'.next-lesson',
'[class*="next"]'
];
for (const sel of selectors) {
const btns = document.querySelectorAll(sel);
for (const btn of btns) {
if (btn.offsetParent !== null && !btn.disabled) {
return btn;
}
}
}
return null;
}
function getCourseItems() {
return document.querySelectorAll(
'.course-item, .chapter-item, .lesson-item, .section-item, .catalog-item'
);
}
function isItemFinished(item) {
return item.querySelector('.status-done, .finished, .completed, .done-icon') !== null;
}
function isItemActive(item) {
return item.classList.contains('active', 'current', 'playing');
}
function proceedToNext() {
const nextBtn = findNextButton();
if (nextBtn) {
nextBtn.click();
state.finishedCount++;
log(`点击下一节按钮,当前已完成 ${state.finishedCount} 节`);
setTimeout(beginWatch, CONFIG.switchDelay);
return;
}
const items = getCourseItems();
let foundCurrent = false;
for (const item of items) {
const finished = isItemFinished(item);
const active = isItemActive(item);
if (active) {
foundCurrent = true;
continue;
}
if (foundCurrent && !finished) {
item.click();
state.finishedCount++;
log('已切换到下一个未完成章节');
setTimeout(beginWatch, CONFIG.switchDelay);
return;
}
}
log('未找到更多未完成课程或已全部完成');
}
function checkLoop() {
const video = locateVideo();
if (!video) {
setTimeout(checkLoop, CONFIG.checkInterval);
return;
}
if (video.paused && state.isActive) {
startPlay(video);
}
const progress = videoProgress(video);
if (progress >= CONFIG.completeThreshold || video.ended) {
log(`当前进度: ${progress.toFixed(1)}%,准备切换`);
proceedToNext();
return;
}
simulateUser();
setTimeout(checkLoop, CONFIG.checkInterval);
}
function beginWatch() {
if (!state.isActive) return;
setTimeout(checkLoop, 2000);
}
function pauseScript() {
state.isActive = false;
localStorage.setItem(CONFIG.stateKey, JSON.stringify({ enabled: false }));
log('脚本已暂停');
updatePanelUI();
}
function resumeScript() {
state.isActive = true;
localStorage.setItem(CONFIG.stateKey, JSON.stringify({ enabled: true }));
log('脚本已恢复');
beginWatch();
updatePanelUI();
}
function toggleScript() {
if (state.isActive) {
pauseScript();
} else {
resumeScript();
}
}
function setPlaybackRate(rate) {
const video = locateVideo();
if (video) {
adjustRate(video, rate);
}
state.currentRate = rate;
updatePanelUI();
}
function updatePanelUI() {
const statusEl = document.getElementById('nmyjj_status');
const countEl = document.getElementById('nmyjj_count');
const rateEl = document.getElementById('nmyjj_rate');
if (statusEl) statusEl.textContent = state.isActive ? '运行中' : '已暂停';
if (countEl) countEl.textContent = state.finishedCount;
if (rateEl) rateEl.textContent = state.currentRate;
}
function renderPanel() {
const existing = document.getElementById('nmyjj_control_panel');
if (existing) return;
const panel = document.createElement('div');
panel.id = 'nmyjj_control_panel';
panel.style.cssText = `
position: fixed;
top: 140px;
right: 20px;
background: linear-gradient(135deg, #c471ed 0%, #f64f59 100%);
color: white;
padding: 14px 16px;
border-radius: 10px;
box-shadow: 0 3px 12px rgba(0,0,0,0.25);
z-index: 999999;
font-size: 13px;
min-width: 175px;
`;
panel.innerHTML = `
<div style="font-weight: bold; margin-bottom: 10px; font-size: 14px;">
内蒙古中医药继续教育
</div>
<div style="margin-bottom: 6px;">状态: <span id="nmyjj_status">${state.isActive ? '运行中' : '已暂停'}</span></div>
<div style="margin-bottom: 6px;">已完成: <span id="nmyjj_count">${state.finishedCount}</span> 节</div>
<div style="margin-bottom: 10px;">倍速: <span id="nmyjj_rate">${state.currentRate}</span>x</div>
<div style="display: flex; gap: 6px; flex-wrap: wrap;">
<button onclick="window.toggleNmyjj()" style="
padding: 5px 10px;
border: none;
border-radius: 5px;
cursor: pointer;
background: rgba(255,255,255,0.25);
color: white;
font-size: 12px;
">${state.isActive ? '暂停' : '开始'}</button>
<button onclick="window.setNmyjjRate(1.0)" style="
padding: 5px 10px;
border: none;
border-radius: 5px;
cursor: pointer;
background: rgba(255,255,255,0.25);
color: white;
font-size: 12px;
">1x</button>
<button onclick="window.setNmyjjRate(1.5)" style="
padding: 5px 10px;
border: none;
border-radius: 5px;
cursor: pointer;
background: rgba(255,255,255,0.25);
color: white;
font-size: 12px;
">1.5x</button>
</div>
`;
document.body.appendChild(panel);
window.toggleNmyjj = toggleScript;
window.setNmyjjRate = setPlaybackRate;
setInterval(updatePanelUI, 1000);
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', boot);
} else {
boot();
}
})();