Compare commits
10 Commits
23308985d5
...
b2c48273e2
Author | SHA1 | Date | |
---|---|---|---|
![]() |
b2c48273e2 | ||
![]() |
7fb2a3b58d | ||
![]() |
4a854c291a | ||
![]() |
fa78f8667b | ||
![]() |
da6512e7aa | ||
![]() |
ce562f59f8 | ||
![]() |
7e541c415e | ||
![]() |
a5b445cbf8 | ||
![]() |
bf74cdc0c1 | ||
![]() |
bbf6558569 |
43
CODE_OF_CONDUCT.md
Normal file
43
CODE_OF_CONDUCT.md
Normal file
@ -0,0 +1,43 @@
|
||||
# 开发要求
|
||||
|
||||
## 我们的目标
|
||||
|
||||
为了高效、高质量的完成项目交付目标。
|
||||
|
||||
## 我们的标准
|
||||
有助于提高编码效率和质量的行为包括:
|
||||
|
||||
- 1、遵守开发规范。
|
||||
- 2、使用eslint+Prettier自动修复代码
|
||||
- 3、代码行文尽量整洁规整。业务逻辑简洁、清晰。
|
||||
- 4、核心业务上必须要有注释,头部需要有注释便与溯源。
|
||||
- 5、公共组件开发需要记录文档且注释丰富。
|
||||
- 6、问题及细节需要有共享文档进行团队同步。
|
||||
- 7、阶段性的代码评审,整体回顾。
|
||||
- 8、提测前需要自测和交叉测试。
|
||||
|
||||
不可以接受的行为包括:
|
||||
|
||||
- 1、独狼行为,忽视团队的开发规范和开发要求。
|
||||
- 2、不自测的代码进行提测。
|
||||
- 3、无用的代码不删除,代码行文和逻辑冗余邋遢。
|
||||
- 4、对于细节及漏洞不用心对待,存在侥幸心理。
|
||||
- 5、恶意违反公司及团队规则。
|
||||
|
||||
一个人的力量始终是有限的,只有在团队中才能更好的发光发热,才完成更大的目标。
|
||||
保持对前端的热爱和新技术的追求。
|
||||
始终有对自己有要求,写的代码如诗歌一般优美。
|
||||
|
||||
|
||||
## 代码评审准则
|
||||
|
||||
- 1、是否遵守开发规范和开发要求。
|
||||
- 2、核心功能是否有缺陷。
|
||||
- 3、页面是否有缺陷。
|
||||
- 4、细节是否完善、整体代码行文。
|
||||
- 5、业务逻辑和代码细节是否合理。
|
||||
|
||||
## 相关文档
|
||||
|
||||
- 前端通用开发规范 [http://172.18.7.186:8181/read/team/prefix_59454ed663cdb4bb8d542c1967a62056.md]
|
||||
|
72
README.md
72
README.md
@ -1,13 +1,32 @@
|
||||
# vue3-element-admin
|
||||
## 简介
|
||||
|
||||
这个模板使用了最新的 vue3 和 element-plus UI 框架,vite 构建工具、pinia 状态管理、vue-router 路由管理、mockjs 数据模拟。功能从 Vue Element Admin 移植而来,详细使用可以参考[该文档](https://panjiachen.github.io/vue-element-admin-site/zh/guide/essentials/router-and-nav.html)。
|
||||
这个模板使用了最新的 vue3 和 element-plus UI 框架,vite 构建工具、pinia 状态管理、vue-router 路由管理、mockjs 数据模拟,并集成了 typescript。功能从 Vue Element Admin 移植而来,详细使用可以参考[该文档](https://vue3-element-admin-site.midfar.com/zh/guide/essentials/router-and-nav.html)。
|
||||
|
||||
# 在线示例
|
||||
## 特性
|
||||
|
||||
- **最新技术栈**:使用 Vue3/vite3 等前端前沿技术开发
|
||||
- **TypeScript**: 应用程序级 JavaScript 的语言
|
||||
- **Mock 数据** 内置 Mock 数据方案
|
||||
- **权限** 内置完善的动态路由权限生成方案
|
||||
- **组件** 二次封装了多个常用的组件
|
||||
|
||||
## 在线示例
|
||||
|
||||
[vue3 element admin](https://vue3-element-admin.midfar.com/)
|
||||
|
||||
[element plus](https://element-plus.midfar.com/)
|
||||
|
||||
## 准备
|
||||
开发前请确保熟悉并掌握以下技术栈:
|
||||
|
||||
- vue: https://cn.vuejs.org/
|
||||
- TypeScript:https://www.tslang.cn/index.html
|
||||
- element-plus:https://element-plus.midfar.com/
|
||||
- pinia: https://pinia.vuejs.org/zh/
|
||||
- vue-router: https://router.vuejs.org/zh/
|
||||
|
||||
注:开发前请务必阅读上述所有文档。应用至实际项目开发请修改 readme 内容。
|
||||
|
||||
## 推荐的 IDE 工具和插件
|
||||
|
||||
[VSCode](https://code.visualstudio.com/) + [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) (禁用 Vetur) + [TypeScript Vue Plugin (Volar)](https://marketplace.visualstudio.com/items?itemName=Vue.vscode-typescript-vue-plugin).
|
||||
@ -16,7 +35,29 @@
|
||||
|
||||
参考 [Vite 配置](https://vitejs.dev/config/).
|
||||
|
||||
## 安装依赖
|
||||
## 主要结构
|
||||
|
||||
```
|
||||
- mock // 模拟数据
|
||||
- public
|
||||
- src
|
||||
- components // 组件
|
||||
- views // 页面
|
||||
- tableTemplates // 示例模块
|
||||
- index.ts
|
||||
- login // 登录模块
|
||||
- index.vue
|
||||
- settings.ts // 全局配置
|
||||
- main.ts // 入口文件
|
||||
- types // TypeScript类型
|
||||
- package.json
|
||||
- CODE_OF_CONDUCT.md // 框架开发要求
|
||||
- README.md //框架使用手册
|
||||
```
|
||||
|
||||
## 使用
|
||||
|
||||
### 安装依赖
|
||||
|
||||
```sh
|
||||
npm install
|
||||
@ -40,14 +81,25 @@ npm run build:test
|
||||
npm run lint
|
||||
```
|
||||
|
||||
### 捐赠
|
||||
## 支持环境
|
||||
|
||||
如果你觉得这个项目帮助到了你,你可以帮作者买一杯果汁表示鼓励 :tropical_drink:
|
||||
现代浏览器。
|
||||
|
||||
wechat: midfar-sun
|
||||
| IE/Edge | Firefox | Chrome | Safari |
|
||||
| ---------------- | --------------- | --------------- | --------------- |
|
||||
| IE10, IE11, Edge | last 2 versions | last 2 versions | last 2 versions |
|
||||
|
||||
## License
|
||||
## TODO
|
||||
|
||||
[MIT](https://opensource.org/licenses/MIT)
|
||||
当前问题
|
||||
|
||||
Copyright (c) 2022-present, Midfar Sun
|
||||
- 暂未配置git提交规范。
|
||||
|
||||
## 参与贡献
|
||||
|
||||
我们非常欢迎你的贡献,你可以通过以下方式和我们一起共建基线框架:
|
||||
|
||||
- 联系维护人员 midfar@qq.com
|
||||
- 提交 pr
|
||||
- 修复 bug
|
||||
- 分享实践案例
|
||||
|
1
auto-imports.d.ts
vendored
1
auto-imports.d.ts
vendored
@ -3,4 +3,5 @@ export {}
|
||||
declare global {
|
||||
const ElMessage: typeof import('element-plus/es')['ElMessage']
|
||||
const ElMessageBox: typeof import('element-plus/es')['ElMessageBox']
|
||||
const ElNotification: typeof import('element-plus/es')['ElNotification']
|
||||
}
|
||||
|
12
components.d.ts
vendored
12
components.d.ts
vendored
@ -9,23 +9,24 @@ declare module '@vue/runtime-core' {
|
||||
export interface GlobalComponents {
|
||||
BackToTop: typeof import('./src/components/BackToTop/index.vue')['default']
|
||||
Breadcrumb: typeof import('./src/components/Breadcrumb/index.vue')['default']
|
||||
DndList: typeof import('./src/components/DndList/index.vue')['default']
|
||||
DragSelect: typeof import('./src/components/DragSelect/index.vue')['default']
|
||||
DropdownMenu: typeof import('./src/components/Share/DropdownMenu.vue')['default']
|
||||
Dropzone: typeof import('./src/components/Dropzone/index.vue')['default']
|
||||
EditorImage: typeof import('./src/components/Tinymce/components/EditorImage.vue')['default']
|
||||
ElAlert: typeof import('element-plus/es')['ElAlert']
|
||||
ElBadge: typeof import('element-plus/es')['ElBadge']
|
||||
ElBreadcrumb: typeof import('element-plus/es')['ElBreadcrumb']
|
||||
ElBreadcrumbItem: typeof import('element-plus/es')['ElBreadcrumbItem']
|
||||
ElButton: typeof import('element-plus/es')['ElButton']
|
||||
ElCard: typeof import('element-plus/es')['ElCard']
|
||||
ElCarousel: typeof import('element-plus/es')['ElCarousel']
|
||||
ElCarouselItem: typeof import('element-plus/es')['ElCarouselItem']
|
||||
ElCheckbox: typeof import('element-plus/es')['ElCheckbox']
|
||||
ElCheckboxGroup: typeof import('element-plus/es')['ElCheckboxGroup']
|
||||
ElCol: typeof import('element-plus/es')['ElCol']
|
||||
ElConfigProvider: typeof import('element-plus/es')['ElConfigProvider']
|
||||
ElDatePicker: typeof import('element-plus/es')['ElDatePicker']
|
||||
ElDialog: typeof import('element-plus/es')['ElDialog']
|
||||
ElDragSelect: typeof import('element-plus/es')['ElDragSelect']
|
||||
ElDropdown: typeof import('element-plus/es')['ElDropdown']
|
||||
ElDropdownItem: typeof import('element-plus/es')['ElDropdownItem']
|
||||
ElDropdownMenu: typeof import('element-plus/es')['ElDropdownMenu']
|
||||
@ -38,10 +39,14 @@ declare module '@vue/runtime-core' {
|
||||
ElOption: typeof import('element-plus/es')['ElOption']
|
||||
ElPagination: typeof import('element-plus/es')['ElPagination']
|
||||
ElProgress: typeof import('element-plus/es')['ElProgress']
|
||||
ElRadio: typeof import('element-plus/es')['ElRadio']
|
||||
ElRadioButton: typeof import('element-plus/es')['ElRadioButton']
|
||||
ElRadioGroup: typeof import('element-plus/es')['ElRadioGroup']
|
||||
ElRate: typeof import('element-plus/es')['ElRate']
|
||||
ElRow: typeof import('element-plus/es')['ElRow']
|
||||
ElScrollbar: typeof import('element-plus/es')['ElScrollbar']
|
||||
ElSelect: typeof import('element-plus/es')['ElSelect']
|
||||
ElSlider: typeof import('element-plus/es')['ElSlider']
|
||||
ElSubMenu: typeof import('element-plus/es')['ElSubMenu']
|
||||
ElSwitch: typeof import('element-plus/es')['ElSwitch']
|
||||
ElTable: typeof import('element-plus/es')['ElTable']
|
||||
@ -49,7 +54,10 @@ declare module '@vue/runtime-core' {
|
||||
ElTabPane: typeof import('element-plus/es')['ElTabPane']
|
||||
ElTabs: typeof import('element-plus/es')['ElTabs']
|
||||
ElTag: typeof import('element-plus/es')['ElTag']
|
||||
ElTimeline: typeof import('element-plus/es')['ElTimeline']
|
||||
ElTimelineItem: typeof import('element-plus/es')['ElTimelineItem']
|
||||
ElTooltip: typeof import('element-plus/es')['ElTooltip']
|
||||
ElTree: typeof import('element-plus/es')['ElTree']
|
||||
ElUpload: typeof import('element-plus/es')['ElUpload']
|
||||
ErrorLog: typeof import('./src/components/ErrorLog/index.vue')['default']
|
||||
GithubCorner: typeof import('./src/components/GithubCorner/index.vue')['default']
|
||||
|
@ -525,7 +525,7 @@ export const asyncRoutes = [
|
||||
component: 'layout/Layout',
|
||||
children: [
|
||||
{
|
||||
path: 'https://github.com/PanJiaChen/vue-element-admin',
|
||||
path: 'https://github.com/midfar/vue3-element-admin',
|
||||
meta: { title: 'External Link', icon: 'link' }
|
||||
}
|
||||
]
|
||||
|
@ -52,16 +52,13 @@ export default defineComponent({
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.drag-select {
|
||||
::v-deep {
|
||||
.sortable-ghost {
|
||||
opacity: .8;
|
||||
color: #fff !important;
|
||||
background: #42b983 !important;
|
||||
}
|
||||
|
||||
.el-tag {
|
||||
cursor: pointer;
|
||||
}
|
||||
:deep(.sortable-ghost) {
|
||||
opacity: .8;
|
||||
color: #fff !important;
|
||||
background: #42b983 !important;
|
||||
}
|
||||
:deep(.el-tag) {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<a href="https://github.com/PanJiaChen/vue-element-admin" target="_blank" class="github-corner" aria-label="View source on Github">
|
||||
<a href="https://github.com/midfar/vue3-element-admin" target="_blank" class="github-corner" aria-label="View source on Github">
|
||||
<svg
|
||||
width="80"
|
||||
height="80"
|
||||
|
@ -6,7 +6,7 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
// doc: https://panjiachen.github.io/vue-element-admin-site/feature/component/svg-icon.html#usage
|
||||
// doc: https://vue3-element-admin-site.midfar.com/feature/component/svg-icon.html#usage
|
||||
import { isExternal } from '@/utils/validate';
|
||||
import { defineComponent } from 'vue';
|
||||
|
||||
|
@ -11,7 +11,7 @@
|
||||
import { defineComponent } from 'vue';
|
||||
/**
|
||||
* docs:
|
||||
* https://panjiachen.github.io/vue-element-admin-site/feature/component/rich-editor.html#tinymce
|
||||
* https://vue3-element-admin-site.midfar.com/feature/component/rich-editor.html#tinymce
|
||||
*/
|
||||
import editorImage from './components/EditorImage';
|
||||
import plugins from './plugins';
|
||||
|
@ -34,10 +34,10 @@
|
||||
<router-link to="/">
|
||||
<el-dropdown-item>Dashboard</el-dropdown-item>
|
||||
</router-link>
|
||||
<a target="_blank" href="https://github.com/PanJiaChen/vue-element-admin/">
|
||||
<a target="_blank" href="https://github.com/midfar/vue3-element-admin">
|
||||
<el-dropdown-item>Github</el-dropdown-item>
|
||||
</a>
|
||||
<a target="_blank" href="https://panjiachen.github.io/vue-element-admin-site/#/">
|
||||
<a target="_blank" href="https://vue3-element-admin-site.midfar.com/">
|
||||
<el-dropdown-item>Docs</el-dropdown-item>
|
||||
</a>
|
||||
<el-dropdown-item divided @click="logout">
|
||||
|
@ -385,6 +385,12 @@ export const asyncRoutes:RouteRecordRaw[] = [
|
||||
component: () => import('@/views/mydemo/StoreDemo.vue'),
|
||||
name: 'StoreDemo',
|
||||
meta: { title: 'StoreDemo', icon: 'lock' }
|
||||
},
|
||||
{
|
||||
path: 'webSocket-demo',
|
||||
component: () => import('@/views/mydemo/WebSocketDemo.vue'),
|
||||
name: 'WebSocketDemo',
|
||||
meta: { title: 'WebSocketDemo', icon: 'lock' }
|
||||
}
|
||||
]
|
||||
},
|
||||
|
@ -7,6 +7,7 @@ import permissionStore from './permission';
|
||||
|
||||
export interface IUserState {
|
||||
token: string;
|
||||
userId: string,
|
||||
name: string;
|
||||
avatar: string;
|
||||
introduction: string;
|
||||
@ -17,6 +18,7 @@ export default defineStore({
|
||||
id: 'user',
|
||||
state: ():IUserState => ({
|
||||
token: getToken(),
|
||||
userId: '',
|
||||
name: '',
|
||||
avatar: '',
|
||||
introduction: '',
|
||||
|
19
src/store/modules/webSocket.ts
Normal file
19
src/store/modules/webSocket.ts
Normal file
@ -0,0 +1,19 @@
|
||||
import { defineStore } from 'pinia';
|
||||
|
||||
interface socketState {
|
||||
socketId: string;
|
||||
}
|
||||
|
||||
export default defineStore('useWebSocket', {
|
||||
state: (): socketState => ({
|
||||
socketId: '' // 会话id
|
||||
}),
|
||||
actions: {
|
||||
setSocketID(id: string) {
|
||||
this.socketId = id;
|
||||
},
|
||||
getSocketID() {
|
||||
return this.socketId;
|
||||
}
|
||||
}
|
||||
});
|
241
src/utils/webSocket.js
Normal file
241
src/utils/webSocket.js
Normal file
@ -0,0 +1,241 @@
|
||||
/*
|
||||
* @Description: Socket构造函数
|
||||
* @Author: Cherry
|
||||
* @Date: 2022-07-21 10:29:53
|
||||
* @LastEditors: Cherry 2858937488@qq.com
|
||||
*/
|
||||
|
||||
class Socket {
|
||||
constructor(options) {
|
||||
// 连接地址
|
||||
this.url = options.url || '';
|
||||
// 连接对象
|
||||
this.socket = null;
|
||||
// 是否允许重新连接
|
||||
this.lockReconnect = false;
|
||||
// 心跳间隔
|
||||
this.timeout = options.timeout || 1000 * 20;
|
||||
// 服务器响应间隔
|
||||
this.serverTimeout = options.serverTimeout || 2000;
|
||||
// 重连间隔
|
||||
this.reTimeout = options.reTimeout || 1000 * 10;
|
||||
// 心跳定时器
|
||||
this.timeoutTimer = null;
|
||||
// 服务器响应定时器
|
||||
this.serverTimeoutTimer = null;
|
||||
// 重连定时器
|
||||
this.reTimeoutTimer = null;
|
||||
// 重连计数,3次重连机制
|
||||
this.reCount = 0;
|
||||
// 制空打印方法
|
||||
const initConsole = {
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
||||
log: () => {},
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
||||
error: () => {},
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
||||
warn: () => {}
|
||||
};
|
||||
// 开启控制台打印
|
||||
this.logs = options.openConsole ? console : initConsole;
|
||||
// 消息传递
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
||||
this.onMsgEventCb = options.onMsgEventCb || (() => {});
|
||||
// 异常断开连接回调
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
||||
this.onDisconnectCb = options.onDisconnectCb || (() => {});
|
||||
}
|
||||
|
||||
// 建立连接
|
||||
create() {
|
||||
if (!this.url) {
|
||||
this.logs.error('请输入长连接地址');
|
||||
return;
|
||||
}
|
||||
// 创建WebSocket对象
|
||||
this.socket = new WebSocket(this.url);
|
||||
// 创建socket对象的事件监听
|
||||
this.initEvent();
|
||||
}
|
||||
|
||||
// 建立事件监听
|
||||
initEvent() {
|
||||
if (!this.socket) {
|
||||
this.logs.error('长连接对象不存在,请先调用create方法创建长连接对象');
|
||||
return;
|
||||
}
|
||||
// 监听连接事件
|
||||
this.socket.addEventListener('open', this.onOpenEvent.bind(this));
|
||||
// 监听错误事件
|
||||
this.socket.addEventListener('error', this.onErrorEvent.bind(this));
|
||||
// 监听服务端发送消息事件
|
||||
this.socket.addEventListener('message', this.onMessageEvent.bind(this));
|
||||
// 监听关闭连接事件
|
||||
this.socket.addEventListener('close', this.onCloseEvent.bind(this));
|
||||
}
|
||||
|
||||
// 移除事件监听
|
||||
removeEvent() {
|
||||
if (!this.socket) {
|
||||
this.logs.error('长连接对象不存在');
|
||||
return;
|
||||
}
|
||||
// 移除监听连接事件
|
||||
this.socket.removeEventListener('open', this.onOpenEvent.bind(this));
|
||||
// 移除监听错误事件
|
||||
this.socket.removeEventListener('error', this.onErrorEvent.bind(this));
|
||||
// 移除监听服务端发送消息事件
|
||||
this.socket.removeEventListener('message', this.onMessageEvent.bind(this));
|
||||
// 移除监听关闭连接事件
|
||||
this.socket.removeEventListener('close', this.onCloseEvent.bind(this));
|
||||
}
|
||||
|
||||
// 连接成功回调
|
||||
onOpenEvent(e) {
|
||||
this.logs.log('onOpenEvent e:', e);
|
||||
const t = e.currentTarget || {};
|
||||
if (t.readyState === 1) {
|
||||
this.closeReConnect();
|
||||
this.heartCheckStart();
|
||||
}
|
||||
}
|
||||
|
||||
// 连接失败回调
|
||||
onErrorEvent(err) {
|
||||
this.logs.error('onErrorEvent err:', err);
|
||||
this.reConnect();
|
||||
}
|
||||
|
||||
// 接受服务端消息回调
|
||||
onMessageEvent(e) {
|
||||
this.logs.log('onMessageEvent e:', e);
|
||||
this.heartCheckReset();
|
||||
const { data } = e;
|
||||
try {
|
||||
const dataObj = data ? JSON.parse(data) : {};
|
||||
if (dataObj.msgType === 2 || dataObj.msgType === 3) {
|
||||
this.onMsgEventCb(dataObj);
|
||||
}
|
||||
} catch (err) {
|
||||
this.logs.log('data解析失败 err:', err);
|
||||
}
|
||||
}
|
||||
|
||||
// 关闭连接回调
|
||||
onCloseEvent(e) {
|
||||
this.logs.log('onCloseEvent e:', e);
|
||||
this.logs.log(`websocket 断开: ${e.code} ${e.reason} ${e.wasClean}`);
|
||||
this.reConnect();
|
||||
}
|
||||
|
||||
// 建立连接心跳
|
||||
heartCheckStart() {
|
||||
this.timeoutTimer = setTimeout(() => {
|
||||
this.send(
|
||||
JSON.stringify({
|
||||
msgType: 1,
|
||||
heart: true
|
||||
})
|
||||
);
|
||||
this.logs.log('send heart');
|
||||
|
||||
// 3、发送数据 2s后没有接收到返回的数据进行关闭websocket重连
|
||||
this.serverTimeoutTimer = setTimeout(() => {
|
||||
this.logs.log('后台挂掉,没有心跳了....');
|
||||
clearTimeout(this.timeoutTimer);
|
||||
clearTimeout(this.serverTimeoutTimer);
|
||||
this.reConnect();
|
||||
}, this.serverTimeout);
|
||||
}, this.timeout);
|
||||
}
|
||||
|
||||
// 重置连接心跳
|
||||
heartCheckReset() {
|
||||
clearTimeout(this.timeoutTimer);
|
||||
clearTimeout(this.serverTimeoutTimer);
|
||||
this.heartCheckStart();
|
||||
}
|
||||
|
||||
// 重连
|
||||
reConnect() {
|
||||
this.timeoutTimer && clearTimeout(this.timeoutTimer);
|
||||
this.serverTimeoutTimer && clearTimeout(this.serverTimeoutTimer);
|
||||
if (!this.socket) {
|
||||
this.logs.log('无长连接对象');
|
||||
return;
|
||||
}
|
||||
this.logs.log('reConnect reCount:', this.reCount, ' lockReconnect:', this.lockReconnect);
|
||||
if (this.lockReconnect) return;
|
||||
if (this.reCount > 2) {
|
||||
this.closeReConnect();
|
||||
this.close();
|
||||
this.removeEvent();
|
||||
this.socket = null;
|
||||
this.onDisconnectCb();
|
||||
return;
|
||||
}
|
||||
|
||||
this.lockReconnect = true;
|
||||
// 没连接上会一直重连,设置延迟避免请求过多
|
||||
this.reTimeoutTimer && clearTimeout(this.reTimeoutTimer);
|
||||
this.reTimeoutTimer = setTimeout(() => {
|
||||
this.close();
|
||||
this.removeEvent();
|
||||
this.socket = null;
|
||||
this.create();
|
||||
this.reCount += 1;
|
||||
this.lockReconnect = false;
|
||||
}, this.reTimeout);
|
||||
}
|
||||
|
||||
// 取消重连
|
||||
closeReConnect() {
|
||||
this.reTimeoutTimer && clearTimeout(this.reTimeoutTimer);
|
||||
this.reCount = 0;
|
||||
this.lockReconnect = false;
|
||||
}
|
||||
|
||||
// 发送消息方法
|
||||
send(data) {
|
||||
if (!this.socket) {
|
||||
this.logs.error('长连接对象不存在,请先调用create方法创建长连接对象');
|
||||
return;
|
||||
}
|
||||
this.socket.send(data);
|
||||
}
|
||||
|
||||
// 发送消息内容
|
||||
sendMsgCustom(msg) {
|
||||
this.send(JSON.stringify({
|
||||
msgType: 2,
|
||||
id: `${new Date().getTime()}`, // demo简单定义,id规则建议用uuid
|
||||
content: msg,
|
||||
self: true
|
||||
}));
|
||||
}
|
||||
|
||||
// 关闭连接方法
|
||||
close() {
|
||||
if (!this.socket) {
|
||||
this.logs.error('长连接对象不存在');
|
||||
return;
|
||||
}
|
||||
this.socket.close();
|
||||
}
|
||||
|
||||
// 注销方法
|
||||
destroy() {
|
||||
if (!this.socket) {
|
||||
this.logs.log('无长连接对象');
|
||||
return;
|
||||
}
|
||||
this.timeoutTimer && clearTimeout(this.timeoutTimer);
|
||||
this.serverTimeoutTimer && clearTimeout(this.serverTimeoutTimer);
|
||||
this.closeReConnect();
|
||||
this.close();
|
||||
this.removeEvent();
|
||||
this.socket = null;
|
||||
}
|
||||
}
|
||||
|
||||
export default Socket;
|
@ -2,7 +2,7 @@
|
||||
<div class="components-container">
|
||||
<aside>
|
||||
Rich text is a core feature of the management backend, but at the same time it is a place with lots of pits. In the process of selecting rich texts, I also took a lot of detours. The common rich texts on the market have been basically used, and I finally chose Tinymce. See the more detailed rich text comparison and introduction.
|
||||
<a target="_blank" class="link-type" href="https://panjiachen.github.io/vue-element-admin-site/feature/component/rich-editor.html">Documentation</a>
|
||||
<a target="_blank" class="link-type" href="https://vue3-element-admin-site.midfar.com/feature/component/rich-editor.html">Documentation</a>
|
||||
</aside>
|
||||
<div>
|
||||
<tinymce v-model="content" :height="300" />
|
||||
|
@ -1,11 +1,11 @@
|
||||
<template>
|
||||
<div class="app-container documentation-container">
|
||||
<a class="document-btn" target="_blank"
|
||||
href="https://panjiachen.github.io/vue-element-admin-site/zh/guide/essentials/router-and-nav.html">国内文档</a>
|
||||
href="https://vue3-element-admin-site.midfar.com/zh/guide/essentials/router-and-nav.html">国内文档</a>
|
||||
<a class="document-btn" target="_blank"
|
||||
href="https://github.com/midfar/vue3-element-admin">Github 仓库</a>
|
||||
<dropdown-menu class="document-btn" :items="articleList" title="系列文章" />
|
||||
<!-- <a class="document-btn" target="_blank" href="https://panjiachen.github.io/vue-element-admin-site/zh/job/">内推招聘</a> -->
|
||||
<!-- <a class="document-btn" target="_blank" href="https://vue3-element-admin-site.midfar.com/zh/job/">内推招聘</a> -->
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
@ -5,7 +5,7 @@
|
||||
<h3>Please click the bug icon in the upper right corner</h3>
|
||||
<aside>
|
||||
Now the management system are basically the form of the spa, it enhances the user experience, but it also increases the possibility of page problems, a small negligence may lead to the entire page deadlock. Fortunately Vue provides a way to catch handling exceptions, where you can handle errors or report exceptions.
|
||||
<a target="_blank" class="link-type" href="https://panjiachen.github.io/vue-element-admin-site/guide/advanced/error.html">
|
||||
<a target="_blank" class="link-type" href="https://vue3-element-admin-site.midfar.com/guide/advanced/error.html">
|
||||
Document introduction
|
||||
</a>
|
||||
</aside>
|
||||
|
@ -5,7 +5,7 @@
|
||||
effect, you can use a browser caching scheme such as localStorage. Or do not use keep-alive include to cache all
|
||||
pages directly. See details
|
||||
<a
|
||||
href="https://panjiachen.github.io/vue-element-admin-site/guide/essentials/tags-view.html"
|
||||
href="https://vue3-element-admin-site.midfar.com/guide/essentials/tags-view.html"
|
||||
target="_blank"
|
||||
>Document</a>
|
||||
</aside>
|
||||
|
@ -8,7 +8,7 @@
|
||||
<el-button :loading="downloadLoading" style="margin:0 0 0 20px;" type="primary" :icon="IconDocument" @click="handleDownload">
|
||||
Export Excel
|
||||
</el-button>
|
||||
<a href="https://panjiachen.github.io/vue-element-admin-site/feature/component/excel.html" target="_blank" style="margin-left:15px;">
|
||||
<a href="https://vue3-element-admin-site.midfar.com/feature/component/excel.html" target="_blank" style="margin-left:15px;">
|
||||
<el-tag type="info" size="large">Documentation</el-tag>
|
||||
</a>
|
||||
</div>
|
||||
|
@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div class="icons-container">
|
||||
<aside>
|
||||
<a href="https://panjiachen.github.io/vue-element-admin-site/guide/advanced/icon.html" target="_blank">Add and use
|
||||
<a href="https://vue3-element-admin-site.midfar.com/guide/advanced/icon.html" target="_blank">Add and use
|
||||
</a>
|
||||
</aside>
|
||||
<el-tabs type="border-card">
|
||||
|
220
src/views/mydemo/WebSocketDemo.vue
Normal file
220
src/views/mydemo/WebSocketDemo.vue
Normal file
@ -0,0 +1,220 @@
|
||||
<!--
|
||||
* @Author: Cherry 2858937488@qq.com
|
||||
* @Date: 2023-03-14 10:40:55
|
||||
* @LastEditors: Cherry 2858937488@qq.com
|
||||
* @LastEditTime: 2023-03-16 20:57:15
|
||||
* @FilePath: \vue3-admin\vue3-element-admin\src\views\mydemo\WebSocketDemo.vue
|
||||
* @Description: websocket
|
||||
-->
|
||||
<template>
|
||||
<div class="web-socket-demo">
|
||||
<h3>node搭建WebSocket服务器</h3>
|
||||
<p>一、新建文件夹demo,然后用npm init命令新建一个项目ws-demo</p>
|
||||
<p>mkdir demo</p>
|
||||
<p>cd demo</p>
|
||||
<p>npm init</p>
|
||||
|
||||
<h3>二、安装依赖ws</h3>
|
||||
<p>npm install ws</p>
|
||||
|
||||
<h3>三、修改入口文件index.js</h3>
|
||||
<p>const WebSocket = require('ws') </p>
|
||||
<p>const ws = new WebSocket.Server({ port: 5001 })</p>
|
||||
<p>ws.on('connection', ws => {</p>
|
||||
<p>console.log('server connection')</p>
|
||||
|
||||
<p>ws.on('message', msg => {</p>
|
||||
|
||||
<p>console.log('服务端接收的消息:', msg)</p>
|
||||
|
||||
<p>const data = JSON.parse(msg)</p>
|
||||
<p>console.log('服务端接收的消息解析:', data)</p>
|
||||
|
||||
<p>if(data.msgType === 1){</p>
|
||||
<p>ws.send(JSON.stringify({</p>
|
||||
<p> msgType: 1,</p>
|
||||
<p> heart: true</p>
|
||||
<p>}))</p>
|
||||
<p>}else{</p>
|
||||
<p>ws.send(JSON.stringify({</p>
|
||||
<p> msgType: 3,</p>
|
||||
<p> content: '接收成功',</p>
|
||||
<p> data})</p>
|
||||
<p>)</p>
|
||||
<p>ws.send(JSON.stringify({</p>
|
||||
<p> msgType: 2,</p>
|
||||
<p> id: `${new Date().getTime()}`,//demo简单定义,id规则建议用uuid</p>
|
||||
<p> content: '模拟回复',</p>
|
||||
<p> self: false</p>
|
||||
<p>}))</p>
|
||||
<p>}</p>
|
||||
<p>})</p>
|
||||
|
||||
<p>ws.send(JSON.stringify({</p>
|
||||
<p> msgType: 0,</p>
|
||||
<p> content: '连接已建立'</p>
|
||||
<p>}))</p>
|
||||
|
||||
<p>})</p>
|
||||
|
||||
<h3>四、运行index.js</h3>
|
||||
<p>node index.js</p>
|
||||
|
||||
<h3>五、本地交互</h3>
|
||||
<div class="item">请输入运行地址进行连接:</div>
|
||||
<div class="flex">
|
||||
<el-input v-model="linkUrl" placeholder="连接地址" />
|
||||
<el-button type="primary" @click="startConnection" v-if="!isConnected">连接</el-button>
|
||||
<el-button type="primary" v-else @click="disconnected">断开连接</el-button>
|
||||
</div>
|
||||
<div class="item">信息交互:</div>
|
||||
<div class="flex">
|
||||
<el-input v-model="msg" placeholder="交互信息" placeholder-class="input-placeholder" />
|
||||
<el-button type="primary" @click="sendMsg" :disabled="disabled">发送</el-button>
|
||||
</div>
|
||||
<div class="item">信息列表:</div>
|
||||
<div class="list">
|
||||
<div v-for="item in msgList" :key="item.id" :class="item.self ? 'self' : 'other'">{{ item.content }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue';
|
||||
import Socket from '@/utils/websocket.js';
|
||||
import webSocketStore from '@/store/modules/webSocket';
|
||||
import userStore from '@/store/modules/user';
|
||||
import { ElMessage } from 'element-plus';
|
||||
|
||||
interface MsgObj {
|
||||
id: string | number,
|
||||
content: string,
|
||||
self: boolean
|
||||
}
|
||||
|
||||
const linkUrl = ref<string>('ws://127.0.0.1:5001');// 连接地址
|
||||
const socket = ref<any>(null); // 长连接对象
|
||||
const isConnected = ref<boolean>(false);// 是否连接
|
||||
const msg = ref<string>('');
|
||||
const disabled = ref<boolean>(false);// 是否禁止发送消息
|
||||
const msgList = ref<MsgObj[]>([]);
|
||||
|
||||
const startConnection = () => {
|
||||
if (!linkUrl.value) {
|
||||
ElMessage({
|
||||
message: '连接地址不能为空!',
|
||||
type: 'error'
|
||||
});
|
||||
return;
|
||||
}
|
||||
isConnected.value = true;
|
||||
createSocket();
|
||||
};
|
||||
|
||||
const disconnected = () => {
|
||||
disabled.value = false;
|
||||
isConnected.value = false;
|
||||
destroySocket();
|
||||
};
|
||||
|
||||
const sendMsg = () => {
|
||||
if (!socket.value) {
|
||||
ElMessage({
|
||||
message: '请先建立连接!',
|
||||
type: 'error'
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (!msg.value) {
|
||||
ElMessage({
|
||||
message: '发送的消息内容不能为空!',
|
||||
type: 'error'
|
||||
});
|
||||
return;
|
||||
}
|
||||
disabled.value = true;
|
||||
socket.value.sendMsgCustom(msg.value);
|
||||
};
|
||||
|
||||
const createSocket = () => {
|
||||
console.log('创建长连接', !socket.value);
|
||||
const useWebSocketStore = webSocketStore();
|
||||
const useUserStore = userStore();
|
||||
const id = useWebSocketStore.getSocketID() || `${useUserStore.userId}-${new Date().getTime() / 1000}`;
|
||||
if (!useWebSocketStore.getSocketID()) {
|
||||
useWebSocketStore.setSocketID(id);
|
||||
}
|
||||
socket.value = new Socket({
|
||||
url: linkUrl.value + '/' + id,
|
||||
openConsole: process.env.NODE_ENV !== 'production',
|
||||
onMsgEventCb: getMsg,
|
||||
onDisconnectCb: disconnectCallback
|
||||
});
|
||||
socket.value.create();
|
||||
};
|
||||
|
||||
// 接受消息
|
||||
const getMsg = (dataObj) => {
|
||||
console.log('getMsg:', dataObj);
|
||||
if (dataObj.msgType === 3) {
|
||||
ElMessage({
|
||||
message: '发送成功!',
|
||||
type: 'success'
|
||||
});
|
||||
msgList.value.push(dataObj.data);
|
||||
msg.value = '';
|
||||
disabled.value = false;
|
||||
return;
|
||||
}
|
||||
msgList.value.push(dataObj);
|
||||
};
|
||||
|
||||
// 断开连接提示
|
||||
const disconnectCallback = () => {
|
||||
socket.value = null;
|
||||
ElMessage({
|
||||
message: '连接已断开...',
|
||||
type: 'warning'
|
||||
});
|
||||
disconnected();
|
||||
};
|
||||
|
||||
// 销毁长连接
|
||||
const destroySocket = () => {
|
||||
console.log('销毁长连接', !!socket.value);
|
||||
if (!socket.value) return;
|
||||
socket.value.destroy();
|
||||
socket.value = null;
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.web-socket-demo {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.flex {
|
||||
width: 500px;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.item {
|
||||
margin: 10px 0;
|
||||
}
|
||||
|
||||
.list {
|
||||
width: 500px;
|
||||
margin: 10px 0;
|
||||
padding: 20px;
|
||||
border: 1px solid #efe;
|
||||
}
|
||||
|
||||
.self {
|
||||
margin: 10px 0;
|
||||
}
|
||||
|
||||
.other {
|
||||
margin: 10px 0;
|
||||
text-align: right;
|
||||
}
|
||||
</style>
|
@ -2,7 +2,7 @@
|
||||
<div class="app-container">
|
||||
<el-card class="box-card">
|
||||
<template #header>
|
||||
<a class="link-type link-title" target="_blank" href="https://panjiachen.github.io/vue-element-admin-site/guide/advanced/theme.html">
|
||||
<a class="link-type link-title" target="_blank" href="https://vue3-element-admin-site.midfar.com/guide/advanced/theme.html">
|
||||
Theme documentation
|
||||
</a>
|
||||
</template>
|
||||
|
Loading…
x
Reference in New Issue
Block a user