├── .gitignore ├── config ├── dev.ts ├── webpack.config.prod.ts ├── metadata.ts ├── webpack.config.dev.ts └── webpack.config.base.ts ├── .prettierrc ├── doc ├── img │ ├── HomePage.jpg │ ├── Buy me a coffee.png │ └── SubmissionPage.jpg ├── usage_old.md ├── update_log_old.md └── update_log.md ├── src ├── components │ ├── Lesson │ │ ├── LessonIndex.vue │ │ └── LessonSubmit │ │ │ ├── config │ │ │ ├── hwtlist.table.config.ts │ │ │ └── lessonlist.table.config.ts │ │ │ ├── HwtInfo.vue │ │ │ ├── LessonSubmit.vue │ │ │ └── HwtEditor.vue │ ├── WelcomePage │ │ ├── config │ │ │ ├── welcomeindex.config.ts │ │ │ ├── informlist.table.config.ts │ │ │ ├── lessontoplist.table.config.ts │ │ │ ├── notifylist.table.config.ts │ │ │ ├── lessonlist.table.config.ts │ │ │ └── hwtlist.table.config.ts │ │ ├── WelcomeIndex.vue │ │ ├── InformList.vue │ │ ├── LessonTopList.vue │ │ ├── NotifyList.vue │ │ ├── LessonList.vue │ │ └── HwtList.vue │ ├── About │ │ ├── AboutIndex.vue │ │ ├── DonateCard.vue │ │ ├── UpdateLogCard.vue │ │ └── ScriptInfoCard.vue │ ├── Profile │ │ ├── config │ │ │ └── form.config.ts │ │ └── ProfileIndex.vue │ ├── App.vue │ └── Common │ │ ├── HeadBar.vue │ │ └── SideBar.vue ├── base-ui │ ├── card │ │ ├── index.ts │ │ └── src │ │ │ └── zu-card.vue │ ├── form │ │ ├── index.ts │ │ ├── types │ │ │ └── index.ts │ │ └── src │ │ │ └── zu-form.vue │ └── table │ │ ├── index.ts │ │ ├── types │ │ └── index.ts │ │ └── src │ │ └── zu-table.vue ├── notify.json ├── request │ ├── Requests │ │ ├── getInformList.ts │ │ ├── getScriptNotify.ts │ │ ├── lessonOrderOperation.ts │ │ ├── getLoginStatus.ts │ │ ├── getHwtReviewNew.ts │ │ ├── getHwtReviewDetails.ts │ │ ├── getHwtSubmitNew.ts │ │ ├── getHwtSubmitOld.ts │ │ ├── getUserInfo.ts │ │ ├── getHwtReviewOld.ts │ │ ├── getLastestScriptNotify.ts │ │ ├── getLessonTopList.ts │ │ ├── getLessonList.ts │ │ ├── getNotifyList.ts │ │ ├── getScriptUpdateLog.ts │ │ ├── getRemindInfo.ts │ │ └── getHwtList.ts │ ├── SendRequest.ts │ └── API.ts ├── hooks │ ├── types │ │ └── index.ts │ ├── useLog.ts │ ├── CheckUsingOld.ts │ ├── useVersionInfo.ts │ ├── CheckUpdate.ts │ ├── useHwtFormatter.ts │ ├── usePageSort.ts │ └── Config │ │ ├── Config.ts │ │ └── ConfigOperations.ts ├── vue-shim.d.ts ├── utils │ ├── localCache.ts │ └── XHR.ts ├── index.ts ├── global │ ├── RegisterApp.ts │ └── RegisterIcons.ts └── route │ └── index.ts ├── .github └── ISSUE_TEMPLATE │ ├── feature.md │ ├── question.md │ └── bug.md ├── tsconfig.json ├── package.json ├── auto-imports.d.ts ├── components.d.ts └── readme.md /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist -------------------------------------------------------------------------------- /config/dev.ts: -------------------------------------------------------------------------------- 1 | console.log("Development Mode") -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 80, 3 | "tabWidth": 2, 4 | "semi": true 5 | } 6 | -------------------------------------------------------------------------------- /doc/img/HomePage.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZiuChen/NO-FLASH-Upload/HEAD/doc/img/HomePage.jpg -------------------------------------------------------------------------------- /src/components/Lesson/LessonIndex.vue: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /src/base-ui/card/index.ts: -------------------------------------------------------------------------------- 1 | import ZUCard from "./src/zu-card.vue"; 2 | 3 | export default ZUCard; 4 | -------------------------------------------------------------------------------- /src/base-ui/form/index.ts: -------------------------------------------------------------------------------- 1 | import ZUForm from "./src/zu-form.vue"; 2 | 3 | export default ZUForm; 4 | -------------------------------------------------------------------------------- /src/base-ui/table/index.ts: -------------------------------------------------------------------------------- 1 | import ZUTable from "./src/zu-table.vue"; 2 | 3 | export default ZUTable; 4 | -------------------------------------------------------------------------------- /doc/img/Buy me a coffee.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZiuChen/NO-FLASH-Upload/HEAD/doc/img/Buy me a coffee.png -------------------------------------------------------------------------------- /doc/img/SubmissionPage.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZiuChen/NO-FLASH-Upload/HEAD/doc/img/SubmissionPage.jpg -------------------------------------------------------------------------------- /src/notify.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": 10, 4 | "title": "保持脚本更新,使用最新版本", 5 | "content": "使用中遇到任何问题,欢迎到兔小巢或入群反馈。", 6 | "pubTime": "2023-04-24" 7 | } 8 | ] 9 | -------------------------------------------------------------------------------- /src/request/Requests/getInformList.ts: -------------------------------------------------------------------------------- 1 | import getNotifyList from "./getNotifyList"; 2 | 3 | export default async function getInformList() { 4 | return await getNotifyList("0"); 5 | } 6 | -------------------------------------------------------------------------------- /src/hooks/types/index.ts: -------------------------------------------------------------------------------- 1 | import { Component } from "vue"; 2 | 3 | export type TCpnConfig = { 4 | cpn: Component; 5 | span: number; 6 | }[]; 7 | 8 | export type TPlainConfig = { 9 | cpn: string; 10 | span: number; 11 | }[]; 12 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: 建议增加新功能 3 | about: 请按照该模板填写,以便我们能真正了解你的需求 4 | --- 5 | 6 | ## 功能描述 7 | 8 | *请输入内容……* 9 | 10 | ## 提炼几个功能点 11 | 12 | - 功能1 13 | - 功能2 14 | - 功能3 15 | 16 | ## 原型图 17 | 18 | *涉及到 UI 改动的功能,请一定提供原型图。原型图能表明功能即可,不要求规范和美观* 19 | 20 | ## 可参考的案例 21 | 22 | *是否已有可参考的案例,有的话请给出截图或链接* -------------------------------------------------------------------------------- /src/components/Lesson/LessonSubmit/config/hwtlist.table.config.ts: -------------------------------------------------------------------------------- 1 | import type { ITableConfig } from "@/base-ui/table/types"; 2 | import { TableConfig as preTableConfig } from "@/components/WelcomePage/config/hwtlist.table.config"; 3 | 4 | export const TableConfig: ITableConfig = { 5 | ...preTableConfig, 6 | showDragHandler: false, 7 | }; 8 | -------------------------------------------------------------------------------- /src/components/Lesson/LessonSubmit/config/lessonlist.table.config.ts: -------------------------------------------------------------------------------- 1 | import type { ITableConfig } from "@/base-ui/table/types"; 2 | import { TableConfig as preTableConfig } from "@/components/WelcomePage/config/lessonlist.table.config"; 3 | 4 | export const TableConfig: ITableConfig = { 5 | ...preTableConfig, 6 | showDragHandler: false, 7 | }; 8 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/question.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: 使用时遇到了问题(非 bug) 3 | about: 请按照该模板填写,以便我们能真正了解你的问题 4 | --- 5 | 6 | ### 问题描述 7 | 8 | *请输入内容……* 9 | 10 | ### 脚本的版本 11 | `右上角下拉菜单/检查更新` 12 | 13 | *请输入内容……* 14 | 15 | ### 浏览器及版本号 16 | `一般位于设置/关于` 17 | 18 | *请输入内容……* 19 | 20 | 21 | ### 最小成本的复现步骤 22 | `请告诉我们,如何最快的复现该问题` 23 | 24 | - 步骤一: 25 | - 步骤二: 26 | - 步骤三: -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: 提交 Bug 3 | about: 请按照该模板填写,以便更快复现问题 4 | --- 5 | 6 | ### Bug 描述 7 | 8 | *请输入内容……* 9 | 10 | ### 你预期的样子是? 11 | 12 | *请输入内容……* 13 | 14 | ### 脚本的版本 15 | `右上角下拉菜单/检查更新` 16 | 17 | *请输入内容……* 18 | 19 | ### 浏览器及版本号 20 | `一般位于设置/关于` 21 | 22 | *请输入内容……* 23 | 24 | 25 | ### 最小成本的复现步骤 26 | `请告诉我们,如何最快的复现该 bug` 27 | 28 | - 步骤一: 29 | - 步骤二: 30 | - 步骤三: -------------------------------------------------------------------------------- /src/request/Requests/getScriptNotify.ts: -------------------------------------------------------------------------------- 1 | import config from "../../hooks/Config/Config"; 2 | import XHR from "../../utils/XHR"; 3 | 4 | export default async function getScriptNotify() { 5 | return await XHR({ 6 | GM: true, 7 | anonymous: true, 8 | method: "GET", 9 | url: config.notifyURL, 10 | responseType: "json", 11 | }).then(({ body }: any) => body); 12 | } 13 | -------------------------------------------------------------------------------- /src/vue-shim.d.ts: -------------------------------------------------------------------------------- 1 | declare module "*.vue" { 2 | import { ComponentOptions } from "vue"; 3 | const componentOptions: ComponentOptions; 4 | export default componentOptions; 5 | } 6 | declare module "sortablejs" { 7 | export interface Temp { 8 | new (arg1: any, arg2: any): any; 9 | } 10 | export class Temp { 11 | constructor(arg1: any, arg2: any); 12 | } 13 | export default Temp; 14 | } 15 | -------------------------------------------------------------------------------- /src/components/WelcomePage/config/welcomeindex.config.ts: -------------------------------------------------------------------------------- 1 | export const plainConfig = [ 2 | { 3 | cpn: "HwtList", 4 | span: 24, 5 | }, 6 | { 7 | cpn: "NotifyList", 8 | span: 12, 9 | }, 10 | { 11 | cpn: "InformList", 12 | span: 12, 13 | }, 14 | { 15 | cpn: "LessonList", 16 | span: 24, 17 | }, 18 | { 19 | cpn: "LessonTopList", 20 | span: 12, 21 | }, 22 | ]; 23 | -------------------------------------------------------------------------------- /src/components/WelcomePage/config/informlist.table.config.ts: -------------------------------------------------------------------------------- 1 | import type { ITableConfig } from "@/base-ui/table/types"; 2 | 3 | export const TableConfig: ITableConfig = { 4 | title: "系统通知", 5 | propList: [ 6 | { 7 | prop: "notifyName", 8 | label: "通知", 9 | minWidth: "250", 10 | slotName: "notifyName", 11 | }, 12 | { prop: "pubTime", label: "发布时间", minWidth: "200", align: "right" }, 13 | ], 14 | tableHeight: "400px", 15 | showDragHandler: true, 16 | }; 17 | -------------------------------------------------------------------------------- /src/base-ui/form/types/index.ts: -------------------------------------------------------------------------------- 1 | type IFormType = "input" | "select" | "switch"; 2 | 3 | export interface IFormItem { 4 | field: string; 5 | type: IFormType; 6 | label: string; 7 | rules?: any[]; 8 | placeholder?: string; 9 | // 针对select 10 | options?: any[]; 11 | // 针对特殊的属性 12 | otherOptions?: any; 13 | isHidden?: boolean; 14 | } 15 | 16 | export interface IFormConfig { 17 | formItems: IFormItem[]; 18 | labelWidth?: string; 19 | itemStyle?: any; 20 | colLayout?: any; 21 | labelPosition?: string; 22 | } 23 | -------------------------------------------------------------------------------- /src/utils/localCache.ts: -------------------------------------------------------------------------------- 1 | class LocalCache { 2 | setCache(key: string, value: any) { 3 | window.localStorage.setItem(key, JSON.stringify(value)); 4 | } 5 | getCache(key: string) { 6 | const r = window.localStorage.getItem(key); 7 | if (r) { 8 | return JSON.parse(r); 9 | } else { 10 | return r; 11 | } 12 | } 13 | deleteCache(key: string) { 14 | window.localStorage.removeItem(key); 15 | } 16 | clearCache() { 17 | window.localStorage.clear(); 18 | } 19 | } 20 | 21 | export default new LocalCache(); 22 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import isUsingOld from "@/hooks/CheckUsingOld"; 2 | import ConfigOperations from "@/hooks/Config/ConfigOperations"; 3 | import registerApp from "@/global/RegisterApp"; 4 | 5 | const atMainPage = window.location.href.indexOf("personal.do") !== -1; 6 | 7 | ConfigOperations.initConfig(); 8 | if (!isUsingOld() && atMainPage) { 9 | // remove other tags 10 | document.querySelectorAll("head link, head script").forEach((n) => { 11 | n.remove(); 12 | }); 13 | // normalize page style 14 | document.body.style.margin = "0"; 15 | // vue init 16 | registerApp(); 17 | } 18 | -------------------------------------------------------------------------------- /src/components/About/AboutIndex.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /src/request/Requests/lessonOrderOperation.ts: -------------------------------------------------------------------------------- 1 | import sendRequest from "../SendRequest"; 2 | 3 | export default async function lessonOrderOperation( 4 | courseId: string, 5 | action: string 6 | ) { 7 | let LESSUP = `http://cc.bjtu.edu.cn:81/meol/lesson/blen.student.lesson.list.jsp?ACTION=LESSUP&lid=${courseId}`; 8 | let LESSDOWN = `http://cc.bjtu.edu.cn:81/meol/lesson/blen.student.lesson.list.jsp?ACTION=LESSDOWN&lid=${courseId}`; 9 | switch (action) { 10 | case "up": 11 | return await sendRequest(LESSUP); 12 | case "down": 13 | return await sendRequest(LESSDOWN); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/global/RegisterApp.ts: -------------------------------------------------------------------------------- 1 | import { createApp } from "vue"; 2 | import App from "../components/App.vue"; 3 | import router from "../route"; 4 | import { registerIcons } from "./RegisterIcons"; 5 | import { ElLoading } from "element-plus"; 6 | import "element-plus/theme-chalk/el-loading.css"; 7 | import "element-plus/theme-chalk/el-notification.css"; 8 | import "element-plus/theme-chalk/el-message-box.css"; 9 | 10 | const registerApp = () => { 11 | const app = createApp(App); 12 | app.directive("load", ElLoading.directive); 13 | app.use(registerIcons).use(router).mount("body"); 14 | }; 15 | export default registerApp; 16 | -------------------------------------------------------------------------------- /doc/usage_old.md: -------------------------------------------------------------------------------- 1 | ### 三步上手使用: 2 | 3 | ① 安装 `Tampermonkey`:[*Tampermonkey*](https://www.tampermonkey.net/) (Chrome浏览器需科学上网) 4 | 5 | ② 导入脚本:[*免Flash文件上传*](https://greasyfork.org/zh-CN/scripts/432056) (点击“安装此脚本”) 6 | 7 | ③ *Enjoy it !* 8 | 9 | 10 | ### 功能列表: 11 | 12 | ① 增加了`“上传作业”`标签,一键从课程列表跳转到上传作业界面,从此告别被23:59支配的恐惧! 13 | 14 | ② 文件上传界面支持`点击选择文件/拖拽文件`,并完美支持单文件、多文件上传。上传完毕后,脚本自动将附件加入编辑器。 15 | 16 | ③ 在作业列表显示了更多信息,如:`作业只允许提交一次`、`作业已提交`等。 17 | 18 | ④ 在主界面显示未提交作业到截止日的剩余时间,可以直接点击跳转到提交作业界面,若作业未提交,则主界面中课程名为红色。 19 | 20 | ⑤ 批量下载资源 21 | 22 | v1.0 - 使用效果 - 2.jpeg -------------------------------------------------------------------------------- /src/components/WelcomePage/WelcomeIndex.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /config/webpack.config.prod.ts: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | import { merge } from "webpack-merge"; 3 | import UserScriptMetaDataPlugin from "userscript-metadata-webpack-plugin"; 4 | import metadata from "./metadata"; 5 | import webpackConfig from "./webpack.config.base"; 6 | import * as BundleAnalyzerPlugin from "webpack-bundle-analyzer"; 7 | 8 | export default merge(webpackConfig, { 9 | mode: "production", 10 | optimization: { 11 | minimize: true, 12 | moduleIds: "named", 13 | }, 14 | output: { 15 | filename: "index.prod.user.js", 16 | }, 17 | plugins: [ 18 | new UserScriptMetaDataPlugin({ 19 | metadata, 20 | }), 21 | new BundleAnalyzerPlugin.BundleAnalyzerPlugin(), 22 | ], 23 | }); 24 | -------------------------------------------------------------------------------- /src/request/Requests/getLoginStatus.ts: -------------------------------------------------------------------------------- 1 | import { ElNotification } from "element-plus"; 2 | import getUserInfo from "./getUserInfo"; 3 | 4 | export default async function getLoginStatus() { 5 | return await getUserInfo().then((res) => { 6 | if (res === undefined) { 7 | let notify = ElNotification({ 8 | title: "免Flash文件上传", 9 | type: "warning", 10 | duration: 0, 11 | message: `获取用户信息失败,请检查登录状态是否过期`, 12 | onClick: () => { 13 | notify.close(); 14 | location.href = `http://cc.bjtu.edu.cn:81/meol/index.do`; 15 | }, 16 | }); 17 | return false; 18 | } else { 19 | return true; 20 | } 21 | }); 22 | } 23 | -------------------------------------------------------------------------------- /src/components/About/DonateCard.vue: -------------------------------------------------------------------------------- 1 | 18 | 19 | 22 | 23 | 31 | -------------------------------------------------------------------------------- /src/base-ui/table/types/index.ts: -------------------------------------------------------------------------------- 1 | type TPropListItem = { 2 | prop?: string; 3 | label: string; 4 | minWidth?: string; 5 | slotName?: string; 6 | align?: string; 7 | sortable?: boolean; 8 | filters?: any[]; 9 | "filtered-value"?: (-1 | 0 | 1)[]; 10 | "filter-method"?: (value: any, row: any, column: any) => any; 11 | }; 12 | 13 | type TSortItem = { 14 | prop: string; 15 | order?: string; 16 | }; 17 | 18 | type TRow = { 19 | [key: string]: any; 20 | }; 21 | 22 | export interface ITableConfig { 23 | title: string; 24 | propList: TPropListItem[]; 25 | tableHeight?: string; 26 | defaultSort?: TSortItem; 27 | rowClassName?: (row: TRow) => any; 28 | showDragHandler?: boolean; 29 | } 30 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "outDir": "/dist/", 4 | "target": "ESNext", 5 | "module": "ESNext", 6 | "strict": true, 7 | "jsx": "preserve", 8 | "importHelpers": true, 9 | "moduleResolution": "node", 10 | "resolveJsonModule": true, 11 | "skipLibCheck": true, 12 | "esModuleInterop": true, 13 | "allowSyntheticDefaultImports": true, 14 | "sourceMap": true, 15 | "baseUrl": ".", 16 | "paths": { 17 | "@/*": ["src/*"] 18 | }, 19 | "lib": ["esnext", "dom", "dom.iterable", "scripthost"] 20 | }, 21 | "ts-node": { 22 | "compilerOptions": { 23 | "module": "CommonJS" 24 | } 25 | }, 26 | "include": ["src/**/*.ts", "config/*.ts", "src/**/*.vue"], 27 | "exclude": ["node_modules"] 28 | } 29 | -------------------------------------------------------------------------------- /src/global/RegisterIcons.ts: -------------------------------------------------------------------------------- 1 | import { App } from "vue"; 2 | import { 3 | ArrowUpBold, 4 | ArrowDownBold, 5 | Refresh, 6 | Platform, 7 | Menu, 8 | Open, 9 | MoreFilled, 10 | CircleCloseFilled, 11 | ZoomIn, 12 | ZoomOut, 13 | StarFilled, 14 | Promotion, 15 | BellFilled, 16 | CollectionTag, 17 | } from "@element-plus/icons-vue"; 18 | 19 | const Icons = [ 20 | ArrowUpBold, 21 | ArrowDownBold, 22 | Refresh, 23 | Platform, 24 | Menu, 25 | Open, 26 | MoreFilled, 27 | CircleCloseFilled, 28 | ZoomIn, 29 | ZoomOut, 30 | StarFilled, 31 | Promotion, 32 | BellFilled, 33 | CollectionTag, 34 | ]; 35 | 36 | export function registerIcons(app: App) { 37 | for (const icon of Icons) { 38 | app.component(icon.name, icon); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/components/WelcomePage/config/lessontoplist.table.config.ts: -------------------------------------------------------------------------------- 1 | import type { ITableConfig } from "@/base-ui/table/types"; 2 | 3 | export const TableConfig: ITableConfig = { 4 | title: "访问排行", 5 | propList: [ 6 | { 7 | prop: "id", 8 | label: "课程ID", 9 | minWidth: "80", 10 | align: "center", 11 | }, 12 | { 13 | prop: "course", 14 | label: "课程名", 15 | minWidth: "120", 16 | align: "center", 17 | }, 18 | { 19 | prop: "teacher", 20 | label: "教师", 21 | minWidth: "80", 22 | align: "center", 23 | }, 24 | { 25 | prop: "visit", 26 | label: "访问数", 27 | minWidth: "80", 28 | align: "center", 29 | slotName: "visit", 30 | }, 31 | ], 32 | tableHeight: "400px", 33 | showDragHandler: true, 34 | }; 35 | -------------------------------------------------------------------------------- /src/request/Requests/getHwtReviewNew.ts: -------------------------------------------------------------------------------- 1 | import sendRequest from "../SendRequest"; 2 | 3 | export default async function getHwtReviewNew(courseId: string, hwtId: string) { 4 | let url = `http://cc.bjtu.edu.cn:81/meol/hw/stu/hwTaskAnswerView.do?courseId=${courseId}&hwtId=${hwtId}`; 5 | return await sendRequest(url, undefined).then((res) => { 6 | return res.json(); 7 | }); 8 | return { 9 | datas: { 10 | hwaAnswer: "", // 回答内容 11 | content: "", // 题目 12 | deadLine: "", // 截止日期 13 | finishTime: "", // 最近提交日期 14 | fullMark: 0, // 满分 15 | hwaId: 1111111, // hwaId 无法提交的作业无此property 16 | id: 11111, // hwtId 17 | manySubmitStatus: true, // 允许多次提交 18 | realName: "", // 发布人 19 | title: "", // 作业标题 20 | }, 21 | sessionid: "", 22 | status: 1, 23 | }; 24 | } 25 | -------------------------------------------------------------------------------- /src/request/Requests/getHwtReviewDetails.ts: -------------------------------------------------------------------------------- 1 | import sendRequest from "../SendRequest"; 2 | 3 | export default async function getHwtReviewDetails(hwtId: string) { 4 | let url = `http://cc.bjtu.edu.cn:81/meol/hw/stu/hwShowReviewDetails.do?type=tea&hwtId=${hwtId}`; 5 | return await sendRequest(url, undefined).then((res) => { 6 | return res.json(); 7 | }); 8 | return { 9 | datas: { 10 | answer: "", // 提交内容 11 | content: "", // 作业内容 12 | finishTime: "2022-03-19 10:00:00", // 提交时间 13 | fullMark: 5, // 满分 14 | id: 33333, // HwtId 15 | markTime: "2022-03-29 10:00", // 评分时间 16 | mutualRealName: "李四四", // 评分人 17 | operationStatus: true, // 18 | realName: "张三三", // 学生名 19 | score: 5, // 获得分数 20 | title: "", // 作业标题 21 | }, 22 | sessionid: "", 23 | status: 1, 24 | }; 25 | } 26 | -------------------------------------------------------------------------------- /src/components/Profile/config/form.config.ts: -------------------------------------------------------------------------------- 1 | import { IFormConfig } from "@/base-ui/form/types"; 2 | import Config from "@/hooks/Config/Config"; 3 | 4 | const FormConfig: IFormConfig = { 5 | formItems: [ 6 | { 7 | field: "config-back-to-old", 8 | type: "switch", 9 | label: "回到旧版", 10 | }, 11 | ], 12 | labelPosition: "top", 13 | colLayout: { 14 | xl: 6, 15 | lg: 6, 16 | md: 6, 17 | sm: 8, 18 | xs: 8, 19 | }, 20 | itemStyle: { 21 | "text-align": "center", 22 | }, 23 | }; 24 | 25 | FormConfig.formItems.length = 0; 26 | for (const [key, item] of Object.entries(Config.userConfig)) { 27 | if (key === "data-last-read-notify") continue; 28 | FormConfig.formItems.push({ 29 | field: key, 30 | type: item.type as any, 31 | label: item.name, 32 | }); 33 | } 34 | 35 | export { FormConfig }; 36 | -------------------------------------------------------------------------------- /src/request/Requests/getHwtSubmitNew.ts: -------------------------------------------------------------------------------- 1 | import sendRequest from "../SendRequest"; 2 | 3 | export default async function getHwtDetailNew(courseId: string, hwtId: string) { 4 | let url = `http://cc.bjtu.edu.cn:81/meol/hw/stu/hwStuSubmit.do?courseId=${courseId}&hwtId=${hwtId}`; 5 | return await sendRequest(url, undefined).then((res) => { 6 | return res.json().then((json: any) => { 7 | return json.datas; 8 | }); 9 | }); 10 | return { 11 | datas: { 12 | answer: "", // 回答内容 13 | content: "", // 题目 14 | deadLine: "", // 截止日期 15 | finishTime: "", // 最近提交日期 16 | fullMark: 0, // 满分 17 | hwaId: 1111111, // hwaId 18 | id: 11111, // hwtId 19 | manySubmitStatus: true, // 允许多次提交 20 | realName: "", // 发布人 21 | title: "", // 作业标题 22 | }, 23 | sessionid: "", 24 | status: 1, 25 | }; 26 | } 27 | -------------------------------------------------------------------------------- /src/hooks/useLog.ts: -------------------------------------------------------------------------------- 1 | import ConfigOperations from "./Config/ConfigOperations"; 2 | 3 | export default function log(Position: string, content: string, type?: string) { 4 | if ( 5 | ConfigOperations.readUserConfig()["config-console-log-show"].value === false 6 | ) 7 | return; 8 | let time = new Date().toLocaleTimeString(); 9 | let color = ""; 10 | switch (type) { 11 | case "success": 12 | color = "green"; 13 | break; 14 | case "warning": 15 | color = "orange"; 16 | break; 17 | case "error": 18 | color = "red"; 19 | break; 20 | case "info": 21 | color = "blue"; 22 | break; 23 | default: 24 | color = "grey"; 25 | break; 26 | } 27 | return console.log( 28 | `%c[${time}]` + `%c [${Position}] ` + content, 29 | "color: #005bac;", 30 | `color: ${color};` 31 | ); 32 | } 33 | -------------------------------------------------------------------------------- /src/components/WelcomePage/config/notifylist.table.config.ts: -------------------------------------------------------------------------------- 1 | import type { ITableConfig } from "@/base-ui/table/types"; 2 | 3 | export const TableConfig: ITableConfig = { 4 | title: "未读通知", 5 | propList: [ 6 | { 7 | prop: "notifyName", 8 | label: "通知名", 9 | minWidth: "150", 10 | slotName: "notifyName", 11 | }, 12 | { prop: "lessonName", label: "课程名", minWidth: "150", align: "center" }, 13 | { 14 | prop: "hadRead", 15 | label: "阅读状态", 16 | align: "center", 17 | slotName: "hadRead", 18 | }, 19 | { 20 | prop: "pubTime", 21 | label: "发布时间", 22 | minWidth: "180", 23 | align: "center", 24 | sortable: true, 25 | }, 26 | ], 27 | tableHeight: "400px", 28 | defaultSort: { 29 | prop: "pubTime", 30 | order: "descending", 31 | }, 32 | showDragHandler: true, 33 | }; 34 | -------------------------------------------------------------------------------- /src/request/Requests/getHwtSubmitOld.ts: -------------------------------------------------------------------------------- 1 | import sendRequest from "../SendRequest"; 2 | 3 | export default async function getHwtSubmitOld(hwtid: string) { 4 | return await sendRequest( 5 | `http://cc.bjtu.edu.cn:81/meol/common/hw/student/write.jsp?hwtid=${hwtid}`, 6 | (obj: Document) => { 7 | return obj; 8 | } 9 | ) 10 | .then((res) => { 11 | let table = res.querySelectorAll(".infotable>tbody>tr>td"); 12 | return { 13 | title: table[0].innerText.trim(), 14 | deadline: table[1].innerText.split(`\n`)[0], 15 | score: table[2].innerText.trim(), 16 | content: table[3].querySelectorAll("input")[0].value, 17 | hwtid: res.querySelector("input[name=hwtid]").attributes["value"].value, 18 | hwaid: res.querySelector("input[name=hwaid]").attributes["value"].value, 19 | }; 20 | }) 21 | .catch((err) => { 22 | console.log(err); 23 | }); 24 | } 25 | -------------------------------------------------------------------------------- /src/components/WelcomePage/config/lessonlist.table.config.ts: -------------------------------------------------------------------------------- 1 | import type { ITableConfig } from "@/base-ui/table/types"; 2 | 3 | export const TableConfig: ITableConfig = { 4 | title: "课程列表", 5 | propList: [ 6 | { 7 | prop: "number", 8 | label: "课程号", 9 | minWidth: "100", 10 | align: "center", 11 | }, 12 | { 13 | prop: "name", 14 | label: "课程名", 15 | minWidth: "150", 16 | align: "center", 17 | slotName: "name", 18 | }, 19 | { 20 | prop: "teacher", 21 | label: "教师", 22 | minWidth: "100", 23 | align: "center", 24 | }, 25 | { 26 | prop: "academy", 27 | label: "学院", 28 | minWidth: "150", 29 | align: "center", 30 | }, 31 | { 32 | label: "操作", 33 | minWidth: "100", 34 | align: "center", 35 | slotName: "handler", 36 | }, 37 | ], 38 | tableHeight: "400px", 39 | showDragHandler: true, 40 | }; 41 | -------------------------------------------------------------------------------- /src/hooks/CheckUsingOld.ts: -------------------------------------------------------------------------------- 1 | import { ElMessage, ElNotification } from "element-plus"; 2 | import ConfigOperations from "./Config/ConfigOperations"; 3 | 4 | function checkUsingOld() { 5 | if (ConfigOperations.readUserConfig()["config-back-to-old"].value) { 6 | // using old version 7 | ElMessage({ 8 | type: "warning", 9 | message: "正在使用旧版", 10 | }); 11 | if (location.href.indexOf("personal.do") !== -1) { 12 | // only show notification in personal.do page 13 | let notify = ElNotification({ 14 | title: "免Flash文件上传", 15 | type: "warning", 16 | message: `正在使用旧版,点此回到新版界面`, 17 | onClick: () => { 18 | ConfigOperations.setUserConfig("config-back-to-old", false); 19 | window.location.reload(); 20 | notify.close(); 21 | }, 22 | }); 23 | } 24 | return true; 25 | } else { 26 | return false; 27 | } 28 | } 29 | 30 | export default checkUsingOld; 31 | -------------------------------------------------------------------------------- /src/request/Requests/getUserInfo.ts: -------------------------------------------------------------------------------- 1 | import sendRequest from "../SendRequest"; 2 | 3 | interface IUserInfoObj { 4 | name: string; 5 | loginTime: string; 6 | onlineTime: string; 7 | loginTimes: string; 8 | [key: string]: any; 9 | } 10 | 11 | export default async function getUserInfo() { 12 | return sendRequest( 13 | `http://cc.bjtu.edu.cn:81/meol/welcomepage/student/index.jsp`, 14 | (obj: Document) => { 15 | return obj.querySelectorAll(".userinfobody>ul>li"); 16 | } 17 | ) 18 | .then((res) => { 19 | let obj: IUserInfoObj = { 20 | name: "", 21 | loginTime: "", 22 | onlineTime: "", 23 | loginTimes: "", 24 | }; 25 | res.forEach((item: { innerText: string }, index: number) => { 26 | obj[Object.keys(obj)[index]] = item.innerText.split(":")[1].trim(); 27 | }); 28 | return obj; 29 | }) 30 | .catch((error) => { 31 | console.log(error); 32 | }); 33 | } 34 | -------------------------------------------------------------------------------- /src/request/Requests/getHwtReviewOld.ts: -------------------------------------------------------------------------------- 1 | import sendRequest from "../SendRequest"; 2 | 3 | export default async function getHwtReviewOld(hwtid: string) { 4 | return await sendRequest( 5 | `http://cc.bjtu.edu.cn:81/meol/common/hw/student/taskanswer.jsp?hwtid=${hwtid}`, 6 | (obj: Document) => { 7 | return obj; 8 | } 9 | ) 10 | .then((res) => { 11 | let table = res.querySelectorAll(".infotable>tbody>tr>td"); 12 | return { 13 | title: table[0].innerText.trim(), 14 | deadLine: table[1].innerText.split(`\n`)[0], 15 | fullMark: table[2].innerText.trim(), 16 | score: table[3].innerText.trim(), 17 | content: table[4].querySelectorAll("input")[0].value, 18 | hwaAnswer: 19 | res.querySelectorAll(".text>input")[1] === undefined 20 | ? undefined 21 | : res.querySelectorAll(".text>input")[1].value, 22 | results: undefined, 23 | comments: undefined, 24 | }; 25 | }) 26 | .catch((err) => { 27 | console.log(err); 28 | }); 29 | } 30 | -------------------------------------------------------------------------------- /src/request/Requests/getLastestScriptNotify.ts: -------------------------------------------------------------------------------- 1 | import { ElMessageBox } from "element-plus"; 2 | import getScriptNotify from "./getScriptNotify"; 3 | import ConfigOperations from "../../hooks/Config/ConfigOperations"; 4 | 5 | export default async function getLastestScriptNotify(manually: boolean) { 6 | return await getScriptNotify().then((notifies) => { 7 | // 取最大通知id若未阅读则弹窗提示阅读 8 | notifies.sort((a: any, b: any) => { 9 | return b.id - a.id; 10 | }); 11 | if ( 12 | notifies[0].id > 13 | ConfigOperations.readUserConfig()["data-last-read-notify"].value || 14 | manually // 手动触发显示 15 | ) { 16 | // 还未阅读此通知,显示通知 17 | ElMessageBox.alert(notifies[0].content, notifies[0].title, { 18 | confirmButtonText: "OK", 19 | callback: (action: any) => { 20 | ConfigOperations.setUserConfig( 21 | "data-last-read-notify", 22 | notifies[0].id 23 | ); 24 | }, 25 | }); 26 | } else { 27 | // 此通知已阅读且并非手动触发 28 | // nothing to do 29 | } 30 | }); 31 | } 32 | -------------------------------------------------------------------------------- /config/metadata.ts: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | import { author, repository, version, description } from "../package.json"; 3 | 4 | export default { 5 | name: "免Flash文件上传", 6 | "name:en": "NO-FLASH-Upload", 7 | description: description, 8 | "description:en": 9 | "Beijing Jiaotong University curriculum platform function enhancements, information aggregation, accessories uploading, allowing you to efficient course information.", 10 | updateURL: 11 | "https://fastly.jsdelivr.net/gh/ZiuChen/NO-FLASH-Upload@master/publish/index.prod.user.js", 12 | downloadURL: 13 | "https://fastly.jsdelivr.net/gh/ZiuChen/NO-FLASH-Upload@master/publish/index.prod.user.js", 14 | version: version, 15 | author: author, 16 | source: repository.url, 17 | supportURL: repository.url + "/issues", 18 | license: "MIT", 19 | match: ["*://cc.bjtu.edu.cn:81/meol*"], 20 | namespace: "https://greasyfork.org/zh-CN/users/605474", 21 | require: [], 22 | icon: "https://gcore.jsdelivr.net/gh/ZiuChen/ZiuChen@main/avatar.jpg", 23 | connect: ["gitee.com"], 24 | grant: ["GM_xmlhttpRequest"], 25 | }; 26 | -------------------------------------------------------------------------------- /src/base-ui/card/src/zu-card.vue: -------------------------------------------------------------------------------- 1 | 17 | 18 | 26 | 27 | 48 | -------------------------------------------------------------------------------- /config/webpack.config.dev.ts: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | import path from "path"; 3 | import { merge } from "webpack-merge"; 4 | import UserScriptMetaDataPlugin from "userscript-metadata-webpack-plugin"; 5 | import metadata from "./metadata"; 6 | import webpackConfig from "./webpack.config.base"; 7 | 8 | metadata.name = "NO-Flash-Upload(dev)"; 9 | metadata["name:en"] = "NO-Flash-Upload(dev)"; 10 | metadata.require.push( 11 | "file://" + path.resolve(__dirname, "../dist/index.debug.user.js") 12 | ); 13 | 14 | export default merge(webpackConfig, { 15 | mode: "development", 16 | entry: { 17 | debug: webpackConfig.entry, 18 | dev: path.resolve(__dirname, "./dev.ts"), // Generate index.dev.user.js 19 | }, 20 | optimization: { 21 | minimize: false, 22 | moduleIds: "named", 23 | }, 24 | output: { 25 | filename: "index.[name].user.js", 26 | path: path.resolve(__dirname, "../dist"), 27 | }, 28 | devtool: "eval-source-map", 29 | watch: true, 30 | watchOptions: { 31 | ignored: ["**/node_modules"], 32 | }, 33 | plugins: [ 34 | new UserScriptMetaDataPlugin({ 35 | metadata, 36 | }), 37 | ], 38 | }); 39 | -------------------------------------------------------------------------------- /src/request/Requests/getLessonTopList.ts: -------------------------------------------------------------------------------- 1 | import sendRequest from "../SendRequest"; 2 | 3 | export default async function getLessonTopList(type: string) { 4 | return await sendRequest( 5 | `http://cc.bjtu.edu.cn:81/meol/homepage/common/${type}_lesson_top_list.jsp`, 6 | (doc: Document) => { 7 | return doc.querySelectorAll(".datatable tr"); 8 | } 9 | ).then((res) => { 10 | let rtnArray: object[] = []; 11 | res.forEach((item: HTMLTableCellElement, index: number) => { 12 | let id = ""; 13 | if (index === 0) return; 14 | if (item.querySelector("a") === null) { 15 | id = "暂无id"; 16 | } else { 17 | id = item 18 | ?.querySelector("a") 19 | ?.getAttribute("href") 20 | ?.split("?courseId=")[1] 21 | ?.split("&_style=")[0] as string; 22 | } 23 | rtnArray.push({ 24 | id: id, 25 | course: item.querySelector("td")?.innerText, 26 | teacher: item.querySelector(".tea")?.innerHTML, 27 | visit: item.querySelector("p")?.innerHTML, 28 | }); 29 | }); 30 | return rtnArray; 31 | }); 32 | } 33 | -------------------------------------------------------------------------------- /src/components/App.vue: -------------------------------------------------------------------------------- 1 | 20 | 21 | 26 | 27 | 49 | -------------------------------------------------------------------------------- /src/components/WelcomePage/InformList.vue: -------------------------------------------------------------------------------- 1 | 20 | 21 | 26 | 27 | 42 | -------------------------------------------------------------------------------- /src/components/About/UpdateLogCard.vue: -------------------------------------------------------------------------------- 1 | 28 | 29 | 42 | 43 | 48 | -------------------------------------------------------------------------------- /src/request/Requests/getLessonList.ts: -------------------------------------------------------------------------------- 1 | import sendRequest from "../SendRequest"; 2 | import localCache from "@/utils/localCache"; 3 | 4 | export default async function getLessonList() { 5 | let url = `http://cc.bjtu.edu.cn:81/meol/lesson/blen.student.lesson.list.jsp`; 6 | return await sendRequest(url, (doc: Document) => { 7 | return doc.querySelectorAll("tbody > tr"); 8 | }).then((res: any) => { 9 | let rtnArray: object[] = []; 10 | res.forEach((item: HTMLTableRowElement, index: number) => { 11 | if (index === 0) return; 12 | let course = item.querySelectorAll("td a")[0] as HTMLTableCellElement; 13 | let academy = item.children[2] as HTMLTableCellElement; 14 | let teacher = item.children[3] as HTMLTableCellElement; 15 | rtnArray.push({ 16 | number: item.querySelector("td")?.innerText, 17 | id: course 18 | ?.getAttribute("onclick") 19 | ?.split("courseId=")[1] 20 | ?.split("','")[0], 21 | name: course.innerText.split("\n")[0].trim(), 22 | academy: academy.innerText.split("\n")[0], 23 | teacher: teacher.innerText.split("\n")[0], 24 | }); 25 | }); 26 | localCache.setCache("lesson-list", rtnArray); 27 | return rtnArray; 28 | }); 29 | } 30 | -------------------------------------------------------------------------------- /src/request/Requests/getNotifyList.ts: -------------------------------------------------------------------------------- 1 | import sendRequest from "../SendRequest"; 2 | 3 | export default async function getNotifyList(lid: string) { 4 | return await sendRequest( 5 | `http://cc.bjtu.edu.cn:81/meol/common/inform/index_stu.jsp?tagbug=client&s_order=0&lid=${lid}`, 6 | (obj: Document) => { 7 | return obj.querySelectorAll(".valuelist tr"); 8 | } 9 | ).then((res) => { 10 | let array: object[] = []; 11 | res.forEach((item: HTMLTableCellElement, index: number) => { 12 | if (index === 0) return; 13 | let obj = { 14 | notifyName: "", 15 | nid: "", 16 | pubTime: "", 17 | hadRead: false, 18 | }; 19 | if (item.querySelectorAll("a").length === 0) return; 20 | obj.notifyName = item 21 | .querySelectorAll("a")[0] 22 | .getAttribute("title") as string; 23 | obj.nid = item 24 | ?.querySelectorAll("a")[0] 25 | ?.getAttribute("href") 26 | ?.split("?nid=")[1] 27 | ?.split('"')[0] as string; 28 | obj.pubTime = item.querySelectorAll(".align_c")[0].innerHTML; 29 | obj.hadRead = item.querySelectorAll("b").length === 0; // without return true 30 | array.push(obj); 31 | }); 32 | return array; 33 | }); 34 | } 35 | -------------------------------------------------------------------------------- /src/request/Requests/getScriptUpdateLog.ts: -------------------------------------------------------------------------------- 1 | import { ElNotification } from "element-plus"; 2 | import log from "../../hooks/useLog"; 3 | import XHR from "../../utils/XHR"; 4 | import config from "../../hooks/Config/Config"; 5 | 6 | export default async function getScriptUpdateLog() { 7 | return await XHR({ 8 | GM: true, 9 | anonymous: true, 10 | method: "GET", 11 | url: config.updateLOG, 12 | headers: { Referer: config.updateLOG }, 13 | responseType: "text/html", 14 | }) 15 | .then((res: any) => { 16 | if (res === undefined) { 17 | log("getScriptUpdateLog", "数据获取异常,请重试或联系开发者", "error"); 18 | let notify = ElNotification({ 19 | title: "免Flash文件上传", 20 | type: "warning", 21 | message: `数据获取异常,请重试或联系开发者`, 22 | onClick: () => { 23 | notify.close(); 24 | }, 25 | }); 26 | } 27 | return res.body; 28 | }) 29 | .then((res: string) => { 30 | const p = new DOMParser(); 31 | const dom = p.parseFromString(res, "text/html"); 32 | const ver = dom?.querySelector("p")?.innerHTML; 33 | const items = dom?.querySelector("ul")?.innerHTML; 34 | return ` 35 |

