✨ 正在等待或自动全量扫描中...
`;
updateCheckedCount();
return;
}
const terms = filterText.split(/[\s,,;;]+/).map(t => t.trim()).filter(t => t);
let displayNames = discState.targetNames;
if (terms.length > 0) {
displayNames = displayNames.filter(name => terms.some(term => name.toLowerCase().includes(term.toLowerCase())));
}
if (displayNames.length === 0) {
listDiv.innerHTML = ``;
displayNames.forEach((name) => {
const safeName = escapeHtml(name);
let displayNameHtml = safeName;
if (terms.length > 0) {
terms.forEach(term => {
const regex = new RegExp(`(${escapeRegex(term)})`, 'gi');
displayNameHtml = displayNameHtml.replace(regex, `$1`);
});
}
const isChecked = discState.selectedNames.has(name);
html += `
`;
});
html += `
`;
listDiv.innerHTML = html;
updateCheckedCount();
}
/**
* 全网任务聚合(雷达/调度/批量提交的共用数据源)。
*
* 流程:un_finish 接口取未完成任务骨架 → 学生课程列表补齐 courseMap
* (GM 持久化 xy_course_map 跨会话缓存)→ 逐课程并发 queryCourseResources
* 补全资源型任务(node_id 去重合并,computed_task_type 回填,group_name 兜底)。
* 单课失败 warn 不中断整体。
* [DEEP-DOC]
*/
async function fetchGlobalTasks() {
let allTasks = [];
try {
const token = await getAuthToken();
const res1 = await fetch(`https://${domain}/api/jx-stat/group/task/un_finish`, { method: "GET", headers: { "authorization": `Bearer ${token}`, "Content-Type": "application/json; charset=utf-8" } });
const data1 = await res1.json();
let unfinishedTasks = [];
if (data1.success && data1.data) {
unfinishedTasks = data1.data;
allTasks = JSON.parse(JSON.stringify(unfinishedTasks));
}
let courseMap = {};
try { courseMap = JSON.parse(GM_getValue('xy_course_map', '{}')); } catch(e) { console.warn('[小雅] 课程名映射缓存解析失败:', e); }
unfinishedTasks.forEach(t => {
if (t.group_id && t.group_name) courseMap[t.group_id] = t.group_name;
});
const currentGroupId = getCourseGroupId();
if (currentGroupId && !courseMap[currentGroupId]) {
const apiName = await getCourseNameFromAPI(currentGroupId);
if (apiName) courseMap[currentGroupId] = apiName;
}
const unfinishedGroupIds = new Set(unfinishedTasks.map(t => t.group_id).filter(Boolean));
const staleIds = Object.keys(courseMap).filter(gId => !unfinishedGroupIds.has(gId));
if (staleIds.length > 0) {
await Promise.all(staleIds.map(async (gId) => {
const apiName = await getCourseNameFromAPI(gId);
if (apiName) courseMap[gId] = apiName;
}));
}
// 拉取全部学生课程,补齐 courseMap(调度/雷达显示所有课程)
try {
const gr = await fetch(`https://${domain}/api/jx-iresource/group/student/groups?time_flag=1`, { headers: { "authorization": `Bearer ${token}` } });
const gj = await gr.json();
const gdata = gj && gj.data;
const garr = Array.isArray(gdata) ? gdata : (gdata && (Array.isArray(gdata.groups) ? gdata.groups : (Array.isArray(gdata.list) ? gdata.list : [])));
(garr || []).forEach(g => {
const gid = g.id || g.group_id;
const gname = g.name || g.group_name || g.title;
if (gid && gname) courseMap[String(gid)] = gname;
});
} catch(e) {}
GM_setValue('xy_course_map', JSON.stringify(courseMap));
const groupIds = Object.keys(courseMap);
if (groupIds.length > 0) {
const fetchPromises = groupIds.map(async (gId) => {
try {
const r = await fetch(`https://${domain}/api/jx-iresource/resource/queryCourseResources?group_id=${gId}`, { headers: { "authorization": `Bearer ${token}` } });
const d = await r.json();
return { gId, gName: courseMap[gId], data: d };
} catch (e) { console.warn('[小雅] 课程资源批量探测失败:', gId, e); return null; }
});
const results = await Promise.all(fetchPromises);
results.forEach(res => {
if (res && res.data && res.data.success && res.data.data) {
const flatRes = extractFilesFromResources(res.data.data);
flatRes.forEach(r => {
const existItem = allTasks.find(t => t.node_id == (r.node_id || r.id) && t.group_id == res.gId);
if (!existItem) {
allTasks.push({
task_id: r.task_id || r.id,
id: r.task_id || r.id,
node_id: r.node_id || r.id,
group_id: res.gId,
resource_id: r.resource_id || r.id,
name: r.name || r.title || '未知任务',
task_type: r.computed_task_type || 1,
finish: 2,
start_time: r.start_time || new Date().toISOString(),
end_time: r.end_time || "2099-12-31T00:00:00.000Z",
group_name: res.gName || "未知课程"
});
} else {
existItem.group_name = res.gName;
existItem.task_type = r.computed_task_type || existItem.task_type;
}
});
}
});
}
} catch (error) { console.warn('[小雅] fetchGlobalTasks 失败', error); }
return allTasks;
}
/**
* 批量任务提交:逐任务 autoSubmitCurrentTask 式验证上报,间隔随机化拟人;
* 实时更新按钮进度文案「i/n」;汇总成功/失败计数 toast 收尾。AbortController
* 未接入——批量一旦启动只能等自然结束或关页。
* [DEEP-DOC]
*/
async function batchSubmitGlobalTasks(taskObjs) {
try {
const token = await getAuthToken(); let successCount = 0;
let submitBtn = document.getElementById('xy-batch-submit-btn');
const total = taskObjs.length;
for (let i = 0; i < taskObjs.length; i++) {
submitBtn = document.getElementById('xy-batch-submit-btn');
const task = taskObjs[i];
if (submitBtn) {
submitBtn.innerText = `⏳ 正在提交任务... (${i+1}/${total})`;
submitBtn.disabled = true;
}
const taskCard = document.getElementById(`xy-global-task-card-${task.task_id || task.id}`);
let statusIndicator = null;
if (taskCard) {
taskCard.style.opacity = '0.8';
taskCard.style.transform = 'scale(0.98)';
statusIndicator = taskCard.querySelector('.xy-task-status-indicator');
if (statusIndicator) {
statusIndicator.innerHTML = '🔄 提交请求中...';
statusIndicator.style.background = T('rgba(251,191,36,0.1)','#fffbeb');
statusIndicator.style.color = T('#fcd34d','#92400e');
}
}
try {
const response = await fetch(`https://${domain}/api/jx-iresource/resource/finishActivity`, { method: "POST", headers: { "authorization": `Bearer ${token}`, "Content-Type": "application/json; charset=UTF-8" }, body: JSON.stringify({ "group_id": task.group_id, "node_id": task.node_id, "task_id": task.task_id || task.id }) });
const data = await response.json();
if (data.success) {
logMsg(`✅ 任务提交成功:${task.name}`, 'success', true);
successCount++;
if (statusIndicator) {
statusIndicator.innerHTML = '✓ 验证通过';
statusIndicator.style.background = T('rgba(52,211,153,0.12)','#ecfdf5');
statusIndicator.style.color = T('#34d399','#065f46');
}
const checkbox = taskCard ? taskCard.querySelector('.xy-task-check') : null;
if (checkbox) { checkbox.disabled = true; checkbox.checked = false; }
if (taskCard) taskCard.style.borderColor = T('rgba(52,211,153,0.25)','#a7f3d0');
} else {
if (statusIndicator) {
statusIndicator.innerHTML = '❌ 验证失败';
statusIndicator.style.background = T('rgba(248,113,113,0.12)','#fee2e2');
statusIndicator.style.color = '#f87171';
}
if (taskCard) taskCard.style.borderColor = T('rgba(248,113,113,0.25)','#fecaca');
}
} catch (err) {
if (statusIndicator) {
statusIndicator.innerHTML = '⚠️ 网络异常';
statusIndicator.style.background = T('rgba(248,113,113,0.1)','#fee2e2');
statusIndicator.style.color = '#f87171';
}
}
if (taskCard) {
taskCard.style.opacity = '1';
taskCard.style.transform = 'scale(1)';
}
await sleep(400);
submitBtn = document.getElementById('xy-batch-submit-btn');
if (submitBtn) submitBtn.innerText = `🔄 正在同步雷达数据... (${i+1}/${total})`;
const latestTasks = await fetchGlobalTasks();
renderGlobalDashboardContent(latestTasks);
await sleep(200);
}
const finalSubmitBtn = document.getElementById('xy-batch-submit-btn');
if (finalSubmitBtn) {
finalSubmitBtn.innerText = '🚀 一键提交勾选任务';
finalSubmitBtn.disabled = false;
}
if (successCount > 0) {
showToast(`🎉 成功完成 ${successCount} 个学习任务!`, 'success');
}
} catch(e) { console.warn('[小雅] 全局任务执行失败', e); }
}
/** 打开全局任务雷达面板:置 overlay 可见 → fetchGlobalTasks 拉数据 → renderGlobalDashboardContent 全量渲染。关闭按钮在面板内绑定。
* [DEEP-DOC]
*/
async function openGlobalTaskDashboard() {
let overlay = document.getElementById('xy-dashboard-overlay');
if (!overlay) { overlay = document.createElement('div'); overlay.id = 'xy-dashboard-overlay'; overlay.style.cssText = `position:fixed; top:0; left:0; width:100vw; height:100vh; background:${T('rgba(15,23,42,0.7)','rgba(0,0,0,0.3)')}; z-index:2147483645; display:flex; justify-content:center; align-items:center; backdrop-filter:${T('blur(12px)','blur(4px)')}; opacity:0; transition:opacity 0.3s;`; document.body.appendChild(overlay); }
overlay.innerHTML = `
`;
const groupedTasks = tasks.reduce((acc, t) => { if(!acc[t.group_name]) acc[t.group_name] = []; acc[t.group_name].push(t); return acc; }, {});
window.xyGlobalTaskMap = new Map();
const courseUnits = {};
const courseUnitOrder = {};
for (const [courseName, courseTasks] of Object.entries(groupedTasks)) {
const gid = courseTasks[0] && courseTasks[0].group_id;
let unitMap = null;
const orderedOut = [];
if (gid) {
try {
const res = await fetchCourseResourcesForRadar(gid);
if (res) {
unitMap = buildUnitNameMap(buildDirTree(res), '', null, orderedOut);
}
} catch(e) {}
}
courseUnits[courseName] = groupTasksByUnit(courseTasks, unitMap);
const orderIdx = new Map(orderedOut.map((n, i) => [n, i]));
orderIdx.set('未分组', Number.MAX_SAFE_INTEGER);
courseUnitOrder[courseName] = orderIdx;
}
Object.entries(groupedTasks).forEach(([courseName, courseTasks], groupIdx) => {
courseTasks.sort((a,b) => {
if (a.finish !== b.finish) return a.finish - b.finish;
return new Date(a.end_time) - new Date(b.end_time);
});
const safeId = 'xy-global-group-' + groupIdx;
html += `
`;
const unitGroups = courseUnits[courseName];
const flatFallback = unitGroups.size === 1 && unitGroups.has('未分组');
const unitOrder = courseUnitOrder[courseName] || new Map();
const unitEntries = Array.from(unitGroups.entries()).sort((a, b) => (unitOrder.get(a[0]) || 0) - (unitOrder.get(b[0]) || 0));
let ui = 0;
if (flatFallback) {
courseTasks.forEach(task => { html += buildRadarTaskCard(task); });
} else {
unitEntries.forEach(([unitName, unitTasks]) => {
const unitId = safeId + '-u' + ui++;
html += `
`;
unitTasks.forEach(task => { html += buildRadarTaskCard(task); });
html += `
`;
});
}
html += `
`;
});
html += `
`; contentBox.innerHTML = html;
document.querySelectorAll('.xy-global-group-header').forEach(header => {
header.onclick = () => {
const targetId = header.getAttribute('data-target');
const content = document.getElementById(targetId);
const arrow = header.querySelector('.xy-global-group-arrow');
if (content.style.display === 'none') {
content.style.display = 'flex';
arrow.style.transform = 'rotate(0deg)';
header.style.background = T('rgba(30,41,59,0.7)','#f8fafc');
} else {
content.style.display = 'none';
arrow.style.transform = 'rotate(-90deg)';
header.style.background = T('rgba(30,41,59,0.35)','#f1f5f9');
}
};
});
document.querySelectorAll('.xy-global-unit-header').forEach(header => {
header.onclick = () => {
const targetId = header.getAttribute('data-target');
const content = document.getElementById(targetId);
const arrow = header.querySelector('.xy-global-unit-arrow');
if (content.style.display === 'none') {
content.style.display = 'flex';
if (arrow) arrow.style.transform = 'rotate(0deg)';
} else {
content.style.display = 'none';
if (arrow) arrow.style.transform = 'rotate(-90deg)';
}
};
});
const selectAllCb = document.getElementById('xy-select-all'), taskCheckboxes = document.querySelectorAll('.xy-task-check:not([disabled])');
if (selectAllCb) selectAllCb.onchange = (e) => { taskCheckboxes.forEach(cb => { cb.checked = e.target.checked; }); };
taskCheckboxes.forEach(cb => { cb.onchange = () => { if (!cb.checked && selectAllCb) selectAllCb.checked = false; else if (selectAllCb) selectAllCb.checked = Array.from(taskCheckboxes).every(c => c.checked); }; });
const fSwitch = document.getElementById('xy-freedom-switch');
if (fSwitch) fSwitch.onchange = (e) => {
if (e.target.checked) { xyShowModal("⚠️ 越级警告", "强行解除非视频节点的锁极易导致数据异常,请确保你清楚后果!", () => { playState.isFreedomMode = true; renderGlobalDashboardContent(tasks); }); e.target.checked = false; }
else { playState.isFreedomMode = false; renderGlobalDashboardContent(tasks); }
};
const submitBtn = document.getElementById('xy-batch-submit-btn');
if (submitBtn) submitBtn.onclick = () => {
const checkedNodes = Array.from(document.querySelectorAll('.xy-task-check:checked')).map(cb => cb.value);
if (checkedNodes.length === 0) { showToast('未勾选任何提交目标', 'warning'); return; }
submitBtn.innerText = '⏳ 正在批量提交任务...'; submitBtn.disabled = true;
batchSubmitGlobalTasks(checkedNodes.map(id => window.xyGlobalTaskMap.get(id)).filter(Boolean));
};
}
/**
* 智能排课排序 —— 启发式贪心调度算法(权重见 SCHEDULE_WEIGHTS 常量)。
*
* 打分模型:ddlScore 按 DDL 分档(<1天:100 / <3天:80 / <7天:60 / <14天:40 /
* <30天:20 : 其余5)× completionPenalty(已完成0.3 抑制重复刷);
* 迭代选取:每轮对 remaining 全量重算 score = ddlScore + typeBonus +
* courseSwitchBonus——typeBonus 奖励与上一选中任务类型交错(视频文档交替
* +25,首任务+10);courseSwitchBonus 奖励跨课程切换(+15)。每轮取最高分
* 移入 sorted 并更新 lastWasVideo。
*
* 学术注记:权值为经验启发值而非建模最优解,属贪心近似;实测排序质量稳定,
* 但理论上不保证全局最优(NP-hard 排序问题的可接受工程折衷)。
* [DEEP-DOC]
*/
function optimizeScheduleOrder(tasks) {
if (!tasks || tasks.length === 0) return [];
const now = Date.now();
const scored = tasks.map(task => {
const endTime = new Date(task.end_time).getTime();
const daysLeft = Math.max(0, (endTime - now) / (1000 * 60 * 60 * 24));
const name = (task.name || '').toLowerCase();
const isVideo = SHARED_PATTERNS.MEDIA.test(name);
const isDoc = SHARED_PATTERNS.DOC.test(name);
const ddlScore = daysLeft < SCHEDULE_WEIGHTS.DDL_DAY_1 ? SCHEDULE_WEIGHTS.DDL_SCORE[0]
: daysLeft < SCHEDULE_WEIGHTS.DDL_DAY_3 ? SCHEDULE_WEIGHTS.DDL_SCORE[1]
: daysLeft < SCHEDULE_WEIGHTS.DDL_DAY_7 ? SCHEDULE_WEIGHTS.DDL_SCORE[2]
: daysLeft < SCHEDULE_WEIGHTS.DDL_DAY_14 ? SCHEDULE_WEIGHTS.DDL_SCORE[3]
: daysLeft < SCHEDULE_WEIGHTS.DDL_DAY_30 ? SCHEDULE_WEIGHTS.DDL_SCORE[4]
: SCHEDULE_WEIGHTS.DDL_SCORE[5];
const completionPenalty = task.finish === 2 ? SCHEDULE_WEIGHTS.COMPLETION_PENALTY : 1.0;
const typeWeight = isVideo ? SCHEDULE_WEIGHTS.TYPE_MEDIA : isDoc ? SCHEDULE_WEIGHTS.TYPE_MEDIA : SCHEDULE_WEIGHTS.TYPE_OTHER;
return {
task,
ddlScore: ddlScore * completionPenalty,
typeWeight,
isVideo,
isDoc,
groupId: task.group_id,
score: 0
};
});
const sorted = [];
const remaining = [...scored];
let lastWasVideo = null;
while (remaining.length > 0) {
remaining.forEach(item => {
let typeBonus = 0;
if (lastWasVideo === true && item.isDoc) typeBonus = SCHEDULE_WEIGHTS.ALTERNATE_BONUS;
if (lastWasVideo === false && item.isVideo) typeBonus = SCHEDULE_WEIGHTS.ALTERNATE_BONUS;
if (lastWasVideo === null) typeBonus = SCHEDULE_WEIGHTS.FIRST_PICK_BONUS;
const hasOtherCourse = remaining.some(r => r.groupId !== item.groupId);
const courseSwitchBonus = (hasOtherCourse && sorted.length > 0 && item.groupId !== sorted[sorted.length-1].groupId) ? SCHEDULE_WEIGHTS.COURSE_SWITCH_BONUS : 0;
item.score = item.ddlScore + typeBonus + courseSwitchBonus;
});
remaining.sort((a, b) => b.score - a.score);
const best = remaining.shift();
sorted.push(best);
lastWasVideo = best.isVideo;
}
return sorted.map(s => s.task);
}
/**
* 智能排课入口编排:fetchGlobalTasks 全网任务 → 过滤出视频/文档且 task_type=1
* 的可刷集合 → optimizeScheduleOrder 排序 → 逐个 getTaskResourceId 补全三元组
* 后入队(已完成的用 duration 策略重刷,未完成 until_done)→ saveScheduleState
* + 渲染队列 + 成功日志/toast。零任务时警告返回。
* [DEEP-DOC]
*/
async function smartOptimizeAndImport() {
const tasks = await fetchGlobalTasks();
const watchTasks = tasks.filter(t => {
const name = (t.name || '').toLowerCase();
const isVideo = SHARED_PATTERNS.MEDIA.test(name);
const isDoc = SHARED_PATTERNS.DOC.test(name);
return (isVideo || isDoc) && t.task_type === 1;
});
if (watchTasks.length === 0) {
showToast('未发现可优化的视频/文档任务', 'warning');
return;
}
const optimized = optimizeScheduleOrder(watchTasks);
xyScheduleState.queue = [];
for (const task of optimized) {
const resId = await getTaskResourceId(task);
xyScheduleState.queue.push({
uuid: generateUUID(),
taskId: task.task_id || task.id,
nodeId: task.node_id,
groupId: task.group_id,
resourceId: resId,
name: task.name,
type: 1,
strategy: task.finish === 2 ? STRATEGY.FIXED_DURATION : STRATEGY.UNTIL_DONE,
duration: 30,
elapsedSec: 0,
actionDone: false,
status: 'pending'
});
}
saveScheduleState();
if (window.xyRenderScheduleQueue) window.xyRenderScheduleQueue();
logMsg(`🧠 智能排课完成:${xyScheduleState.queue.length} 个任务已按 DDL紧迫度×类型交错 优化排序`, 'success', false);
showToast(`已优化导入 ${xyScheduleState.queue.length} 个任务`, 'success');
}
/**
* 一键雷达连播:全网扫描 → 过滤未完成且已开始的视频/文档 → DDL 升序排序
* (同 DDL 先看完成态再比课程号节点号)→ 整体入队 until_done 策略 →
* currentIdx 归零立即开跑。运行中禁止重复触发(先停调度)。
* [DEEP-DOC]
*/
async function oneClickRadarPlay() {
if (xyScheduleState.isRunning) {
showToast('计划调度正在运行中,请先停止后再一键连播', 'warning');
return;
}
logMsg('🔊 一键雷达连播:正在扫描全网未完成任务...', 'info', false);
showToast('正在扫描全网任务...', 'info');
const allTasks = await fetchGlobalTasks();
const now = new Date();
const pendingTasks = allTasks.filter(t => {
const name = (t.name || '').toLowerCase();
const isVideo = SHARED_PATTERNS.MEDIA.test(name);
const isDoc = SHARED_PATTERNS.DOC.test(name);
if (!(isVideo || isDoc)) return false;
if (t.finish === 2) return false;
if (t.start_time && new Date(t.start_time) > now) return false;
return true;
});
if (pendingTasks.length === 0) {
logMsg('🔊 一键连播:全网未发现可挂机的待完成任务', 'warning', false);
showToast('未发现待完成的视频/文档任务', 'warning');
return;
}
pendingTasks.sort((a, b) => {
const aEnd = new Date(a.end_time || '2099-12-31').getTime();
const bEnd = new Date(b.end_time || '2099-12-31').getTime();
if (aEnd !== bEnd) return aEnd - bEnd;
if (a.finish !== b.finish) return (a.finish || 0) - (b.finish || 0);
if (a.group_id !== b.group_id) return (parseInt(a.group_id) || 0) - (parseInt(b.group_id) || 0);
return (parseInt(a.node_id) || 0) - (parseInt(b.node_id) || 0);
});
xyScheduleState.queue = [];
xyScheduleState.currentIdx = 0;
for (const task of pendingTasks) {
const resId = await getTaskResourceId(task);
xyScheduleState.queue.push({
uuid: generateUUID(),
taskId: task.task_id || task.id,
nodeId: task.node_id,
groupId: task.group_id,
resourceId: resId,
name: task.name,
type: 1,
strategy: STRATEGY.UNTIL_DONE,
duration: 30,
elapsedSec: 0,
actionDone: false,
status: 'pending'
});
}
saveScheduleState();
if (window.xyRenderScheduleQueue) window.xyRenderScheduleQueue();
logMsg(`🔊 一键连播:已导入 ${xyScheduleState.queue.length} 个待完成任务,按DDL紧迫度排序`, 'success', false);
showToast(`已导入 ${xyScheduleState.queue.length} 个任务,启动连播`, 'success');
xyScheduleState.lastMode = playState.mode;
playState.mode = PLAY_MODE.MANUAL;
GM_setValue('xy_play_mode', PLAY_MODE.MANUAL);
xyScheduleState.isRunning = true;
xyScheduleState.isPaused = false;
saveScheduleState();
updateCourseUI();
updateSchCard();
try { unsafeWindow._xyAntiThrottleStart?.(); } catch(e) {}
const firstTask = xyScheduleState.queue[0];
if (firstTask) {
const pathPrefix = window.location.href.includes('/course/') ? 'course' : 'mycourse';
logMsg(`🔊 一键连播:正在跳转至首个任务「${(firstTask.name||'未知').substring(0,12)}」...`, 'success', false);
setTimeout(() => {
window.location.href = `/app/jx-web/${pathPrefix}/${firstTask.groupId}/${firstTask.resourceId}/${firstTask.nodeId}`;
}, 800);
}
}
let quickKillRunning = false;
/** 快速击破当前任务:跳过自然挂机等待,直接对当前 node 发起一次完成验证并
* 根据结果反馈日志。适用于「就差这一个」的场景。
* [DEEP-DOC]
*/
async function quickKillCurrentTask() {
if (quickKillRunning) { showToast('极速秒交正在执行中', 'warning'); return; }
const groupId = getCourseGroupId();
const nodeId = getNodeId();
if (!groupId || !nodeId) { showToast('当前页面无任务可提交', 'warning'); return; }
quickKillRunning = true;
const btn = document.getElementById('xy-btn-quick-kill');
const origText = btn ? btn.innerText : '';
try {
if (btn) { btn.innerText = '⚡ 秒交中...'; btn.disabled = true; }
logMsg('⚡ 极速秒交:正在提交当前页面任务...', 'info', false);
const success = await autoSubmitCurrentTask();
if (success) {
logMsg('✅ 极速秒交成功!当前任务已提交', 'success', false);
showToast('秒交成功!', 'success');
playState.isTaskCompleted = true;
updateCourseUI();
} else {
logMsg('❌ 秒交失败:可能需要先挂机积累时长', 'warning', false);
showToast('秒交失败,请先挂机积累时长', 'warning');
}
} catch(e) {
logMsg('❌ 极速秒交异常', 'error', false);
} finally {
quickKillRunning = false;
if (btn) { btn.innerText = origText; btn.disabled = false; }
}
}
/**
* 计划调度面板打开编排:overlay 显示 → 任务库渲染(fetchGlobalTasks 全集按
* 课程折叠分组,卡片带添加按钮写 xyGlobalTaskMap)→ 当前队列渲染(策略下拉/
* 时长输入/删除/上下移控件绑定)→ 自动启停时间输入框回填。
* [DEEP-DOC]
*/
async function openScheduleDashboard() {
let overlay = document.getElementById('xy-schedule-overlay');
if (!overlay) {
overlay = document.createElement('div');
overlay.id = 'xy-schedule-overlay';
overlay.style.cssText = `position:fixed; top:0; left:0; width:100vw; height:100vh; background:${T('rgba(15,23,42,0.8)','rgba(0,0,0,0.4)')}; z-index:2147483648; display:flex; justify-content:center; align-items:center; backdrop-filter:${T('blur(15px)','blur(4px)')}; opacity:0; transition:all 0.3s;`;
document.body.appendChild(overlay);
}
const renderQueueList = () => {
const container = document.getElementById('xy-sch-queue-list');
if (!container) return;
if (xyScheduleState.queue.length === 0) {
container.innerHTML = `
`;
const unitGroups = courseUnits[courseName];
const flatFallback = unitGroups.size === 1 && unitGroups.has('未分组');
const unitOrder = courseUnitOrder[courseName] || new Map();
const unitEntries = Array.from(unitGroups.entries()).sort((a, b) => (unitOrder.get(a[0]) || 0) - (unitOrder.get(b[0]) || 0));
let ui = 0;
if (flatFallback) {
validTasks.forEach(task => { html += buildSchLibCard(task); });
} else {
unitEntries.forEach(([unitName, unitTasks]) => {
const validUnitTasks = unitTasks.filter(isValidSchTask);
if (validUnitTasks.length === 0) return;
const unitId = 'xy-sch-group-' + groupIdx + '-u' + (ui++);
html += `
`;
validUnitTasks.forEach(task => { html += buildSchLibCard(task); });
html += `
`;
});
}
html += `
`;
});
if (!hasAnyValidTask) {
html = `