// ==UserScript==
// @name 学习通作业/考试/待办列表(优化版)
// @namespace https://github.com/Cooanyh
// @version 1.5.0
// @author 甜檸Cirtron (lcandy2); Modified by Coren
// @description 【优化版】支持作业、考试、待办列表快速查看。基于原版脚本[学习通任务一览 Chaoxing Assignment]修改:1. 新增支持在 https://i.chaoxing.com/ 空间页面显示;2. 优化考试与作业列表 UI(增加 Chip 标签);3. 新增“待办”标签,汇总所有未完成的考试和作业。
// @license AGPL-3.0-or-later
// @copyright lcandy2 All Rights Reserved
// @copyright 2025, Coren (Modified based on original work)
// @source https://github.com/lcandy2/user.js/tree/main/websites/chaoxing.com/chaoxing-assignment
// @match *://mooc1-api.chaoxing.com/work/stu-work*
// @match *://i.chaoxing.com/*
// @match *://i.chaoxing.com/base*
// @match *://i.mooc.chaoxing.com/space/index*
// @match *://i.mooc.chaoxing.com/settings*
// @match *://mooc1-api.chaoxing.com/exam-ans/exam/phone/examcode*
// @require https://registry.npmmirror.com/vue/3.4.27/files/dist/vue.global.prod.js
// @require data:application/javascript,%3Bwindow.Vue%3DVue%3B
// @require https://registry.npmmirror.com/vuetify/3.6.6/files/dist/vuetify.min.js
// @require data:application/javascript,%3B
// @resource VuetifyStyle https://registry.npmmirror.com/vuetify/3.6.6/files/dist/vuetify.min.css
// @resource material-design-icons-iconfont/dist/material-design-icons.css https://fonts.googlefonts.cn/css?family=Material+Icons
// @grant GM_addStyle
// @grant GM_getResourceText
// @run-at document-end
// ==/UserScript==
(function (vuetify, vue) {
'use strict';
// --- 核心工具函数 ---
const wrapElements = () => {
const wrapper = document.createElement("body");
wrapper.id = "chaoxing-assignment-wrapper";
while (document.body.firstChild) {
wrapper.appendChild(document.body.firstChild);
}
document.body.appendChild(wrapper);
wrapper.style.display = "none";
};
const removeStyles = () => {
removeHtmlStyle();
const styles = document.querySelectorAll("link[rel=stylesheet]");
styles.forEach((style) => {
var _a;
if ((_a = style.getAttribute("href")) == null ? void 0 : _a.includes("chaoxing")) {
style.remove();
}
});
};
const removeHtmlStyle = () => {
const html = document.querySelector("html");
html == null ? void 0 : html.removeAttribute("style");
};
const keepRemoveHtmlStyle = () => {
const observer = new MutationObserver((mutations) => {
mutations.forEach((mutation) => {
if (mutation.type === "attributes" && mutation.attributeName === "style") {
removeHtmlStyle();
}
});
});
const html = document.querySelector("html");
html && observer.observe(html, { attributes: true });
};
const removeScripts = () => {
const scripts = document.querySelectorAll("script");
scripts.forEach((script) => {
var _a;
if ((_a = script.getAttribute("src")) == null ? void 0 : _a.includes("chaoxing")) {
script.remove();
}
});
};
// --- URL 检测逻辑 (修改版) ---
const urlDetection = () => {
const url = window.location.href;
const hash = window.location.hash;
// 待办事项检测 (新增)
if (hash.includes("chaoxing-assignment-todo")) {
return "todo";
}
if (hash.includes("chaoxing-assignment")) {
if (url.includes("mooc1-api.chaoxing.com/work/stu-work")) {
return "homework";
}
if (url.includes("mooc1-api.chaoxing.com/exam-ans/exam/phone/examcode")) {
return "exam";
}
}
if (url.includes("i.chaoxing.com")) {
return "home";
}
if (url.includes("i.mooc.chaoxing.com/space/index") || url.includes("i.mooc.chaoxing.com/settings")) {
return "legacyHome";
}
};
// --- 样式修复 (新增) ---
const fixCssConflict = () => {
const style = document.createElement('style');
style.textContent = `
.menu-list .label-item h3 {
font-size: 14px !important;
margin: 0 !important;
line-height: 24px !important;
font-weight: normal !important;
}
div.menubar a h5 {
font-size: 14px !important;
margin: 0 !important;
line-height: normal !important;
font-weight: bold !important;
white-space: nowrap !important;
}
.leftnav h3, .left_nav h3, .funclistul h3, .user-info h3,
div[class*="menu"] h3, div[class*="nav"] h3, #space_left h3, .space-left h3 {
font-size: 14px !important;
margin: 0 !important;
line-height: 1.5 !important;
font-weight: normal !important;
padding: 0 !important;
display: inline-block !important;
}
.funclistul li a {
display: flex !important;
align-items: center !important;
}
.funclistul li a em, .funclistul li a b {
font-style: normal !important;
font-weight: normal !important;
font-size: 14px !important;
}
.space_opt {
display: flex !important;
align-items: center !important;
min-width: 200px !important;
white-space: nowrap !important;
}
.space_opt .manageBtn {
font-size: 12px !important;
line-height: 24px !important;
height: auto !important;
width: auto !important;
padding: 0 10px !important;
margin: 0 5px !important;
display: inline-block !important;
box-sizing: content-box !important;
text-align: center !important;
white-space: nowrap !important;
border-radius: 4px !important;
}
.space_opt a.manageBtn {
text-decoration: none !important;
color: #333 !important;
}
`;
document.head.appendChild(style);
};
// --- 菜单插入逻辑 (修改版) ---
const createMenuItem = (id, text, iconClass, urlHash, insertFunc) => {
const url = `https://mooc1-api.chaoxing.com/work/stu-work${urlHash}`;
const menubarElement = document.querySelector('div.menubar[role="menubar"]');
if (menubarElement) {
const a = document.createElement("a");
a.setAttribute("role", "menuitem");
a.setAttribute("tabindex", "-1");
a.id = `first${id}`;
a.setAttribute("onclick", `setUrl('${id}','${url}',this,'0','${text}')`);
a.setAttribute("dataurl", url);
const icon = document.createElement("span");
icon.className = `icon-space ${iconClass}`;
a.appendChild(icon);
const h5 = document.createElement("h5");
h5.title = text;
h5.innerHTML = `${text}`;
a.appendChild(h5);
const arrow = document.createElement("span");
arrow.className = "arrow icon-uniE900";
a.appendChild(arrow);
if(insertFunc) insertFunc(menubarElement, a);
else menubarElement.prepend(a);
}
};
const createMenuItemNew = (id, text, iconClass, urlHash, insertFunc) => {
const menuListElement = document.querySelector("ul.menu-list-ul");
if (menuListElement) {
const li = document.createElement("li");
li.setAttribute("level", "1");
li.setAttribute("table-type", "1");
li.setAttribute("data-id", `chaoxing-assignment-${id}`);
const div = document.createElement("div");
div.className = "label-item";
div.setAttribute("role", "menuitem");
div.setAttribute("level", "1");
div.setAttribute("tabindex", "-1");
div.setAttribute("name", text);
div.setAttribute("id", `first_chaoxing_assignment_${id}`);
div.setAttribute("onclick", `setUrl('chaoxing-assignment-${id}','https://mooc1-api.chaoxing.com/work/stu-work${urlHash}',this,'0','${text}')`);
div.setAttribute("dataurl", `https://mooc1-api.chaoxing.com/work/stu-work${urlHash}`);
const icon = document.createElement("span");
icon.className = `icon-space ${iconClass}`;
div.appendChild(icon);
const h3 = document.createElement("h3");
h3.title = text;
h3.textContent = text;
div.appendChild(h3);
const arrow = document.createElement("span");
arrow.className = "slide-arrow icon-h-arrow-l hide";
div.appendChild(arrow);
li.appendChild(div);
li.appendChild(Object.assign(document.createElement("div"), {className: "school-level"})).appendChild(document.createElement("ul"));
if(insertFunc) insertFunc(menuListElement, li);
else menuListElement.prepend(li);
}
};
const createMenuItemLegacy = (id, text, iconClass, urlHash) => {
const list = document.querySelector("ul.funclistul");
if(list) {
const li = document.createElement("li");
li.id = `li_chaoxing-assignment-${id}`;
li.innerHTML = `${text}`;
list.prepend(li);
}
};
const initMenus = () => {
// 插入顺序:prepend 会把后插入的放最上面
// 我们希望显示顺序:待办 -> 作业 -> 考试
// 所以执行顺序:考试 -> 作业 -> 待办
// 旧版首页
if (document.querySelector('div.menubar[role="menubar"]')) {
createMenuItem('1000002', '全部考试', 'icon-cj', '#chaoxing-assignment');
createMenuItem('1000001', '全部作业', 'icon-bj', '#chaoxing-assignment');
createMenuItem('1000003', '待办任务', 'icon-bj', '#chaoxing-assignment-todo');
}
// 新版首页
if (document.querySelector("ul.menu-list-ul")) {
createMenuItemNew('exam', '全部考试', 'icon-cj', '#chaoxing-assignment');
createMenuItemNew('homework', '全部作业', 'icon-bj', '#chaoxing-assignment');
createMenuItemNew('todo', '待办任务', 'icon-bj', '#chaoxing-assignment-todo');
}
// Legacy
if (document.querySelector("ul.funclistul")) {
createMenuItemLegacy('exam', '全部考试', 'zne_jc_icon', '#chaoxing-assignment');
createMenuItemLegacy('task', '全部作业', 'zne_bj_icon', '#chaoxing-assignment');
createMenuItemLegacy('todo', '待办任务', 'zne_bj_icon', '#chaoxing-assignment-todo');
}
};
// --- 数据提取逻辑 (修改版) ---
function extractTasks(doc = document) {
let taskElements = doc.querySelectorAll("#chaoxing-assignment-wrapper ul.nav > li");
if (taskElements.length === 0) taskElements = doc.querySelectorAll("ul.nav > li");
const tasks = Array.from(taskElements).map((task) => {
var _a, _b, _c;
const optionElement = task.querySelector('div[role="option"]');
let title = "";
let status = "";
let uncommitted = false;
let course = "";
let leftTime = "";
if (optionElement) {
title = ((_a = optionElement.querySelector("p")) == null ? void 0 : _a.textContent) || "";
const statusElement = optionElement.querySelector("span:nth-of-type(1)");
status = (statusElement == null ? void 0 : statusElement.textContent) || "";
uncommitted = (statusElement == null ? void 0 : statusElement.className.includes("status")) || false;
course = ((_b = optionElement.querySelector("span:nth-of-type(2)")) == null ? void 0 : _b.textContent) || "";
leftTime = ((_c = optionElement.querySelector(".fr")) == null ? void 0 : _c.textContent) || "";
}
const raw = task.getAttribute("data") || "";
let workId = "";
let courseId = "";
let clazzId = "";
if (raw) {
const rawUrl = new URL(raw);
const searchParams = rawUrl.searchParams;
workId = searchParams.get("taskrefId") || "";
courseId = searchParams.get("courseId") || "";
clazzId = searchParams.get("clazzId") || "";
}
return { type: "作业", title, status, uncommitted, course, leftTime, workId, courseId, clazzId, raw };
});
return tasks;
}
function extractExams(doc = document) {
let examElements = doc.querySelectorAll("#chaoxing-assignment-wrapper ul.ks_list > li");
if (examElements.length === 0) examElements = doc.querySelectorAll("ul.ks_list > li");
const exams = Array.from(examElements).map((exam) => {
var _a, _b, _c, _d;
const dlElement = exam.querySelector("dl");
const imgElement = exam.querySelector("div.ks_pic > img");
let title = "";
let timeLeft = "";
let status = "";
let expired = false;
let examId = "";
let courseId = "";
let classId = "";
if (dlElement) {
title = ((_a = dlElement.querySelector("dt")) == null ? void 0 : _a.textContent) || "";
timeLeft = ((_b = dlElement.querySelector("dd")) == null ? void 0 : _b.textContent) || "";
}
if (imgElement) {
expired = ((_c = imgElement.getAttribute("src")) == null ? void 0 : _c.includes("ks_02")) || false;
}
status = ((_d = exam.querySelector("span.ks_state")) == null ? void 0 : _d.textContent) || "";
const raw = exam.getAttribute("data") || "";
if (raw) {
let fullRaw = raw;
if (raw.startsWith('/')) fullRaw = window.location.protocol + "//" + window.location.host + raw;
try {
const rawUrl = new URL(fullRaw);
const searchParams = rawUrl.searchParams;
examId = searchParams.get("taskrefId") || "";
courseId = searchParams.get("courseId") || "";
classId = searchParams.get("classId") || "";
} catch(e) {}
}
const finished = status.includes("已完成") || status.includes("待批阅");
return { type: "考试", title, status, timeLeft, expired, finished, examId, courseId, classId, raw };
});
return exams;
}
const API_VISIT_COURSE = "https://mooc1.chaoxing.com/visit/stucoursemiddle?ismooc2=1";
const API_OPEN_EXAM = "https://mooc1-api.chaoxing.com/exam-ans/exam/test/examcode/examnotes";
const cssLoader = (e) => {
const t = GM_getResourceText(e);
return GM_addStyle(t), t;
};
cssLoader("VuetifyStyle");
// --- Vue Components ---
const _sfc_main$2 = /* @__PURE__ */ vue.defineComponent({
__name: "tasks-list",
setup(__props) {
const extractedData = extractTasks();
const headers = [
{ key: "title", title: "作业名称" },
{ key: "course", title: "课程" },
{ key: "leftTime", title: "剩余时间" },
{ key: "status", title: "状态" },
{ key: "action", title: "", sortable: false }
];
const search = vue.ref("");
const getCourseLinkHref = (item) => {
const courseId = item.courseId;
const clazzId = item.clazzId;
const requestUrl = new URL(API_VISIT_COURSE);
requestUrl.searchParams.append("courseid", courseId);
requestUrl.searchParams.append("clazzid", clazzId);
requestUrl.searchParams.append("pageHeader", "8");
return requestUrl.href;
};
return (_ctx, _cache) => {
const _component_v_text_field = vue.resolveComponent("v-text-field");
const _component_v_btn = vue.resolveComponent("v-btn");
const _component_v_data_table = vue.resolveComponent("v-data-table");
const _component_v_card = vue.resolveComponent("v-card");
return vue.openBlock(), vue.createBlock(_component_v_card, { title: "作业列表", variant: "flat" }, {
text: vue.withCtx(() => [
vue.createVNode(_component_v_text_field, {
modelValue: search.value,
"onUpdate:modelValue": _cache[0] || (_cache[0] = ($event) => search.value = $event),
label: "搜索", "prepend-inner-icon": "search", variant: "outlined", "hide-details": "", "single-line": ""
})
]),
default: vue.withCtx(() => [
vue.createVNode(_component_v_data_table, {
items: vue.unref(extractedData), search: search.value, hover: "", headers, sticky: "", "items-per-page": "-1", "hide-default-footer": ""
}, {
"item.action": vue.withCtx(({ item }) => [
vue.createVNode(_component_v_btn, {
variant: item.uncommitted ? "tonal" : "plain", color: "primary", href: getCourseLinkHref(item), target: "_blank"
}, {
default: vue.withCtx(() => [ vue.createTextVNode(vue.toDisplayString(item.uncommitted ? "立即完成" : "查看详情"), 1) ])
}, 1032, ["variant", "href"])
])
}, 8, ["items", "search"])
])
});
};
}
});
const _sfc_main$1 = /* @__PURE__ */ vue.defineComponent({
__name: "App",
setup(__props) {
return (_ctx, _cache) => {
return vue.openBlock(), vue.createBlock(_sfc_main$2);
};
}
});
cssLoader("material-design-icons-iconfont/dist/material-design-icons.css");
// --- Vuetify Helper Functions (恢复原版代码) ---
// 这些是原脚本为了适配图标组件而手写的一堆辅助函数,之前被误删
function isObject(obj) {
return obj !== null && typeof obj === "object" && !Array.isArray(obj);
}
function pick(obj, paths) {
const found = {};
const keys = new Set(Object.keys(obj));
for (const path of paths) {
if (keys.has(path)) {
found[path] = obj[path];
}
}
return found;
}
function mergeDeep() {
let source = arguments.length > 0 && arguments[0] !== void 0 ? arguments[0] : {};
let target = arguments.length > 1 && arguments[1] !== void 0 ? arguments[1] : {};
let arrayFn = arguments.length > 2 ? arguments[2] : void 0;
const out = {};
for (const key in source) {
out[key] = source[key];
}
for (const key in target) {
const sourceProperty = source[key];
const targetProperty = target[key];
if (isObject(sourceProperty) && isObject(targetProperty)) {
out[key] = mergeDeep(sourceProperty, targetProperty, arrayFn);
continue;
}
if (Array.isArray(sourceProperty) && Array.isArray(targetProperty) && arrayFn) {
out[key] = arrayFn(sourceProperty, targetProperty);
continue;
}
out[key] = targetProperty;
}
return out;
}
function toKebabCase() {
let str = arguments.length > 0 && arguments[0] !== void 0 ? arguments[0] : "";
if (toKebabCase.cache.has(str))
return toKebabCase.cache.get(str);
const kebab = str.replace(/[^a-z]/gi, "-").replace(/\B([A-Z])/g, "-$1").toLowerCase();
toKebabCase.cache.set(str, kebab);
return kebab;
}
toKebabCase.cache = /* @__PURE__ */ new Map();
function consoleWarn(message) {
vue.warn(`Vuetify: ${message}`);
}
function propsFactory(props, source) {
return (defaults) => {
return Object.keys(props).reduce((obj, prop) => {
const isObjectDefinition = typeof props[prop] === "object" && props[prop] != null && !Array.isArray(props[prop]);
const definition = isObjectDefinition ? props[prop] : {
type: props[prop]
};
if (defaults && prop in defaults) {
obj[prop] = {
...definition,
default: defaults[prop]
};
} else {
obj[prop] = definition;
}
if (source && !obj[prop].source) {
obj[prop].source = source;
}
return obj;
}, {});
};
}
const DefaultsSymbol = Symbol.for("vuetify:defaults");
function injectDefaults() {
const defaults = vue.inject(DefaultsSymbol);
if (!defaults)
throw new Error("[Vuetify] Could not find defaults instance");
return defaults;
}
function propIsDefined(vnode, prop) {
var _a, _b;
return typeof ((_a = vnode.props) == null ? void 0 : _a[prop]) !== "undefined" || typeof ((_b = vnode.props) == null ? void 0 : _b[toKebabCase(prop)]) !== "undefined";
}
function internalUseDefaults() {
let props = arguments.length > 0 && arguments[0] !== void 0 ? arguments[0] : {};
let name = arguments.length > 1 ? arguments[1] : void 0;
let defaults = arguments.length > 2 && arguments[2] !== void 0 ? arguments[2] : injectDefaults();
const vm = getCurrentInstance("useDefaults");
name = name ?? vm.type.name ?? vm.type.__name;
if (!name) {
throw new Error("[Vuetify] Could not determine component name");
}
const componentDefaults = vue.computed(() => {
var _a;
return (_a = defaults.value) == null ? void 0 : _a[props._as ?? name];
});
const _props = new Proxy(props, {
get(target, prop) {
var _a, _b, _c, _d;
const propValue = Reflect.get(target, prop);
if (prop === "class" || prop === "style") {
return [(_a = componentDefaults.value) == null ? void 0 : _a[prop], propValue].filter((v) => v != null);
} else if (typeof prop === "string" && !propIsDefined(vm.vnode, prop)) {
return ((_b = componentDefaults.value) == null ? void 0 : _b[prop]) ?? ((_d = (_c = defaults.value) == null ? void 0 : _c.global) == null ? void 0 : _d[prop]) ?? propValue;
}
return propValue;
}
});
const _subcomponentDefaults = vue.shallowRef();
vue.watchEffect(() => {
if (componentDefaults.value) {
const subComponents = Object.entries(componentDefaults.value).filter((_ref) => {
let [key] = _ref;
return key.startsWith(key[0].toUpperCase());
});
_subcomponentDefaults.value = subComponents.length ? Object.fromEntries(subComponents) : void 0;
} else {
_subcomponentDefaults.value = void 0;
}
});
function provideSubDefaults() {
const injected = injectSelf(DefaultsSymbol, vm);
vue.provide(DefaultsSymbol, vue.computed(() => {
return _subcomponentDefaults.value ? mergeDeep((injected == null ? void 0 : injected.value) ?? {}, _subcomponentDefaults.value) : injected == null ? void 0 : injected.value;
}));
}
return {
props: _props,
provideSubDefaults
};
}
function defineComponent(options) {
options._setup = options._setup ?? options.setup;
if (!options.name) {
consoleWarn("The component is missing an explicit name, unable to generate default prop value");
return options;
}
if (options._setup) {
options.props = propsFactory(options.props ?? {}, options.name)();
const propKeys = Object.keys(options.props).filter((key) => key !== "class" && key !== "style");
options.filterProps = function filterProps(props) {
return pick(props, propKeys);
};
options.props._as = String;
options.setup = function setup(props, ctx) {
const defaults = injectDefaults();
if (!defaults.value)
return options._setup(props, ctx);
const {
props: _props,
provideSubDefaults
} = internalUseDefaults(props, props._as ?? options.name, defaults);
const setupBindings = options._setup(_props, ctx);
provideSubDefaults();
return setupBindings;
};
}
return options;
}
function genericComponent() {
let exposeDefaults = arguments.length > 0 && arguments[0] !== void 0 ? arguments[0] : true;
return (options) => (exposeDefaults ? defineComponent : vue.defineComponent)(options);
}
function getCurrentInstance(name, message) {
const vm = vue.getCurrentInstance();
if (!vm) {
throw new Error(`[Vuetify] ${name} ${"must be called from inside a setup function"}`);
}
return vm;
}
function injectSelf(key) {
let vm = arguments.length > 1 && arguments[1] !== void 0 ? arguments[1] : getCurrentInstance("injectSelf");
const {
provides
} = vm;
if (provides && key in provides) {
return provides[key];
}
return void 0;
}
const IconValue = [String, Function, Object, Array];
const makeIconProps = propsFactory({
icon: {
type: IconValue
},
tag: {
type: String,
required: true
}
}, "icon");
genericComponent()({
name: "VComponentIcon",
props: makeIconProps(),
setup(props, _ref) {
let {
slots
} = _ref;
return () => {
const Icon = props.icon;
return vue.createVNode(props.tag, null, {
default: () => {
var _a;
return [props.icon ? vue.createVNode(Icon, null, null) : (_a = slots.default) == null ? void 0 : _a.call(slots)];
}
});
};
}
});
defineComponent({
name: "VSvgIcon",
inheritAttrs: false,
props: makeIconProps(),
setup(props, _ref2) {
let {
attrs
} = _ref2;
return () => {
return vue.createVNode(props.tag, vue.mergeProps(attrs, {
"style": null
}), {
default: () => [vue.createVNode("svg", {
"class": "v-icon__svg",
"xmlns": "http://www.w3.org/2000/svg",
"viewBox": "0 0 24 24",
"role": "img",
"aria-hidden": "true"
}, [Array.isArray(props.icon) ? props.icon.map((path) => Array.isArray(path) ? vue.createVNode("path", {
"d": path[0],
"fill-opacity": path[1]
}, null) : vue.createVNode("path", {
"d": path
}, null)) : vue.createVNode("path", {
"d": props.icon
}, null)])]
});
};
}
});
const VLigatureIcon = defineComponent({
name: "VLigatureIcon",
props: makeIconProps(),
setup(props) {
return () => {
return vue.createVNode(props.tag, null, {
default: () => [props.icon]
});
};
}
});
defineComponent({
name: "VClassIcon",
props: makeIconProps(),
setup(props) {
return () => {
return vue.createVNode(props.tag, {
"class": props.icon
}, null);
};
}
});
const aliases = {
collapse: "keyboard_arrow_up",
complete: "check",
cancel: "cancel",
close: "close",
delete: "cancel",
clear: "cancel",
success: "check_circle",
info: "info",
warning: "priority_high",
error: "warning",
prev: "chevron_left",
next: "chevron_right",
checkboxOn: "check_box",
checkboxOff: "check_box_outline_blank",
checkboxIndeterminate: "indeterminate_check_box",
delimiter: "fiber_manual_record",
sortAsc: "arrow_upward",
sortDesc: "arrow_downward",
expand: "keyboard_arrow_down",
menu: "menu",
subgroup: "arrow_drop_down",
dropdown: "arrow_drop_down",
radioOn: "radio_button_checked",
radioOff: "radio_button_unchecked",
edit: "edit",
ratingEmpty: "star_border",
ratingFull: "star",
ratingHalf: "star_half",
loading: "cached",
first: "first_page",
last: "last_page",
unfold: "unfold_more",
file: "attach_file",
plus: "add",
minus: "remove",
calendar: "event",
treeviewCollapse: "arrow_drop_down",
treeviewExpand: "arrow_right",
eyeDropper: "colorize"
};
const md = {
component: (props) => vue.h(VLigatureIcon, {
...props,
class: "material-icons"
})
};
// --- End of Vuetify Helpers ---
const _sfc_main = /* @__PURE__ */ vue.defineComponent({
__name: "exams-list",
setup(__props) {
const extractedData = extractExams();
const headers = [
{ key: "title", title: "考试名称" },
{ key: "timeLeft", title: "剩余时间" },
{ key: "status", title: "状态" },
{ key: "action", title: "", sortable: false }
];
const search = vue.ref("");
const getCourseLinkHref = (item) => {
const requestUrl = new URL(API_OPEN_EXAM);
requestUrl.searchParams.append("courseId", item.courseId);
requestUrl.searchParams.append("classId", item.classId);
requestUrl.searchParams.append("examId", item.examId);
return requestUrl.href;
};
return (_ctx, _cache) => {
const _component_v_text_field = vue.resolveComponent("v-text-field");
const _component_v_btn = vue.resolveComponent("v-btn");
const _component_v_data_table = vue.resolveComponent("v-data-table");
const _component_v_card = vue.resolveComponent("v-card");
return vue.openBlock(), vue.createBlock(_component_v_card, { title: "考试列表", variant: "flat" }, {
text: vue.withCtx(() => [
vue.createVNode(_component_v_text_field, {
modelValue: search.value, "onUpdate:modelValue": _cache[0] || (_cache[0] = ($event) => search.value = $event),
label: "搜索", "prepend-inner-icon": "search", variant: "outlined", "hide-details": "", "single-line": ""
})
]),
default: vue.withCtx(() => [
vue.createVNode(_component_v_data_table, {
items: vue.unref(extractedData), search: search.value, hover: "", headers, sticky: "", "items-per-page": "-1", "hide-default-footer": ""
}, {
"item.action": vue.withCtx(({ item }) => [
vue.createVNode(_component_v_btn, {
variant: item.finished || item.expired ? "plain" : "tonal", color: "primary", href: getCourseLinkHref(item), target: "_blank"
}, {
default: vue.withCtx(() => [ vue.createTextVNode(vue.toDisplayString(item.finished || item.expired ? "查看详情" : "前往考试"), 1) ])
}, 1032, ["variant", "href"])
])
}, 8, ["items", "search"])
])
});
};
}
});
// --- Todo List Component (新增) ---
const _sfc_todo = /* @__PURE__ */ vue.defineComponent({
__name: "todo-list",
setup(__props) {
const todoList = vue.ref([]);
const loading = vue.ref(true);
const search = vue.ref("");
const headers = [
{ key: "type", title: "类型" },
{ key: "title", title: "任务名称" },
{ key: "course", title: "课程" },
{ key: "info", title: "截止/剩余时间" },
{ key: "status", title: "状态" },
{ key: "action", title: "", sortable: false }
];
const getLink = (item) => {
if (item.type === "作业") {
const requestUrl = new URL(API_VISIT_COURSE);
requestUrl.searchParams.append("courseid", item.courseId);
requestUrl.searchParams.append("clazzid", item.clazzId);
requestUrl.searchParams.append("pageHeader", "8");
return requestUrl.href;
} else {
const requestUrl = new URL(API_OPEN_EXAM);
requestUrl.searchParams.append("courseId", item.courseId);
requestUrl.searchParams.append("classId", item.classId);
requestUrl.searchParams.append("examId", item.examId);
return requestUrl.href;
}
};
vue.onMounted(async () => {
const currentTasks = extractTasks();
const pendingTasks = currentTasks.filter(t => t.uncommitted).map(t => ({
...t,
info: t.leftTime
}));
let pendingExams = [];
try {
const res = await fetch('https://mooc1-api.chaoxing.com/exam-ans/exam/phone/examcode');
const text = await res.text();
const parser = new DOMParser();
const doc = parser.parseFromString(text, 'text/html');
const allExams = extractExams(doc);
pendingExams = allExams.filter(e => !e.finished && !e.expired).map(e => ({
...e,
course: "考试课程",
info: e.timeLeft
}));
} catch(e) {
console.error("Fetch exams failed", e);
}
todoList.value = [...pendingTasks, ...pendingExams];
loading.value = false;
});
return (_ctx, _cache) => {
const _component_v_text_field = vue.resolveComponent("v-text-field");
const _component_v_btn = vue.resolveComponent("v-btn");
const _component_v_data_table = vue.resolveComponent("v-data-table");
const _component_v_card = vue.resolveComponent("v-card");
const _component_v_chip = vue.resolveComponent("v-chip");
return vue.openBlock(), vue.createBlock(_component_v_card, { title: "待办任务", variant: "flat" }, {
text: vue.withCtx(() => [
vue.createVNode(_component_v_text_field, {
modelValue: search.value, "onUpdate:modelValue": ($event) => search.value = $event,
label: "搜索待办", "prepend-inner-icon": "search", variant: "outlined", "hide-details": "", "single-line": ""
})
]),
default: vue.withCtx(() => [
vue.createVNode(_component_v_data_table, {
items: todoList.value, loading: loading.value, search: search.value, hover: "", headers, sticky: "", "items-per-page": "-1", "hide-default-footer": ""
}, {
"item.type": vue.withCtx(({ item }) => [
vue.createVNode(_component_v_chip, {
color: item.type === '作业' ? 'blue' : 'purple',
size: 'small',
label: ''
}, { default: () => [vue.createTextVNode(item.type)] })
]),
"item.action": vue.withCtx(({ item }) => [
vue.createVNode(_component_v_btn, {
variant: "tonal", color: "error", href: getLink(item), target: "_blank"
}, {
default: vue.withCtx(() => [ vue.createTextVNode("立即去办") ])
}, 8, ["href"])
])
}, 8, ["items", "loading", "search"])
])
});
};
}
});
const appendApp = () => {
// 这里的 aliases 和 md 现在能正确引用到了
const vuetify$1 = vuetify.createVuetify({
icons: {
defaultSet: "md",
aliases,
sets: {
md
}
}
});
let app = _sfc_main$1;
const urlDetect2 = urlDetection();
if (urlDetect2 === "homework") app = _sfc_main$2;
if (urlDetect2 === "exam") app = _sfc_main;
if (urlDetect2 === "todo") app = _sfc_todo;
vue.createApp(app).use(vuetify$1).mount(
(() => {
const app2 = document.createElement("div");
document.body.append(app2);
return app2;
})()
);
};
const urlDetect = urlDetection();
if (urlDetect === "homework" || urlDetect === "todo") {
wrapElements();
removeStyles();
removeScripts();
appendApp();
}
if (urlDetect === "exam") {
wrapElements();
removeStyles();
removeScripts();
keepRemoveHtmlStyle();
appendApp();
}
if (urlDetect === "home") {
fixCssConflict();
initMenus();
}
if (urlDetect === "legacyHome") {
fixCssConflict();
initMenus();
}
})(Vuetify, Vue);