${ver}

36 | ${items}`; 37 | }); 38 | } 39 | -------------------------------------------------------------------------------- /src/hooks/useVersionInfo.ts: -------------------------------------------------------------------------------- 1 | import log from "./useLog"; 2 | import config from "./Config/Config"; 3 | import XHR from "../utils/XHR"; 4 | 5 | export default async function useVersionInfo() { 6 | return await XHR({ 7 | GM: true, 8 | anonymous: true, 9 | method: "GET", 10 | url: config.updateInfo, 11 | headers: { Referer: config.updateInfo }, 12 | responseType: "json", 13 | }) 14 | .then((res: any) => { 15 | if (res === undefined) { 16 | log("getVersionInfo", "数据获取异常,请重试或联系开发者", "error"); 17 | } 18 | return res.body.version; 19 | }) 20 | .then((version) => { 21 | const weightLastest = v2weight(version); 22 | const weightNow = v2weight(config.version); 23 | log("getVersionInfo", `最新版本: ${version}`, "info"); 24 | log("getVersionInfo", `脚本当前版本: ${config.version}`, "info"); 25 | if (weightLastest > weightNow) { 26 | return { need: true, current: config.version, lastest: version }; 27 | } else { 28 | return { need: false, current: config.version, lastest: version }; 29 | } 30 | }); 31 | } 32 | 33 | function v2weight(v: string) { 34 | let weight = 0; 35 | v.split(".") 36 | .reverse() 37 | .forEach((value, index) => { 38 | weight += (index + 1) * Math.pow(100, index + 1) * parseInt(value); 39 | }); 40 | return weight; 41 | } 42 | -------------------------------------------------------------------------------- /src/components/WelcomePage/LessonTopList.vue: -------------------------------------------------------------------------------- 1 | 20 | 21 | 26 | 27 | 41 | 42 | 49 | -------------------------------------------------------------------------------- /src/components/Common/HeadBar.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 36 | 37 | 55 | -------------------------------------------------------------------------------- /src/request/Requests/getRemindInfo.ts: -------------------------------------------------------------------------------- 1 | import sendRequest from "../SendRequest"; 2 | interface INotifyObj { 3 | notify: any[]; 4 | hwt: any[]; 5 | } 6 | 7 | export default async function getRemindInfo() { 8 | return await sendRequest( 9 | `http://cc.bjtu.edu.cn:81/meol/welcomepage/student/interaction_reminder.jsp`, 10 | (obj: Document) => { 11 | return obj.querySelectorAll("ul[id='reminder']>li>ul"); 12 | } 13 | ) 14 | .then((res) => { 15 | let obj: INotifyObj = { 16 | notify: [], 17 | hwt: [], 18 | }; 19 | res.forEach((it: Document, ind: number) => { 20 | it.querySelectorAll("li>a").forEach((item: any) => { 21 | let classobj = { 22 | name: "", 23 | id: "", 24 | type: "", 25 | }; 26 | classobj.name = item.innerText.trim(); 27 | classobj.id = item 28 | ?.getAttribute("href") 29 | ?.split("lid=")[1] 30 | ?.split("&t=")[0] as string; 31 | classobj.type = item 32 | ?.getAttribute("href") 33 | ?.split("lid=")[1] 34 | ?.split("&t=")[1] as string; 35 | if (classobj.type === "hw") { 36 | obj["hwt"].push(classobj); 37 | } else if (classobj.type === "info") { 38 | obj["notify"].push(classobj); 39 | } 40 | }); 41 | }); 42 | return obj; 43 | }) 44 | .catch((error) => { 45 | console.log(error); 46 | }); 47 | } 48 | -------------------------------------------------------------------------------- /config/webpack.config.base.ts: -------------------------------------------------------------------------------- 1 | import path from "path"; 2 | import { VueLoaderPlugin } from "vue-loader"; 3 | import AutoImport from "unplugin-auto-import/webpack"; 4 | import Components from "unplugin-vue-components/webpack"; 5 | import { ElementPlusResolver } from "unplugin-vue-components/resolvers"; 6 | 7 | export default { 8 | resolve: { 9 | alias: { 10 | "@": path.resolve(__dirname, "../src"), 11 | }, 12 | extensions: [".js", ".ts"], 13 | }, 14 | entry: "./src/index.ts", 15 | output: { 16 | publicPath: path.resolve(__dirname, "../dist"), 17 | path: path.resolve(__dirname, "../dist"), 18 | }, 19 | target: "web", 20 | module: { 21 | rules: [ 22 | { 23 | test: /\.vue$/, 24 | loader: "vue-loader", 25 | }, 26 | { 27 | test: /\.js$/, 28 | exclude: /node_modules/, 29 | loader: "babel-loader", 30 | }, 31 | { 32 | test: /\.m?js/, 33 | resolve: { 34 | fullySpecified: false, 35 | }, 36 | }, 37 | { 38 | test: /\.ts$/, 39 | loader: "ts-loader", 40 | }, 41 | { 42 | test: /\.css$/, 43 | use: ["style-loader", "css-loader"], 44 | }, 45 | ], 46 | }, 47 | plugins: [ 48 | new VueLoaderPlugin(), 49 | AutoImport({ 50 | imports: ["vue", "vue-router"], 51 | resolvers: [ElementPlusResolver()], 52 | }), 53 | Components({ 54 | resolvers: [ElementPlusResolver()], 55 | }), 56 | ], 57 | }; 58 | -------------------------------------------------------------------------------- /src/route/index.ts: -------------------------------------------------------------------------------- 1 | import { createRouter, createWebHashHistory } from "vue-router"; 2 | import { ElMessage } from "element-plus"; 3 | import WelcomeIndex from "../components/WelcomePage/WelcomeIndex.vue"; 4 | import LessonIndex from "../components/Lesson/LessonIndex.vue"; 5 | import LessonSubmit from "../components/Lesson/LessonSubmit/LessonSubmit.vue"; 6 | import ProfileIndex from "../components/Profile/ProfileIndex.vue"; 7 | import AboutIndex from "../components/About/AboutIndex.vue"; 8 | 9 | import API from "../request/API"; 10 | import log from "../hooks/useLog"; 11 | 12 | const router = createRouter({ 13 | history: createWebHashHistory(), 14 | routes: [ 15 | { path: "/", redirect: "/welcome" }, 16 | { 17 | path: "/welcome", 18 | component: WelcomeIndex, 19 | }, 20 | { 21 | path: "/lesson/:lid", 22 | component: LessonIndex, 23 | redirect: (to) => { 24 | return `${to.path}/info`; 25 | }, 26 | children: [ 27 | { 28 | path: "submit/:hwtid", 29 | component: LessonSubmit, 30 | }, 31 | ], 32 | }, 33 | { 34 | path: "/profile", 35 | component: ProfileIndex, 36 | }, 37 | { 38 | path: "/about", 39 | component: AboutIndex, 40 | }, 41 | ], 42 | }); 43 | 44 | router.beforeEach((to, from, next) => { 45 | API.getLoginStatus(); 46 | if (to.matched.length === 0) { 47 | // no match 48 | log("router.beforeEach", `未找到界面,重定向`, `warning`); 49 | from.name ? next({ name: from.name }) : next("/"); 50 | ElMessage({ 51 | type: "error", 52 | message: "未找到页面", 53 | }); 54 | } else { 55 | // matched 56 | next(); 57 | } 58 | }); 59 | 60 | export default router; 61 | -------------------------------------------------------------------------------- /src/hooks/CheckUpdate.ts: -------------------------------------------------------------------------------- 1 | import { ElNotification, ElMessageBox } from "element-plus"; 2 | import useVersionInfo from "./useVersionInfo"; 3 | import config from "./Config/Config"; 4 | import ConfigOperations from "./Config/ConfigOperations"; 5 | import type { Action } from "element-plus"; 6 | 7 | async function checkUpdate() { 8 | await useVersionInfo().then((res) => { 9 | if (res.need) { 10 | let notify = ElNotification({ 11 | title: "免Flash文件上传", 12 | type: "warning", 13 | message: `有新版本${res.lastest},当前版本${res.current}。请点击此处更新`, 14 | duration: 0, 15 | onClick: () => { 16 | window.location.href = config.updateURL; 17 | notify.close(); 18 | ElMessageBox.alert( 19 | `请在弹出的网页中更新脚本,更新后点击“ OK ”重新加载此页面`, 20 | "提示", 21 | { 22 | confirmButtonText: "OK", 23 | callback: (action: Action) => { 24 | if (action === "confirm") { 25 | window.location.reload(); 26 | } else { 27 | return false; 28 | } 29 | }, 30 | } 31 | ); 32 | }, 33 | }); 34 | return true; // need update 35 | } else { 36 | if ( 37 | ConfigOperations.readUserConfig()["config-hide-update-notify"].value === 38 | false 39 | ) { 40 | let notify = ElNotification({ 41 | title: "免Flash文件上传", 42 | type: "success", 43 | message: `版本已是最新:${res.current}`, 44 | onClick: () => { 45 | notify.close(); 46 | }, 47 | }); 48 | } 49 | return false; // don't need update 50 | } 51 | }); 52 | } 53 | 54 | export default checkUpdate; 55 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "no-flash-upload", 3 | "version": "2.4.0", 4 | "description": "北京交通大学课程平台功能增强脚本,实现信息聚合,附件上传,让你高效处理课程信息。", 5 | "main": "userscript.js", 6 | "scripts": { 7 | "dev": "webpack --mode development --config config/webpack.config.dev.ts", 8 | "build": "webpack --mode production --config config/webpack.config.prod.ts" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "https://github.com/ZiuChen/NO-FLASH-Upload" 13 | }, 14 | "author": "ZiuChen", 15 | "license": "MIT", 16 | "bugs": { 17 | "url": "https://github.com/ZiuChen/NO-FLASH-Upload/issues" 18 | }, 19 | "homepage": "https://github.com/ZiuChen/NO-FLASH-Upload#readme", 20 | "devDependencies": { 21 | "@babel/core": "^7.16.7", 22 | "@types/node": "^18.11.9", 23 | "babel-loader": "^8.2.3", 24 | "css-loader": "^6.5.1", 25 | "style-loader": "^3.3.1", 26 | "terser-webpack-plugin": "^5.3.1", 27 | "ts-loader": "^9.2.6", 28 | "ts-node": "^10.9.1", 29 | "tsconfig-paths": "^4.2.0", 30 | "typescript": "^4.5.5", 31 | "unplugin-auto-import": "^0.11.5", 32 | "unplugin-vue-components": "^0.22.11", 33 | "userscript-metadata-webpack-plugin": "^0.1.1", 34 | "vue-loader": "^17.0.0", 35 | "vue-loader-plugin": "^1.3.0", 36 | "vue-router": "^4.0.12", 37 | "vue-template-compiler": "^2.6.14", 38 | "webpack-bundle-analyzer": "^4.5.0", 39 | "webpack-cli": "^4.9.1", 40 | "webpack-merge": "^5.8.0" 41 | }, 42 | "dependencies": { 43 | "@element-plus/icons-vue": "^2.0.6", 44 | "@wangeditor/editor": "^5.1.23", 45 | "@wangeditor/editor-for-vue": "^5.1.12", 46 | "element-plus": "^2.2.6", 47 | "sortablejs": "^1.15.0", 48 | "vue": "^3.2.28", 49 | "webpack": "^5.65.0" 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/request/SendRequest.ts: -------------------------------------------------------------------------------- 1 | import { ElNotification } from "element-plus"; 2 | import log from "../hooks/useLog"; 3 | import config from "../hooks/Config/Config"; 4 | const baseURL = "http://cc.bjtu.edu.cn:81/meol/"; 5 | 6 | async function sendRequest(url: string, callBack?: Function, options?: Object) { 7 | const reportURL = url.split(baseURL)[1] ?? url; 8 | log("sendRequest", `发送请求: ${reportURL}`); 9 | return fetch(url, options) 10 | .then((response) => { 11 | // 没有处理DOM的回调则直接返回 12 | if (callBack === undefined) return response; 13 | else { 14 | return response 15 | .blob() 16 | .then((blob) => blob2dom(blob)) 17 | .then((res) => callBack(res)); 18 | } 19 | }) 20 | .then((res) => { 21 | if (!!res || res.ok) { 22 | log("sendRequest", `请求完成: ${reportURL}`, "success"); 23 | } else { 24 | log("sendRequest", `请求出错: ${reportURL}`, "error"); 25 | } 26 | return res; 27 | }) 28 | .catch((err) => 29 | ElNotification({ 30 | title: "免Flash文件上传", 31 | type: "error", 32 | message: `请求出错:${err},请重新尝试或点此联系开发者解决。`, 33 | duration: 0, 34 | onClick: function () { 35 | window.open(config.supportURL); 36 | }, 37 | }) 38 | ); 39 | } 40 | 41 | async function blob2dom(blob: Blob) { 42 | return new Promise((res, rej) => { 43 | let reader = new FileReader(); 44 | reader.readAsText(blob, "GBK"); 45 | reader.onload = (e) => { 46 | const text = e?.target?.result as string; 47 | const dom = new window.DOMParser().parseFromString(text, "text/html"); 48 | res(dom); 49 | }; 50 | reader.onerror = rej; 51 | }); 52 | } 53 | 54 | export default sendRequest; 55 | -------------------------------------------------------------------------------- /src/request/API.ts: -------------------------------------------------------------------------------- 1 | import getUserInfo from "./Requests/getUserInfo"; 2 | import getRemindInfo from "./Requests/getRemindInfo"; 3 | import getHwtList from "./Requests/getHwtList"; 4 | import getHwtSubmitNew from "./Requests/getHwtSubmitNew"; 5 | import getHwtReviewNew from "./Requests/getHwtReviewNew"; 6 | import getHwtReviewDetails from "./Requests/getHwtReviewDetails"; 7 | import getHwtSubmitOld from "./Requests/getHwtSubmitOld"; 8 | import getHwtReviewOld from "./Requests/getHwtReviewOld"; 9 | import getLessonList from "./Requests/getLessonList"; 10 | import getNotifyList from "./Requests/getNotifyList"; 11 | import getInformList from "./Requests/getInformList"; 12 | import lessonOrderOperation from "./Requests/lessonOrderOperation"; 13 | import getLoginStatus from "./Requests/getLoginStatus"; 14 | import getLessonTopList from "./Requests/getLessonTopList"; 15 | import getScriptNotify from "./Requests/getScriptNotify"; 16 | import getLastestScriptNotify from "./Requests/getLastestScriptNotify"; 17 | import getScriptUpdateLog from "./Requests/getScriptUpdateLog"; 18 | 19 | export default { 20 | getUserInfo: getUserInfo, 21 | getRemindInfo: getRemindInfo, 22 | getHwtList: getHwtList, 23 | getHwtSubmitNew: getHwtSubmitNew, 24 | getHwtReviewNew: getHwtReviewNew, 25 | getHwtReviewDetails: getHwtReviewDetails, 26 | getHwtSubmitOld: getHwtSubmitOld, 27 | getHwtReviewOld: getHwtReviewOld, 28 | getLessonList: getLessonList, 29 | getNotifyList: getNotifyList, 30 | getInformList: getInformList, 31 | lessonOrderOperation: lessonOrderOperation, 32 | getLoginStatus: getLoginStatus, 33 | getLessonTopList: getLessonTopList, 34 | getScriptNotify: getScriptNotify, 35 | getLastestScriptNotify: getLastestScriptNotify, 36 | getScriptUpdateLog: getScriptUpdateLog, 37 | }; 38 | -------------------------------------------------------------------------------- /src/components/Profile/ProfileIndex.vue: -------------------------------------------------------------------------------- 1 | 18 | 19 | 53 | 54 | 61 | -------------------------------------------------------------------------------- /src/hooks/useHwtFormatter.ts: -------------------------------------------------------------------------------- 1 | import localCache from "@/utils/localCache"; 2 | 3 | const formatterRemain = ({ remainFloat, remain, date }: any) => { 4 | const dDate = new Date(date).getDate(); 5 | const cDate = new Date().getDate(); 6 | if (remainFloat < 0) { 7 | return `已过期${Math.abs(remain)}天`; 8 | } else if (remainFloat >= 0 && remainFloat < 1) { 9 | if (dDate > cDate) { 10 | return "还有1天截止"; // <24h 但是截止日期在明天 11 | } else { 12 | return `今日截止`; 13 | } 14 | } else { 15 | return `还有${remain}天截止`; 16 | } 17 | }; 18 | const formatterAnswerStatus = ({ able, answerStatus }: any) => { 19 | // 挺复杂的 😅 详细注释在 getHwtList.ts 中 20 | if (able === false && answerStatus === false) { 21 | return { text: "已提交", tag: "success" }; 22 | } else if (able === false && answerStatus === undefined) { 23 | return { text: "未提交", tag: "warning" }; 24 | } else if (able === true && answerStatus === true) { 25 | return { text: "已提交", tag: "success" }; 26 | } else if (able === true && answerStatus === undefined) { 27 | return { text: "未提交", tag: "warning" }; 28 | } else if (able === true && answerStatus === false) { 29 | return { text: "已提交", tag: "success" }; 30 | } 31 | }; 32 | const formatterCourseId = ({ courseId }: any) => { 33 | const lessonList = localCache.getCache("lesson-list"); 34 | return lessonList?.find((lesson: any) => lesson.id === courseId).name; 35 | }; 36 | const formatterMark = ({ mark }: any) => { 37 | return mark !== undefined 38 | ? { text: mark, tag: "" } 39 | : { text: "未批阅", tag: "info" }; 40 | }; 41 | const formatterHandler = ({ able }: any) => { 42 | return able ? "交作业" : "看作业"; 43 | }; 44 | 45 | export function useHwtFormatter() { 46 | return { 47 | formatterRemain, 48 | formatterAnswerStatus, 49 | formatterCourseId, 50 | formatterMark, 51 | formatterHandler, 52 | }; 53 | } 54 | -------------------------------------------------------------------------------- /src/components/WelcomePage/NotifyList.vue: -------------------------------------------------------------------------------- 1 | 25 | 26 | 31 | 32 | 62 | -------------------------------------------------------------------------------- /src/request/Requests/getHwtList.ts: -------------------------------------------------------------------------------- 1 | import sendRequest from "../SendRequest"; 2 | 3 | interface json { 4 | datas: { 5 | courseId: string; 6 | hwtList: []; 7 | }; 8 | } 9 | 10 | export default async function getHwtList(courseId: string) { 11 | let url = `http://cc.bjtu.edu.cn:81/meol/hw/stu/hwStuHwtList.do?courseId=${courseId}&pagingNumberPer=100`; 12 | return await sendRequest(url, undefined).then((response: any) => { 13 | return response.json().then((json: json) => { 14 | let rtnArray: object[] = []; 15 | if (json.datas.hwtList === undefined) return []; 16 | json.datas.hwtList.forEach((item: any) => { 17 | /* 18 | 只能提交一次的作业 19 | 未过期 20 | 已提交 able: false answerStatus: false 21 | 未提交 able: true answerStatus: undefined 22 | 已过期 23 | 已提交 24 | 未提交 able: false answerStatus: undefined 25 | 允许重复提交的作业 26 | 未过期 27 | 已提交 able: true answerStatus: true 28 | 未提交 able: true answerStatus: undefined 29 | 已过期 30 | 已提交 able: false answerStatus: false 31 | 未提交 able: false answerStatus: undefined 32 | */ 33 | rtnArray.push({ 34 | courseId: json.datas.courseId, 35 | id: item.id.toString(), 36 | title: item.title, 37 | deadLine: item.deadLine, 38 | remainFloat: 39 | (new Date(item.deadLine).getTime() - new Date().getTime()) / 40 | (24 * 60 * 60 * 1000), 41 | remain: Math.floor( 42 | (new Date(item.deadLine).getTime() - new Date().getTime()) / 43 | (24 * 60 * 60 * 1000) 44 | ), 45 | able: item.submitStruts, // 是否允许提交 46 | mark: item.mark, 47 | publisher: item.realName, 48 | mutualTask: item.mutualTask, 49 | answerStatus: item.answerStatus, // 是否已经提交 50 | showGrade: item.showGrade, // answerStatus == true || able == false 51 | }); 52 | }); 53 | return rtnArray; 54 | }); 55 | }); 56 | } 57 | -------------------------------------------------------------------------------- /src/components/Common/SideBar.vue: -------------------------------------------------------------------------------- 1 | 33 | 34 | 60 | 61 | 80 | -------------------------------------------------------------------------------- /src/utils/XHR.ts: -------------------------------------------------------------------------------- 1 | import { ElNotification } from "element-plus"; 2 | import log from "../hooks/useLog"; 3 | import config from "../hooks/Config/Config"; 4 | declare function GM_xmlhttpRequest(param: any): any; 5 | 6 | export default function XHR(XHROptions: any) { 7 | return new Promise((resolve) => { 8 | const onerror = (error: any) => { 9 | log("XHR", error, error); 10 | ElNotification({ 11 | title: "免Flash文件上传", 12 | type: "error", 13 | message: `请求出错:${error},请重新尝试或点此联系开发者解决。`, 14 | duration: 0, 15 | onClick: function () { 16 | window.open(config.supportURL); 17 | }, 18 | }); 19 | resolve(undefined); 20 | }; 21 | if (XHROptions.GM) { 22 | if (XHROptions.method === "POST") { 23 | if (XHROptions.headers === undefined) XHROptions.headers = {}; 24 | if (XHROptions.headers["Content-Type"] === undefined) 25 | XHROptions.headers["Content-Type"] = 26 | "application/x-www-form-urlencoded; charset=utf-8"; 27 | } 28 | XHROptions.timeout = 30 * 1000; 29 | XHROptions.onload = (res: any) => 30 | resolve({ response: res, body: res.response }); 31 | XHROptions.onerror = onerror; 32 | XHROptions.ontimeout = onerror; 33 | GM_xmlhttpRequest(XHROptions); 34 | } else { 35 | const xhr = new XMLHttpRequest(); 36 | xhr.open(XHROptions.method, XHROptions.url); 37 | if ( 38 | XHROptions.method === "POST" && 39 | xhr.getResponseHeader("Content-Type") === null 40 | ) 41 | xhr.setRequestHeader( 42 | "Content-Type", 43 | "application/x-www-form-urlencoded; charset=utf-8" 44 | ); 45 | if (XHROptions.cookie) xhr.withCredentials = true; 46 | if (XHROptions.responseType !== undefined) 47 | xhr.responseType = XHROptions.responseType; 48 | xhr.timeout = 30 * 1000; 49 | xhr.onload = (ev) => { 50 | const res = ev.target as any; 51 | resolve({ response: res, body: res.response }); 52 | }; 53 | xhr.onerror = onerror; 54 | xhr.ontimeout = onerror; 55 | xhr.send(XHROptions.data); 56 | } 57 | }); 58 | } 59 | -------------------------------------------------------------------------------- /src/components/WelcomePage/LessonList.vue: -------------------------------------------------------------------------------- 1 | 39 | 40 | 45 | 46 | 72 | -------------------------------------------------------------------------------- /src/base-ui/table/src/zu-table.vue: -------------------------------------------------------------------------------- 1 | 41 | 42 | 85 | -------------------------------------------------------------------------------- /src/hooks/usePageSort.ts: -------------------------------------------------------------------------------- 1 | import HwtList from "@/components/WelcomePage/HwtList.vue"; 2 | import InformList from "@/components/WelcomePage/InformList.vue"; 3 | import NotifyList from "@/components/WelcomePage/NotifyList.vue"; 4 | import LessonList from "@/components/WelcomePage/LessonList.vue"; 5 | import LessonTopList from "@/components/WelcomePage/LessonTopList.vue"; 6 | 7 | import { ref, onMounted } from "vue"; 8 | import localCache from "@/utils/localCache"; 9 | import Sortable from "sortablejs"; 10 | 11 | import type { TCpnConfig, TPlainConfig } from "@/hooks/types"; 12 | 13 | const cpnMap = { 14 | HwtList, 15 | InformList, 16 | NotifyList, 17 | LessonList, 18 | LessonTopList, 19 | }; 20 | 21 | export default function usePageSort( 22 | plainConfig: TPlainConfig, 23 | { selector, otherOptions }: any 24 | ) { 25 | const localCpnCache = localCache.getCache("cpn-cache"); 26 | const components = localCpnCache 27 | ? toCpnConfig(localCpnCache) 28 | : toCpnConfig(plainConfig); 29 | const cpnCacheRef = ref(components); 30 | onMounted(() => { 31 | var el = document.querySelector(selector); 32 | var sortable = new Sortable(el, { 33 | ...otherOptions, 34 | animation: 150, 35 | onEnd: function ({ oldIndex, newIndex }: any) { 36 | // replace position: oldIndex <=> newIndex 37 | [cpnCacheRef.value[oldIndex], cpnCacheRef.value[newIndex]] = [ 38 | cpnCacheRef.value[newIndex], 39 | cpnCacheRef.value[oldIndex], 40 | ]; 41 | localCache.setCache("cpn-cache", toPlainConfig(cpnCacheRef.value)); 42 | }, 43 | }); 44 | }); 45 | return [components]; 46 | } 47 | 48 | export function toCpnConfig(plainConfig: TPlainConfig): TCpnConfig { 49 | const cpnConfig: TCpnConfig = []; 50 | for (const c of plainConfig) { 51 | for (const [key, cpn] of Object.entries(cpnMap)) { 52 | if (c.cpn === key) { 53 | cpnConfig.push({ 54 | cpn, 55 | span: c.span, 56 | }); 57 | } 58 | } 59 | } 60 | return cpnConfig; 61 | } 62 | 63 | export function toPlainConfig(cpnConfig: TCpnConfig): TPlainConfig { 64 | const plainConfig: TPlainConfig = []; 65 | for (const c of cpnConfig) { 66 | for (const [key, cpn] of Object.entries(cpnMap)) { 67 | if (c.cpn.name === key) { 68 | plainConfig.push({ 69 | cpn: key, 70 | span: c.span, 71 | }); 72 | } 73 | } 74 | } 75 | return plainConfig; 76 | } 77 | -------------------------------------------------------------------------------- /src/hooks/Config/Config.ts: -------------------------------------------------------------------------------- 1 | import { version, repository, description } from "../../../package.json" 2 | import metaData from '../../../config/metadata' 3 | const giteeURL = `https://gitee.com/ziuc/NO-FLASH-Upload`; 4 | const supportURL = `https://support.qq.com/products/395800`; 5 | const donateURL = `https://gitee.com/ZiuChen/NO-FLASH-Upload/raw/master/doc/img/Buy%20me%20a%20coffee.png`; 6 | const qGroupURL = `https://qm.qq.com/cgi-bin/qm/qr?k=9qfHKTaQuWqYN1ys1yiQPdJ4iIlHwgL5&jump_from=webapi`; 7 | const updateURL = metaData.updateURL; 8 | const updateInfo = `https://gitee.com/ZiuChen/NO-FLASH-Upload/raw/master/package.json`; 9 | const updateLOG = `https://gitee.com/ZiuChen/NO-FLASH-Upload/raw/master/doc/update_log.md`; 10 | const notifyURL = `https://gitee.com/ZiuChen/NO-FLASH-Upload/raw/master/src/notify.json`; 11 | 12 | const userConfig = { 13 | "data-last-read-notify": { 14 | value: 0, 15 | default: 0, 16 | name: "最近阅读的通知", 17 | id: "data-last-read-notify", 18 | type: "input", 19 | }, 20 | "config-back-to-old": { 21 | value: false, 22 | default: false, 23 | name: "回到旧版", 24 | id: "config-back-to-old", 25 | type: "switch", 26 | }, 27 | "config-console-log-show": { 28 | value: true, 29 | default: true, 30 | name: "控制台日志输出", 31 | id: "config-console-log-show", 32 | type: "switch", 33 | }, 34 | "config-post-interval": { 35 | value: true, 36 | default: true, 37 | name: "定期发送请求以保持登录状态", 38 | id: "config-post-interval", 39 | type: "switch", 40 | }, 41 | "config-hide-update-notify": { 42 | value: false, 43 | default: false, 44 | name: "隐藏检查更新提示", 45 | id: "config-hide-update-notify", 46 | type: "switch", 47 | }, 48 | "config-hwt-submit-drag": { 49 | value: true, 50 | default: true, 51 | name: "拖拽上传附件功能", 52 | id: "config-hwt-submit-drag", 53 | type: "switch", 54 | }, 55 | "config-hwt-default-expand": { 56 | value: false, 57 | default: false, 58 | name: "自动扩展作业卡片宽度", 59 | id: "config-hwt-default-expand", 60 | type: "switch", 61 | }, 62 | }; 63 | 64 | export default { 65 | version: version, 66 | githubURL: repository.url, 67 | giteeURL: giteeURL, 68 | supportURL: supportURL, 69 | donateURL: donateURL, 70 | qGroupURL: qGroupURL, 71 | description: description, 72 | updateURL: updateURL, 73 | updateInfo: updateInfo, 74 | updateLOG: updateLOG, 75 | notifyURL: notifyURL, 76 | userConfig: userConfig, 77 | }; 78 | -------------------------------------------------------------------------------- /doc/update_log_old.md: -------------------------------------------------------------------------------- 1 | ### 更新日志 2 | 3 |

