├── public
└── favicon.ico
├── src
├── assets
│ ├── logo.png
│ └── docs
│ │ └── log.json
├── main.scss
├── views
│ ├── fatherContainer.vue
│ ├── webData.vue
│ ├── faq.vue
│ ├── index.vue
│ ├── about.vue
│ ├── admin
│ │ └── hideVideo.vue
│ ├── log.vue
│ ├── user
│ │ ├── rank.vue
│ │ ├── signin.vue
│ │ ├── setting.vue
│ │ └── signup.vue
│ ├── space
│ │ └── index.vue
│ ├── video
│ │ └── detail.vue
│ └── member
│ │ └── upload.vue
├── vite-env.d.ts
├── store
│ ├── urlStore.ts
│ └── userStore.ts
├── components
│ ├── rzm
│ │ ├── mmvCard.vue
│ │ └── mmCard.vue
│ ├── user
│ │ └── avatar.vue
│ ├── dataCount.vue
│ ├── video
│ │ ├── VideoList.vue
│ │ ├── VideoGrid.vue
│ │ └── player.vue
│ ├── common
│ │ └── comment.vue
│ └── navBar.vue
├── composables
│ └── useFormat.ts
├── main.ts
├── router
│ └── index.ts
└── App.vue
├── .prettierrc
├── tailwind.config.js
├── tsconfig.node.json
├── auto-imports.d.ts
├── .gitignore
├── README.md
├── vite.config.ts
├── tsconfig.json
├── .eslintrc.cjs
├── package.json
├── components.d.ts
└── index.html
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rzmaoo/maomao-frontend/HEAD/public/favicon.ico
--------------------------------------------------------------------------------
/src/assets/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rzmaoo/maomao-frontend/HEAD/src/assets/logo.png
--------------------------------------------------------------------------------
/src/main.scss:
--------------------------------------------------------------------------------
1 | @import 'tailwindcss/base';
2 | @import 'tailwindcss/components';
3 | @import 'tailwindcss/utilities';
4 |
--------------------------------------------------------------------------------
/src/views/fatherContainer.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "semi": false,
3 | "singleQuote": true,
4 | "trailingComma": "es5",
5 | "endOfLine": "crlf"
6 | }
7 |
--------------------------------------------------------------------------------
/tailwind.config.js:
--------------------------------------------------------------------------------
1 | /** @type {import('tailwindcss').Config} */
2 | export default {
3 | content: [],
4 | theme: {
5 | extend: {},
6 | },
7 | plugins: [],
8 | }
9 |
--------------------------------------------------------------------------------
/src/views/webData.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
11 |
--------------------------------------------------------------------------------
/tsconfig.node.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "composite": true,
4 | "skipLibCheck": true,
5 | "module": "ESNext",
6 | "moduleResolution": "bundler",
7 | "allowSyntheticDefaultImports": true
8 | },
9 | "include": ["vite.config.ts"]
10 | }
11 |
--------------------------------------------------------------------------------
/src/vite-env.d.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable @typescript-eslint/ban-types */
2 | /* eslint-disable @typescript-eslint/no-explicit-any */
3 | ///
4 |
5 | declare module '*.vue' {
6 | import { DefineComponent } from 'vue'
7 | const component: DefineComponent<{}, {}, any>
8 | export default component
9 | }
10 |
--------------------------------------------------------------------------------
/auto-imports.d.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable */
2 | /* prettier-ignore */
3 | // @ts-nocheck
4 | // noinspection JSUnusedGlobalSymbols
5 | // Generated by unplugin-auto-import
6 | export {}
7 | declare global {
8 | const ElMessage: typeof import('element-plus/es')['ElMessage']
9 | const ElNotification: typeof import('element-plus/es')['ElNotification']
10 | }
11 |
--------------------------------------------------------------------------------
/.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 | dist-ssr
13 | *.local
14 |
15 | # Editor directories and files
16 | .vscode/*
17 | !.vscode/extensions.json
18 | .idea
19 | .DS_Store
20 | *.suo
21 | *.ntvs*
22 | *.njsproj
23 | *.sln
24 | *.sw?
25 | /yarn.lock
26 | /package-lock.json
--------------------------------------------------------------------------------
/src/store/urlStore.ts:
--------------------------------------------------------------------------------
1 | import { defineStore } from 'pinia'
2 |
3 | export const useUrlStore = defineStore({
4 | id: 'urlStore',
5 |
6 | state: () => ({
7 | // apiUrl: 'http://localhost:5000',
8 | // cosUrl: 'https://cos.bilirz.com',
9 | apiUrl: `${window.location.protocol}//${window.location.host}`,
10 | // apiUrl: 'https://v.bilirz.com',
11 | cosUrl: `${window.location.protocol}//${window.location.host}/api/public/cos`,
12 | faceUrl: `https://cos.bilirz.com`,
13 | }),
14 | })
15 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # 猫猫站
2 |
3 | ### 写在前面
4 |
5 | 本项目是B站UP主[认真猫](https://space.bilibili.com/256195234)使用Vue3开发的视频网站,仅供[认真猫](https://space.bilibili.com/256195234)学习编程使用。
6 |
7 | 网站功能还非常简单,期待大家的丰富。
8 |
9 | 欢迎加入猫猫站用户与技术QQ群:`542174643`
10 |
11 | ### TODO
12 |
13 | 目标
14 |
15 | - [ ] 用户
16 |
17 | - [x] 注册
18 | - [x] 登录
19 | - [ ] 个人中心
20 |
21 | - [ ] 视频
22 |
23 | - [x] m3u8
24 | - [x] 播放量
25 | - [x] 点赞
26 | - [ ] 投币
27 | - [ ] 收藏
28 | - [x] 弹幕
29 | - [x] 评论
30 | - [ ] 转发
31 |
32 | - [ ] 首页推荐
33 | - [ ] 热门
34 | - [ ] 热搜
35 |
--------------------------------------------------------------------------------
/src/views/faq.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | 目前,已注册的用户每8小时签到可以获得10点积分和10经验值。消耗积分可以增加等量的经验。
6 |
7 | 消耗积分可以评论、发布视频、修改头像等。
8 |
9 | 发布评论消耗0.2积分,发送弹幕消耗0.2积分,发布视频消耗1积分,修改头像或名字消耗5积分。
10 |
11 | 视频被点赞可增加10积分,在自己的视频下评论不增加积分。
12 |
13 |
14 |
15 |
16 |
17 |
20 |
21 |
28 |
--------------------------------------------------------------------------------
/src/components/rzm/mmvCard.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
31 |
--------------------------------------------------------------------------------
/vite.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'vite'
2 | import vue from '@vitejs/plugin-vue'
3 | import AutoImport from 'unplugin-auto-import/vite'
4 | import Components from 'unplugin-vue-components/vite'
5 | import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'
6 | import vuetify from 'vite-plugin-vuetify'
7 |
8 | export default defineConfig({
9 | plugins: [
10 | vue(),
11 | vuetify(),
12 | AutoImport({
13 | resolvers: [ElementPlusResolver()],
14 | }),
15 | Components({
16 | resolvers: [ElementPlusResolver()],
17 | }),
18 | ],
19 | css: {
20 | preprocessorOptions: {
21 | scss: {
22 | additionalData: `@import "./src/main.scss";`,
23 | },
24 | },
25 | },
26 | resolve: {
27 | alias: {
28 | '@': '/src',
29 | },
30 | },
31 | })
32 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ES2020",
4 | "useDefineForClassFields": true,
5 | "module": "ESNext",
6 | "lib": ["ES2020", "DOM", "DOM.Iterable"],
7 | "skipLibCheck": true,
8 | "allowJs": true,
9 |
10 | /* Bundler mode */
11 | "moduleResolution": "bundler",
12 | "allowImportingTsExtensions": true,
13 | "resolveJsonModule": true,
14 | "isolatedModules": true,
15 | "noEmit": true,
16 | "jsx": "preserve",
17 |
18 | /* Linting */
19 | "strict": true,
20 | "noUnusedLocals": true,
21 | "noUnusedParameters": true,
22 | "noFallthroughCasesInSwitch": true,
23 |
24 | "types": ["element-plus/global"]
25 | },
26 | "include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"],
27 | "references": [{ "path": "./tsconfig.node.json" }]
28 | }
29 |
--------------------------------------------------------------------------------
/src/store/userStore.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable @typescript-eslint/no-explicit-any */
2 | import { defineStore } from 'pinia'
3 | import axios from 'axios'
4 | import { useUrlStore } from './urlStore'
5 |
6 | export const useUserStore = defineStore({
7 | id: 'userStore',
8 |
9 | state: () => ({
10 | sessionData: {
11 | signin: null,
12 | status: null,
13 | isload: false,
14 | },
15 | }),
16 |
17 | actions: {
18 | async fetchSessionData() {
19 | const urlStore = useUrlStore()
20 |
21 | try {
22 | const response = await axios.get(
23 | `${urlStore.apiUrl}/api/user/session/get`
24 | )
25 |
26 | this.setSessionData(response.data)
27 | } catch (error) {
28 | console.error('获取session数据出错:', error)
29 | }
30 | },
31 |
32 | setSessionData(data: any) {
33 | this.sessionData = data
34 | },
35 | },
36 | })
37 |
--------------------------------------------------------------------------------
/src/views/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
32 |
--------------------------------------------------------------------------------
/src/components/rzm/mmCard.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | {{ title }}
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
22 |
46 |
--------------------------------------------------------------------------------
/.eslintrc.cjs:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | env: {
3 | browser: true,
4 | es2021: true,
5 | },
6 | extends: [
7 | 'eslint:recommended',
8 | 'plugin:vue/recommended',
9 | 'plugin:@typescript-eslint/recommended',
10 | 'plugin:prettier/recommended',
11 | ],
12 | overrides: [
13 | {
14 | env: {
15 | node: true,
16 | },
17 | files: ['.eslintrc.{js,cjs}'],
18 | parserOptions: {
19 | sourceType: 'script',
20 | },
21 | },
22 | ],
23 | parser: 'vue-eslint-parser',
24 | parserOptions: {
25 | parser: '@typescript-eslint/parser', // TypeScript 解析器
26 | ecmaVersion: 2020,
27 | sourceType: 'module',
28 | ecmaFeatures: {
29 | jsx: true,
30 | },
31 | },
32 | plugins: [
33 | 'vue',
34 | '@typescript-eslint', // TypeScript
35 | 'prettier', // 启用 Prettier 插件
36 | ],
37 | rules: {
38 | 'vue/multi-word-component-names': 'off',
39 | 'prettier/prettier': 'error', // 确保代码风格一致性
40 | },
41 | globals: {
42 | ElNotification: 'readonly',
43 | ElMessage: 'readonly',
44 | },
45 | }
46 |
--------------------------------------------------------------------------------
/src/composables/useFormat.ts:
--------------------------------------------------------------------------------
1 | type Categories = {
2 | [key: number]: string
3 | }
4 |
5 | // 将时间戳改为 2023-01-01 12:00:00 格式
6 | export default function useFormat() {
7 | const formatTimestamp = (timestamp: number): string => {
8 | const date = new Date(timestamp * 1000)
9 | const year = date.getFullYear()
10 | const month = (date.getMonth() + 1).toString().padStart(2, '0')
11 | const day = date.getDate().toString().padStart(2, '0')
12 | const hours = date.getHours().toString().padStart(2, '0')
13 | const minutes = date.getMinutes().toString().padStart(2, '0')
14 | const seconds = date.getSeconds().toString().padStart(2, '0')
15 | return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`
16 | }
17 |
18 | // 将分区ID展示成分区
19 | const getCategoryByValue = (value: number): string => {
20 | const categories: Categories = {
21 | 100: '游戏',
22 | 200: '生活',
23 | 300: '知识',
24 | 400: '科技',
25 | 500: '音乐',
26 | 600: '鬼畜',
27 | 700: '动画',
28 | 800: '时尚',
29 | 900: '舞蹈',
30 | 1000: '娱乐',
31 | 1100: '美食',
32 | 1200: '动物',
33 | }
34 |
35 | return categories[value] || '未知'
36 | }
37 |
38 | return { formatTimestamp, getCategoryByValue }
39 | }
40 |
--------------------------------------------------------------------------------
/src/components/user/avatar.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
9 |
10 |
11 |
12 |
51 |
52 |
59 |
--------------------------------------------------------------------------------
/src/main.ts:
--------------------------------------------------------------------------------
1 | import { createApp } from 'vue'
2 | import App from './App.vue'
3 | import router from './router'
4 | import { createPinia } from 'pinia'
5 | import { useUserStore } from './store/userStore'
6 | import ElementPlus from 'element-plus'
7 | import 'element-plus/dist/index.css'
8 | import * as ElementPlusIconsVue from '@element-plus/icons-vue'
9 | import 'element-plus/theme-chalk/display.css'
10 | import axios from 'axios'
11 |
12 | import 'vuetify/styles'
13 | import '@mdi/font/css/materialdesignicons.css'
14 | import { createVuetify } from 'vuetify'
15 | import * as components from 'vuetify/components'
16 | import * as directives from 'vuetify/directives'
17 |
18 | const app = createApp(App)
19 | const pinia = createPinia()
20 | const vuetify = createVuetify({
21 | components,
22 | directives,
23 | icons: {
24 | defaultSet: 'mdi',
25 | },
26 | defaults: {
27 | VBtn: {
28 | color: '#61aefb',
29 | variant: 'outlined',
30 | },
31 | },
32 | })
33 |
34 | app.use(pinia)
35 | app.use(router)
36 | app.use(ElementPlus)
37 | app.use(vuetify)
38 |
39 | // 全局注册element-plus icon
40 | for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
41 | app.component(key, component)
42 | }
43 |
44 | app.mount('#app')
45 |
46 | const userStore = useUserStore()
47 |
48 | userStore.fetchSessionData().then(() => {
49 | // 移除加载器
50 | const loader = document.getElementById('loader')
51 | if (loader) {
52 | loader.style.display = 'none'
53 | }
54 |
55 | // 全局设置 withCredentials
56 | axios.defaults.withCredentials = true
57 | })
58 |
--------------------------------------------------------------------------------
/src/views/about.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | 本网站仅供B站UP主认真猫学习编程使用。
5 |
6 | 前端设计和相关想法借鉴了我最喜欢的网站BiliOB观测者,这个网站也是我全栈开发的启蒙之地。
7 |
8 |
9 |
10 | 我的粉丝QQ群:883422705,这里也是网站讨论群。
11 | 我的技术交流群:542174643。
12 | 欢迎前往本网站github提issus和pr,也欢迎Star和Fork。
13 |
14 |
15 | 友情赞助
16 |
17 | 感谢腾讯云轻量应用服务器赞助本网站,让我一个高一学生也有机会上线网站。
18 |
19 | 前端
20 |
21 | 使用Vue3.0进行开发,应用了Pinia前端状态管理与vue-router前端路由,使用vite搭建项目。
22 |
23 |
24 | 前端UI组件库大部分采用的是Vuetify.js,少部分使用Element-plus(完全重构后会移除)
25 |
26 | 后端
27 | 后端采用Flask构建API。
28 | 数据库
29 | 数据库采用MongoDB存储文本数据。
30 | 缓存
31 | 缓存数据库采用的是Redis。未来会将热门视频加入Redis队列。
32 | 存储
33 | 使用了腾讯云对象存储COS存储媒体文件。
34 | CDN
35 | 使用了Cloudflare CDN Free计划。
36 |
37 |
38 |
39 |
40 |
43 |
44 |
62 |
--------------------------------------------------------------------------------
/src/views/admin/hideVideo.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
8 |
9 |
10 |
14 |
15 |
16 |
17 | 隐藏视频
18 |
19 |
20 |
21 |
22 |
23 |
58 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "frontend",
3 | "private": true,
4 | "version": "beta.1.2.1",
5 | "type": "module",
6 | "scripts": {
7 | "dev": "vite",
8 | "build": "vue-tsc && vite build",
9 | "preview": "vite preview"
10 | },
11 | "dependencies": {
12 | "@element-plus/icons-vue": "^2.1.0",
13 | "autoprefixer": "^10.4.16",
14 | "axios": "^1.5.1",
15 | "cropperjs": "^1.6.1",
16 | "echarts": "^5.4.3",
17 | "element-plus": "^2.4.1",
18 | "hls.js": "^1.4.12",
19 | "pinia": "^2.1.7",
20 | "postcss": "^8.4.31",
21 | "sass": "^1.69.4",
22 | "socket.io-client": "^4.7.2",
23 | "tailwindcss": "^3.3.4",
24 | "uuid": "^9.0.1",
25 | "video.js": "^8.6.1",
26 | "videojs-hlsjs-plugin": "^1.0.5",
27 | "vite-plugin-vuetify": "^1.0.2",
28 | "vue": "^3.3.4",
29 | "vue-cropperjs": "^5.0.0",
30 | "vue-router": "^4.0.13",
31 | "vuetify": "^3.3.23"
32 | },
33 | "devDependencies": {
34 | "@mdi/font": "^7.3.67",
35 | "@typescript-eslint/eslint-plugin": "^6.10.0",
36 | "@typescript-eslint/parser": "^6.10.0",
37 | "@vitejs/plugin-vue": "^4.2.3",
38 | "eslint": "^8.53.0",
39 | "eslint-config-prettier": "^9.0.0",
40 | "eslint-config-standard-with-typescript": "^39.1.1",
41 | "eslint-plugin-import": "^2.29.0",
42 | "eslint-plugin-n": "^16.3.1",
43 | "eslint-plugin-prettier": "^5.0.1",
44 | "eslint-plugin-promise": "^6.1.1",
45 | "eslint-plugin-vue": "^9.18.1",
46 | "prettier": "^3.0.3",
47 | "typescript": "^5.2.2",
48 | "unplugin-auto-import": "^0.16.6",
49 | "unplugin-vue-components": "^0.25.2",
50 | "vite": "^4.4.5",
51 | "vue-eslint-parser": "^9.3.2",
52 | "vue-tsc": "^1.8.5"
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/src/components/dataCount.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | 每小时访问量统计
5 |
6 |
7 |
8 |
9 |
10 |
82 |
--------------------------------------------------------------------------------
/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/user/avatar.vue')['default']
11 | Comment: typeof import('./src/components/common/comment.vue')['default']
12 | DataCount: typeof import('./src/components/dataCount.vue')['default']
13 | ElButton: typeof import('element-plus/es')['ElButton']
14 | ElCol: typeof import('element-plus/es')['ElCol']
15 | ElColorPicker: typeof import('element-plus/es')['ElColorPicker']
16 | ElDialog: typeof import('element-plus/es')['ElDialog']
17 | ElForm: typeof import('element-plus/es')['ElForm']
18 | ElFormItem: typeof import('element-plus/es')['ElFormItem']
19 | ElIcon: typeof import('element-plus/es')['ElIcon']
20 | ElInput: typeof import('element-plus/es')['ElInput']
21 | ElLink: typeof import('element-plus/es')['ElLink']
22 | ElOption: typeof import('element-plus/es')['ElOption']
23 | ElPopover: typeof import('element-plus/es')['ElPopover']
24 | ElProgress: typeof import('element-plus/es')['ElProgress']
25 | ElRadio: typeof import('element-plus/es')['ElRadio']
26 | ElRadioGroup: typeof import('element-plus/es')['ElRadioGroup']
27 | ElResult: typeof import('element-plus/es')['ElResult']
28 | ElSelect: typeof import('element-plus/es')['ElSelect']
29 | ElTag: typeof import('element-plus/es')['ElTag']
30 | ElUpload: typeof import('element-plus/es')['ElUpload']
31 | MmCard: typeof import('./src/components/rzm/mmCard.vue')['default']
32 | MmvCard: typeof import('./src/components/rzm/mmvCard.vue')['default']
33 | NavBar: typeof import('./src/components/navBar.vue')['default']
34 | Player: typeof import('./src/components/video/player.vue')['default']
35 | RouterLink: typeof import('vue-router')['RouterLink']
36 | RouterView: typeof import('vue-router')['RouterView']
37 | VideoGrid: typeof import('./src/components/video/VideoGrid.vue')['default']
38 | VideoList: typeof import('./src/components/video/VideoList.vue')['default']
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/src/views/log.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
12 |
13 |
Ver.{{ update.stage }}.{{
15 | update.version
16 | }} {{ update.date }}
18 |
19 |
20 |
21 |
25 |
28 | {{
29 | categoryIconMap[category]
30 | }}
31 | {{ categoryMap[category] }}
32 |
33 |
34 | -
39 | {{ content }}
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
78 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | 猫猫站
8 |
98 |
99 |
100 |
101 |
104 |
正在加载
105 |
110 |
111 |
112 |
113 |
114 |
115 |
--------------------------------------------------------------------------------
/src/assets/docs/log.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "stage": "alpha",
4 | "version": "0.0.0",
5 | "dotColor": "red",
6 | "size": "large",
7 | "date": "2023-10-08",
8 | "changes": {
9 | "milestone": ["萌发了想制作视频网站的念头"]
10 | }
11 | },
12 | {
13 | "stage": "alpha",
14 | "version": "0.1.0",
15 | "dotColor": "blue",
16 | "date": "2023-10-14",
17 | "size": "small",
18 | "changes": {
19 | "feature": ["加入基本的用户注册与登录", "加入基本的发布与查看视频"],
20 | "fix": ["修复了已知bug"]
21 | }
22 | },
23 | {
24 | "stage": "alpha",
25 | "version": "0.2.0",
26 | "dotColor": "blue",
27 | "date": "2023-10-19",
28 | "size": "small",
29 | "changes": {
30 | "feature": ["加入了签到功能", "现在可以AI审核视频了"],
31 | "architecture": ["使用腾讯云对象存储"]
32 | }
33 | },
34 | {
35 | "stage": "alpha",
36 | "version": "0.2.1",
37 | "dotColor": "green",
38 | "date": "2023-10-21",
39 | "size": "small",
40 | "changes": {
41 | "feature": ["使用上传视频分片技术,可以上传更大的视频了"],
42 | "fix": ["修复了已知bug"]
43 | }
44 | },
45 | {
46 | "stage": "beta",
47 | "version": "1.0.0",
48 | "dotColor": "red",
49 | "date": "2023-10-28",
50 | "size": "large",
51 | "changes": {
52 | "feature": [
53 | "加入了更好看的导航栏与侧边栏",
54 | "加入了主页留言板",
55 | "加入了开发日志"
56 | ],
57 | "beautify": ["使用vuetify代替element-plus,全局美化"],
58 | "architecture": [
59 | "使用pinia代替vuex",
60 | "使用TypeScript代替JavaScript",
61 | "使用vite代替webpack"
62 | ]
63 | }
64 | },
65 | {
66 | "stage": "beta",
67 | "version": "1.0.1",
68 | "dotColor": "green",
69 | "date": "2023-10-29",
70 | "size": "small",
71 | "changes": {
72 | "feature": ["加入了关于本网站和FAQ页面"],
73 | "other": ["优化经验系统", "优化了session持续时间"]
74 | }
75 | },
76 | {
77 | "stage": "beta",
78 | "version": "1.1.0",
79 | "dotColor": "blue",
80 | "date": "2023-11-01",
81 | "size": "small",
82 | "changes": {
83 | "feature": [
84 | "加入了喜闻乐见的排行榜功能",
85 | "添加了网站访问量统计公共页面",
86 | "首页视频选择新增随机、入站必刷页面"
87 | ],
88 | "beautify": [
89 | "美化了首页视频卡片与导航栏",
90 | "美化了加载页面",
91 | "美化了广告内容"
92 | ],
93 | "fix": [
94 | "修复了部分页面不能评论的bug",
95 | "修复了经验精度错误",
96 | "修复了竖屏竖屏电脑端显示问题"
97 | ]
98 | }
99 | },
100 | {
101 | "stage": "beta",
102 | "version": "1.2.0",
103 | "dotColor": "blue",
104 | "date": "2023-11-05",
105 | "size": "small",
106 | "changes": {
107 | "feature": ["通宵12小时完成了弹幕功能", "编辑资料页面回归"],
108 | "beautify": ["我会逐渐美化这个网站的"],
109 | "fix": ["修复了不能上传视频的bug", "修复了不能修改头像的bug"]
110 | }
111 | },{
112 | "stage": "beta",
113 | "version": "1.2.1",
114 | "dotColor": "green",
115 | "date": "2023-11-11",
116 | "size": "small",
117 | "changes": {
118 | "beautify": ["加了一些细节"],
119 | "fix": ["修复了一些BUG"],
120 | "architecture": ["重构前后端代码,更符合标准"]
121 | }
122 | }
123 | ]
124 |
--------------------------------------------------------------------------------
/src/views/user/rank.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
13 |
14 |
#{{ user.rank }}
15 |
16 |
17 |
{{ user.name }}
18 |
ID: {{ user.uid }}
19 |
{{ user.experience.toFixed(1) }}点
20 |
{{ formatTimestamp(user.time) }}
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
54 |
55 |
130 |
--------------------------------------------------------------------------------
/src/router/index.ts:
--------------------------------------------------------------------------------
1 | import { createRouter, createWebHistory, RouteRecordRaw } from 'vue-router'
2 |
3 | const routes: RouteRecordRaw[] = [
4 | {
5 | path: '/',
6 | name: '主页',
7 | component: () => import('@/views/index.vue'),
8 | },
9 | {
10 | path: '/log',
11 | name: '开发日志',
12 | component: () => import('@/views/log.vue'),
13 | },
14 | {
15 | path: '/about',
16 | name: '关于本网站',
17 | component: () => import('@/views/about.vue'),
18 | },
19 | {
20 | path: '/faq',
21 | name: 'FAQ',
22 | component: () => import('@/views/faq.vue'),
23 | },
24 | {
25 | path: '/data',
26 | name: '网站可公开数据',
27 | component: () => import('@/views/webData.vue'),
28 | },
29 | {
30 | path: '/member',
31 | name: '创作中心',
32 | component: () => import('@/views/fatherContainer.vue'),
33 | children: [
34 | {
35 | path: 'upload',
36 | name: '上传',
37 | meta: { layout: 'form' },
38 | component: () => import('@/views/member/upload.vue'),
39 | },
40 | ],
41 | },
42 | {
43 | path: '/user',
44 | name: '我的信息',
45 | component: () => import('@/views/fatherContainer.vue'),
46 | children: [
47 | {
48 | path: 'signin',
49 | name: '登录',
50 | meta: { layout: 'form' },
51 | component: () => import('@/views/user/signin.vue'),
52 | },
53 | {
54 | path: 'signup',
55 | name: '注册',
56 | meta: { layout: 'form' },
57 | component: () => import('@/views/user/signup.vue'),
58 | },
59 | {
60 | path: 'setting',
61 | name: '修改个人信息',
62 | meta: { layout: 'form' },
63 | component: () => import('@/views/user/setting.vue'),
64 | },
65 | {
66 | path: 'rank',
67 | name: '喵绘者排行',
68 | component: () => import('@/views/user/rank.vue'),
69 | },
70 | ],
71 | },
72 | {
73 | path: '/video/:aid',
74 | component: () => import('@/views/video/detail.vue'),
75 | },
76 | {
77 | path: '/space/:uid',
78 | component: () => import('@/views/space/index.vue'),
79 | },
80 | {
81 | path: '/admin',
82 | name: '管理员',
83 | component: () => import('@/views/fatherContainer.vue'),
84 | children: [
85 | {
86 | path: 'hidevideo',
87 | name: '隐藏视频',
88 | meta: { layout: 'form' },
89 | component: () => import('@/views/admin/hideVideo.vue'),
90 | },
91 | ],
92 | },
93 | ]
94 |
95 | const router = createRouter({
96 | history: createWebHistory(),
97 | routes,
98 | })
99 |
100 | import { useUserStore } from '../store/userStore'
101 |
102 | router.beforeEach(async (to, _from, next) => {
103 | document.title = `${String(
104 | to.name || to.params.aid || to.params.uid
105 | )} - 猫猫站`
106 |
107 | const userStore = useUserStore()
108 |
109 | if (userStore.sessionData.isload == false) {
110 | await userStore.fetchSessionData()
111 | }
112 |
113 | const { signin, status } = userStore.sessionData
114 |
115 | if (signin && (to.path === '/user/signup' || to.path === '/user/signin')) {
116 | next('/')
117 | return
118 | }
119 |
120 | if (
121 | !signin &&
122 | (to.path === '/member/upload' || to.path === '/user/setting')
123 | ) {
124 | next('/user/signin')
125 | return
126 | }
127 | // TODO: 这只是一个应急方式,后续需要改成动态路由
128 | if (status !== 1 && to.path === '/admin/hidevideo') {
129 | next('/')
130 | return
131 | }
132 |
133 | next()
134 | })
135 |
136 | export default router
137 |
--------------------------------------------------------------------------------
/src/views/user/signin.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
14 |
21 |
22 |
32 |
38 |
39 |
40 | 登录
43 | 注册
44 |
45 |
46 |
47 |
48 |
49 |
139 |
--------------------------------------------------------------------------------
/src/components/video/VideoList.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | 新发布
5 | 随机
6 | 热门
7 | 入站必刷
8 |
9 |
10 |
11 | loadMore('new')"
15 | />
16 |
17 |
18 | loadMore('random')"
22 | />
23 |
24 |
25 | loadMore('hot')"
29 | />
30 |
31 |
32 | loadMore('must')"
36 | />
37 |
38 |
39 |
40 |
41 |
42 |
123 |
124 |
131 |
--------------------------------------------------------------------------------
/src/App.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 | 最新更新 - {{ latestUpdate.stage }}{{ latestUpdate.version }}
21 |
22 |
23 |
24 |
28 |
32 | {{
33 | categoryIconMap[category]
34 | }}
35 | {{ categoryMap[category] }}
36 |
37 |
38 | -
43 | {{ content }}
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 | 关闭
52 |
53 |
54 |
55 |
56 |
57 |
58 |
119 |
--------------------------------------------------------------------------------
/src/components/video/VideoGrid.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
11 |
12 |
16 |
17 |
18 |
19 | {{
21 | video.data.view || '0'
22 | }}
24 | {{
26 | video.data.like || '0'
27 | }}
29 | {{
31 | video.uploader_name
32 | }}
34 |
35 |
36 |
37 |
38 |
很抱歉,{{ video.hidden.reason }}
39 |
操作人:{{ video.hidden.operator_name }}
40 |
41 | {{
42 | video.title
43 | }}
44 |
45 |
46 |
47 | 加载更多
48 |
49 |
50 |
51 |
52 |
92 |
93 |
191 |
--------------------------------------------------------------------------------
/src/views/space/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
#{{ userInfo.exp_rank }} / {{ userInfo.name }}
7 |
8 | mdi-clock-outline
9 | {{ formatTimestamp(userInfo.registration_time) }}
10 |
11 |
12 | 关注: {{ userInfo.following_count || '0' }}
13 | 粉丝: {{ userInfo.followers_count || '0' }}
14 | 经验: {{ (userInfo.experience || '0').toFixed(1) }}
15 |
16 |
{{ isFollowing ? '取消关注' : '关注' }}
22 |
编辑资料
23 |
24 |
25 |
30 |
31 |
32 |
33 |
149 |
150 |
180 |
--------------------------------------------------------------------------------
/src/views/video/detail.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
{{ videoInfo.title }}
5 |
6 |
7 | {{
9 | videoInfo.data.view
10 | }}
12 | {{
14 | videoInfo.data.danmaku
15 | }}
17 |
19 | {{ formatTimestamp(videoInfo.time) }}
21 |
22 |
23 |
24 |
31 |
32 |
33 |
34 |
39 |
46 | {{ videoInfo.data.like || '0' }}
47 |
48 |
49 |
50 | {{ videoInfo.description || '-' }}
51 |
52 |
53 | 搬运于: {{ videoInfo.origin }}
54 |
55 |
56 | {{
57 | tag
58 | }}
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
#{{ upInfo.exp_rank }} / {{ upInfo.name }}
70 |
71 |
72 | {{ (upInfo.experience || '0').toFixed(1) }} 点经验
73 |
74 |
前往TA的空间
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
178 |
179 |
205 |
--------------------------------------------------------------------------------
/src/components/common/comment.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
9 |
10 | 提交
11 |
12 |
13 |
14 |
18 |
19 |
20 | #{{ comment.exp_rank }} / {{ comment.username }}
21 | {{ comment.floor }}楼
24 |
25 | {{
26 | formatTimestamp(comment.time)
27 | }}
28 | {{ comment.content }}
29 |
30 |
35 |
36 |
37 |
38 |
39 |
40 |
45 |
46 |
52 | 提交回复
53 |
54 |
55 |
56 |
61 |
62 |
63 | #{{ reply.exp_rank }} / {{ reply.username }}
64 | {{ reply.floor }}楼
67 |
68 | {{
69 | formatTimestamp(reply.time)
70 | }}
71 | {{ reply.content }}
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
209 |
--------------------------------------------------------------------------------
/src/components/navBar.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
12 |
13 |
17 | 猫猫站
20 |
21 |
22 |
23 |
24 | {{ hasCheckedIn ? '已签到' : '签到' }}
31 | mdi-upload投稿
34 |
35 |
36 |
37 |
38 |
44 |
45 |
46 |
47 |
48 |
49 |
50 | {{ sessionData.name }}
51 | UID:{{ sessionData.uid }}
52 |
53 |
54 |
55 |
56 |
57 | 经验:{{ (sessionData.checkin.experience || 0).toFixed(1) }} (#{{
58 | sessionData.checkin.exp_rank
59 | }})
60 |
61 | 积分:{{ (sessionData.checkin.points || 0).toFixed(1) }}
62 |
63 |
64 |
65 |
66 |
72 |
73 |
74 |
75 |
76 | {{ item.text }}
77 |
78 |
79 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
202 |
203 |
237 |
--------------------------------------------------------------------------------
/src/views/user/setting.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
12 |
(event.target.src = '/default_face.png')"
17 | />
18 |
19 |
20 | handleDialogClose(val)"
24 | >
25 |
33 |
37 |
38 |
39 |
49 |
54 |
55 |
56 |
62 | 修改
63 |
64 |
65 |
66 |
67 |
73 |
74 | 返回到主页
75 |
76 |
77 |
78 |
79 |
80 |
212 |
213 |
252 |
--------------------------------------------------------------------------------
/src/views/user/signup.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
14 |
15 |
19 |
20 |
36 |
37 |
38 |
39 |
40 |
41 |
47 | {{ formData.btn.text }}
48 |
49 |
50 |
51 |
52 |
58 | {{ formData.btn.text }}
59 |
60 |
61 |
62 |
72 |
79 |
80 |
90 |
95 |
96 |
106 |
112 |
113 |
123 |
129 |
130 |
131 | 注册
134 | 登录
135 |
136 |
137 |
138 |
139 |
140 |
278 |
--------------------------------------------------------------------------------
/src/views/member/upload.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
15 |
16 | 上传
17 |
18 | 文件大小不能超过500M。
19 |
20 |
21 |
25 |
26 | 正在加载...
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
46 |
52 |
53 |
58 |
59 |
60 |
61 |
62 |
72 |
77 |
78 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
111 |
112 |
113 |
123 |
127 | 自制
128 | 转载
129 |
130 |
131 |
132 |
133 |
144 |
148 |
149 |
150 |
151 |
156 |
161 | {{ tag }}
162 |
163 |
168 |
169 |
170 | 提交
177 |
178 | 正在上传,请不要关闭此页面...
179 |
180 |
181 |
182 |
188 |
189 | 返回到主页
190 |
191 |
192 |
193 |
194 |
195 |
457 |
458 |
503 |
--------------------------------------------------------------------------------
/src/components/video/player.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
26 | {{ danmaku.content }}
27 |
28 |
29 |
35 |
36 |
37 |
38 |
39 | 弹幕BETA版测试
40 |
41 |
42 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
383 |
384 |
543 |
--------------------------------------------------------------------------------