// ==UserScript== // @name 学习通作业/考试/待办列表(优化版) // @namespace https://github.com/Cooanyh // @version 1.5.1 // @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* // @match *://mooc1.chaoxing.com/exam-ans/exam/test/examcode/examlist* // @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(); } }); }; 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("mooc1.chaoxing.com/exam-ans/exam/test/examcode/examlist")) { 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; } .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, targetUrl, insertFunc) => { const url = targetUrl; 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, targetUrl, 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}','${targetUrl}',this,'0','${text}')`); div.setAttribute("dataurl", targetUrl); 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, targetUrl) => { const list = document.querySelector("ul.funclistul"); if (list && !document.querySelector(`#li_chaoxing-assignment-${id}`)) { const li = document.createElement("li"); li.id = `li_chaoxing-assignment-${id}`; li.className = ''; li.innerHTML = `${text}`; list.prepend(li); } }; // URL 常量 const URL_HOMEWORK = 'https://mooc1-api.chaoxing.com/work/stu-work#chaoxing-assignment'; const URL_EXAM = 'https://mooc1-api.chaoxing.com/exam-ans/exam/phone/examcode#chaoxing-assignment'; const URL_TODO = 'https://mooc1-api.chaoxing.com/work/stu-work#chaoxing-assignment-todo'; const initMenus = () => { if (document.querySelector('div.menubar[role="menubar"]')) { if (!document.querySelector('#first1000002')) { createMenuItem('1000002', '全部考试', 'icon-cj', URL_EXAM); } if (!document.querySelector('#first1000001')) { createMenuItem('1000001', '全部作业', 'icon-bj', URL_HOMEWORK); } if (!document.querySelector('#first1000003')) { createMenuItem('1000003', '待办任务', 'icon-bj', URL_TODO); } } else if (document.querySelector("ul.menu-list-ul")) { if (!document.querySelector('#first_chaoxing_assignment_exam')) { createMenuItemNew('exam', '全部考试', 'icon-cj', URL_EXAM); } if (!document.querySelector('#first_chaoxing_assignment_homework')) { createMenuItemNew('homework', '全部作业', 'icon-bj', URL_HOMEWORK); } if (!document.querySelector('#first_chaoxing_assignment_todo')) { createMenuItemNew('todo', '待办任务', 'icon-bj', URL_TODO); } } else if (document.querySelector("ul.funclistul")) { createMenuItemLegacy('exam', '全部考试', 'zne_jc_icon', URL_EXAM); createMenuItemLegacy('task', '全部作业', 'zne_bj_icon', URL_HOMEWORK); createMenuItemLegacy('todo', '待办任务', 'zne_bj_icon', URL_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; } function extractExamsFromTable(doc = document) { let examRows = doc.querySelectorAll("table.dataTable tr.dataTr"); if (examRows.length === 0) return []; const exams = Array.from(examRows).map((row) => { const cells = row.querySelectorAll("td"); if (cells.length < 9) return null; const index = cells[0]?.textContent?.trim() || ""; const title = cells[1]?.textContent?.trim() || ""; const timeRange = cells[2]?.textContent?.trim() || ""; const duration = cells[3]?.textContent?.trim() || ""; const examStatus = cells[4]?.textContent?.trim() || ""; const answerStatus = cells[5]?.textContent?.trim() || ""; const score = cells[6]?.textContent?.trim() || "---"; const examMethod = cells[7]?.textContent?.trim() || ""; // 从操作链接中提取参数 const actionLink = cells[8]?.querySelector("a"); const actionText = actionLink?.textContent?.trim() || ""; const onclickAttr = actionLink?.getAttribute("onclick") || ""; let courseId = ""; let classId = ""; let examId = ""; let raw = ""; const goMatch = onclickAttr.match(/go\(['"]([^'"]+)['"]\)/); if (goMatch) { raw = goMatch[1]; try { const fullUrl = new URL(raw, window.location.origin); const refer = fullUrl.searchParams.get("refer") || ""; const referDecoded = decodeURIComponent(refer); const referUrl = new URL(referDecoded, window.location.origin); courseId = referUrl.searchParams.get("courseId") || fullUrl.searchParams.get("moocId") || ""; classId = referUrl.searchParams.get("classId") || fullUrl.searchParams.get("clazzid") || ""; examId = referUrl.searchParams.get("examId") || ""; } catch (e) { const moocMatch = onclickAttr.match(/moocId=(\d+)/); const clazzMatch = onclickAttr.match(/clazzid=(\d+)/); const examIdMatch = onclickAttr.match(/examId=(\d+)/); if (moocMatch) courseId = moocMatch[1]; if (clazzMatch) classId = clazzMatch[1]; if (examIdMatch) examId = examIdMatch[1]; } } const expired = examStatus.includes("已结束"); const finished = answerStatus.includes("已完成") || answerStatus.includes("待批阅"); const status = answerStatus || examStatus; const timeLeft = expired ? "已结束" : timeRange; return { type: "考试", title, status, timeLeft, timeRange, duration, examStatus, answerStatus, score, examMethod, expired, finished, examId, courseId, classId, raw }; }).filter(e => e !== null); return exams; } const API_VISIT_COURSE = "https://mooc1.chaoxing.com/visit/stucoursemiddle?ismooc2=1"; const API_EXAM_LIST = "https://mooc1.chaoxing.com/exam-ans/exam/test/examcode/examlist?edition=1&nohead=0&fid="; 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" }) }; 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"]) ]) }); }; } }); 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 = []; const seenExamIds = new Set(); try { const res1 = await fetch('https://mooc1-api.chaoxing.com/exam-ans/exam/phone/examcode'); const text1 = await res1.text(); const parser1 = new DOMParser(); const doc1 = parser1.parseFromString(text1, 'text/html'); const exams1 = extractExams(doc1); exams1.filter(e => !e.finished && !e.expired).forEach(e => { const key = e.examId || e.title; if (!seenExamIds.has(key)) { seenExamIds.add(key); pendingExams.push({ ...e, course: "考试课程", info: e.timeLeft }); } }); } catch (e) { console.error("Fetch exams from old API failed", e); } try { const res2 = await fetch(API_EXAM_LIST); const text2 = await res2.text(); const parser2 = new DOMParser(); const doc2 = parser2.parseFromString(text2, 'text/html'); const exams2 = extractExamsFromTable(doc2); exams2.filter(e => !e.finished && !e.expired).forEach(e => { const key = e.examId || e.title; if (!seenExamIds.has(key)) { seenExamIds.add(key); pendingExams.push({ ...e, course: "考试课程", info: e.timeLeft }); } }); } catch (e) { console.error("Fetch exams from new API 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);