├── .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 | 
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 |
167 |
168 |
169 |
170 |
171 |
172 |
173 |
174 |
175 |
177 |
178 |
179 |
180 |
181 | BIT101
182 |
183 |
184 |
185 |
186 |
187 |
188 |
189 |
190 |
191 |
192 |
193 |
194 |
195 |
196 |
197 |
198 |
199 |
200 |
201 |
202 |
203 |
204 |
205 |
206 |
207 |
209 |
210 |
211 |
212 |
213 |
214 |
215 |
216 |
217 |
218 |
219 |
220 |
221 |
222 |
223 |
224 |
225 |
226 |
227 |
228 |
229 |
230 |
231 |
232 |
233 |
234 |
235 |
236 |
237 |
238 | {{ hitokoto }}
239 | 👆回到顶部👆
240 |
241 | GitHub
242 | |
243 | 加入BIT101
244 |
245 | Powered⚡ by BIT101 Project Team with 💖.
246 |
247 |
248 |
249 |
250 |
251 |
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 |
34 |
35 |
37 |
39 |
40 |
--------------------------------------------------------------------------------
/src/components/Billboard.vue:
--------------------------------------------------------------------------------
1 |
7 |
31 |
32 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
50 |
51 |
52 |
53 | {{ i['title'] }}
54 | {{ i['text'] }}
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
66 |
67 |
68 |
69 | {{ i['title'] }}
70 | {{ i['text'] }}
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
--------------------------------------------------------------------------------
/src/components/Comment.vue:
--------------------------------------------------------------------------------
1 |
8 |
92 |
93 |
94 | sub_comment.modal = false" />
96 |
97 |
98 |
99 |
100 | 已加载{{ comments.list.length }}条
101 |
102 |
103 | {{ comments.end ? '木有更多了' : '加载更多' }}
104 |
105 |
106 |
107 |
109 | sub_comment.modal = false"
113 | @close-sub-comments="() => sub_comment_list.modal = false" />
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
127 |
128 |
129 | {{ sub_comment_list.parent!.user.nickname }}
130 | {{ FormatTime(sub_comment_list.parent!.create_time) }}
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 | 现有{{ sub_comment_list.list.length }}条评论
140 |
141 |
142 |
143 |
144 | 已加载{{ sub_comment_list.list.length }}条
145 |
147 | {{ sub_comment_list.end ? '木有更多了' : '加载更多' }}
148 |
149 |
150 |
--------------------------------------------------------------------------------
/src/components/CommentEdit.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
6 |
9 |
10 |
11 |
12 | 附图:{{ now_comment.with_image ? '是' : '否' }}
13 |
14 |
15 | 匿名:{{ now_comment.anonymous ? '是' : '否' }}
16 |
17 |
18 |
19 |
20 |
21 | 放弃草稿
22 |
23 | 汝真放弃耶?放弃的草稿无法恢复。
24 |
25 |
31 |
32 |
33 |
34 |
35 |
36 |
37 | {{ props.subcomment ? "回复" : "发表评论" }}
38 |
39 |
40 |
41 |
42 |
43 |
44 |
46 |
47 |
48 |
--------------------------------------------------------------------------------
/src/components/CommentList.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
10 |
11 | {{ i.user.nickname }}
12 | {{ FormatTime(i.create_time) }}
13 |
14 |
15 |
16 | @{{ i.reply_user.nickname + ' ' }}
17 |
18 |
19 |
20 |
21 |
22 |
23 |
25 |
26 |
27 |
28 |
29 |
30 |
31 | {{ i.like_num }}赞同
32 |
33 |
34 | (props.ctx.parent)
35 | ? OpenReplyModal(props.ctx.parent, i.user, 'comment' + i.id)
36 | : OpenReplyModal(i)">
37 |
38 |
39 |
40 |
41 |
42 | 回复
43 |
44 |
45 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 | 删除
55 |
56 |
57 | 汝真断舍离耶?
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 | {{ j.user.nickname }}:
75 | @{{ j.reply_user.nickname + ' ' }}
76 | {{ (j.images.length ? '【图片】' : '') +
77 | j.text
78 | }}
79 |
80 |
共{{ i.comment_num }}条回复>>
81 |
82 |
83 |
84 |
85 |
86 |
87 |
162 |
--------------------------------------------------------------------------------
/src/components/GlobalComponents.vue:
--------------------------------------------------------------------------------
1 |
8 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 | 越人工 越智能
21 |
22 |
23 |
24 | 确认
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/src/components/ImageViewer/ImageBox.vue:
--------------------------------------------------------------------------------
1 |
46 |
47 |
48 | {
49 | e.preventDefault()
50 | open()
51 | }">
52 |
53 |
54 |
55 |
--------------------------------------------------------------------------------
/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 |
280 | close()" @mousedown.stop="dragStart"
281 | @mousemove.stop="dragEventHandler" @mouseup.stop="dragEndHandler" @touchstart.stop="touchStart"
282 | @touchmove.stop="touchEventHandler" @touchend.stop="touchEndHandler" @wheel.stop="wheelEventHandler">
283 |
284 |
285 |
286 |
{ loadError = true; load = true }" @load="() => load = true" @click.stop />
288 |
289 |
290 |
291 |
292 |
293 |
294 |
295 |
296 | 再试试
297 |
298 |
299 |
300 |
301 |
302 |
303 |
304 |
305 |
306 |
307 |
308 |
310 |
311 |
312 |
313 |
314 |
315 |
316 |
317 |
318 |
319 |
320 |
321 |
322 |
323 |
324 |
325 |
326 |
327 |
328 |
329 |
330 |
331 |
--------------------------------------------------------------------------------
/src/components/ImageViewer/ImageViewer.vue:
--------------------------------------------------------------------------------
1 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
42 |
+{{ props.images!.length - 2 }}
43 |
44 |
45 |
46 |
47 |
48 | openModal = false" @prev="() => position -= 1" @next="() => position += 1" />
50 |
51 |
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 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
61 |
62 |
63 |
64 |
65 | “
66 |
68 | ”
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 | {{ i.data.text }}
81 | {{ i.data.link }}
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 | |
95 |
96 |
97 |
98 |
99 |
100 |
--------------------------------------------------------------------------------
/src/components/Posters.vue:
--------------------------------------------------------------------------------
1 |
7 |
89 |
90 |
91 |
92 |
93 |
94 | {{ i.title }}
96 |
97 |
98 |
99 |
100 | 仅自己可见
101 |
102 |
103 |
104 |
105 |
106 | {{ i.claim.text }}
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 | {{ i.text.substring(0, 2333) }}
115 |
116 |
117 |
118 |
119 |
{
120 | e.preventDefault()
121 | OpenLink('/user/' + i.user.id)
122 | }" style="display:flex;align-items: center;flex:1;">
123 |
124 |
125 |
126 | {{ i.user.nickname }}
127 |
128 |
129 |
130 | {{ i.like_num }}赞 | {{ i.comment_num }}评 | {{ FormatTime(i.create_time) }}
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 | 已加载{{ posters.list.length }}条
139 |
140 |
141 | {{ posters.end ? '去做点更有意思的事情吧' : '加载更多' }}
142 |
143 |
--------------------------------------------------------------------------------
/src/components/RenderLink.vue:
--------------------------------------------------------------------------------
1 |
7 |
57 |
58 |
59 |
60 |
61 | {{ i.text }}
62 |
63 |
65 | {{ i.text }}
66 |
67 |
68 |
--------------------------------------------------------------------------------
/src/components/Subscription.vue:
--------------------------------------------------------------------------------
1 |
7 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 | 插眼
77 |
78 |
79 |
--------------------------------------------------------------------------------
/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 |
12 |
13 |
14 | 溯洄
15 |
16 |
17 |
--------------------------------------------------------------------------------
/src/views/About.vue:
--------------------------------------------------------------------------------
1 |
8 |
10 |
11 |
16 |
17 |
18 |
19 |
关于 | About
20 |
21 | - 如果有Bug、功能建议或任何问题可以通过issue、交流QQ群
22 | 726965926
23 | 或邮件admin@bit101.cn提出
24 |
25 | - 我们正在建立BIT101企划组,如果你感兴趣,欢迎加入我们
26 | - 欢迎到GitHub来给上一颗可爱的Star⭐~
27 | - 关于这个网站背后的故事可以康康这篇文章
28 | - 希望你能喜欢这个网站
29 | - 也请分享给更多人哦_(:з」∠)_
30 | - 这个网站完完全全是用爱发电的
31 | - 祝你天天开心万事如意!
32 | - Bang15便士
33 |
34 |
35 |
--------------------------------------------------------------------------------
/src/views/Course.vue:
--------------------------------------------------------------------------------
1 |
8 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 | 搜索
63 |
64 | 排序方式
65 |
66 |
67 | 最新
68 | 最高分
69 | 最多赞
70 | 最多评
71 |
72 |
73 | 检索
74 |
75 | 结果不准确?试试相关搜索
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 | {{ i['name'] }}
86 |
87 | 授课教师:{{ i['teachers_name'] }}
88 | {{ i['like_num'] }}赞 | {{ i['comment_num'] }}评价 | {{
89 | (i['rate'] /
90 | 2).toFixed(2)
91 | }}分
92 |
93 |
94 |
已加载{{ course.list.length }}条
95 |
96 | {{ course.end ? '木有更多了' : '加载更多' }}
97 |
98 |
--------------------------------------------------------------------------------
/src/views/CourseShow.vue:
--------------------------------------------------------------------------------
1 |
193 |
194 |
195 |
196 |
{{ course.name }}
197 |
198 |
199 |
200 | {{ course.rate.toFixed(2) }}分({{ course.comment_num
201 | }}人评价)
202 |
203 |
课程编号:{{ course.number }}
204 |
205 |
206 |
207 |
208 |
209 | 授课教师:
210 |
211 | {{ i['name'] }}
212 |
213 | 查找其他老师讲授的该课程
214 |
215 |
216 |
217 |
219 |
220 |
221 |
222 |
223 |
224 |
225 | {{ course.like_num }}人点赞
226 |
227 |
228 |
229 |
230 |
231 |
232 |
233 |
234 | 分享
235 |
236 |
237 | 教学大纲
238 |
239 |
240 | 共享资料
241 |
242 |
243 | 上传资料
244 |
245 |
246 | 历史记录
247 |
248 |
249 |
250 |
我的评价是
251 |
252 | 评分请尽量客观,以授课质量为主要视角
可以谈谈上课风格、给分方式、作业情况、学习感想等
253 |
254 |
255 |
256 |
257 |
258 |
259 |
260 |
261 |
262 | 注:数据仅供参考,不区分教学班。
263 | 「如果一个人把政策评分作为自己的至高追求,那么他就是这个政策的牺牲品。」
264 |
——《上海交通大学生存手册》
265 |
266 |
267 |
268 |
269 |
--------------------------------------------------------------------------------
/src/views/CourseUpload.vue:
--------------------------------------------------------------------------------
1 |
8 |
106 |
107 |
108 |
109 |
{{ course.name }} 共享资料上传
110 |
111 |
112 |
113 | 上传前请先看看是否已经有相同资料了,有就不要重复上传啦QwQ
114 | 非常感谢您的贡献_(:з」∠)_
115 |
116 | 关于类别的说明:
117 | 书籍:教科书、课程相关电子书等
118 | 课件:PPT什么的
119 | 考试:考试相关的往年题、复习资料等
120 | 其他:兜底条款,比如作业资料....?
121 |
122 |
123 |
124 |
125 | 查看课程资料
126 |
127 |
128 |
129 |
130 | 类别:
131 | 书籍
132 | 课件
133 | 考试
134 | 其他
135 |
136 |
137 |
138 |
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 | 点击或者拖动文件到该区域来选择文件
148 |
149 |
150 |
151 |
153 | 上传
154 |
155 |
156 |
--------------------------------------------------------------------------------
/src/views/Gallery.vue:
--------------------------------------------------------------------------------
1 |
7 |
82 |
83 |
84 |
85 |
86 |
话廊 | Gallery
87 |
88 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 | 隐藏机器人
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
116 | 搜索
117 |
118 |
119 |
120 | 最新
121 | 最多赞
122 | 最多评
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
134 |
135 |
136 |
137 |
138 |
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 |
--------------------------------------------------------------------------------
/src/views/GalleryEdit.vue:
--------------------------------------------------------------------------------
1 |
7 |
251 |
252 |
253 |
254 |
255 | 🌟 {{ poster.id == 0 ? '发布 Poster' : '编辑 Poster ' }}
256 |
257 |
258 | 标题
259 |
260 |
261 |
262 |
263 | 内容
264 |
266 |
267 |
268 |
269 | 图片
270 |
272 |
273 |
274 |
275 | 标签
276 | 请至少添加2个标签,合适的标签将有助于内容推荐。
277 |
278 |
279 | { submit($event); deactivate() }"
281 | @blur="() => { deactivate(); rawTag = ''; }" placeholder="选一个吧,或者输入你想要的" :get-show="() => true"
282 | :status="rawTagLengthLimit" />
283 |
284 |
285 |
286 |
287 |
288 | 声明
289 | 请根据社区公约选择合适的声明,否则可能会被制裁。
290 |
291 |
292 |
293 |
294 | 匿名:{{ poster.anonymous ? '是' : '否' }}
295 | 公开:{{ poster.public ? '是' : '否' }}
296 |
297 |
298 |
299 |
300 |
301 | 放弃草稿
302 |
303 | 汝真放弃耶?放弃的草稿无法恢复。
304 |
305 |
306 |
307 | 张贴Poster
308 |
309 | 汝真发布耶?
310 |
311 |
312 |
313 |
314 |
315 |
317 |
318 |
322 |
323 |
--------------------------------------------------------------------------------
/src/views/GalleryShow.vue:
--------------------------------------------------------------------------------
1 |
7 |
135 |
136 |
137 |
138 |
{{ poster.title }}
139 |
140 |
141 |
144 |
145 | {{ poster.user.nickname }}
146 | {{ poster.user.identity.text }}
147 |
148 |
149 |
150 |
151 |
152 | 仅自己可见
153 |
154 |
155 |
156 |
157 |
158 | {{ poster.claim.text }}
159 |
160 |
161 |
162 |
163 |
164 |
165 |
166 |
167 |
168 |
169 |
170 |
171 |
172 |
173 |
174 |
175 |
176 | {{ i
178 | }}
179 |
180 |
181 |
182 | 首次发布于 {{ FormatTime(poster.create_time) }}
183 |
184 | 最后编辑于 {{ FormatTime(poster.edit_time) }}
185 |
186 |
187 |
188 |
189 |
190 |
191 |
192 |
193 |
194 | 编辑
195 |
196 |
197 |
199 |
200 |
201 |
202 |
203 |
204 | 删除
205 |
206 |
207 | 汝真断舍离耶?
208 |
209 |
210 |
211 |
212 |
213 |
214 | 举报
215 |
216 |
217 |
218 |
219 |
220 |
221 | 分享
222 |
223 |
224 |
225 |
226 |
228 |
229 |
230 |
231 | {{ poster.like_num }}赞同
232 |
233 |
234 |
235 |
236 |
现有{{ poster.comment_num }}条评论
237 |
238 |
239 |
240 |
242 |
243 |
244 |
245 |
246 |
247 |
248 |
249 |
--------------------------------------------------------------------------------
/src/views/Home.vue:
--------------------------------------------------------------------------------
1 |
8 |
33 |
34 |
44 |
45 |
46 |
47 |
48 |
49 |
52 |
53 |
54 |
55 | Tips:点击左上角的≡可以打开菜单哟!
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
--------------------------------------------------------------------------------
/src/views/Intro.vue:
--------------------------------------------------------------------------------
1 |
7 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/src/views/Login.vue:
--------------------------------------------------------------------------------
1 |
8 |
154 |
155 |
156 |
157 |
158 |
159 |
160 | 已登录
161 | 抑或你想注销
162 |
163 |
164 | 未登录
165 |
166 |
167 |
168 | 人不能两次登入同一个账号
169 | 你或许可以到个人中心康康
170 |
171 |
172 |
173 |
174 |
175 |
176 |
177 |
178 |
179 |
180 |
181 |
182 | 登录
183 |
184 |
185 |
186 |
187 |
188 | 1. BIT101奉行「前端匿名」理念,不会主动公开任何个人信息,你在登录后可以设置供展示的头像、昵称等。
189 |
190 | 2. 密码将被不可逆加密后传输到服务器。
191 | 3. 你需要选择统一身份认证或学校邮箱以证明自己的身份。
192 |
193 |
194 |
195 |
196 |
197 |
198 |
199 |
200 |
201 |
202 |
203 |
204 |
206 |
207 |
208 |
209 |
210 |
211 | 统一身份认证
212 |
213 |
214 | @bit.edu.cn邮箱
215 |
216 |
217 |
218 |
219 |
220 |
221 |
222 |
223 |
224 |
226 |
227 |
228 | 验证
230 |
231 |
232 |
233 |
234 |
235 |
236 |
237 |
238 |
239 | {{ count_down
240 | ? count_down : '发送验证码' }}
241 |
242 |
243 |
244 |
245 |
246 |
247 | 注册
248 |
249 |
250 |
251 |
252 |
253 |
254 |
255 |
256 |
257 |
--------------------------------------------------------------------------------
/src/views/Map.vue:
--------------------------------------------------------------------------------
1 |
7 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 | 乡
31 | 村
32 |
33 |
34 |
35 |
36 |
37 |
--------------------------------------------------------------------------------
/src/views/Message.vue:
--------------------------------------------------------------------------------
1 |
7 |
208 |
209 |
210 |
211 |
212 |
213 |
214 |
215 |
216 |
217 |
218 | 收到的赞
219 |
220 |
221 |
222 |
223 |
224 |
225 |
226 |
227 | 收到的评论
228 |
229 |
230 |
231 |
232 |
233 |
234 |
235 |
236 | 新增粉丝
237 |
238 |
239 |
240 |
241 |
242 |
243 |
244 |
245 | 插眼订阅
246 |
247 |
248 |
249 |
250 |
251 |
252 |
253 |
254 | 系统通知
255 |
256 |
257 |
258 |
259 |
260 |
261 |
262 |
263 |
264 |
265 |
266 |
267 |
268 | {{ FormatTime(i.update_time) }}
269 | 详情>
270 | 查看>
271 |
272 |
273 | 已加载{{ messages.list.length }}条
274 |
275 | {{ messages.end ? '木有更多了' : '加载更多' }}
276 |
277 |
278 |
279 |
280 |
281 |
283 |
284 |
285 |
286 | 开启推送服务后,浏览器将会进行消息推送, 包括点赞、评论、关注、系统通知等.
287 | 但是由于网络问题, 推送可能比较玄乎 ()
288 |
289 |
290 |
291 |
292 |
293 |
294 |
295 |
296 |
297 |
298 |
299 |
300 |
--------------------------------------------------------------------------------
/src/views/Paper.vue:
--------------------------------------------------------------------------------
1 |
8 |
53 |
54 |
55 |
56 |
57 | 新建 Paper
59 |
60 |
61 |
62 | 搜索
63 |
64 |
65 |
66 | 排序方式
67 | 最新
68 | 最多赞
69 | 最多评
70 |
71 |
72 | 检索
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 | {{ i['title'] }}
83 |
84 | {{ i['intro'] }}
85 |
86 | {{ i['like_num'] }}赞 | {{ i['comment_num'] }}评论 |
87 | {{ FormatTime(i['update_time']) }}更新
88 |
89 |
90 |
已加载{{ papers.list.length }}条
91 |
92 | {{ papers.end ? '木有更多了' : '加载更多' }}
93 |
94 |
--------------------------------------------------------------------------------
/src/views/PaperEdit.vue:
--------------------------------------------------------------------------------
1 |
8 |
223 |
224 |
225 |
226 |
227 |
228 | 标题
229 |
230 | 简介(为空则根据文章自动生成)
231 |
232 |
233 | 预览
234 |
235 |
236 | 发Paper
237 |
238 | 汝真发表耶?
239 |
240 | 匿名:{{ paper.anonymous ? '是' : '否' }}
241 | 其他人可编辑:{{ paper.public_edit ?
242 | '是' : '否' }}
243 |
244 |
246 |
247 | 删除
248 |
249 | 汝真断舍离耶?
250 |
251 |
252 |
253 |
254 |
255 |
256 |
257 |
258 |
259 |
260 |
261 |
262 |
263 |
264 |
265 |
266 |
--------------------------------------------------------------------------------
/src/views/PaperShow.vue:
--------------------------------------------------------------------------------
1 |
8 |
86 |
87 |
88 |
89 |
90 | {{ paper.title }}
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 | {{ paper.user.nickname }}
99 | 最后编辑于{{ paper.update_time }}
100 |
101 |
102 |
103 |
104 |
105 |
107 |
108 |
109 |
110 |
111 |
112 |
113 | {{ paper.like_num }}赞同
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 | 分享
123 |
124 |
125 |
126 |
127 |
首次编辑于{{ paper.create_time }}
128 |
129 |
130 |
132 |
133 |
134 |
135 |
136 |
137 | 编辑此篇Paper
138 |
139 | 不允许编辑
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 |
148 | 分享
149 |
150 |
151 |
152 |
153 |
155 |
156 |
157 |
158 |
159 |
160 |
161 | {{ paper.like_num }}赞同
162 |
163 |
164 |
165 |
现有{{ paper.comment_num }}条评论
166 |
167 |
168 |
--------------------------------------------------------------------------------
/src/views/Report.vue:
--------------------------------------------------------------------------------
1 |
7 |
69 |
70 |
71 |
72 |
73 | {{ report.title }}
74 |
75 |
76 | 举报类别
77 |
78 |
79 |
80 |
81 | 举报理由
82 |
84 |
85 |
86 |
87 |
88 |
89 | 举报
90 |
91 | 汝真举报耶?
92 |
93 |
94 |
95 |
96 |
97 |
--------------------------------------------------------------------------------
/src/views/Schedule.vue:
--------------------------------------------------------------------------------
1 |
8 |
9 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
71 |
72 |
73 |
78 |
79 |
80 |
81 |
87 | 查询
88 |
89 |
90 |
91 |
92 |
93 | 正在获取课程表
94 |
95 |
96 |
97 |
{{ schedule.msg }}
98 |
打开课程表
99 |
100 |
101 |
102 | 导出的是当前学期的课表(.ics格式),iOS用户直接打开链接即可添加到日历,安卓用户可使用BIT101-Android
103 | APP(或支持.ics格式的日历软件),桌面端也有软件可以适配.ics格式。具体请见:
104 |
105 | 使用攻略
106 |
107 |
108 |
109 |
110 |
--------------------------------------------------------------------------------
/src/views/Score.vue:
--------------------------------------------------------------------------------
1 |
8 |
328 |
329 |
330 |
331 |
332 |
333 |
334 |
335 |
337 | 查询
338 |
339 |
340 |
341 |
342 | 查询详细信息
343 |
344 |
345 | 获取可信成绩单
346 |
347 |
348 |
349 | 筛选
350 |
351 | 导出为 CSV
352 |
353 |
354 |
355 |
356 |
357 | Tips:
358 | 学分绩根据筛选出的课程加权计算,估计平均绩使用各科平均分计算得出,4分制GPA采用
359 | 官方公式
360 | 计算。可手动勾选纳入计算的课程,点击课程名称可查看详情。更多说明请见:
361 | BIT101常见问题解惑
362 |
363 | 总学分:{{ stat.credit }}({{ stat.num }}门)
364 | 个人学分绩 | GPA:{{ stat.score }} | {{ stat.gpa }}
365 | 估计平均绩 | GPA:{{ stat.avg_score }} | {{ stat.avg_gpa }}
366 |
367 |
368 |
370 |
371 |
372 |
373 |
374 | 查找课程评价和资料
375 | {{ key + ': ' + value }}
376 |
377 |
378 |
379 |
380 |
381 |
382 |
383 |
384 |
385 |
386 |
--------------------------------------------------------------------------------
/src/views/Subscription.vue:
--------------------------------------------------------------------------------
1 |
7 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
63 |
64 | {{ GetObjName(i.obj) }}:{{ i.text }}
65 |
66 |
67 | {{ subscriptionLevels[i.level] }} 于 {{ FormatTime(i.subscription_time) }}
68 | 查看>
69 |
70 |
71 | 已加载{{ subscriptions.list.length }}条
72 |
73 | {{ subscriptions.end ? '木有更多了' : '加载更多' }}
74 |
75 |
76 |
--------------------------------------------------------------------------------
/src/views/User.vue:
--------------------------------------------------------------------------------
1 |
8 |
195 |
196 |
197 |
198 |
199 |
200 |
201 |
204 |
205 | {{ user_info.user.nickname }}
206 | uid:{{ user_info.user.id }}
207 |
208 |
209 |
210 |
212 |
213 |
214 | {{ user_info.following ? (user_info.follower ? '已互粉' : '已关注') : '关注' }}
215 |
216 |
217 | 汝真取关耶?
218 |
219 |
220 | {{ user_info.following ? (user_info.follower ? '已互粉' : '已关注') : '关注' }}
221 |
222 |
223 |
224 |
225 |
226 |
228 | {{ user_info.user.identity.text }}
229 |
230 |
232 | {{ user_info.following_num }}关注
233 |
234 |
236 | {{ user_info.follower_num }}粉丝
237 |
238 |
239 |
240 |
241 | {{ user_info.user.motto }}
242 |
243 | 于 {{ FormatTime(user_info.user.create_time) }} 来到BIT101
244 | 编辑信息
245 | {{
246 | route.params.id == '0' ? '切换到访客视角' : '切换到个人中心' }}
247 |
248 |
249 |
250 |
251 |
252 |
253 |
254 |
255 |
256 |
258 | 上传头像
259 |
260 |
261 | 昵称
262 |
263 |
264 | 格言/简介
265 |
266 |
267 |
268 |
269 | 提交
270 | 取消
271 |
272 |
273 |
274 |
275 |
276 |
277 |
278 |
279 |
280 |
281 |
282 |
283 |
{{ user.nickname }}
284 |
uid:{{ user.id }}
285 |
286 |
287 |
288 |
289 |
290 | 已加载{{ follows.list.length }}条
291 |
292 | {{ follows.end ? '木有更多了' : '加载更多' }}
293 |
294 |
295 |
296 |
--------------------------------------------------------------------------------
/src/views/admin/Billboard.vue:
--------------------------------------------------------------------------------
1 |
8 |
53 |
54 |
55 |
56 |
57 | 橱窗设置
58 |
59 |
60 | 添加
61 | 预览
62 | 提交
63 |
64 |
65 |
66 |
--------------------------------------------------------------------------------
/src/views/admin/Carousel.vue:
--------------------------------------------------------------------------------
1 |
8 |
52 |
53 |
54 |
55 |
56 | 轮播图设置
57 |
58 |
61 |
62 |
63 | 添加
64 | 预览
65 | 提交
66 |
67 |
68 |
69 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------