V1.4.1:

4 | 7 |

V1.4.0:

8 | 12 |

V1.3.9:

13 | 17 |

V1.3.8:

18 | 24 |

V1.3.7:

25 | 31 |

V1.3.6:

32 | 37 |

V1.3.5:

38 | 42 |

V1.3.4:

43 | 48 |

V1.3.3:

49 | 52 |

V1.3.2:

53 | 58 |

V1.3.1:

59 | 63 |

V1.3:

64 | 72 |

V1.2.2:

73 | 78 |

V1.2.1:

79 | 82 |

V1.2:

83 | 89 |

V1.1:

90 | 96 |

V1.0:

97 | 108 |

V0.1:

109 | -------------------------------------------------------------------------------- /src/components/WelcomePage/config/hwtlist.table.config.ts: -------------------------------------------------------------------------------- 1 | import type { ITableConfig } from "@/base-ui/table/types"; 2 | 3 | export const TableConfig: ITableConfig = { 4 | title: "作业列表", 5 | propList: [ 6 | { 7 | prop: "remain", 8 | label: "剩余时间", 9 | minWidth: "100", 10 | align: "center", 11 | slotName: "remain", 12 | sortable: true, 13 | filters: [ 14 | { 15 | text: "近期截止", 16 | value: 1, 17 | }, 18 | { 19 | text: "未过期", 20 | value: 0, 21 | }, 22 | { 23 | text: "已过期", 24 | value: -1, 25 | }, 26 | ], 27 | "filtered-value": [1], 28 | "filter-method": (value, row) => { 29 | switch (value) { 30 | case 1: 31 | return row.remain <= 15 && row.remain >= -2; 32 | case 0: 33 | return row.remain >= 0; 34 | case -1: 35 | return row.remain < 0; 36 | } 37 | }, 38 | }, 39 | { 40 | prop: "title", 41 | label: "作业标题", 42 | minWidth: "200", 43 | align: "center", 44 | slotName: "title", 45 | }, 46 | { 47 | prop: "answerStatus", 48 | label: "提交状态", 49 | minWidth: "80", 50 | align: "center", 51 | slotName: "answerStatus", 52 | }, 53 | { 54 | prop: "courseId", 55 | label: "课程名", 56 | minWidth: "150", 57 | align: "center", 58 | slotName: "courseId", 59 | sortable: true, 60 | }, 61 | { 62 | prop: "deadLine", 63 | label: "截止日期", 64 | minWidth: "150", 65 | align: "center", 66 | }, 67 | { 68 | prop: "mark", 69 | label: "取得分数", 70 | minWidth: "80", 71 | align: "center", 72 | slotName: "mark", 73 | sortable: true, 74 | }, 75 | { 76 | label: "操作", 77 | minWidth: "80", 78 | align: "center", 79 | slotName: "handler", 80 | }, 81 | ], 82 | defaultSort: { 83 | prop: "remain", 84 | order: "ascending", 85 | }, 86 | rowClassName: ({ row }) => { 87 | if (row.able === false) { 88 | return "info-row"; 89 | } else { 90 | if (row.remain <= 3 && row.remain > 0) { 91 | return "warning-row"; 92 | } else if (row.remain === 0) { 93 | return "danger-row"; 94 | } else { 95 | return "success-row"; 96 | } 97 | } 98 | }, 99 | showDragHandler: true, 100 | }; 101 | -------------------------------------------------------------------------------- /src/base-ui/form/src/zu-form.vue: -------------------------------------------------------------------------------- 1 | 58 | 59 | 97 | 98 | 103 | -------------------------------------------------------------------------------- /src/components/Lesson/LessonSubmit/HwtInfo.vue: -------------------------------------------------------------------------------- 1 | 59 | 60 | 92 | 93 | 103 | -------------------------------------------------------------------------------- /auto-imports.d.ts: -------------------------------------------------------------------------------- 1 | // Generated by 'unplugin-auto-import' 2 | export {} 3 | declare global { 4 | const EffectScope: typeof import('vue')['EffectScope'] 5 | const computed: typeof import('vue')['computed'] 6 | const createApp: typeof import('vue')['createApp'] 7 | const customRef: typeof import('vue')['customRef'] 8 | const defineAsyncComponent: typeof import('vue')['defineAsyncComponent'] 9 | const defineComponent: typeof import('vue')['defineComponent'] 10 | const effectScope: typeof import('vue')['effectScope'] 11 | const getCurrentInstance: typeof import('vue')['getCurrentInstance'] 12 | const getCurrentScope: typeof import('vue')['getCurrentScope'] 13 | const h: typeof import('vue')['h'] 14 | const inject: typeof import('vue')['inject'] 15 | const isProxy: typeof import('vue')['isProxy'] 16 | const isReactive: typeof import('vue')['isReactive'] 17 | const isReadonly: typeof import('vue')['isReadonly'] 18 | const isRef: typeof import('vue')['isRef'] 19 | const markRaw: typeof import('vue')['markRaw'] 20 | const nextTick: typeof import('vue')['nextTick'] 21 | const onActivated: typeof import('vue')['onActivated'] 22 | const onBeforeMount: typeof import('vue')['onBeforeMount'] 23 | const onBeforeRouteLeave: typeof import('vue-router')['onBeforeRouteLeave'] 24 | const onBeforeRouteUpdate: typeof import('vue-router')['onBeforeRouteUpdate'] 25 | const onBeforeUnmount: typeof import('vue')['onBeforeUnmount'] 26 | const onBeforeUpdate: typeof import('vue')['onBeforeUpdate'] 27 | const onDeactivated: typeof import('vue')['onDeactivated'] 28 | const onErrorCaptured: typeof import('vue')['onErrorCaptured'] 29 | const onMounted: typeof import('vue')['onMounted'] 30 | const onRenderTracked: typeof import('vue')['onRenderTracked'] 31 | const onRenderTriggered: typeof import('vue')['onRenderTriggered'] 32 | const onScopeDispose: typeof import('vue')['onScopeDispose'] 33 | const onServerPrefetch: typeof import('vue')['onServerPrefetch'] 34 | const onUnmounted: typeof import('vue')['onUnmounted'] 35 | const onUpdated: typeof import('vue')['onUpdated'] 36 | const provide: typeof import('vue')['provide'] 37 | const reactive: typeof import('vue')['reactive'] 38 | const readonly: typeof import('vue')['readonly'] 39 | const ref: typeof import('vue')['ref'] 40 | const resolveComponent: typeof import('vue')['resolveComponent'] 41 | const resolveDirective: typeof import('vue')['resolveDirective'] 42 | const shallowReactive: typeof import('vue')['shallowReactive'] 43 | const shallowReadonly: typeof import('vue')['shallowReadonly'] 44 | const shallowRef: typeof import('vue')['shallowRef'] 45 | const toRaw: typeof import('vue')['toRaw'] 46 | const toRef: typeof import('vue')['toRef'] 47 | const toRefs: typeof import('vue')['toRefs'] 48 | const triggerRef: typeof import('vue')['triggerRef'] 49 | const unref: typeof import('vue')['unref'] 50 | const useAttrs: typeof import('vue')['useAttrs'] 51 | const useCssModule: typeof import('vue')['useCssModule'] 52 | const useCssVars: typeof import('vue')['useCssVars'] 53 | const useLink: typeof import('vue-router')['useLink'] 54 | const useRoute: typeof import('vue-router')['useRoute'] 55 | const useRouter: typeof import('vue-router')['useRouter'] 56 | const useSlots: typeof import('vue')['useSlots'] 57 | const watch: typeof import('vue')['watch'] 58 | const watchEffect: typeof import('vue')['watchEffect'] 59 | const watchPostEffect: typeof import('vue')['watchPostEffect'] 60 | const watchSyncEffect: typeof import('vue')['watchSyncEffect'] 61 | } 62 | -------------------------------------------------------------------------------- /src/components/WelcomePage/HwtList.vue: -------------------------------------------------------------------------------- 1 | 39 | 40 | 45 | 46 | 102 | 103 | 117 | -------------------------------------------------------------------------------- /components.d.ts: -------------------------------------------------------------------------------- 1 | // generated by unplugin-vue-components 2 | // We suggest you to commit this file into source control 3 | // Read more: https://github.com/vuejs/core/pull/3399 4 | import '@vue/runtime-core' 5 | 6 | export {} 7 | 8 | declare module '@vue/runtime-core' { 9 | export interface GlobalComponents { 10 | AboutIndex: typeof import('./src/components/About/AboutIndex.vue')['default'] 11 | App: typeof import('./src/components/App.vue')['default'] 12 | DonateCard: typeof import('./src/components/About/DonateCard.vue')['default'] 13 | ElAside: typeof import('element-plus/es')['ElAside'] 14 | ElBadge: typeof import('element-plus/es')['ElBadge'] 15 | ElButton: typeof import('element-plus/es')['ElButton'] 16 | ElButtonGroup: typeof import('element-plus/es')['ElButtonGroup'] 17 | ElCard: typeof import('element-plus/es')['ElCard'] 18 | ElCol: typeof import('element-plus/es')['ElCol'] 19 | ElContainer: typeof import('element-plus/es')['ElContainer'] 20 | ElDescriptions: typeof import('element-plus/es')['ElDescriptions'] 21 | ElDescriptionsItem: typeof import('element-plus/es')['ElDescriptionsItem'] 22 | ElDropdown: typeof import('element-plus/es')['ElDropdown'] 23 | ElDropdownItem: typeof import('element-plus/es')['ElDropdownItem'] 24 | ElDropdownMenu: typeof import('element-plus/es')['ElDropdownMenu'] 25 | ElForm: typeof import('element-plus/es')['ElForm'] 26 | ElFormItem: typeof import('element-plus/es')['ElFormItem'] 27 | ElHeader: typeof import('element-plus/es')['ElHeader'] 28 | ElIcon: typeof import('element-plus/es')['ElIcon'] 29 | ElInput: typeof import('element-plus/es')['ElInput'] 30 | ElLink: typeof import('element-plus/es')['ElLink'] 31 | ElMain: typeof import('element-plus/es')['ElMain'] 32 | ElMenu: typeof import('element-plus/es')['ElMenu'] 33 | ElMenuItem: typeof import('element-plus/es')['ElMenuItem'] 34 | ElOption: typeof import('element-plus/es')['ElOption'] 35 | ElRow: typeof import('element-plus/es')['ElRow'] 36 | ElScrollbar: typeof import('element-plus/es')['ElScrollbar'] 37 | ElSelect: typeof import('element-plus/es')['ElSelect'] 38 | ElSwitch: typeof import('element-plus/es')['ElSwitch'] 39 | ElTable: typeof import('element-plus/es')['ElTable'] 40 | ElTableColumn: typeof import('element-plus/es')['ElTableColumn'] 41 | ElTag: typeof import('element-plus/es')['ElTag'] 42 | ElUpload: typeof import('element-plus/es')['ElUpload'] 43 | HeadBar: typeof import('./src/components/Common/HeadBar.vue')['default'] 44 | HwtEditor: typeof import('./src/components/Lesson/LessonSubmit/HwtEditor.vue')['default'] 45 | HwtInfo: typeof import('./src/components/Lesson/LessonSubmit/HwtInfo.vue')['default'] 46 | HwtList: typeof import('./src/components/WelcomePage/HwtList.vue')['default'] 47 | InformList: typeof import('./src/components/WelcomePage/InformList.vue')['default'] 48 | LessonIndex: typeof import('./src/components/Lesson/LessonIndex.vue')['default'] 49 | LessonList: typeof import('./src/components/WelcomePage/LessonList.vue')['default'] 50 | LessonSubmit: typeof import('./src/components/Lesson/LessonSubmit/LessonSubmit.vue')['default'] 51 | LessonTopList: typeof import('./src/components/WelcomePage/LessonTopList.vue')['default'] 52 | NotifyList: typeof import('./src/components/WelcomePage/NotifyList.vue')['default'] 53 | ProfileIndex: typeof import('./src/components/Profile/ProfileIndex.vue')['default'] 54 | RouterLink: typeof import('vue-router')['RouterLink'] 55 | RouterView: typeof import('vue-router')['RouterView'] 56 | ScriptInfoCard: typeof import('./src/components/About/ScriptInfoCard.vue')['default'] 57 | SideBar: typeof import('./src/components/Common/SideBar.vue')['default'] 58 | UpdateLogCard: typeof import('./src/components/About/UpdateLogCard.vue')['default'] 59 | WelcomeIndex: typeof import('./src/components/WelcomePage/WelcomeIndex.vue')['default'] 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |

