├── .github └── workflows │ ├── oss.yml │ └── oss_dev.yml ├── .gitignore ├── .vscode └── extensions.json ├── LICENSE ├── README.md ├── components.d.ts ├── index.html ├── package.json ├── pnpm-lock.yaml ├── public ├── favicon.ico ├── pwa-192x192.png ├── pwa-512x512.png ├── pwa-96x96.png └── robots.txt ├── src ├── App.vue ├── assets │ ├── logo.jpg │ ├── logo.png │ ├── logo_big.png │ └── vue_logo.png ├── components │ ├── Avatar.vue │ ├── Billboard.vue │ ├── Comment.vue │ ├── CommentEdit.vue │ ├── CommentList.vue │ ├── GlobalComponents.vue │ ├── ImageViewer │ │ ├── ImageBox.vue │ │ ├── ImageModal.css │ │ ├── ImageModal.vue │ │ └── ImageViewer.vue │ ├── MessageContent.d.ts │ ├── MessageContent.vue │ ├── PaperRender.vue │ ├── Posters.vue │ ├── RenderLink.vue │ └── Subscription.vue ├── env.d.ts ├── main.ts ├── router │ └── index.ts ├── sw.ts ├── utils │ ├── EncryptPassword.ts │ ├── naive-ui-dark-theme-overrides.json │ ├── naive-ui-light-theme-overrides.json │ ├── request.ts │ ├── store.ts │ ├── tools.ts │ └── types.ts └── views │ ├── 404.vue │ ├── About.vue │ ├── Course.vue │ ├── CourseShow.vue │ ├── CourseUpload.vue │ ├── Gallery.vue │ ├── GalleryEdit.vue │ ├── GalleryShow.vue │ ├── Home.vue │ ├── Intro.vue │ ├── Login.vue │ ├── Map.vue │ ├── Message.vue │ ├── Paper.vue │ ├── PaperEdit.vue │ ├── PaperShow.vue │ ├── Report.vue │ ├── Schedule.vue │ ├── Score.vue │ ├── Subscription.vue │ ├── User.vue │ └── admin │ ├── Billboard.vue │ └── Carousel.vue ├── tsconfig.json ├── tsconfig.node.json └── vite.config.ts /.github/workflows/oss.yml: -------------------------------------------------------------------------------- 1 | name: Publish to OSS 2 | 3 | on: 4 | push: 5 | branches: 6 | - main # 这里可以根据您的需要更改分支名称 7 | 8 | jobs: 9 | sync: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: Checkout code 13 | uses: actions/checkout@v2 14 | # 安装Node.js 15 | - name: Setup Node.js environment 16 | uses: actions/setup-node@v3.8.1 17 | # 安装pnpm 18 | - name: Install pnpm 19 | run: npm install -g pnpm 20 | # 执行应用打包步骤 21 | - name: Install dependencies and build 22 | run: | 23 | echo ${{ secrets.OSS_ACCESSKEY_ID }} 24 | echo ${{ secrets.OSS_ACCESSKEY_SECRET }} 25 | pnpm install 26 | pnpm build 27 | # 上传到OSS 28 | - name: aliyun-oss-website-action 29 | uses: fangbinwei/aliyun-oss-website-action@v1.4.0 30 | with: 31 | # Folder which contains the website files 32 | folder: ./dist/ 33 | # Aliyun OSS accessKeyId. 34 | accessKeyId: ${{ secrets.OSS_ACCESSKEY_ID }} 35 | # Aliyun OSS accessKeySecret. 36 | accessKeySecret: ${{ secrets.OSS_ACCESSKEY_SECRET }} 37 | # Aliyun OSS bucket instance. 38 | bucket: bit101-page 39 | # OSS region domain 40 | endpoint: oss-cn-hongkong.aliyuncs.com 41 | # `true` to identify the endpoint is your custom domain. 42 | cname: false # optional, default is false 43 | # `true` to skip setting static pages related configuration. `indexPage`, `notFoundPage` will not be used. 44 | skipSetting: true # optional, default is false 45 | # Save info of uploaded files to increase next upload speed 46 | incremental: true # optional, default is true 47 | # index page 48 | indexPage: index.html # optional, default is index.html 49 | # not found page 50 | notFoundPage: index.html # optional, default is 404.html 51 | -------------------------------------------------------------------------------- /.github/workflows/oss_dev.yml: -------------------------------------------------------------------------------- 1 | name: Publish to OSS 2 | 3 | on: 4 | push: 5 | branches: 6 | - dev # 这里可以根据您的需要更改分支名称 7 | 8 | jobs: 9 | sync: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: Checkout code 13 | uses: actions/checkout@v2 14 | # 安装Node.js 15 | - name: Setup Node.js environment 16 | uses: actions/setup-node@v3.8.1 17 | # 安装pnpm 18 | - name: Install pnpm 19 | run: npm install -g pnpm 20 | # 执行应用打包步骤 21 | - name: Install dependencies and build 22 | run: | 23 | echo ${{ secrets.OSS_ACCESSKEY_ID }} 24 | echo ${{ secrets.OSS_ACCESSKEY_SECRET }} 25 | pnpm install 26 | pnpm build 27 | # 上传到OSS 28 | - name: aliyun-oss-website-action 29 | uses: fangbinwei/aliyun-oss-website-action@v1.4.0 30 | with: 31 | # Folder which contains the website files 32 | folder: ./dist/ 33 | # Aliyun OSS accessKeyId. 34 | accessKeyId: ${{ secrets.OSS_ACCESSKEY_ID }} 35 | # Aliyun OSS accessKeySecret. 36 | accessKeySecret: ${{ secrets.OSS_ACCESSKEY_SECRET }} 37 | # Aliyun OSS bucket instance. 38 | bucket: bit101-page-dev 39 | # OSS region domain 40 | endpoint: oss-cn-hongkong.aliyuncs.com 41 | # `true` to identify the endpoint is your custom domain. 42 | cname: false # optional, default is false 43 | # `true` to skip setting static pages related configuration. `indexPage`, `notFoundPage` will not be used. 44 | skipSetting: true # optional, default is false 45 | # Save info of uploaded files to increase next upload speed 46 | incremental: true # optional, default is true 47 | # index page 48 | indexPage: index.html # optional, default is index.html 49 | # not found page 50 | notFoundPage: index.html # optional, default is 404.html 51 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dev-dist 13 | dist-ssr 14 | *.local 15 | .history/ 16 | 17 | # Editor directories and files 18 | .vscode/* 19 | !.vscode/extensions.json 20 | .idea 21 | .DS_Store 22 | *.suo 23 | *.ntvs* 24 | *.njsproj 25 | *.sln 26 | *.sw? 27 | 28 | /backend/__pycache__ 29 | /backend/other/*.xlsx 30 | /backend/other/*.csv -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": ["Vue.volar"] 3 | } 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 8 | 9 | ![p92oJjH.png](https://s1.ax1x.com/2023/05/16/p92oJjH.png) 10 | 11 |
12 | 13 |

BIT101

