├── .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 |
2 |
3 |
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 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
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 |
--------------------------------------------------------------------------------
/src/components/WelcomePage/WelcomeIndex.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
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 |
2 |
3 |
4 |
7 |
8 |
9 | 【定期更新】感谢捐赠名单❤
15 |
16 |
17 |
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 |
2 |
3 |
4 |
5 |
11 |
12 |
13 |
14 |
15 |
16 |
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 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
26 |
27 |
49 |
--------------------------------------------------------------------------------
/src/components/WelcomePage/InformList.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 |
9 | {{ row.notifyName }}
16 |
17 |
18 |
19 |
20 |
21 |
26 |
27 |
42 |
--------------------------------------------------------------------------------
/src/components/About/UpdateLogCard.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | 更新日志
5 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
23 | 查看完整更新日志
24 |
25 |
26 |
27 |
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 |
2 |
3 |
9 |
10 |
16 |
17 |
18 |
19 |
20 |
21 |
26 |
27 |
41 |
42 |
49 |
--------------------------------------------------------------------------------
/src/components/Common/HeadBar.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
10 |
11 |
12 |
13 |
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 |
2 |
3 |
4 |
5 | 用户设置
6 |
7 |
8 |
9 |
10 |
11 | 导入设置
12 | 导出设置
13 | 重置设置
14 |
15 |
16 |
17 |
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 |
2 |
3 |
8 |
9 | {{ row.notifyName }}
16 |
17 |
18 | {{
19 | row.hadRead ? "已阅读" : "未阅读"
20 | }}
21 |
22 |
23 |
24 |
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 |
2 |
11 |
12 |
13 | 主页
14 |
15 |
16 |
17 | 个人设置
18 |
19 |
20 |
21 | 关于
22 |
23 |
31 |
32 |
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 |
2 |
3 |
10 |
11 |
17 | {{ row.name }}
18 |
19 |
20 |
21 |
22 |
27 |
28 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
45 |
46 |
72 |
--------------------------------------------------------------------------------
/src/base-ui/table/src/zu-table.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | {{ title }}
6 |
7 |
8 |
15 |
16 |
17 |
18 |
19 |
20 |
27 |
28 |
29 |
30 |
31 | {{ scope.row[item.prop] }}
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
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 |
9 | - 修改了上传区域的样式
10 | - TextArea为空时先清空再append
11 |
12 | V1.3.9:
13 |
14 | - 请求操作全部用异步重构,代码执行效率更高了
15 | - 优化了部分界面效果
16 |
17 | V1.3.8:
18 |
19 | - 代码同步Github
20 | - 增加批量下载资源功能
21 | - 增加自动检查更新功能
22 | - 引入PopNotify.js
23 |
24 | V1.3.7:
25 |
26 | - 优化作业上传界面效果
27 | - 增加“下载资源”标签
28 | - 主界面显示15天内的所有作业
29 | - 其他优化
30 |
31 | V1.3.6:
32 |
33 | - 修复作业列表截止日期显示错误的Bug
34 | - 作业列可以按照截止日期/课程排序了
35 | - 其他优化
36 |
37 | V1.3.5:
38 |
39 | - 修复作业列表被锁定的Bug
40 | - 其他优化
41 |
42 | V1.3.4:
43 |
44 | - 作业列表可以滑动显示所有作业信息了
45 | - 截止日期计算部分代码优化
46 | - 修复了部分浏览器Reminder()执行失败的BUG
47 |
48 | V1.3.3:
49 |
52 | V1.3.2:
53 |
54 | - 可以直接从主界面跳转到作业页面了
55 | - 主界面展示未提交作业的剩余时间
56 | - 其他优化
57 |
58 | V1.3.1:
59 |
60 | - 修复了某些情况下文件前图标显示错误的BUG
61 | - 其他优化
62 |
63 | V1.3:
64 |
65 | - 文件缓存区代码重写,增加点击图标删除缓存区文件功能;
66 | - 增加在主界面未提交作业显示为红的功能;
67 | - 优化文件名展示效果;
68 | - 优化缓存区展示效果;
69 | - 修复了缓存区文件名中
.无法正常显示的bug;
70 | - 其他优化。
71 |
72 | V1.2.2:
73 |
74 | - 增加点击文件复制链接到剪切板功能(可用于插入图片);
75 | - 针对不同文件显示不同图标,已适配部分常用文件类型;
76 | - 优化文件名显示效果;
77 |
78 | V1.2.1:
79 |
80 | - 修复了作业列表横线提示的显示异常
81 |
82 | V1.2:
83 |
84 | - 拖拽上传功能回归;
85 | - 作业列表信息显示增强(横线提示、单次提交、已经提交)
86 | - 优化了文件名过长时的显示效果;
87 | - 部分图标用数组变量统一管理,方便自定义;
88 |
89 | V1.1:
90 |
91 | - 修复了上传大于953MB文件报错的BUG;
92 | - 增加了上传大于1GB文件时的提醒;
93 | - 上传进程可以被“清空”按钮中断了,不影响已上传附件;
94 | - 点击上传后按钮隐藏,暂时性屏蔽事件监听还不会写,先鸽着
95 |
96 | V1.0:
97 |
98 | - 移除拖拽上传功能;
99 | - 修改界面效果,更加美观;
100 | - 增加上传进度条;
101 | - 增加上传速度;
102 | - 大文件自动触发上传剩余时间显示;
103 | - 支持清空文件缓存列表;
104 | - 上传成功的附件自动重命名;
105 | - 代码重构,可读性提高;
106 | - 修复Bug。
107 |
108 | V0.1:
109 |
110 | - 脚本发布,实现基础上传功能
111 | - 为了兼容课程平台原有代码,脚本引入了1.7.2版本的`jQuery`库,否则课程平台首页`“X课程有待提交作业”`将无法正常点击
112 |
--------------------------------------------------------------------------------
/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 |
2 |
3 |
6 |
7 |
8 |
9 |
10 |
16 |
17 |
23 |
24 |
25 |
32 | {{ option.title }}
38 |
39 |
40 |
41 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
56 |
57 |
58 |
59 |
97 |
98 |
103 |
--------------------------------------------------------------------------------
/src/components/Lesson/LessonSubmit/HwtInfo.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | {{
5 | propHwtContent.title
6 | }}
7 | {{
8 | propHwtContent.deadLine
9 | }}
10 | {{
11 | propHwtContent.fullMark
12 | }}
13 |
14 | {{ formatter.content }}
15 |
16 |
17 |
18 |
19 | 作业内容
20 |
21 | 显示/隐藏作业内容
22 |
23 |
24 |
25 |
26 |
27 |
28 | 作业内容已隐藏
29 |
30 |
31 |
32 |
33 |
34 |
35 | 回答内容
36 |
40 | 显示/隐藏回答内容
41 |
42 |
43 |
44 |
45 |
51 |
52 | 回答内容已隐藏
53 |
54 |
55 |
56 |
57 |
58 |
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 |
2 |
3 |
10 |
11 | {{ formatterRemain(row) }}
12 |
13 |
14 | {{
15 | row.title
16 | }}
17 |
18 |
19 | {{
20 | formatterAnswerStatus(row).text
21 | }}
22 |
23 |
24 | {{ formatterCourseId(row) }}
25 |
26 |
27 | {{
28 | formatterMark(row).text
29 | }}
30 |
31 |
32 | {{
33 | formatterHandler(row)
34 | }}
35 |
36 |
37 |
38 |
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 |
42 |
43 |
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 |
2 |
3 |
4 |
7 |
8 |
9 |
10 |
免Flash文件上传
11 | {{ config.version }}
12 |
13 |
14 |
15 | {{ config.description }}
16 |
17 |
18 |
92 |
93 |
94 |
95 |
96 |
116 |
117 |
148 |
--------------------------------------------------------------------------------
/src/components/Lesson/LessonSubmit/LessonSubmit.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | 提交作业
8 |
9 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
29 |
30 |
31 |
32 |
33 |
37 |
38 | 提交
39 | 返回
40 |
41 |
42 |
43 |
44 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
192 |
193 |
203 |
--------------------------------------------------------------------------------
/doc/update_log.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | 2023年4月24日
4 | V2.4.0:
5 |
6 |
7 | - FEAT: 移除插件页
8 | - FEAT: 调整更新链接
9 |
10 |
11 |
12 | 2022年11月26日
13 | V2.3.12:
14 |
15 |
16 | - FIX: 修复组件重构后作业无法正确提交的问题
17 |
18 |
19 |
20 | 2022年11月24日
21 | V2.3.11:
22 |
23 |
24 | - FEAT: 页面使用keep-alive缓存
25 | - FEAT: 作业提交组件重构 性能更优
26 | - FEAT: wangEditor本地化 脚本不再有外部依赖
27 |
28 |
29 |
30 | 2022年11月14日
31 | V2.3.10:
32 |
33 |
34 | - FIX: 修复某些场景下作业列表无法正确展示的问题
35 |
36 |
37 |
38 | 2022年9月18日
39 | V2.3.9:
40 |
41 |
42 | - FIX: 修复脚本更新链接 修复更新时跳转Gitee登录的问题
43 |
44 |
45 |
46 | 2022年9月16日
47 | V2.3.8:
48 |
49 |
50 | - FIX: 修复只能提交一次的作业 提交状态错误的问题
51 | - FIX: 修复作业提交自动刷新列表功能失效的问题
52 | - FIX: 修复身份保活功能时效的问题
53 | - FIX: 修复插件设置页弹窗样式的问题
54 |
55 |
56 |
57 | 2022年9月8日
58 | V2.3.4:
59 |
60 |
61 | - FEAT: 插件数据源从jsdelivr改为gitee
62 | - FIX: 修复已提交作业错误展示为未提交的问题
63 |
64 |
65 |
66 | 2022年7月3日
67 | V2.3.2:
68 |
69 |
70 | - FIX: 修复作业无法提交的问题
71 |
72 |
73 |
74 | 2022年7月2日
75 | V2.3.1:
76 |
77 |
78 | - FIX: "近期截止"默认展示近15天的未截止作业与已过期2天的作业
79 |
80 |
81 |
82 | 2022年6月30日
83 | V2.3.0:
84 |
85 |
86 | - REFACTOR: 脚本重构, 绝大部分组件使用Composition API重写
87 | - REFACTOR: 移除了部分冗余功能, 精简逻辑, 提高组件复用性
88 | - FEAT: 主页卡片支持拖动排序
89 | - FEAT: 对部分数据加入了本地缓存机制, 减少网络请求次数
90 |
91 |
92 |
93 | 2022年5月26日
94 | V2.2.14:
95 |
96 |
97 | - FIX: 修复了不足24小时截止的作业分类错误的问题
98 | - FEAT: 增加了请求出错时的弹窗提醒
99 | - FEAT: 关于页增加Gitee的访问入口
100 | - STYLE: 调整了侧栏分组样式
101 | - STYLE: 调整了插件页卡片的宽度
102 |
103 |
104 |
105 | 2022年5月22日
106 | V2.2.9:
107 |
108 |
109 | - FIX: 将CDN迁移至fastly.jsdelivr.net
110 | - STYLE: 调整插件列表样式
111 |
112 |
113 |
114 | 2022年4月23日
115 | V2.2.7:
116 |
117 |
118 | - FIX: 修复2.2.6版本部分反馈通道无法正确打开的bug
119 |
120 |
121 |
122 | 2022年4月23日
123 | V2.2.6:
124 |
125 |
126 | - FEAT: 增加插件选项卡,可以安装额外插件改善平台使用体验
127 | - FEAT: 增加导出课程作业卡片为图片功能
128 | - FEAT: 增加显示最近脚本通知的入口
129 | - STYLE: 侧栏选项卡分组,调整样式
130 | - STYLE: 部分通知消息弹窗改为顶部消息
131 | - STYLE: 调整关于页卡片样式
132 |
133 |
134 |
135 | 2022年4月11日
136 | V2.2.0:
137 |
138 |
139 | - FIX: 提交的图片过大导致作业表格被撑开
140 | - FIX: 移除了右上角下拉菜单
141 | - FIX: 移除平台弃用的作业参数
142 | - FIX: 移除平台不支持的编辑器功能
143 | - FIX: 移除Firefox不支持的属性
144 | - FEAT: 作业卡片支持扩展宽度
145 | - FEAT: 作业提交后自动刷新作业列表
146 | - FEAT: 支持显示已批阅的作业分数
147 | - FEAT: 支持展示/隐藏部分作业内容
148 | - FEAT: 支持关闭检查更新提示
149 | - FEAT: 优化附件上传体验
150 | - STYLE: 调整作业提交页卡片样式
151 |
152 |
153 |
154 | 2022年4月3日
155 | V2.1.11:
156 |
157 |
158 | - FIX: 修复由于缺少后缀导致无法更新的bug
159 |
160 |
161 |
162 | 2022年4月3日
163 | V2.1.10:
164 |
165 |
166 | - FIX: 修复切换作业时重复提交状态不更新的bug
167 | - FIX: 修复脚本通知sort方法报错的bug
168 | - PREF: 降低身份保活请求的发送频率
169 |
170 |
171 |
172 | 2022年3月26日
173 | V2.1.7:
174 |
175 |
176 | - FIX: 弃用从Gitee请求静态数据的方法,全部转移到jsdelivr
177 |
178 |
179 |
180 | 2022年3月26日
181 | V2.1.6:
182 |
183 |
184 | - FIX: 增加请求头,紧急修复由于Gitee屏蔽外链导致的无法检查更新等操作的bug
185 |
186 |
187 |
188 | 2022年3月24日
189 | V2.1.5:
190 |
191 |
192 | - FIX: 修复了只能提交一次的作业由于缺少参数导致无法提交的bug
193 | - FEAT: 增加了兔小巢、QQ群的反馈通道
194 |
195 |
196 |
197 | 2022年3月24日
198 | V2.1.3:
199 |
200 |
201 | - FIX: 修复了由于平台弃用旧的作业功能导致无法提交的bug
202 | - FIX: 静态数据读取地址转移到了Gitee以获得更快响应速度
203 |
204 |
205 |
206 | 2022年3月23日
207 | V2.1.1:
208 |
209 |
210 | - FIX: 移除了“展示/隐藏侧栏”功能
211 | - FIX: 修正检查更新函数的参数
212 | - FEAT: 增加拖拽上传附件功能
213 | - FEAT: 增加保持登录状态功能
214 | - FEAT: 增加脚本通知功能
215 | - FEAT: 增加导入设置功能
216 | - FEAT: 支持修改作业列表卡片默认筛选方式
217 | - FEAT: 访问排行卡片支持修改今日/总排行
218 | - STYLE: 重新设计了界面样式
219 | - STYLE: 增加了加载的过渡动画
220 | - STYLE: 作业提交页增加“课程列表”卡片
221 | - STYLE: 默认只显示最近一次的更新日志
222 |
223 |
224 |
225 | 2022年3月19日
226 | V2.0.20:
227 |
228 |
229 | - FIX: 修复了某些情况下编辑器创建失败导致编辑器内容不更新的bug
230 | - FIX: 给请求加入了超时时间验证,请求超时则报错
231 | - FEAT: 支持设置开启或关闭控制台日志输出,有助于提升性能
232 | - PREF: 从CDN引入wangEditor,减小包体积
233 | - STYLE: 修改了作业列表卡片提交状态描述
234 | - STYLE: 修改了控制台输出Log文本样式
235 |
236 |
237 |
238 | 2022年3月17日
239 | V2.0.14:
240 |
241 |
242 | - FIX: 修复了作业列表中“未截止”选项不能筛选当日截止的作业的bug
243 | - FIX: 修复了某些情况下切换作业时编辑器内容不改变的bug
244 | - FIX: 修复了登录信息过期导致获取数据失败的bug
245 | - REFACTOR: 个人设置页面重构
246 | - FEAT: wangEditor升级为5.0并且本地化
247 | - FEAT: 可以直接点击作业标题进入提交界面了
248 | - FEAT: 主页增加访问排行卡片
249 | - FEAT: 关于页增加更新日志卡片
250 | - STYLE: 增加了作业列表提交状态字段
251 | - STYLE: 主页的UserInfo卡片增加头像展示
252 | - STYLE: 作业列表卡片高度跟随内容多少变化
253 | - STYLE: 修改作业列表默认排序方式
254 | - STYLE: 移除了作业提交页的“查看效果”按钮
255 | - STYLE: 输出日志增加时间戳
256 |
257 |
258 |
259 | 2022年3月8日
260 | V2.0.0:
261 |
262 |
263 | - RELEASE: 2.0版本发布
264 |
265 |
--------------------------------------------------------------------------------
/src/components/Lesson/LessonSubmit/HwtEditor.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
10 |
11 |
12 |
28 |
29 |
30 | 拖拽文件到此处或 点击上传附件
31 |
32 |
33 |
34 |
35 |
42 |
50 |
51 |
52 |
53 |
54 |
218 |
219 |
238 |
--------------------------------------------------------------------------------