免Flash文件上传

5 |
6 | 北京交通大学课程平台功能增强脚本,实现信息聚合,附件上传,让你高效处理课程信息。 7 |
8 |
9 | 10 | 11 | 12 | 13 | 14 |
15 |
16 |
17 | 18 | ## 🚀 初衷 19 | 20 | 由于课程平台使用的`THEOL清华教育在线`系统版本过旧,其基于`Flash`的附件上传功能不再在主流浏览器上被支持,故编写此脚本以实现信息汇总、附件上传等功能。 21 | 22 | **课程平台更新后,脚本升级为`2.0`,提供了更多更方便的功能。** 23 | 24 | 本脚本于 2021 年 9 月 7 日发布,已在[_Greasy Fork_](https://greasyfork.org/zh-CN/scripts/432056)被安装千余次,日检查更新次数`~400`。 25 | 26 | ## 🔰 开始使用 27 | 28 | 1. 在任一浏览器上安装 [`Tampermonkey`](https://www.tampermonkey.net/) 脚本管理器。 29 | 2. [`点击此处`](https://fastly.jsdelivr.net/gh/ZiuChen/NO-FLASH-Upload@master/publish/index.prod.user.js) 安装脚本。 30 | 31 | | 浏览器版本 | 脚本管理器及其版本 | 脚本版本 | 可用性 | 32 | | ----------- | --------------------- | -------- | ------ | 33 | | **Chrome** | `Tampermonkey 4.16.1` | `2.3.2` | ✔ | 34 | | **Edge** | `Tampermonkey 4.16.1` | `2.3.2` | ✔ | 35 | | **FireFox** | `Tampermonkey 4.16.1` | `2.3.2` | ✔ | 36 | 37 | 新版具有更好的稳定性,**请尽量保持脚本更新,使用最新版本**。使用中遇到任何问题,欢迎在[`Github Issues`](https://github.com/ZiuChen/NO-FLASH-Upload/issues)或[`腾讯兔小巢`](https://support.qq.com/products/395800)反馈交流。 38 | 39 | ## 📸 效果展示 40 | 41 | homepage 42 | 43 | submissionpage 44 | 45 | ## 🏗️ 参与开发 46 | 47 | 1. `Fork`[本仓库](https://github.com/ZiuChen/NO-FLASH-Upload)并`clone`到本地。 48 | 2. 使用`npm i`安装依赖,使用`npm run dev`执行自动编译。 49 | 3. 在`Tampermonkey`脚本管理器中新建脚本,将`dist`目录下生成的`index.dev.user.js`复制到其中并保存,开始开发。 50 | 4. 开发完毕,整理代码,提交 Pr。 51 | 52 | ### 📁 目录说明 53 | 54 | - `config`:`Webpack`配置文件(分为基本配置、开发环境配置、生产环境配置)、脚本元信息 55 | - `dist`:执行打包命令后,脚本输出目录 56 | - `doc`:存放文档的目录 57 | - `publish`:版本更新源,存放发布脚本的目录 58 | - `src`:源文件目录 59 | 60 | - `base-ui`:存放基础组件的目录 61 | - `components`:存放`Vue`组件的目录 62 | - `global`:存放页面预处理、全局注册等逻辑的目录 63 | - `hooks`:存放公共逻辑的目录 64 | - `request`:存放网络请求的目录 65 | - `route`:存放`Vue Router`路由的目录 66 | - `style`:存放样式文件的目录 67 | - `index.ts`:脚本的入口文件 68 | 69 | **注意:** 目前项目有两个分支:旧的`script`分支与新的`Vue`分支,分别对应`1.x`版本与`2.x`版本,`1.x`版本由于平台更新,已基本不可用,不再维护。 70 | 71 | ## ❤️ 感谢捐赠 72 | 73 | **如果觉得脚本帮到了你,可以通过[捐赠二维码](https://fastly.jsdelivr.net/gh/ZiuChen/NO-FLASH-Upload@master/doc/img/Buy%20me%20a%20coffee.png)赞赏我,这将鼓励我继续维护这个脚本。** 74 | 75 | [【定期更新】感谢捐赠名单 ❤](https://docs.qq.com/sheet/DRWFjSFlKWFplSldi) 76 | 77 | 捐赠二维码 78 | 79 | ## 🧱 第三方开源组件 80 | 81 | 感谢这些组件帮助我极大地提升了开发效率: 82 | 83 | - [Vue](https://cn.vuejs.org/) 84 | - [Element Plus](http://element-plus.gitee.io/) 85 | - [wangEditor](https://www.wangeditor.com/) 86 | - [MDUI](https://www.mdui.org/) 87 | 88 | ## 📎 相关链接 89 | 90 | [`兔小巢反馈平台`](https://support.qq.com/products/395800) 91 | [`反馈交流QQ群:769115389`](https://qm.qq.com/cgi-bin/qm/qr?k=9qfHKTaQuWqYN1ys1yiQPdJ4iIlHwgL5&jump_from=webapi) 92 | [`更新计划`](https://github.com/ZiuChen/NO-FLASH-Upload/projects/2) 93 | [`更新日志`](doc/update_log.md) 94 | [`v1.x脚本介绍`](doc/usage_old.md) 95 | [`v1.x更新日志`](doc/update_log_old.md) 96 | -------------------------------------------------------------------------------- /src/hooks/Config/ConfigOperations.ts: -------------------------------------------------------------------------------- 1 | import { ElMessage } from "element-plus"; 2 | import config from "./Config"; 3 | import log from "../useLog"; 4 | import localCache from "@/utils/localCache"; 5 | 6 | function initConfig() { 7 | if (readConfig() === null) { 8 | localCache.setCache("config", config); 9 | return; 10 | } 11 | initScriptConfig(); 12 | initUserConfig(); 13 | log("initConfig", "设置初始化完毕", "success"); 14 | } 15 | 16 | function initScriptConfig() { 17 | // 不移除已有项,只添加新项或赋初值 18 | let defaultScriptConfig = config as any; 19 | let currentScriptConfig = readConfig(); 20 | let defaultKeys = Object.keys(defaultScriptConfig); 21 | defaultKeys.forEach((key) => { 22 | if (key === "userConfig") return; 23 | currentScriptConfig[key] = defaultScriptConfig[key]; 24 | }); 25 | updateConfig(currentScriptConfig); 26 | } 27 | 28 | function initUserConfig() { 29 | // 只检查新增或移除项并执行添加或删除,不改变项值 30 | let currentConfig = readConfig(); 31 | let currentUserConfig = currentConfig.userConfig; 32 | let currentUserConfigs = Object.getOwnPropertyNames(currentUserConfig); 33 | let defaultUserConfigs = Object.getOwnPropertyNames(config.userConfig); 34 | let removedConfigs = currentUserConfigs.filter((item) => { 35 | return defaultUserConfigs.indexOf(item) === -1; 36 | }); 37 | let newConfigs = defaultUserConfigs.filter((item) => { 38 | return currentUserConfigs.indexOf(item) === -1; 39 | }); 40 | if (newConfigs.length === 0 && removedConfigs.length === 0) return; 41 | else { 42 | removedConfigs.forEach((item) => { 43 | delete currentUserConfig[item]; 44 | }); 45 | newConfigs.forEach((item: any) => { 46 | let Dconfig = config as any; 47 | currentUserConfig[item] = Dconfig.userConfig[item]; 48 | }); 49 | currentConfig.userConfig = currentUserConfig; 50 | updateConfig(currentConfig); 51 | } 52 | } 53 | 54 | function setUserConfig(id: string, value: boolean | object | string | number) { 55 | let currentConfig = readConfig(); 56 | currentConfig.userConfig[id].value = value; 57 | updateConfig(currentConfig); 58 | } 59 | 60 | function getDefaultConfig() { 61 | return config; 62 | } 63 | 64 | function getDefaultUserConfig() { 65 | return config.userConfig; 66 | } 67 | 68 | function readConfig() { 69 | return localCache.getCache("config"); 70 | } 71 | 72 | function readUserConfig() { 73 | return readConfig().userConfig; 74 | } 75 | 76 | function readUserConfigWithFilter(type: string) { 77 | let userConfig = readUserConfig(); 78 | let rtnArray: object[] = []; 79 | Object.keys(userConfig).forEach((key) => { 80 | if (userConfig[key].type === type) { 81 | rtnArray.push(userConfig[key]); 82 | } 83 | }); 84 | return rtnArray; 85 | } 86 | 87 | function updateConfig(config: object) { 88 | localCache.setCache("config", config); 89 | } 90 | 91 | function updateUserConfig(userConfig: object) { 92 | let currentConfig = readConfig(); 93 | currentConfig.userConfig = userConfig; 94 | localCache.setCache("config", currentConfig); 95 | } 96 | 97 | function restoreUserConfig() { 98 | updateUserConfig(getDefaultUserConfig()); 99 | log("restoreUserConfig", "用户设置已重置", "success"); 100 | } 101 | 102 | function exportUserConfig() { 103 | let blob = new Blob( 104 | [ 105 | JSON.stringify({ 106 | flag: "NOFLASHUPLOAD Setting", 107 | userConfig: readUserConfig(), 108 | }), 109 | ], 110 | { 111 | type: "text/json", 112 | } 113 | ); 114 | let a = document.createElement("a"); 115 | a.download = `[NOFLASHUPLOAD] setting.json`; 116 | a.href = window.URL.createObjectURL(blob); 117 | a.click(); 118 | log("exportUserConfig", "用户设置已导出", "success"); 119 | } 120 | 121 | function importUserConfig() { 122 | let input = document.createElement("input"); 123 | input.type = "file"; 124 | input.click(); 125 | input.addEventListener("change", (e: any) => { 126 | let reader = new FileReader(); 127 | reader.onload = (res) => { 128 | let result: any = res?.target?.result; 129 | if (result.indexOf(`NOFLASHUPLOAD Setting`) === -1) { 130 | log("importUserConfig", "校验错误,导入设置中止", "error"); 131 | ElMessage({ 132 | message: "校验出错,导入中止,请检查后重试", 133 | type: "error", 134 | }); 135 | return false; 136 | } else { 137 | updateUserConfig(JSON.parse(result).userConfig); 138 | log("importUserConfig", "成功导入设置", "success"); 139 | ElMessage({ 140 | message: "成功导入设置,即将自动刷新", 141 | type: "success", 142 | }); 143 | setTimeout("location.reload()", 2500); 144 | return true; 145 | } 146 | }; 147 | reader.readAsText(e.path[0].files[0], "utf-8"); 148 | }); 149 | } 150 | 151 | export default { 152 | initConfig: initConfig, 153 | readConfig: readConfig, 154 | readUserConfig: readUserConfig, 155 | readUserConfigWithFilter: readUserConfigWithFilter, 156 | updateConfig: updateConfig, 157 | setUserConfig: setUserConfig, 158 | restoreUserConfig: restoreUserConfig, 159 | exportUserConfig: exportUserConfig, 160 | importUserConfig: importUserConfig, 161 | }; 162 | -------------------------------------------------------------------------------- /src/components/About/ScriptInfoCard.vue: -------------------------------------------------------------------------------- 1 | 95 | 96 | 116 | 117 | 148 | -------------------------------------------------------------------------------- /src/components/Lesson/LessonSubmit/LessonSubmit.vue: -------------------------------------------------------------------------------- 1 | 53 | 54 | 192 | 193 | 203 | -------------------------------------------------------------------------------- /doc/update_log.md: -------------------------------------------------------------------------------- 1 | 2 |