14 | 15 | [网站( bit101.cn )](https://bit101.cn) | [API文档](https://bit101-api.apifox.cn) 16 | 17 | [Go服务端](https://github.com/BIT101-dev/BIT101-GO) | [Android客户端](https://github.com/BIT101-dev/BIT101-Android) 18 | 19 | [了解BIT101](https://bit101-project.feishu.cn/wiki/W8TxwAs7rizGVEkONjAcvgvsnxe) | [加入BIT101](https://bit101-project.feishu.cn/wiki/OY1Xw6y27iNZqgkSDCkc5Cfdnjc) 20 | 21 |
22 | 23 | --- 24 | 25 | `BIT101`企划旨在打造一个富于互联网精神的、开放共享的社区,打破校内信息壁垒,使同学们学习生活得更加优雅。现在包括由`Vue3`+`Naïve UI`构建的网站前端(本仓库)、基于`Gin`框架的[Go后端](https://github.com/BIT101-dev/BIT101-GO)和基于`Jetpack Compose`构建的[Android客户端](https://github.com/BIT101-dev/BIT101-Android)。 26 | 27 | 如果有`Bug`提交、功能建议或其他任何问题,欢迎提交`issues`、加入交流QQ群[726965926](https://jq.qq.com/?_wv=1027&k=OTttwrzb)或邮件[admin@bit101.cn](mailto:admin@bit101.cn)提出。 28 | 29 | 也希望你能把`BIT101`告诉更多的同学_(:з」∠)_ 30 | 31 | 🥳`BIT101`期待你的贡献!! 32 | 33 | 34 | ## 启动! 35 | 首先下载或克隆仓库: 36 | ```bash 37 | git clone https://github.com/BIT101-dev/BIT101.git 38 | ``` 39 | 40 | 然后配置好`pnpm`环境后即可启动: 41 | ```bash 42 | cd BIT101 #进入目录 43 | pnpm install #安装依赖 44 | pnpm dev #开发模式 45 | pnpm build #编译 46 | ``` 47 | 48 | ## 发行日志 49 | 50 | * `2022-08-01`:`version:0.0.1`首次部署,撒花!完成了预想中的功能:用户系统(使用学校统一身份认证和邮箱辅助注册)、文章(校园维基)、课程评教和资料共享、成绩查询。 51 | 52 | * `2022-08-14`:`version:0.0.2`1.完善成绩查询,由于挂科重修等规则复杂,添加支持手动选择计入GPA的课程。2.添加可信成绩单查询。3.添加教学大纲查询。 53 | 54 | * `2023-05-16`:`version:0.1.0`1.迁移到`GO`后端。2.添加消息系统。3.添加地图。 55 | 56 | -------------------------------------------------------------------------------- /components.d.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | /* prettier-ignore */ 3 | // @ts-nocheck 4 | // Generated by unplugin-vue-components 5 | // Read more: https://github.com/vuejs/core/pull/3399 6 | export {} 7 | 8 | declare module 'vue' { 9 | export interface GlobalComponents { 10 | Avatar: typeof import('./src/components/Avatar.vue')['default'] 11 | Billboard: typeof import('./src/components/Billboard.vue')['default'] 12 | Comment: typeof import('./src/components/Comment.vue')['default'] 13 | CommentEdit: typeof import('./src/components/CommentEdit.vue')['default'] 14 | CommentList: typeof import('./src/components/CommentList.vue')['default'] 15 | GlobalComponents: typeof import('./src/components/GlobalComponents.vue')['default'] 16 | ImageBox: typeof import('./src/components/ImageViewer/ImageBox.vue')['default'] 17 | ImageModal: typeof import('./src/components/ImageViewer/ImageModal.vue')['default'] 18 | ImageViewer: typeof import('./src/components/ImageViewer/ImageViewer.vue')['default'] 19 | MessageContent: typeof import('./src/components/MessageContent.vue')['default'] 20 | NA: typeof import('naive-ui')['NA'] 21 | NAlert: typeof import('naive-ui')['NAlert'] 22 | NAutoComplete: typeof import('naive-ui')['NAutoComplete'] 23 | NAvatar: typeof import('naive-ui')['NAvatar'] 24 | NBadge: typeof import('naive-ui')['NBadge'] 25 | NButton: typeof import('naive-ui')['NButton'] 26 | NButtonGroup: typeof import('naive-ui')['NButtonGroup'] 27 | NCard: typeof import('naive-ui')['NCard'] 28 | NCarousel: typeof import('naive-ui')['NCarousel'] 29 | NCollapse: typeof import('naive-ui')['NCollapse'] 30 | NCollapseItem: typeof import('naive-ui')['NCollapseItem'] 31 | NCollapseTransition: typeof import('naive-ui')['NCollapseTransition'] 32 | NConfigProvider: typeof import('naive-ui')['NConfigProvider'] 33 | NDataTable: typeof import('naive-ui')['NDataTable'] 34 | NDivider: typeof import('naive-ui')['NDivider'] 35 | NDrawer: typeof import('naive-ui')['NDrawer'] 36 | NDrawerContent: typeof import('naive-ui')['NDrawerContent'] 37 | NDropdown: typeof import('naive-ui')['NDropdown'] 38 | NDynamicTags: typeof import('naive-ui')['NDynamicTags'] 39 | NEl: typeof import('naive-ui')['NEl'] 40 | NElement: typeof import('naive-ui')['NElement'] 41 | NEllipsis: typeof import('naive-ui')['NEllipsis'] 42 | NEmpty: typeof import('naive-ui')['NEmpty'] 43 | NForm: typeof import('naive-ui')['NForm'] 44 | NFormItem: typeof import('naive-ui')['NFormItem'] 45 | NFormItemGi: typeof import('naive-ui')['NFormItemGi'] 46 | NGi: typeof import('naive-ui')['NGi'] 47 | NGlobalStyle: typeof import('naive-ui')['NGlobalStyle'] 48 | NGrid: typeof import('naive-ui')['NGrid'] 49 | NH2: typeof import('naive-ui')['NH2'] 50 | NIcon: typeof import('naive-ui')['NIcon'] 51 | NImage: typeof import('naive-ui')['NImage'] 52 | NInput: typeof import('naive-ui')['NInput'] 53 | NInputGroup: typeof import('naive-ui')['NInputGroup'] 54 | NLayout: typeof import('naive-ui')['NLayout'] 55 | NLayoutContent: typeof import('naive-ui')['NLayoutContent'] 56 | NLayoutFooter: typeof import('naive-ui')['NLayoutFooter'] 57 | NLayoutHeader: typeof import('naive-ui')['NLayoutHeader'] 58 | NMenu: typeof import('naive-ui')['NMenu'] 59 | NMessageProvider: typeof import('naive-ui')['NMessageProvider'] 60 | NModal: typeof import('naive-ui')['NModal'] 61 | NPopconfirm: typeof import('naive-ui')['NPopconfirm'] 62 | NRadio: typeof import('naive-ui')['NRadio'] 63 | NRadioButton: typeof import('naive-ui')['NRadioButton'] 64 | NRadioGroup: typeof import('naive-ui')['NRadioGroup'] 65 | NRate: typeof import('naive-ui')['NRate'] 66 | NResult: typeof import('naive-ui')['NResult'] 67 | NScrollbar: typeof import('naive-ui')['NScrollbar'] 68 | NSelect: typeof import('naive-ui')['NSelect'] 69 | NSpace: typeof import('naive-ui')['NSpace'] 70 | NSpin: typeof import('naive-ui')['NSpin'] 71 | NSwitch: typeof import('naive-ui')['NSwitch'] 72 | NTable: typeof import('naive-ui')['NTable'] 73 | NTabPane: typeof import('naive-ui')['NTabPane'] 74 | NTabs: typeof import('naive-ui')['NTabs'] 75 | NTag: typeof import('naive-ui')['NTag'] 76 | NUpload: typeof import('naive-ui')['NUpload'] 77 | NUploadDragger: typeof import('naive-ui')['NUploadDragger'] 78 | PaperRender: typeof import('./src/components/PaperRender.vue')['default'] 79 | Posters: typeof import('./src/components/Posters.vue')['default'] 80 | RenderLink: typeof import('./src/components/RenderLink.vue')['default'] 81 | RouterLink: typeof import('vue-router')['RouterLink'] 82 | RouterView: typeof import('vue-router')['RouterView'] 83 | Subscription: typeof import('./src/components/Subscription.vue')['default'] 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | BIT101 15 | 16 | 17 | 18 | 20 | 21 | 23 | 24 | 25 | 26 | 27 | 48 | 49 | 50 |
51 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bit101", 3 | "private": true, 4 | "version": "1.1.0", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite", 8 | "build": "vite build", 9 | "preview": "vite preview" 10 | }, 11 | "dependencies": { 12 | "@editorjs/delimiter": "^1.3.0", 13 | "@editorjs/editorjs": "^2.28.2", 14 | "@editorjs/header": "^2.7.0", 15 | "@editorjs/list": "^1.8.0", 16 | "@editorjs/table": "^2.2.2", 17 | "axios": "^1.6.0", 18 | "crypto-js": "^4.2.0", 19 | "echarts": "^5.4.3", 20 | "editorjs-button": "^1.0.4", 21 | "editorjs-undo": "^2.0.26", 22 | "highlight.js": "^11.9.0", 23 | "ts-md5": "^1.3.1", 24 | "vue": "^3.3.7", 25 | "vue-clipboard3": "^2.0.0", 26 | "vue-echarts": "^6.6.1", 27 | "vue-router": "^4.2.5" 28 | }, 29 | "devDependencies": { 30 | "@editorjs/code": "^2.8.0", 31 | "@editorjs/image": "^2.8.2", 32 | "@editorjs/inline-code": "^1.4.0", 33 | "@editorjs/quote": "^2.5.0", 34 | "@editorjs/raw": "^2.4.0", 35 | "@types/crypto-js": "^4.2.2", 36 | "@types/node": "^20.8.9", 37 | "@vicons/antd": "^0.12.0", 38 | "@vicons/material": "^0.12.0", 39 | "@vitejs/plugin-vue": "^4.4.0", 40 | "@vue-leaflet/vue-leaflet": "^0.10.1", 41 | "leaflet": "^1.9.4", 42 | "naive-ui": "^2.35.0", 43 | "typescript": "^5.2.2", 44 | "unplugin-vue-components": "^0.25.2", 45 | "vite": "^4.5.0", 46 | "vite-plugin-pwa": "^0.16.6", 47 | "workbox-core": "^7.3.0", 48 | "workbox-precaching": "^7.3.0" 49 | }, 50 | "packageManager": "pnpm@9.13.2+sha512.88c9c3864450350e65a33587ab801acf946d7c814ed1134da4a924f6df5a2120fd36b46aab68f7cd1d413149112d53c7db3a4136624cfd00ff1846a0c6cef48a" 51 | } -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BIT101-dev/BIT101/8c7106c4b23e448c82f14ed1744507373dd251b5/public/favicon.ico -------------------------------------------------------------------------------- /public/pwa-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BIT101-dev/BIT101/8c7106c4b23e448c82f14ed1744507373dd251b5/public/pwa-192x192.png -------------------------------------------------------------------------------- /public/pwa-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BIT101-dev/BIT101/8c7106c4b23e448c82f14ed1744507373dd251b5/public/pwa-512x512.png -------------------------------------------------------------------------------- /public/pwa-96x96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BIT101-dev/BIT101/8c7106c4b23e448c82f14ed1744507373dd251b5/public/pwa-96x96.png -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: * 2 | Allow: / -------------------------------------------------------------------------------- /src/App.vue: -------------------------------------------------------------------------------- 1 | 8 | 165 | 166 | 252 | 253 | 314 | -------------------------------------------------------------------------------- /src/assets/logo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BIT101-dev/BIT101/8c7106c4b23e448c82f14ed1744507373dd251b5/src/assets/logo.jpg -------------------------------------------------------------------------------- /src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BIT101-dev/BIT101/8c7106c4b23e448c82f14ed1744507373dd251b5/src/assets/logo.png -------------------------------------------------------------------------------- /src/assets/logo_big.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BIT101-dev/BIT101/8c7106c4b23e448c82f14ed1744507373dd251b5/src/assets/logo_big.png -------------------------------------------------------------------------------- /src/assets/vue_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BIT101-dev/BIT101/8c7106c4b23e448c82f14ed1744507373dd251b5/src/assets/vue_logo.png -------------------------------------------------------------------------------- /src/components/Avatar.vue: -------------------------------------------------------------------------------- 1 | 7 | 32 | 33 | -------------------------------------------------------------------------------- /src/components/Billboard.vue: -------------------------------------------------------------------------------- 1 | 7 | 31 | 32 | 40 | 41 | 77 | 78 | 79 | -------------------------------------------------------------------------------- /src/components/Comment.vue: -------------------------------------------------------------------------------- 1 | 8 | 92 | 93 | -------------------------------------------------------------------------------- /src/components/CommentEdit.vue: -------------------------------------------------------------------------------- 1 | 47 | 48 | -------------------------------------------------------------------------------- /src/components/CommentList.vue: -------------------------------------------------------------------------------- 1 | 86 | 87 | 162 | -------------------------------------------------------------------------------- /src/components/GlobalComponents.vue: -------------------------------------------------------------------------------- 1 | 8 | 13 | 14 | -------------------------------------------------------------------------------- /src/components/ImageViewer/ImageBox.vue: -------------------------------------------------------------------------------- 1 | 46 | 47 | -------------------------------------------------------------------------------- /src/components/ImageViewer/ImageModal.css: -------------------------------------------------------------------------------- 1 | @keyframes fadeInRight { 2 | 0% { 3 | transform: translateX(40%); 4 | opacity: 0 5 | } 6 | 7 | 100% { 8 | opacity: 1 9 | } 10 | } 11 | 12 | @keyframes fadeInLeft { 13 | 0% { 14 | transform: translateX(-40%); 15 | opacity: 0 16 | } 17 | 18 | 100% { 19 | opacity: 1 20 | } 21 | } 22 | 23 | .slide-fade-left-enter-active { 24 | animation: fadeInLeft 0.267s; 25 | } 26 | 27 | .slide-fade-left-leave-active { 28 | animation: unset; 29 | } 30 | 31 | .slide-fade-right-enter-active { 32 | animation: fadeInRight 0.267s 33 | } 34 | 35 | .slide-fade-right-leave-active { 36 | animation: unset; 37 | } -------------------------------------------------------------------------------- /src/components/ImageViewer/ImageModal.vue: -------------------------------------------------------------------------------- 1 | 278 | 279 | -------------------------------------------------------------------------------- /src/components/ImageViewer/ImageViewer.vue: -------------------------------------------------------------------------------- 1 | 34 | 35 | 52 | 53 | -------------------------------------------------------------------------------- /src/components/MessageContent.d.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: flwfdd 3 | * @Date: 2022-05-29 17:44:11 4 | * @LastEditTime: 2022-05-29 17:44:31 5 | * @Description: 6 | * _(:з」∠)_ 7 | */ 8 | // 引入naive对应的定义类型 9 | import type { DialogApiInjection } from "naive-ui/lib/dialog/src/DialogProvider"; 10 | import type { MessageApiInjection } from "naive-ui/lib/message/src/MessageProvider"; 11 | 12 | declare global { 13 | interface Window { 14 | $message: MessageApiInjection; 15 | $dialog: DialogApiInjection; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/components/MessageContent.vue: -------------------------------------------------------------------------------- 1 | 8 | 13 | 14 | -------------------------------------------------------------------------------- /src/components/PaperRender.vue: -------------------------------------------------------------------------------- 1 | 8 | 15 | 16 | 34 | 35 | -------------------------------------------------------------------------------- /src/components/Posters.vue: -------------------------------------------------------------------------------- 1 | 7 | 89 | 90 | -------------------------------------------------------------------------------- /src/components/RenderLink.vue: -------------------------------------------------------------------------------- 1 | 7 | 57 | 58 | -------------------------------------------------------------------------------- /src/components/Subscription.vue: -------------------------------------------------------------------------------- 1 | 7 | 66 | 67 | -------------------------------------------------------------------------------- /src/env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | declare module '*.vue' { 4 | import type { DefineComponent } from 'vue' 5 | // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/ban-types 6 | const component: DefineComponent<{}, {}, any> 7 | export default component 8 | } 9 | -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: flwfdd 3 | * @Date: 2022-05-28 00:01:07 4 | * @LastEditTime: 2024-12-15 14:45:43 5 | * @Description: 6 | * _(:з」∠)_ 7 | */ 8 | import { createApp } from 'vue' 9 | import App from './App.vue' 10 | import router from './router/index' 11 | 12 | const app = createApp(App); 13 | app.use(router) 14 | app.mount('#app') 15 | -------------------------------------------------------------------------------- /src/router/index.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: flwfdd 3 | * @Date: 2022-05-28 01:19:14 4 | * @LastEditTime: 2025-03-19 02:35:21 5 | * @Description: 6 | * _(:з」∠)_ 7 | */ 8 | import { createRouter, createWebHistory } from 'vue-router' 9 | import { setTitle } from '@/utils/tools' 10 | 11 | const router = createRouter({ 12 | history: createWebHistory(), 13 | routes: [ 14 | { 15 | path: '/', 16 | name: 'intro', 17 | component: () => import('@/views/Intro.vue') 18 | }, 19 | { 20 | path: '/home', 21 | name: 'home', 22 | component: () => import('@/views/Home.vue') 23 | }, 24 | { 25 | path: '/login', 26 | name: 'login', 27 | component: () => import('@/views/Login.vue'), 28 | meta: { keepAlive: false } 29 | }, 30 | { 31 | path: '/user/:id', 32 | name: 'user', 33 | component: () => import('@/views/User.vue'), 34 | meta: { login: true } 35 | }, 36 | { 37 | path: '/score', 38 | name: 'score', 39 | component: () => import('@/views/Score.vue') 40 | }, 41 | { 42 | path: '/paper', 43 | name: 'paper', 44 | component: () => import('@/views/Paper.vue') 45 | }, 46 | { 47 | path: '/paper/edit/:id', 48 | name: 'paper_edit', 49 | component: () => import('@/views/PaperEdit.vue') 50 | }, 51 | { 52 | path: '/paper/:id', 53 | name: 'paper_show', 54 | component: () => import('@/views/PaperShow.vue') 55 | }, 56 | { 57 | path: '/course/:id', 58 | name: 'course_show', 59 | component: () => import('@/views/CourseShow.vue'), 60 | meta: { login: true } 61 | }, 62 | { 63 | path: '/course', 64 | name: 'course', 65 | component: () => import('@/views/Course.vue'), 66 | meta: { login: true } 67 | }, 68 | { 69 | path: '/course/upload/:id', 70 | name: 'course_upload', 71 | component: () => import('@/views/CourseUpload.vue'), 72 | meta: { keepAlive: false, login: true } 73 | }, 74 | { 75 | path: '/about/', 76 | name: 'about', 77 | component: () => import('@/views/About.vue'), 78 | meta: { keepAlive: false } 79 | }, 80 | { 81 | path: '/schedule/', 82 | name: 'schedule', 83 | component: () => import('@/views/Schedule.vue'), 84 | meta: { keepAlive: false } 85 | }, 86 | { 87 | path: '/message/', 88 | name: 'message', 89 | component: () => import('@/views/Message.vue'), 90 | meta: { login: true } 91 | }, 92 | { 93 | path: '/subscription/', 94 | name: 'subscription', 95 | component: () => import('@/views/Subscription.vue'), 96 | meta: { login: true } 97 | }, 98 | { 99 | path: '/map/', 100 | name: 'map', 101 | component: () => import('@/views/Map.vue') 102 | }, 103 | { 104 | path: '/gallery/', 105 | name: 'gallery', 106 | component: () => import('@/views/Gallery.vue'), 107 | meta: { login: true } 108 | }, 109 | { 110 | path: '/gallery/:id', 111 | name: 'gallery_show', 112 | component: () => import('@/views/GalleryShow.vue'), 113 | meta: { login: true } 114 | }, 115 | { 116 | path: '/gallery/edit/:id', 117 | name: 'gallery_edit', 118 | component: () => import('@/views/GalleryEdit.vue'), 119 | meta: { keepAlive: false, login: true } 120 | }, 121 | { 122 | path: '/report/:obj', 123 | name: 'report', 124 | component: () => import('@/views/Report.vue'), 125 | meta: { login: true } 126 | }, 127 | { 128 | path: '/admin/carousel/', 129 | name: 'admin_carousel', 130 | component: () => import('@/views/admin/Carousel.vue') 131 | }, 132 | { 133 | path: '/admin/billboard/', 134 | name: 'admin_billboard', 135 | component: () => import('@/views/admin/Billboard.vue') 136 | }, 137 | { 138 | path: '/:pathMatch(.*)', 139 | name: '404', 140 | component: () => import('@/views/404.vue') 141 | } 142 | ], 143 | // 保存滚动位置 144 | scrollBehavior(to, from, savedPosition) { 145 | if (savedPosition) return savedPosition 146 | else return { top: 0 } 147 | }, 148 | }); 149 | 150 | router.beforeEach((to) => { 151 | // 兼容旧hash模式的链接 152 | // 例:/#/paper/show/15 👉 /paper/15 153 | if (to.path === '/' && to.hash.startsWith('#/')) { 154 | let path = to.hash.slice('#'.length) 155 | path = path.replace(/^\/(paper|course)\/show\//, '/$1/') 156 | return path 157 | } 158 | 159 | 160 | // 设置大类标题 161 | // 此处只是尽快响应,之后各组件可以再覆盖。 162 | 163 | const titleMap: Record = { 164 | home: '主页', 165 | login: '登录', 166 | user: '我的', 167 | paper: '文章', 168 | course: '课程', 169 | score: '成绩', 170 | schedule: '课表', 171 | about: '关于', 172 | message: '消息', 173 | map: '地图', 174 | gallery: '话廊', 175 | report: '举报', 176 | } 177 | const top = to.path.split('/').filter(piece => piece.length > 0)[0] ?? '' 178 | const title = titleMap[top] ?? top 179 | if (title) setTitle(title) 180 | }) 181 | 182 | export default router; 183 | -------------------------------------------------------------------------------- /src/sw.ts: -------------------------------------------------------------------------------- 1 | declare let self: ServiceWorkerGlobalScope; 2 | 3 | type PushMessage = { 4 | data: string 5 | badge: string 6 | icon: string 7 | timestamp: number 8 | } 9 | 10 | type PushNotificationData = { 11 | id: number 12 | created_time: string 13 | update_time: string 14 | delete_time: string 15 | uid: number 16 | type: "comment" | "like" | "follow" | "system" 17 | unread_num: number 18 | last_time: string 19 | content: string 20 | } 21 | 22 | 23 | 24 | self.addEventListener('push', (event) => { 25 | console.debug("[Service Worker] Push event received", event); 26 | const data: PushMessage = event.data?.json(); 27 | console.debug("[Service Worker] Push event data", data); 28 | 29 | const msg : PushNotificationData = JSON.parse(atob(data.data)); 30 | 31 | const title = "BIT101"; 32 | let body = ""; 33 | 34 | if (msg.type === "comment") { 35 | body = `你收到了 ${msg.unread_num} 条评论`; 36 | } else if (msg.type === "like") { 37 | body = `你收到了 ${msg.unread_num} 个赞`; 38 | } else if (msg.type === "follow") { 39 | body = `你有 ${msg.unread_num} 个新粉丝`; 40 | } else if (msg.type === "system") { 41 | body = `你有 ${msg.unread_num} 条系统消息`; 42 | } else return ; 43 | 44 | event.waitUntil( 45 | self.registration.showNotification(title, { 46 | body: body, 47 | badge: data.badge, 48 | icon: data.icon, 49 | // 好像是Workers Typing问题 50 | //@ts-expect-error 51 | timestamp: data.timestamp, 52 | tag: msg.type, 53 | }) 54 | ); 55 | }) 56 | 57 | self.addEventListener('notificationclick', function (event) { 58 | console.log('[Service Worker] Notification click Received.'); 59 | 60 | let clients = new Clients(); 61 | 62 | event.notification.close(); 63 | event.waitUntil( 64 | clients 65 | .matchAll({ 66 | type: "window", 67 | }) 68 | .then((clientList) => { 69 | for (const client of clientList) { 70 | if (client.url === "/" && "focus" in client) return client.focus(); 71 | } 72 | if (clients.openWindow) return clients.openWindow("/"); 73 | }), 74 | ); 75 | }); 76 | 77 | import { precacheAndRoute, cleanupOutdatedCaches } from 'workbox-precaching'; 78 | 79 | cleanupOutdatedCaches(); 80 | 81 | precacheAndRoute(self.__WB_MANIFEST); 82 | 83 | import { clientsClaim } from 'workbox-core'; 84 | 85 | self.skipWaiting(); 86 | clientsClaim(); -------------------------------------------------------------------------------- /src/utils/EncryptPassword.ts: -------------------------------------------------------------------------------- 1 | import CryptoJS from 'crypto-js'; 2 | 3 | export function encryptPassword(password: string, salt: string): string { 4 | const key = CryptoJS.enc.Base64.parse(salt); 5 | const encrypted = CryptoJS.AES.encrypt(password, key, { 6 | mode: CryptoJS.mode.ECB, 7 | padding: CryptoJS.pad.Pkcs7 8 | }); 9 | return encrypted.toString(); 10 | } -------------------------------------------------------------------------------- /src/utils/naive-ui-dark-theme-overrides.json: -------------------------------------------------------------------------------- 1 | { 2 | "common": { 3 | "primaryColor": "#FF9A57FF", 4 | "primaryColorHover": "#FFAB73FF", 5 | "primaryColorSuppl": "#FFAB73FF", 6 | "primaryColorPressed": "#EB8E50FF", 7 | "fontFamily": "Source Han Serif CN, Source Han Serif SC, Source Han Serif, Noto Serif CJK, Noto Serif SC, Noto Serif, serif", 8 | "fontWeight": "500", 9 | "fontWeightStrong": "700", 10 | "borderRadius": "4px", 11 | "infoColor": "#00ABD6FF", 12 | "infoColorHover": "#00BCEBFF", 13 | "infoColorSuppl": "#00BCEBFF", 14 | "infoColorPressed": "#0087A8FF", 15 | "warningColor": "#F0A020FF", 16 | "textColor2": "#A3CCD6FF", 17 | "textColor1": "#22B2D6FF", 18 | "textColor3": "#809ba8FF", 19 | "fontSize": "16px" 20 | }, 21 | "Badge": { 22 | "fontFamily": "Source Han Serif CN, Source Han Serif SC, Source Han Serif, Noto Serif CJK, Noto Serif SC, Noto Serif, serif" 23 | }, 24 | "Divider": { 25 | "textColor": "#809BA8FF" 26 | } 27 | } -------------------------------------------------------------------------------- /src/utils/naive-ui-light-theme-overrides.json: -------------------------------------------------------------------------------- 1 | { 2 | "common": { 3 | "primaryColor": "#FF9A57FF", 4 | "primaryColorHover": "#FFAB73FF", 5 | "primaryColorSuppl": "#FFAB73FF", 6 | "primaryColorPressed": "#EB8E50FF", 7 | "fontFamily": "Source Han Serif CN, Source Han Serif SC, Source Han Serif, Noto Serif CJK, Noto Serif SC, Noto Serif, serif", 8 | "fontWeight": "500", 9 | "fontWeightStrong": "700", 10 | "borderRadius": "4px", 11 | "infoColor": "#00ABD6FF", 12 | "infoColorHover": "#00BCEBFF", 13 | "infoColorSuppl": "#00BCEBFF", 14 | "infoColorPressed": "#0087A8FF", 15 | "warningColor": "#F0A020FF", 16 | "textColor2": "#245D6BFF", 17 | "textColor1": "#027B99FF", 18 | "textColor3": "#809ba8FF", 19 | "fontSize": "16px" 20 | }, 21 | "Badge": { 22 | "fontFamily": "Source Han Serif CN, Source Han Serif SC, Source Han Serif, Noto Serif CJK, Noto Serif SC, Noto Serif, serif" 23 | }, 24 | "Divider": { 25 | "textColor": "#809BA8FF" 26 | } 27 | } -------------------------------------------------------------------------------- /src/utils/request.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: flwfdd 3 | * @Date: 2022-05-28 08:34:05 4 | * @LastEditTime: 2023-10-31 00:11:43 5 | * @Description: 6 | * _(:з」∠)_ 7 | */ 8 | import axios from 'axios'; 9 | import store from './store'; 10 | // import { useRouter, useRoute } from 'vue-router'; 11 | import router from "../router/index"; 12 | 13 | const http = axios.create(); 14 | 15 | // 添加请求拦截器 16 | http.interceptors.request.use( 17 | (config: any) => { 18 | if (config.url[0] == '/') config.url = store.api_url + config.url; 19 | config.headers['fake-cookie'] = store.fake_cookie; 20 | return config; 21 | }, 22 | (err) => { 23 | return Promise.reject(err); 24 | } 25 | ); 26 | 27 | // 添加响应拦截器 28 | http.interceptors.response.use( 29 | (res) => { 30 | if (res.data.msg) window.$message.success(res.data.msg); 31 | return res; 32 | }, 33 | (err) => { 34 | if (err.request.status == 500) { 35 | if (err.response.data.msg) window.$message.error(err.response.data.msg); 36 | else window.$message.error('出错了Orz'); 37 | } 38 | else if (err.request.status == 401) { 39 | store.fake_cookie = ""; 40 | // 自动跳转 41 | const route = router.currentRoute.value; 42 | if (route.name != "login") { 43 | window.$message.error(err.response.data.msg || '请先登录awa'); 44 | } else if (err.response.data.msg) { 45 | window.$message.error(err.response.data.msg); 46 | } 47 | if (route.meta.login) { 48 | router.push({ name: 'login', query: { redirect: encodeURIComponent(route.fullPath) } }); 49 | } 50 | } 51 | else if (err.request.status == 400) { 52 | if (err.response.data.msg) window.$message.error(err.response.data.msg); 53 | else window.$message.error('请检查请求参数awa'); 54 | } 55 | return Promise.reject(err); 56 | } 57 | ); 58 | 59 | export default http; -------------------------------------------------------------------------------- /src/utils/store.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: flwfdd 3 | * @Date: 2022-05-28 09:18:09 4 | * @LastEditTime: 2025-06-04 02:14:51 5 | * @Description: 全局状态管理 6 | * _(:з」∠)_ 7 | */ 8 | import { reactive, watch } from 'vue' 9 | import package_json from '../../package.json' 10 | 11 | let s = window.localStorage.getItem('store'); 12 | let x: any = {}; 13 | if (s) x = JSON.parse(s); 14 | else x = {}; 15 | 16 | const store = reactive({ 17 | version: package_json.version, 18 | api_url: "https://bit101.flwfdd.xyz", 19 | // api_url: "http://127.0.0.1:8080", 20 | // api_url: "http://e5.flwfdd.xyz:8080", 21 | // api_url:"http://127.0.0.1:4523/m1/2401657-0-default", 22 | // api_url:"http://192.168.0.108:4523/m1/2401657-0-default", 23 | fake_cookie: x.fake_cookie || "", 24 | theme_mode: x.theme_mode || "auto", // auto dark light 25 | grade_query: x.grade_query || {}, 26 | last_draft: x.last_draft ?? {}, 27 | hide_bot: x.hide_bot ?? false, 28 | }) 29 | 30 | watch(store, () => { 31 | window.localStorage.setItem('store', JSON.stringify(store)); 32 | }) 33 | 34 | // console.log(store); 35 | 36 | export default store -------------------------------------------------------------------------------- /src/utils/tools.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: flwfdd 3 | * @Date: 2022-05-28 00:01:07 4 | * @LastEditTime: 2025-05-12 21:00:02 5 | * @Description: 一些全局使用的函数 6 | * _(:з」∠)_ 7 | */ 8 | import { ref, reactive } from "vue"; 9 | import http from "@/utils/request"; 10 | import { encryptPassword } from "./EncryptPassword"; 11 | import useClipboard from "vue-clipboard3"; 12 | import router from "@/router"; 13 | 14 | //一言 15 | const hitokoto = ref(""); 16 | function UpHitokoto() { 17 | http.get("https://international.v1.hitokoto.cn/").then((res) => { 18 | hitokoto.value = res.data.hitokoto + " ——" + res.data.from; 19 | }); 20 | } 21 | 22 | UpHitokoto(); 23 | setInterval(UpHitokoto, 10 * 1000); 24 | 25 | //时间格式化 26 | function FormatTime(t: number | Date | string) { 27 | if (!t) return "No Time"; 28 | if (typeof t == "string") { 29 | //日期以GMT结尾时使用本地时区 30 | if (t.endsWith("GMT")) t = t.replace("GMT", ""); 31 | t = new Date(t); 32 | t = t.getTime() / 1000; 33 | } else if (typeof t != "number") t = t.getTime() / 1000; 34 | let dt = new Date().getTime() / 1000 - t; 35 | if (dt < 60) return Math.round(dt) + "秒前"; 36 | if (dt < 60 * 60) return Math.round(dt / 60) + "分钟前"; 37 | if (dt < 12 * 60 * 60) return Math.round(dt / 60 / 60) + "小时前"; 38 | 39 | let now = new Date(t * 1000); 40 | let year = now.getFullYear(); 41 | let month = (now.getMonth() + 1).toString().padStart(2, "0"); 42 | let date = now.getDate().toString().padStart(2, "0"); 43 | let hour = now.getHours().toString().padStart(2, "0"); 44 | let minute = now.getMinutes().toString().padStart(2, "0"); 45 | let second = now.getSeconds().toString().padStart(2, "0"); 46 | return ( 47 | year + "-" + month + "-" + date + " " + hour + ":" + minute + ":" + second 48 | ); 49 | } 50 | 51 | //Webvpn模块 52 | const webvpn = reactive({ 53 | model: false, 54 | loading: false, 55 | sid: "", 56 | password: "", 57 | verify_token: "", //用于注册 58 | verify_code: "", //用于注册 59 | cookie: "", 60 | data: { 61 | execution: "", 62 | cookie: "", 63 | salt: "", 64 | captcha: "", 65 | captcha_text: "", 66 | password: "", 67 | }, 68 | }); 69 | 70 | //webvpn验证初始化 71 | function WebvpnVerify(sid: string, password: string) { 72 | webvpn.loading = true; 73 | webvpn.sid = sid; 74 | webvpn.password = password; 75 | http 76 | .post("/user/webvpn_verify_init", { 77 | sid: webvpn.sid, 78 | }) 79 | .then((res) => { 80 | webvpn.data = res.data; 81 | webvpn.data.password = encryptPassword(webvpn.password, webvpn.data.salt); 82 | // TODO: real captcha 83 | // if (webvpn.data.captcha) { 84 | // webvpn.data.captcha = webvpn.data.captcha; 85 | // webvpn.data.captcha_text = ""; 86 | // webvpn.model = true; 87 | // } else WebvpnVerify2(); 88 | webvpn.data.captcha_text = encryptPassword("{}", webvpn.data.salt); 89 | WebvpnVerify2(); 90 | }); 91 | } 92 | 93 | //webvpn验证后续步骤 94 | function WebvpnVerify2() { 95 | webvpn.model = false; 96 | http 97 | .post("/user/webvpn_verify", { 98 | sid: webvpn.sid, 99 | salt: webvpn.data.salt, 100 | password: webvpn.data.password, 101 | execution: webvpn.data.execution, 102 | cookie: webvpn.data.cookie, 103 | captcha: webvpn.data.captcha_text, 104 | }) 105 | .then((res) => { 106 | webvpn.verify_code = res.data.code; 107 | webvpn.verify_token = res.data.token; 108 | webvpn.cookie = webvpn.data.cookie; 109 | webvpn.loading = false; 110 | }) 111 | .catch(() => { 112 | webvpn.loading = false; 113 | }); 114 | } 115 | 116 | //复制 117 | const { toClipboard } = useClipboard(); 118 | async function Clip(s: string, msg = "已复制到剪贴板OvO") { 119 | try { 120 | await toClipboard(s); 121 | window.$message.success(msg); 122 | } catch (e) { 123 | console.error(e); 124 | window.$message.error("复制失败Orz"); 125 | } 126 | } 127 | 128 | // 分享 129 | export function Share(title: string, text: string, url: string) { 130 | if (navigator.share) { 131 | navigator 132 | .share({ 133 | title: title, 134 | text: text, 135 | url: url, 136 | }) 137 | .then(() => { 138 | window.$message.success("分享成功OvO"); 139 | }) 140 | .catch(() => { 141 | window.$message.error("分享失败Orz"); 142 | }); 143 | } else { 144 | Clip(url, "分享链接已复制OvO"); 145 | } 146 | } 147 | 148 | // 打开链接 149 | export function OpenLink(url: string, blank = false) { 150 | if (url) { 151 | if (url.startsWith("/") && blank == false) router.push(url); 152 | else 153 | window.open( 154 | url, 155 | blank ? "_blank" : url.startsWith("/") ? "_self" : "_blank" 156 | ); 157 | } 158 | } 159 | 160 | // 监测网络情况 161 | const network = ref(true); 162 | export function WatchNetwork() { 163 | window.addEventListener("offline", () => { 164 | if (network.value == true) window.$message.error("网络已断开Orz"); 165 | network.value = false; 166 | }); 167 | 168 | window.addEventListener("online", () => { 169 | if (network.value == false) window.$message.success("网络已连接OvO"); 170 | network.value = true; 171 | }); 172 | } 173 | 174 | /** 设置页面标题 */ 175 | export function setTitle(...titles: string[]): void { 176 | if (titles.length === 0) { 177 | document.title = "BIT101"; 178 | return; 179 | } 180 | document.title = `${titles.join(" - ")} | BIT101`; 181 | } 182 | 183 | // 设置颜色透明度 184 | export function opacityColor(color: string, opacity: number): string { 185 | if (color.startsWith("#")) { 186 | let r = parseInt(color.slice(1, 2), 16); 187 | r = r * 16 + r; 188 | let g = parseInt(color.slice(2, 3), 16); 189 | g = g * 16 + g; 190 | let b = parseInt(color.slice(3, 4), 16); 191 | b = b * 16 + b; 192 | if (color.length > 5) { 193 | r = parseInt(color.slice(1, 3), 16); 194 | g = parseInt(color.slice(3, 5), 16); 195 | b = parseInt(color.slice(5, 7), 16); 196 | } 197 | color = `rgba(${r}, ${g}, ${b}, ${opacity})`; 198 | } else if (color.startsWith("rgb")) { 199 | color = color.replace("rgb", "rgba").replace(")", `, ${opacity})`); 200 | } else if (color.startsWith("rgba")) { 201 | color = color.replace(/,\s*[\d.]+\)/, `, ${opacity})`); 202 | } 203 | return color; 204 | } 205 | 206 | 207 | function GetObjName(obj: string) { 208 | if (obj.startsWith("like")) return "赞"; 209 | if (obj.startsWith("poster")) return "帖子"; 210 | if (obj.startsWith("comment")) return "评论"; 211 | if (obj.startsWith("course")) return "课程"; 212 | if (obj.startsWith("paper")) return "文章"; 213 | return ""; 214 | } 215 | 216 | function GetObjUrl(obj: string) { 217 | if (obj.startsWith("paper")) return "/paper/" + obj.substring(5); 218 | if (obj.startsWith("course")) return "/course/" + obj.substring(6); 219 | if (obj.startsWith("poster")) return "/gallery/" + obj.substring(6); 220 | return ""; 221 | } 222 | 223 | export { hitokoto, FormatTime, webvpn, WebvpnVerify, WebvpnVerify2, Clip, GetObjName, GetObjUrl }; 224 | -------------------------------------------------------------------------------- /src/utils/types.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: flwfdd 3 | * @Date: 2023-10-17 13:28:48 4 | * @LastEditTime: 2025-03-19 01:21:23 5 | * @Description: _(:з」∠)_ 6 | */ 7 | 8 | export enum SubscriptionLevel { 9 | None = 0, 10 | Silent = 1, 11 | Update = 2, 12 | Comment = 3, 13 | } 14 | 15 | 16 | // 帖子 17 | export interface Poster { 18 | anonymous: boolean; // 是否匿名 19 | claim: { 20 | id: number; // 声明id 21 | text: string; // 声明内容 22 | } 23 | comment_num: number; // 评论数量 24 | create_time: string; // 发布时间 25 | edit_time: string; // 编辑时间 26 | id: number; 27 | images: Image[]; // 图片列表 28 | like: boolean; // 是否赞过 29 | like_num: number; // 点赞数量 30 | own: boolean; // 是否可编辑 31 | plugins: string; // 插件列表 32 | public: boolean; // 是否公开 33 | tags: string[]; // 标签列表 34 | text: string; // 文本内容 35 | title: string; // 标题 36 | update_time: string; // 更新时间 37 | user: User; // 发布用户 38 | subscription: SubscriptionLevel; // 订阅状态 39 | } 40 | 41 | // 声明 42 | export interface Claim { 43 | id: number; 44 | text: string; 45 | } 46 | 47 | 48 | // 用户 49 | export interface User { 50 | avatar: Image; // 头像链接 51 | create_time: string; // 注册时间 52 | id: number; 53 | level: number; // 等级 54 | motto: string; // 格言 简介 55 | nickname: string; // 昵称 56 | identity: { 57 | color: string; // 勾勾颜色 58 | text: string; // 用户类型描述 59 | } 60 | } 61 | 62 | 63 | // 图片 64 | export interface Image { 65 | low_url: string; // 低分辨率图片链接 66 | mid: string; // 图片唯一编码 67 | url: string; // 原图链接 68 | } 69 | 70 | // 评论 71 | export interface Comment { 72 | id: number; // 评论id 73 | obj: string; // 评论对象 74 | text: string; // 评论内容 75 | images: Image[]; // 图片列表 76 | user: User; // 评论用户 77 | anonymous: boolean; // 是否匿名 78 | create_time: string; // 发布时间 79 | update_time: string; // 更新时间 80 | like: boolean; // 是否赞过 81 | like_num: number; // 点赞数量 82 | comment_num: number; // 评论数量 83 | own: boolean; // 是否可删除 84 | rate: number; // 评分 85 | reply_user: User; // 回复用户 86 | reply_obj: string; // 回复对象 87 | sub: Comment[]; // 子评论 88 | } 89 | 90 | export interface CommentList { 91 | order: 'default' | 'old', 92 | page: number, 93 | end: boolean, 94 | list: Comment[], 95 | loading: boolean, 96 | parent?: Comment, 97 | modal?: boolean 98 | } -------------------------------------------------------------------------------- /src/views/404.vue: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /src/views/About.vue: -------------------------------------------------------------------------------- 1 | 8 | 10 | 11 | 16 | 17 | -------------------------------------------------------------------------------- /src/views/Course.vue: -------------------------------------------------------------------------------- 1 | 8 | 55 | 56 | -------------------------------------------------------------------------------- /src/views/CourseShow.vue: -------------------------------------------------------------------------------- 1 | 193 | 194 | -------------------------------------------------------------------------------- /src/views/CourseUpload.vue: -------------------------------------------------------------------------------- 1 | 8 | 106 | 107 | -------------------------------------------------------------------------------- /src/views/Gallery.vue: -------------------------------------------------------------------------------- 1 | 7 | 82 | 83 | -------------------------------------------------------------------------------- /src/views/GalleryEdit.vue: -------------------------------------------------------------------------------- 1 | 7 | 251 | 252 | -------------------------------------------------------------------------------- /src/views/GalleryShow.vue: -------------------------------------------------------------------------------- 1 | 7 | 135 | 136 | -------------------------------------------------------------------------------- /src/views/Home.vue: -------------------------------------------------------------------------------- 1 | 8 | 33 | 34 | 44 | 45 | 64 | 65 | 66 | -------------------------------------------------------------------------------- /src/views/Intro.vue: -------------------------------------------------------------------------------- 1 | 7 | 20 | 21 | -------------------------------------------------------------------------------- /src/views/Login.vue: -------------------------------------------------------------------------------- 1 | 8 | 154 | 155 | 255 | 256 | 257 | -------------------------------------------------------------------------------- /src/views/Map.vue: -------------------------------------------------------------------------------- 1 | 7 | 23 | 24 | -------------------------------------------------------------------------------- /src/views/Message.vue: -------------------------------------------------------------------------------- 1 | 7 | 208 | 209 | -------------------------------------------------------------------------------- /src/views/Paper.vue: -------------------------------------------------------------------------------- 1 | 8 | 53 | 54 | -------------------------------------------------------------------------------- /src/views/PaperEdit.vue: -------------------------------------------------------------------------------- 1 | 8 | 223 | 224 | -------------------------------------------------------------------------------- /src/views/PaperShow.vue: -------------------------------------------------------------------------------- 1 | 8 | 86 | 87 | -------------------------------------------------------------------------------- /src/views/Report.vue: -------------------------------------------------------------------------------- 1 | 7 | 69 | 70 | -------------------------------------------------------------------------------- /src/views/Schedule.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 59 | 60 | 110 | -------------------------------------------------------------------------------- /src/views/Score.vue: -------------------------------------------------------------------------------- 1 | 8 | 328 | 329 | 386 | -------------------------------------------------------------------------------- /src/views/Subscription.vue: -------------------------------------------------------------------------------- 1 | 7 | 53 | 54 | -------------------------------------------------------------------------------- /src/views/User.vue: -------------------------------------------------------------------------------- 1 | 8 | 195 | 196 | -------------------------------------------------------------------------------- /src/views/admin/Billboard.vue: -------------------------------------------------------------------------------- 1 | 8 | 53 | 54 | -------------------------------------------------------------------------------- /src/views/admin/Carousel.vue: -------------------------------------------------------------------------------- 1 | 8 | 52 | 53 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "esnext", 4 | "useDefineForClassFields": true, 5 | "module": "esnext", 6 | "moduleResolution": "node", 7 | "strict": true, 8 | "jsx": "preserve", 9 | "sourceMap": true, 10 | "resolveJsonModule": true, 11 | "isolatedModules": true, 12 | "esModuleInterop": true, 13 | "lib": [ 14 | "esnext", 15 | "dom", 16 | "WebWorker" 17 | ], 18 | "skipLibCheck": true, 19 | "baseUrl": ".", 20 | "paths": { 21 | "@/*": [ 22 | "src/*" 23 | ] 24 | }, 25 | "allowJs": true, 26 | }, 27 | "include": [ 28 | "src/**/*.ts", 29 | "src/**/*.d.ts", 30 | "src/**/*.tsx", 31 | "src/**/*.vue" 32 | ], 33 | "references": [ 34 | { 35 | "path": "./tsconfig.node.json" 36 | } 37 | ], 38 | } -------------------------------------------------------------------------------- /tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "composite": true, 4 | "module": "esnext", 5 | "moduleResolution": "node" 6 | }, 7 | "include": ["vite.config.ts"] 8 | } 9 | -------------------------------------------------------------------------------- /vite.config.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: flwfdd 3 | * @Date: 2022-05-28 00:01:07 4 | * @LastEditTime: 2023-11-02 00:41:16 5 | * @Description: 6 | * _(:з」∠)_ 7 | */ 8 | import { defineConfig } from 'vite' 9 | import vue from '@vitejs/plugin-vue' 10 | import Components from 'unplugin-vue-components/vite' 11 | import { NaiveUiResolver } from 'unplugin-vue-components/resolvers' 12 | import { resolve } from 'path' 13 | import { VitePWA } from 'vite-plugin-pwa' 14 | 15 | // https://vitejs.dev/config/ 16 | export default defineConfig({ 17 | plugins: [ 18 | vue(), 19 | Components({ 20 | resolvers: [NaiveUiResolver()], 21 | }), 22 | VitePWA({ 23 | srcDir: 'src', 24 | filename: 'sw.ts', 25 | registerType: "autoUpdate", 26 | strategies: 'injectManifest', 27 | workbox: { 28 | clientsClaim: true, 29 | skipWaiting: true, 30 | runtimeCaching: [ 31 | { 32 | urlPattern: /(.*?)\.(js|css|ts)/, // js /css /ts静态资源缓存 33 | handler: 'CacheFirst', 34 | options: { 35 | cacheName: 'js-css-cache', 36 | }, 37 | }, 38 | { 39 | urlPattern: /(.*?)\.(png|jpe?g|svg|gif|bmp|psd|tiff|tga|eps|avif)/, // 图片缓存 40 | handler: 'CacheFirst', 41 | options: { 42 | cacheName: 'image-cache', 43 | }, 44 | }, 45 | ], 46 | }, 47 | includeAssets: ["favicon.ico", "pwa-192x192.png", "pwa-512x512.png"], 48 | manifest: { 49 | name: "BIT101", 50 | short_name: "BIT101", 51 | description: "BIT101校园社区", 52 | theme_color: "#FF9A57", 53 | icons: [ 54 | { 55 | src: "pwa-96x96.png", 56 | sizes: "96x96", 57 | type: "image/png", 58 | }, 59 | { 60 | src: "pwa-192x192.png", 61 | sizes: "192x192", 62 | type: "image/png", 63 | }, 64 | { 65 | src: "pwa-512x512.png", 66 | sizes: "512x512", 67 | type: "image/png", 68 | }, 69 | { 70 | src: "pwa-512x512.png", 71 | sizes: "512x512", 72 | type: "image/png", 73 | purpose: "maskable", 74 | }, 75 | { 76 | src: "pwa-512x512.png", 77 | sizes: "512x512", 78 | type: "image/png", 79 | purpose: "any maskable", 80 | }, 81 | ], 82 | }, 83 | devOptions: { 84 | enabled: true, 85 | type: "module" 86 | } 87 | }), 88 | ], 89 | resolve: { 90 | alias: { 91 | "@": resolve(__dirname, "src"), 92 | }, 93 | }, 94 | server: { 95 | port: 3000, 96 | }, 97 | }); 98 | --------------------------------------------------------------------------------