├── .env
├── .env.development
├── .env.production
├── .env.testing
├── .eslintrc.cjs
├── .gitignore
├── .vscode
└── extensions.json
├── LICENSE
├── README.md
├── index.html
├── jsconfig.json
├── package-lock.json
├── package.json
├── public
└── favicon.ico
├── src
├── App.vue
├── api
│ ├── apply
│ │ └── index.js
│ ├── auth
│ │ └── index.js
│ ├── conversation
│ │ └── index.js
│ ├── expression
│ │ └── index.js
│ ├── file
│ │ └── index.js
│ ├── friend
│ │ └── index.js
│ ├── grouping
│ │ └── index.js
│ ├── index.js
│ ├── media
│ │ └── index.js
│ ├── message
│ │ └── index.js
│ ├── room
│ │ └── index.js
│ └── user
│ │ └── index.js
├── assets
│ ├── fonts
│ │ ├── AppleChancery.ttf
│ │ ├── BarbaraHand.ttf
│ │ └── JoinedUp.ttf
│ ├── images
│ │ ├── gitee.png
│ │ ├── github.png
│ │ ├── group.png
│ │ ├── logo.png
│ │ ├── official-account-qr-code.png
│ │ ├── official-account.png
│ │ ├── qq.png
│ │ ├── wechat-qr-code.png
│ │ └── wechat.png
│ └── sass
│ │ ├── _animation.scss
│ │ ├── _element.scss
│ │ ├── _font.scss
│ │ ├── _global.scss
│ │ ├── _normalize.scss
│ │ ├── _nprogress.scss
│ │ ├── _root.scss
│ │ ├── _transition.scss
│ │ ├── _variable.scss
│ │ └── index.scss
├── common
│ ├── constants
│ │ ├── file.js
│ │ └── index.js
│ ├── enums
│ │ ├── apply.js
│ │ ├── index.js
│ │ ├── media.js
│ │ ├── message.js
│ │ ├── user.js
│ │ └── websocket.js
│ ├── props
│ │ └── index.js
│ ├── rules
│ │ └── user.js
│ └── utils
│ │ ├── index.js
│ │ ├── regular.js
│ │ ├── storage.js
│ │ └── websocket.js
├── components
│ ├── apply-friend-dialog
│ │ ├── components
│ │ │ └── form-ui
│ │ │ │ ├── index.js
│ │ │ │ └── index.vue
│ │ └── index.vue
│ ├── avatar-upload
│ │ └── index.vue
│ ├── avatar
│ │ └── index.vue
│ ├── brand
│ │ └── index.vue
│ ├── captcha-input
│ │ └── index.vue
│ ├── card
│ │ └── index.vue
│ ├── context-menu
│ │ └── index.vue
│ ├── countdown-button
│ │ └── index.vue
│ ├── countdown
│ │ └── index.vue
│ ├── empty
│ │ └── index.vue
│ ├── filing
│ │ └── index.vue
│ ├── loading
│ │ └── index.vue
│ ├── message-send-status
│ │ └── index.vue
│ ├── online-dot
│ │ └── index.vue
│ ├── panel
│ │ └── index.vue
│ ├── router
│ │ └── index.vue
│ ├── search-dialog
│ │ ├── components
│ │ │ ├── group-list-panel
│ │ │ │ └── index.vue
│ │ │ ├── user-card
│ │ │ │ └── index.vue
│ │ │ └── user-list-panel
│ │ │ │ └── index.vue
│ │ └── index.vue
│ ├── timer
│ │ └── index.vue
│ ├── upload
│ │ └── index.vue
│ ├── user-dialog
│ │ └── index.vue
│ └── user-panel
│ │ └── index.vue
├── directive
│ ├── index.js
│ └── longpress
│ │ └── index.js
├── hooks
│ ├── bind-exposed.js
│ ├── debounce-ref.js
│ └── model.js
├── main.js
├── router
│ └── index.js
├── stores
│ ├── index.js
│ ├── modules
│ │ ├── apply.js
│ │ ├── auth.js
│ │ ├── conversation.js
│ │ ├── expression.js
│ │ ├── grouping.js
│ │ ├── media.js
│ │ ├── room.js
│ │ ├── user.js
│ │ └── websocket.js
│ └── root.js
└── views
│ ├── apply
│ ├── components
│ │ ├── apply-card
│ │ │ └── index.vue
│ │ ├── apply-panel
│ │ │ └── index.vue
│ │ └── pass-dialog
│ │ │ ├── components
│ │ │ └── form-ui
│ │ │ │ ├── index.js
│ │ │ │ └── index.vue
│ │ │ └── index.vue
│ └── index.vue
│ ├── conversation
│ ├── components
│ │ ├── conversation-card
│ │ │ └── index.vue
│ │ ├── editor
│ │ │ ├── components
│ │ │ │ ├── audio
│ │ │ │ │ └── index.vue
│ │ │ │ ├── expression
│ │ │ │ │ └── index.vue
│ │ │ │ ├── file
│ │ │ │ │ └── index.vue
│ │ │ │ ├── image
│ │ │ │ │ └── index.vue
│ │ │ │ ├── video-call
│ │ │ │ │ └── index.vue
│ │ │ │ └── voice-call
│ │ │ │ │ └── index.vue
│ │ │ └── index.vue
│ │ ├── group-user-panel
│ │ │ └── index.vue
│ │ ├── group-user
│ │ │ └── index.vue
│ │ ├── message-panel
│ │ │ └── index.vue
│ │ └── message
│ │ │ ├── components
│ │ │ ├── audio-message
│ │ │ │ └── index.vue
│ │ │ ├── file-message
│ │ │ │ └── index.vue
│ │ │ ├── image-message
│ │ │ │ └── index.vue
│ │ │ └── text-message
│ │ │ │ └── index.vue
│ │ │ └── index.vue
│ └── index.vue
│ ├── friend
│ ├── components
│ │ ├── friend-crad
│ │ │ └── index.vue
│ │ ├── friend-panel
│ │ │ └── index.vue
│ │ └── headbar
│ │ │ └── index.vue
│ └── index.vue
│ ├── group
│ └── index.vue
│ ├── layout
│ ├── components
│ │ ├── media-dialog
│ │ │ ├── components
│ │ │ │ ├── operation
│ │ │ │ │ └── index.vue
│ │ │ │ ├── status
│ │ │ │ │ └── index.vue
│ │ │ │ └── user-box
│ │ │ │ │ └── index.vue
│ │ │ └── index.vue
│ │ ├── promote
│ │ │ └── index.vue
│ │ ├── sidebar
│ │ │ ├── components
│ │ │ │ ├── edit-email-dialog
│ │ │ │ │ ├── index.vue
│ │ │ │ │ └── ui
│ │ │ │ │ │ ├── index.js
│ │ │ │ │ │ └── index.vue
│ │ │ │ ├── edit-info-dialog
│ │ │ │ │ ├── index.vue
│ │ │ │ │ └── ui
│ │ │ │ │ │ ├── index.js
│ │ │ │ │ │ └── index.vue
│ │ │ │ ├── publicize
│ │ │ │ │ └── index.vue
│ │ │ │ └── tabbar
│ │ │ │ │ ├── components
│ │ │ │ │ ├── tab-apply
│ │ │ │ │ │ └── index.vue
│ │ │ │ │ ├── tab-conversation
│ │ │ │ │ │ └── index.vue
│ │ │ │ │ ├── tab-exit
│ │ │ │ │ │ └── index.vue
│ │ │ │ │ ├── tab-friend
│ │ │ │ │ │ └── index.vue
│ │ │ │ │ ├── tab-group
│ │ │ │ │ │ └── index.vue
│ │ │ │ │ └── tab
│ │ │ │ │ │ └── index.vue
│ │ │ │ │ └── index.vue
│ │ │ └── index.vue
│ │ └── websocket
│ │ │ └── index.vue
│ └── index.vue
│ └── login
│ ├── components
│ ├── login-form
│ │ ├── index.vue
│ │ └── ui
│ │ │ ├── index.js
│ │ │ └── index.vue
│ ├── other
│ │ ├── index.vue
│ │ └── qq
│ │ │ └── index.vue
│ ├── panel
│ │ └── index.vue
│ └── register-form
│ │ ├── index.vue
│ │ └── ui
│ │ ├── index.js
│ │ └── index.vue
│ └── index.vue
└── vite.config.js
/.env:
--------------------------------------------------------------------------------
1 | # 项目启动端口
2 | VITE_PORT = '9585'
3 |
--------------------------------------------------------------------------------
/.env.development:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gmingchen/chatterbox/fdef8d4bc876acdf7dcf4b294cd4cfb73098329b/.env.development
--------------------------------------------------------------------------------
/.env.production:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gmingchen/chatterbox/fdef8d4bc876acdf7dcf4b294cd4cfb73098329b/.env.production
--------------------------------------------------------------------------------
/.env.testing:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gmingchen/chatterbox/fdef8d4bc876acdf7dcf4b294cd4cfb73098329b/.env.testing
--------------------------------------------------------------------------------
/.eslintrc.cjs:
--------------------------------------------------------------------------------
1 | /* eslint-env node */
2 | module.exports = {
3 | root: true,
4 | 'extends': [
5 | 'plugin:vue/vue3-essential',
6 | 'eslint:recommended',
7 | './.eslintrc-auto-import.json',
8 | ],
9 | env: {
10 | browser: true,
11 | node: true,
12 | es6: true,
13 | },
14 | parserOptions: {
15 | ecmaVersion: 'latest'
16 | },
17 | rules: {
18 | 'vue/multi-word-component-names': [0],
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/.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 | .DS_Store
12 | dist
13 | dist-ssr
14 | coverage
15 | *.local
16 |
17 | dist/
18 | dist.zip
19 | chatterbox/
20 | chatterbox.zip
21 | deploy/
22 | .eslintrc-auto-import.json
23 |
24 | /cypress/videos/
25 | /cypress/screenshots/
26 |
27 | # Editor directories and files
28 | .vscode/*
29 | !.vscode/extensions.json
30 | .idea
31 | *.suo
32 | *.ntvs*
33 | *.njsproj
34 | *.sln
35 | *.sw?
36 |
37 | *.tsbuildinfo
38 |
--------------------------------------------------------------------------------
/.vscode/extensions.json:
--------------------------------------------------------------------------------
1 | {
2 | "recommendations": [
3 | "Vue.volar",
4 | "dbaeumer.vscode-eslint"
5 | ]
6 | }
7 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2021 GuMingChen
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | Chatterbox(话匣子)
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 | ### 简介
38 |
39 | 🎈[Chatterbox(话匣子)](https://github.com/gmingchen/chatterbox)是`im-vue`重构后的即时聊天系统🆕。
40 |
41 | 🎃目前前端只有基于 [vue3](https://github.com/vuejs/vue-next)、[element-plus](https://github.com/element-plus/element-plus) 实现的相关内容,后续会分别实现 `react`、`h5`版本。
42 | 🤿后端是基于 __`java`__ 的 __`springboot`__ 、 __`netty`__ 实现。
43 |
44 | 🔔比较关键的技术点是通过 `Websocket` 实现了消息的实时传递 和 通过 `RTCPeerConnection` 实现语音通话、视频通话。
45 |
46 | [](https://api.star-history.com/svg?repos=gmingchen/chatterbox&type=Date)
47 |
48 | ###### 已内置如下功能:
49 | - [X] 邮箱登录、注册、个人信息编辑
50 | - [X] 用户搜索
51 | - [X] 好友申请
52 | - [X] 好友私聊、群聊
53 | - [X] 文字消息
54 | - [X] 图片消息
55 | - [X] 音频消息
56 | - [X] 文件消息
57 | - [X] 好友通话
58 | - [X] 语音通话
59 | - [X] 视频通话
60 |
61 | 🏷️🏷️🏷️后续会 __`持续迭代更新`__,点个 ⭐star 不错过更多的功能更新😎。
62 |
63 | ### 在线预览
64 | > ☀️
65 | > [👉 在线预览 👀](https://chatterbox.gumingchen.icu)
66 | >
67 | > 服务器比较low,访问有点慢!等有条件了再加配!😬
68 | >
69 | > 如果觉得还不错的话,请点个 ⭐star 支持一下吧,这将是对我最大的支持和鼓励☕!
70 | > 🌙
71 |
72 | > ⚠️
73 | > 如果想要旧版本相关内容请移步👉` [old分支](https://github.com/gmingchen/chatterbox/tree/old)
74 | > 🛑
75 |
76 | #### 演示图片
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 | ### 开发
105 | > ⚠️
106 | > 前提条件: 已安装 18.3 或更高版本的 Node.js `
107 | > 建议不要用直接使用 cnpm 安装,可以通过配置 registry 来解决 npm 安装速度慢或中断的问题。
108 | > 🛑
109 | ```bash
110 |
111 | # 克隆项目
112 | git clone https://github.com/gmingchen/chatterbox.git
113 |
114 | # 进入项目目录
115 | cd chatterbox
116 |
117 | # 安装依赖
118 | npm install
119 |
120 | # 启动服务
121 | npm run dev
122 |
123 | # 发布
124 | npm run build
125 | ```
126 |
127 | ### 关于作者
128 | Hi there, I'm [Slipper](https://github.com/gmingchen)(拖孩)👋. Thank you for your attention ⭐!
129 | I'm a code enthusiast who has been working in the IT industry for many years.
130 | I like open source and all interesting things and want to try to do it.
131 | I want to be an interesting person and create something that can be remembered by others.
132 | If you want to write code with me, you can contact me for internal promotion.
133 |
134 | - 🔭 I’m currently working on [万店掌](https://www.ovopark.com/)
135 | - 📫 How to reach me: ```🐧1240235512``` ```🛰️Gy1240235512``` ```📪gumingchen@foxmail.com```
136 | - 🌏 How to follow me: [Github](https://github.com/gmingchen) [Gitee](https://gitee.com/shychen) [掘金](https://juejin.cn/user/4103845398710846) [简书](https://www.jianshu.com/u/81a5a02678d3)
137 | - ❤️ I like playing 🎮, sleeping in 🛌 and coding 👨💻.
138 |
139 | 
140 |
141 |
142 |
143 | 公众号
144 | 个人微信
145 | 交流群
146 | 摸鱼群
147 |
148 |
149 |
150 |
151 |
152 |
153 |
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 |
189 |
190 | ### 其它开源项目
191 |
192 | [vue3-element-plus-admin](https://github.com/gmingchen/vue3-element-plus-admin)
193 |
194 | 是一个管理后台基础功能框架,基于 [vue3](https://github.com/vuejs/vue-next) 、 [element-plus](https://github.com/element-plus/element-plus) 和 [typescript](https://github.com/microsoft/TypeScript) 实现。内置了 i18n 国际化,动态路由,权限验证。-[私活神器]
195 |
196 | [java-admin-base](https://github.com/gmingchen/java-admin-base)
197 |
198 | 是一个管理后台基础功能框架 [base-refactoring](https://github.com/gmingchen/vue3-element-plus-admin/tree/base-refactoring) 分支的后端代码,基于 __`java`__ 的 __`springboot`__
199 |
200 | [nod-server](https://github.com/gmingchen/node-server)
201 | 是一个基于 node 开发的后端服务框架,只要你会 SQL 就也可以写接口了,再也不用看后端的脸色了。
202 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Chatterbox
8 |
69 |
78 |
79 |
80 |
81 |
82 |
83 |
C
84 |
h
85 |
a
86 |
t
87 |
t
88 |
e
89 |
r
90 |
b
91 |
o
92 |
x
93 |
94 |
95 |
96 |
97 |
98 |
--------------------------------------------------------------------------------
/jsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "paths": {
4 | "@/*": ["./src/*"]
5 | }
6 | },
7 | "exclude": ["node_modules", "dist"]
8 | }
9 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "chatterbox",
3 | "version": "1.0.1",
4 | "private": true,
5 | "type": "module",
6 | "scripts": {
7 | "dev": "vite",
8 | "build": "vite build",
9 | "deploy": "vite build --mode production && cross-env NODE_ENV=production node ./deploy",
10 | "preview": "vite preview",
11 | "lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs --fix --ignore-path .gitignore"
12 | },
13 | "dependencies": {
14 | "@element-plus/icons-vue": "^2.3.1",
15 | "axios": "^1.6.8",
16 | "element-plus": "^2.7.2",
17 | "js-cookie": "^3.0.5",
18 | "nprogress": "^0.2.0",
19 | "pinia": "^2.1.7",
20 | "vue": "^3.4.21",
21 | "vue-router": "^4.3.0"
22 | },
23 | "devDependencies": {
24 | "@iconify-json/ep": "^1.1.15",
25 | "@iconify-json/ic": "^1.1.17",
26 | "@vitejs/plugin-vue": "^5.0.4",
27 | "@vitejs/plugin-vue-jsx": "^3.1.0",
28 | "cross-env": "^7.0.3",
29 | "eslint": "^8.57.0",
30 | "eslint-plugin-vue": "^9.23.0",
31 | "ora": "^5.4.1",
32 | "qs": "^6.12.1",
33 | "sass": "^1.77.1",
34 | "sass-loader": "^14.2.1",
35 | "scp2": "^0.5.0",
36 | "unplugin-auto-import": "^0.17.6",
37 | "unplugin-icons": "^0.19.0",
38 | "unplugin-vue-components": "^0.27.0",
39 | "vite": "^5.2.8",
40 | "vite-plugin-vue-devtools": "^7.0.25"
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gmingchen/chatterbox/fdef8d4bc876acdf7dcf4b294cd4cfb73098329b/public/favicon.ico
--------------------------------------------------------------------------------
/src/App.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
15 |
16 |
25 |
26 |
35 |
--------------------------------------------------------------------------------
/src/api/apply/index.js:
--------------------------------------------------------------------------------
1 | import service from '..'
2 |
3 | /**
4 | * 好友申请
5 | * @param {*} params
6 | * @returns
7 | */
8 | export function applyFriendApi(data) {
9 | return service({
10 | url: '/apply/friend',
11 | method: 'post',
12 | data
13 | })
14 | }
15 |
16 | /**
17 | * 审核好友
18 | * @param {*} params
19 | * @returns
20 | */
21 | export function reviewFriendApi(data) {
22 | return service({
23 | url: '/apply/friend/review',
24 | method: 'post',
25 | data
26 | })
27 | }
28 |
29 |
30 | /**
31 | * 审核列表
32 | * @param {*} params
33 | * @returns
34 | */
35 | export function pageApi(params) {
36 | return service({
37 | url: '/apply/page',
38 | method: 'get',
39 | params
40 | })
41 | }
42 |
43 | /**
44 | * 待审核数量
45 | * @returns
46 | */
47 | export function auditCountApi() {
48 | return service({
49 | url: '/apply/audit/count',
50 | method: 'get'
51 | })
52 | }
53 |
--------------------------------------------------------------------------------
/src/api/auth/index.js:
--------------------------------------------------------------------------------
1 | import service from '../'
2 |
3 | /**
4 | * 注册验证码
5 | * @param {*} params
6 | * @returns
7 | */
8 | export function registerCaptchaApi(params) {
9 | return service({
10 | url: '/auth/captcha/register',
11 | method: 'get',
12 | params
13 | })
14 | }
15 |
16 | /**
17 | * 注册
18 | * @param {*} params
19 | * @returns
20 | */
21 | export function registerApi(data) {
22 | return service({
23 | url: '/auth/register',
24 | method: 'post',
25 | data
26 | })
27 | }
28 |
29 | /**
30 | * 登录验证码
31 | * @param {*} params
32 | * @returns
33 | */
34 | export function loginCaptchaApi(params) {
35 | return service({
36 | url: '/auth/captcha/login',
37 | method: 'get',
38 | params
39 | })
40 | }
41 |
42 | /**
43 | * 登录
44 | * @param {*} params
45 | * @returns
46 | */
47 | export function loginApi(data) {
48 | return service({
49 | url: '/auth/login',
50 | method: 'post',
51 | data
52 | })
53 | }
54 |
55 | /**
56 | * QQ登录
57 | * @param {*} params
58 | * @returns
59 | */
60 | export function loginQQApi(params) {
61 | return service({
62 | url: '/auth/login/qq',
63 | method: 'get',
64 | params
65 | })
66 | }
67 |
68 |
69 | /**
70 | * 获取当前用户信息
71 | * @param {*} params
72 | * @returns
73 | */
74 | export function userInfoApi() {
75 | return service({
76 | url: '/auth/user/info',
77 | method: 'get',
78 | })
79 | }
80 |
81 | /**
82 | * 退出登录
83 | * @param {*} params
84 | * @returns
85 | */
86 | export function logoutApi(data) {
87 | return service({
88 | url: '/auth/logoff',
89 | method: 'post',
90 | data
91 | })
92 | }
93 |
--------------------------------------------------------------------------------
/src/api/conversation/index.js:
--------------------------------------------------------------------------------
1 | import service from '..'
2 |
3 | /**
4 | * 会话列表
5 | * @param {*} params
6 | * @returns
7 | */
8 | export function listApi() {
9 | return service({
10 | url: '/conversation/list',
11 | method: 'get',
12 | })
13 | }
14 |
15 | /**
16 | * 新增会话
17 | * @param {*} params
18 | * @returns
19 | */
20 | export function createApi(data) {
21 | return service({
22 | url: '/conversation/create',
23 | method: 'post',
24 | data
25 | })
26 | }
27 |
--------------------------------------------------------------------------------
/src/api/expression/index.js:
--------------------------------------------------------------------------------
1 | import service from '..'
2 |
3 | /**
4 | * 表情选择列表
5 | * @param {*} params
6 | * @returns
7 | */
8 | export function listApi() {
9 | return service({
10 | url: '/expression/select',
11 | method: 'get',
12 | })
13 | }
14 |
--------------------------------------------------------------------------------
/src/api/file/index.js:
--------------------------------------------------------------------------------
1 | import service from '../'
2 | import { ContentType } from '@enums'
3 |
4 | /**
5 | * 参数处理
6 | * @param {*} params 参数
7 | * @returns
8 | */
9 | const paramsHandle = (params) => {
10 | const formData = new FormData()
11 | for (const key in params) {
12 | formData.append(key, params[key])
13 | }
14 | return formData
15 | }
16 | /**
17 | * service统一处理
18 | * @param {*} url api
19 | * @param {*} params 参数
20 | * @returns
21 | */
22 | const serviceHandle = (url, params) => {
23 | const data = paramsHandle(params)
24 | return service({
25 | url,
26 | method: 'post',
27 | data,
28 | headers: {
29 | 'Content-Type': ContentType.UPLOAD
30 | }
31 | })
32 | }
33 |
34 | const uploadAvatar = '/file/upload/avatar'
35 | /**
36 | * 上传头像
37 | * @param {*} params
38 | * @returns
39 | */
40 | export function uploadAvatarApi(params) {
41 | return serviceHandle(uploadAvatar, params)
42 | }
43 | /**
44 | * 上传头像
45 | */
46 | export function uploadAvatarUrl() {
47 | return `${ service.defaults.baseURL }${ uploadAvatar }`
48 | }
49 |
50 | const uploadImage = '/file/upload/image'
51 | /**
52 | * 上传图片消息
53 | * @param {*} params
54 | * @returns
55 | */
56 | export function uploadImageApi(params) {
57 | return serviceHandle(uploadImage, params)
58 | }
59 | /**
60 | * 上传图片消息
61 | */
62 | export function uploadImageUrl() {
63 | return `${ service.defaults.baseURL }${ uploadImage }`
64 | }
65 |
66 | const uploadFile = '/file/upload/file'
67 | /**
68 | * 上传图片消息
69 | * @param {*} params
70 | * @returns
71 | */
72 | export function uploadFileApi(params) {
73 | return serviceHandle(uploadFile, params)
74 | }
75 | /**
76 | * 上传图片消息
77 | */
78 | export function uploadFileUrl() {
79 | return `${ service.defaults.baseURL }${ uploadFile }`
80 | }
81 |
82 | const uploadAudio = '/file/upload/audio'
83 | /**
84 | * 上传音频消息
85 | * @param {*} params
86 | * @returns
87 | */
88 | export function uploadAudioApi(blob) {
89 | const formData = new FormData()
90 | formData.append('file', blob,'.mp3');
91 | return service({
92 | url: uploadAudio,
93 | method: 'post',
94 | data: formData,
95 | headers: {
96 | 'Content-Type': ContentType.UPLOAD
97 | }
98 | })
99 | }
100 | /**
101 | * 上传音频消息
102 | */
103 | export function uploadAudioUrl() {
104 | return `${ service.defaults.baseURL }${ uploadAudio }`
105 | }
--------------------------------------------------------------------------------
/src/api/friend/index.js:
--------------------------------------------------------------------------------
1 | import service from '..'
2 |
3 | /**
4 | * 分组好友列表
5 | * @param {*} params
6 | * @returns
7 | */
8 | export function listApi() {
9 | return service({
10 | url: '/grouping/friend',
11 | method: 'get',
12 | })
13 | }
14 |
15 | /**
16 | * 删除好友
17 | * @param {*} params
18 | * @returns
19 | */
20 | export function deleteApi(data) {
21 | return service({
22 | url: '/friend/delete',
23 | method: 'post',
24 | data
25 | })
26 | }
27 |
--------------------------------------------------------------------------------
/src/api/grouping/index.js:
--------------------------------------------------------------------------------
1 | import service from '..'
2 |
3 | /**
4 | * 分组好友列表
5 | * @param {*} params
6 | * @returns
7 | */
8 | export function listApi() {
9 | return service({
10 | url: '/grouping/friend',
11 | method: 'get',
12 | })
13 | }
14 |
15 |
16 | /**
17 | * 分组选择列表
18 | * @param {*} params
19 | * @returns
20 | */
21 | export function selectListApi() {
22 | return service({
23 | url: '/grouping/select',
24 | method: 'get',
25 | })
26 | }
--------------------------------------------------------------------------------
/src/api/index.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 | import axios from 'axios'
3 | import qs from 'qs'
4 | import router from '@/router'
5 | import { ElMessage } from 'element-plus'
6 |
7 | import { MAPPING, CONTENT_TYPE, SUCCESS_CODE, TIME_OUT, AUTH_KEY } from '@constants'
8 | import { ContentType } from '@enums'
9 | import { blob2Json } from '@utils'
10 |
11 | /**
12 | * @description: 异常消息提示
13 | * @param {string} string
14 | * @return {*}
15 | * @author: gumingchen
16 | */
17 | const prompt = (message) => {
18 | ElMessage({
19 | message: message,
20 | type: 'warning',
21 | duration: 3000
22 | })
23 | }
24 |
25 | /**
26 | * @description: code处理
27 | * @param {number} code
28 | * @param {string} msg
29 | * @return {*}
30 | * @author: gumingchen
31 | */
32 | const codeHandle = (code, message) => {
33 | switch (code) {
34 | case 4006:
35 | case 4007:
36 | router.replace({ name: 'login' })
37 | prompt(message)
38 | useRootStore().clearData()
39 | break
40 | case 401:
41 | router.replace({
42 | name: '401'
43 | })
44 | break
45 | case 404:
46 | router.replace({
47 | name: '404'
48 | })
49 | break
50 | case 500:
51 | // router.replace({
52 | // name: '500'
53 | // })
54 | break
55 | default:
56 | prompt(message, false)
57 | break
58 | }
59 | }
60 |
61 | /**
62 | * @description: axios创建
63 | * @param {*}
64 | * @return {*}
65 | * @author: gumingchen
66 | */
67 | const service = axios.create({
68 | baseURL: `${ MAPPING }`,
69 | withCredentials: true,
70 | timeout: TIME_OUT,
71 | headers: {
72 | 'Content-Type': CONTENT_TYPE
73 | }
74 | })
75 |
76 | /**
77 | * @description: axios请求拦截器
78 | * @param {*}
79 | * @return {*}
80 | * @author: gumingchen
81 | */
82 | service.interceptors.request.use(
83 | config => {
84 | const { token } = useAuthStore()
85 | // 设置 token
86 | if (token.trim()) {
87 | config.headers[AUTH_KEY] = token.trim()
88 | }
89 | if (config.data) {
90 | if (config.headers['Content-Type'] === ContentType.FORM) {
91 | config.data = qs.stringify(config.data)
92 | }
93 | }
94 | return config
95 | },
96 | error => {
97 | console.log(error) // for debug
98 | return Promise.reject(error)
99 | }
100 | )
101 |
102 | /**
103 | * @description: axios响应拦截器
104 | * @param {*}
105 | * @return {*}
106 | * @author: gumingchen
107 | */
108 | service.interceptors.response.use(
109 | async response => {
110 | if (response.headers['content-type'] === ContentType.STREAM) {
111 | if (!response.data.code) {
112 | return {
113 | blob: response.data,
114 | name: decodeURI(response.headers['content-disposition'].replace('attachment;filename=', ''))
115 | }
116 | } else {
117 | return response.data || null
118 | }
119 | }
120 | const { responseType } = response.config
121 | if (responseType === 'blob') {
122 | response.data = await blob2Json(response.data)
123 | }
124 | const { code, message } = response.data
125 | if (!SUCCESS_CODE.includes(code)) {
126 | codeHandle(code, message)
127 | return null
128 | }
129 | return response.data || null
130 | },
131 | error => {
132 | if (error && error.response) {
133 | switch (error.response.status) {
134 | case 400:
135 | console.log('错误请求')
136 | break
137 | case 401:
138 | console.log('未授权,请重新登录')
139 | break
140 | case 403:
141 | console.log('拒绝访问')
142 | break
143 | case 404:
144 | console.log('请求错误,未找到该资源')
145 | break
146 | case 405:
147 | console.log('请求方法未允许')
148 | break
149 | case 408:
150 | console.log('请求超时')
151 | break
152 | case 411:
153 | console.log('需要知道长度')
154 | break
155 | case 413:
156 | console.log('请求的实体太大')
157 | break
158 | case 414:
159 | console.log('请求的URL太长')
160 | break
161 | case 415:
162 | console.log('不支持的媒体类型')
163 | break
164 | case 500:
165 | console.log('服务器端出错')
166 | break
167 | case 501:
168 | console.log('网络未实现')
169 | break
170 | case 502:
171 | console.log('网络错误')
172 | break
173 | case 503:
174 | console.log('服务不可用')
175 | break
176 | case 504:
177 | console.log('网络超时')
178 | break
179 | case 505:
180 | console.log('http版本不支持该请求')
181 | break
182 | default:
183 | console.log(`连接错误${ error.response.status }`)
184 | }
185 | } else {
186 | console.log('连接到服务器失败')
187 | // router.replace({
188 | // name: '500'
189 | // })
190 | }
191 | return Promise.reject(error)
192 | }
193 | )
194 |
195 | export default service
196 |
--------------------------------------------------------------------------------
/src/api/media/index.js:
--------------------------------------------------------------------------------
1 | import service from '..'
2 |
3 | /**
4 | * 语音请求
5 | * @param {*} params
6 | * @returns
7 | */
8 | export function voiceCallApi(data) {
9 | return service({
10 | url: '/media/voice/call',
11 | method: 'post',
12 | data
13 | })
14 | }
15 | /**
16 | * 取消语音请求
17 | * @param {*} params
18 | * @returns
19 | */
20 | export function voiceCancelApi(data) {
21 | return service({
22 | url: '/media/voice/cancel',
23 | method: 'post',
24 | data
25 | })
26 | }
27 | /**
28 | * 接受语音请求
29 | * @param {*} params
30 | * @returns
31 | */
32 | export function voiceAcceptApi(data) {
33 | return service({
34 | url: '/media/voice/accept',
35 | method: 'post',
36 | data
37 | })
38 | }
39 | /**
40 | * 拒绝语音请求
41 | * @param {*} params
42 | * @returns
43 | */
44 | export function voiceRejectApi(data) {
45 | return service({
46 | url: '/media/voice/reject',
47 | method: 'post',
48 | data
49 | })
50 | }
51 | /**
52 | * 挂断语音通话
53 | * @param {*} params
54 | * @returns
55 | */
56 | export function voiceCloseApi(data) {
57 | return service({
58 | url: '/media/voice/close',
59 | method: 'post',
60 | data
61 | })
62 | }
63 |
64 | /**
65 | * 视频请求
66 | * @param {*} params
67 | * @returns
68 | */
69 | export function videoCallApi(data) {
70 | return service({
71 | url: '/media/video/call',
72 | method: 'post',
73 | data
74 | })
75 | }
76 | /**
77 | * 取消视频请求
78 | * @param {*} params
79 | * @returns
80 | */
81 | export function videoCancelApi(data) {
82 | return service({
83 | url: '/media/video/cancel',
84 | method: 'post',
85 | data
86 | })
87 | }
88 | /**
89 | * 接受视频请求
90 | * @param {*} params
91 | * @returns
92 | */
93 | export function videoAcceptApi(data) {
94 | return service({
95 | url: '/media/video/accept',
96 | method: 'post',
97 | data
98 | })
99 | }
100 | /**
101 | * 拒绝视频请求
102 | * @param {*} params
103 | * @returns
104 | */
105 | export function videoRejectApi(data) {
106 | return service({
107 | url: '/media/video/reject',
108 | method: 'post',
109 | data
110 | })
111 | }
112 | /**
113 | * 挂断视频通话
114 | * @param {*} params
115 | * @returns
116 | */
117 | export function videoCloseApi(data) {
118 | return service({
119 | url: '/media/video/close',
120 | method: 'post',
121 | data
122 | })
123 | }
124 |
--------------------------------------------------------------------------------
/src/api/message/index.js:
--------------------------------------------------------------------------------
1 | import service from '..'
2 |
3 | /**
4 | * 消息分页列表
5 | * @param {*} params
6 | * @returns
7 | */
8 | export function pageApi(params) {
9 | return service({
10 | url: '/message/page/id',
11 | method: 'get',
12 | params
13 | })
14 | }
15 |
16 | /**
17 | * 发送消息
18 | * @param {*} data
19 | * @returns
20 | */
21 | export function sendApi(data) {
22 | return service({
23 | url: '/message/create',
24 | method: 'post',
25 | data
26 | })
27 | }
--------------------------------------------------------------------------------
/src/api/room/index.js:
--------------------------------------------------------------------------------
1 | import service from '..'
2 |
3 | /**
4 | * 群房间用户分页列表
5 | * @param {*} params
6 | * @returns
7 | */
8 | export function roomGroupUserPageApi(params) {
9 | return service({
10 | url: '/roomGroupUser/page',
11 | method: 'get',
12 | params
13 | })
14 | }
15 |
16 | /**
17 | * 群房间用户数量
18 | * @param {*} params
19 | * @returns
20 | */
21 | export function roomGroupUserCountApi(params) {
22 | return service({
23 | url: '/roomGroupUser/count',
24 | method: 'get',
25 | params
26 | })
27 | }
28 |
--------------------------------------------------------------------------------
/src/api/user/index.js:
--------------------------------------------------------------------------------
1 | import service from '..'
2 |
3 | /**
4 | * 更新基本信息
5 | * @param {*} params
6 | * @returns
7 | */
8 | export function updateApi(data) {
9 | return service({
10 | url: '/user/update',
11 | method: 'post',
12 | data
13 | })
14 | }
15 |
16 | /**
17 | * 更新邮箱验证码
18 | * @param {*} params
19 | * @returns
20 | */
21 | export function updateEmailCaptchaApi(params) {
22 | return service({
23 | url: '/user/update/email/captcha',
24 | method: 'get',
25 | params
26 | })
27 | }
28 |
29 | /**
30 | * 更新邮箱
31 | * @param {*} params
32 | * @returns
33 | */
34 | export function updateEmailApi(data) {
35 | return service({
36 | url: '/user/update/email',
37 | method: 'post',
38 | data
39 | })
40 | }
41 |
42 | /**
43 | * 获取用户列表
44 | * @param {*} params
45 | * @returns
46 | */
47 | export function getUserListApi(params) {
48 | return service({
49 | url: '/user/search',
50 | method: 'get',
51 | params
52 | })
53 | }
54 |
55 | /**
56 | * 获取用户信息
57 | * @param {*} params
58 | * @returns
59 | */
60 | export function getUserInfoApi(params) {
61 | return service({
62 | url: '/user/info',
63 | method: 'get',
64 | params
65 | })
66 | }
67 |
--------------------------------------------------------------------------------
/src/assets/fonts/AppleChancery.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gmingchen/chatterbox/fdef8d4bc876acdf7dcf4b294cd4cfb73098329b/src/assets/fonts/AppleChancery.ttf
--------------------------------------------------------------------------------
/src/assets/fonts/BarbaraHand.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gmingchen/chatterbox/fdef8d4bc876acdf7dcf4b294cd4cfb73098329b/src/assets/fonts/BarbaraHand.ttf
--------------------------------------------------------------------------------
/src/assets/fonts/JoinedUp.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gmingchen/chatterbox/fdef8d4bc876acdf7dcf4b294cd4cfb73098329b/src/assets/fonts/JoinedUp.ttf
--------------------------------------------------------------------------------
/src/assets/images/gitee.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gmingchen/chatterbox/fdef8d4bc876acdf7dcf4b294cd4cfb73098329b/src/assets/images/gitee.png
--------------------------------------------------------------------------------
/src/assets/images/github.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gmingchen/chatterbox/fdef8d4bc876acdf7dcf4b294cd4cfb73098329b/src/assets/images/github.png
--------------------------------------------------------------------------------
/src/assets/images/group.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gmingchen/chatterbox/fdef8d4bc876acdf7dcf4b294cd4cfb73098329b/src/assets/images/group.png
--------------------------------------------------------------------------------
/src/assets/images/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gmingchen/chatterbox/fdef8d4bc876acdf7dcf4b294cd4cfb73098329b/src/assets/images/logo.png
--------------------------------------------------------------------------------
/src/assets/images/official-account-qr-code.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gmingchen/chatterbox/fdef8d4bc876acdf7dcf4b294cd4cfb73098329b/src/assets/images/official-account-qr-code.png
--------------------------------------------------------------------------------
/src/assets/images/official-account.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gmingchen/chatterbox/fdef8d4bc876acdf7dcf4b294cd4cfb73098329b/src/assets/images/official-account.png
--------------------------------------------------------------------------------
/src/assets/images/qq.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gmingchen/chatterbox/fdef8d4bc876acdf7dcf4b294cd4cfb73098329b/src/assets/images/qq.png
--------------------------------------------------------------------------------
/src/assets/images/wechat-qr-code.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gmingchen/chatterbox/fdef8d4bc876acdf7dcf4b294cd4cfb73098329b/src/assets/images/wechat-qr-code.png
--------------------------------------------------------------------------------
/src/assets/images/wechat.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gmingchen/chatterbox/fdef8d4bc876acdf7dcf4b294cd4cfb73098329b/src/assets/images/wechat.png
--------------------------------------------------------------------------------
/src/assets/sass/_animation.scss:
--------------------------------------------------------------------------------
1 |
2 | // 旋转
3 | @keyframes rotate {
4 | from {
5 | transform: rotate(0deg)
6 | }
7 | to {
8 | transform: rotate(360deg)
9 | }
10 | }
11 |
12 | // 点
13 | @keyframes dots {
14 | 33.33% {
15 | content: ".";
16 | }
17 | 66.67% {
18 | content: "..";
19 | }
20 | 100% {
21 | content: "...";
22 | }
23 | }
--------------------------------------------------------------------------------
/src/assets/sass/_element.scss:
--------------------------------------------------------------------------------
1 | @forward "element-plus/theme-chalk/src/common/var.scss" with (
2 | $colors: (
3 | 'white': #fff,
4 | 'black': #000,
5 | 'primary': (
6 | 'base': #409eff,
7 | ),
8 | 'success': (
9 | 'base': #67c23a,
10 | ),
11 | 'warning': (
12 | 'base': #e6a23c,
13 | ),
14 | 'danger': (
15 | 'base': #f56c6c,
16 | ),
17 | 'error': (
18 | 'base': #f56c6c,
19 | ),
20 | 'info': (
21 | 'base': #909399,
22 | ),
23 | ),
24 | $text-color: (
25 | (
26 | 'primary': #303133,
27 | 'regular': #606266,
28 | 'secondary': #909399,
29 | 'placeholder': #a8abb2,
30 | 'disabled': #c0c4cc,
31 | )
32 | ),
33 | );
34 |
35 | @import "element-plus/theme-chalk/src/index.scss";
36 | @import 'element-plus/theme-chalk/dark/css-vars.css';
37 |
38 | .el-dialog {
39 | border-radius: var(--box-border-radius) !important;
40 | background-color: var(--wrap-background-color) !important;
41 | }
42 | .el-icon {
43 | font-style: normal;
44 | }
45 |
--------------------------------------------------------------------------------
/src/assets/sass/_font.scss:
--------------------------------------------------------------------------------
1 | @font-face {
2 | font-family: 'BarbaraHand';
3 | src: url("../fonts/BarbaraHand.ttf") format('truetype');
4 | }
5 | @font-face {
6 | font-family: 'AppleChancery';
7 | src: url("../fonts/AppleChancery.ttf") format('truetype');
8 | }
9 | @font-face {
10 | font-family: 'JoinedUp';
11 | src: url("../fonts/JoinedUp.ttf") format('truetype');
12 | }
13 |
--------------------------------------------------------------------------------
/src/assets/sass/_global.scss:
--------------------------------------------------------------------------------
1 | /**
2 | * todo: n->none 没有
3 | * todo: t->top 上
4 | * todo: r->right 右
5 | * todo: b->bottom 下
6 | * todo: l->left 左
7 | * todo: a->auto 自动
8 | */
9 |
10 | * {
11 | // 怪异盒子模型
12 | box-sizing: border-box;
13 | // 允许在单词内换行
14 | word-break: break-all;
15 | }
16 |
17 | html, body {
18 | height: 100%;
19 | }
20 |
21 | // 通用ul li
22 | ul {
23 | margin: 0;
24 | padding: 0;
25 | li {
26 | list-style-type: none;
27 | }
28 | }
29 |
30 | // todo: 后期若没有那么多可以改为 @each
31 | // 高度 宽度
32 | .height-full {
33 | height: 100% !important;
34 | }
35 | .min-height-full {
36 | min-height: 100% !important;
37 | }
38 | .height-unset {
39 | height: unset !important;
40 | }
41 | .width-full {
42 | width: 100% !important;
43 | }
44 | @for $i from 0 through 1000 {
45 | .height-#{$i} {
46 | height: #{$i}px;
47 | }
48 | .min-height-#{$i} {
49 | min-height: #{$i}px;
50 | }
51 | .max-height-#{$i} {
52 | max-height: #{$i}px;
53 | }
54 | .width-#{$i} {
55 | width: #{$i}px;
56 | }
57 | .min-width-#{$i} {
58 | min-width: #{$i}px;
59 | }
60 | .max-width-#{$i} {
61 | max-width: #{$i}px;
62 | }
63 | }
64 | // 字体大小
65 | @for $i from 12 through 60 {
66 | .font-size-#{$i} {
67 | font-size: #{$i}px;
68 | }
69 | }
70 | // 居中方式
71 | @each $var in left, center, right {
72 | .text-align-#{$var} {
73 | text-align: #{$var};
74 | }
75 | }
76 | // 外边框
77 | @for $i from 0 through 200 {
78 | .margin-#{$i} {
79 | margin: #{$i}px;
80 | }
81 | .margin-#{$i}-a {
82 | margin: #{$i}px auto;
83 | }
84 | .margin-#{$i}-n {
85 | margin: #{$i}px 0px;
86 | }
87 | .margin-n-#{$i} {
88 | margin: 0px #{$i}px;
89 | }
90 | .margin_t-#{$i} {
91 | margin-top: #{$i}px;
92 | }
93 | .margin_r-#{$i} {
94 | margin-right: #{$i}px;
95 | }
96 | .margin_b-#{$i} {
97 | margin-bottom: #{$i}px;
98 | }
99 | .margin_l-#{$i} {
100 | margin-left: #{$i}px;
101 | }
102 | }
103 | // 内边框
104 | @for $i from 1 through 200 {
105 | .padding-#{$i} {
106 | padding: #{$i}px;
107 | }
108 | .padding-#{$i}-n {
109 | padding: #{$i}px 0px;
110 | }
111 | .padding-n-#{$i} {
112 | padding: 0px #{$i}px;
113 | }
114 | .padding_t-#{$i} {
115 | padding-top: #{$i}px;
116 | }
117 | .padding_r-#{$i} {
118 | padding-right: #{$i}px;
119 | }
120 | .padding_b-#{$i} {
121 | padding-bottom: #{$i}px;
122 | }
123 | .padding_l-#{$i} {
124 | padding-left: #{$i}px;
125 | }
126 | }
127 | // 文字省略
128 | .ellipse {
129 | display: -webkit-box;
130 | overflow: hidden;
131 | text-overflow: ellipsis;
132 | word-break: break-all;
133 | -webkit-box-orient: vertical;
134 | -webkit-line-clamp: 1;
135 | }
136 | @for $i from 1 through 5 {
137 | .ellipse-#{$i} {
138 | display: -webkit-box;
139 | overflow: hidden;
140 | text-overflow: ellipsis;
141 | word-break: break-all;
142 | -webkit-box-orient: vertical;
143 | -webkit-line-clamp: #{$i};
144 | }
145 | }
146 | // 鼠标显示 手指
147 | .cursor-pointer {
148 | cursor: pointer;
149 | };
150 | // overflow
151 | $overflow: visible, hidden, scroll, auto, inherit;
152 | @each $var in $overflow {
153 | .overflow-#{$var} {
154 | overflow: #{$var};
155 | }
156 | }
157 |
158 | // 居中
159 | .position-center {
160 | position: absolute;
161 | top: 50%;
162 | left: 50%;
163 | transform: translate(-50%, -50%);
164 | }
165 |
166 | // todo: flex 布局
167 | $directions: row, row-reverse, column, column-reverse;
168 | $wraps: nowrap, wrap, wrap-reverse;
169 | $justifyContents: flex-start, flex-end, center, space-between, space-around;
170 | $alignItems: flex-start, flex-end, center, baseline, stretch;
171 | $alignContents: flex-start, flex-end, center, space-between, space-around, stretch;
172 |
173 | $alignSelfs: auto, flex-start, flex-end, center, baseline, stretch;
174 |
175 | .ff {
176 | display: flex;
177 | }
178 |
179 | // todo: 容器属性
180 | .flex {
181 | display: flex;
182 | display: -webkit-flex;
183 | display: -moz-box;
184 | display: -ms-flexbox;
185 | // flex-direction
186 | &_d {
187 | @each $var in $directions {
188 | &-#{$var} {
189 | @extend .flex;
190 | flex-direction: #{$var};
191 | }
192 | }
193 | }
194 | // flex-wrap
195 | &_w {
196 | @each $var in $wraps {
197 | &-#{$var} {
198 | @extend .flex;
199 | flex-wrap: #{$var};
200 | }
201 | }
202 | }
203 | // justify-content
204 | &_j_c {
205 | @each $var in $justifyContents {
206 | &-#{$var} {
207 | @extend .flex;
208 | justify-content: #{$var};
209 | }
210 | &_a_i-#{$var} {
211 | @extend .flex;
212 | justify-content: #{$var};
213 | align-items: #{$var};
214 | }
215 | }
216 | }
217 | // align-items
218 | &_a_i {
219 | @each $var in $alignItems {
220 | &-#{$var} {
221 | @extend .flex;
222 | align-items: #{$var};
223 | }
224 | }
225 | }
226 | // align-content
227 | &_a_c {
228 | @each $var in $alignContents {
229 | &-#{$var} {
230 | @extend .flex;
231 | align-content: #{$var};
232 | }
233 | }
234 | }
235 | // todo: 子元素属性
236 | &-item {
237 | // order
238 | @for $i from -20 to 20 {
239 | &_o-#{$i} {
240 | order: #{$i};
241 | }
242 | }
243 | // flex: flex-grow flex-shrink flex-basis
244 | &_f-a {
245 | flex: auto;
246 | }
247 | &_f-n {
248 | flex: none;
249 | }
250 | @for $i from 0 to 10 {
251 | &_f-#{$i} {
252 | flex: #{$i};
253 | }
254 | // @for $j from 1 to 10 {
255 | // @for $k from 1 to 10 {
256 | // &_f-#{$i}-#{$j}-#{$k} {
257 | // flex: #{&i} #{&j} #{&k};
258 | // }
259 | // }
260 | // }
261 | }
262 | // align-self
263 | @each $var in $alignSelfs {
264 | &_a_s-#{$var} {
265 | align-self: #{$var};
266 | }
267 | }
268 | }
269 | }
270 |
--------------------------------------------------------------------------------
/src/assets/sass/_normalize.scss:
--------------------------------------------------------------------------------
1 | /*! https://github.com/necolas/normalize.css/blob/master/normalize.css 参考地址*/
2 |
3 | /* Document========================================================================== */
4 | /**
5 | * 1. 在所有浏览器中更正行高.
6 | * 2. 在iOS中更改方向后,防止调整字体大小.
7 | */
8 | html {
9 | line-height: 1.15; /* 1 */
10 | -webkit-text-size-adjust: 100%; /* 2 */
11 | }
12 |
13 |
14 | /* Sections========================================================================== */
15 | /**
16 | * 删除所有浏览器中的边距
17 | */
18 | body {
19 | margin: 0;
20 | }
21 |
22 | /**
23 | * 在IE中一致地渲染`main`元素
24 | */
25 | main {
26 | display: block;
27 | }
28 |
29 | /**
30 | * 更正`section`和``中`h1`元素的字体大小和边距
31 | * Chrome文章,Firefox和Safari中的文章背景
32 | */
33 | h1 {
34 | font-size: 2em;
35 | margin: 0.67em 0;
36 | }
37 |
38 | /* Grouping content========================================================================== */
39 | /**
40 | * 1. 在Firefox中添加正确的大小调整大小
41 | * 2. 在Edge和IE中显示溢出
42 | */
43 | hr {
44 | box-sizing: content-box; /* 1 */
45 | height: 0; /* 1 */
46 | overflow: visible; /* 2 */
47 | }
48 |
49 | /**
50 | * 1. 纠正所有浏览器中字体大小的继承和缩放
51 | * 2. 纠正所有浏览器中奇怪的'em`字体大小
52 | */
53 | pre {
54 | font-family: monospace, monospace; /* 1 */
55 | font-size: 1em; /* 2 */
56 | }
57 |
58 |
59 | /* Text-level semantics========================================================================== */
60 | /**
61 | * 删除IE 10中活动链接的灰色背景
62 | */
63 | a {
64 | background-color: transparent;
65 | }
66 |
67 | /**
68 | * 1. 删除Chrome 57中的底部边框
69 | * 2. 在Chrome,Edge,IE,Opera和Safari中添加正确的文本装饰
70 | */
71 | abbr[title] {
72 | border-bottom: none; /* 1 */
73 | text-decoration: underline; /* 2 */
74 | text-decoration: underline dotted; /* 2 */
75 | }
76 |
77 | /**
78 | * 在Chrome,Edge和Safari中添加正确的字体粗细
79 | */
80 | b,
81 | strong {
82 | font-weight: bolder;
83 | }
84 |
85 | /**
86 | * 1. 纠正所有浏览器中字体大小的继承和缩放
87 | * 2. 纠正所有浏览器中奇怪的'em`字体大小
88 | */
89 | code, kbd, samp {
90 | font-family: monospace, monospace; /* 1 */
91 | font-size: 1em; /* 2 */
92 | }
93 |
94 | /**
95 | * 在所有浏览器中添加正确的字体大小.
96 | */
97 | small {
98 | font-size: 80%;
99 | }
100 |
101 | /**
102 | * 防止`sub`和`sup`元素影响所有浏览器行高
103 | */
104 | sub, sup {
105 | font-size: 75%;
106 | line-height: 0;
107 | position: relative;
108 | vertical-align: baseline;
109 | }
110 | sub {
111 | bottom: -0.25em;
112 | }
113 | sup {
114 | top: -0.5em;
115 | }
116 |
117 |
118 | /* Embedded content========================================================================== */
119 | /**
120 | * 删除IE 10中链接内图像的边框
121 | */
122 | img {
123 | border-style: none;
124 | }
125 |
126 |
127 | /* Forms========================================================================== */
128 | /**
129 | * 1. 更改所有浏览器的字体样式
130 | * 2. 删除Firefox和Safari中的边距
131 | */
132 | button, input, optgroup, select, textarea {
133 | font-family: inherit; /* 1 */
134 | font-size: 100%; /* 1 */
135 | line-height: 1.15; /* 1 */
136 | margin: 0; /* 2 */
137 | }
138 |
139 | /**
140 | * 在IE中显示溢出
141 | * 1. 在Edge中显示溢出
142 | */
143 | button, input { /* 1 */
144 | overflow: visible;
145 | }
146 |
147 | /**
148 | * 删除Edge,Firefox和IE中文本转换的继承
149 | * 1. 删除Firefox中文本转换的继承
150 | */
151 | button, select { /* 1 */
152 | text-transform: none;
153 | }
154 |
155 | /**
156 | * 纠正无法在iOS和Safari中设置可点击类型的样式
157 | */
158 | button,
159 | [type="button"],
160 | [type="reset"],
161 | [type="submit"] {
162 | -webkit-appearance: button;
163 | }
164 |
165 | /**
166 | * 删除Firefox中的内边框和填充。
167 | */
168 | button::-moz-focus-inner,
169 | [type="button"]::-moz-focus-inner,
170 | [type="reset"]::-moz-focus-inner,
171 | [type="submit"]::-moz-focus-inner {
172 | border-style: none;
173 | padding: 0;
174 | }
175 |
176 | /**
177 | * 恢复之前规则未设置的焦点样式。
178 | */
179 | button:-moz-focusring,
180 | [type="button"]:-moz-focusring,
181 | [type="reset"]:-moz-focusring,
182 | [type="submit"]:-moz-focusring {
183 | outline: 1px dotted ButtonText;
184 | }
185 |
186 | /**
187 | * 更正Firefox中的填充
188 | */
189 | fieldset {
190 | padding: 0.35em 0.75em 0.625em;
191 | }
192 |
193 | /**
194 | * 1. 更正Edge和IE中的文本换行
195 | * 2. 纠正IE中`fieldset`元素的颜色继承
196 | * 3. 删除填充,以便开发人员在零填充时不会被捕获所有浏览器中的`fieldset`元素
197 | */
198 | legend {
199 | box-sizing: border-box; /* 1 */
200 | color: inherit; /* 2 */
201 | display: table; /* 1 */
202 | max-width: 100%; /* 1 */
203 | padding: 0; /* 3 */
204 | white-space: normal; /* 1 */
205 | }
206 |
207 | /**
208 | * 在Chrome,Firefox和Opera中添加正确的垂直对齐方式
209 | */
210 | progress {
211 | vertical-align: baseline;
212 | }
213 |
214 | /**
215 | * 删除IE 10+中的默认垂直滚动条
216 | */
217 | textarea {
218 | overflow: auto;
219 | }
220 |
221 | /**
222 | * 1. 在IE 10中添加正确的大小调整大小
223 | * 2. 删除IE 10中的填充
224 | */
225 | [type="checkbox"],
226 | [type="radio"] {
227 | box-sizing: border-box; /* 1 */
228 | padding: 0; /* 2 */
229 | }
230 |
231 | /**
232 | * 更正Chrome中增量和减量按钮的光标样式
233 | */
234 | [type="number"]::-webkit-inner-spin-button,
235 | [type="number"]::-webkit-outer-spin-button {
236 | height: auto;
237 | }
238 |
239 | /**
240 | * 1. 纠正Chrome和Safari中的奇怪外观
241 | * 2. 更正Safari中的轮廓样式
242 | */
243 | [type="search"] {
244 | -webkit-appearance: textfield; /* 1 */
245 | outline-offset: -2px; /* 2 */
246 | }
247 |
248 | /**
249 | * 在macOS上删除Chrome和Safari中的内部填充
250 | */
251 | [type="search"]::-webkit-search-decoration {
252 | -webkit-appearance: none;
253 | }
254 |
255 | /**
256 | * 1. 纠正无法在iOS和Safari中设置可点击类型的样式
257 | * 2. 在Safari中将字体属性更改为`inherit`
258 | */
259 | ::-webkit-file-upload-button {
260 | -webkit-appearance: button; /* 1 */
261 | font: inherit; /* 2 */
262 | }
263 |
264 |
265 | /* Interactive========================================================================== */
266 | /*
267 | * 在Edge,IE 10+和Firefox中添加正确的显示
268 | */
269 | details {
270 | display: block;
271 | }
272 |
273 | /*
274 | * 在所有浏览器中添加正确的显示
275 | */
276 | summary {
277 | display: list-item;
278 | }
279 |
280 |
281 | /* Misc========================================================================== */
282 | /**
283 | * 在IE 10+中添加正确的显示
284 | */
285 | template {
286 | display: none;
287 | }
288 |
289 | /**
290 | * 在IE 10中添加正确的显示
291 | */
292 | [hidden] {
293 | display: none;
294 | }
295 |
--------------------------------------------------------------------------------
/src/assets/sass/_nprogress.scss:
--------------------------------------------------------------------------------
1 | @import 'nprogress/nprogress.css';
2 |
3 | #nprogress {
4 | .bar {
5 | background-color: var(--el-color-primary);
6 | }
7 | .spinner-icon {
8 | border-top-color: var(--el-color-primary);
9 | border-left-color: var(--el-color-primary);
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/src/assets/sass/_root.scss:
--------------------------------------------------------------------------------
1 | :root {
2 | --box-border-radius: 10px;
3 | --wrap-background-color: #272a37;
4 | --tabbar-background-color: #323644;
5 | --tabbar-hover-color: #484d5f;
6 | --card-background-color: var(--tabbar-background-color);
7 | --card-hover-background-color: #484d5f;
8 | }
9 |
--------------------------------------------------------------------------------
/src/assets/sass/_transition.scss:
--------------------------------------------------------------------------------
1 | // 浅入浅出
2 | .shallow-in-out-enter-active,
3 | .shallow-in-out-leave-active {
4 | transition: opacity 0.3s;
5 | }
6 | .shallow-in-out-enter,
7 | .shallow-in-out-leave-to {
8 | opacity: 0;
9 | }
10 |
11 | // 左浅入右浅出
12 | .left-in-right-out-enter-active,
13 | .left-in-right-out-leave-active {
14 | transition: all 0.3s;
15 | }
16 | .left-in-right-out-enter-active {
17 | opacity: 0;
18 | transform: translateX(-20px);
19 | }
20 | .left-in-right-out-enter-to {
21 | opacity: 1;
22 | transform: translateX(0px);
23 | }
24 | .left-in-right-out-leave-to {
25 | opacity: 0;
26 | transform: translateX(20px);
27 | }
28 |
29 | // 右浅入浅出
30 | .right-in-out-move,
31 | .right-in-out-enter-active,
32 | .right-in-out-leave-active {
33 | transition: all 0.3s;
34 | }
35 | .right-in-out-enter-active {
36 | transition-delay: 0.3s;
37 | opacity: 0;
38 | transform: translateX(50px);
39 | }
40 | .right-in-out-enter-to {
41 | opacity: 1;
42 | transform: translateX(0px);
43 | }
44 | .right-in-out-leave-to {
45 | opacity: 0;
46 | transform: translateX(50px);
47 | }
48 |
49 |
50 | .move-move,
51 | .move-enter-active,
52 | .move-leave-active {
53 | transition: all 0.5s cubic-bezier(0.55, 0, 0.1, 1);
54 | }
55 | .move-leave-active {
56 | position: absolute;
57 | }
58 |
--------------------------------------------------------------------------------
/src/assets/sass/_variable.scss:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/src/assets/sass/index.scss:
--------------------------------------------------------------------------------
1 | @import "normalize"; /* 页面标准化文件导入 */
2 | @import "element"; /* element */
3 | @import "font"; /* 字体 */
4 | @import "transition";
5 | @import "animation";
6 | @import "global";
7 | @import "nprogress";
8 | @import "root";
9 |
--------------------------------------------------------------------------------
/src/common/constants/file.js:
--------------------------------------------------------------------------------
1 | // 接受的图片类型
2 | export const IMAGE_ACCEPT = ['IMAGE/JPG', 'IMAGE/PNG', 'IMAGE/GIF', 'IMAGE/JPEG']
3 |
--------------------------------------------------------------------------------
/src/common/constants/index.js:
--------------------------------------------------------------------------------
1 | import { ContentType, AuthKey, StorageType, SuccessCode, RequestMapping, WebsocketMapping, ModelBinding } from '@enums'
2 |
3 | // request Mapping
4 | export const MAPPING = RequestMapping.CHATTERBOX
5 | // websocket Mapping
6 | export const WEBSOCKET_MAPPING = RequestMapping.CHATTERBOX + WebsocketMapping.WEBSOCKET
7 | // 请求数据类型
8 | export const CONTENT_TYPE = ContentType.JSON
9 | // 请求超时时长
10 | export const TIME_OUT = 50000
11 | // 访问秘钥 存储
12 | export const AUTH_KEY = AuthKey.TOKEN
13 | // 秘钥本地存储类型
14 | export const AUTH_STORAGE = StorageType.COOKIE
15 | // 请求成功响应code
16 | export const SUCCESS_CODE = [SuccessCode.ZERO, SuccessCode.TWO_HUNDRED]
17 | // 双向绑定方法名
18 | export const MODEL_NAME = 'modelValue'
19 | export const UPDATE_MODEL_EVENT = ModelBinding.MODEL_VALUE
20 |
--------------------------------------------------------------------------------
/src/common/enums/apply.js:
--------------------------------------------------------------------------------
1 | export const APPLY_STATUS = {
2 | AUDIT: 0,
3 | PASS: 1,
4 | REJECT: 2,
5 | }
6 | export const applyStatusList = [
7 | { label: '待审核', value: APPLY_STATUS.AUDIT },
8 | { label: '已通过', value: APPLY_STATUS.PASS },
9 | { label: '已拒绝', value: APPLY_STATUS.REJECT },
10 | ]
11 |
12 | export const APPLY_TYPE = {
13 | FRIEND: 0, // 申请加好友
14 | GROUP: 1, // 申请加群
15 | }
16 |
17 |
--------------------------------------------------------------------------------
/src/common/enums/index.js:
--------------------------------------------------------------------------------
1 | // 请求头-内容类型
2 | export const ContentType = {
3 | JSON: 'application/json;charset=UTF-8',
4 | FORM: 'application/x-www-form-urlencoded;charset=UTF-8',
5 | UPLOAD: 'multipart/form-data',
6 | STREAM: 'application/octet-stream'
7 | }
8 | // 令牌键值
9 | export const AuthKey = {
10 | TOKEN: 'token',
11 | ACCESS: 'access'
12 | }
13 | // 本地存储类型
14 | export const StorageType = {
15 | COOKIE: 'cookie',
16 | SESSION: 'sessionStorage',
17 | LOCAL: 'localStorage'
18 | }
19 | // 请求成功状态码
20 | export const SuccessCode = {
21 | ZERO: 0,
22 | TWO_HUNDRED: 200
23 | }
24 | // 请求 mapping
25 | export const RequestMapping = {
26 | CHATTERBOX: '/chatterbox',
27 | SLIPPER: '/slipper',
28 | API: '/api'
29 | }
30 | // websocket mapping
31 | export const WebsocketMapping = {
32 | WEBSOCKET: '/websocket',
33 | }
34 | // 双向绑定名
35 | export const ModelBinding = {
36 | MODEL_VALUE: 'update:modelValue',
37 | MODEL_EVENT: 'update:modelEvent'
38 | }
39 |
--------------------------------------------------------------------------------
/src/common/enums/media.js:
--------------------------------------------------------------------------------
1 | export const MEDIA_TYPE = {
2 | VOICE: 1, // 语音
3 | VIDEO: 2, // 视频
4 | }
5 |
6 | export const MEDIA_STATUS = {
7 | INVITING: 1, // 邀请中
8 | REJECTED: 2, // 被拒绝
9 |
10 | CALLING: 3, // 被呼叫中
11 | CANCELED: 4, // 被取消
12 |
13 | UNANSWERED: 5, // 未接听
14 |
15 | ING: 6, // 进行中
16 |
17 | HANGUP: 7, // 挂断
18 | CLOSED: 8, // 中断
19 | }
20 |
--------------------------------------------------------------------------------
/src/common/enums/message.js:
--------------------------------------------------------------------------------
1 | export const MESSAGE_TYPE = {
2 | TEXT: 0,
3 | IMAGE: 1,
4 | AUDIO: 2,
5 | FILE: 3,
6 | }
7 |
8 | export const messageTypeList = [
9 | { label: '文本', value: MESSAGE_TYPE.TEXT },
10 | { label: '图片', value: MESSAGE_TYPE.IMAGE },
11 | { label: '语音', value: MESSAGE_TYPE.AUDIO },
12 | { label: '文件', value: MESSAGE_TYPE.FILE },
13 | ]
14 |
15 | export const MESSAGE_SEND_STATUS = {
16 | PENDING: 1, // 发送中
17 | SUCCESS: 2, // 发送成功
18 | FAIL: 3, // 发送失败
19 | }
20 |
--------------------------------------------------------------------------------
/src/common/enums/user.js:
--------------------------------------------------------------------------------
1 | export const SEX = {
2 | FEMALE: 0,
3 | MALE: 1,
4 | UNKNOWN: 2,
5 | }
6 | export const sexList = [
7 | { label: '男', value: SEX.MALE },
8 | { label: '女', value: SEX.FEMALE },
9 | { label: '保密', value: SEX.UNKNOWN },
10 | ]
11 |
12 | export const EDIT_TYPE = {
13 | INFO: 'info',
14 | EMAIL: 'email',
15 | }
16 |
17 | export const ONLINE_STATUS = {
18 | OFFLINE: 0,
19 | ONLINE: 1,
20 | }
21 |
--------------------------------------------------------------------------------
/src/common/enums/websocket.js:
--------------------------------------------------------------------------------
1 | export const WEBSOCKET_TYPE = {
2 | HEARTBEAT: 0, // 心跳
3 | PRIVATE_CHAT_MESSAGE: 1, // 私聊消息
4 | GROUP_CHAT_MESSAGE: 2, // 群聊消息
5 |
6 | FRIEND_APPLY: 3, // 好友申请
7 | PASS_FRIEND_APPLY: 4, // 通过好友申请
8 | REJECT_FRIEND_APPLY: 5, // 拒绝好友申请
9 | DELETE_FRIEND: 6, // 删除好友
10 |
11 | JOIN_GROUP: 11, // 加入群聊
12 | EXIT_GROUP: 12, // 退出群聊
13 |
14 | VOICE_APPLY: 13, // 语音请求
15 | VOICE_CANCEL: 14, // 取消语音请求
16 | VOICE_ACCEPT: 15, // 接听语音
17 | VOICE_REJECT: 16, // 拒绝语音
18 | VOICE_CLOSE: 17, // 关闭语音
19 |
20 | VIDEO_APPLY: 18, // 视频请求
21 | VIDEO_CANCEL: 19, // 取消视频请求
22 | VIDEO_ACCEPT: 20, // 接听视频
23 | VIDEO_REJECT: 21, // 拒绝视频
24 | VIDEO_CLOSE: 22, // 关闭视频
25 |
26 | USER_ONLINE: 23, // 用户上线
27 | USER_OFFLINE: 24, // 用户下线
28 | }
29 |
--------------------------------------------------------------------------------
/src/common/props/index.js:
--------------------------------------------------------------------------------
1 | export const form = {
2 | type: Object,
3 | default: () => ({})
4 | }
5 |
6 | export const loading = {
7 | type: Boolean,
8 | default: () => false
9 | }
10 |
--------------------------------------------------------------------------------
/src/common/rules/user.js:
--------------------------------------------------------------------------------
1 | import { isEmail } from '@utils/regular.js'
2 |
3 | /* 昵称 */
4 | export const nickname = [
5 | { required: true, message: '请输入昵称', trigger: 'blur' }
6 | ]
7 | /* 性别 */
8 | export const sex = [
9 | { required: true, message: '请选择性别', trigger: 'change' }
10 | ]
11 | /* 邮箱 */
12 | const checkEmail = (_rule, value, callback) => {
13 | if (!isEmail(value)) {
14 | callback(new Error('请输入正确的邮箱地址'))
15 | }
16 | callback()
17 | }
18 | export const email = [
19 | { required: true, message: '请输入邮箱地址', trigger: 'blur' },
20 | { validator: checkEmail, trigger: 'blur' }
21 | ]
22 | /* 验证码 */
23 | export const captcha = [
24 | { required: true, message: '请输入验证码', trigger: 'blur' }
25 | ]
26 |
--------------------------------------------------------------------------------
/src/common/utils/regular.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @description: 邮箱
3 | * @param {*} input
4 | * @return {*}
5 | * @author: gumingchen
6 | */
7 | export function isEmail(input) {
8 | const reg = /^([a-zA-Z0-9_-])+@([a-zA-Z0-9_-])+((.[a-zA-Z0-9_-]{2,3}){1,2})$/
9 | return reg.test(input)
10 | }
11 |
--------------------------------------------------------------------------------
/src/common/utils/storage.js:
--------------------------------------------------------------------------------
1 | /*
2 | * @Description: 本地存储
3 | * @Author: gumingchen
4 | * @Email: 1240235512@qq.com
5 | * @Date: 2020-12-28 16:25:18
6 | * @LastEditors: gumingchen
7 | * @LastEditTime: 2021-04-30 14:01:54
8 | */
9 | import cookie from 'js-cookie'
10 | import { AUTH_KEY, AUTH_STORAGE } from '@constants'
11 | import { StorageType } from '@enums'
12 |
13 | /**
14 | * @description: 本地存储、获取、清除
15 | * @param {String} key 存储键值
16 | * @param {String} value 存储值
17 | * @param {String} storage 存储位置
18 | * @return {*}
19 | * @author: gumingchen
20 | */
21 | export function set(key, value = '', storage) {
22 | switch (storage) {
23 | case StorageType.COOKIE:
24 | cookie.set(key, value)
25 | break
26 | case StorageType.SESSION:
27 | sessionStorage.setItem(key, value)
28 | break
29 | case StorageType.LOCAL:
30 | localStorage.setItem(key, value)
31 | break
32 | default:
33 | cookie.set(key, value)
34 | break
35 | }
36 | }
37 | export function get(key, storage) {
38 | let result
39 | switch (storage) {
40 | case StorageType.COOKIE:
41 | result = cookie.get(key)
42 | break
43 | case StorageType.SESSION:
44 | result = sessionStorage.getItem(key)
45 | break
46 | case StorageType.LOCAL:
47 | result = localStorage.getItem(key)
48 | break
49 | default:
50 | result = cookie.get(key)
51 | break
52 | }
53 | return result
54 | }
55 | export function clear(key, storage) {
56 | switch (storage) {
57 | case StorageType.COOKIE:
58 | cookie.remove(key)
59 | break
60 | case StorageType.SESSION:
61 | sessionStorage.removeItem(key)
62 | break
63 | case StorageType.LOCAL:
64 | localStorage.removeItem(key)
65 | break
66 | default:
67 | cookie.remove(key)
68 | break
69 | }
70 | }
71 |
72 | /**
73 | * @description: token-存储、获取、清除
74 | * @param {*}
75 | * @return {*}
76 | * @author: gumingchen
77 | */
78 | export function getAuth() {
79 | return JSON.parse(get(AUTH_KEY, AUTH_STORAGE) || '{}')
80 | }
81 | export function setAuth(auth) {
82 | set(AUTH_KEY, JSON.stringify(auth), AUTH_STORAGE)
83 | }
84 | export function clearAuth() {
85 | clear(AUTH_KEY, AUTH_STORAGE)
86 | }
87 |
--------------------------------------------------------------------------------
/src/common/utils/websocket.js:
--------------------------------------------------------------------------------
1 | import { WEBSOCKET_TYPE } from '@enums/websocket'
2 |
3 | export default class WebsocketClass {
4 | /**
5 | * @description: 初始化参数
6 | * @param {*} url ws资源路径
7 | * @param {*} callback 服务端信息回调
8 | * @return {*}
9 | * @author: gumingchen
10 | */
11 | constructor(url, callback) {
12 | this.url = url
13 | this.callback = callback
14 | this.ws = null // websocket 对象
15 | this.status = 0 // 连接状态: 0-关闭 1-连接 2-手动关闭
16 | this.ping = 10000 // 心跳时长
17 | this.pingInterval = null // 心跳定时器
18 | this.reconnect = 5000 // 重连间隔
19 | }
20 |
21 | /**
22 | * @description: 连接
23 | * @param {*}
24 | * @return {*}
25 | * @author: gumingchen
26 | */
27 | connect() {
28 | this.ws = new WebSocket(this.url)
29 | // 监听socket连接
30 | this.ws.onopen = () => {
31 | this.status = 1
32 | this.heartHandler()
33 | }
34 | // 监听socket消息
35 | this.ws.onmessage = (e) => {
36 | this.callback(JSON.parse(e.data))
37 | }
38 | // 监听socket错误信息
39 | this.ws.onerror = (e) => {
40 | console.log(e)
41 | }
42 | // 监听socket关闭
43 | this.ws.onclose = (e) => {
44 | this.onClose(e)
45 | }
46 | }
47 |
48 | /**
49 | * @description: 发送消息
50 | * @param {*} data
51 | * @return {*}
52 | * @author: gumingchen
53 | */
54 | send(data) {
55 | return this.ws.send(JSON.stringify(data))
56 | }
57 |
58 | /**
59 | * @description: 关闭weibsocket 主动关闭不会触发重连
60 | * @param {*}
61 | * @return {*}
62 | * @author: gumingchen
63 | */
64 | close() {
65 | this.status = 2
66 | this.ws.close()
67 | }
68 |
69 | /**
70 | * @description: socket关闭事件
71 | * @param {*}
72 | * @return {*}
73 | * @author: gumingchen
74 | */
75 | onClose(e) {
76 | console.error(e)
77 | this.status = this.status === 2 ? this.status : 0
78 | setTimeout(() => {
79 | if (this.status === 0) {
80 | this.connect()
81 | }
82 | }, this.reconnect)
83 | }
84 |
85 | /**
86 | * @description: 心跳机制
87 | * @param {*}
88 | * @return {*}
89 | * @author: gumingchen
90 | */
91 | heartHandler() {
92 | const data = {
93 | type: WEBSOCKET_TYPE.HEARTBEAT
94 | }
95 | this.pingInterval = setInterval(() => {
96 | if (this.status === 1) {
97 | this.ws.send(JSON.stringify(data))
98 | } else {
99 | clearInterval(this.pingInterval)
100 | }
101 | }, this.ping)
102 | }
103 | }
104 |
--------------------------------------------------------------------------------
/src/components/apply-friend-dialog/components/form-ui/index.js:
--------------------------------------------------------------------------------
1 | import { form, loading } from '@props'
2 |
3 | export const props = {
4 | form,
5 | loading,
6 | groupings: { type: Array, default: () => [] }
7 | }
8 |
9 | export const rules = {
10 | groupingId: [{ required: true, message: '请选择分组', trigger: 'change' }],
11 | content: [{ required: true, message: '请输入申请内容', trigger: 'blur' }],
12 | }
13 |
--------------------------------------------------------------------------------
/src/components/apply-friend-dialog/components/form-ui/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
11 |
12 |
13 |
14 |
19 |
20 |
21 |
22 |
23 |
24 | 加为好友
25 |
26 |
27 |
28 |
50 |
51 |
--------------------------------------------------------------------------------
/src/components/apply-friend-dialog/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
94 |
95 |
98 | ./components/form-ui
--------------------------------------------------------------------------------
/src/components/avatar-upload/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
50 |
51 |
75 |
--------------------------------------------------------------------------------
/src/components/avatar/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 | {{ nameFormat }}
4 |
5 |
6 |
7 |
33 |
34 |
36 |
--------------------------------------------------------------------------------
/src/components/brand/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | {{ character }}
5 |
6 |
7 |
8 |
13 |
14 |
38 |
--------------------------------------------------------------------------------
/src/components/captcha-input/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
10 |
11 |
12 |
13 |
14 |
15 |
37 |
38 |
41 |
--------------------------------------------------------------------------------
/src/components/card/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | {{ label }}
10 |
11 |
12 |
13 |
14 | {{ content }}
15 |
16 |
17 |
{{ tips }}
18 |
19 |
20 |
21 |
50 |
51 |
67 |
--------------------------------------------------------------------------------
/src/components/context-menu/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
18 |
19 |
20 |
21 |
22 |
81 |
82 |
100 |
--------------------------------------------------------------------------------
/src/components/countdown-button/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
10 | {{ buttonText }}
11 |
12 |
13 |
14 |
15 |
16 |
48 |
49 |
62 |
--------------------------------------------------------------------------------
/src/components/countdown/index.vue:
--------------------------------------------------------------------------------
1 |
2 | {{ seconds }}s
3 |
4 |
5 |
46 |
47 |
52 |
--------------------------------------------------------------------------------
/src/components/empty/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | {{ text }}
5 |
6 |
7 |
8 |
9 |
25 |
26 |
39 |
--------------------------------------------------------------------------------
/src/components/filing/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 |
9 |
10 |
18 |
--------------------------------------------------------------------------------
/src/components/loading/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | {{ text }}
5 |
6 |
7 |
8 |
16 |
17 |
35 |
--------------------------------------------------------------------------------
/src/components/message-send-status/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
20 |
21 |
29 |
--------------------------------------------------------------------------------
/src/components/online-dot/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
8 |
16 |
17 |
32 |
--------------------------------------------------------------------------------
/src/components/panel/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | {{ title }}
6 |
7 |
8 |
9 |
{{ item.label }}:
10 |
11 | {{ item.value}}
12 |
13 |
14 |
15 |
16 |
17 |
18 |
34 |
35 |
42 |
--------------------------------------------------------------------------------
/src/components/router/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
54 |
--------------------------------------------------------------------------------
/src/components/search-dialog/components/group-list-panel/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
9 |
10 |
13 |
--------------------------------------------------------------------------------
/src/components/search-dialog/components/user-card/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
22 |
23 |
34 |
--------------------------------------------------------------------------------
/src/components/search-dialog/components/user-list-panel/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
8 |
9 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
89 |
90 |
99 |
--------------------------------------------------------------------------------
/src/components/search-dialog/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
11 |
12 |
13 |
16 |
17 |
18 |
19 |
20 |
37 |
38 |
42 |
--------------------------------------------------------------------------------
/src/components/timer/index.vue:
--------------------------------------------------------------------------------
1 |
2 | {{ time }}
3 |
4 |
5 |
34 |
35 |
38 |
--------------------------------------------------------------------------------
/src/components/upload/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
45 |
46 |
48 |
--------------------------------------------------------------------------------
/src/components/user-dialog/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
11 |
12 |
13 |
14 |
32 |
33 |
37 |
--------------------------------------------------------------------------------
/src/components/user-panel/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
29 |
30 |
32 |
--------------------------------------------------------------------------------
/src/directive/index.js:
--------------------------------------------------------------------------------
1 | export default {
2 | install: function (app) {
3 | const directives = import.meta.glob('./**/index.js', { eager: true })
4 | for (const key in directives) {
5 | if (key === './index.js') return
6 | const directive = directives[key]
7 | const name = key.replace(/\.\/|\/index.js/g, '')
8 | app.directive(name, directive.default || directive)
9 | }
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/src/directive/longpress/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @description: 长按
3 | * @param {*}
4 | * @return {*}
5 | * @author: gumingchen
6 | */
7 | export default {
8 | beforeMount(el, binding) {
9 | const callback = binding.value;
10 | if (typeof callback !== 'function') {
11 | return console.warn('[Directive warn]: Invalid value: validation failed for value. Must be a function.')
12 | }
13 | el.$duration = binding.arg || 2000; // 获取长按时长, 默认3秒执行长按事件
14 | let timer = null;
15 | const add = (event) => {
16 | const { type, button } = event
17 | if (type === 'click' && button !== 0) return;
18 | event.preventDefault();
19 | if (timer === null) {
20 | timer = setTimeout(() => {
21 | callback();
22 | timer = null;
23 | }, el.$duration)
24 | }
25 | }
26 | const cancel = () => {
27 | if (timer !== null) {
28 | clearTimeout(timer);
29 | timer = null;
30 | }
31 | }
32 | // 添加计时器
33 | el.addEventListener('mousedown', add);
34 | el.addEventListener('touchstart', add);
35 | // 取消计时器
36 | el.addEventListener('click', cancel);
37 | el.addEventListener('mouseout', cancel);
38 | el.addEventListener('touchend', cancel)
39 | el.addEventListener('touchcancel', cancel)
40 | },
41 | updated(el, binding) {
42 | // 可以实时更新时长
43 | el.$duration = binding.arg;
44 | },
45 | unmounted(el) {
46 | el.removeEventListener('mousedown', () => { });
47 | el.removeEventListener('touchstart', () => { });
48 | el.removeEventListener('click', () => { });
49 | el.removeEventListener('mouseout', () => { });
50 | el.removeEventListener('touchend', () => { });
51 | el.removeEventListener('touchcancel', () => { });
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/src/hooks/bind-exposed.js:
--------------------------------------------------------------------------------
1 | export default function bindExposed(ref) {
2 | const instance = getCurrentInstance()
3 | if (ref.value.$.exposed) {
4 | const entries = Object.entries(ref.value.$.exposed)
5 | for (const [key, value] of entries) {
6 | if (instance.exposed) {
7 | instance.exposed[key] = value
8 | }
9 | }
10 | }
11 | }
--------------------------------------------------------------------------------
/src/hooks/debounce-ref.js:
--------------------------------------------------------------------------------
1 | export default function debounceRef(value, delay = 1000) {
2 | let timer
3 | return customRef((track, trigger) => ({
4 | get() {
5 | track()
6 | return value
7 | },
8 | set(val) {
9 | clearTimeout(timer)
10 | timer = setTimeout(() => {
11 | value = val
12 | trigger()
13 | }, delay)
14 | }
15 | }))
16 | }
--------------------------------------------------------------------------------
/src/hooks/model.js:
--------------------------------------------------------------------------------
1 | import { MODEL_NAME, UPDATE_MODEL_EVENT } from '@constants'
2 |
3 | export default function (props, key) {
4 | const vm = getCurrentInstance().proxy
5 | return computed({
6 | get() {
7 | return props[key || MODEL_NAME]
8 | },
9 | set(value) {
10 | const event = key ? `update:${ key }` : UPDATE_MODEL_EVENT
11 | vm.$emit(event, value)
12 | }
13 | })
14 | }
15 |
--------------------------------------------------------------------------------
/src/main.js:
--------------------------------------------------------------------------------
1 | import { createApp } from 'vue'
2 |
3 | import App from './App.vue'
4 | import router from './router'
5 | import pinia from './stores'
6 |
7 | import '@/assets/sass/index.scss' // 全局样式
8 | import Directive from '@/directive' // 自定义指令
9 |
10 | const app = createApp(App)
11 |
12 | app.use(router)
13 | .use(pinia)
14 | .use(Directive)
15 | .mount('#app')
16 |
--------------------------------------------------------------------------------
/src/router/index.js:
--------------------------------------------------------------------------------
1 | import { createRouter, createWebHistory } from 'vue-router'
2 | import NProgress from 'nprogress'
3 |
4 | const router = createRouter({
5 | history: createWebHistory(import.meta.env.BASE_URL),
6 | routes: [
7 | { path: '/', redirect: { name: 'login' }, meta: { name: '重定向' } },
8 | {
9 | path: '/login',
10 | name: 'login',
11 | component: () => import('../views/login/index.vue')
12 | },
13 | {
14 | path: '/layout',
15 | name: 'layout',
16 | component: () => import('../views/layout/index.vue'),
17 | children: [
18 | {
19 | path: '/conversation',
20 | name: 'conversation',
21 | component: () => import('../views/conversation/index.vue')
22 | },
23 | {
24 | path: '/friend',
25 | name: 'friend',
26 | component: () => import('../views/friend/index.vue')
27 | },
28 | {
29 | path: '/group',
30 | name: 'group',
31 | component: () => import('../views/group/index.vue')
32 | },
33 | {
34 | path: '/apply',
35 | name: 'apply',
36 | component: () => import('../views/apply/index.vue')
37 | },
38 | ],
39 | beforeEnter: async (to, from, next) => {
40 | const authStore = useAuthStore()
41 | if (authStore.validateToken()) {
42 | await useUserStore().getUserInfo()
43 | next()
44 | } else {
45 | const rootStore = useRootStore()
46 | rootStore.clearData()
47 | next({ name: 'login', replace: true })
48 | }
49 | }
50 | }
51 | ]
52 | })
53 |
54 | router.beforeEach(async (to, _from, next) => {
55 | NProgress.start()
56 |
57 | const authStore = useAuthStore()
58 | if (to.name === 'login' && authStore.validateToken()) {
59 | next({ name: 'conversation', replace: true })
60 | } else {
61 | next()
62 | }
63 | })
64 |
65 | router.afterEach(() => {
66 | NProgress.done()
67 | })
68 |
69 | export default router
70 |
--------------------------------------------------------------------------------
/src/stores/index.js:
--------------------------------------------------------------------------------
1 | import { createPinia } from 'pinia'
2 |
3 | const pinia = createPinia()
4 |
5 | export default pinia
--------------------------------------------------------------------------------
/src/stores/modules/apply.js:
--------------------------------------------------------------------------------
1 | import { clearJson } from '@utils'
2 |
3 | import { auditCountApi, pageApi } from '@/api/apply'
4 |
5 | export const useApplyStore = defineStore('apply', {
6 | state: () => ({
7 | auditCount: 0,
8 | active: null,
9 | list: [],
10 | }),
11 | actions: {
12 | /**
13 | * 获取待审核数量
14 | */
15 | async getAuditCount() {
16 | const r = await auditCountApi()
17 | if (r) {
18 | this.auditCount = r.data
19 | }
20 | },
21 | /**
22 | * 获取消息列表
23 | * @param {*} size 数据量
24 | * @returns
25 | */
26 | async getList(size = 10) {
27 | let lastId = ''
28 | const length = this.list.length
29 | if (length) {
30 | lastId = this.list[length - 1].id
31 | }
32 | const r = await pageApi({ lastId, size })
33 | if (r) {
34 | this.list.push(...r.data)
35 | return r.data
36 | }
37 | },
38 | /**
39 | * 新增需求
40 | * @param {*} apply
41 | */
42 | addApply(apply) {
43 | this.list.unshift(apply)
44 | this.auditCount += 1
45 | },
46 | /**
47 | * 设置选中
48 | * @param {*} apply
49 | */
50 | setActive({ id }) {
51 | const apply = this.list.find(item => item.id === id)
52 | this.active = apply
53 | },
54 | /**
55 | * 设置状态
56 | * @param {*} id
57 | * @param {*} status
58 | */
59 | setStatus(id, status) {
60 | const apply = this.list.find(item => item.id === id)
61 | apply.status = status
62 | this.auditCount = this.auditCount > 0 ? this.auditCount - 1 : 0
63 | },
64 | /**
65 | * 更新用户在线状态
66 | * @param {*} userId 用户ID
67 | * @param {*} online 在线状态
68 | */
69 | updateUserOnline(userId, online) {
70 | for (let i = 0; i < this.list.length; i++) {
71 | const { user } = this.list[i];
72 | if (user.id === userId) {
73 | user.online = online
74 | }
75 | }
76 | },
77 | /**
78 | * 清除数据
79 | */
80 | clear() {
81 | clearJson(this.$state)
82 | }
83 | }
84 | })
85 |
--------------------------------------------------------------------------------
/src/stores/modules/auth.js:
--------------------------------------------------------------------------------
1 | import { dayjs } from 'element-plus'
2 | import { clearJson } from '@utils'
3 | import { getAuth, setAuth, clearAuth } from '@utils/storage'
4 |
5 | import { loginApi, registerApi, loginQQApi, logoutApi } from '@/api/auth'
6 |
7 | const auth = getAuth()
8 |
9 | export const useAuthStore = defineStore('auth', {
10 | state: () => ({
11 | userId: '',
12 | token: '',
13 | expiredAt: '',
14 | ...auth
15 | }),
16 | actions: {
17 | /**
18 | * 登入系统
19 | * @param {*} api 接口
20 | * @param {*} params 参数
21 | * @returns
22 | */
23 | async sign(api, params) {
24 | const r = await api(params)
25 | if (r) {
26 | setAuth(r.data)
27 | this.$state = r.data
28 | }
29 | return r
30 | },
31 | /**
32 | * 登录
33 | * @param {*} params
34 | * @returns
35 | */
36 | login(params) {
37 | return this.sign(loginApi, params)
38 | },
39 | /**
40 | * 注册
41 | * @param {*} params
42 | * @returns
43 | */
44 | async register(params) {
45 | return this.sign(registerApi, params)
46 | },
47 | /**
48 | * QQ登录
49 | * @param {*} params
50 | * @returns
51 | */
52 | async qqLogin(params) {
53 | return this.sign(loginQQApi, params)
54 | },
55 | /**
56 | * 退出登录
57 | */
58 | async logout() {
59 | const r = await logoutApi()
60 | return r
61 | },
62 | /**
63 | * 校验token 是否过期
64 | */
65 | validateToken() {
66 | const { token, expiredAt } = this.$state
67 | if (!token.trim() || dayjs(expiredAt).valueOf() < +new Date()) {
68 | return false
69 | }
70 | return true
71 | },
72 | /**
73 | * 清除数据
74 | */
75 | clear() {
76 | clearAuth()
77 | clearJson(this.$state)
78 | }
79 | }
80 | })
81 |
--------------------------------------------------------------------------------
/src/stores/modules/conversation.js:
--------------------------------------------------------------------------------
1 | import { clearJson } from '@utils'
2 |
3 | import { listApi } from '@/api/conversation'
4 |
5 | export const useConversationStore = defineStore('conversation', {
6 | state: () => ({
7 | active: null,
8 | list: [],
9 | }),
10 | getters: {
11 | hasUnread: ({ list }) => {
12 | return list.some(item => item.unread > 0)
13 | }
14 | },
15 | actions: {
16 | /**
17 | * 获取会话列表
18 | * @param {*} params
19 | * @returns
20 | */
21 | async getList() {
22 | const r = await listApi()
23 | if (r) {
24 | this.$state.list = r.data
25 | }
26 | },
27 | /**
28 | * 新增会话 存在则更新消息
29 | * @param {*} conversation
30 | */
31 | addConversation(conversation) {
32 | const { roomId, message } = conversation
33 | const exist = this.list.find(item => item.roomId === roomId)
34 | if (exist) {
35 | exist.message = message
36 | } else {
37 | this.list.unshift(conversation)
38 | }
39 | },
40 | /**
41 | * 更新消息
42 | * @param {*} id 消息ID
43 | * @param {*} conversation 会话
44 | */
45 | updateMessage(messageId, conversation){
46 | for (let i = 0; i < this.list.length; i++) {
47 | const row = this.list[i];
48 | if (messageId === row.message.id){
49 | row.message = conversation.message
50 | return
51 | }
52 | }
53 | },
54 | /**
55 | * 设置未读
56 | * @param {*} id
57 | */
58 | setUnread(id) {
59 | const conversation = this.list.find(item => item.id === id)
60 | if (conversation.unread) {
61 | conversation.unread += 1
62 | } else {
63 | conversation.unread = 1
64 | }
65 | },
66 | /**
67 | * 设置已读
68 | * @param {*} id
69 | * @returns
70 | */
71 | setRead(id) {
72 | const conversation = this.list.find(item => item.id === id)
73 | conversation.unread = 0
74 | return conversation
75 | },
76 | /**
77 | * 设置选中
78 | * @param {*} conversation
79 | */
80 | setActive({ id }) {
81 | const conversation = this.setRead(id)
82 | this.active = conversation
83 | },
84 | /**
85 | * 更新用户在线状态
86 | * @param {*} userId 用户ID
87 | * @param {*} online 在线状态
88 | */
89 | updateUserOnline(userId, online) {
90 | for (let i = 0; i < this.list.length; i++) {
91 | const { friend } = this.list[i];
92 | if (friend && friend.userId === userId) {
93 | friend.online = online
94 | break
95 | }
96 | }
97 | },
98 | /**
99 | * 清除数据
100 | */
101 | clear() {
102 | clearJson(this.$state)
103 | }
104 | }
105 | })
106 |
--------------------------------------------------------------------------------
/src/stores/modules/expression.js:
--------------------------------------------------------------------------------
1 | import { clearJson } from '@utils'
2 |
3 | import { listApi } from '@/api/expression'
4 |
5 | export const useExpressionStore = defineStore('expression', {
6 | state: () => ({
7 | list: [],
8 | }),
9 | actions: {
10 | /**
11 | * 获取表情列表
12 | * @param {*} params
13 | * @returns
14 | */
15 | async getList() {
16 | const r = await listApi()
17 | if (r) {
18 | this.$state.list = r.data
19 | }
20 | },
21 | /**
22 | * 清除数据
23 | */
24 | clear() {
25 | clearJson(this.$state)
26 | }
27 | }
28 | })
29 |
--------------------------------------------------------------------------------
/src/stores/modules/grouping.js:
--------------------------------------------------------------------------------
1 | import { clearJson } from '@utils'
2 |
3 | import { listApi } from '@/api/grouping'
4 |
5 | export const useGroupingStore = defineStore('grouping', {
6 | state: () => ({
7 | active: null,
8 | list: [],
9 | keyword: ''
10 | }),
11 | getters: {
12 | filterList: (state) => {
13 | const result = []
14 | const { list, keyword } = state
15 | list.forEach(grouping => {
16 | const exist = grouping.friends.filter(({ nickname , remark }) => nickname.includes(keyword) || remark.includes(keyword))
17 | exist.sort((a, b) => {
18 | return b.online - a.online
19 | })
20 | result.push({
21 | ...grouping,
22 | friends: exist
23 | })
24 | })
25 | return result
26 | }
27 | },
28 | actions: {
29 | /**
30 | * 获取分组好友列表
31 | * @param {*} params
32 | * @returns
33 | */
34 | async getList() {
35 | const r = await listApi()
36 | if (r) {
37 | this.$state.list = r.data
38 | }
39 | },
40 | /**
41 | * 新增好友
42 | * @param {*} params
43 | */
44 | addFriend(grouping) {
45 | const { id, friends } = grouping
46 | const exist = this.list.find(item => item.id === id)
47 | if (exist) {
48 | exist.friends.push(...friends)
49 | } else {
50 | this.list.push(grouping)
51 | }
52 | },
53 | /**
54 | * 移除好友
55 | * @param {*} userId
56 | */
57 | removeFriend(userId) {
58 | let flag = false, groupIndex = '', friendIndex = ''
59 |
60 | outer:for (let i = 0; i < this.list.length; i++) {
61 | const grouping = this.list[i];
62 | for (let j = 0; j < grouping.friends.length; j++) {
63 | const friend = grouping.friends[j];
64 | if (friend.userId === userId) {
65 | flag = true
66 | groupIndex = i
67 | friendIndex = j
68 | break outer
69 | }
70 | }
71 | }
72 |
73 | if (flag) {
74 | this.list[groupIndex].friends.splice(friendIndex, 1);
75 | }
76 | },
77 | /**
78 | * 设置选中
79 | * @param {*} friend
80 | */
81 | setActive({ id }) {
82 | let friend = null
83 | for (let i = 0; i < this.list.length; i++) {
84 | const { friends } = this.list[i];
85 | friend = friends.find(item => item.id === id)
86 | if (friend) {
87 | break
88 | }
89 | }
90 | this.active = friend
91 | },
92 | /**
93 | * 更新用户在线状态
94 | * @param {*} userId 用户ID
95 | * @param {*} online 在线状态
96 | */
97 | updateUserOnline(userId, online) {
98 | for (let i = 0; i < this.list.length; i++) {
99 | const { friends } = this.list[i];
100 | inner:for (let j = 0; j < friends.length; j++) {
101 | const friend = friends[j];
102 | if (friend.userId === userId) {
103 | friend.online = online
104 | break inner;
105 | }
106 | }
107 | }
108 | },
109 | /**
110 | * 清除数据
111 | */
112 | clear() {
113 | clearJson(this.$state)
114 | }
115 | }
116 | })
117 |
--------------------------------------------------------------------------------
/src/stores/modules/room.js:
--------------------------------------------------------------------------------
1 | import { ONLINE_STATUS } from '@enums/user'
2 | import { clearJson } from '@utils'
3 |
4 | import { pageApi } from '@/api/message'
5 | import { roomGroupUserPageApi, roomGroupUserCountApi } from '@/api/room'
6 |
7 | export const useRoomStore = defineStore('room', {
8 | state: () => ({
9 | list: [],
10 | }),
11 | actions: {
12 | /**
13 | * 获取消息列表
14 | * @param {*} roomId 房间ID
15 | * @param {*} lastId 最后一个ID
16 | * @param {*} size 数据量
17 | * @returns
18 | */
19 | async getMessageList(roomId, lastId, size = 10) {
20 | const r = await pageApi({ roomId, lastId, size })
21 | if (r) {
22 | const messageList = r.data.reverse()
23 | const room = this.list.find(room => room.id === roomId)
24 | if (room) {
25 | const { messages } = room
26 | messages.unshift(...messageList)
27 | } else {
28 | this.list.push({
29 | id: roomId,
30 | messages: messageList,
31 | userTotalCount: 0,
32 | userOnlineCount: 0,
33 | users: []
34 | })
35 | }
36 | return messageList
37 | }
38 | },
39 | /**
40 | * 更新消息
41 | * @param {*} id 消息ID
42 | * @param {*} conversation 会话
43 | */
44 | updateMessage(messageId, conversation){
45 | const { roomId, message } = conversation
46 | for (let i = 0; i < this.list.length; i++) {
47 | const { messages } = this.list[i];
48 | for (let j = 0; j < messages.length; j++) {
49 | if (messageId === messages[j].id) {
50 | messages[j] = message
51 | return
52 | }
53 | }
54 | }
55 | },
56 | /**
57 | * 获取房间用户
58 | * @param {*} roomId 房间ID
59 | * @param {*} lastId 最后一个ID
60 | * @param {*} size 数据量
61 | * @returns
62 | */
63 | async getUserList(roomId, lastId, size = 40) {
64 | const r = await roomGroupUserPageApi({ roomId, lastId, size })
65 | if (r) {
66 | const userList = r.data
67 | const room = this.list.find(room => room.id === roomId)
68 | if (room) {
69 | const { users } = room
70 | users.push(...userList)
71 | } else {
72 | this.list.push({
73 | id: roomId,
74 | messages: [],
75 | userTotalCount: 0,
76 | userOnlineCount: 0,
77 | users: userList
78 | })
79 | }
80 | return userList
81 | }
82 | },
83 | /**
84 | * 获取房间用户数量
85 | * @param {*} roomId 房间ID
86 | * @returns
87 | */
88 | async getUserCount(roomId) {
89 | const r = await roomGroupUserCountApi({ roomId })
90 | if (r) {
91 | const { totalCount, onlineCount } = r.data
92 | const room = this.list.find(room => room.id === roomId)
93 | if (room) {
94 | room.userTotalCount = totalCount
95 | room.userOnlineCount = onlineCount
96 | } else {
97 | this.list.push({
98 | id: roomId,
99 | messages: [],
100 | userTotalCount: totalCount,
101 | userOnlineCount: onlineCount,
102 | users: [],
103 | })
104 | }
105 | return r.data
106 | }
107 | },
108 |
109 | /**
110 | * 新增房间 存在则新增消息 不存在则获取消息列表
111 | * @param {*} roomId 房间ID
112 | * @param {*} message 消息
113 | */
114 | addRoom(roomId, message) {
115 | const room = this.list.find(item => item.id === roomId)
116 | if (room) {
117 | room.messages.push(message)
118 | } else {
119 | this.getMessageList(roomId)
120 | }
121 | },
122 | /**
123 | * 添加用户
124 | */
125 | addUser(user) {
126 | const room = this.list.find(room => room.id === user.roomId)
127 | if (room) {
128 | const { users } = room
129 | const { roomUserId } = users[users.length - 1]
130 | if (user.roomUserId === roomUserId + 1) {
131 | users.push(user)
132 | }
133 | room.userTotalCount += 1
134 | room.userOnlineCount += 1
135 | }
136 | },
137 | /**
138 | * 更新用户信息
139 | * @param {*} id 房间ID
140 | * @param {*} nickname 消息
141 | */
142 | updateUser(user){
143 | this.list.forEach(room => {
144 | const { users } = room
145 | for (let i = 0; i < users.length; i++) {
146 | const item = users[i];
147 | if (item.id === user.id) {
148 | item.nickname = user.nickname,
149 | item.avatar = user.avatar,
150 | item.sex = user.sex
151 | break
152 | }
153 | }
154 | });
155 | },
156 | /**
157 | * 更新用户在线状态
158 | * @param {*} userId 用户ID
159 | * @param {*} online 在线状态
160 | */
161 | updateUserOnline(userId, online) {
162 | for (let i = 0; i < this.list.length; i++) {
163 | const room = this.list[i];
164 | const { users } = room
165 | inner:for (let j = 0; j < users.length; j++) {
166 | const user = users[j];
167 | if (user.id === userId) {
168 | user.online = online
169 | if (online === ONLINE_STATUS.ONLINE) {
170 | room.userOnlineCount += 1
171 | } else if (online === ONLINE_STATUS.OFFLINE) {
172 | room.userOnlineCount -= 1
173 | }
174 | break inner;
175 | }
176 | }
177 | }
178 | },
179 | /**
180 | * 清除数据
181 | */
182 | clear() {
183 | clearJson(this.$state)
184 | }
185 | }
186 | })
187 |
--------------------------------------------------------------------------------
/src/stores/modules/user.js:
--------------------------------------------------------------------------------
1 | import { clearJson } from '@utils'
2 |
3 | import { userInfoApi } from '@/api/auth'
4 |
5 | export const useUserStore = defineStore('user', {
6 | state: () => ({
7 | id: '',
8 | nickname: '',
9 | avatar: '',
10 | sex: '',
11 | online: '',
12 | email: '',
13 | lastAt: '',
14 | status: '',
15 | createdAt: '',
16 | token: '',
17 | expireAt: ''
18 | }),
19 | actions: {
20 | /**
21 | * 登录
22 | * @param {*} params
23 | * @returns
24 | */
25 | async getUserInfo() {
26 | const r = await userInfoApi()
27 | if (r) {
28 | this.$state = r.data
29 | }
30 | return r ? r.data : null
31 | },
32 |
33 | /**
34 | * 清除数据
35 | */
36 | clear() {
37 | clearJson(this.$state)
38 | }
39 | }
40 | })
41 |
--------------------------------------------------------------------------------
/src/stores/modules/websocket.js:
--------------------------------------------------------------------------------
1 | import WebsocketClass from '@utils/websocket'
2 | import { getWebsocketOrigin } from '@utils'
3 | import { WEBSOCKET_MAPPING } from '@constants'
4 | import { WEBSOCKET_TYPE } from '@enums/websocket'
5 |
6 | export const useWebsocketStore = defineStore('websocket', {
7 | state: () => ({
8 | response: null,
9 | socket: null
10 | }),
11 | getters: {},
12 | actions: {
13 | /**
14 | * 初始化websocket
15 | */
16 | init() {
17 | if (!this.socket) {
18 | const url = `${ getWebsocketOrigin() }${ WEBSOCKET_MAPPING }/${ useAuthStore().token }`
19 | this.socket = new WebsocketClass(url, data => {
20 | if (data && data.type === WEBSOCKET_TYPE.HEARTBEAT) {
21 | return
22 | }
23 | this.response = data
24 | console.log('🚲~~:', data)
25 | })
26 | this.socket.connect()
27 | }
28 | },
29 | /**
30 | * 发送信息
31 | * @param {*} data
32 | */
33 | send(params) {
34 | this.socket.send(params)
35 | },
36 | /**
37 | * 手动断开websocket
38 | */
39 | close() {
40 | if (this.socket) {
41 | this.socket.close()
42 | }
43 | this.response = null
44 | this.socket = null
45 | }
46 | }
47 | })
48 |
--------------------------------------------------------------------------------
/src/stores/root.js:
--------------------------------------------------------------------------------
1 | import { useApplyStore } from "./modules/apply"
2 |
3 | export const useRootStore = defineStore('root', {
4 | state: () => ({}),
5 | getters: {},
6 | actions: {
7 | /**
8 | * 新增消息
9 | * @param {*} conversation
10 | */
11 | addMessage(conversation) {
12 | const { roomId, message } = conversation
13 | useConversationStore().addConversation(conversation)
14 | useRoomStore().addRoom(roomId, message)
15 | },
16 | /**
17 | * 新增未读消息
18 | * @param {*} conversation
19 | */
20 | addUnreadMessage(conversation) {
21 | this.addMessage(conversation)
22 | useConversationStore().setUnread(conversation.id)
23 | },
24 | /**
25 | * 更新消息状态
26 | */
27 | updateMessage(id, conversation, sendStatus){
28 | conversation.message.sendStatus = sendStatus
29 | useConversationStore().updateMessage(id, conversation)
30 | useRoomStore().updateMessage(id, conversation)
31 | },
32 | /**
33 | * 更新好友,群用户在线状态
34 | * @param {*} userId 用户ID
35 | * @param {*} online 在线状态
36 | */
37 | updateUserOnline(userId, online) {
38 | useConversationStore().updateUserOnline(userId, online)
39 | useGroupingStore().updateUserOnline(userId, online)
40 | useRoomStore().updateUserOnline(userId, online)
41 | useApplyStore().updateUserOnline(userId, online)
42 | },
43 | /**
44 | * 清除用户数据 用户信息
45 | * @param {*}
46 | */
47 | clearUserData() {
48 | useUserStore().clear()
49 | },
50 | /**
51 | * 清除所有数据
52 | * @param {*} param0
53 | */
54 | clearData() {
55 | this.clearUserData()
56 | useAuthStore().clear()
57 | }
58 | }
59 | })
60 |
--------------------------------------------------------------------------------
/src/views/apply/components/apply-card/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
11 |
12 |
13 |
14 |
15 |
75 |
76 |
89 |
--------------------------------------------------------------------------------
/src/views/apply/components/apply-panel/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | 接受
10 |
11 |
17 |
18 |
19 |
20 | 拒绝
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 | 发送消息
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
111 |
112 |
122 |
--------------------------------------------------------------------------------
/src/views/apply/components/pass-dialog/components/form-ui/index.js:
--------------------------------------------------------------------------------
1 | import { form, loading } from '@props'
2 |
3 | export const props = {
4 | form,
5 | loading,
6 | groupings: { type: Array, default: () => [] }
7 | }
8 |
9 | export const rules = {
10 | groupingId: [{ required: true, message: '请选择分组', trigger: 'change' }],
11 | }
12 |
--------------------------------------------------------------------------------
/src/views/apply/components/pass-dialog/components/form-ui/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
11 |
12 |
13 |
14 |
19 |
20 |
21 | 接受
22 |
23 |
24 |
25 |
47 |
48 |
--------------------------------------------------------------------------------
/src/views/apply/components/pass-dialog/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
93 |
94 |
--------------------------------------------------------------------------------
/src/views/apply/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
62 |
63 |
66 |
--------------------------------------------------------------------------------
/src/views/conversation/components/conversation-card/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
11 |
12 |
13 |
{{ message }}
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
88 |
89 |
100 |
--------------------------------------------------------------------------------
/src/views/conversation/components/editor/components/audio/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | 语音录制中
8 |
9 |
10 |
11 |
12 |
64 |
65 |
75 |
--------------------------------------------------------------------------------
/src/views/conversation/components/editor/components/expression/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
{{ item.content }}
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
37 |
38 |
43 |
44 |
55 |
--------------------------------------------------------------------------------
/src/views/conversation/components/editor/components/file/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
37 |
38 |
41 |
--------------------------------------------------------------------------------
/src/views/conversation/components/editor/components/image/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
38 |
39 |
42 |
--------------------------------------------------------------------------------
/src/views/conversation/components/editor/components/video-call/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
35 |
36 |
39 |
--------------------------------------------------------------------------------
/src/views/conversation/components/editor/components/voice-call/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
35 |
36 |
39 |
--------------------------------------------------------------------------------
/src/views/conversation/components/group-user-panel/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 |
9 |
10 |
11 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
124 |
125 |
135 |
--------------------------------------------------------------------------------
/src/views/conversation/components/group-user/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
{{ name }}
7 |
8 |
9 |
10 |
29 |
30 |
42 |
--------------------------------------------------------------------------------
/src/views/conversation/components/message-panel/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
8 |
9 |
10 |
11 |
12 |
{{ key }}
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
191 |
192 |
210 |
--------------------------------------------------------------------------------
/src/views/conversation/components/message/components/audio-message/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 |
{{ duration }}''
9 |
10 |
11 |
12 |
13 |
73 |
74 |
130 |
--------------------------------------------------------------------------------
/src/views/conversation/components/message/components/file-message/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
19 |
20 |
29 |
--------------------------------------------------------------------------------
/src/views/conversation/components/message/components/image-message/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
19 |
20 |
29 |
--------------------------------------------------------------------------------
/src/views/conversation/components/message/components/text-message/index.vue:
--------------------------------------------------------------------------------
1 |
2 | {{ text.trim() }}
3 |
4 |
5 |
13 |
14 |
29 |
--------------------------------------------------------------------------------
/src/views/conversation/components/message/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | {{ message.nickname }}
7 | ({{ message.email }})
8 | {{ time }}
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
97 |
98 |
127 |
--------------------------------------------------------------------------------
/src/views/conversation/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
32 |
33 |
37 |
--------------------------------------------------------------------------------
/src/views/friend/components/friend-crad/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
11 |
12 |
47 |
48 |
50 |
--------------------------------------------------------------------------------
/src/views/friend/components/friend-panel/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | 发送消息
9 |
10 |
16 |
17 |
18 |
19 | 删除好友
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
62 |
63 |
73 |
--------------------------------------------------------------------------------
/src/views/friend/components/headbar/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
26 |
27 |
30 |
--------------------------------------------------------------------------------
/src/views/friend/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
41 |
42 |
67 |
--------------------------------------------------------------------------------
/src/views/group/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 | 群组
4 |
5 |
6 |
9 |
10 |
13 |
--------------------------------------------------------------------------------
/src/views/layout/components/media-dialog/components/operation/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
12 |
13 |
14 |
15 |
16 |
17 |
93 |
94 |
98 |
--------------------------------------------------------------------------------
/src/views/layout/components/media-dialog/components/status/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
8 |
9 |
46 |
47 |
57 |
--------------------------------------------------------------------------------
/src/views/layout/components/media-dialog/components/user-box/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
8 |
20 |
21 |
24 |
--------------------------------------------------------------------------------
/src/views/layout/components/media-dialog/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
54 |
55 |
78 |
--------------------------------------------------------------------------------
/src/views/layout/components/promote/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
12 |
13 |
14 |
{{ item.label }}
15 |
16 |
17 |
18 | 💖 如果你觉得这个项目帮助到了你,可以扫码加群与大佬们一起探讨、关注公众号支持一下作者🦀🦀
19 |
20 | 🚀 如果遇到消息发送、加载过慢等问题皆是服务器带宽问题☹️☹️,终究是被贫穷打败😭😭
21 |
22 |
23 |
24 | 关闭
25 |
26 |
27 |
28 |
29 |
30 |
63 |
64 |
72 |
--------------------------------------------------------------------------------
/src/views/layout/components/sidebar/components/edit-email-dialog/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
11 |
12 |
13 |
55 |
56 |
58 |
--------------------------------------------------------------------------------
/src/views/layout/components/sidebar/components/edit-email-dialog/ui/index.js:
--------------------------------------------------------------------------------
1 | import { form, loading } from '@props'
2 | import { email, captcha, } from '@rules/user'
3 |
4 | export const props = { form, loading }
5 |
6 | export const rules = { originalEmail: email, newEmail: email, captcha, }
7 |
--------------------------------------------------------------------------------
/src/views/layout/components/sidebar/components/edit-email-dialog/ui/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
9 |
12 |
13 |
14 |
15 |
16 |
17 |
18 | 保存
19 |
20 |
21 |
22 |
41 |
42 |
--------------------------------------------------------------------------------
/src/views/layout/components/sidebar/components/edit-info-dialog/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
11 |
12 |
13 |
53 |
54 |
56 |
--------------------------------------------------------------------------------
/src/views/layout/components/sidebar/components/edit-info-dialog/ui/index.js:
--------------------------------------------------------------------------------
1 | import { form, loading } from '@props'
2 | import { nickname, sex, email, captcha, } from '@rules/user'
3 |
4 | export const props = { form, loading }
5 |
6 | export const rules = { nickname, sex, email, captcha, }
7 |
--------------------------------------------------------------------------------
/src/views/layout/components/sidebar/components/edit-info-dialog/ui/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
21 | {{ item.label }}
22 |
23 |
24 |
25 | 保存
26 |
27 |
28 |
29 |
48 |
49 |
--------------------------------------------------------------------------------
/src/views/layout/components/sidebar/components/publicize/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
43 |
44 |
55 |
--------------------------------------------------------------------------------
/src/views/layout/components/sidebar/components/tabbar/components/tab-apply/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
22 |
23 |
25 |
--------------------------------------------------------------------------------
/src/views/layout/components/sidebar/components/tabbar/components/tab-conversation/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
15 |
16 |
18 |
--------------------------------------------------------------------------------
/src/views/layout/components/sidebar/components/tabbar/components/tab-exit/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
27 |
28 |
30 |
--------------------------------------------------------------------------------
/src/views/layout/components/sidebar/components/tabbar/components/tab-friend/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
10 |
11 |
13 |
--------------------------------------------------------------------------------
/src/views/layout/components/sidebar/components/tabbar/components/tab-group/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
10 |
11 |
13 |
--------------------------------------------------------------------------------
/src/views/layout/components/sidebar/components/tabbar/components/tab/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
8 |
9 |
{{ label }}
10 |
11 |
12 |
13 |
46 |
47 |
67 |
--------------------------------------------------------------------------------
/src/views/layout/components/sidebar/components/tabbar/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
11 |
18 |
19 |
23 |
--------------------------------------------------------------------------------
/src/views/layout/components/sidebar/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
27 |
28 |
29 |
60 |
61 |
68 |
--------------------------------------------------------------------------------
/src/views/layout/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
19 |
20 |
34 |
--------------------------------------------------------------------------------
/src/views/login/components/login-form/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
40 |
41 |
43 |
--------------------------------------------------------------------------------
/src/views/login/components/login-form/ui/index.js:
--------------------------------------------------------------------------------
1 | import { form, loading } from '@props'
2 | import { email, captcha } from '@rules/user'
3 |
4 | export const props = { form, loading }
5 |
6 | export const rules = { email, captcha }
7 |
--------------------------------------------------------------------------------
/src/views/login/components/login-form/ui/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
10 |
11 |
12 |
17 |
18 |
19 | 登录
20 |
21 |
22 |
23 |
42 |
43 |
--------------------------------------------------------------------------------
/src/views/login/components/other/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
其它登录方式
4 |
5 |
6 |
7 |
8 |
9 |
10 |
14 |
15 |
23 |
--------------------------------------------------------------------------------
/src/views/login/components/other/qq/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
52 |
53 |
55 |
--------------------------------------------------------------------------------
/src/views/login/components/panel/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
10 |
11 |
18 |
--------------------------------------------------------------------------------
/src/views/login/components/register-form/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
44 |
45 |
47 |
--------------------------------------------------------------------------------
/src/views/login/components/register-form/ui/index.js:
--------------------------------------------------------------------------------
1 | import { form, loading } from '@props'
2 | import { nickname, sex, email, captcha, } from '@rules/user'
3 |
4 | export const props = { form, loading }
5 |
6 | export const rules = { nickname, sex, email, captcha, }
7 |
--------------------------------------------------------------------------------
/src/views/login/components/register-form/ui/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
20 | {{ item.label }}
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 | 注册
31 |
32 |
33 |
34 |
54 |
55 |
--------------------------------------------------------------------------------
/src/views/login/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | 未有账号?去注册
7 |
8 |
9 |
10 |
11 | 已有账号?去登录
12 |
13 |
14 |
15 |
16 |
17 |
29 |
30 |
34 |
--------------------------------------------------------------------------------
/vite.config.js:
--------------------------------------------------------------------------------
1 | import { fileURLToPath, URL } from 'node:url'
2 |
3 | import { defineConfig, loadEnv } from 'vite'
4 | import vue from '@vitejs/plugin-vue'
5 | import vueJsx from '@vitejs/plugin-vue-jsx'
6 | import VueDevTools from 'vite-plugin-vue-devtools'
7 |
8 | import AutoImport from 'unplugin-auto-import/vite'
9 | import Components from 'unplugin-vue-components/vite'
10 | import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'
11 | import Icons from 'unplugin-icons/vite'
12 | import IconsResolver from 'unplugin-icons/resolver'
13 |
14 | const aliasPath = (path) => {
15 | return fileURLToPath(new URL(path, import.meta.url))
16 | }
17 |
18 | // https://vitejs.dev/config/
19 | export default defineConfig(({ mode }) => {
20 | const env = loadEnv(mode, process.cwd(), 'VITE_')
21 |
22 | return {
23 | plugins: [
24 | vue(),
25 | vueJsx(),
26 | VueDevTools(),
27 | AutoImport({
28 | imports: ['vue', 'vue-router', 'pinia'],
29 | resolvers: [ElementPlusResolver(), IconsResolver({ prefix: 'Icon' })],
30 | dirs: ['src/stores/*'],
31 | eslintrc: {
32 | enabled: true,
33 | filepath: './.eslintrc-auto-import.json',
34 | globalsPropValue: true
35 | }
36 | }),
37 | Components({
38 | resolvers: [ElementPlusResolver(), IconsResolver({ prefix: false, enabledCollections: ['ep'] })]
39 | }),
40 | Icons({
41 | autoInstall: true,
42 | }),
43 | ],
44 | // 项目根目录(index.html 文件所在的位置)。可以是一个绝对路径,或者一个相对于该配置文件本身的相对路径。
45 | root: process.cwd(),
46 | // 公共基础路径。
47 | base: './',
48 | // 作为静态资源服务的文件夹。
49 | publicDir: 'public',
50 | // 存储缓存文件的目录。
51 | cacheDir: 'node_modules/.vite',
52 | resolve: {
53 | // 路径别名
54 | alias: {
55 | '@': aliasPath('./src'),
56 | '@utils': aliasPath('./src/common/utils'),
57 | '@enums': aliasPath('./src/common/enums'),
58 | '@constants': aliasPath('./src/common/constants'),
59 | '@props': aliasPath('./src/common/props'),
60 | '@rules': aliasPath('./src/common/rules'),
61 | }
62 | },
63 | css: {
64 | // 在开发过程中是否启用 sourcemap。
65 | devSourcemap: true
66 | },
67 | // 控制台输出的级别。
68 | logLevel: 'info',
69 | // 避免 Vite 清屏而错过在终端中打印某些关键信息。
70 | clearScreen: true,
71 | // 以 envPrefix 开头的环境变量会通过 import.meta.env 暴露在你的客户端源码中。
72 | envPrefix: 'VITE_',
73 | // 开发服务器选项
74 | server: {
75 | // 指定服务器应该监听哪个 IP 地址。
76 | host: true,
77 | // 服务器端口。
78 | port: env.VITE_PORT,
79 | // 若端口已被占用则会直接退出
80 | strictPort: true,
81 | // 自动打开浏览器。
82 | open: false,
83 | // 代理。
84 | proxy: {
85 | '^/chatterbox/websocket': {
86 | target: 'https://chatterbox.gumingchen.icu',
87 | // target: 'http://localhost:8831',
88 | changeOrigin: true,
89 | ws: true
90 | },
91 | '^/chatterbox': {
92 | target: 'https://chatterbox.gumingchen.icu',
93 | // target: 'http://localhost:8830',
94 | changeOrigin: true,
95 | },
96 | '^/resource': {
97 | target: 'https://chatterbox.gumingchen.icu',
98 | // target: 'http://localhost:8830',
99 | changeOrigin: true,
100 | }
101 | },
102 | // 为开发服务器配置 CORS。
103 | cors: true
104 | },
105 | // 构建选项
106 | build: {
107 | // 设置最终构建的浏览器兼容目标。
108 | target: 'modules',
109 | // 决定是否自动注入 module preload 的 polyfill。
110 | // polyfillModulePreload: true,
111 | modulePreload: {
112 | polyfill: true
113 | },
114 | // 指定输出路径(相对于 项目根目录).
115 | outDir: 'dist',
116 | // 指定生成静态资源的存放路径(相对于 build.outDir)。
117 | assetsDir: 'assets',
118 | // 小于此阈值的导入或引用资源将内联为 base64 编码
119 | assetsInlineLimit: 'assets',
120 | // 启用/禁用 CSS 代码拆分。
121 | cssCodeSplit: true,
122 | // 构建后是否生成 source map 文件。 boolean | 'inline' | 'hidden'
123 | sourcemap: false,
124 | // chunk 大小警告的限制(以 kbs 为单位)。
125 | chunkSizeWarningLimit: 500
126 | }
127 | }
128 | })
129 |
--------------------------------------------------------------------------------