3 | 2023年4月24日 4 | V2.4.0: 5 |

6 | 10 | 11 |

12 | 2022年11月26日 13 | V2.3.12: 14 |

15 | 18 | 19 |

20 | 2022年11月24日 21 | V2.3.11: 22 |

23 | 28 | 29 |

30 | 2022年11月14日 31 | V2.3.10: 32 |

33 | 36 | 37 |

38 | 2022年9月18日 39 | V2.3.9: 40 |

41 | 44 | 45 |

46 | 2022年9月16日 47 | V2.3.8: 48 |

49 | 55 | 56 |

57 | 2022年9月8日 58 | V2.3.4: 59 |

60 | 64 | 65 |

66 | 2022年7月3日 67 | V2.3.2: 68 |

69 | 72 | 73 |

74 | 2022年7月2日 75 | V2.3.1: 76 |

77 | 80 | 81 |

82 | 2022年6月30日 83 | V2.3.0: 84 |

85 | 91 | 92 |

93 | 2022年5月26日 94 | V2.2.14: 95 |

96 | 103 | 104 |

105 | 2022年5月22日 106 | V2.2.9: 107 |

108 | 112 | 113 |

114 | 2022年4月23日 115 | V2.2.7: 116 |

117 | 120 | 121 |

122 | 2022年4月23日 123 | V2.2.6: 124 |

