Compare commits

...

10 Commits

Author SHA1 Message Date
midfar
b2c48273e2
Update README.md 2023-11-10 18:09:45 +08:00
Cherry
7fb2a3b58d docs: pinia文档地址修改 2023-03-21 16:50:30 +08:00
Cherry
4a854c291a docs: element plus文档地址修改 2023-03-21 16:47:29 +08:00
Cherry
fa78f8667b docs: 文档更新 2023-03-21 15:05:15 +08:00
midfar
da6512e7aa update ts file 2023-03-19 17:48:33 +08:00
Cherry
ce562f59f8 fix: 命名优化 2023-03-16 20:58:44 +08:00
midfar
7e541c415e update link 2023-03-16 15:13:33 +08:00
Cherry
a5b445cbf8 fix: 文件引入大小写问题修复 2023-03-16 14:33:52 +08:00
Cherry
bf74cdc0c1 feat: 新增webSocket demo 2023-03-16 13:51:00 +08:00
midfar
bbf6558569 rename v-deep 2023-03-16 09:38:59 +08:00
22 changed files with 625 additions and 36 deletions

43
CODE_OF_CONDUCT.md Normal file
View 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]

View File

@ -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/) [vue3 element admin](https://vue3-element-admin.midfar.com/)
[element plus](https://element-plus.midfar.com/) [element plus](https://element-plus.midfar.com/)
## 准备
开发前请确保熟悉并掌握以下技术栈:
- vue: https://cn.vuejs.org/
- TypeScripthttps://www.tslang.cn/index.html
- element-plushttps://element-plus.midfar.com/
- pinia: https://pinia.vuejs.org/zh/
- vue-router: https://router.vuejs.org/zh/
注:开发前请务必阅读上述所有文档。应用至实际项目开发请修改 readme 内容。
## 推荐的 IDE 工具和插件 ## 推荐的 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). [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/). 参考 [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 ```sh
npm install npm install
@ -40,14 +81,25 @@ npm run build:test
npm run lint 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
View File

@ -3,4 +3,5 @@ export {}
declare global { declare global {
const ElMessage: typeof import('element-plus/es')['ElMessage'] const ElMessage: typeof import('element-plus/es')['ElMessage']
const ElMessageBox: typeof import('element-plus/es')['ElMessageBox'] const ElMessageBox: typeof import('element-plus/es')['ElMessageBox']
const ElNotification: typeof import('element-plus/es')['ElNotification']
} }

12
components.d.ts vendored
View File

@ -9,23 +9,24 @@ declare module '@vue/runtime-core' {
export interface GlobalComponents { export interface GlobalComponents {
BackToTop: typeof import('./src/components/BackToTop/index.vue')['default'] BackToTop: typeof import('./src/components/BackToTop/index.vue')['default']
Breadcrumb: typeof import('./src/components/Breadcrumb/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'] DragSelect: typeof import('./src/components/DragSelect/index.vue')['default']
DropdownMenu: typeof import('./src/components/Share/DropdownMenu.vue')['default'] DropdownMenu: typeof import('./src/components/Share/DropdownMenu.vue')['default']
Dropzone: typeof import('./src/components/Dropzone/index.vue')['default'] Dropzone: typeof import('./src/components/Dropzone/index.vue')['default']
EditorImage: typeof import('./src/components/Tinymce/components/EditorImage.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'] ElBadge: typeof import('element-plus/es')['ElBadge']
ElBreadcrumb: typeof import('element-plus/es')['ElBreadcrumb'] ElBreadcrumb: typeof import('element-plus/es')['ElBreadcrumb']
ElBreadcrumbItem: typeof import('element-plus/es')['ElBreadcrumbItem'] ElBreadcrumbItem: typeof import('element-plus/es')['ElBreadcrumbItem']
ElButton: typeof import('element-plus/es')['ElButton'] ElButton: typeof import('element-plus/es')['ElButton']
ElCard: typeof import('element-plus/es')['ElCard'] 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'] ElCheckbox: typeof import('element-plus/es')['ElCheckbox']
ElCheckboxGroup: typeof import('element-plus/es')['ElCheckboxGroup'] ElCheckboxGroup: typeof import('element-plus/es')['ElCheckboxGroup']
ElCol: typeof import('element-plus/es')['ElCol'] ElCol: typeof import('element-plus/es')['ElCol']
ElConfigProvider: typeof import('element-plus/es')['ElConfigProvider'] ElConfigProvider: typeof import('element-plus/es')['ElConfigProvider']
ElDatePicker: typeof import('element-plus/es')['ElDatePicker'] ElDatePicker: typeof import('element-plus/es')['ElDatePicker']
ElDialog: typeof import('element-plus/es')['ElDialog'] ElDialog: typeof import('element-plus/es')['ElDialog']
ElDragSelect: typeof import('element-plus/es')['ElDragSelect']
ElDropdown: typeof import('element-plus/es')['ElDropdown'] ElDropdown: typeof import('element-plus/es')['ElDropdown']
ElDropdownItem: typeof import('element-plus/es')['ElDropdownItem'] ElDropdownItem: typeof import('element-plus/es')['ElDropdownItem']
ElDropdownMenu: typeof import('element-plus/es')['ElDropdownMenu'] ElDropdownMenu: typeof import('element-plus/es')['ElDropdownMenu']
@ -38,10 +39,14 @@ declare module '@vue/runtime-core' {
ElOption: typeof import('element-plus/es')['ElOption'] ElOption: typeof import('element-plus/es')['ElOption']
ElPagination: typeof import('element-plus/es')['ElPagination'] ElPagination: typeof import('element-plus/es')['ElPagination']
ElProgress: typeof import('element-plus/es')['ElProgress'] 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'] ElRate: typeof import('element-plus/es')['ElRate']
ElRow: typeof import('element-plus/es')['ElRow'] ElRow: typeof import('element-plus/es')['ElRow']
ElScrollbar: typeof import('element-plus/es')['ElScrollbar'] ElScrollbar: typeof import('element-plus/es')['ElScrollbar']
ElSelect: typeof import('element-plus/es')['ElSelect'] ElSelect: typeof import('element-plus/es')['ElSelect']
ElSlider: typeof import('element-plus/es')['ElSlider']
ElSubMenu: typeof import('element-plus/es')['ElSubMenu'] ElSubMenu: typeof import('element-plus/es')['ElSubMenu']
ElSwitch: typeof import('element-plus/es')['ElSwitch'] ElSwitch: typeof import('element-plus/es')['ElSwitch']
ElTable: typeof import('element-plus/es')['ElTable'] ElTable: typeof import('element-plus/es')['ElTable']
@ -49,7 +54,10 @@ declare module '@vue/runtime-core' {
ElTabPane: typeof import('element-plus/es')['ElTabPane'] ElTabPane: typeof import('element-plus/es')['ElTabPane']
ElTabs: typeof import('element-plus/es')['ElTabs'] ElTabs: typeof import('element-plus/es')['ElTabs']
ElTag: typeof import('element-plus/es')['ElTag'] 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'] ElTooltip: typeof import('element-plus/es')['ElTooltip']
ElTree: typeof import('element-plus/es')['ElTree']
ElUpload: typeof import('element-plus/es')['ElUpload'] ElUpload: typeof import('element-plus/es')['ElUpload']
ErrorLog: typeof import('./src/components/ErrorLog/index.vue')['default'] ErrorLog: typeof import('./src/components/ErrorLog/index.vue')['default']
GithubCorner: typeof import('./src/components/GithubCorner/index.vue')['default'] GithubCorner: typeof import('./src/components/GithubCorner/index.vue')['default']

View File

@ -525,7 +525,7 @@ export const asyncRoutes = [
component: 'layout/Layout', component: 'layout/Layout',
children: [ children: [
{ {
path: 'https://github.com/PanJiaChen/vue-element-admin', path: 'https://github.com/midfar/vue3-element-admin',
meta: { title: 'External Link', icon: 'link' } meta: { title: 'External Link', icon: 'link' }
} }
] ]

View File

@ -52,16 +52,13 @@ export default defineComponent({
<style lang="scss" scoped> <style lang="scss" scoped>
.drag-select { .drag-select {
::v-deep { :deep(.sortable-ghost) {
.sortable-ghost {
opacity: .8; opacity: .8;
color: #fff !important; color: #fff !important;
background: #42b983 !important; background: #42b983 !important;
} }
:deep(.el-tag) {
.el-tag {
cursor: pointer; cursor: pointer;
} }
} }
}
</style> </style>

View File

@ -1,5 +1,5 @@
<template> <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 <svg
width="80" width="80"
height="80" height="80"

View File

@ -6,7 +6,7 @@
</template> </template>
<script> <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 { isExternal } from '@/utils/validate';
import { defineComponent } from 'vue'; import { defineComponent } from 'vue';

View File

@ -11,7 +11,7 @@
import { defineComponent } from 'vue'; import { defineComponent } from 'vue';
/** /**
* docs: * 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 editorImage from './components/EditorImage';
import plugins from './plugins'; import plugins from './plugins';

View File

@ -34,10 +34,10 @@
<router-link to="/"> <router-link to="/">
<el-dropdown-item>Dashboard</el-dropdown-item> <el-dropdown-item>Dashboard</el-dropdown-item>
</router-link> </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> <el-dropdown-item>Github</el-dropdown-item>
</a> </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> <el-dropdown-item>Docs</el-dropdown-item>
</a> </a>
<el-dropdown-item divided @click="logout"> <el-dropdown-item divided @click="logout">

View File

@ -385,6 +385,12 @@ export const asyncRoutes:RouteRecordRaw[] = [
component: () => import('@/views/mydemo/StoreDemo.vue'), component: () => import('@/views/mydemo/StoreDemo.vue'),
name: 'StoreDemo', name: 'StoreDemo',
meta: { title: 'StoreDemo', icon: 'lock' } meta: { title: 'StoreDemo', icon: 'lock' }
},
{
path: 'webSocket-demo',
component: () => import('@/views/mydemo/WebSocketDemo.vue'),
name: 'WebSocketDemo',
meta: { title: 'WebSocketDemo', icon: 'lock' }
} }
] ]
}, },

View File

@ -7,6 +7,7 @@ import permissionStore from './permission';
export interface IUserState { export interface IUserState {
token: string; token: string;
userId: string,
name: string; name: string;
avatar: string; avatar: string;
introduction: string; introduction: string;
@ -17,6 +18,7 @@ export default defineStore({
id: 'user', id: 'user',
state: ():IUserState => ({ state: ():IUserState => ({
token: getToken(), token: getToken(),
userId: '',
name: '', name: '',
avatar: '', avatar: '',
introduction: '', introduction: '',

View 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
View 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;

View File

@ -2,7 +2,7 @@
<div class="components-container"> <div class="components-container">
<aside> <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. 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> </aside>
<div> <div>
<tinymce v-model="content" :height="300" /> <tinymce v-model="content" :height="300" />

View File

@ -1,11 +1,11 @@
<template> <template>
<div class="app-container documentation-container"> <div class="app-container documentation-container">
<a class="document-btn" target="_blank" <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" <a class="document-btn" target="_blank"
href="https://github.com/midfar/vue3-element-admin">Github 仓库</a> href="https://github.com/midfar/vue3-element-admin">Github 仓库</a>
<dropdown-menu class="document-btn" :items="articleList" title="系列文章" /> <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> </div>
</template> </template>

View File

@ -5,7 +5,7 @@
<h3>Please click the bug icon in the upper right corner</h3> <h3>Please click the bug icon in the upper right corner</h3>
<aside> <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. 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 Document introduction
</a> </a>
</aside> </aside>

View File

@ -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 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 pages directly. See details
<a <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" target="_blank"
>Document</a> >Document</a>
</aside> </aside>

View File

@ -8,7 +8,7 @@
<el-button :loading="downloadLoading" style="margin:0 0 0 20px;" type="primary" :icon="IconDocument" @click="handleDownload"> <el-button :loading="downloadLoading" style="margin:0 0 0 20px;" type="primary" :icon="IconDocument" @click="handleDownload">
Export Excel Export Excel
</el-button> </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> <el-tag type="info" size="large">Documentation</el-tag>
</a> </a>
</div> </div>

View File

@ -1,7 +1,7 @@
<template> <template>
<div class="icons-container"> <div class="icons-container">
<aside> <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> </a>
</aside> </aside>
<el-tabs type="border-card"> <el-tabs type="border-card">

View 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()}`,//demoiduuid</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>

View File

@ -2,7 +2,7 @@
<div class="app-container"> <div class="app-container">
<el-card class="box-card"> <el-card class="box-card">
<template #header> <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 Theme documentation
</a> </a>
</template> </template>