125 | 133 | 134 |

135 | 2022年4月11日 136 | V2.2.0: 137 |

138 | 152 | 153 |

154 | 2022年4月3日 155 | V2.1.11: 156 |

157 | 160 | 161 |

162 | 2022年4月3日 163 | V2.1.10: 164 |

165 | 170 | 171 |

172 | 2022年3月26日 173 | V2.1.7: 174 |

175 | 178 | 179 |

180 | 2022年3月26日 181 | V2.1.6: 182 |

183 | 186 | 187 |

188 | 2022年3月24日 189 | V2.1.5: 190 |

191 | 195 | 196 |

197 | 2022年3月24日 198 | V2.1.3: 199 |

200 | 204 | 205 |

206 | 2022年3月23日 207 | V2.1.1: 208 |

209 | 223 | 224 |

225 | 2022年3月19日 226 | V2.0.20: 227 |

228 | 236 | 237 |

238 | 2022年3月17日 239 | V2.0.14: 240 |

241 | 257 | 258 |

259 | 2022年3月8日 260 | V2.0.0: 261 |

262 | 265 | -------------------------------------------------------------------------------- /src/components/Lesson/LessonSubmit/HwtEditor.vue: -------------------------------------------------------------------------------- 1 | 53 | 54 | 218 | 219 | 238 | --------------------------------------------------------------------------------