mirror of
				https://gitee.com/dromara/mayfly-go
				synced 2025-11-04 08:20:25 +08:00 
			
		
		
		
	Compare commits
	
		
			105 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					2118acf244 | ||
| 
						 | 
					44a1bd626e | ||
| 
						 | 
					ea3c70a8a8 | ||
| 
						 | 
					6343173cf8 | ||
| 
						 | 
					6837a9c867 | ||
| 
						 | 
					a726927a28 | ||
| 
						 | 
					e135e4ce64 | ||
| 
						 | 
					43edef412c | ||
| 
						 | 
					2deb3109c2 | ||
| 
						 | 
					a80221a950 | ||
| 
						 | 
					10630847df | ||
| 
						 | 
					f43851698e | ||
| 
						 | 
					73884bb693 | ||
| 
						 | 
					1b5bb1de8b | ||
| 
						 | 
					4814793546 | ||
| 
						 | 
					d85bbff270 | ||
| 
						 | 
					bb1522f4dc | ||
| 
						 | 
					a7632fbf58 | ||
| 
						 | 
					c4cb4234fd | ||
| 
						 | 
					89e12678eb | ||
| 
						 | 
					137ebb8e9e | ||
| 
						 | 
					05625bd8c1 | ||
| 
						 | 
					4afeac5fdd | ||
| 
						 | 
					1d0e91f1af | ||
| 
						 | 
					cf5111a325 | ||
| 
						 | 
					78957a8ebd | ||
| 
						 | 
					4ed892a656 | ||
| 
						 | 
					3486b07003 | ||
| 
						 | 
					a5cd7caf19 | ||
| 
						 | 
					f2c7ef78c0 | ||
| 
						 | 
					653953ee76 | ||
| 
						 | 
					a831614d5a | ||
| 
						 | 
					ebe73e2f19 | ||
| 
						 | 
					29fd5a25d2 | ||
| 
						 | 
					44805ce580 | ||
| 
						 | 
					2a6d620830 | ||
| 
						 | 
					01d3e1ad28 | ||
| 
						 | 
					f4162c38db | ||
| 
						 | 
					1a4626c24d | ||
| 
						 | 
					d6eb9683d1 | ||
| 
						 | 
					e2b524dadb | ||
| 
						 | 
					8998a21626 | ||
| 
						 | 
					abc015aec0 | ||
| 
						 | 
					4ef8d27b1e | ||
| 
						 | 
					40b6e603fc | ||
| 
						 | 
					21498584b1 | ||
| 
						 | 
					408bac09a1 | ||
| 
						 | 
					582d879a77 | ||
| 
						 | 
					38ff5152e0 | ||
| 
						 | 
					d1d372e1bf | ||
| 
						 | 
					5e4793433b | ||
| 
						 | 
					54ad19f97e | ||
| 
						 | 
					fc166650b3 | ||
| 
						 | 
					2acc295259 | ||
| 
						 | 
					4b3ed1310d | ||
| 
						 | 
					b2cfd1517c | ||
| 
						 | 
					b13d27ccd6 | ||
| 
						 | 
					68e0088016 | ||
| 
						 | 
					bd1e83989d | ||
| 
						 | 
					263dfa6be7 | ||
| 
						 | 
					eb55f93864 | ||
| 
						 | 
					8589105e44 | ||
| 
						 | 
					986b187f0a | ||
| 
						 | 
					008d34c453 | ||
| 
						 | 
					49d3f988c9 | ||
| 
						 | 
					76475e807e | ||
| 
						 | 
					f93231da61 | ||
| 
						 | 
					bf75483a3c | ||
| 
						 | 
					b56b0187cf | ||
| 
						 | 
					7e7f02b502 | ||
| 
						 | 
					878985f7c5 | ||
| 
						 | 
					2133d9b737 | ||
| 
						 | 
					d711a36749 | ||
| 
						 | 
					9dbf104ef1 | ||
| 
						 | 
					20eb06fb28 | ||
| 
						 | 
					9c20bdef39 | ||
| 
						 | 
					3fdd98a390 | ||
| 
						 | 
					d4f456c0cf | ||
| 
						 | 
					f2b6e15cf4 | ||
| 
						 | 
					6be0ea6aed | ||
| 
						 | 
					eee08be2cc | ||
| 
						 | 
					252fc553f2 | ||
| 
						 | 
					ac2ceed3f9 | ||
| 
						 | 
					3f828cc5b0 | ||
| 
						 | 
					fc1b9ef35d | ||
| 
						 | 
					d0b71a1c40 | ||
| 
						 | 
					a743a6a05a | ||
| 
						 | 
					0e6b9713ce | ||
| 
						 | 
					b9afbc764d | ||
| 
						 | 
					923e183a67 | ||
| 
						 | 
					7e9a381641 | ||
| 
						 | 
					bed95254d0 | ||
| 
						 | 
					e4d13f3377 | ||
| 
						 | 
					d530365ef9 | ||
| 
						 | 
					070d4ea104 | ||
| 
						 | 
					3fc86f0fae | ||
| 
						 | 
					3b77ab2727 | ||
| 
						 | 
					76cb991282 | ||
| 
						 | 
					9efd20f1b9 | ||
| 
						 | 
					de5b9e46d3 | ||
| 
						 | 
					f27d3d200f | ||
| 
						 | 
					f4a64b96a9 | ||
| 
						 | 
					9a59749763 | ||
| 
						 | 
					b017b902f8 | ||
| 
						 | 
					7c53353c60 | 
@@ -5,12 +5,12 @@ WORKDIR /mayfly
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
COPY mayfly_go_web .
 | 
					COPY mayfly_go_web .
 | 
				
			||||||
 | 
					
 | 
				
			||||||
RUN yarn config set registry 'https://registry.npm.taobao.org' && \
 | 
					RUN yarn config set registry 'https://registry.npmmirror.com' && \
 | 
				
			||||||
    yarn install && \
 | 
					    yarn install && \
 | 
				
			||||||
    yarn build
 | 
					    yarn build
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# 构建后端资源
 | 
					# 构建后端资源
 | 
				
			||||||
FROM golang:1.21.5 as be-builder
 | 
					FROM golang:1.23 as be-builder
 | 
				
			||||||
 | 
					
 | 
				
			||||||
ENV GOPROXY https://goproxy.cn
 | 
					ENV GOPROXY https://goproxy.cn
 | 
				
			||||||
WORKDIR /mayfly
 | 
					WORKDIR /mayfly
 | 
				
			||||||
@@ -24,7 +24,7 @@ COPY --from=fe-builder /mayfly/dist /mayfly/static/static
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
# Build
 | 
					# Build
 | 
				
			||||||
RUN GO111MODULE=on CGO_ENABLED=0 GOOS=linux \
 | 
					RUN GO111MODULE=on CGO_ENABLED=0 GOOS=linux \
 | 
				
			||||||
    go build -a \
 | 
					    go build -a -ldflags=-w \
 | 
				
			||||||
    -o mayfly-go main.go
 | 
					    -o mayfly-go main.go
 | 
				
			||||||
 | 
					
 | 
				
			||||||
FROM debian:bookworm-slim
 | 
					FROM debian:bookworm-slim
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										39
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										39
									
								
								README.md
									
									
									
									
									
								
							@@ -13,7 +13,7 @@
 | 
				
			|||||||
    <img src="https://img.shields.io/docker/pulls/mayflygo/mayfly-go.svg?label=docker%20pulls&color=fac858" alt="docker pulls"/>
 | 
					    <img src="https://img.shields.io/docker/pulls/mayflygo/mayfly-go.svg?label=docker%20pulls&color=fac858" alt="docker pulls"/>
 | 
				
			||||||
  </a>
 | 
					  </a>
 | 
				
			||||||
  <a href="https://github.com/golang/go" target="_blank">
 | 
					  <a href="https://github.com/golang/go" target="_blank">
 | 
				
			||||||
    <img src="https://img.shields.io/badge/Golang-1.21%2B-yellow.svg" alt="golang"/>
 | 
					    <img src="https://img.shields.io/badge/Golang-1.22%2B-yellow.svg" alt="golang"/>
 | 
				
			||||||
  </a>
 | 
					  </a>
 | 
				
			||||||
  <a href="https://cn.vuejs.org" target="_blank">
 | 
					  <a href="https://cn.vuejs.org" target="_blank">
 | 
				
			||||||
    <img src="https://img.shields.io/badge/Vue-3.x-green.svg" alt="vue">
 | 
					    <img src="https://img.shields.io/badge/Vue-3.x-green.svg" alt="vue">
 | 
				
			||||||
@@ -22,7 +22,7 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
### 介绍
 | 
					### 介绍
 | 
				
			||||||
 | 
					
 | 
				
			||||||
web 版 **linux(终端[终端回放] 文件 脚本 进程 计划任务)、数据库(mysql postgres oracle 达梦 高斯)、redis(单机 哨兵 集群)、mongo 统一管理操作平台**
 | 
					web 版 **linux(终端[终端回放、命令过滤] 文件 脚本 进程 计划任务)、数据库(mysql postgres oracle sqlserver 达梦 高斯 sqlite)数据操作 数据同步 数据迁移、redis(单机 哨兵 集群)、mongo 等集工单流程审批于一体的统一管理操作平台**
 | 
				
			||||||
 | 
					
 | 
				
			||||||
### 开发语言与主要框架
 | 
					### 开发语言与主要框架
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -45,56 +45,61 @@ http://go.mayfly.run
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
### 系统核心功能截图
 | 
					### 系统核心功能截图
 | 
				
			||||||
 | 
					
 | 
				
			||||||
##### 记录操作记录
 | 
					#### 首页
 | 
				
			||||||
 | 
					
 | 
				
			||||||

 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#### 机器操作
 | 
					#### 机器操作
 | 
				
			||||||
 | 
					
 | 
				
			||||||
##### 状态查看
 | 
					##### 状态查看
 | 
				
			||||||
 | 
					
 | 
				
			||||||

 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
##### ssh 终端
 | 
					##### ssh 终端
 | 
				
			||||||
 | 
					
 | 
				
			||||||

 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
##### 文件操作
 | 
					##### 文件操作
 | 
				
			||||||
 | 
					
 | 
				
			||||||

 | 
					
 | 
				
			||||||

 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#### 数据库操作
 | 
					#### 数据库操作
 | 
				
			||||||
 | 
					
 | 
				
			||||||
##### sql 编辑器
 | 
					##### sql 编辑器
 | 
				
			||||||
 | 
					
 | 
				
			||||||

 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
##### 在线增删改查数据
 | 
					##### 在线增删改查数据
 | 
				
			||||||
 | 
					
 | 
				
			||||||

 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#### Redis 操作
 | 
					#### Redis 操作
 | 
				
			||||||
 | 
					
 | 
				
			||||||

 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#### Mongo 操作
 | 
					#### Mongo 操作
 | 
				
			||||||
 | 
					
 | 
				
			||||||

 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
##### 系统管理
 | 
					#### 工单流程审批
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#### 系统管理
 | 
				
			||||||
 | 
					
 | 
				
			||||||
##### 账号管理
 | 
					##### 账号管理
 | 
				
			||||||
 | 
					
 | 
				
			||||||

 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
##### 角色管理
 | 
					##### 角色管理
 | 
				
			||||||
 | 
					
 | 
				
			||||||

 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
##### 资源管理
 | 
					##### 菜单资源管理
 | 
				
			||||||
 | 
					
 | 
				
			||||||

 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
**其他更多功能&操作指南可查看在线文档**: https://www.yuque.com/may-fly/mayfly-go
 | 
					**其他更多功能&操作指南可查看在线文档**: https://www.yuque.com/may-fly/mayfly-go
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -14,7 +14,7 @@ services:
 | 
				
			|||||||
    restart: always
 | 
					    restart: always
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  server:
 | 
					  server:
 | 
				
			||||||
    image: mayfly-go:v1.3.1
 | 
					    image: ccr.ccs.tencentyun.com/mayfly/mayfly-go:v1.8.5
 | 
				
			||||||
    build:
 | 
					    build:
 | 
				
			||||||
      context: .
 | 
					      context: .
 | 
				
			||||||
      dockerfile: Dockerfile
 | 
					      dockerfile: Dockerfile
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -5,4 +5,6 @@ VITE_PORT = 8889
 | 
				
			|||||||
VITE_OPEN = false
 | 
					VITE_OPEN = false
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# public path 配置线上环境路径(打包)
 | 
					# public path 配置线上环境路径(打包)
 | 
				
			||||||
VITE_PUBLIC_PATH = ''
 | 
					VITE_PUBLIC_PATH = ''
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					VITE_EDITOR=idea
 | 
				
			||||||
@@ -1,66 +1,72 @@
 | 
				
			|||||||
{
 | 
					{
 | 
				
			||||||
  "name": "mayfly",
 | 
					    "name": "mayfly",
 | 
				
			||||||
  "version": "1.0.0",
 | 
					    "version": "1.0.0",
 | 
				
			||||||
  "scripts": {
 | 
					    "type": "module",
 | 
				
			||||||
    "dev": "vite",
 | 
					    "scripts": {
 | 
				
			||||||
    "build": "vite build",
 | 
					      "dev": "vite",
 | 
				
			||||||
    "preview": "vite preview",
 | 
					      "build": "vite build",
 | 
				
			||||||
    "build-preview": "npm run build && npm run preview",
 | 
					      "preview": "vite preview",
 | 
				
			||||||
    "lint-fix": "eslint --fix --ext .js --ext .jsx --ext .vue src/"
 | 
					      "build-preview": "npm run build && npm run preview",
 | 
				
			||||||
  },
 | 
					      "lint-fix": "eslint --fix --ext .js --ext .jsx --ext .vue src/"
 | 
				
			||||||
  "dependencies": {
 | 
					    },
 | 
				
			||||||
    "@element-plus/icons-vue": "^2.3.1",
 | 
					    "dependencies": {
 | 
				
			||||||
    "@vueuse/core": "^10.7.2",
 | 
					      "@element-plus/icons-vue": "^2.3.1",
 | 
				
			||||||
    "asciinema-player": "^3.6.3",
 | 
					      "@vueuse/core": "^11.1.0",
 | 
				
			||||||
    "axios": "^1.6.2",
 | 
					      "asciinema-player": "^3.8.1",
 | 
				
			||||||
    "clipboard": "^2.0.11",
 | 
					      "axios": "^1.6.2",
 | 
				
			||||||
    "countup.js": "^2.7.0",
 | 
					      "clipboard": "^2.0.11",
 | 
				
			||||||
    "cropperjs": "^1.5.11",
 | 
					      "cropperjs": "^1.6.1",
 | 
				
			||||||
    "echarts": "^5.4.3",
 | 
					      "crypto-js": "^4.2.0",
 | 
				
			||||||
    "element-plus": "^2.5.1",
 | 
					      "dayjs": "^1.11.13",
 | 
				
			||||||
    "js-base64": "^3.7.5",
 | 
					      "echarts": "^5.5.1",
 | 
				
			||||||
    "jsencrypt": "^3.3.2",
 | 
					      "element-plus": "^2.8.6",
 | 
				
			||||||
    "lodash": "^4.17.21",
 | 
					      "js-base64": "^3.7.7",
 | 
				
			||||||
    "mitt": "^3.0.1",
 | 
					      "jsencrypt": "^3.3.2",
 | 
				
			||||||
    "monaco-editor": "^0.45.0",
 | 
					      "lodash": "^4.17.21",
 | 
				
			||||||
    "monaco-sql-languages": "^0.11.0",
 | 
					      "mitt": "^3.0.1",
 | 
				
			||||||
    "monaco-themes": "^0.4.4",
 | 
					      "monaco-editor": "^0.52.0",
 | 
				
			||||||
    "nprogress": "^0.2.0",
 | 
					      "monaco-sql-languages": "^0.12.2",
 | 
				
			||||||
    "pinia": "^2.1.7",
 | 
					      "monaco-themes": "^0.4.4",
 | 
				
			||||||
    "qrcode.vue": "^3.4.1",
 | 
					      "nprogress": "^0.2.0",
 | 
				
			||||||
    "screenfull": "^6.0.2",
 | 
					      "pinia": "^2.2.4",
 | 
				
			||||||
    "sortablejs": "^1.15.0",
 | 
					      "qrcode.vue": "^3.5.0",
 | 
				
			||||||
    "splitpanes": "^3.1.5",
 | 
					      "screenfull": "^6.0.2",
 | 
				
			||||||
    "sql-formatter": "^15.0.2",
 | 
					      "sortablejs": "^1.15.3",
 | 
				
			||||||
    "uuid": "^9.0.1",
 | 
					      "splitpanes": "^3.1.5",
 | 
				
			||||||
    "vue": "^3.4.14",
 | 
					      "sql-formatter": "^15.4.5",
 | 
				
			||||||
    "vue-router": "^4.2.5",
 | 
					      "trzsz": "^1.1.5",
 | 
				
			||||||
    "xterm": "^5.3.0",
 | 
					      "uuid": "^9.0.1",
 | 
				
			||||||
    "xterm-addon-fit": "^0.8.0",
 | 
					      "vue": "^3.5.12",
 | 
				
			||||||
    "xterm-addon-search": "^0.13.0",
 | 
					      "vue-router": "^4.4.5",
 | 
				
			||||||
    "xterm-addon-web-links": "^0.9.0"
 | 
					      "xterm": "^5.3.0",
 | 
				
			||||||
  },
 | 
					      "xterm-addon-fit": "^0.8.0",
 | 
				
			||||||
  "devDependencies": {
 | 
					      "xterm-addon-search": "^0.13.0",
 | 
				
			||||||
    "@types/lodash": "^4.14.178",
 | 
					      "xterm-addon-web-links": "^0.9.0"
 | 
				
			||||||
    "@types/node": "^18.14.0",
 | 
					    },
 | 
				
			||||||
    "@types/nprogress": "^0.2.0",
 | 
					    "devDependencies": {
 | 
				
			||||||
    "@types/sortablejs": "^1.15.3",
 | 
					      "@types/crypto-js": "^4.2.2",
 | 
				
			||||||
    "@typescript-eslint/eslint-plugin": "^6.7.4",
 | 
					      "@types/lodash": "^4.14.178",
 | 
				
			||||||
    "@typescript-eslint/parser": "^6.7.4",
 | 
					      "@types/node": "^18.14.0",
 | 
				
			||||||
    "@vitejs/plugin-vue": "^5.0.3",
 | 
					      "@types/nprogress": "^0.2.0",
 | 
				
			||||||
    "@vue/compiler-sfc": "^3.4.14",
 | 
					      "@types/sortablejs": "^1.15.8",
 | 
				
			||||||
    "dotenv": "^16.3.1",
 | 
					      "@typescript-eslint/eslint-plugin": "^6.7.4",
 | 
				
			||||||
    "eslint": "^8.35.0",
 | 
					      "@typescript-eslint/parser": "^6.7.4",
 | 
				
			||||||
    "eslint-plugin-vue": "^9.19.2",
 | 
					      "@vitejs/plugin-vue": "^5.1.4",
 | 
				
			||||||
    "prettier": "^3.1.0",
 | 
					      "@vue/compiler-sfc": "^3.5.12",
 | 
				
			||||||
    "sass": "^1.69.0",
 | 
					      "code-inspector-plugin": "^0.4.5",
 | 
				
			||||||
    "typescript": "^5.3.2",
 | 
					      "dotenv": "^16.3.1",
 | 
				
			||||||
    "vite": "^5.0.11",
 | 
					      "eslint": "^8.35.0",
 | 
				
			||||||
    "vue-eslint-parser": "^9.4.0"
 | 
					      "eslint-plugin-vue": "^9.28.0",
 | 
				
			||||||
  },
 | 
					      "prettier": "^3.2.5",
 | 
				
			||||||
  "browserslist": [
 | 
					      "sass": "^1.80.3",
 | 
				
			||||||
    "> 1%",
 | 
					      "typescript": "^5.6.3",
 | 
				
			||||||
    "last 2 versions",
 | 
					      "vite": "^5.4.10",
 | 
				
			||||||
    "not dead"
 | 
					      "vue-eslint-parser": "^9.4.3"
 | 
				
			||||||
  ]
 | 
					    },
 | 
				
			||||||
}
 | 
					    "browserslist": [
 | 
				
			||||||
 | 
					      "> 1%",
 | 
				
			||||||
 | 
					      "last 2 versions",
 | 
				
			||||||
 | 
					      "not dead"
 | 
				
			||||||
 | 
					    ]
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
@@ -4,7 +4,7 @@
 | 
				
			|||||||
            :zIndex="10000000"
 | 
					            :zIndex="10000000"
 | 
				
			||||||
            :width="210"
 | 
					            :width="210"
 | 
				
			||||||
            v-if="themeConfig.isWatermark"
 | 
					            v-if="themeConfig.isWatermark"
 | 
				
			||||||
            :font="{ color: 'rgba(180, 180, 180, 0.5)' }"
 | 
					            :font="{ color: 'rgba(180, 180, 180, 0.3)' }"
 | 
				
			||||||
            :content="themeConfig.watermarkText"
 | 
					            :content="themeConfig.watermarkText"
 | 
				
			||||||
            class="h100"
 | 
					            class="h100"
 | 
				
			||||||
        >
 | 
					        >
 | 
				
			||||||
 
 | 
				
			|||||||
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							@@ -55,11 +55,11 @@
 | 
				
			|||||||
      "unicode_decimal": 58905
 | 
					      "unicode_decimal": 58905
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
      "icon_id": "11617944",
 | 
					      "icon_id": "25271976",
 | 
				
			||||||
      "name": "oracle",
 | 
					      "name": "oracle",
 | 
				
			||||||
      "font_class": "oracle",
 | 
					      "font_class": "oracle",
 | 
				
			||||||
      "unicode": "e6ea",
 | 
					      "unicode": "e507",
 | 
				
			||||||
      "unicode_decimal": 59114
 | 
					      "unicode_decimal": 58631
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
      "icon_id": "8105644",
 | 
					      "icon_id": "8105644",
 | 
				
			||||||
@@ -67,6 +67,41 @@
 | 
				
			|||||||
      "font_class": "mariadb",
 | 
					      "font_class": "mariadb",
 | 
				
			||||||
      "unicode": "e513",
 | 
					      "unicode": "e513",
 | 
				
			||||||
      "unicode_decimal": 58643
 | 
					      "unicode_decimal": 58643
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					      "icon_id": "13601813",
 | 
				
			||||||
 | 
					      "name": "sqlite",
 | 
				
			||||||
 | 
					      "font_class": "sqlite",
 | 
				
			||||||
 | 
					      "unicode": "e546",
 | 
				
			||||||
 | 
					      "unicode_decimal": 58694
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					      "icon_id": "29340317",
 | 
				
			||||||
 | 
					      "name": "temp-mssql",
 | 
				
			||||||
 | 
					      "font_class": "MSSQLNATIVE",
 | 
				
			||||||
 | 
					      "unicode": "e600",
 | 
				
			||||||
 | 
					      "unicode_decimal": 58880
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					      "icon_id": "7699332",
 | 
				
			||||||
 | 
					      "name": "gaussdb",
 | 
				
			||||||
 | 
					      "font_class": "gauss",
 | 
				
			||||||
 | 
					      "unicode": "e683",
 | 
				
			||||||
 | 
					      "unicode_decimal": 59011
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					      "icon_id": "34836637",
 | 
				
			||||||
 | 
					      "name": "kingbase",
 | 
				
			||||||
 | 
					      "font_class": "kingbase",
 | 
				
			||||||
 | 
					      "unicode": "e882",
 | 
				
			||||||
 | 
					      "unicode_decimal": 59522
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					      "icon_id": "33047500",
 | 
				
			||||||
 | 
					      "name": "vastbase",
 | 
				
			||||||
 | 
					      "font_class": "vastbase",
 | 
				
			||||||
 | 
					      "unicode": "e62b",
 | 
				
			||||||
 | 
					      "unicode_decimal": 58923
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  ]
 | 
					  ]
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -69,7 +69,7 @@ class Api {
 | 
				
			|||||||
     */
 | 
					     */
 | 
				
			||||||
    async xhrReq(param: any = null, options: any = {}): Promise<any> {
 | 
					    async xhrReq(param: any = null, options: any = {}): Promise<any> {
 | 
				
			||||||
        if (this.beforeHandler) {
 | 
					        if (this.beforeHandler) {
 | 
				
			||||||
            this.beforeHandler(param);
 | 
					            await this.beforeHandler(param);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        return request.xhrReq(this.method, this.url, param, options);
 | 
					        return request.xhrReq(this.method, this.url, param, options);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -22,6 +22,8 @@ export class EnumValue {
 | 
				
			|||||||
     */
 | 
					     */
 | 
				
			||||||
    tag: EnumValueTag;
 | 
					    tag: EnumValueTag;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    extra: any;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    constructor(value: any, label: string) {
 | 
					    constructor(value: any, label: string) {
 | 
				
			||||||
        this.value = value;
 | 
					        this.value = value;
 | 
				
			||||||
        this.label = label;
 | 
					        this.label = label;
 | 
				
			||||||
@@ -53,6 +55,11 @@ export class EnumValue {
 | 
				
			|||||||
        return this;
 | 
					        return this;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    setExtra(extra: any): EnumValue {
 | 
				
			||||||
 | 
					        this.extra = extra;
 | 
				
			||||||
 | 
					        return this;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public static of(value: any, label: string): EnumValue {
 | 
					    public static of(value: any, label: string): EnumValue {
 | 
				
			||||||
        return new EnumValue(value, label);
 | 
					        return new EnumValue(value, label);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@@ -60,11 +67,12 @@ export class EnumValue {
 | 
				
			|||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * 根据枚举值获取指定枚举值对象
 | 
					     * 根据枚举值获取指定枚举值对象
 | 
				
			||||||
     *
 | 
					     *
 | 
				
			||||||
     * @param enumValues 所有枚举值
 | 
					     * @param enums 枚举对象
 | 
				
			||||||
     * @param value 需要匹配的枚举值
 | 
					     * @param value 需要匹配的枚举值
 | 
				
			||||||
     * @returns 枚举值对象
 | 
					     * @returns 枚举值对象
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    static getEnumByValue(enumValues: EnumValue[], value: any): EnumValue | null {
 | 
					    static getEnumByValue(enums: any, value: any): EnumValue | null {
 | 
				
			||||||
 | 
					        const enumValues = Object.values(enums) as any;
 | 
				
			||||||
        for (let enumValue of enumValues) {
 | 
					        for (let enumValue of enumValues) {
 | 
				
			||||||
            if (enumValue.value == value) {
 | 
					            if (enumValue.value == value) {
 | 
				
			||||||
                return enumValue;
 | 
					                return enumValue;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,9 +1,24 @@
 | 
				
			|||||||
import EnumValue from './Enum';
 | 
					import EnumValue from './Enum';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// 资源类型
 | 
				
			||||||
 | 
					export const ResourceTypeEnum = {
 | 
				
			||||||
 | 
					    Machine: EnumValue.of(1, '机器').setExtra({ icon: 'Monitor', iconColor: 'var(--el-color-primary)' }).tagTypeSuccess(),
 | 
				
			||||||
 | 
					    Db: EnumValue.of(2, '数据库实例').setExtra({ icon: 'Coin', iconColor: 'var(--el-color-warning)' }).tagTypeWarning(),
 | 
				
			||||||
 | 
					    Redis: EnumValue.of(3, 'redis').setExtra({ icon: 'iconfont icon-redis', iconColor: 'var(--el-color-danger)' }).tagTypeInfo(),
 | 
				
			||||||
 | 
					    Mongo: EnumValue.of(4, 'mongo').setExtra({ icon: 'iconfont icon-mongo', iconColor: 'var(--el-color-success)' }).tagTypeDanger(),
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// 标签关联的资源类型
 | 
					// 标签关联的资源类型
 | 
				
			||||||
export const TagResourceTypeEnum = {
 | 
					export const TagResourceTypeEnum = {
 | 
				
			||||||
    Machine: EnumValue.of(1, '机器'),
 | 
					    AuthCert: EnumValue.of(-2, '公共凭证').setExtra({ icon: 'Ticket' }),
 | 
				
			||||||
    Db: EnumValue.of(2, '数据库'),
 | 
					    Tag: EnumValue.of(-1, '标签').setExtra({ icon: 'CollectionTag' }),
 | 
				
			||||||
    Redis: EnumValue.of(3, 'redis'),
 | 
					
 | 
				
			||||||
    Mongo: EnumValue.of(4, 'mongo'),
 | 
					    Machine: ResourceTypeEnum.Machine,
 | 
				
			||||||
 | 
					    Db: ResourceTypeEnum.Db,
 | 
				
			||||||
 | 
					    Redis: ResourceTypeEnum.Redis,
 | 
				
			||||||
 | 
					    Mongo: ResourceTypeEnum.Mongo,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    MachineAuthCert: EnumValue.of(11, '机器-授权凭证').setExtra({ icon: 'Ticket', iconColor: 'var(--el-color-success)' }),
 | 
				
			||||||
 | 
					    DbAuthCert: EnumValue.of(21, '数据库-授权凭证').setExtra({ icon: 'Ticket', iconColor: 'var(--el-color-success)' }),
 | 
				
			||||||
 | 
					    DbName: EnumValue.of(22, '数据库').setExtra({ icon: 'Coin' }),
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -15,7 +15,7 @@ const config = {
 | 
				
			|||||||
    baseWsUrl: `${(window as any).globalConfig.BaseWsUrl || `${location.protocol == 'https:' ? 'wss:' : 'ws:'}//${getBaseApiUrl()}`}/api`,
 | 
					    baseWsUrl: `${(window as any).globalConfig.BaseWsUrl || `${location.protocol == 'https:' ? 'wss:' : 'ws:'}//${getBaseApiUrl()}`}/api`,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // 系统版本
 | 
					    // 系统版本
 | 
				
			||||||
    version: 'v1.7.0',
 | 
					    version: 'v1.9.0',
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default config;
 | 
					export default config;
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										38
									
								
								mayfly_go_web/src/common/crypto.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								mayfly_go_web/src/common/crypto.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,38 @@
 | 
				
			|||||||
 | 
					import CryptoJS from 'crypto-js';
 | 
				
			||||||
 | 
					import { getToken } from '@/common/utils/storage';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * AES 加密数据
 | 
				
			||||||
 | 
					 * @param word
 | 
				
			||||||
 | 
					 * @param key
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					export function AesEncrypt(word: string, key?: string) {
 | 
				
			||||||
 | 
					    if (!key) {
 | 
				
			||||||
 | 
					        key = getToken().substring(0, 24);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const sKey = CryptoJS.enc.Utf8.parse(key);
 | 
				
			||||||
 | 
					    const encrypted = CryptoJS.AES.encrypt(word, sKey, {
 | 
				
			||||||
 | 
					        iv: sKey,
 | 
				
			||||||
 | 
					        mode: CryptoJS.mode.CBC,
 | 
				
			||||||
 | 
					        padding: CryptoJS.pad.Pkcs7,
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return encrypted.ciphertext.toString(CryptoJS.enc.Base64);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function AesDecrypt(word: string, key?: string): string {
 | 
				
			||||||
 | 
					    if (!key) {
 | 
				
			||||||
 | 
					        key = getToken().substring(0, 24);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const sKey = CryptoJS.enc.Utf8.parse(key);
 | 
				
			||||||
 | 
					    // key 和 iv 使用同一个值
 | 
				
			||||||
 | 
					    const decrypted = CryptoJS.AES.decrypt(word, sKey, {
 | 
				
			||||||
 | 
					        iv: sKey,
 | 
				
			||||||
 | 
					        mode: CryptoJS.mode.CBC, // CBC算法
 | 
				
			||||||
 | 
					        padding: CryptoJS.pad.Pkcs7, //使用pkcs7 进行padding 后端需要注意
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return decrypted.toString(CryptoJS.enc.Base64);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -2,6 +2,7 @@ import request from './request';
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
export default {
 | 
					export default {
 | 
				
			||||||
    login: (param: any) => request.post('/auth/accounts/login', param),
 | 
					    login: (param: any) => request.post('/auth/accounts/login', param),
 | 
				
			||||||
 | 
					    refreshToken: (param: any) => request.get('/auth/accounts/refreshToken', param),
 | 
				
			||||||
    otpVerify: (param: any) => request.post('/auth/accounts/otp-verify', param),
 | 
					    otpVerify: (param: any) => request.post('/auth/accounts/otp-verify', param),
 | 
				
			||||||
    getPublicKey: () => request.get('/common/public-key'),
 | 
					    getPublicKey: () => request.get('/common/public-key'),
 | 
				
			||||||
    getConfigValue: (params: any) => request.get('/sys/configs/value', params),
 | 
					    getConfigValue: (params: any) => request.get('/sys/configs/value', params),
 | 
				
			||||||
@@ -13,4 +14,5 @@ export default {
 | 
				
			|||||||
    oauth2Callback: (params: any) => request.get('/auth/oauth2/callback', params),
 | 
					    oauth2Callback: (params: any) => request.get('/auth/oauth2/callback', params),
 | 
				
			||||||
    getLdapEnabled: () => request.get('/auth/ldap/enabled'),
 | 
					    getLdapEnabled: () => request.get('/auth/ldap/enabled'),
 | 
				
			||||||
    ldapLogin: (param: any) => request.post('/auth/ldap/login', param),
 | 
					    ldapLogin: (param: any) => request.post('/auth/ldap/login', param),
 | 
				
			||||||
 | 
					    getFileDetail: (keys: string[]) => request.get(`/sys/files/detail/${keys.join(',')}`),
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,4 +1,9 @@
 | 
				
			|||||||
export const AccountUsernamePattern = {
 | 
					export const AccountUsernamePattern = {
 | 
				
			||||||
    pattern: /^[a-zA-Z0-9_]{5,20}$/g,
 | 
					    pattern: /^[a-zA-Z0-9_]{5,20}$/g,
 | 
				
			||||||
    message: '只允许输入5-20位大小写字母、数字、下划线',
 | 
					    message: '只允许输入5-20位大小写字母、数字、_-.:',
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const ResourceCodePattern = {
 | 
				
			||||||
 | 
					    pattern: /^[a-zA-Z0-9_\-.:]{1,32}$/g,
 | 
				
			||||||
 | 
					    message: '只允许输入1-32位大小写字母、数字、_-.:',
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -38,6 +38,7 @@ export enum ResultEnum {
 | 
				
			|||||||
    PARAM_ERROR = 405,
 | 
					    PARAM_ERROR = 405,
 | 
				
			||||||
    SERVER_ERROR = 500,
 | 
					    SERVER_ERROR = 500,
 | 
				
			||||||
    NO_PERMISSION = 501,
 | 
					    NO_PERMISSION = 501,
 | 
				
			||||||
 | 
					    ACCESS_TOKEN_INVALID = 502, // accessToken失效
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const baseUrl: string = config.baseApiUrl;
 | 
					export const baseUrl: string = config.baseApiUrl;
 | 
				
			||||||
@@ -208,6 +209,36 @@ export function joinClientParams(): string {
 | 
				
			|||||||
    return `token=${getToken()}&clientId=${getClientId()}`;
 | 
					    return `token=${getToken()}&clientId=${getClientId()}`;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * 获取文件url地址
 | 
				
			||||||
 | 
					 * @param key 文件key
 | 
				
			||||||
 | 
					 * @returns 文件url
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					export function getFileUrl(key: string) {
 | 
				
			||||||
 | 
					    return `${baseUrl}/sys/files/${key}`;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * 获取系统文件上传url
 | 
				
			||||||
 | 
					 * @param key 文件key
 | 
				
			||||||
 | 
					 * @returns 文件上传url
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					export function getUploadFileUrl(key: string = '') {
 | 
				
			||||||
 | 
					    return `${baseUrl}/sys/files/upload?token=${getToken()}&fileKey=${key}`;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * 下载文件
 | 
				
			||||||
 | 
					 * @param key 文件key
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					export function downloadFile(key: string) {
 | 
				
			||||||
 | 
					    const a = document.createElement('a');
 | 
				
			||||||
 | 
					    a.setAttribute('href', `${getFileUrl(key)}`);
 | 
				
			||||||
 | 
					    a.setAttribute('target', '_blank');
 | 
				
			||||||
 | 
					    a.click();
 | 
				
			||||||
 | 
					    a.remove();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function parseResult(result: Result) {
 | 
					function parseResult(result: Result) {
 | 
				
			||||||
    if (result.code === ResultEnum.SUCCESS) {
 | 
					    if (result.code === ResultEnum.SUCCESS) {
 | 
				
			||||||
        return result.data;
 | 
					        return result.data;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,9 +1,9 @@
 | 
				
			|||||||
import Config from './config';
 | 
					import Config from './config';
 | 
				
			||||||
import { ElNotification } from 'element-plus';
 | 
					import {ElNotification} from 'element-plus';
 | 
				
			||||||
import SocketBuilder from './SocketBuilder';
 | 
					import SocketBuilder from './SocketBuilder';
 | 
				
			||||||
import { getToken } from '@/common/utils/storage';
 | 
					import {getToken} from '@/common/utils/storage';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import { joinClientParams } from './request';
 | 
					import {joinClientParams} from './request';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class SysSocket {
 | 
					class SysSocket {
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
@@ -19,10 +19,11 @@ class SysSocket {
 | 
				
			|||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * 消息类型
 | 
					     * 消息类型
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    messageTypes = {
 | 
					    messageTypes: any = {
 | 
				
			||||||
        0: 'error',
 | 
					        0: 'error',
 | 
				
			||||||
        1: 'success',
 | 
					        1: 'success',
 | 
				
			||||||
        2: 'info',
 | 
					        2: 'info',
 | 
				
			||||||
 | 
					        22: 'info',
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
@@ -57,12 +58,20 @@ class SysSocket {
 | 
				
			|||||||
                }
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                const type = this.getMsgType(message.type);
 | 
					                const type = this.getMsgType(message.type);
 | 
				
			||||||
 | 
					                let msg = message.msg
 | 
				
			||||||
 | 
					                let duration = 0
 | 
				
			||||||
 | 
					                if (message.type == 22) {
 | 
				
			||||||
 | 
					                    let obj = JSON.parse(msg);
 | 
				
			||||||
 | 
					                    msg = `文件:${obj['title']} 执行成功: ${obj['executedStatements']} 条`
 | 
				
			||||||
 | 
					                    duration = 2000
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
                ElNotification({
 | 
					                ElNotification({
 | 
				
			||||||
                    duration: 0,
 | 
					                    duration: duration,
 | 
				
			||||||
                    title: message.title,
 | 
					                    title: message.title,
 | 
				
			||||||
                    message: message.msg,
 | 
					                    message: msg,
 | 
				
			||||||
                    type: type,
 | 
					                    type: type,
 | 
				
			||||||
                });
 | 
					                });
 | 
				
			||||||
 | 
					                console.log(message)
 | 
				
			||||||
            })
 | 
					            })
 | 
				
			||||||
            .open((event: any) => console.log(event))
 | 
					            .open((event: any) => console.log(event))
 | 
				
			||||||
            .close(() => {
 | 
					            .close(() => {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,31 +0,0 @@
 | 
				
			|||||||
export function dateFormat2(fmt: string, date: Date) {
 | 
					 | 
				
			||||||
    let ret;
 | 
					 | 
				
			||||||
    const opt = {
 | 
					 | 
				
			||||||
        'y+': date.getFullYear().toString(), // 年
 | 
					 | 
				
			||||||
        'M+': (date.getMonth() + 1).toString(), // 月
 | 
					 | 
				
			||||||
        'd+': date.getDate().toString(), // 日
 | 
					 | 
				
			||||||
        'H+': date.getHours().toString(), // 时
 | 
					 | 
				
			||||||
        'm+': date.getMinutes().toString(), // 分
 | 
					 | 
				
			||||||
        's+': date.getSeconds().toString(), // 秒
 | 
					 | 
				
			||||||
        'S+': date.getMilliseconds() ? date.getMilliseconds().toString() : '', // 毫秒
 | 
					 | 
				
			||||||
        // 有其他格式化字符需求可以继续添加,必须转化成字符串
 | 
					 | 
				
			||||||
    };
 | 
					 | 
				
			||||||
    for (const k in opt) {
 | 
					 | 
				
			||||||
        ret = new RegExp('(' + k + ')').exec(fmt);
 | 
					 | 
				
			||||||
        if (ret) {
 | 
					 | 
				
			||||||
            fmt = fmt.replace(ret[1], ret[1].length == 1 ? opt[k] : opt[k].padStart(ret[1].length, '0'));
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    return fmt;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export function dateStrFormat(fmt: string, dateStr: string) {
 | 
					 | 
				
			||||||
    return dateFormat2(fmt, new Date(dateStr));
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export function dateFormat(dateStr: string) {
 | 
					 | 
				
			||||||
    if (!dateStr) {
 | 
					 | 
				
			||||||
        return '';
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    return dateFormat2('yyyy-MM-dd HH:mm:ss', new Date(dateStr));
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@@ -7,24 +7,22 @@ export function exportCsv(filename: string, columns: string[], datas: []) {
 | 
				
			|||||||
        for (let column of columns) {
 | 
					        for (let column of columns) {
 | 
				
			||||||
            let val: any = data[column];
 | 
					            let val: any = data[column];
 | 
				
			||||||
            if (val == null || val == undefined) {
 | 
					            if (val == null || val == undefined) {
 | 
				
			||||||
                dataValueArr.push('');
 | 
					                val = '';
 | 
				
			||||||
                continue;
 | 
					            } else if (val && typeof val == 'string') {
 | 
				
			||||||
            }
 | 
					                // 替换换行符
 | 
				
			||||||
 | 
					                val = val.replace(/[\r\n]/g, '\\n');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            if (typeof val == 'string' && val) {
 | 
					 | 
				
			||||||
                // csv格式如果有逗号,整体用双引号括起来;如果里面还有双引号就替换成两个双引号,这样导出来的格式就不会有问题了
 | 
					                // csv格式如果有逗号,整体用双引号括起来;如果里面还有双引号就替换成两个双引号,这样导出来的格式就不会有问题了
 | 
				
			||||||
                if (val.indexOf(',') != -1) {
 | 
					                if (val.indexOf(',') != -1) {
 | 
				
			||||||
                    // 如果还有双引号,先将双引号转义,避免两边加了双引号后转义错误
 | 
					                    // 如果还有双引号,先将双引号转义,避免两边加了双引号后转义错误
 | 
				
			||||||
                    if (val.indexOf('"') != -1) {
 | 
					                    if (val.indexOf('"') != -1) {
 | 
				
			||||||
                        val = val.replace(/\"/g, '""');
 | 
					                        val = val.replace(/"/g, '""');
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
                    // 再将逗号转义
 | 
					                    // 再将逗号转义
 | 
				
			||||||
                    val = `"${val}"`;
 | 
					                    val = `"${val}"`;
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
                dataValueArr.push(val + '\t');
 | 
					 | 
				
			||||||
            } else {
 | 
					 | 
				
			||||||
                dataValueArr.push(val + '\t');
 | 
					 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					            dataValueArr.push(String(val));
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        cvsData.push(dataValueArr);
 | 
					        cvsData.push(dataValueArr);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,3 +1,18 @@
 | 
				
			|||||||
 | 
					import dayjs from 'dayjs';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * 格式化日期
 | 
				
			||||||
 | 
					 * @param date 日期 字符串 Date 时间戳等
 | 
				
			||||||
 | 
					 * @param format 格式化格式  默认 YYYY-MM-DD HH:mm:ss
 | 
				
			||||||
 | 
					 * @returns 格式化后内容
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					export function formatDate(date: any, format: string = 'YYYY-MM-DD HH:mm:ss') {
 | 
				
			||||||
 | 
					    if (!date) {
 | 
				
			||||||
 | 
					        return '';
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    return dayjs(date).format(format);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * 格式化字节单位
 | 
					 * 格式化字节单位
 | 
				
			||||||
 * @param size byte size
 | 
					 * @param size byte size
 | 
				
			||||||
@@ -47,161 +62,42 @@ export function convertToBytes(sizeStr: string) {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * 格式化json字符串
 | 
					 * 格式化指定时间数为人性化可阅读的内容(默认time为秒单位)
 | 
				
			||||||
 * @param txt  json字符串
 | 
					 *
 | 
				
			||||||
 * @param compress 是否压缩
 | 
					 * @param time 时间数
 | 
				
			||||||
 * @returns 格式化后的字符串
 | 
					 * @param unit time对应的单位
 | 
				
			||||||
 | 
					 * @returns
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
export function formatJsonString(txt: string, compress: boolean) {
 | 
					export function formatTime(time: number, unit: string = 's') {
 | 
				
			||||||
    var indentChar = '    ';
 | 
					    const units = {
 | 
				
			||||||
    if (/^\s*$/.test(txt)) {
 | 
					        y: 31536000,
 | 
				
			||||||
        console.log('数据为空,无法格式化! ');
 | 
					        M: 2592000,
 | 
				
			||||||
        return txt;
 | 
					        d: 86400,
 | 
				
			||||||
    }
 | 
					        h: 3600,
 | 
				
			||||||
    try {
 | 
					        m: 60,
 | 
				
			||||||
        var data = JSON.parse(txt);
 | 
					        s: 1,
 | 
				
			||||||
    } catch (e: any) {
 | 
					    };
 | 
				
			||||||
        console.log('数据源语法错误,格式化失败! 错误信息: ' + e.description, 'err');
 | 
					 | 
				
			||||||
        return txt;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    var draw: any = [],
 | 
					 | 
				
			||||||
        line = compress ? '' : '\n',
 | 
					 | 
				
			||||||
        // eslint-disable-next-line no-unused-vars
 | 
					 | 
				
			||||||
        nodeCount: number = 0,
 | 
					 | 
				
			||||||
        // eslint-disable-next-line no-unused-vars
 | 
					 | 
				
			||||||
        maxDepth: number = 0;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    var notify = function (name: any, value: any, isLast: any, indent: any, formObj: any) {
 | 
					    if (!units[unit]) {
 | 
				
			||||||
        nodeCount++; /*节点计数*/
 | 
					        return 'Invalid unit';
 | 
				
			||||||
        for (var i = 0, tab = ''; i < indent; i++) tab += indentChar; /* 缩进HTML */
 | 
					    }
 | 
				
			||||||
        tab = compress ? '' : tab; /*压缩模式忽略缩进*/
 | 
					
 | 
				
			||||||
        maxDepth = ++indent; /*缩进递增并记录*/
 | 
					    let seconds = time * units[unit];
 | 
				
			||||||
        if (value && value.constructor == Array) {
 | 
					    let result = '';
 | 
				
			||||||
            /*处理数组*/
 | 
					
 | 
				
			||||||
            draw.push(tab + (formObj ? '"' + name + '": ' : '') + '[' + line); /*缩进'[' 然后换行*/
 | 
					    const timeUnits = Object.entries(units).map(([unit, duration]) => {
 | 
				
			||||||
            for (var i = 0; i < value.length; i++) notify(i, value[i], i == value.length - 1, indent, false);
 | 
					        const value = Math.floor(seconds / duration);
 | 
				
			||||||
            draw.push(tab + ']' + (isLast ? line : ',' + line)); /*缩进']'换行,若非尾元素则添加逗号*/
 | 
					        seconds %= duration;
 | 
				
			||||||
        } else if (value && typeof value == 'object') {
 | 
					        return { value, unit };
 | 
				
			||||||
            /*处理对象*/
 | 
					    });
 | 
				
			||||||
            draw.push(tab + (formObj ? '"' + name + '": ' : '') + '{' + line); /*缩进'{' 然后换行*/
 | 
					
 | 
				
			||||||
            var len = 0,
 | 
					    timeUnits.forEach(({ value, unit }) => {
 | 
				
			||||||
                i = 0;
 | 
					        if (value > 0) {
 | 
				
			||||||
            for (var key in value) len++;
 | 
					            result += `${value}${unit} `;
 | 
				
			||||||
            for (var key in value) notify(key, value[key], ++i == len, indent, true);
 | 
					 | 
				
			||||||
            draw.push(tab + '}' + (isLast ? line : ',' + line)); /*缩进'}'换行,若非尾元素则添加逗号*/
 | 
					 | 
				
			||||||
        } else {
 | 
					 | 
				
			||||||
            if (typeof value == 'string') value = '"' + value + '"';
 | 
					 | 
				
			||||||
            draw.push(tab + (formObj ? '"' + name + '": ' : '') + value + (isLast ? '' : ',') + line);
 | 
					 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    };
 | 
					    });
 | 
				
			||||||
    var isLast = true,
 | 
					 | 
				
			||||||
        indent = 0;
 | 
					 | 
				
			||||||
    notify('', data, isLast, indent, false);
 | 
					 | 
				
			||||||
    return draw.join('');
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
/*
 | 
					    return result;
 | 
				
			||||||
 * 年(Y) 可用1-4个占位符
 | 
					 | 
				
			||||||
 * 月(m)、日(d)、小时(H)、分(M)、秒(S) 可用1-2个占位符
 | 
					 | 
				
			||||||
 * 星期(W) 可用1-3个占位符
 | 
					 | 
				
			||||||
 * 季度(q为阿拉伯数字,Q为中文数字)可用1或4个占位符
 | 
					 | 
				
			||||||
 *
 | 
					 | 
				
			||||||
 * let date = new Date()
 | 
					 | 
				
			||||||
 * formatDate(date, "YYYY-mm-dd HH:MM:SS")           // 2020-02-09 14:04:23
 | 
					 | 
				
			||||||
 * formatDate(date, "YYYY-mm-dd HH:MM:SS Q")         // 2020-02-09 14:09:03 一
 | 
					 | 
				
			||||||
 * formatDate(date, "YYYY-mm-dd HH:MM:SS WWW")       // 2020-02-09 14:45:12 星期日
 | 
					 | 
				
			||||||
 * formatDate(date, "YYYY-mm-dd HH:MM:SS QQQQ")      // 2020-02-09 14:09:36 第一季度
 | 
					 | 
				
			||||||
 * formatDate(date, "YYYY-mm-dd HH:MM:SS WWW QQQQ")  // 2020-02-09 14:46:12 星期日 第一季度
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
export function formatDate(date: Date, format: string) {
 | 
					 | 
				
			||||||
    let we = date.getDay(); // 星期
 | 
					 | 
				
			||||||
    let qut = Math.floor((date.getMonth() + 3) / 3).toString(); // 季度
 | 
					 | 
				
			||||||
    const opt: any = {
 | 
					 | 
				
			||||||
        'Y+': date.getFullYear().toString(), // 年
 | 
					 | 
				
			||||||
        'm+': (date.getMonth() + 1).toString(), // 月(月份从0开始,要+1)
 | 
					 | 
				
			||||||
        'd+': date.getDate().toString(), // 日
 | 
					 | 
				
			||||||
        'H+': date.getHours().toString(), // 时
 | 
					 | 
				
			||||||
        'M+': date.getMinutes().toString(), // 分
 | 
					 | 
				
			||||||
        'S+': date.getSeconds().toString(), // 秒
 | 
					 | 
				
			||||||
        'q+': qut, // 季度
 | 
					 | 
				
			||||||
    };
 | 
					 | 
				
			||||||
    // 中文数字 (星期)
 | 
					 | 
				
			||||||
    const week: any = {
 | 
					 | 
				
			||||||
        '0': '日',
 | 
					 | 
				
			||||||
        '1': '一',
 | 
					 | 
				
			||||||
        '2': '二',
 | 
					 | 
				
			||||||
        '3': '三',
 | 
					 | 
				
			||||||
        '4': '四',
 | 
					 | 
				
			||||||
        '5': '五',
 | 
					 | 
				
			||||||
        '6': '六',
 | 
					 | 
				
			||||||
    };
 | 
					 | 
				
			||||||
    // 中文数字(季度)
 | 
					 | 
				
			||||||
    const quarter: any = {
 | 
					 | 
				
			||||||
        '1': '一',
 | 
					 | 
				
			||||||
        '2': '二',
 | 
					 | 
				
			||||||
        '3': '三',
 | 
					 | 
				
			||||||
        '4': '四',
 | 
					 | 
				
			||||||
    };
 | 
					 | 
				
			||||||
    if (/(W+)/.test(format)) format = format.replace(RegExp.$1, RegExp.$1.length > 1 ? (RegExp.$1.length > 2 ? '星期' + week[we] : '周' + week[we]) : week[we]);
 | 
					 | 
				
			||||||
    if (/(Q+)/.test(format)) format = format.replace(RegExp.$1, RegExp.$1.length == 4 ? '第' + quarter[qut] + '季度' : quarter[qut]);
 | 
					 | 
				
			||||||
    for (let k in opt) {
 | 
					 | 
				
			||||||
        let r = new RegExp('(' + k + ')').exec(format);
 | 
					 | 
				
			||||||
        // 若输入的长度不为1,则前面补零
 | 
					 | 
				
			||||||
        if (r) format = format.replace(r[1], RegExp.$1.length == 1 ? opt[k] : opt[k].padStart(RegExp.$1.length, '0'));
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    return format;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * 10秒:  10 * 1000
 | 
					 | 
				
			||||||
 * 1分:   60 * 1000
 | 
					 | 
				
			||||||
 * 1小时: 60 * 60 * 1000
 | 
					 | 
				
			||||||
 * 24小时:60 * 60 * 24 * 1000
 | 
					 | 
				
			||||||
 * 3天:   60 * 60* 24 * 1000 * 3
 | 
					 | 
				
			||||||
 *
 | 
					 | 
				
			||||||
 * let data = new Date()
 | 
					 | 
				
			||||||
 * formatPast(data)                                           // 刚刚
 | 
					 | 
				
			||||||
 * formatPast(data - 11 * 1000)                               // 11秒前
 | 
					 | 
				
			||||||
 * formatPast(data - 2 * 60 * 1000)                           // 2分钟前
 | 
					 | 
				
			||||||
 * formatPast(data - 60 * 60 * 2 * 1000)                      // 2小时前
 | 
					 | 
				
			||||||
 * formatPast(data - 60 * 60 * 2 * 1000)                      // 2小时前
 | 
					 | 
				
			||||||
 * formatPast(data - 60 * 60 * 71 * 1000)                     // 2天前
 | 
					 | 
				
			||||||
 * formatPast("2020-06-01")                                   // 2020-06-01
 | 
					 | 
				
			||||||
 * formatPast("2020-06-01", "YYYY-mm-dd HH:MM:SS WWW QQQQ")   // 2020-06-01 08:00:00 星期一 第二季度
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
export function formatPast(param: any, format: string = 'YYYY-mm-dd') {
 | 
					 | 
				
			||||||
    // 传入格式处理、存储转换值
 | 
					 | 
				
			||||||
    let t: any, s: any;
 | 
					 | 
				
			||||||
    // 获取js 时间戳
 | 
					 | 
				
			||||||
    let time: any = new Date().getTime();
 | 
					 | 
				
			||||||
    // 是否是对象
 | 
					 | 
				
			||||||
    typeof param === 'string' || 'object' ? (t = new Date(param).getTime()) : (t = param);
 | 
					 | 
				
			||||||
    // 当前时间戳 - 传入时间戳
 | 
					 | 
				
			||||||
    time = Number.parseInt(`${time - t}`);
 | 
					 | 
				
			||||||
    if (time < 10000) {
 | 
					 | 
				
			||||||
        // 10秒内
 | 
					 | 
				
			||||||
        return '刚刚';
 | 
					 | 
				
			||||||
    } else if (time < 60000 && time >= 10000) {
 | 
					 | 
				
			||||||
        // 超过10秒少于1分钟内
 | 
					 | 
				
			||||||
        s = Math.floor(time / 1000);
 | 
					 | 
				
			||||||
        return `${s}秒前`;
 | 
					 | 
				
			||||||
    } else if (time < 3600000 && time >= 60000) {
 | 
					 | 
				
			||||||
        // 超过1分钟少于1小时
 | 
					 | 
				
			||||||
        s = Math.floor(time / 60000);
 | 
					 | 
				
			||||||
        return `${s}分钟前`;
 | 
					 | 
				
			||||||
    } else if (time < 86400000 && time >= 3600000) {
 | 
					 | 
				
			||||||
        // 超过1小时少于24小时
 | 
					 | 
				
			||||||
        s = Math.floor(time / 3600000);
 | 
					 | 
				
			||||||
        return `${s}小时前`;
 | 
					 | 
				
			||||||
    } else if (time < 259200000 && time >= 86400000) {
 | 
					 | 
				
			||||||
        // 超过1天少于3天内
 | 
					 | 
				
			||||||
        s = Math.floor(time / 86400000);
 | 
					 | 
				
			||||||
        return `${s}天前`;
 | 
					 | 
				
			||||||
    } else {
 | 
					 | 
				
			||||||
        // 超过3天
 | 
					 | 
				
			||||||
        let date = typeof param === 'string' || 'object' ? new Date(param) : param;
 | 
					 | 
				
			||||||
        return formatDate(date, format);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										56
									
								
								mayfly_go_web/src/common/utils/object.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										56
									
								
								mayfly_go_web/src/common/utils/object.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,56 @@
 | 
				
			|||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * 根据对象访问路径,获取对应的值
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @param obj 对象,如 {user: {name: 'xxx'}, orderNo: 1212211, products: [{id: 12}]}
 | 
				
			||||||
 | 
					 * @param path 访问路径,如 orderNo 或者 user.name 或者product[0].id
 | 
				
			||||||
 | 
					 * @returns 路径对应的值
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					export function getValueByPath(obj: any, path: string) {
 | 
				
			||||||
 | 
					    const keys = path.split('.');
 | 
				
			||||||
 | 
					    let result = obj;
 | 
				
			||||||
 | 
					    for (let key of keys) {
 | 
				
			||||||
 | 
					        if (!result) {
 | 
				
			||||||
 | 
					            return undefined;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        // 如果是字符串,则尝试使用json解析
 | 
				
			||||||
 | 
					        if (typeof result == 'string') {
 | 
				
			||||||
 | 
					            try {
 | 
				
			||||||
 | 
					                result = JSON.parse(result);
 | 
				
			||||||
 | 
					            } catch (e) {
 | 
				
			||||||
 | 
					                console.error(e);
 | 
				
			||||||
 | 
					                return undefined;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        if (typeof result !== 'object') {
 | 
				
			||||||
 | 
					            return undefined;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (key.includes('[') && key.includes(']')) {
 | 
				
			||||||
 | 
					            // 处理包含数组索引的情况
 | 
				
			||||||
 | 
					            const arrayKey = key.substring(0, key.indexOf('['));
 | 
				
			||||||
 | 
					            const matchIndex = key.match(/\[(.*?)\]/);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (!matchIndex) {
 | 
				
			||||||
 | 
					                return undefined;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            const index = parseInt(matchIndex[1]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            let arrValue = result[arrayKey];
 | 
				
			||||||
 | 
					            if (typeof arrValue == 'string') {
 | 
				
			||||||
 | 
					                try {
 | 
				
			||||||
 | 
					                    arrValue = JSON.parse(arrValue);
 | 
				
			||||||
 | 
					                } catch (e) {
 | 
				
			||||||
 | 
					                    result = undefined;
 | 
				
			||||||
 | 
					                    break;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            result = Array.isArray(arrValue) ? arrValue[index] : undefined;
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            result = result[key];
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return result;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -1,6 +1,7 @@
 | 
				
			|||||||
import { randomUuid } from './string';
 | 
					import { randomUuid } from './string';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const TokenKey = 'm-token';
 | 
					const TokenKey = 'm-token';
 | 
				
			||||||
 | 
					const RefreshTokenKey = 'm-refresh-token';
 | 
				
			||||||
const UserKey = 'm-user';
 | 
					const UserKey = 'm-user';
 | 
				
			||||||
const TagViewsKey = 'm-tagViews';
 | 
					const TagViewsKey = 'm-tagViews';
 | 
				
			||||||
const ClientIdKey = 'm-clientId';
 | 
					const ClientIdKey = 'm-clientId';
 | 
				
			||||||
@@ -15,6 +16,14 @@ export function saveToken(token: string) {
 | 
				
			|||||||
    setLocal(TokenKey, token);
 | 
					    setLocal(TokenKey, token);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function getRefreshToken(): string {
 | 
				
			||||||
 | 
					    return getLocal(RefreshTokenKey);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function saveRefreshToken(refreshToken: string) {
 | 
				
			||||||
 | 
					    return setLocal(RefreshTokenKey, refreshToken);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// 获取登录用户基础信息
 | 
					// 获取登录用户基础信息
 | 
				
			||||||
export function getUser() {
 | 
					export function getUser() {
 | 
				
			||||||
    return getLocal(UserKey);
 | 
					    return getLocal(UserKey);
 | 
				
			||||||
@@ -39,6 +48,7 @@ export function getThemeConfig() {
 | 
				
			|||||||
export function clearUser() {
 | 
					export function clearUser() {
 | 
				
			||||||
    removeLocal(TokenKey);
 | 
					    removeLocal(TokenKey);
 | 
				
			||||||
    removeLocal(UserKey);
 | 
					    removeLocal(UserKey);
 | 
				
			||||||
 | 
					    removeLocal(RefreshTokenKey);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function getTagViews() {
 | 
					export function getTagViews() {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -97,43 +97,6 @@ export function getTextWidth(str: string) {
 | 
				
			|||||||
    return width;
 | 
					    return width;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * 获取内容所需要占用的宽度
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
export function getContentWidth(content: any): number {
 | 
					 | 
				
			||||||
    if (!content) {
 | 
					 | 
				
			||||||
        return 50;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    // 以下分配的单位长度可根据实际需求进行调整
 | 
					 | 
				
			||||||
    let flexWidth = 0;
 | 
					 | 
				
			||||||
    for (const char of content) {
 | 
					 | 
				
			||||||
        if (flexWidth > 500) {
 | 
					 | 
				
			||||||
            break;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        if ((char >= '0' && char <= '9') || (char >= 'a' && char <= 'z')) {
 | 
					 | 
				
			||||||
            // 小写字母、数字字符
 | 
					 | 
				
			||||||
            flexWidth += 9.3;
 | 
					 | 
				
			||||||
            continue;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        if (char >= 'A' && char <= 'Z') {
 | 
					 | 
				
			||||||
            flexWidth += 9;
 | 
					 | 
				
			||||||
            continue;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        if (char >= '\u4e00' && char <= '\u9fa5') {
 | 
					 | 
				
			||||||
            // 如果是中文字符,为字符分配16个单位宽度
 | 
					 | 
				
			||||||
            flexWidth += 20;
 | 
					 | 
				
			||||||
        } else {
 | 
					 | 
				
			||||||
            // 其他种类字符
 | 
					 | 
				
			||||||
            flexWidth += 8;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    // if (flexWidth > 450) {
 | 
					 | 
				
			||||||
    //     // 设置最大宽度
 | 
					 | 
				
			||||||
    //     flexWidth = 450;
 | 
					 | 
				
			||||||
    // }
 | 
					 | 
				
			||||||
    return flexWidth;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 *
 | 
					 *
 | 
				
			||||||
 * @returns uuid
 | 
					 * @returns uuid
 | 
				
			||||||
@@ -179,3 +142,38 @@ export async function copyToClipboard(txt: string, selector: string = '#copyValu
 | 
				
			|||||||
        clipboard.destroy();
 | 
					        clipboard.destroy();
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function fuzzyMatchField(keyword: string, fields: any[], ...valueExtractFuncs: Function[]) {
 | 
				
			||||||
 | 
					    keyword = keyword?.toLowerCase();
 | 
				
			||||||
 | 
					    return fields.filter((field) => {
 | 
				
			||||||
 | 
					        for (let valueExtractFunc of valueExtractFuncs) {
 | 
				
			||||||
 | 
					            const value = valueExtractFunc(field)?.toLowerCase();
 | 
				
			||||||
 | 
					            if (isPrefixSubsequence(keyword, value)) {
 | 
				
			||||||
 | 
					                return true;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        return false;
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * 匹配是否为前缀子序列 targetTemplate=username prefix=uname -> true,prefix=uname2 -> false
 | 
				
			||||||
 | 
					 * @param prefix 字符串前缀(不连续也可以,但不改变字符的相对顺序)
 | 
				
			||||||
 | 
					 * @param targetTemplate 目标模板
 | 
				
			||||||
 | 
					 * @returns 是否匹配
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					export function isPrefixSubsequence(prefix: string, targetTemplate: string) {
 | 
				
			||||||
 | 
					    let i = 0; // 指向prefix的索引
 | 
				
			||||||
 | 
					    let j = 0; // 指向targetTemplate的索引
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    while (i < prefix.length && j < targetTemplate.length) {
 | 
				
			||||||
 | 
					        if (prefix[i] === targetTemplate[j]) {
 | 
				
			||||||
 | 
					            // 字符匹配,两个指针都向前移动
 | 
				
			||||||
 | 
					            i++;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        j++; // 目标字符串指针始终向前移动
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // 如果prefix的所有字符都被找到,返回true
 | 
				
			||||||
 | 
					    return i === prefix.length;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -4,6 +4,7 @@ export interface ViteEnv {
 | 
				
			|||||||
    VITE_PORT: number;
 | 
					    VITE_PORT: number;
 | 
				
			||||||
    VITE_OPEN: boolean;
 | 
					    VITE_OPEN: boolean;
 | 
				
			||||||
    VITE_PUBLIC_PATH: string;
 | 
					    VITE_PUBLIC_PATH: string;
 | 
				
			||||||
 | 
					    VITE_EDITOR: string;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function loadEnv(): ViteEnv {
 | 
					export function loadEnv(): ViteEnv {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -83,7 +83,7 @@ const breakPoint = computed<BreakPoint>(() => gridRef.value?.breakPoint);
 | 
				
			|||||||
// 判断是否显示 展开/合并 按钮
 | 
					// 判断是否显示 展开/合并 按钮
 | 
				
			||||||
const showCollapse = computed(() => {
 | 
					const showCollapse = computed(() => {
 | 
				
			||||||
    let show = false;
 | 
					    let show = false;
 | 
				
			||||||
    props.items.reduce((prev, current) => {
 | 
					    props.items.reduce((prev, current: any) => {
 | 
				
			||||||
        prev += (current![breakPoint.value]?.span ?? current?.span ?? 1) + (current![breakPoint.value]?.offset ?? current?.offset ?? 0);
 | 
					        prev += (current![breakPoint.value]?.span ?? current?.span ?? 1) + (current![breakPoint.value]?.offset ?? current?.offset ?? 0);
 | 
				
			||||||
        if (typeof props.searchCol !== 'number') {
 | 
					        if (typeof props.searchCol !== 'number') {
 | 
				
			||||||
            if (prev >= props.searchCol[breakPoint.value]) show = true;
 | 
					            if (prev >= props.searchCol[breakPoint.value]) show = true;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -14,11 +14,11 @@ export function hasPerm(code: string) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * 判断用户是否拥有权限对象里对应的code
 | 
					 * 判断用户是否拥有权限对象里对应的code
 | 
				
			||||||
 * @param perms { save: "xxx:save"}
 | 
					 | 
				
			||||||
 * @returns {"xxx:save": true}  key->permission code
 | 
					 * @returns {"xxx:save": true}  key->permission code
 | 
				
			||||||
 | 
					 * @param permCodes
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
export function hasPerms(permCodes: any[]) {
 | 
					export function hasPerms(permCodes: any[]) {
 | 
				
			||||||
    const res = {};
 | 
					    const res = {} as { [key: string]: boolean };
 | 
				
			||||||
    for (let permCode of permCodes) {
 | 
					    for (let permCode of permCodes) {
 | 
				
			||||||
        if (hasPerm(permCode)) {
 | 
					        if (hasPerm(permCode)) {
 | 
				
			||||||
            res[permCode] = true;
 | 
					            res[permCode] = true;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -35,38 +35,42 @@
 | 
				
			|||||||
                <p class="title">时间表达式</p>
 | 
					                <p class="title">时间表达式</p>
 | 
				
			||||||
                <table>
 | 
					                <table>
 | 
				
			||||||
                    <thead>
 | 
					                    <thead>
 | 
				
			||||||
                        <th v-for="item of tabTitles" width="40" :key="item">{{ item }}</th>
 | 
					                        <tr>
 | 
				
			||||||
                        <th>crontab完整表达式</th>
 | 
					                            <th v-for="item of tabTitles" width="40" :key="item">{{ item }}</th>
 | 
				
			||||||
 | 
					                            <th>crontab完整表达式</th>
 | 
				
			||||||
 | 
					                        </tr>
 | 
				
			||||||
                    </thead>
 | 
					                    </thead>
 | 
				
			||||||
                    <tbody>
 | 
					                    <tbody>
 | 
				
			||||||
                        <td>
 | 
					                        <tr>
 | 
				
			||||||
                            <span>{{ crontabValueObj.second }}</span>
 | 
					                            <td>
 | 
				
			||||||
                        </td>
 | 
					                                <span>{{ crontabValueObj.second }}</span>
 | 
				
			||||||
                        <td>
 | 
					                            </td>
 | 
				
			||||||
                            <span>{{ crontabValueObj.min }}</span>
 | 
					                            <td>
 | 
				
			||||||
                        </td>
 | 
					                                <span>{{ crontabValueObj.min }}</span>
 | 
				
			||||||
                        <td>
 | 
					                            </td>
 | 
				
			||||||
                            <span>{{ crontabValueObj.hour }}</span>
 | 
					                            <td>
 | 
				
			||||||
                        </td>
 | 
					                                <span>{{ crontabValueObj.hour }}</span>
 | 
				
			||||||
                        <td>
 | 
					                            </td>
 | 
				
			||||||
                            <span>{{ crontabValueObj.day }}</span>
 | 
					                            <td>
 | 
				
			||||||
                        </td>
 | 
					                                <span>{{ crontabValueObj.day }}</span>
 | 
				
			||||||
                        <td>
 | 
					                            </td>
 | 
				
			||||||
                            <span>{{ crontabValueObj.mouth }}</span>
 | 
					                            <td>
 | 
				
			||||||
                        </td>
 | 
					                                <span>{{ crontabValueObj.mouth }}</span>
 | 
				
			||||||
                        <td>
 | 
					                            </td>
 | 
				
			||||||
                            <span>{{ crontabValueObj.week }}</span>
 | 
					                            <td>
 | 
				
			||||||
                        </td>
 | 
					                                <span>{{ crontabValueObj.week }}</span>
 | 
				
			||||||
                        <td>
 | 
					                            </td>
 | 
				
			||||||
                            <span>{{ crontabValueObj.year }}</span>
 | 
					                            <td>
 | 
				
			||||||
                        </td>
 | 
					                                <span>{{ crontabValueObj.year }}</span>
 | 
				
			||||||
                        <td>
 | 
					                            </td>
 | 
				
			||||||
                            <span>{{ contabValueString }}</span>
 | 
					                            <td>
 | 
				
			||||||
                        </td>
 | 
					                                <span>{{ crontabValueString }}</span>
 | 
				
			||||||
 | 
					                            </td>
 | 
				
			||||||
 | 
					                        </tr>
 | 
				
			||||||
                    </tbody>
 | 
					                    </tbody>
 | 
				
			||||||
                </table>
 | 
					                </table>
 | 
				
			||||||
            </div>
 | 
					            </div>
 | 
				
			||||||
            <CrontabResult :ex="contabValueString"></CrontabResult>
 | 
					            <CrontabResult :ex="crontabValueString"></CrontabResult>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            <div class="pop_btn">
 | 
					            <div class="pop_btn">
 | 
				
			||||||
                <el-button size="small" @click="hidePopup">取消</el-button>
 | 
					                <el-button size="small" @click="hidePopup">取消</el-button>
 | 
				
			||||||
@@ -202,7 +206,7 @@ function hidePopup() {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
// 填充表达式
 | 
					// 填充表达式
 | 
				
			||||||
const submitFill = () => {
 | 
					const submitFill = () => {
 | 
				
			||||||
    emit('fill', contabValueString.value);
 | 
					    emit('fill', crontabValueString.value);
 | 
				
			||||||
    hidePopup();
 | 
					    hidePopup();
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -220,7 +224,7 @@ const clearCron = () => {
 | 
				
			|||||||
    changeTab(state.activeName);
 | 
					    changeTab(state.activeName);
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const contabValueString = computed(() => {
 | 
					const crontabValueString = computed(() => {
 | 
				
			||||||
    let obj = state.crontabValueObj;
 | 
					    let obj = state.crontabValueObj;
 | 
				
			||||||
    let str = obj.second + ' ' + obj.min + ' ' + obj.hour + ' ' + obj.day + ' ' + obj.mouth + ' ' + obj.week + (obj.year == '' ? '' : ' ' + obj.year);
 | 
					    let str = obj.second + ' ' + obj.min + ' ' + obj.hour + ' ' + obj.day + ' ' + obj.mouth + ' ' + obj.week + (obj.year == '' ? '' : ' ' + obj.year);
 | 
				
			||||||
    return str;
 | 
					    return str;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,5 +1,5 @@
 | 
				
			|||||||
<template>
 | 
					<template>
 | 
				
			||||||
    <el-input v-model="cron" placeholder="可点击左边按钮进行可视化配置">
 | 
					    <el-input v-model="cron" placeholder="可点击左边按钮配置">
 | 
				
			||||||
        <template #prepend>
 | 
					        <template #prepend>
 | 
				
			||||||
            <el-button @click="showCron = true" icon="Pointer"></el-button>
 | 
					            <el-button @click="showCron = true" icon="Pointer"></el-button>
 | 
				
			||||||
        </template>
 | 
					        </template>
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										33
									
								
								mayfly_go_web/src/components/drawer-header/DrawerHeader.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								mayfly_go_web/src/components/drawer-header/DrawerHeader.vue
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,33 @@
 | 
				
			|||||||
 | 
					<template>
 | 
				
			||||||
 | 
					    <el-page-header @back="props.back">
 | 
				
			||||||
 | 
					        <template #content>
 | 
				
			||||||
 | 
					            <span>{{ header }}</span>
 | 
				
			||||||
 | 
					            <span v-if="resource && !hideResource">
 | 
				
			||||||
 | 
					                -
 | 
				
			||||||
 | 
					                <el-tooltip v-if="resource.length > 25" :content="resource" placement="bottom">
 | 
				
			||||||
 | 
					                    <el-tag effect="dark" type="success">{{ resource.substring(0, 23) + '...' }}</el-tag>
 | 
				
			||||||
 | 
					                </el-tooltip>
 | 
				
			||||||
 | 
					                <el-tag v-else effect="dark" type="success">{{ resource }}</el-tag>
 | 
				
			||||||
 | 
					            </span>
 | 
				
			||||||
 | 
					            <el-divider v-if="slots.buttons" direction="vertical" />
 | 
				
			||||||
 | 
					            <slot v-if="slots.buttons" name="buttons"></slot>
 | 
				
			||||||
 | 
					        </template>
 | 
				
			||||||
 | 
					        <template #extra>
 | 
				
			||||||
 | 
					            <slot v-if="slots.extra" name="extra"></slot>
 | 
				
			||||||
 | 
					        </template>
 | 
				
			||||||
 | 
					    </el-page-header>
 | 
				
			||||||
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<script lang="ts" setup>
 | 
				
			||||||
 | 
					import { useSlots } from 'vue';
 | 
				
			||||||
 | 
					const slots = useSlots();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					defineOptions({ name: 'DrawerHeader' });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const props = defineProps({
 | 
				
			||||||
 | 
					    header: String,
 | 
				
			||||||
 | 
					    back: Function,
 | 
				
			||||||
 | 
					    resource: String,
 | 
				
			||||||
 | 
					    hideResource: Boolean,
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					</script>
 | 
				
			||||||
							
								
								
									
										17
									
								
								mayfly_go_web/src/components/enumselect/EnumSelect.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								mayfly_go_web/src/components/enumselect/EnumSelect.vue
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,17 @@
 | 
				
			|||||||
 | 
					<template>
 | 
				
			||||||
 | 
					    <el-select v-bind="$attrs" v-model="modelValue">
 | 
				
			||||||
 | 
					        <el-option v-for="item in props.enums" :key="item.value" :label="item.label" :value="item.value"> </el-option>
 | 
				
			||||||
 | 
					    </el-select>
 | 
				
			||||||
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<script lang="ts" setup>
 | 
				
			||||||
 | 
					const props = defineProps({
 | 
				
			||||||
 | 
					    enums: {
 | 
				
			||||||
 | 
					        type: Object, // 需要为EnumValue类型
 | 
				
			||||||
 | 
					        required: true,
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const modelValue: any = defineModel('modelValue');
 | 
				
			||||||
 | 
					</script>
 | 
				
			||||||
 | 
					<style scoped lang="scss"></style>
 | 
				
			||||||
@@ -1,5 +1,5 @@
 | 
				
			|||||||
<template>
 | 
					<template>
 | 
				
			||||||
    <el-tag v-bind="$attrs" :type="type" :color="color" effect="plain">{{ enumLabel }}</el-tag>
 | 
					    <el-tag :disable-transitions="true" v-bind="$attrs" :type="type" :color="color" effect="plain">{{ enumLabel }}</el-tag>
 | 
				
			||||||
</template>
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<script lang="ts" setup>
 | 
					<script lang="ts" setup>
 | 
				
			||||||
@@ -40,7 +40,7 @@ onMounted(() => {
 | 
				
			|||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const convert = (value: any) => {
 | 
					const convert = (value: any) => {
 | 
				
			||||||
    const enumValue = EnumValue.getEnumByValue(Object.values(props.enums as any) as any, value) as any;
 | 
					    const enumValue = EnumValue.getEnumByValue(props.enums, value) as any;
 | 
				
			||||||
    if (!enumValue) {
 | 
					    if (!enumValue) {
 | 
				
			||||||
        state.enumLabel = '-';
 | 
					        state.enumLabel = '-';
 | 
				
			||||||
        state.type = 'danger';
 | 
					        state.type = 'danger';
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										60
									
								
								mayfly_go_web/src/components/file/FileInfo.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										60
									
								
								mayfly_go_web/src/components/file/FileInfo.vue
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,60 @@
 | 
				
			|||||||
 | 
					<template>
 | 
				
			||||||
 | 
					    <el-tooltip :content="formatByteSize(fileDetail?.size)" placement="left">
 | 
				
			||||||
 | 
					        <el-link v-if="props.canDownload" target="_blank" rel="noopener noreferrer" icon="Download" type="primary" :href="getFileUrl(props.fileKey)"></el-link>
 | 
				
			||||||
 | 
					    </el-tooltip>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    {{ fileDetail?.filename }}
 | 
				
			||||||
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<script lang="ts" setup>
 | 
				
			||||||
 | 
					import { onMounted, ref, watch } from 'vue';
 | 
				
			||||||
 | 
					import openApi from '@/common/openApi';
 | 
				
			||||||
 | 
					import { getFileUrl } from '@/common/request';
 | 
				
			||||||
 | 
					import { formatByteSize } from '@/common/utils/format';
 | 
				
			||||||
 | 
					const props = defineProps({
 | 
				
			||||||
 | 
					    fileKey: {
 | 
				
			||||||
 | 
					        type: String,
 | 
				
			||||||
 | 
					        required: true,
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    files: {
 | 
				
			||||||
 | 
					        type: [Array],
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    canDownload: {
 | 
				
			||||||
 | 
					        type: Boolean,
 | 
				
			||||||
 | 
					        default: true,
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					onMounted(async () => {
 | 
				
			||||||
 | 
					    setFileInfo();
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					watch(
 | 
				
			||||||
 | 
					    () => props.fileKey,
 | 
				
			||||||
 | 
					    async (val) => {
 | 
				
			||||||
 | 
					        if (val) {
 | 
				
			||||||
 | 
					            setFileInfo();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const fileDetail: any = ref({});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const setFileInfo = async () => {
 | 
				
			||||||
 | 
					    if (!props.fileKey) {
 | 
				
			||||||
 | 
					        return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    if (props.files && props.files.length > 0) {
 | 
				
			||||||
 | 
					        const file: any = props.files.find((file: any) => {
 | 
				
			||||||
 | 
					            return file.fileKey === props.fileKey;
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					        fileDetail.value = file;
 | 
				
			||||||
 | 
					        return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const files = await openApi.getFileDetail([props.fileKey]);
 | 
				
			||||||
 | 
					    fileDetail.value = files?.[0];
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<style lang="scss"></style>
 | 
				
			||||||
@@ -252,7 +252,9 @@ const changeLanguage = (value: any) => {
 | 
				
			|||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const setEditorValue = (value: any) => {
 | 
					const setEditorValue = (value: any) => {
 | 
				
			||||||
    monacoEditorIns.getModel()?.setValue(value);
 | 
					    if (value) {
 | 
				
			||||||
 | 
					        monacoEditorIns.getModel()?.setValue(value);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -119,8 +119,8 @@ const open = (optionProps: MonacoEditorDialogProps) => {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    setTimeout(() => {
 | 
					    setTimeout(() => {
 | 
				
			||||||
        editorRef.value?.format();
 | 
					 | 
				
			||||||
        editorRef.value?.focus();
 | 
					        editorRef.value?.focus();
 | 
				
			||||||
 | 
					        editorRef.value?.format();
 | 
				
			||||||
    }, 300);
 | 
					    }, 300);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    state.dialogVisible = true;
 | 
					    state.dialogVisible = true;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -74,7 +74,7 @@
 | 
				
			|||||||
                                        trigger="click"
 | 
					                                        trigger="click"
 | 
				
			||||||
                                    >
 | 
					                                    >
 | 
				
			||||||
                                        <div v-for="(item, index) in tableColumns" :key="index">
 | 
					                                        <div v-for="(item, index) in tableColumns" :key="index">
 | 
				
			||||||
                                            <el-checkbox v-model="item.show" :label="item.label" :true-label="true" :false-label="false" />
 | 
					                                            <el-checkbox v-model="item.show" :label="item.label" :true-value="true" :false-value="false" />
 | 
				
			||||||
                                        </div>
 | 
					                                        </div>
 | 
				
			||||||
                                        <template #reference>
 | 
					                                        <template #reference>
 | 
				
			||||||
                                            <el-button icon="Operation" circle :size="props.size"></el-button>
 | 
					                                            <el-button icon="Operation" circle :size="props.size"></el-button>
 | 
				
			||||||
@@ -115,18 +115,18 @@
 | 
				
			|||||||
                        >
 | 
					                        >
 | 
				
			||||||
                            <!-- 插槽:预留功能 -->
 | 
					                            <!-- 插槽:预留功能 -->
 | 
				
			||||||
                            <template #default="scope" v-if="item.slot">
 | 
					                            <template #default="scope" v-if="item.slot">
 | 
				
			||||||
                                <slot :name="item.prop" :data="scope.row"></slot>
 | 
					                                <slot :name="item.slotName ? item.slotName : item.prop" :data="scope.row"></slot>
 | 
				
			||||||
                            </template>
 | 
					                            </template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                            <!-- 枚举类型使用tab展示 -->
 | 
					                            <!-- 枚举类型使用tab展示 -->
 | 
				
			||||||
                            <template #default="scope" v-else-if="item.type == 'tag'">
 | 
					                            <template #default="scope" v-else-if="item.type == 'tag'">
 | 
				
			||||||
                                <enum-tag :size="props.size" :enums="item.typeParam" :value="scope.row[item.prop]"></enum-tag>
 | 
					                                <enum-tag :size="props.size" :enums="item.typeParam" :value="item.getValueByData(scope.row)"></enum-tag>
 | 
				
			||||||
                            </template>
 | 
					                            </template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                            <template #default="scope" v-else>
 | 
					                            <template #default="scope" v-else>
 | 
				
			||||||
                                <!-- 配置了美化文本按钮以及文本内容大于指定长度,则显示美化按钮 -->
 | 
					                                <!-- 配置了美化文本按钮以及文本内容大于指定长度,则显示美化按钮 -->
 | 
				
			||||||
                                <el-popover
 | 
					                                <el-popover
 | 
				
			||||||
                                    v-if="item.isBeautify && scope.row[item.prop]?.length > 35"
 | 
					                                    v-if="item.isBeautify && item.getValueByData(scope.row)?.length > 35"
 | 
				
			||||||
                                    effect="light"
 | 
					                                    effect="light"
 | 
				
			||||||
                                    trigger="click"
 | 
					                                    trigger="click"
 | 
				
			||||||
                                    placement="top"
 | 
					                                    placement="top"
 | 
				
			||||||
@@ -137,7 +137,7 @@
 | 
				
			|||||||
                                    </template>
 | 
					                                    </template>
 | 
				
			||||||
                                    <template #reference>
 | 
					                                    <template #reference>
 | 
				
			||||||
                                        <el-link
 | 
					                                        <el-link
 | 
				
			||||||
                                            @click="formatText(scope.row[item.prop])"
 | 
					                                            @click="formatText(item.getValueByData(scope.row))"
 | 
				
			||||||
                                            :underline="false"
 | 
					                                            :underline="false"
 | 
				
			||||||
                                            type="success"
 | 
					                                            type="success"
 | 
				
			||||||
                                            icon="MagicStick"
 | 
					                                            icon="MagicStick"
 | 
				
			||||||
@@ -156,10 +156,10 @@
 | 
				
			|||||||
            <el-row v-if="props.pageable" class="mt20" type="flex" justify="end">
 | 
					            <el-row v-if="props.pageable" class="mt20" type="flex" justify="end">
 | 
				
			||||||
                <el-pagination
 | 
					                <el-pagination
 | 
				
			||||||
                    :small="props.size == 'small'"
 | 
					                    :small="props.size == 'small'"
 | 
				
			||||||
                    @current-change="handlePageNumChange"
 | 
					                    @current-change="pageNumChange"
 | 
				
			||||||
                    @size-change="handlePageSizeChange"
 | 
					                    @size-change="pageSizeChange"
 | 
				
			||||||
                    style="text-align: right"
 | 
					                    style="text-align: right"
 | 
				
			||||||
                    layout="prev, pager, next, total, sizes, jumper"
 | 
					                    layout="prev, pager, next, total, sizes"
 | 
				
			||||||
                    :total="total"
 | 
					                    :total="total"
 | 
				
			||||||
                    v-model:current-page="queryForm.pageNum"
 | 
					                    v-model:current-page="queryForm.pageNum"
 | 
				
			||||||
                    v-model:page-size="queryForm.pageSize"
 | 
					                    v-model:page-size="queryForm.pageSize"
 | 
				
			||||||
@@ -185,11 +185,11 @@ import SvgIcon from '@/components/svgIcon/index.vue';
 | 
				
			|||||||
import { usePageTable } from '@/hooks/usePageTable';
 | 
					import { usePageTable } from '@/hooks/usePageTable';
 | 
				
			||||||
import { ElTable } from 'element-plus';
 | 
					import { ElTable } from 'element-plus';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const emit = defineEmits(['update:queryForm', 'update:selectionData', 'pageChange']);
 | 
					const emit = defineEmits(['update:selectionData', 'pageSizeChange', 'pageNumChange']);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export interface PageTableProps {
 | 
					export interface PageTableProps {
 | 
				
			||||||
    size?: string;
 | 
					    size?: string;
 | 
				
			||||||
    pageApi: Api; // 请求表格数据的 api
 | 
					    pageApi?: Api; // 请求表格数据的 api
 | 
				
			||||||
    columns: TableColumn[]; // 列配置项  ==> 必传
 | 
					    columns: TableColumn[]; // 列配置项  ==> 必传
 | 
				
			||||||
    showSelection?: boolean;
 | 
					    showSelection?: boolean;
 | 
				
			||||||
    selectable?: (row: any) => boolean; // 是否可选
 | 
					    selectable?: (row: any) => boolean; // 是否可选
 | 
				
			||||||
@@ -257,7 +257,16 @@ const changeSimpleFormItem = (searchItem: SearchItem) => {
 | 
				
			|||||||
    nowSearchItem.value = searchItem;
 | 
					    nowSearchItem.value = searchItem;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const { tableData, total, loading, search, reset, getTableData, handlePageNumChange, handlePageSizeChange } = usePageTable(
 | 
					const pageSizeChange = (val: number) => {
 | 
				
			||||||
 | 
					    emit('pageSizeChange', val);
 | 
				
			||||||
 | 
					    handlePageSizeChange(val);
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					const pageNumChange = (val: number) => {
 | 
				
			||||||
 | 
					    emit('pageNumChange', val);
 | 
				
			||||||
 | 
					    handlePageNumChange(val);
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					let { tableData, total, loading, search, reset, getTableData, handlePageNumChange, handlePageSizeChange } = usePageTable(
 | 
				
			||||||
    props.pageable,
 | 
					    props.pageable,
 | 
				
			||||||
    props.pageApi,
 | 
					    props.pageApi,
 | 
				
			||||||
    queryForm,
 | 
					    queryForm,
 | 
				
			||||||
@@ -288,6 +297,13 @@ watch(isShowSearch, () => {
 | 
				
			|||||||
    calcuTableHeight();
 | 
					    calcuTableHeight();
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					watch(
 | 
				
			||||||
 | 
					    () => props.data,
 | 
				
			||||||
 | 
					    (newValue: any) => {
 | 
				
			||||||
 | 
					        tableData = newValue;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
onMounted(async () => {
 | 
					onMounted(async () => {
 | 
				
			||||||
    calcuTableHeight();
 | 
					    calcuTableHeight();
 | 
				
			||||||
    useEventListener(window, 'resize', calcuTableHeight);
 | 
					    useEventListener(window, 'resize', calcuTableHeight);
 | 
				
			||||||
@@ -346,6 +362,7 @@ defineExpose({
 | 
				
			|||||||
    tableRef: tableRef,
 | 
					    tableRef: tableRef,
 | 
				
			||||||
    search: getTableData,
 | 
					    search: getTableData,
 | 
				
			||||||
    getData,
 | 
					    getData,
 | 
				
			||||||
 | 
					    total,
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
</script>
 | 
					</script>
 | 
				
			||||||
<style scoped lang="scss">
 | 
					<style scoped lang="scss">
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,5 +1,6 @@
 | 
				
			|||||||
import EnumValue from '@/common/Enum';
 | 
					import EnumValue from '@/common/Enum';
 | 
				
			||||||
import { dateFormat } from '@/common/utils/date';
 | 
					import { formatDate } from '@/common/utils/format';
 | 
				
			||||||
 | 
					import { getValueByPath } from '@/common/utils/object';
 | 
				
			||||||
import { getTextWidth } from '@/common/utils/string';
 | 
					import { getTextWidth } from '@/common/utils/string';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export class TableColumn {
 | 
					export class TableColumn {
 | 
				
			||||||
@@ -29,10 +30,15 @@ export class TableColumn {
 | 
				
			|||||||
    minWidth: number | string;
 | 
					    minWidth: number | string;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * 是否插槽,是的话插槽名则为prop属性名
 | 
					     * 是否为插槽,若slotName为空则插槽名为prop属性名
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    slot: boolean = false;
 | 
					    slot: boolean = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * 插槽名,
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    slotName: string = '';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    showOverflowTooltip: boolean = true;
 | 
					    showOverflowTooltip: boolean = true;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    sortable: boolean = false;
 | 
					    sortable: boolean = false;
 | 
				
			||||||
@@ -87,7 +93,7 @@ export class TableColumn {
 | 
				
			|||||||
        if (this.formatFunc) {
 | 
					        if (this.formatFunc) {
 | 
				
			||||||
            return this.formatFunc(rowData, this.prop);
 | 
					            return this.formatFunc(rowData, this.prop);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        return rowData[this.prop];
 | 
					        return getValueByPath(rowData, this.prop);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    static new(prop: string, label: string): TableColumn {
 | 
					    static new(prop: string, label: string): TableColumn {
 | 
				
			||||||
@@ -144,8 +150,9 @@ export class TableColumn {
 | 
				
			|||||||
     * 标识该列为插槽
 | 
					     * 标识该列为插槽
 | 
				
			||||||
     * @returns this
 | 
					     * @returns this
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    isSlot(): TableColumn {
 | 
					    isSlot(slotName: string = ''): TableColumn {
 | 
				
			||||||
        this.slot = true;
 | 
					        this.slot = true;
 | 
				
			||||||
 | 
					        this.slotName = slotName;
 | 
				
			||||||
        return this;
 | 
					        return this;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -165,7 +172,7 @@ export class TableColumn {
 | 
				
			|||||||
     */
 | 
					     */
 | 
				
			||||||
    isTime(): TableColumn {
 | 
					    isTime(): TableColumn {
 | 
				
			||||||
        this.setFormatFunc((data: any, prop: string) => {
 | 
					        this.setFormatFunc((data: any, prop: string) => {
 | 
				
			||||||
            return dateFormat(data[prop]);
 | 
					            return formatDate(getValueByPath(data, prop));
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
        return this;
 | 
					        return this;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@@ -176,7 +183,7 @@ export class TableColumn {
 | 
				
			|||||||
     */
 | 
					     */
 | 
				
			||||||
    isEnum(enums: any): TableColumn {
 | 
					    isEnum(enums: any): TableColumn {
 | 
				
			||||||
        this.setFormatFunc((data: any, prop: string) => {
 | 
					        this.setFormatFunc((data: any, prop: string) => {
 | 
				
			||||||
            return EnumValue.getLabelByValue(enums, data[prop]);
 | 
					            return EnumValue.getLabelByValue(enums, getValueByPath(data, prop));
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
        return this;
 | 
					        return this;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@@ -218,7 +225,7 @@ export class TableColumn {
 | 
				
			|||||||
        // 获取该列中最长的数据(内容)
 | 
					        // 获取该列中最长的数据(内容)
 | 
				
			||||||
        for (let i = 0; i < tableData.length; i++) {
 | 
					        for (let i = 0; i < tableData.length; i++) {
 | 
				
			||||||
            let nowData = tableData[i];
 | 
					            let nowData = tableData[i];
 | 
				
			||||||
            let nowValue = nowData[prop];
 | 
					            let nowValue = getValueByPath(nowData, prop);
 | 
				
			||||||
            if (!nowValue) {
 | 
					            if (!nowValue) {
 | 
				
			||||||
                continue;
 | 
					                continue;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										505
									
								
								mayfly_go_web/src/components/terminal-rdp/MachineRdp.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										505
									
								
								mayfly_go_web/src/components/terminal-rdp/MachineRdp.vue
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,505 @@
 | 
				
			|||||||
 | 
					<template>
 | 
				
			||||||
 | 
					    <div>
 | 
				
			||||||
 | 
					        <div ref="viewportRef" class="viewport" :style="{ width: state.size.width + 'px', height: state.size.height + 'px' }">
 | 
				
			||||||
 | 
					            <div ref="displayRef" class="display" tabindex="0" />
 | 
				
			||||||
 | 
					            <div class="btn-box">
 | 
				
			||||||
 | 
					                <SvgIcon name="DocumentCopy" @click="openPaste" :size="20" class="pointer-icon mr10" title="剪贴板" />
 | 
				
			||||||
 | 
					                <SvgIcon name="FolderOpened" @click="openFilesystem" :size="20" class="pointer-icon mr10" title="文件管理" />
 | 
				
			||||||
 | 
					                <SvgIcon name="FullScreen" @click="state.fullscreen ? closeFullScreen() : openFullScreen()" :size="20" class="pointer-icon mr10" title="全屏" />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                <el-dropdown>
 | 
				
			||||||
 | 
					                    <SvgIcon name="Monitor" :size="20" class="pointer-icon mr10" title="发送快捷键" style="color: #fff" />
 | 
				
			||||||
 | 
					                    <template #dropdown>
 | 
				
			||||||
 | 
					                        <el-dropdown-menu>
 | 
				
			||||||
 | 
					                            <el-dropdown-item @click="openSendKeyboard(['65507', '65513', '65535'])"> Ctrl + Alt + Delete </el-dropdown-item>
 | 
				
			||||||
 | 
					                            <el-dropdown-item @click="openSendKeyboard(['65507', '65513', '65288'])"> Ctrl + Alt + Backspace </el-dropdown-item>
 | 
				
			||||||
 | 
					                            <el-dropdown-item @click="openSendKeyboard(['65515', '100'])"> Windows + D </el-dropdown-item>
 | 
				
			||||||
 | 
					                            <el-dropdown-item @click="openSendKeyboard(['65515', '101'])"> Windows + E </el-dropdown-item>
 | 
				
			||||||
 | 
					                            <el-dropdown-item @click="openSendKeyboard(['65515', '114'])"> Windows + R </el-dropdown-item>
 | 
				
			||||||
 | 
					                            <el-dropdown-item @click="openSendKeyboard(['65515'])"> Windows </el-dropdown-item>
 | 
				
			||||||
 | 
					                        </el-dropdown-menu>
 | 
				
			||||||
 | 
					                    </template>
 | 
				
			||||||
 | 
					                </el-dropdown>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                <SvgIcon name="Refresh" @click="connect(0, 0)" :size="20" class="pointer-icon mr10" title="重新连接" />
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					            <clipboard-dialog ref="clipboardRef" v-model:visible="state.clipboardDialog.visible" @close="closePaste" @submit="onsubmitClipboard" />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            <el-dialog
 | 
				
			||||||
 | 
					                v-if="!state.fullscreen"
 | 
				
			||||||
 | 
					                destroy-on-close
 | 
				
			||||||
 | 
					                :title="state.filesystemDialog.title"
 | 
				
			||||||
 | 
					                v-model="state.filesystemDialog.visible"
 | 
				
			||||||
 | 
					                :close-on-click-modal="false"
 | 
				
			||||||
 | 
					                width="70%"
 | 
				
			||||||
 | 
					            >
 | 
				
			||||||
 | 
					                <machine-file
 | 
				
			||||||
 | 
					                    :machine-id="state.filesystemDialog.machineId"
 | 
				
			||||||
 | 
					                    :auth-cert-name="state.filesystemDialog.authCertName"
 | 
				
			||||||
 | 
					                    :protocol="state.filesystemDialog.protocol"
 | 
				
			||||||
 | 
					                    :file-id="state.filesystemDialog.fileId"
 | 
				
			||||||
 | 
					                    :path="state.filesystemDialog.path"
 | 
				
			||||||
 | 
					                />
 | 
				
			||||||
 | 
					            </el-dialog>
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        <el-dialog
 | 
				
			||||||
 | 
					            v-if="!state.fullscreen"
 | 
				
			||||||
 | 
					            destroy-on-close
 | 
				
			||||||
 | 
					            :title="state.filesystemDialog.title"
 | 
				
			||||||
 | 
					            v-model="state.filesystemDialog.visible"
 | 
				
			||||||
 | 
					            :close-on-click-modal="false"
 | 
				
			||||||
 | 
					            width="70%"
 | 
				
			||||||
 | 
					        >
 | 
				
			||||||
 | 
					            <machine-file
 | 
				
			||||||
 | 
					                :machine-id="state.filesystemDialog.machineId"
 | 
				
			||||||
 | 
					                :auth-cert-name="state.filesystemDialog.authCertName"
 | 
				
			||||||
 | 
					                :protocol="state.filesystemDialog.protocol"
 | 
				
			||||||
 | 
					                :file-id="state.filesystemDialog.fileId"
 | 
				
			||||||
 | 
					                :path="state.filesystemDialog.path"
 | 
				
			||||||
 | 
					            />
 | 
				
			||||||
 | 
					        </el-dialog>
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<script lang="ts" setup>
 | 
				
			||||||
 | 
					import Guacamole from './guac/guacamole-common';
 | 
				
			||||||
 | 
					import { getMachineRdpSocketUrl } from '@/views/ops/machine/api';
 | 
				
			||||||
 | 
					import clipboard from './guac/clipboard';
 | 
				
			||||||
 | 
					import { reactive, ref } from 'vue';
 | 
				
			||||||
 | 
					import { TerminalStatus } from '@/components/terminal/common';
 | 
				
			||||||
 | 
					import ClipboardDialog from '@/components/terminal-rdp/guac/ClipboardDialog.vue';
 | 
				
			||||||
 | 
					import { TerminalExpose } from '@/components/terminal-rdp/index';
 | 
				
			||||||
 | 
					import SvgIcon from '@/components/svgIcon/index.vue';
 | 
				
			||||||
 | 
					import MachineFile from '@/views/ops/machine/file/MachineFile.vue';
 | 
				
			||||||
 | 
					import { exitFullscreen, launchIntoFullscreen, unWatchFullscreenChange, watchFullscreenChange } from '@/components/terminal-rdp/guac/screen';
 | 
				
			||||||
 | 
					import { useEventListener } from '@vueuse/core';
 | 
				
			||||||
 | 
					import { debounce } from 'lodash';
 | 
				
			||||||
 | 
					import { ClientState, TunnelState } from '@/components/terminal-rdp/guac/states';
 | 
				
			||||||
 | 
					import { ElMessage } from 'element-plus';
 | 
				
			||||||
 | 
					import { joinClientParams } from '@/common/request';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const viewportRef = ref({} as any);
 | 
				
			||||||
 | 
					const displayRef = ref({} as any);
 | 
				
			||||||
 | 
					const clipboardRef = ref({} as any);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const props = defineProps({
 | 
				
			||||||
 | 
					    machineId: {
 | 
				
			||||||
 | 
					        type: Number,
 | 
				
			||||||
 | 
					        required: true,
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    authCert: {
 | 
				
			||||||
 | 
					        type: String,
 | 
				
			||||||
 | 
					        required: true,
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    clipboardList: {
 | 
				
			||||||
 | 
					        type: Array,
 | 
				
			||||||
 | 
					        default: () => [],
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const emit = defineEmits(['statusChange']);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const state = reactive({
 | 
				
			||||||
 | 
					    client: null as any,
 | 
				
			||||||
 | 
					    display: null as any,
 | 
				
			||||||
 | 
					    displayElm: {} as any,
 | 
				
			||||||
 | 
					    clipboard: {} as any,
 | 
				
			||||||
 | 
					    keyboard: {} as any,
 | 
				
			||||||
 | 
					    mouse: null as any,
 | 
				
			||||||
 | 
					    touchpad: null as any,
 | 
				
			||||||
 | 
					    errorMessage: '',
 | 
				
			||||||
 | 
					    arguments: {} as any,
 | 
				
			||||||
 | 
					    status: TerminalStatus.NoConnected,
 | 
				
			||||||
 | 
					    size: {
 | 
				
			||||||
 | 
					        height: 710,
 | 
				
			||||||
 | 
					        width: 1024,
 | 
				
			||||||
 | 
					        force: false,
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    enableClipboard: true,
 | 
				
			||||||
 | 
					    clipboardDialog: {
 | 
				
			||||||
 | 
					        visible: false,
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    filesystemDialog: {
 | 
				
			||||||
 | 
					        visible: false,
 | 
				
			||||||
 | 
					        authCertName: '',
 | 
				
			||||||
 | 
					        machineId: 0,
 | 
				
			||||||
 | 
					        protocol: 1,
 | 
				
			||||||
 | 
					        title: '',
 | 
				
			||||||
 | 
					        fileId: 0,
 | 
				
			||||||
 | 
					        path: '',
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    fullscreen: false,
 | 
				
			||||||
 | 
					    beforeFullSize: {
 | 
				
			||||||
 | 
					        height: 710,
 | 
				
			||||||
 | 
					        width: 1024,
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const installKeyboard = () => {
 | 
				
			||||||
 | 
					    state.keyboard = new Guacamole.Keyboard(state.displayElm);
 | 
				
			||||||
 | 
					    uninstallKeyboard();
 | 
				
			||||||
 | 
					    state.keyboard.onkeydown = (keysym: any) => {
 | 
				
			||||||
 | 
					        state.client.sendKeyEvent(1, keysym);
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					    state.keyboard.onkeyup = (keysym: any) => {
 | 
				
			||||||
 | 
					        state.client.sendKeyEvent(0, keysym);
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					const uninstallKeyboard = () => {
 | 
				
			||||||
 | 
					    state.keyboard!.onkeydown = state.keyboard!.onkeyup = () => {};
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const installMouse = () => {
 | 
				
			||||||
 | 
					    state.mouse = new Guacamole.Mouse(state.displayElm);
 | 
				
			||||||
 | 
					    // Hide software cursor when mouse leaves display
 | 
				
			||||||
 | 
					    state.mouse.onmouseout = () => {
 | 
				
			||||||
 | 
					        if (!state.display) return;
 | 
				
			||||||
 | 
					        state.display.showCursor(false);
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					    state.mouse.onmousedown = state.mouse.onmouseup = state.mouse.onmousemove = handleMouseState;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const installTouchpad = () => {
 | 
				
			||||||
 | 
					    state.touchpad = new Guacamole.Mouse.Touchpad(state.displayElm);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    state.touchpad.onmousedown =
 | 
				
			||||||
 | 
					        state.touchpad.onmouseup =
 | 
				
			||||||
 | 
					        state.touchpad.onmousemove =
 | 
				
			||||||
 | 
					            (st: any) => {
 | 
				
			||||||
 | 
					                // 记录按下时,光标所在位置
 | 
				
			||||||
 | 
					                console.log(st);
 | 
				
			||||||
 | 
					                handleMouseState(st, true);
 | 
				
			||||||
 | 
					            };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // 记录单指按压时候手在屏幕的位置
 | 
				
			||||||
 | 
					    state.displayElm.ontouchend = (event: TouchEvent) => {
 | 
				
			||||||
 | 
					        console.log('end', event);
 | 
				
			||||||
 | 
					        state.displayElm.ontouchend = () => {};
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const setClipboard = (data: string) => {
 | 
				
			||||||
 | 
					    clipboardRef.value.setValue(data);
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const installClipboard = () => {
 | 
				
			||||||
 | 
					    state.enableClipboard = clipboard.install(state.client) as any;
 | 
				
			||||||
 | 
					    clipboard.installWatcher(props.clipboardList, setClipboard);
 | 
				
			||||||
 | 
					    state.client.onclipboard = clipboard.onClipboard;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const installResize = () => {
 | 
				
			||||||
 | 
					    // 在resize事件结束后300毫秒执行
 | 
				
			||||||
 | 
					    useEventListener('resize', debounce(resize, 300));
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const installDisplay = () => {
 | 
				
			||||||
 | 
					    let { width, height, force } = state.size;
 | 
				
			||||||
 | 
					    state.display = state.client.getDisplay();
 | 
				
			||||||
 | 
					    const displayElm = displayRef.value;
 | 
				
			||||||
 | 
					    displayElm.appendChild(state.display.getElement());
 | 
				
			||||||
 | 
					    displayElm.addEventListener('contextmenu', (e: any) => {
 | 
				
			||||||
 | 
					        e.stopPropagation();
 | 
				
			||||||
 | 
					        if (e.preventDefault) {
 | 
				
			||||||
 | 
					            e.preventDefault();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        e.returnValue = false;
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    state.client.connect('width=' + width + '&height=' + height + '&force=' + force + '&' + joinClientParams());
 | 
				
			||||||
 | 
					    window.onunload = () => state.client.disconnect();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // allows focusing on the display div so that keyboard doesn't always go to session
 | 
				
			||||||
 | 
					    displayElm.onclick = () => {
 | 
				
			||||||
 | 
					        displayElm.focus();
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					    displayElm.onfocus = () => {
 | 
				
			||||||
 | 
					        displayElm.className = 'focus';
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					    displayElm.onblur = () => {
 | 
				
			||||||
 | 
					        displayElm.className = '';
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    state.displayElm = displayElm;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const installClient = () => {
 | 
				
			||||||
 | 
					    let tunnel = new Guacamole.WebSocketTunnel(getMachineRdpSocketUrl(props.authCert)) as any;
 | 
				
			||||||
 | 
					    if (state.client) {
 | 
				
			||||||
 | 
					        state.display?.scale(0);
 | 
				
			||||||
 | 
					        uninstallKeyboard();
 | 
				
			||||||
 | 
					        state.client.disconnect();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    state.client = new Guacamole.Client(tunnel);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    tunnel.onerror = (status: any) => {
 | 
				
			||||||
 | 
					        // eslint-disable-next-line no-console
 | 
				
			||||||
 | 
					        console.error(`Tunnel failed ${JSON.stringify(status)}`);
 | 
				
			||||||
 | 
					        // state.connectionState = states.TUNNEL_ERROR;
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    tunnel.onstatechange = (st: any) => {
 | 
				
			||||||
 | 
					        console.log('statechange', st);
 | 
				
			||||||
 | 
					        state.status = st;
 | 
				
			||||||
 | 
					        switch (st) {
 | 
				
			||||||
 | 
					            case TunnelState.CONNECTING: // 'CONNECTING'
 | 
				
			||||||
 | 
					                break;
 | 
				
			||||||
 | 
					            case TunnelState.OPEN: // 'OPEN'
 | 
				
			||||||
 | 
					                state.status = TerminalStatus.Connected;
 | 
				
			||||||
 | 
					                emit('statusChange', TerminalStatus.Connected);
 | 
				
			||||||
 | 
					                break;
 | 
				
			||||||
 | 
					            case TunnelState.CLOSED: // 'CLOSED'
 | 
				
			||||||
 | 
					                state.status = TerminalStatus.Disconnected;
 | 
				
			||||||
 | 
					                emit('statusChange', TerminalStatus.Disconnected);
 | 
				
			||||||
 | 
					                break;
 | 
				
			||||||
 | 
					            case TunnelState.UNSTABLE: // 'UNSTABLE'
 | 
				
			||||||
 | 
					                state.status = TerminalStatus.Error;
 | 
				
			||||||
 | 
					                emit('statusChange', TerminalStatus.Error);
 | 
				
			||||||
 | 
					                break;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    state.client.onstatechange = (clientState: any) => {
 | 
				
			||||||
 | 
					        console.log('clientState', clientState);
 | 
				
			||||||
 | 
					        switch (clientState) {
 | 
				
			||||||
 | 
					            case ClientState.IDLE:
 | 
				
			||||||
 | 
					                console.log('连接空闲');
 | 
				
			||||||
 | 
					                break;
 | 
				
			||||||
 | 
					            case ClientState.CONNECTING:
 | 
				
			||||||
 | 
					                console.log('连接中...');
 | 
				
			||||||
 | 
					                break;
 | 
				
			||||||
 | 
					            case ClientState.WAITING:
 | 
				
			||||||
 | 
					                console.log('等待服务器响应...');
 | 
				
			||||||
 | 
					                break;
 | 
				
			||||||
 | 
					            case ClientState.CONNECTED:
 | 
				
			||||||
 | 
					                console.log('连接成功...');
 | 
				
			||||||
 | 
					                break;
 | 
				
			||||||
 | 
					            // eslint-disable-next-line no-fallthrough
 | 
				
			||||||
 | 
					            case ClientState.DISCONNECTING:
 | 
				
			||||||
 | 
					                console.log('断开连接中...');
 | 
				
			||||||
 | 
					                break;
 | 
				
			||||||
 | 
					            case ClientState.DISCONNECTED:
 | 
				
			||||||
 | 
					                console.log('已断开连接...');
 | 
				
			||||||
 | 
					                break;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    state.client.onerror = (error: any) => {
 | 
				
			||||||
 | 
					        state.client.disconnect();
 | 
				
			||||||
 | 
					        console.error(`Client error ${JSON.stringify(error)}`);
 | 
				
			||||||
 | 
					        state.errorMessage = error.message;
 | 
				
			||||||
 | 
					        // state.connectionState = states.CLIENT_ERROR;
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    state.client.onsync = () => {};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    state.client.onargv = (stream: any, mimetype: any, name: any) => {
 | 
				
			||||||
 | 
					        if (mimetype !== 'text/plain') return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const reader = new Guacamole.StringReader(stream);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Assemble received data into a single string
 | 
				
			||||||
 | 
					        let value = '';
 | 
				
			||||||
 | 
					        reader.ontext = (text: any) => {
 | 
				
			||||||
 | 
					            value += text;
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Test mutability once stream is finished, storing the current value for the argument only if it is mutable
 | 
				
			||||||
 | 
					        reader.onend = () => {
 | 
				
			||||||
 | 
					            const stream = state.client.createArgumentValueStream('text/plain', name);
 | 
				
			||||||
 | 
					            stream.onack = (status: any) => {
 | 
				
			||||||
 | 
					                if (status.isError()) {
 | 
				
			||||||
 | 
					                    // ignore reject
 | 
				
			||||||
 | 
					                    return;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                state.arguments[name] = value;
 | 
				
			||||||
 | 
					            };
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const resize = () => {
 | 
				
			||||||
 | 
					    const elm = viewportRef.value;
 | 
				
			||||||
 | 
					    if (!elm || !elm.offsetWidth) {
 | 
				
			||||||
 | 
					        // resize is being called on the hidden window
 | 
				
			||||||
 | 
					        return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let box = elm.parentElement;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    state.size.width = box.clientWidth;
 | 
				
			||||||
 | 
					    state.size.height = box.clientHeight;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const width = parseInt(String(box.clientWidth));
 | 
				
			||||||
 | 
					    const height = parseInt(String(box.clientHeight));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (state.display.getWidth() !== width || state.display.getHeight() !== height) {
 | 
				
			||||||
 | 
					        if (state.status !== TerminalStatus.Connected) {
 | 
				
			||||||
 | 
					            connect(width, height);
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            state.client.sendSize(width, height);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    // setting timeout so display has time to get the correct size
 | 
				
			||||||
 | 
					    // setTimeout(() => {
 | 
				
			||||||
 | 
					    //     const scale = Math.min(box.clientWidth / Math.max(state.display.getWidth(), 1), box.clientHeight / Math.max(state.display.getHeight(), 1));
 | 
				
			||||||
 | 
					    //     state.display.scale(scale);
 | 
				
			||||||
 | 
					    //     console.log(state.size, scale);
 | 
				
			||||||
 | 
					    // }, 100);
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const handleMouseState = (mouseState: any, showCursor = false) => {
 | 
				
			||||||
 | 
					    state.client.getDisplay().showCursor(showCursor);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const scaledMouseState = Object.assign({}, mouseState, {
 | 
				
			||||||
 | 
					        x: mouseState.x / state.display.getScale(),
 | 
				
			||||||
 | 
					        y: mouseState.y / state.display.getScale(),
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    state.client.sendMouseState(scaledMouseState);
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const connect = (width: number, height: number, force = false) => {
 | 
				
			||||||
 | 
					    if (!width && !height) {
 | 
				
			||||||
 | 
					        if (state.size && state.size.width && state.size.height) {
 | 
				
			||||||
 | 
					            width = state.size.width;
 | 
				
			||||||
 | 
					            height = state.size.height;
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            // 获取当前viewportRef宽高
 | 
				
			||||||
 | 
					            width = viewportRef.value.clientWidth;
 | 
				
			||||||
 | 
					            height = viewportRef.value.clientHeight;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    state.size = { width, height, force };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    installClient();
 | 
				
			||||||
 | 
					    installDisplay();
 | 
				
			||||||
 | 
					    installKeyboard();
 | 
				
			||||||
 | 
					    installMouse();
 | 
				
			||||||
 | 
					    installTouchpad();
 | 
				
			||||||
 | 
					    installClipboard();
 | 
				
			||||||
 | 
					    installResize();
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const disconnect = () => {
 | 
				
			||||||
 | 
					    uninstallKeyboard();
 | 
				
			||||||
 | 
					    state.client?.disconnect();
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const blur = () => {
 | 
				
			||||||
 | 
					    uninstallKeyboard();
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const focus = () => {};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const openPaste = async () => {
 | 
				
			||||||
 | 
					    state.clipboardDialog.visible = true;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const closePaste = async () => {
 | 
				
			||||||
 | 
					    installKeyboard();
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const onsubmitClipboard = (val: string) => {
 | 
				
			||||||
 | 
					    state.clipboardDialog.visible = false;
 | 
				
			||||||
 | 
					    installKeyboard();
 | 
				
			||||||
 | 
					    clipboard.sendRemoteClipboard(state.client, val);
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const openFilesystem = async () => {
 | 
				
			||||||
 | 
					    state.filesystemDialog.protocol = 2;
 | 
				
			||||||
 | 
					    state.filesystemDialog.machineId = props.machineId;
 | 
				
			||||||
 | 
					    state.filesystemDialog.authCertName = props.authCert;
 | 
				
			||||||
 | 
					    state.filesystemDialog.fileId = props.machineId;
 | 
				
			||||||
 | 
					    state.filesystemDialog.path = '/';
 | 
				
			||||||
 | 
					    state.filesystemDialog.title = `远程桌面文件管理`;
 | 
				
			||||||
 | 
					    state.filesystemDialog.visible = true;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const openFullScreen = function () {
 | 
				
			||||||
 | 
					    launchIntoFullscreen(viewportRef.value);
 | 
				
			||||||
 | 
					    state.fullscreen = true;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // 记录原始尺寸
 | 
				
			||||||
 | 
					    state.beforeFullSize = {
 | 
				
			||||||
 | 
					        width: state.size.width,
 | 
				
			||||||
 | 
					        height: state.size.height,
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // 使用新的宽高重新连接
 | 
				
			||||||
 | 
					    setTimeout(() => {
 | 
				
			||||||
 | 
					        connect(viewportRef.value.clientWidth, viewportRef.value.clientHeight, false);
 | 
				
			||||||
 | 
					    }, 500);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    watchFullscreenChange(watchFullscreen);
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function watchFullscreen(event: Event, isFull: boolean) {
 | 
				
			||||||
 | 
					    if (!isFull) {
 | 
				
			||||||
 | 
					        closeFullScreen();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const closeFullScreen = function () {
 | 
				
			||||||
 | 
					    exitFullscreen();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    state.fullscreen = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // 使用新的宽高重新连接
 | 
				
			||||||
 | 
					    setTimeout(() => {
 | 
				
			||||||
 | 
					        connect(state.beforeFullSize.width, state.beforeFullSize.height, false);
 | 
				
			||||||
 | 
					    }, 500);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // 取消注册esc事件,退出全屏
 | 
				
			||||||
 | 
					    unWatchFullscreenChange(watchFullscreen);
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const openSendKeyboard = (keys: string[]) => {
 | 
				
			||||||
 | 
					    if (!state.client) {
 | 
				
			||||||
 | 
					        return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    for (let i = 0; i < keys.length; i++) {
 | 
				
			||||||
 | 
					        state.client.sendKeyEvent(1, keys[i]);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    for (let j = 0; j < keys.length; j++) {
 | 
				
			||||||
 | 
					        state.client.sendKeyEvent(0, keys[j]);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    ElMessage.success('发送组合键成功');
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const exposes = {
 | 
				
			||||||
 | 
					    connect,
 | 
				
			||||||
 | 
					    disconnect,
 | 
				
			||||||
 | 
					    init: connect,
 | 
				
			||||||
 | 
					    close: disconnect,
 | 
				
			||||||
 | 
					    fitTerminal: resize,
 | 
				
			||||||
 | 
					    focus,
 | 
				
			||||||
 | 
					    blur,
 | 
				
			||||||
 | 
					    setRemoteClipboard: onsubmitClipboard,
 | 
				
			||||||
 | 
					} as TerminalExpose;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					defineExpose(exposes);
 | 
				
			||||||
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<style lang="scss">
 | 
				
			||||||
 | 
					.viewport {
 | 
				
			||||||
 | 
					    position: relative;
 | 
				
			||||||
 | 
					    width: 1024px;
 | 
				
			||||||
 | 
					    min-height: 710px;
 | 
				
			||||||
 | 
					    z-index: 1;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					.display {
 | 
				
			||||||
 | 
					    overflow: hidden;
 | 
				
			||||||
 | 
					    width: 100%;
 | 
				
			||||||
 | 
					    height: 100%;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					.btn-box {
 | 
				
			||||||
 | 
					    position: absolute;
 | 
				
			||||||
 | 
					    top: 20px;
 | 
				
			||||||
 | 
					    right: 30px;
 | 
				
			||||||
 | 
					    padding: 5px 0 5px 10px;
 | 
				
			||||||
 | 
					    background: #dddddd4a;
 | 
				
			||||||
 | 
					    color: #fff;
 | 
				
			||||||
 | 
					    border-radius: 3px;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					</style>
 | 
				
			||||||
							
								
								
									
										137
									
								
								mayfly_go_web/src/components/terminal-rdp/MachineRdpDialog.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										137
									
								
								mayfly_go_web/src/components/terminal-rdp/MachineRdpDialog.vue
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,137 @@
 | 
				
			|||||||
 | 
					<template>
 | 
				
			||||||
 | 
					    <div class="rdpDialog" ref="dialogRef">
 | 
				
			||||||
 | 
					        <el-dialog
 | 
				
			||||||
 | 
					            v-model="dialogVisible"
 | 
				
			||||||
 | 
					            :before-close="handleClose"
 | 
				
			||||||
 | 
					            :close-on-click-modal="false"
 | 
				
			||||||
 | 
					            :destroy-on-close="true"
 | 
				
			||||||
 | 
					            :close-on-press-escape="false"
 | 
				
			||||||
 | 
					            :show-close="false"
 | 
				
			||||||
 | 
					            width="1024"
 | 
				
			||||||
 | 
					            @open="connect()"
 | 
				
			||||||
 | 
					        >
 | 
				
			||||||
 | 
					            <template #header>
 | 
				
			||||||
 | 
					                <div class="terminal-title-wrapper">
 | 
				
			||||||
 | 
					                    <!-- 左侧 -->
 | 
				
			||||||
 | 
					                    <div class="title-left-fixed">
 | 
				
			||||||
 | 
					                        <!-- title信息 -->
 | 
				
			||||||
 | 
					                        <div>
 | 
				
			||||||
 | 
					                            {{ title }}
 | 
				
			||||||
 | 
					                        </div>
 | 
				
			||||||
 | 
					                    </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    <!-- 右侧 -->
 | 
				
			||||||
 | 
					                    <div class="title-right-fixed">
 | 
				
			||||||
 | 
					                        <el-popconfirm @confirm="connect(true)" title="确认重新连接?">
 | 
				
			||||||
 | 
					                            <template #reference>
 | 
				
			||||||
 | 
					                                <div class="mr10 pointer">
 | 
				
			||||||
 | 
					                                    <el-tag v-if="state.status == TerminalStatus.Connected" type="success" effect="light" round> 已连接 </el-tag>
 | 
				
			||||||
 | 
					                                    <el-tag v-else type="danger" effect="light" round> 未连接,点击重连 </el-tag>
 | 
				
			||||||
 | 
					                                </div>
 | 
				
			||||||
 | 
					                            </template>
 | 
				
			||||||
 | 
					                        </el-popconfirm>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                        <el-popconfirm @confirm="handleClose" title="确认关闭?">
 | 
				
			||||||
 | 
					                            <template #reference>
 | 
				
			||||||
 | 
					                                <SvgIcon name="Close" class="pointer-icon" title="关闭" :size="20" />
 | 
				
			||||||
 | 
					                            </template>
 | 
				
			||||||
 | 
					                        </el-popconfirm>
 | 
				
			||||||
 | 
					                    </div>
 | 
				
			||||||
 | 
					                </div>
 | 
				
			||||||
 | 
					            </template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            <machine-rdp ref="rdpRef" :machine-id="machineId" :auth-cert="authCert" @status-change="handleStatusChange" />
 | 
				
			||||||
 | 
					        </el-dialog>
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<script lang="ts" setup>
 | 
				
			||||||
 | 
					import { reactive, ref, toRefs, watch } from 'vue';
 | 
				
			||||||
 | 
					import MachineRdp from '@/components/terminal-rdp/MachineRdp.vue';
 | 
				
			||||||
 | 
					import { TerminalStatus } from '@/components/terminal/common';
 | 
				
			||||||
 | 
					import SvgIcon from '@/components/svgIcon/index.vue';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const rdpRef = ref({} as any);
 | 
				
			||||||
 | 
					const dialogRef = ref({} as any);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const props = defineProps({
 | 
				
			||||||
 | 
					    visible: { type: Boolean },
 | 
				
			||||||
 | 
					    machineId: {
 | 
				
			||||||
 | 
					        type: Number,
 | 
				
			||||||
 | 
					        required: true,
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    authCert: {
 | 
				
			||||||
 | 
					        type: String,
 | 
				
			||||||
 | 
					        required: true,
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    title: { type: String },
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const emit = defineEmits(['update:visible', 'cancel', 'update:machineId']);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const state = reactive({
 | 
				
			||||||
 | 
					    dialogVisible: false,
 | 
				
			||||||
 | 
					    title: '',
 | 
				
			||||||
 | 
					    status: TerminalStatus.NoConnected,
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const { dialogVisible } = toRefs(state);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					watch(props, async (newValue: any) => {
 | 
				
			||||||
 | 
					    const visible = newValue.visible;
 | 
				
			||||||
 | 
					    state.dialogVisible = visible;
 | 
				
			||||||
 | 
					    if (visible) {
 | 
				
			||||||
 | 
					        state.title = newValue.title;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const connect = (force = false) => {
 | 
				
			||||||
 | 
					    rdpRef.value?.disconnect();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let width = 1024;
 | 
				
			||||||
 | 
					    let height = 710;
 | 
				
			||||||
 | 
					    rdpRef.value?.connect(width, height, force);
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const handleStatusChange = (status: TerminalStatus) => {
 | 
				
			||||||
 | 
					    state.status = status;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * 关闭取消按钮触发的事件
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					const handleClose = () => {
 | 
				
			||||||
 | 
					    emit('update:visible', false);
 | 
				
			||||||
 | 
					    emit('update:machineId', null);
 | 
				
			||||||
 | 
					    emit('cancel');
 | 
				
			||||||
 | 
					    rdpRef.value?.disconnect();
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					</script>
 | 
				
			||||||
 | 
					<style lang="scss">
 | 
				
			||||||
 | 
					.rdpDialog {
 | 
				
			||||||
 | 
					    .el-dialog {
 | 
				
			||||||
 | 
					        padding: 0;
 | 
				
			||||||
 | 
					        .el-dialog__header {
 | 
				
			||||||
 | 
					            padding: 10px;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    .el-overlay .el-overlay-dialog .el-dialog .el-dialog__body {
 | 
				
			||||||
 | 
					        max-height: 100% !important;
 | 
				
			||||||
 | 
					        padding: 0 !important;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    .terminal-title-wrapper {
 | 
				
			||||||
 | 
					        display: flex;
 | 
				
			||||||
 | 
					        justify-content: space-between;
 | 
				
			||||||
 | 
					        font-size: 16px;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        .title-right-fixed {
 | 
				
			||||||
 | 
					            display: flex;
 | 
				
			||||||
 | 
					            align-items: center;
 | 
				
			||||||
 | 
					            font-size: 20px;
 | 
				
			||||||
 | 
					            text-align: end;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					</style>
 | 
				
			||||||
@@ -0,0 +1,66 @@
 | 
				
			|||||||
 | 
					<template>
 | 
				
			||||||
 | 
					    <div class="clipboard-dialog">
 | 
				
			||||||
 | 
					        <el-dialog
 | 
				
			||||||
 | 
					            v-model="dialogVisible"
 | 
				
			||||||
 | 
					            title="请输入需要粘贴的文本"
 | 
				
			||||||
 | 
					            :before-close="onclose"
 | 
				
			||||||
 | 
					            :close-on-click-modal="false"
 | 
				
			||||||
 | 
					            :close-on-press-escape="false"
 | 
				
			||||||
 | 
					            width="600"
 | 
				
			||||||
 | 
					        >
 | 
				
			||||||
 | 
					            <el-input v-model="state.modelValue" type="textarea" :rows="20" />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            <template #footer>
 | 
				
			||||||
 | 
					                <el-button type="primary" @click="onsubmit">确 定</el-button>
 | 
				
			||||||
 | 
					            </template>
 | 
				
			||||||
 | 
					        </el-dialog>
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<script setup lang="ts">
 | 
				
			||||||
 | 
					import { reactive, toRefs, watch } from 'vue';
 | 
				
			||||||
 | 
					import { ElMessage } from 'element-plus';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const props = defineProps({
 | 
				
			||||||
 | 
					    visible: { type: Boolean },
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const emits = defineEmits(['submit', 'close', 'update:visible']);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const state = reactive({
 | 
				
			||||||
 | 
					    dialogVisible: false,
 | 
				
			||||||
 | 
					    modelValue: '',
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const { dialogVisible } = toRefs(state);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					watch(props, async (newValue: any) => {
 | 
				
			||||||
 | 
					    state.dialogVisible = newValue.visible;
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const onclose = () => {
 | 
				
			||||||
 | 
					    emits('update:visible', false);
 | 
				
			||||||
 | 
					    emits('close');
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const onsubmit = () => {
 | 
				
			||||||
 | 
					    state.dialogVisible = false;
 | 
				
			||||||
 | 
					    if (state.modelValue) {
 | 
				
			||||||
 | 
					        ElMessage.success('发送剪贴板数据成功');
 | 
				
			||||||
 | 
					        emits('submit', state.modelValue);
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					        ElMessage.warning('请输入需要粘贴的文本');
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const setValue = (val: string) => {
 | 
				
			||||||
 | 
					    state.modelValue = val;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					defineExpose({ setValue });
 | 
				
			||||||
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<style lang="scss">
 | 
				
			||||||
 | 
					.clipboard-dialog {
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					</style>
 | 
				
			||||||
							
								
								
									
										147
									
								
								mayfly_go_web/src/components/terminal-rdp/guac/clipboard.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										147
									
								
								mayfly_go_web/src/components/terminal-rdp/guac/clipboard.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,147 @@
 | 
				
			|||||||
 | 
					import Guacamole from './guacamole-common';
 | 
				
			||||||
 | 
					import { ElMessage } from 'element-plus';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const clipboard = {};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					clipboard.install = (client) => {
 | 
				
			||||||
 | 
					    if (!navigator.clipboard) {
 | 
				
			||||||
 | 
					        return false;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    clipboard.getLocalClipboard().then((data) => (clipboard.cache = data));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    window.addEventListener('load', clipboard.update(client), true);
 | 
				
			||||||
 | 
					    window.addEventListener('copy', clipboard.update(client));
 | 
				
			||||||
 | 
					    window.addEventListener('cut', clipboard.update(client));
 | 
				
			||||||
 | 
					    window.addEventListener(
 | 
				
			||||||
 | 
					        'focus',
 | 
				
			||||||
 | 
					        (e) => {
 | 
				
			||||||
 | 
					            if (e.target === window) {
 | 
				
			||||||
 | 
					                clipboard.update(client)();
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        true
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return true;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					clipboard.update = (client) => {
 | 
				
			||||||
 | 
					    return () => {
 | 
				
			||||||
 | 
					        clipboard.getLocalClipboard().then((data) => {
 | 
				
			||||||
 | 
					            clipboard.cache = data;
 | 
				
			||||||
 | 
					            clipboard.setRemoteClipboard(client);
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					clipboard.sendRemoteClipboard = (client, text) => {
 | 
				
			||||||
 | 
					    clipboard.cache = {
 | 
				
			||||||
 | 
					        type: 'text/plain',
 | 
				
			||||||
 | 
					        data: text,
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    clipboard.setRemoteClipboard(client);
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					clipboard.setRemoteClipboard = (client) => {
 | 
				
			||||||
 | 
					    if (!clipboard.cache) {
 | 
				
			||||||
 | 
					        return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let writer;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const stream = client.createClipboardStream(clipboard.cache.type);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (typeof clipboard.cache.data === 'string') {
 | 
				
			||||||
 | 
					        writer = new Guacamole.StringWriter(stream);
 | 
				
			||||||
 | 
					        writer.sendText(clipboard.cache.data);
 | 
				
			||||||
 | 
					        writer.sendEnd();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        clipboard.appendClipboardList('up', clipboard.cache.data);
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					        writer = new Guacamole.BlobWriter(stream);
 | 
				
			||||||
 | 
					        writer.oncomplete = function clipboardSent() {
 | 
				
			||||||
 | 
					            writer.sendEnd();
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					        writer.sendBlob(clipboard.cache.data);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					clipboard.getLocalClipboard = async () => {
 | 
				
			||||||
 | 
					    // 获取本地剪贴板数据
 | 
				
			||||||
 | 
					    if (navigator.clipboard && navigator.clipboard.readText) {
 | 
				
			||||||
 | 
					        const text = await navigator.clipboard.readText();
 | 
				
			||||||
 | 
					        return {
 | 
				
			||||||
 | 
					            type: 'text/plain',
 | 
				
			||||||
 | 
					            data: text,
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					        ElMessage.warning('只有https才可以访问剪贴板');
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					clipboard.setLocalClipboard = async (data) => {
 | 
				
			||||||
 | 
					    if (data.type === 'text/plain') {
 | 
				
			||||||
 | 
					        if (navigator.clipboard && navigator.clipboard.writeText) {
 | 
				
			||||||
 | 
					            await navigator.clipboard.writeText(data.data);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// 获取到远程服务器剪贴板变动
 | 
				
			||||||
 | 
					clipboard.onClipboard = (stream, mimetype) => {
 | 
				
			||||||
 | 
					    let reader;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (/^text\//.exec(mimetype)) {
 | 
				
			||||||
 | 
					        reader = new Guacamole.StringReader(stream);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Assemble received data into a single string
 | 
				
			||||||
 | 
					        let data = '';
 | 
				
			||||||
 | 
					        reader.ontext = (text) => {
 | 
				
			||||||
 | 
					            data += text;
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Set clipboard contents once stream is finished
 | 
				
			||||||
 | 
					        reader.onend = () => {
 | 
				
			||||||
 | 
					            clipboard.setLocalClipboard({
 | 
				
			||||||
 | 
					                type: mimetype,
 | 
				
			||||||
 | 
					                data: data,
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            clipboard.setClipboardFn && typeof clipboard.setClipboardFn === 'function' && clipboard.setClipboardFn(data);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            clipboard.appendClipboardList('down', data);
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					        reader = new Guacamole.BlobReader(stream, mimetype);
 | 
				
			||||||
 | 
					        reader.onend = () => {
 | 
				
			||||||
 | 
					            clipboard.setLocalClipboard({
 | 
				
			||||||
 | 
					                type: mimetype,
 | 
				
			||||||
 | 
					                data: reader.getBlob(),
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/***
 | 
				
			||||||
 | 
					 * 注册剪贴板监听器,如果有本地或远程剪贴板变动,则会更新剪贴板列表
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					clipboard.installWatcher = (clipboardList, setClipboardFn) => {
 | 
				
			||||||
 | 
					    clipboard.clipboardList = clipboardList;
 | 
				
			||||||
 | 
					    clipboard.setClipboardFn = setClipboardFn;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					clipboard.appendClipboardList = (src, data) => {
 | 
				
			||||||
 | 
					    clipboard.clipboardList = clipboard.clipboardList || [];
 | 
				
			||||||
 | 
					    // 循环判断是否重复
 | 
				
			||||||
 | 
					    for (let i = 0; i < clipboard.clipboardList.length; i++) {
 | 
				
			||||||
 | 
					        if (clipboard.clipboardList[i].data === data) {
 | 
				
			||||||
 | 
					            return;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    clipboard.clipboardList.push({ type: 'text/plain', data, src });
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default clipboard;
 | 
				
			||||||
							
								
								
									
										14441
									
								
								mayfly_go_web/src/components/terminal-rdp/guac/guacamole-common.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14441
									
								
								mayfly_go_web/src/components/terminal-rdp/guac/guacamole-common.js
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										38
									
								
								mayfly_go_web/src/components/terminal-rdp/guac/screen.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								mayfly_go_web/src/components/terminal-rdp/guac/screen.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,38 @@
 | 
				
			|||||||
 | 
					export function launchIntoFullscreen(element) {
 | 
				
			||||||
 | 
					    if (element.requestFullscreen) {
 | 
				
			||||||
 | 
					        element.requestFullscreen();
 | 
				
			||||||
 | 
					    } else if (element.mozRequestFullScreen) {
 | 
				
			||||||
 | 
					        element.mozRequestFullScreen();
 | 
				
			||||||
 | 
					    } else if (element.webkitRequestFullscreen) {
 | 
				
			||||||
 | 
					        element.webkitRequestFullscreen();
 | 
				
			||||||
 | 
					    } else if (element.msRequestFullscreen) {
 | 
				
			||||||
 | 
					        element.msRequestFullscreen();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					export function exitFullscreen() {
 | 
				
			||||||
 | 
					    if (document.exitFullscreen) {
 | 
				
			||||||
 | 
					        document.exitFullscreen();
 | 
				
			||||||
 | 
					    } else if (document.mozCancelFullScreen) {
 | 
				
			||||||
 | 
					        document.mozCancelFullScreen();
 | 
				
			||||||
 | 
					    } else if (document.webkitExitFullscreen) {
 | 
				
			||||||
 | 
					        document.webkitExitFullscreen();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function watchFullscreenChange(callback) {
 | 
				
			||||||
 | 
					    function onFullscreenChange(e) {
 | 
				
			||||||
 | 
					        let isFull = (document.fullscreenElement || document.mozFullScreenElement || document.webkitFullscreenElement || document.msFullscreenElement) != null;
 | 
				
			||||||
 | 
					        callback(e, isFull);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    document.addEventListener('fullscreenchange', onFullscreenChange);
 | 
				
			||||||
 | 
					    document.addEventListener('mozfullscreenchange', onFullscreenChange);
 | 
				
			||||||
 | 
					    document.addEventListener('webkitfullscreenchange', onFullscreenChange);
 | 
				
			||||||
 | 
					    document.addEventListener('msfullscreenchange', onFullscreenChange);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function unWatchFullscreenChange(callback) {
 | 
				
			||||||
 | 
					    document.removeEventListener('fullscreenchange', callback);
 | 
				
			||||||
 | 
					    document.removeEventListener('mozfullscreenchange', callback);
 | 
				
			||||||
 | 
					    document.removeEventListener('webkitfullscreenchange', callback);
 | 
				
			||||||
 | 
					    document.removeEventListener('msfullscreenchange', callback);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										78
									
								
								mayfly_go_web/src/components/terminal-rdp/guac/states.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										78
									
								
								mayfly_go_web/src/components/terminal-rdp/guac/states.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,78 @@
 | 
				
			|||||||
 | 
					export const ClientState = {
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * The client is idle, with no active connection.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @type number
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    IDLE: 0,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * The client is in the process of establishing a connection.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @type {!number}
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    CONNECTING: 1,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * The client is waiting on further information or a remote server to
 | 
				
			||||||
 | 
					     * establish the connection.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @type {!number}
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    WAITING: 2,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * The client is actively connected to a remote server.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @type {!number}
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    CONNECTED: 3,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * The client is in the process of disconnecting from the remote server.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @type {!number}
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    DISCONNECTING: 4,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * The client has completed the connection and is no longer connected.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @type {!number}
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    DISCONNECTED: 5,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const TunnelState = {
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * A connection is in pending. It is not yet known whether connection was
 | 
				
			||||||
 | 
					     * successful.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @type {!number}
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    CONNECTING: 0,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Connection was successful, and data is being received.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @type {!number}
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    OPEN: 1,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * The connection is closed. Connection may not have been successful, the
 | 
				
			||||||
 | 
					     * tunnel may have been explicitly closed by either side, or an error may
 | 
				
			||||||
 | 
					     * have occurred.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @type {!number}
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    CLOSED: 2,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * The connection is open, but communication through the tunnel appears to
 | 
				
			||||||
 | 
					     * be disrupted, and the connection may close as a result.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @type {!number}
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    UNSTABLE: 3,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
							
								
								
									
										11
									
								
								mayfly_go_web/src/components/terminal-rdp/index.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								mayfly_go_web/src/components/terminal-rdp/index.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,11 @@
 | 
				
			|||||||
 | 
					export interface TerminalExpose {
 | 
				
			||||||
 | 
					    /** 连接 */
 | 
				
			||||||
 | 
					    init(width: number, height: number, force: boolean): void;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /** 短开连接 */
 | 
				
			||||||
 | 
					    close(): void;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    blur(): void;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    focus(): void;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -1,5 +1,5 @@
 | 
				
			|||||||
<template>
 | 
					<template>
 | 
				
			||||||
    <div id="terminal-body" :style="{ height, background: themeConfig.terminalBackground }">
 | 
					    <div id="terminal-body" :style="{ height }">
 | 
				
			||||||
        <div ref="terminalRef" class="terminal" />
 | 
					        <div ref="terminalRef" class="terminal" />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        <TerminalSearch ref="terminalSearchRef" :search-addon="state.addon.search" @close="focus" />
 | 
					        <TerminalSearch ref="terminalSearchRef" :search-addon="state.addon.search" @close="focus" />
 | 
				
			||||||
@@ -8,7 +8,7 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
<script lang="ts" setup>
 | 
					<script lang="ts" setup>
 | 
				
			||||||
import 'xterm/css/xterm.css';
 | 
					import 'xterm/css/xterm.css';
 | 
				
			||||||
import { Terminal } from 'xterm';
 | 
					import { Terminal, ITheme } from 'xterm';
 | 
				
			||||||
import { FitAddon } from 'xterm-addon-fit';
 | 
					import { FitAddon } from 'xterm-addon-fit';
 | 
				
			||||||
import { SearchAddon } from 'xterm-addon-search';
 | 
					import { SearchAddon } from 'xterm-addon-search';
 | 
				
			||||||
import { WebLinksAddon } from 'xterm-addon-web-links';
 | 
					import { WebLinksAddon } from 'xterm-addon-web-links';
 | 
				
			||||||
@@ -20,8 +20,15 @@ import TerminalSearch from './TerminalSearch.vue';
 | 
				
			|||||||
import { debounce } from 'lodash';
 | 
					import { debounce } from 'lodash';
 | 
				
			||||||
import { TerminalStatus } from './common';
 | 
					import { TerminalStatus } from './common';
 | 
				
			||||||
import { useEventListener } from '@vueuse/core';
 | 
					import { useEventListener } from '@vueuse/core';
 | 
				
			||||||
 | 
					import themes from './themes';
 | 
				
			||||||
 | 
					import { TrzszFilter } from 'trzsz';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const props = defineProps({
 | 
					const props = defineProps({
 | 
				
			||||||
 | 
					    // mounted时,是否执行init方法
 | 
				
			||||||
 | 
					    mountInit: {
 | 
				
			||||||
 | 
					        type: Boolean,
 | 
				
			||||||
 | 
					        default: true,
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * 初始化执行命令
 | 
					     * 初始化执行命令
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
@@ -60,13 +67,13 @@ const state = reactive({
 | 
				
			|||||||
        search: null as any,
 | 
					        search: null as any,
 | 
				
			||||||
        weblinks: null as any,
 | 
					        weblinks: null as any,
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    status: TerminalStatus.NoConnected,
 | 
					    status: -11,
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
onMounted(() => {
 | 
					onMounted(() => {
 | 
				
			||||||
    nextTick(() => {
 | 
					    if (props.mountInit) {
 | 
				
			||||||
        init();
 | 
					        init();
 | 
				
			||||||
    });
 | 
					    }
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
watch(
 | 
					watch(
 | 
				
			||||||
@@ -76,15 +83,30 @@ watch(
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
);
 | 
					);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// 监听 themeConfig terminalTheme配置的变化
 | 
				
			||||||
 | 
					watch(
 | 
				
			||||||
 | 
					    () => themeConfig.value.terminalTheme,
 | 
				
			||||||
 | 
					    () => {
 | 
				
			||||||
 | 
					        term.options.theme = getTerminalTheme();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
onBeforeUnmount(() => {
 | 
					onBeforeUnmount(() => {
 | 
				
			||||||
    close();
 | 
					    close();
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function init() {
 | 
					function init() {
 | 
				
			||||||
 | 
					    state.status = TerminalStatus.NoConnected;
 | 
				
			||||||
    if (term) {
 | 
					    if (term) {
 | 
				
			||||||
        console.log('重新连接...');
 | 
					        console.log('重新连接...');
 | 
				
			||||||
        close();
 | 
					        close();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					    nextTick(() => {
 | 
				
			||||||
 | 
					        initTerm();
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					async function initTerm() {
 | 
				
			||||||
    term = new Terminal({
 | 
					    term = new Terminal({
 | 
				
			||||||
        fontSize: themeConfig.value.terminalFontSize || 15,
 | 
					        fontSize: themeConfig.value.terminalFontSize || 15,
 | 
				
			||||||
        fontWeight: themeConfig.value.terminalFontWeight || 'normal',
 | 
					        fontWeight: themeConfig.value.terminalFontWeight || 'normal',
 | 
				
			||||||
@@ -92,13 +114,10 @@ function init() {
 | 
				
			|||||||
        cursorBlink: true,
 | 
					        cursorBlink: true,
 | 
				
			||||||
        disableStdin: false,
 | 
					        disableStdin: false,
 | 
				
			||||||
        allowProposedApi: true,
 | 
					        allowProposedApi: true,
 | 
				
			||||||
        theme: {
 | 
					        fastScrollModifier: 'ctrl',
 | 
				
			||||||
            foreground: themeConfig.value.terminalForeground || '#7e9192', //字体
 | 
					        theme: getTerminalTheme(),
 | 
				
			||||||
            background: themeConfig.value.terminalBackground || '#002833', //背景色
 | 
					 | 
				
			||||||
            cursor: themeConfig.value.terminalCursor || '#268F81', //设置光标
 | 
					 | 
				
			||||||
            // cursorAccent: "red",  // 光标停止颜色
 | 
					 | 
				
			||||||
        } as any,
 | 
					 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    term.open(terminalRef.value);
 | 
					    term.open(terminalRef.value);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // 注册自适应组件
 | 
					    // 注册自适应组件
 | 
				
			||||||
@@ -106,31 +125,12 @@ function init() {
 | 
				
			|||||||
    state.addon.fit = fitAddon;
 | 
					    state.addon.fit = fitAddon;
 | 
				
			||||||
    term.loadAddon(fitAddon);
 | 
					    term.loadAddon(fitAddon);
 | 
				
			||||||
    fitTerminal();
 | 
					    fitTerminal();
 | 
				
			||||||
 | 
					    // 注册窗口大小监听器
 | 
				
			||||||
 | 
					    useEventListener('resize', debounce(fitTerminal, 400));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // 注册搜索组件
 | 
					 | 
				
			||||||
    const searchAddon = new SearchAddon();
 | 
					 | 
				
			||||||
    state.addon.search = searchAddon;
 | 
					 | 
				
			||||||
    term.loadAddon(searchAddon);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // 注册 url link组件
 | 
					 | 
				
			||||||
    const weblinks = new WebLinksAddon();
 | 
					 | 
				
			||||||
    state.addon.weblinks = weblinks;
 | 
					 | 
				
			||||||
    term.loadAddon(weblinks);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // 初始化websocket
 | 
					 | 
				
			||||||
    initSocket();
 | 
					    initSocket();
 | 
				
			||||||
}
 | 
					    // 注册其他插件
 | 
				
			||||||
 | 
					    loadAddon();
 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * 连接成功
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
const onConnected = () => {
 | 
					 | 
				
			||||||
    // 注册心跳
 | 
					 | 
				
			||||||
    pingInterval = setInterval(sendPing, 15000);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // 注册 terminal 事件
 | 
					 | 
				
			||||||
    term.onResize((event) => sendResize(event.cols, event.rows));
 | 
					 | 
				
			||||||
    term.onData((event) => sendCmd(event));
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // 注册自定义快捷键
 | 
					    // 注册自定义快捷键
 | 
				
			||||||
    term.attachCustomKeyEventHandler((event: KeyboardEvent) => {
 | 
					    term.attachCustomKeyEventHandler((event: KeyboardEvent) => {
 | 
				
			||||||
@@ -142,50 +142,26 @@ const onConnected = () => {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        return true;
 | 
					        return true;
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
    state.status = TerminalStatus.Connected;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // 注册窗口大小监听器
 | 
					 | 
				
			||||||
    useEventListener('resize', debounce(fitTerminal, 400));
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    focus();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // 如果有初始要执行的命令,则发送执行命令
 | 
					 | 
				
			||||||
    if (props.cmd) {
 | 
					 | 
				
			||||||
        sendCmd(props.cmd + ' \r');
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// 自适应终端
 | 
					 | 
				
			||||||
const fitTerminal = () => {
 | 
					 | 
				
			||||||
    const dimensions = state.addon.fit && state.addon.fit.proposeDimensions();
 | 
					 | 
				
			||||||
    if (!dimensions) {
 | 
					 | 
				
			||||||
        return;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    if (dimensions?.cols && dimensions?.rows) {
 | 
					 | 
				
			||||||
        term.resize(dimensions.cols, dimensions.rows);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const focus = () => {
 | 
					 | 
				
			||||||
    setTimeout(() => term.focus(), 400);
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const clear = () => {
 | 
					 | 
				
			||||||
    term.clear();
 | 
					 | 
				
			||||||
    term.clearSelection();
 | 
					 | 
				
			||||||
    term.focus();
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
function initSocket() {
 | 
					function initSocket() {
 | 
				
			||||||
    if (props.socketUrl) {
 | 
					    if (!props.socketUrl) {
 | 
				
			||||||
        let socketUrl = `${props.socketUrl}&rows=${term?.rows}&cols=${term?.cols}`;
 | 
					        return;
 | 
				
			||||||
        socket = new WebSocket(socketUrl);
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					    socket = new WebSocket(`${props.socketUrl}&rows=${term?.rows}&cols=${term?.cols}`);
 | 
				
			||||||
    // 监听socket连接
 | 
					    // 监听socket连接
 | 
				
			||||||
    socket.onopen = () => {
 | 
					    socket.onopen = () => {
 | 
				
			||||||
        onConnected();
 | 
					        // 注册心跳
 | 
				
			||||||
 | 
					        pingInterval = setInterval(sendPing, 15000);
 | 
				
			||||||
 | 
					        state.status = TerminalStatus.Connected;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        focus();
 | 
				
			||||||
 | 
					        fitTerminal();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // 如果有初始要执行的命令,则发送执行命令
 | 
				
			||||||
 | 
					        if (props.cmd) {
 | 
				
			||||||
 | 
					            sendCmd(props.cmd + ' \r');
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // 监听socket错误信息
 | 
					    // 监听socket错误信息
 | 
				
			||||||
@@ -197,20 +173,95 @@ function initSocket() {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    socket.onclose = (e: CloseEvent) => {
 | 
					    socket.onclose = (e: CloseEvent) => {
 | 
				
			||||||
        console.log('terminal socket close...', e.reason);
 | 
					        console.log('terminal socket close...', e.reason);
 | 
				
			||||||
        // 清除 ping
 | 
					 | 
				
			||||||
        pingInterval && clearInterval(pingInterval);
 | 
					 | 
				
			||||||
        state.status = TerminalStatus.Disconnected;
 | 
					        state.status = TerminalStatus.Disconnected;
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
 | 
					 | 
				
			||||||
    // 监听socket消息
 | 
					 | 
				
			||||||
    socket.onmessage = getMessage;
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function getMessage(msg: any) {
 | 
					function loadAddon() {
 | 
				
			||||||
    // msg.data是真正后端返回的数据
 | 
					    // 注册搜索组件
 | 
				
			||||||
    term.write(msg.data);
 | 
					    const searchAddon = new SearchAddon();
 | 
				
			||||||
 | 
					    state.addon.search = searchAddon;
 | 
				
			||||||
 | 
					    term.loadAddon(searchAddon);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // 注册 url link组件
 | 
				
			||||||
 | 
					    const weblinks = new WebLinksAddon();
 | 
				
			||||||
 | 
					    state.addon.weblinks = weblinks;
 | 
				
			||||||
 | 
					    term.loadAddon(weblinks);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // 注册 trzsz
 | 
				
			||||||
 | 
					    // initialize trzsz filter
 | 
				
			||||||
 | 
					    const trzsz = new TrzszFilter({
 | 
				
			||||||
 | 
					        // write the server output to the terminal
 | 
				
			||||||
 | 
					        writeToTerminal: (data: any) => term.write(typeof data === 'string' ? data : new Uint8Array(data)),
 | 
				
			||||||
 | 
					        // send the user input to the server
 | 
				
			||||||
 | 
					        sendToServer: sendCmd,
 | 
				
			||||||
 | 
					        // the terminal columns
 | 
				
			||||||
 | 
					        terminalColumns: term.cols,
 | 
				
			||||||
 | 
					        // there is a windows shell
 | 
				
			||||||
 | 
					        isWindowsShell: false,
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // let trzsz process the server output
 | 
				
			||||||
 | 
					    socket?.addEventListener('message', (e) => trzsz.processServerOutput(e.data));
 | 
				
			||||||
 | 
					    // let trzsz process the user input
 | 
				
			||||||
 | 
					    term.onData((data) => trzsz.processTerminalInput(data));
 | 
				
			||||||
 | 
					    term.onBinary((data) => trzsz.processBinaryInput(data));
 | 
				
			||||||
 | 
					    term.onResize((size) => {
 | 
				
			||||||
 | 
					        sendResize(size.cols, size.rows);
 | 
				
			||||||
 | 
					        // tell trzsz the terminal columns has been changed
 | 
				
			||||||
 | 
					        trzsz.setTerminalColumns(size.cols);
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    // enable drag files or directories to upload
 | 
				
			||||||
 | 
					    terminalRef.value.addEventListener('dragover', (event: Event) => event.preventDefault());
 | 
				
			||||||
 | 
					    terminalRef.value.addEventListener('drop', (event: any) => {
 | 
				
			||||||
 | 
					        event.preventDefault();
 | 
				
			||||||
 | 
					        trzsz
 | 
				
			||||||
 | 
					            .uploadFiles(event.dataTransfer.items)
 | 
				
			||||||
 | 
					            .then(() => console.log('upload success'))
 | 
				
			||||||
 | 
					            .catch((err: any) => console.log(err));
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// 写入内容至终端
 | 
				
			||||||
 | 
					const write2Term = (data: any) => {
 | 
				
			||||||
 | 
					    term.write(data);
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const writeln2Term = (data: any) => {
 | 
				
			||||||
 | 
					    term.writeln(data);
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const getTerminalTheme = () => {
 | 
				
			||||||
 | 
					    const terminalTheme = themeConfig.value.terminalTheme;
 | 
				
			||||||
 | 
					    // 如果不是自定义主题,则返回内置主题
 | 
				
			||||||
 | 
					    if (terminalTheme != 'custom') {
 | 
				
			||||||
 | 
					        return themes[terminalTheme];
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // 自定义主题
 | 
				
			||||||
 | 
					    return {
 | 
				
			||||||
 | 
					        foreground: themeConfig.value.terminalForeground || '#7e9192', //字体
 | 
				
			||||||
 | 
					        background: themeConfig.value.terminalBackground || '#002833', //背景色
 | 
				
			||||||
 | 
					        cursor: themeConfig.value.terminalCursor || '#268F81', //设置光标
 | 
				
			||||||
 | 
					        // cursorAccent: "red",  // 光标停止颜色
 | 
				
			||||||
 | 
					    } as ITheme;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// 自适应终端
 | 
				
			||||||
 | 
					const fitTerminal = () => {
 | 
				
			||||||
 | 
					    state.addon.fit.fit();
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const focus = () => {
 | 
				
			||||||
 | 
					    setTimeout(() => term.focus(), 300);
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const clear = () => {
 | 
				
			||||||
 | 
					    term.clear();
 | 
				
			||||||
 | 
					    term.clearSelection();
 | 
				
			||||||
 | 
					    term.focus();
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
enum MsgType {
 | 
					enum MsgType {
 | 
				
			||||||
    Resize = 1,
 | 
					    Resize = 1,
 | 
				
			||||||
    Data = 2,
 | 
					    Data = 2,
 | 
				
			||||||
@@ -218,29 +269,19 @@ enum MsgType {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const send = (msg: any) => {
 | 
					const send = (msg: any) => {
 | 
				
			||||||
    state.status == TerminalStatus.Connected && socket.send(JSON.stringify(msg));
 | 
					    state.status == TerminalStatus.Connected && socket?.send(msg);
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const sendResize = (cols: number, rows: number) => {
 | 
					const sendResize = (cols: number, rows: number) => {
 | 
				
			||||||
    send({
 | 
					    send(`${MsgType.Resize}|${rows}|${cols}`);
 | 
				
			||||||
        type: MsgType.Resize,
 | 
					 | 
				
			||||||
        Cols: cols,
 | 
					 | 
				
			||||||
        Rows: rows,
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const sendPing = () => {
 | 
					const sendPing = () => {
 | 
				
			||||||
    send({
 | 
					    send(`${MsgType.Ping}|ping`);
 | 
				
			||||||
        type: MsgType.Ping,
 | 
					 | 
				
			||||||
        msg: 'ping',
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function sendCmd(key: any) {
 | 
					function sendCmd(key: any) {
 | 
				
			||||||
    send({
 | 
					    send(`${MsgType.Data}|${key}`);
 | 
				
			||||||
        type: MsgType.Data,
 | 
					 | 
				
			||||||
        msg: key,
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function closeSocket() {
 | 
					function closeSocket() {
 | 
				
			||||||
@@ -265,20 +306,19 @@ const getStatus = (): TerminalStatus => {
 | 
				
			|||||||
    return state.status;
 | 
					    return state.status;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
defineExpose({ init, fitTerminal, focus, clear, close, getStatus });
 | 
					defineExpose({ init, fitTerminal, focus, clear, close, getStatus, sendResize, write2Term, writeln2Term });
 | 
				
			||||||
</script>
 | 
					</script>
 | 
				
			||||||
<style lang="scss">
 | 
					<style lang="scss">
 | 
				
			||||||
#terminal-body {
 | 
					#terminal-body {
 | 
				
			||||||
    background: #212529;
 | 
					 | 
				
			||||||
    width: 100%;
 | 
					    width: 100%;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    .terminal {
 | 
					    .terminal {
 | 
				
			||||||
        width: 100%;
 | 
					        width: 100%;
 | 
				
			||||||
        height: 100%;
 | 
					        height: 100%;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        .xterm .xterm-viewport {
 | 
					        // .xterm .xterm-viewport {
 | 
				
			||||||
            overflow-y: hidden;
 | 
					        //     overflow-y: hidden;
 | 
				
			||||||
        }
 | 
					        // }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
</style>
 | 
					</style>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -2,7 +2,7 @@
 | 
				
			|||||||
    <div>
 | 
					    <div>
 | 
				
			||||||
        <div class="terminal-dialog-container" v-for="openTerminal of terminals" :key="openTerminal.terminalId">
 | 
					        <div class="terminal-dialog-container" v-for="openTerminal of terminals" :key="openTerminal.terminalId">
 | 
				
			||||||
            <el-dialog
 | 
					            <el-dialog
 | 
				
			||||||
                title="终端"
 | 
					                title="SSH终端"
 | 
				
			||||||
                v-model="openTerminal.visible"
 | 
					                v-model="openTerminal.visible"
 | 
				
			||||||
                top="32px"
 | 
					                top="32px"
 | 
				
			||||||
                class="terminal-dialog"
 | 
					                class="terminal-dialog"
 | 
				
			||||||
@@ -58,7 +58,7 @@
 | 
				
			|||||||
                        </div>
 | 
					                        </div>
 | 
				
			||||||
                    </div>
 | 
					                    </div>
 | 
				
			||||||
                </template>
 | 
					                </template>
 | 
				
			||||||
                <div class="terminal-wrapper" :style="{ height: `calc(100vh - ${openTerminal.fullscreen ? '49px' : '200px'})` }">
 | 
					                <div :style="{ height: `calc(100vh - ${openTerminal.fullscreen ? '49px' : '200px'})` }">
 | 
				
			||||||
                    <TerminalBody
 | 
					                    <TerminalBody
 | 
				
			||||||
                        @status-change="terminalStatusChange(openTerminal.terminalId, $event)"
 | 
					                        @status-change="terminalStatusChange(openTerminal.terminalId, $event)"
 | 
				
			||||||
                        :ref="(el) => setTerminalRef(el, openTerminal.terminalId)"
 | 
					                        :ref="(el) => setTerminalRef(el, openTerminal.terminalId)"
 | 
				
			||||||
@@ -92,7 +92,7 @@
 | 
				
			|||||||
</template>
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<script lang="ts" setup>
 | 
					<script lang="ts" setup>
 | 
				
			||||||
import { toRefs, reactive } from 'vue';
 | 
					import { reactive, toRefs } from 'vue';
 | 
				
			||||||
import TerminalBody from '@/components/terminal/TerminalBody.vue';
 | 
					import TerminalBody from '@/components/terminal/TerminalBody.vue';
 | 
				
			||||||
import SvgIcon from '@/components/svgIcon/index.vue';
 | 
					import SvgIcon from '@/components/svgIcon/index.vue';
 | 
				
			||||||
import { TerminalStatus } from './common';
 | 
					import { TerminalStatus } from './common';
 | 
				
			||||||
@@ -259,6 +259,10 @@ defineExpose({
 | 
				
			|||||||
        padding: 10px;
 | 
					        padding: 10px;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    .el-dialog {
 | 
				
			||||||
 | 
					        padding: 1px 1px;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // 取消body最大高度,否则全屏有问题
 | 
					    // 取消body最大高度,否则全屏有问题
 | 
				
			||||||
    .el-dialog__body {
 | 
					    .el-dialog__body {
 | 
				
			||||||
        max-height: 100% !important;
 | 
					        max-height: 100% !important;
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										113
									
								
								mayfly_go_web/src/components/terminal/TerminalLog.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										113
									
								
								mayfly_go_web/src/components/terminal/TerminalLog.vue
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,113 @@
 | 
				
			|||||||
 | 
					<template>
 | 
				
			||||||
 | 
					    <div>
 | 
				
			||||||
 | 
					        <el-drawer v-model="visible" :before-close="cancel" size="50%">
 | 
				
			||||||
 | 
					            <template #header>
 | 
				
			||||||
 | 
					                <DrawerHeader :header="props.title" :back="cancel">
 | 
				
			||||||
 | 
					                    <template #extra>
 | 
				
			||||||
 | 
					                        <EnumTag :enums="LogTypeEnum" :value="log?.type" class="mr20" />
 | 
				
			||||||
 | 
					                    </template>
 | 
				
			||||||
 | 
					                </DrawerHeader>
 | 
				
			||||||
 | 
					            </template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            <el-descriptions class="mb10" :column="1" border v-if="extra">
 | 
				
			||||||
 | 
					                <el-descriptions-item v-for="(value, key) in extra" :key="key" :span="1" :label="key">{{ value }}</el-descriptions-item>
 | 
				
			||||||
 | 
					            </el-descriptions>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            <TerminalBody class="mb10" ref="terminalRef" height="calc(100vh - 220px)" />
 | 
				
			||||||
 | 
					        </el-drawer>
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<script lang="ts" setup>
 | 
				
			||||||
 | 
					import { computed, ref, watch } from 'vue';
 | 
				
			||||||
 | 
					import DrawerHeader from '@/components/drawer-header/DrawerHeader.vue';
 | 
				
			||||||
 | 
					import TerminalBody from './TerminalBody.vue';
 | 
				
			||||||
 | 
					import { logApi } from '../../views/system/api';
 | 
				
			||||||
 | 
					import { LogTypeEnum } from '@/views/system/enums';
 | 
				
			||||||
 | 
					import { useIntervalFn } from '@vueuse/core';
 | 
				
			||||||
 | 
					import EnumTag from '@/components/enumtag/EnumTag.vue';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const props = defineProps({
 | 
				
			||||||
 | 
					    title: {
 | 
				
			||||||
 | 
					        type: String,
 | 
				
			||||||
 | 
					        default: '日志',
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const visible = defineModel<boolean>('visible', { default: false });
 | 
				
			||||||
 | 
					const logId = defineModel<number>('logId', { default: 0 });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const terminalRef: any = ref(null);
 | 
				
			||||||
 | 
					const nowLine = ref(0);
 | 
				
			||||||
 | 
					const log = ref({}) as any;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const extra = computed(() => {
 | 
				
			||||||
 | 
					    if (log.value?.extra) {
 | 
				
			||||||
 | 
					        return JSON.parse(log.value.extra);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    return null;
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// 定时获取最新日志
 | 
				
			||||||
 | 
					const { pause, resume } = useIntervalFn(() => {
 | 
				
			||||||
 | 
					    writeLog();
 | 
				
			||||||
 | 
					}, 500);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					watch(
 | 
				
			||||||
 | 
					    () => logId.value,
 | 
				
			||||||
 | 
					    (logId: number) => {
 | 
				
			||||||
 | 
					        terminalRef.value?.clear();
 | 
				
			||||||
 | 
					        if (!logId) {
 | 
				
			||||||
 | 
					            return;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        writeLog();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const cancel = () => {
 | 
				
			||||||
 | 
					    visible.value = false;
 | 
				
			||||||
 | 
					    logId.value = 0;
 | 
				
			||||||
 | 
					    nowLine.value = 0;
 | 
				
			||||||
 | 
					    pause();
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const writeLog = async () => {
 | 
				
			||||||
 | 
					    const log = await getLog();
 | 
				
			||||||
 | 
					    if (!log) {
 | 
				
			||||||
 | 
					        return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    writeLog2Term(log);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // 如果不是还在执行中的日志,则暂停轮询
 | 
				
			||||||
 | 
					    if (log.type != LogTypeEnum.Running.value) {
 | 
				
			||||||
 | 
					        pause();
 | 
				
			||||||
 | 
					        return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    resume();
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const writeLog2Term = (log: any) => {
 | 
				
			||||||
 | 
					    if (!log) {
 | 
				
			||||||
 | 
					        return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    const lines = log.resp.split('\n');
 | 
				
			||||||
 | 
					    for (let line of lines.slice(nowLine.value)) {
 | 
				
			||||||
 | 
					        nowLine.value += 1;
 | 
				
			||||||
 | 
					        terminalRef.value?.writeln2Term(line);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    terminalRef.value?.focus();
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const getLog = async () => {
 | 
				
			||||||
 | 
					    if (!logId.value) {
 | 
				
			||||||
 | 
					        return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    const logRes = await logApi.detail.request({
 | 
				
			||||||
 | 
					        id: logId.value,
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    log.value = logRes;
 | 
				
			||||||
 | 
					    return logRes;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<style lang="scss"></style>
 | 
				
			||||||
@@ -1,6 +1,15 @@
 | 
				
			|||||||
 | 
					import EnumValue from '@/common/Enum';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export enum TerminalStatus {
 | 
					export enum TerminalStatus {
 | 
				
			||||||
    Error = -1,
 | 
					    Error = -1,
 | 
				
			||||||
    NoConnected = 0,
 | 
					    NoConnected = 0,
 | 
				
			||||||
    Connected = 1,
 | 
					    Connected = 1,
 | 
				
			||||||
    Disconnected = 2,
 | 
					    Disconnected = 2,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const TerminalStatusEnum = {
 | 
				
			||||||
 | 
					    Error: EnumValue.of(TerminalStatus.Error, '连接出错').setExtra({ iconColor: 'var(--el-color-error)' }),
 | 
				
			||||||
 | 
					    NoConnected: EnumValue.of(TerminalStatus.NoConnected, '未连接').setExtra({ iconColor: 'var(--el-color-primary)' }),
 | 
				
			||||||
 | 
					    Connected: EnumValue.of(TerminalStatus.Connected, '连接成功').setExtra({ iconColor: 'var(--el-color-success)' }),
 | 
				
			||||||
 | 
					    Disconnected: EnumValue.of(TerminalStatus.Disconnected, '连接失败').setExtra({ iconColor: 'var(--el-color-error)' }),
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										92
									
								
								mayfly_go_web/src/components/terminal/themes.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										92
									
								
								mayfly_go_web/src/components/terminal/themes.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,92 @@
 | 
				
			|||||||
 | 
					export default {
 | 
				
			||||||
 | 
					    dark: {
 | 
				
			||||||
 | 
					        foreground: '#c7c7c7',
 | 
				
			||||||
 | 
					        background: '#000000',
 | 
				
			||||||
 | 
					        cursor: '#c7c7c7',
 | 
				
			||||||
 | 
					        selectionBackground: '#686868',
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        black: '#000000',
 | 
				
			||||||
 | 
					        brightBlack: '#676767',
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        red: '#c91b00',
 | 
				
			||||||
 | 
					        brightRed: '#ff6d67',
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        green: '#00c200',
 | 
				
			||||||
 | 
					        brightGreen: '#5ff967',
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        yellow: '#c7c400',
 | 
				
			||||||
 | 
					        brightYellow: '#fefb67',
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        blue: '#0225c7',
 | 
				
			||||||
 | 
					        brightBlue: '#6871ff',
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        magenta: '#c930c7',
 | 
				
			||||||
 | 
					        brightMagenta: '#ff76ff',
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        cyan: '#00c5c7',
 | 
				
			||||||
 | 
					        brightCyan: '#5ffdff',
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        white: '#c7c7c7',
 | 
				
			||||||
 | 
					        brightWhite: '#fffefe',
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    light: {
 | 
				
			||||||
 | 
					        foreground: '#000000',
 | 
				
			||||||
 | 
					        background: '#fffefe',
 | 
				
			||||||
 | 
					        cursor: '#000000',
 | 
				
			||||||
 | 
					        selectionBackground: '#c7c7c7',
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        black: '#000000',
 | 
				
			||||||
 | 
					        brightBlack: '#676767',
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        red: '#c91b00',
 | 
				
			||||||
 | 
					        brightRed: '#ff6d67',
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        green: '#00c200',
 | 
				
			||||||
 | 
					        brightGreen: '#5ff967',
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        yellow: '#c7c400',
 | 
				
			||||||
 | 
					        brightYellow: '#fefb67',
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        blue: '#0225c7',
 | 
				
			||||||
 | 
					        brightBlue: '#6871ff',
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        magenta: '#c930c7',
 | 
				
			||||||
 | 
					        brightMagenta: '#ff76ff',
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        cyan: '#00c5c7',
 | 
				
			||||||
 | 
					        brightCyan: '#5ffdff',
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        white: '#c7c7c7',
 | 
				
			||||||
 | 
					        brightWhite: '#fffefe',
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    solarizedLight: {
 | 
				
			||||||
 | 
					        foreground: '#657b83',
 | 
				
			||||||
 | 
					        background: '#fdf6e3',
 | 
				
			||||||
 | 
					        cursor: '#657b83',
 | 
				
			||||||
 | 
					        selectionBackground: '#c7c7c7',
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        black: '#073642',
 | 
				
			||||||
 | 
					        brightBlack: '#002b36',
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        red: '#dc322f',
 | 
				
			||||||
 | 
					        brightRed: '#cb4b16',
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        green: '#859900',
 | 
				
			||||||
 | 
					        brightGreen: '#586e75',
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        yellow: '#b58900',
 | 
				
			||||||
 | 
					        brightYellow: '#657b83',
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        blue: '#268bd2',
 | 
				
			||||||
 | 
					        brightBlue: '#839496',
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        magenta: '#d33682',
 | 
				
			||||||
 | 
					        brightMagenta: '#6c71c4',
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        cyan: '#2aa198',
 | 
				
			||||||
 | 
					        brightCyan: '#93a1a1',
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        white: '#eee8d5',
 | 
				
			||||||
 | 
					        brightWhite: '#fdf6e3',
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
@@ -1,5 +1,5 @@
 | 
				
			|||||||
import Api from '@/common/Api';
 | 
					import Api from '@/common/Api';
 | 
				
			||||||
import { isReactive, reactive, toRefs, toValue } from 'vue';
 | 
					import { reactive, toRefs, toValue } from 'vue';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * @description table 页面操作方法封装
 | 
					 * @description table 页面操作方法封装
 | 
				
			||||||
@@ -41,15 +41,10 @@ export const usePageTable = (
 | 
				
			|||||||
            let sp = toValue(state.searchParams);
 | 
					            let sp = toValue(state.searchParams);
 | 
				
			||||||
            if (beforeQueryFn) {
 | 
					            if (beforeQueryFn) {
 | 
				
			||||||
                sp = beforeQueryFn(sp);
 | 
					                sp = beforeQueryFn(sp);
 | 
				
			||||||
 | 
					 | 
				
			||||||
                if (isReactive(state.searchParams)) {
 | 
					 | 
				
			||||||
                    state.searchParams.value = sp;
 | 
					 | 
				
			||||||
                } else {
 | 
					 | 
				
			||||||
                    state.searchParams = sp;
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            let res = await api.request(sp);
 | 
					            let res = await api.request(sp);
 | 
				
			||||||
 | 
					            res.list = res.list || [];
 | 
				
			||||||
            dataCallBack && (res = await dataCallBack(res));
 | 
					            dataCallBack && (res = await dataCallBack(res));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            if (pageable) {
 | 
					            if (pageable) {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,5 +1,5 @@
 | 
				
			|||||||
import router from '@/router';
 | 
					import router from '@/router';
 | 
				
			||||||
import { getClientId, getToken } from '@/common/utils/storage';
 | 
					import { clearUser, getClientId, getRefreshToken, getToken, saveRefreshToken, saveToken } from '@/common/utils/storage';
 | 
				
			||||||
import { templateResolve } from '@/common/utils/string';
 | 
					import { templateResolve } from '@/common/utils/string';
 | 
				
			||||||
import { ElMessage } from 'element-plus';
 | 
					import { ElMessage } from 'element-plus';
 | 
				
			||||||
import { createFetch } from '@vueuse/core';
 | 
					import { createFetch } from '@vueuse/core';
 | 
				
			||||||
@@ -8,6 +8,7 @@ import { Result, ResultEnum } from '@/common/request';
 | 
				
			|||||||
import config from '@/common/config';
 | 
					import config from '@/common/config';
 | 
				
			||||||
import { unref } from 'vue';
 | 
					import { unref } from 'vue';
 | 
				
			||||||
import { URL_401 } from '@/router/staticRouter';
 | 
					import { URL_401 } from '@/router/staticRouter';
 | 
				
			||||||
 | 
					import openApi from '@/common/openApi';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const baseUrl: string = config.baseApiUrl;
 | 
					const baseUrl: string = config.baseApiUrl;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -16,7 +17,7 @@ const useCustomFetch = createFetch({
 | 
				
			|||||||
    combination: 'chain',
 | 
					    combination: 'chain',
 | 
				
			||||||
    options: {
 | 
					    options: {
 | 
				
			||||||
        immediate: false,
 | 
					        immediate: false,
 | 
				
			||||||
        timeout: 60000,
 | 
					        timeout: 600000,
 | 
				
			||||||
        // beforeFetch in pre-configured instance will only run when the newly spawned instance do not pass beforeFetch
 | 
					        // beforeFetch in pre-configured instance will only run when the newly spawned instance do not pass beforeFetch
 | 
				
			||||||
        async beforeFetch({ options }) {
 | 
					        async beforeFetch({ options }) {
 | 
				
			||||||
            const token = getToken();
 | 
					            const token = getToken();
 | 
				
			||||||
@@ -41,16 +42,13 @@ const useCustomFetch = createFetch({
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
export function useApiFetch<T>(api: Api, params: any = null, reqOptions: RequestInit = {}) {
 | 
					export function useApiFetch<T>(api: Api, params: any = null, reqOptions: RequestInit = {}) {
 | 
				
			||||||
    const uaf = useCustomFetch<T>(api.url, {
 | 
					    const uaf = useCustomFetch<T>(api.url, {
 | 
				
			||||||
        beforeFetch({ url, options }) {
 | 
					        async beforeFetch({ url, options }) {
 | 
				
			||||||
            options.method = api.method;
 | 
					            options.method = api.method;
 | 
				
			||||||
            if (!params) {
 | 
					            if (!params) {
 | 
				
			||||||
                return;
 | 
					                return;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            let paramsValue = unref(params);
 | 
					            let paramsValue = unref(params);
 | 
				
			||||||
            if (api.beforeHandler) {
 | 
					 | 
				
			||||||
                paramsValue = api.beforeHandler(paramsValue);
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
            let apiUrl = url;
 | 
					            let apiUrl = url;
 | 
				
			||||||
            // 简单判断该url是否是restful风格
 | 
					            // 简单判断该url是否是restful风格
 | 
				
			||||||
@@ -58,6 +56,10 @@ export function useApiFetch<T>(api: Api, params: any = null, reqOptions: Request
 | 
				
			|||||||
                apiUrl = templateResolve(apiUrl, paramsValue);
 | 
					                apiUrl = templateResolve(apiUrl, paramsValue);
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (api.beforeHandler) {
 | 
				
			||||||
 | 
					                paramsValue = await api.beforeHandler(paramsValue);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            if (paramsValue) {
 | 
					            if (paramsValue) {
 | 
				
			||||||
                const method = options.method?.toLowerCase();
 | 
					                const method = options.method?.toLowerCase();
 | 
				
			||||||
                // post和put使用json格式传参
 | 
					                // post和put使用json格式传参
 | 
				
			||||||
@@ -87,61 +89,104 @@ export function useApiFetch<T>(api: Api, params: any = null, reqOptions: Request
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    return {
 | 
					    return {
 | 
				
			||||||
        execute: async function () {
 | 
					        execute: async function () {
 | 
				
			||||||
            try {
 | 
					            return execUaf(uaf);
 | 
				
			||||||
                await uaf.execute(true);
 | 
					 | 
				
			||||||
            } catch (e: any) {
 | 
					 | 
				
			||||||
                const rejectPromise = Promise.reject(e);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                if (e?.name == 'AbortError') {
 | 
					 | 
				
			||||||
                    console.log('请求已取消');
 | 
					 | 
				
			||||||
                    return rejectPromise;
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                const respStatus = uaf.response.value?.status;
 | 
					 | 
				
			||||||
                if (respStatus == 404) {
 | 
					 | 
				
			||||||
                    ElMessage.error('请求接口不存在');
 | 
					 | 
				
			||||||
                    return rejectPromise;
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
                if (respStatus == 500) {
 | 
					 | 
				
			||||||
                    ElMessage.error('服务器响应异常');
 | 
					 | 
				
			||||||
                    return rejectPromise;
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                console.error(e);
 | 
					 | 
				
			||||||
                ElMessage.error('网络请求错误');
 | 
					 | 
				
			||||||
                return rejectPromise;
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            const result: Result = uaf.data.value as any;
 | 
					 | 
				
			||||||
            if (!result) {
 | 
					 | 
				
			||||||
                ElMessage.error('网络请求失败');
 | 
					 | 
				
			||||||
                return Promise.reject(result);
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            // 如果返回为成功结果,则将结果的data赋值给响应式data
 | 
					 | 
				
			||||||
            if (result.code === ResultEnum.SUCCESS) {
 | 
					 | 
				
			||||||
                uaf.data.value = result.data;
 | 
					 | 
				
			||||||
                return;
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            // 如果提示没有权限,则跳转至无权限页面
 | 
					 | 
				
			||||||
            if (result.code === ResultEnum.NO_PERMISSION) {
 | 
					 | 
				
			||||||
                router.push({
 | 
					 | 
				
			||||||
                    path: URL_401,
 | 
					 | 
				
			||||||
                });
 | 
					 | 
				
			||||||
                return Promise.reject(result);
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            // 如果返回的code不为成功,则会返回对应的错误msg,则直接统一通知即可。忽略登录超时或没有权限的提示(直接跳转至401页面)
 | 
					 | 
				
			||||||
            if (result.msg && result?.code != ResultEnum.NO_PERMISSION) {
 | 
					 | 
				
			||||||
                ElMessage.error(result.msg);
 | 
					 | 
				
			||||||
                uaf.error.value = new Error(result.msg);
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            return Promise.reject(result);
 | 
					 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
        isFetching: uaf.isFetching,
 | 
					        isFetching: uaf.isFetching,
 | 
				
			||||||
        data: uaf.data,
 | 
					        data: uaf.data,
 | 
				
			||||||
        abort: uaf.abort,
 | 
					        abort: uaf.abort,
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					let refreshingToken = false;
 | 
				
			||||||
 | 
					let queue: any[] = [];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					async function execUaf(uaf: any) {
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					        await uaf.execute(true);
 | 
				
			||||||
 | 
					    } catch (e: any) {
 | 
				
			||||||
 | 
					        const rejectPromise = Promise.reject(e);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (e?.name == 'AbortError') {
 | 
				
			||||||
 | 
					            console.log('请求已取消');
 | 
				
			||||||
 | 
					            return rejectPromise;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const respStatus = uaf.response.value?.status;
 | 
				
			||||||
 | 
					        if (respStatus == 404) {
 | 
				
			||||||
 | 
					            ElMessage.error('请求接口不存在');
 | 
				
			||||||
 | 
					            return rejectPromise;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        if (respStatus == 500) {
 | 
				
			||||||
 | 
					            ElMessage.error('服务器响应异常');
 | 
				
			||||||
 | 
					            return rejectPromise;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        console.error(e);
 | 
				
			||||||
 | 
					        ElMessage.error('网络请求错误');
 | 
				
			||||||
 | 
					        return rejectPromise;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const result: Result = uaf.data.value as any;
 | 
				
			||||||
 | 
					    if (!result) {
 | 
				
			||||||
 | 
					        ElMessage.error('网络请求失败');
 | 
				
			||||||
 | 
					        return Promise.reject(result);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const resultCode = result.code;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // 如果返回为成功结果,则将结果的data赋值给响应式data
 | 
				
			||||||
 | 
					    if (resultCode === ResultEnum.SUCCESS) {
 | 
				
			||||||
 | 
					        uaf.data.value = result.data;
 | 
				
			||||||
 | 
					        return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // 如果是accessToken失效,则使用refreshToken刷新token
 | 
				
			||||||
 | 
					    if (resultCode == ResultEnum.ACCESS_TOKEN_INVALID) {
 | 
				
			||||||
 | 
					        if (refreshingToken) {
 | 
				
			||||||
 | 
					            // 请求加入队列等待, 防止并发多次请求refreshToken
 | 
				
			||||||
 | 
					            return new Promise((resolve) => {
 | 
				
			||||||
 | 
					                queue.push(() => {
 | 
				
			||||||
 | 
					                    resolve(execUaf(uaf));
 | 
				
			||||||
 | 
					                });
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        try {
 | 
				
			||||||
 | 
					            refreshingToken = true;
 | 
				
			||||||
 | 
					            const res = await openApi.refreshToken({ refresh_token: getRefreshToken() });
 | 
				
			||||||
 | 
					            saveToken(res.token);
 | 
				
			||||||
 | 
					            saveRefreshToken(res.refresh_token);
 | 
				
			||||||
 | 
					            // 重新缓存后端用户权限code
 | 
				
			||||||
 | 
					            await openApi.getPermissions();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // 执行accessToken失效的请求
 | 
				
			||||||
 | 
					            queue.forEach((resolve: any) => {
 | 
				
			||||||
 | 
					                resolve();
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					        } catch (e: any) {
 | 
				
			||||||
 | 
					            clearUser();
 | 
				
			||||||
 | 
					        } finally {
 | 
				
			||||||
 | 
					            refreshingToken = false;
 | 
				
			||||||
 | 
					            queue = [];
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        await execUaf(uaf);
 | 
				
			||||||
 | 
					        return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // 如果提示没有权限,则跳转至无权限页面
 | 
				
			||||||
 | 
					    if (resultCode === ResultEnum.NO_PERMISSION) {
 | 
				
			||||||
 | 
					        router.push({
 | 
				
			||||||
 | 
					            path: URL_401,
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					        return Promise.reject(result);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // 如果返回的code不为成功,则会返回对应的错误msg,则直接统一通知即可。忽略登录超时或没有权限的提示(直接跳转至401页面)
 | 
				
			||||||
 | 
					    if (result.msg && resultCode != ResultEnum.NO_PERMISSION) {
 | 
				
			||||||
 | 
					        ElMessage.error(result.msg);
 | 
				
			||||||
 | 
					        uaf.error.value = new Error(result.msg);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return Promise.reject(result);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,18 +1,15 @@
 | 
				
			|||||||
<template>
 | 
					<template>
 | 
				
			||||||
    <div class="layout-navbars-breadcrumb" v-show="themeConfig.isBreadcrumb">
 | 
					    <div class="layout-navbars-breadcrumb" v-show="themeConfig.isBreadcrumb">
 | 
				
			||||||
        <SvgIcon class="layout-navbars-breadcrumb-icon" :name="themeConfig.isCollapse ? 'expand' : 'fold'"
 | 
					        <SvgIcon class="layout-navbars-breadcrumb-icon" :name="themeConfig.isCollapse ? 'expand' : 'fold'" @click="onThemeConfigChange" />
 | 
				
			||||||
            @click="onThemeConfigChange" />
 | 
					 | 
				
			||||||
        <el-breadcrumb class="layout-navbars-breadcrumb-hide">
 | 
					        <el-breadcrumb class="layout-navbars-breadcrumb-hide">
 | 
				
			||||||
            <transition-group name="breadcrumb" mode="out-in">
 | 
					            <transition-group name="breadcrumb" mode="out-in">
 | 
				
			||||||
                <el-breadcrumb-item v-for="(v, k) in state.breadcrumbList" :key="v.meta.title">
 | 
					                <el-breadcrumb-item v-for="(v, k) in state.breadcrumbList" :key="v.meta.title">
 | 
				
			||||||
                    <span v-if="k === state.breadcrumbList.length - 1" class="layout-navbars-breadcrumb-span">
 | 
					                    <span v-if="k === state.breadcrumbList.length - 1" class="layout-navbars-breadcrumb-span">
 | 
				
			||||||
                        <SvgIcon :name="v.meta.icon" class="layout-navbars-breadcrumb-iconfont"
 | 
					                        <SvgIcon :name="v.meta.icon" class="layout-navbars-breadcrumb-iconfont" v-if="themeConfig.isBreadcrumbIcon" />
 | 
				
			||||||
                            v-if="themeConfig.isBreadcrumbIcon" />
 | 
					 | 
				
			||||||
                        {{ v.meta.title }}
 | 
					                        {{ v.meta.title }}
 | 
				
			||||||
                    </span>
 | 
					                    </span>
 | 
				
			||||||
                    <a v-else @click.prevent="onBreadcrumbClick(v)">
 | 
					                    <a v-else @click.prevent="onBreadcrumbClick(v)">
 | 
				
			||||||
                        <SvgIcon :name="v.meta.icon" class="layout-navbars-breadcrumb-iconfont"
 | 
					                        <SvgIcon :name="v.meta.icon" class="layout-navbars-breadcrumb-iconfont" v-if="themeConfig.isBreadcrumbIcon" />
 | 
				
			||||||
                            v-if="themeConfig.isBreadcrumbIcon" />
 | 
					 | 
				
			||||||
                        {{ v.meta.title }}
 | 
					                        {{ v.meta.title }}
 | 
				
			||||||
                    </a>
 | 
					                    </a>
 | 
				
			||||||
                </el-breadcrumb-item>
 | 
					                </el-breadcrumb-item>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,17 +1,22 @@
 | 
				
			|||||||
<template>
 | 
					<template>
 | 
				
			||||||
    <div class="layout-search-dialog">
 | 
					    <div class="layout-search-dialog">
 | 
				
			||||||
        <el-dialog v-model="state.isShowSearch" width="300px" destroy-on-close :modal="false" fullscreen :show-close="false">
 | 
					        <el-dialog v-model="state.isShowSearch" width="300px" destroy-on-close :modal="false" fullscreen :show-close="false">
 | 
				
			||||||
            <el-autocomplete v-model="state.menuQuery" :fetch-suggestions="menuSearch" placeholder="菜单搜索"
 | 
					            <el-autocomplete
 | 
				
			||||||
                prefix-icon="el-icon-search" ref="layoutMenuAutocompleteRef" @select="onHandleSelect" @blur="onSearchBlur">
 | 
					                v-model="state.menuQuery"
 | 
				
			||||||
 | 
					                :fetch-suggestions="menuSearch"
 | 
				
			||||||
 | 
					                placeholder="菜单搜索"
 | 
				
			||||||
 | 
					                prefix-icon="el-icon-search"
 | 
				
			||||||
 | 
					                ref="layoutMenuAutocompleteRef"
 | 
				
			||||||
 | 
					                @select="onHandleSelect"
 | 
				
			||||||
 | 
					                @blur="onSearchBlur"
 | 
				
			||||||
 | 
					            >
 | 
				
			||||||
                <template #prefix>
 | 
					                <template #prefix>
 | 
				
			||||||
                    <el-icon class="el-input__icon">
 | 
					                    <el-icon class="el-input__icon">
 | 
				
			||||||
                        <search />
 | 
					                        <search />
 | 
				
			||||||
                    </el-icon>
 | 
					                    </el-icon>
 | 
				
			||||||
                </template>
 | 
					                </template>
 | 
				
			||||||
                <template #default="{ item }">
 | 
					                <template #default="{ item }">
 | 
				
			||||||
                    <div>
 | 
					                    <div><SvgIcon :name="item.meta.icon" class="mr5" />{{ item.meta.title }}</div>
 | 
				
			||||||
                        <SvgIcon :name="item.meta.icon" class="mr5" />{{ item.meta.title }}
 | 
					 | 
				
			||||||
                    </div>
 | 
					 | 
				
			||||||
                </template>
 | 
					                </template>
 | 
				
			||||||
            </el-autocomplete>
 | 
					            </el-autocomplete>
 | 
				
			||||||
        </el-dialog>
 | 
					        </el-dialog>
 | 
				
			||||||
@@ -23,7 +28,7 @@ import { reactive, ref, nextTick } from 'vue';
 | 
				
			|||||||
import { useRouter } from 'vue-router';
 | 
					import { useRouter } from 'vue-router';
 | 
				
			||||||
import { useRoutesList } from '@/store/routesList';
 | 
					import { useRoutesList } from '@/store/routesList';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const layoutMenuAutocompleteRef: any = ref(null);;
 | 
					const layoutMenuAutocompleteRef: any = ref(null);
 | 
				
			||||||
const router = useRouter();
 | 
					const router = useRouter();
 | 
				
			||||||
const state: any = reactive({
 | 
					const state: any = reactive({
 | 
				
			||||||
    isShowSearch: false,
 | 
					    isShowSearch: false,
 | 
				
			||||||
@@ -54,8 +59,7 @@ const menuSearch = (queryString: any, cb: any) => {
 | 
				
			|||||||
const createFilter = (queryString: any) => {
 | 
					const createFilter = (queryString: any) => {
 | 
				
			||||||
    return (restaurant: any) => {
 | 
					    return (restaurant: any) => {
 | 
				
			||||||
        return (
 | 
					        return (
 | 
				
			||||||
            restaurant.path.toLowerCase().indexOf(queryString.toLowerCase()) > -1 ||
 | 
					            restaurant.path.toLowerCase().indexOf(queryString.toLowerCase()) > -1 || restaurant.meta.title.toLowerCase().indexOf(queryString.toLowerCase()) > -1
 | 
				
			||||||
            restaurant.meta.title.toLowerCase().indexOf(queryString.toLowerCase()) > -1
 | 
					 | 
				
			||||||
        );
 | 
					        );
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
@@ -97,7 +101,7 @@ const onSearchBlur = () => {
 | 
				
			|||||||
    closeSearch();
 | 
					    closeSearch();
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
defineExpose({openSearch})
 | 
					defineExpose({ openSearch });
 | 
				
			||||||
</script>
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<style scoped lang="scss">
 | 
					<style scoped lang="scss">
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -5,26 +5,39 @@
 | 
				
			|||||||
                <!-- ssh终端主题 -->
 | 
					                <!-- ssh终端主题 -->
 | 
				
			||||||
                <el-divider content-position="left">终端主题</el-divider>
 | 
					                <el-divider content-position="left">终端主题</el-divider>
 | 
				
			||||||
                <div class="layout-breadcrumb-seting-bar-flex">
 | 
					                <div class="layout-breadcrumb-seting-bar-flex">
 | 
				
			||||||
                    <div class="layout-breadcrumb-seting-bar-flex-label">字体颜色</div>
 | 
					                    <div class="layout-breadcrumb-seting-bar-flex-label">主题</div>
 | 
				
			||||||
                    <div class="layout-breadcrumb-seting-bar-flex-value">
 | 
					                    <div class="layout-breadcrumb-seting-bar-flex-value">
 | 
				
			||||||
                        <el-color-picker v-model="themeConfig.terminalForeground" size="small" @change="onColorPickerChange('terminalForeground')">
 | 
					                        <el-select @change="setLocalThemeConfig" v-model="themeConfig.terminalTheme" size="small" style="width: 140px">
 | 
				
			||||||
                        </el-color-picker>
 | 
					                            <el-option v-for="(_, k) in themes" :key="k" :label="k" :value="k"> </el-option>
 | 
				
			||||||
 | 
					                            <el-option label="自定义" value="custom"> </el-option>
 | 
				
			||||||
 | 
					                        </el-select>
 | 
				
			||||||
                    </div>
 | 
					                    </div>
 | 
				
			||||||
                </div>
 | 
					                </div>
 | 
				
			||||||
                <div class="layout-breadcrumb-seting-bar-flex">
 | 
					                <template v-if="themeConfig.terminalTheme == 'custom'">
 | 
				
			||||||
                    <div class="layout-breadcrumb-seting-bar-flex-label">背景颜色</div>
 | 
					                    <div class="layout-breadcrumb-seting-bar-flex mt10">
 | 
				
			||||||
                    <div class="layout-breadcrumb-seting-bar-flex-value">
 | 
					                        <div class="layout-breadcrumb-seting-bar-flex-label">字体颜色</div>
 | 
				
			||||||
                        <el-color-picker v-model="themeConfig.terminalBackground" size="small" @change="onColorPickerChange('terminalBackground')">
 | 
					                        <div class="layout-breadcrumb-seting-bar-flex-value">
 | 
				
			||||||
                        </el-color-picker>
 | 
					                            <el-color-picker v-model="themeConfig.terminalForeground" size="small" @change="onColorPickerChange('terminalForeground')">
 | 
				
			||||||
 | 
					                            </el-color-picker>
 | 
				
			||||||
 | 
					                        </div>
 | 
				
			||||||
                    </div>
 | 
					                    </div>
 | 
				
			||||||
                </div>
 | 
					                    <div class="layout-breadcrumb-seting-bar-flex">
 | 
				
			||||||
                <div class="layout-breadcrumb-seting-bar-flex">
 | 
					                        <div class="layout-breadcrumb-seting-bar-flex-label">背景颜色</div>
 | 
				
			||||||
                    <div class="layout-breadcrumb-seting-bar-flex-label">cursor颜色</div>
 | 
					                        <div class="layout-breadcrumb-seting-bar-flex-value">
 | 
				
			||||||
                    <div class="layout-breadcrumb-seting-bar-flex-value">
 | 
					                            <el-color-picker v-model="themeConfig.terminalBackground" size="small" @change="onColorPickerChange('terminalBackground')">
 | 
				
			||||||
                        <el-color-picker v-model="themeConfig.terminalCursor" size="small" @change="onColorPickerChange('terminalCursor')"> </el-color-picker>
 | 
					                            </el-color-picker>
 | 
				
			||||||
 | 
					                        </div>
 | 
				
			||||||
                    </div>
 | 
					                    </div>
 | 
				
			||||||
                </div>
 | 
					                    <div class="layout-breadcrumb-seting-bar-flex">
 | 
				
			||||||
                <div class="layout-breadcrumb-seting-bar-flex mt15">
 | 
					                        <div class="layout-breadcrumb-seting-bar-flex-label">cursor颜色</div>
 | 
				
			||||||
 | 
					                        <div class="layout-breadcrumb-seting-bar-flex-value">
 | 
				
			||||||
 | 
					                            <el-color-picker v-model="themeConfig.terminalCursor" size="small" @change="onColorPickerChange('terminalCursor')">
 | 
				
			||||||
 | 
					                            </el-color-picker>
 | 
				
			||||||
 | 
					                        </div>
 | 
				
			||||||
 | 
					                    </div>
 | 
				
			||||||
 | 
					                </template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                <div class="layout-breadcrumb-seting-bar-flex mt10">
 | 
				
			||||||
                    <div class="layout-breadcrumb-seting-bar-flex-label">字体大小</div>
 | 
					                    <div class="layout-breadcrumb-seting-bar-flex-label">字体大小</div>
 | 
				
			||||||
                    <div class="layout-breadcrumb-seting-bar-flex-value">
 | 
					                    <div class="layout-breadcrumb-seting-bar-flex-value">
 | 
				
			||||||
                        <el-input-number
 | 
					                        <el-input-number
 | 
				
			||||||
@@ -39,7 +52,7 @@
 | 
				
			|||||||
                        </el-input-number>
 | 
					                        </el-input-number>
 | 
				
			||||||
                    </div>
 | 
					                    </div>
 | 
				
			||||||
                </div>
 | 
					                </div>
 | 
				
			||||||
                <div class="layout-breadcrumb-seting-bar-flex mt15">
 | 
					                <div class="layout-breadcrumb-seting-bar-flex mt10">
 | 
				
			||||||
                    <div class="layout-breadcrumb-seting-bar-flex-label">字体粗细</div>
 | 
					                    <div class="layout-breadcrumb-seting-bar-flex-label">字体粗细</div>
 | 
				
			||||||
                    <div class="layout-breadcrumb-seting-bar-flex-value">
 | 
					                    <div class="layout-breadcrumb-seting-bar-flex-value">
 | 
				
			||||||
                        <el-select @change="setLocalThemeConfig" v-model="themeConfig.terminalFontWeight" size="small" style="width: 90px">
 | 
					                        <el-select @change="setLocalThemeConfig" v-model="themeConfig.terminalFontWeight" size="small" style="width: 90px">
 | 
				
			||||||
@@ -418,6 +431,7 @@ import { useThemeConfig } from '@/store/themeConfig';
 | 
				
			|||||||
import { getLightColor } from '@/common/utils/theme';
 | 
					import { getLightColor } from '@/common/utils/theme';
 | 
				
			||||||
import { setLocal, getLocal, removeLocal } from '@/common/utils/storage';
 | 
					import { setLocal, getLocal, removeLocal } from '@/common/utils/storage';
 | 
				
			||||||
import mittBus from '@/common/utils/mitt';
 | 
					import mittBus from '@/common/utils/mitt';
 | 
				
			||||||
 | 
					import themes from '@/components/terminal/themes';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const copyConfigBtnRef = ref();
 | 
					const copyConfigBtnRef = ref();
 | 
				
			||||||
const { themeConfig } = storeToRefs(useThemeConfig());
 | 
					const { themeConfig } = storeToRefs(useThemeConfig());
 | 
				
			||||||
@@ -615,6 +629,9 @@ const setLocalThemeConfigStyle = () => {
 | 
				
			|||||||
};
 | 
					};
 | 
				
			||||||
// 一键复制配置
 | 
					// 一键复制配置
 | 
				
			||||||
const onCopyConfigClick = (target: any) => {
 | 
					const onCopyConfigClick = (target: any) => {
 | 
				
			||||||
 | 
					    if (!target) {
 | 
				
			||||||
 | 
					        return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
    let copyThemeConfig = getLocal('themeConfig');
 | 
					    let copyThemeConfig = getLocal('themeConfig');
 | 
				
			||||||
    copyThemeConfig.isDrawer = false;
 | 
					    copyThemeConfig.isDrawer = false;
 | 
				
			||||||
    const clipboard = new ClipboardJS(target, {
 | 
					    const clipboard = new ClipboardJS(target, {
 | 
				
			||||||
@@ -690,6 +707,25 @@ defineExpose({ openDrawer });
 | 
				
			|||||||
</script>
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<style scoped lang="scss">
 | 
					<style scoped lang="scss">
 | 
				
			||||||
 | 
					::v-deep(.el-drawer) {
 | 
				
			||||||
 | 
					    --el-drawer-padding-primary: unset !important;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    .el-drawer__header {
 | 
				
			||||||
 | 
					        padding: 0 15px !important;
 | 
				
			||||||
 | 
					        height: 50px;
 | 
				
			||||||
 | 
					        display: flex;
 | 
				
			||||||
 | 
					        align-items: center;
 | 
				
			||||||
 | 
					        margin-bottom: 0 !important;
 | 
				
			||||||
 | 
					        border-bottom: 1px solid var(--el-border-color);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    .el-drawer__body {
 | 
				
			||||||
 | 
					        width: 100%;
 | 
				
			||||||
 | 
					        height: 100%;
 | 
				
			||||||
 | 
					        overflow: auto;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.layout-breadcrumb-seting-bar {
 | 
					.layout-breadcrumb-seting-bar {
 | 
				
			||||||
    height: calc(100vh - 50px);
 | 
					    height: calc(100vh - 50px);
 | 
				
			||||||
    padding: 0 15px;
 | 
					    padding: 0 15px;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -174,12 +174,7 @@ watch(preDark, (newValue) => {
 | 
				
			|||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const switchDark = () => {
 | 
					const switchDark = () => {
 | 
				
			||||||
    themeConfig.value.isDark = isDark.value;
 | 
					    themeConfigStore.switchDark(isDark.value);
 | 
				
			||||||
    if (isDark.value) {
 | 
					 | 
				
			||||||
        themeConfig.value.editorTheme = 'vs-dark';
 | 
					 | 
				
			||||||
    } else {
 | 
					 | 
				
			||||||
        themeConfig.value.editorTheme = 'vs';
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    saveThemeConfig(themeConfig.value);
 | 
					    saveThemeConfig(themeConfig.value);
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -284,7 +284,9 @@ const onTagsClick = (v: any, k: number) => {
 | 
				
			|||||||
    state.tagsRefsIndex = k;
 | 
					    state.tagsRefsIndex = k;
 | 
				
			||||||
    try {
 | 
					    try {
 | 
				
			||||||
        router.push(v);
 | 
					        router.push(v);
 | 
				
			||||||
    } catch (e) {}
 | 
					    } catch (e) {
 | 
				
			||||||
 | 
					        // skip
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
// 更新滚动条显示
 | 
					// 更新滚动条显示
 | 
				
			||||||
const updateScrollbar = () => {
 | 
					const updateScrollbar = () => {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -89,13 +89,18 @@ type RouterConvCallbackFunc = (router: any) => void;
 | 
				
			|||||||
 * @param meta.link ==> 外链地址
 | 
					 * @param meta.link ==> 外链地址
 | 
				
			||||||
 * */
 | 
					 * */
 | 
				
			||||||
export function backEndRouterConverter(routes: any, callbackFunc: RouterConvCallbackFunc = null as any, parentPath: string = '/') {
 | 
					export function backEndRouterConverter(routes: any, callbackFunc: RouterConvCallbackFunc = null as any, parentPath: string = '/') {
 | 
				
			||||||
    if (!routes) return [];
 | 
					    if (!routes) {
 | 
				
			||||||
    return routes.map((item: any) => {
 | 
					        return [];
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const routeItems = [];
 | 
				
			||||||
 | 
					    for (let item of routes) {
 | 
				
			||||||
        if (!item.meta) {
 | 
					        if (!item.meta) {
 | 
				
			||||||
            return item;
 | 
					            return item;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        // 将json字符串的meta转为对象
 | 
					        // 将json字符串的meta转为对象
 | 
				
			||||||
        item.meta = JSON.parse(item.meta);
 | 
					        item.meta = JSON.parse(item.meta);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // 将meta.comoponet 解析为route.component
 | 
					        // 将meta.comoponet 解析为route.component
 | 
				
			||||||
        if (item.meta.component) {
 | 
					        if (item.meta.component) {
 | 
				
			||||||
            item.component = dynamicImport(dynamicViewsModules, item.meta.component);
 | 
					            item.component = dynamicImport(dynamicViewsModules, item.meta.component);
 | 
				
			||||||
@@ -126,8 +131,10 @@ export function backEndRouterConverter(routes: any, callbackFunc: RouterConvCall
 | 
				
			|||||||
        // 存在回调,则执行回调
 | 
					        // 存在回调,则执行回调
 | 
				
			||||||
        callbackFunc && callbackFunc(item);
 | 
					        callbackFunc && callbackFunc(item);
 | 
				
			||||||
        item.children && backEndRouterConverter(item.children, callbackFunc, item.path);
 | 
					        item.children && backEndRouterConverter(item.children, callbackFunc, item.path);
 | 
				
			||||||
        return item;
 | 
					        routeItems.push(item);
 | 
				
			||||||
    });
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return routeItems;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
@@ -152,6 +159,6 @@ export function dynamicImport(dynamicViewsModules: Record<string, Function>, com
 | 
				
			|||||||
        return null;
 | 
					        return null;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    console.error(`未匹配到[${component}]组件名对应的组件文件`);
 | 
					    console.warn(`未匹配到[${component}]组件名对应的组件文件`);
 | 
				
			||||||
    return null;
 | 
					    return null;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -92,7 +92,7 @@ router.beforeEach(async (to, from, next) => {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // 终端不需要连接系统websocket消息
 | 
					    // 终端不需要连接系统websocket消息
 | 
				
			||||||
    if (to.path != '/machine/terminal') {
 | 
					    if (to.path != '/machine/terminal' && to.path != '/machine/terminal-rdp') {
 | 
				
			||||||
        syssocket.init();
 | 
					        syssocket.init();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -54,6 +54,17 @@ export const staticRoutes: Array<RouteRecordRaw> = [
 | 
				
			|||||||
            titleRename: true,
 | 
					            titleRename: true,
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        path: '/machine/terminal-rdp',
 | 
				
			||||||
 | 
					        name: 'machineTerminalRdp',
 | 
				
			||||||
 | 
					        component: () => import('@/views/ops/machine/RdpTerminalPage.vue'),
 | 
				
			||||||
 | 
					        meta: {
 | 
				
			||||||
 | 
					            // 将路径 'xxx?name=名字' 里的name字段值替换到title里
 | 
				
			||||||
 | 
					            title: '终端 | {name}',
 | 
				
			||||||
 | 
					            // 是否根据query对标题名进行参数替换,即最终显示为‘终端_机器名’
 | 
				
			||||||
 | 
					            titleRename: true,
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
];
 | 
					];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// 错误页面路由
 | 
					// 错误页面路由
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										29
									
								
								mayfly_go_web/src/store/autoOpenResource.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								mayfly_go_web/src/store/autoOpenResource.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,29 @@
 | 
				
			|||||||
 | 
					import { defineStore } from 'pinia';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * 自动打开资源
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					export const useAutoOpenResource = defineStore('autoOpenResource', {
 | 
				
			||||||
 | 
					    state: () => ({
 | 
				
			||||||
 | 
					        autoOpenResource: {
 | 
				
			||||||
 | 
					            machineCodePath: '',
 | 
				
			||||||
 | 
					            dbCodePath: '',
 | 
				
			||||||
 | 
					            redisCodePath: '',
 | 
				
			||||||
 | 
					            mongoCodePath: '',
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					    }),
 | 
				
			||||||
 | 
					    actions: {
 | 
				
			||||||
 | 
					        setMachineCodePath(codePath: string) {
 | 
				
			||||||
 | 
					            this.autoOpenResource.machineCodePath = codePath;
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        setDbCodePath(codePath: string) {
 | 
				
			||||||
 | 
					            this.autoOpenResource.dbCodePath = codePath;
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        setRedisCodePath(codePath: string) {
 | 
				
			||||||
 | 
					            this.autoOpenResource.redisCodePath = codePath;
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        setMongoCodePath(codePath: string) {
 | 
				
			||||||
 | 
					            this.autoOpenResource.mongoCodePath = codePath;
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
@@ -1,5 +1,5 @@
 | 
				
			|||||||
import { defineStore } from 'pinia';
 | 
					import { defineStore } from 'pinia';
 | 
				
			||||||
import { dateFormat2 } from '@/common/utils/date';
 | 
					import { formatDate } from '@/common/utils/format';
 | 
				
			||||||
import { useUserInfo } from '@/store/userInfo';
 | 
					import { useUserInfo } from '@/store/userInfo';
 | 
				
			||||||
import { getSysStyleConfig } from '@/common/sysconfig';
 | 
					import { getSysStyleConfig } from '@/common/sysconfig';
 | 
				
			||||||
import { getLocal, getThemeConfig } from '@/common/utils/storage';
 | 
					import { getLocal, getThemeConfig } from '@/common/utils/storage';
 | 
				
			||||||
@@ -114,6 +114,7 @@ export const useThemeConfig = defineStore('themeConfig', {
 | 
				
			|||||||
            // 默认布局,可选 1、默认 defaults 2、经典 classic 3、横向 transverse 4、分栏 columns
 | 
					            // 默认布局,可选 1、默认 defaults 2、经典 classic 3、横向 transverse 4、分栏 columns
 | 
				
			||||||
            layout: 'classic',
 | 
					            layout: 'classic',
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            terminalTheme: 'light',
 | 
				
			||||||
            // ssh终端字体颜色
 | 
					            // ssh终端字体颜色
 | 
				
			||||||
            terminalForeground: '#C5C8C6',
 | 
					            terminalForeground: '#C5C8C6',
 | 
				
			||||||
            // ssh终端背景色
 | 
					            // ssh终端背景色
 | 
				
			||||||
@@ -190,7 +191,25 @@ export const useThemeConfig = defineStore('themeConfig', {
 | 
				
			|||||||
        },
 | 
					        },
 | 
				
			||||||
        // 设置水印时间为当前时间
 | 
					        // 设置水印时间为当前时间
 | 
				
			||||||
        setWatermarkNowTime() {
 | 
					        setWatermarkNowTime() {
 | 
				
			||||||
            this.themeConfig.watermarkText[1] = dateFormat2('yyyy-MM-dd HH:mm:ss', new Date());
 | 
					            this.themeConfig.watermarkText[1] = formatDate(new Date());
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        // 切换暗黑模式
 | 
				
			||||||
 | 
					        switchDark(isDark: boolean) {
 | 
				
			||||||
 | 
					            this.themeConfig.isDark = isDark;
 | 
				
			||||||
 | 
					            // 切换编辑器主题
 | 
				
			||||||
 | 
					            if (isDark) {
 | 
				
			||||||
 | 
					                this.themeConfig.editorTheme = 'vs-dark';
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					                this.themeConfig.editorTheme = 'vs';
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            // 如果终端主题不是自定义主题,则切换主题
 | 
				
			||||||
 | 
					            if (this.themeConfig.terminalTheme != 'custom') {
 | 
				
			||||||
 | 
					                if (isDark) {
 | 
				
			||||||
 | 
					                    this.themeConfig.terminalTheme = 'dark';
 | 
				
			||||||
 | 
					                } else {
 | 
				
			||||||
 | 
					                    this.themeConfig.terminalTheme = 'light';
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -35,7 +35,7 @@ body,
 | 
				
			|||||||
    width: 100%;
 | 
					    width: 100%;
 | 
				
			||||||
    height: 100%;
 | 
					    height: 100%;
 | 
				
			||||||
    font-family: 'Helvetica Neue', Helvetica, 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', '微软雅黑', Arial, sans-serif;
 | 
					    font-family: 'Helvetica Neue', Helvetica, 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', '微软雅黑', Arial, sans-serif;
 | 
				
			||||||
    font-weight: 450;
 | 
					    font-weight: 500;
 | 
				
			||||||
    -webkit-font-smoothing: antialiased;
 | 
					    -webkit-font-smoothing: antialiased;
 | 
				
			||||||
    -webkit-tap-highlight-color: transparent;
 | 
					    -webkit-tap-highlight-color: transparent;
 | 
				
			||||||
    background-color: var(--bg-main-color);
 | 
					    background-color: var(--bg-main-color);
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1 +1 @@
 | 
				
			|||||||
@import 'common/transition.scss';
 | 
					@use 'common/transition.scss';
 | 
				
			||||||
@@ -1,4 +1,4 @@
 | 
				
			|||||||
@import 'mixins/index.scss';
 | 
					@use 'mixins/index' as mixins;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/* Button 按钮
 | 
					/* Button 按钮
 | 
				
			||||||
------------------------------- */
 | 
					------------------------------- */
 | 
				
			||||||
@@ -7,335 +7,353 @@
 | 
				
			|||||||
------------------------------- */
 | 
					------------------------------- */
 | 
				
			||||||
// 菜单搜索
 | 
					// 菜单搜索
 | 
				
			||||||
.el-autocomplete-suggestion__wrap {
 | 
					.el-autocomplete-suggestion__wrap {
 | 
				
			||||||
	max-height: 280px !important;
 | 
					    max-height: 280px !important;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/* Form 表单
 | 
					/* Form 表单
 | 
				
			||||||
------------------------------- */
 | 
					------------------------------- */
 | 
				
			||||||
.el-form {
 | 
					.el-form {
 | 
				
			||||||
	// 用于修改弹窗时表单内容间隔太大问题,如系统设置的新增菜单弹窗里的表单内容
 | 
					
 | 
				
			||||||
	.el-form-item:last-of-type {
 | 
					    // 用于修改弹窗时表单内容间隔太大问题,如系统设置的新增菜单弹窗里的表单内容
 | 
				
			||||||
		margin-bottom: 0 !important;
 | 
					    .el-form-item:last-of-type {
 | 
				
			||||||
	}
 | 
					        margin-bottom: 0 !important;
 | 
				
			||||||
	// 修复行内表单最后一个 el-form-item 位置下移问题
 | 
					    }
 | 
				
			||||||
	&.el-form--inline {
 | 
					
 | 
				
			||||||
		.el-form-item--large.el-form-item:last-of-type {
 | 
					    // 修复行内表单最后一个 el-form-item 位置下移问题
 | 
				
			||||||
			margin-bottom: 22px !important;
 | 
					    &.el-form--inline {
 | 
				
			||||||
		}
 | 
					        .el-form-item--large.el-form-item:last-of-type {
 | 
				
			||||||
		.el-form-item--default.el-form-item:last-of-type,
 | 
					            margin-bottom: 22px !important;
 | 
				
			||||||
		.el-form-item--small.el-form-item:last-of-type {
 | 
					        }
 | 
				
			||||||
			margin-bottom: 18px !important;
 | 
					
 | 
				
			||||||
		}
 | 
					        .el-form-item--default.el-form-item:last-of-type,
 | 
				
			||||||
	}
 | 
					        .el-form-item--small.el-form-item:last-of-type {
 | 
				
			||||||
	// https://gitee.com/lyt-top/vue-next-admin/issues/I5K1PM
 | 
					            margin-bottom: 18px !important;
 | 
				
			||||||
	.el-form-item .el-form-item__label .el-icon {
 | 
					        }
 | 
				
			||||||
		margin-right: 0px;
 | 
					    }
 | 
				
			||||||
	}
 | 
					
 | 
				
			||||||
 | 
					    // https://gitee.com/lyt-top/vue-next-admin/issues/I5K1PM
 | 
				
			||||||
 | 
					    .el-form-item .el-form-item__label .el-icon {
 | 
				
			||||||
 | 
					        margin-right: 0px;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/* Alert 警告
 | 
					/* Alert 警告
 | 
				
			||||||
------------------------------- */
 | 
					------------------------------- */
 | 
				
			||||||
.el-alert {
 | 
					.el-alert {
 | 
				
			||||||
	border: 1px solid;
 | 
					    border: 1px solid;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.el-alert__title {
 | 
					.el-alert__title {
 | 
				
			||||||
	word-break: break-all;
 | 
					    word-break: break-all;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/* Message 消息提示
 | 
					/* Message 消息提示
 | 
				
			||||||
------------------------------- */
 | 
					------------------------------- */
 | 
				
			||||||
.el-message {
 | 
					.el-message {
 | 
				
			||||||
	min-width: unset !important;
 | 
					    min-width: unset !important;
 | 
				
			||||||
	padding: 15px !important;
 | 
					    padding: 15px !important;
 | 
				
			||||||
	box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.02);
 | 
					    box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.02);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/* NavMenu 导航菜单
 | 
					/* NavMenu 导航菜单
 | 
				
			||||||
------------------------------- */
 | 
					------------------------------- */
 | 
				
			||||||
// 鼠标 hover 时颜色
 | 
					// 鼠标 hover 时颜色
 | 
				
			||||||
.el-menu-hover-bg-color {
 | 
					.el-menu-hover-bg-color {
 | 
				
			||||||
	background-color: var(--bg-menuBarActiveColor) !important;
 | 
					    background-color: var(--bg-menuBarActiveColor) !important;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// 默认样式修改
 | 
					// 默认样式修改
 | 
				
			||||||
.el-menu {
 | 
					.el-menu {
 | 
				
			||||||
	border-right: none !important;
 | 
					    border-right: none !important;
 | 
				
			||||||
	width: 220px;
 | 
					    width: 220px;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.el-menu-item {
 | 
					.el-menu-item {
 | 
				
			||||||
	height: 56px !important;
 | 
					    height: 56px !important;
 | 
				
			||||||
	line-height: 56px !important;
 | 
					    line-height: 56px !important;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.el-menu-item,
 | 
					.el-menu-item,
 | 
				
			||||||
.el-sub-menu__title {
 | 
					.el-sub-menu__title {
 | 
				
			||||||
	color: var(--bg-menuBarColor);
 | 
					    color: var(--bg-menuBarColor);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// 修复点击左侧菜单折叠再展开时,宽度不跟随问题
 | 
					// 修复点击左侧菜单折叠再展开时,宽度不跟随问题
 | 
				
			||||||
.el-menu--collapse {
 | 
					.el-menu--collapse {
 | 
				
			||||||
	width: 64px !important;
 | 
					    width: 64px !important;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// 外部链接时
 | 
					// 外部链接时
 | 
				
			||||||
.el-menu-item a,
 | 
					.el-menu-item a,
 | 
				
			||||||
.el-menu-item a:hover,
 | 
					.el-menu-item a:hover,
 | 
				
			||||||
.el-menu-item i,
 | 
					.el-menu-item i,
 | 
				
			||||||
.el-sub-menu__title i {
 | 
					.el-sub-menu__title i {
 | 
				
			||||||
	color: inherit;
 | 
					    color: inherit;
 | 
				
			||||||
	text-decoration: none;
 | 
					    text-decoration: none;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// 第三方图标字体间距/大小设置
 | 
					// 第三方图标字体间距/大小设置
 | 
				
			||||||
.el-menu-item .iconfont,
 | 
					.el-menu-item .iconfont,
 | 
				
			||||||
.el-sub-menu .iconfont,
 | 
					.el-sub-menu .iconfont,
 | 
				
			||||||
.el-menu-item .fa,
 | 
					.el-menu-item .fa,
 | 
				
			||||||
.el-sub-menu .fa {
 | 
					.el-sub-menu .fa {
 | 
				
			||||||
	@include generalIcon;
 | 
					    @include mixins.generalIcon;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// 水平菜单、横向菜单高亮 背景色,鼠标 hover 时,有子级菜单的背景色
 | 
					// 水平菜单、横向菜单高亮 背景色,鼠标 hover 时,有子级菜单的背景色
 | 
				
			||||||
.el-menu-item.is-active,
 | 
					.el-menu-item.is-active,
 | 
				
			||||||
.el-sub-menu.is-active .el-sub-menu__title,
 | 
					.el-sub-menu.is-active .el-sub-menu__title,
 | 
				
			||||||
.el-sub-menu:not(.is-opened):hover .el-sub-menu__title {
 | 
					.el-sub-menu:not(.is-opened):hover .el-sub-menu__title {
 | 
				
			||||||
	@extend .el-menu-hover-bg-color;
 | 
					    @extend .el-menu-hover-bg-color;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.el-menu-item:hover {
 | 
					.el-menu-item:hover {
 | 
				
			||||||
	@extend .el-menu-hover-bg-color;
 | 
					    @extend .el-menu-hover-bg-color;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.el-sub-menu.is-active.is-opened .el-sub-menu__title {
 | 
					.el-sub-menu.is-active.is-opened .el-sub-menu__title {
 | 
				
			||||||
	background-color: unset !important;
 | 
					    background-color: unset !important;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// 子级菜单背景颜色
 | 
					// 子级菜单背景颜色
 | 
				
			||||||
// .el-menu--inline {
 | 
					// .el-menu--inline {
 | 
				
			||||||
// 	background: var(--next-bg-menuBar-light-1);
 | 
					// 	background: var(--next-bg-menuBar-light-1);
 | 
				
			||||||
// }
 | 
					// }
 | 
				
			||||||
// 水平菜单、横向菜单折叠 a 标签
 | 
					// 水平菜单、横向菜单折叠 a 标签
 | 
				
			||||||
.el-popper.is-dark a {
 | 
					.el-popper.is-dark a {
 | 
				
			||||||
	color: var(--el-color-white) !important;
 | 
					    color: var(--el-color-white) !important;
 | 
				
			||||||
	text-decoration: none;
 | 
					    text-decoration: none;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// 水平菜单、横向菜单折叠背景色
 | 
					// 水平菜单、横向菜单折叠背景色
 | 
				
			||||||
.el-popper.is-pure.is-light {
 | 
					.el-popper.is-pure.is-light {
 | 
				
			||||||
	// 水平菜单
 | 
					
 | 
				
			||||||
	.el-menu--vertical {
 | 
					    // 水平菜单
 | 
				
			||||||
		background: var(--bg-menuBar);
 | 
					    .el-menu--vertical {
 | 
				
			||||||
		.el-sub-menu.is-active .el-sub-menu__title {
 | 
					        background: var(--bg-menuBar);
 | 
				
			||||||
			color: var(--el-menu-active-color);
 | 
					
 | 
				
			||||||
		}
 | 
					        .el-sub-menu.is-active .el-sub-menu__title {
 | 
				
			||||||
		.el-popper.is-pure.is-light {
 | 
					            color: var(--el-menu-active-color);
 | 
				
			||||||
			.el-menu--vertical {
 | 
					        }
 | 
				
			||||||
				.el-sub-menu .el-sub-menu__title {
 | 
					
 | 
				
			||||||
					background-color: unset !important;
 | 
					        .el-popper.is-pure.is-light {
 | 
				
			||||||
					color: var(--bg-menuBarColor);
 | 
					            .el-menu--vertical {
 | 
				
			||||||
				}
 | 
					                .el-sub-menu .el-sub-menu__title {
 | 
				
			||||||
				.el-sub-menu.is-active .el-sub-menu__title {
 | 
					                    background-color: unset !important;
 | 
				
			||||||
					color: var(--el-menu-active-color);
 | 
					                    color: var(--bg-menuBarColor);
 | 
				
			||||||
				}
 | 
					                }
 | 
				
			||||||
			}
 | 
					
 | 
				
			||||||
		}
 | 
					                .el-sub-menu.is-active .el-sub-menu__title {
 | 
				
			||||||
	}
 | 
					                    color: var(--el-menu-active-color);
 | 
				
			||||||
	// 横向菜单
 | 
					                }
 | 
				
			||||||
	.el-menu--horizontal {
 | 
					            }
 | 
				
			||||||
		background: var(--bg-topBar);
 | 
					        }
 | 
				
			||||||
		.el-menu-item,
 | 
					    }
 | 
				
			||||||
		.el-sub-menu {
 | 
					
 | 
				
			||||||
			height: 48px !important;
 | 
					    // 横向菜单
 | 
				
			||||||
			line-height: 48px !important;
 | 
					    .el-menu--horizontal {
 | 
				
			||||||
			color: var(--bg-topBarColor);
 | 
					        background: var(--bg-topBar);
 | 
				
			||||||
			.el-sub-menu__title {
 | 
					
 | 
				
			||||||
				height: 48px !important;
 | 
					        .el-menu-item,
 | 
				
			||||||
				line-height: 48px !important;
 | 
					        .el-sub-menu {
 | 
				
			||||||
				color: var(--bg-topBarColor);
 | 
					            height: 48px !important;
 | 
				
			||||||
			}
 | 
					            line-height: 48px !important;
 | 
				
			||||||
			.el-popper.is-pure.is-light {
 | 
					            color: var(--bg-topBarColor);
 | 
				
			||||||
				.el-menu--horizontal {
 | 
					
 | 
				
			||||||
					.el-sub-menu .el-sub-menu__title {
 | 
					            .el-sub-menu__title {
 | 
				
			||||||
						background-color: unset !important;
 | 
					                height: 48px !important;
 | 
				
			||||||
						color: var(--bg-topBarColor);
 | 
					                line-height: 48px !important;
 | 
				
			||||||
					}
 | 
					                color: var(--bg-topBarColor);
 | 
				
			||||||
					.el-sub-menu.is-active .el-sub-menu__title {
 | 
					            }
 | 
				
			||||||
						color: var(--el-menu-active-color);
 | 
					
 | 
				
			||||||
					}
 | 
					            .el-popper.is-pure.is-light {
 | 
				
			||||||
				}
 | 
					                .el-menu--horizontal {
 | 
				
			||||||
			}
 | 
					                    .el-sub-menu .el-sub-menu__title {
 | 
				
			||||||
		}
 | 
					                        background-color: unset !important;
 | 
				
			||||||
		.el-menu-item.is-active,
 | 
					                        color: var(--bg-topBarColor);
 | 
				
			||||||
		.el-sub-menu.is-active .el-sub-menu__title {
 | 
					                    }
 | 
				
			||||||
			color: var(--el-menu-active-color);
 | 
					
 | 
				
			||||||
		}
 | 
					                    .el-sub-menu.is-active .el-sub-menu__title {
 | 
				
			||||||
	}
 | 
					                        color: var(--el-menu-active-color);
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        .el-menu-item.is-active,
 | 
				
			||||||
 | 
					        .el-sub-menu.is-active .el-sub-menu__title {
 | 
				
			||||||
 | 
					            color: var(--el-menu-active-color);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// 横向菜单(经典、横向)布局
 | 
					// 横向菜单(经典、横向)布局
 | 
				
			||||||
.el-menu.el-menu--horizontal {
 | 
					.el-menu.el-menu--horizontal {
 | 
				
			||||||
	border-bottom: none !important;
 | 
					    border-bottom: none !important;
 | 
				
			||||||
	width: 100% !important;
 | 
					    width: 100% !important;
 | 
				
			||||||
	.el-menu-item,
 | 
					
 | 
				
			||||||
	.el-sub-menu__title {
 | 
					    .el-menu-item,
 | 
				
			||||||
		height: 48px !important;
 | 
					    .el-sub-menu__title {
 | 
				
			||||||
		color: var(--bg-topBarColor);
 | 
					        height: 48px !important;
 | 
				
			||||||
	}
 | 
					        color: var(--bg-topBarColor);
 | 
				
			||||||
	.el-menu-item:not(.is-active):hover,
 | 
					    }
 | 
				
			||||||
	.el-sub-menu:not(.is-active):hover .el-sub-menu__title {
 | 
					
 | 
				
			||||||
		color: var(--bg-topBarColor);
 | 
					    .el-menu-item:not(.is-active):hover,
 | 
				
			||||||
	}
 | 
					    .el-sub-menu:not(.is-active):hover .el-sub-menu__title {
 | 
				
			||||||
 | 
					        color: var(--bg-topBarColor);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// 菜单收起时,图标不居中问题
 | 
					// 菜单收起时,图标不居中问题
 | 
				
			||||||
.el-menu--collapse {
 | 
					.el-menu--collapse {
 | 
				
			||||||
	.el-menu-item .iconfont,
 | 
					
 | 
				
			||||||
	.el-sub-menu .iconfont,
 | 
					    .el-menu-item .iconfont,
 | 
				
			||||||
	.el-menu-item .fa,
 | 
					    .el-sub-menu .iconfont,
 | 
				
			||||||
	.el-sub-menu .fa {
 | 
					    .el-menu-item .fa,
 | 
				
			||||||
		margin-right: 0 !important;
 | 
					    .el-sub-menu .fa {
 | 
				
			||||||
	}
 | 
					        margin-right: 0 !important;
 | 
				
			||||||
	.el-sub-menu__title {
 | 
					    }
 | 
				
			||||||
		padding-right: 0 !important;
 | 
					
 | 
				
			||||||
	}
 | 
					    .el-sub-menu__title {
 | 
				
			||||||
 | 
					        padding-right: 0 !important;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/* Tabs 标签页
 | 
					/* Tabs 标签页
 | 
				
			||||||
------------------------------- */
 | 
					------------------------------- */
 | 
				
			||||||
.el-tabs__nav-wrap::after {
 | 
					.el-tabs__nav-wrap::after {
 | 
				
			||||||
	height: 1px !important;
 | 
					    height: 1px !important;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/* Dropdown 下拉菜单
 | 
					/* Dropdown 下拉菜单
 | 
				
			||||||
------------------------------- */
 | 
					------------------------------- */
 | 
				
			||||||
.el-dropdown-menu {
 | 
					.el-dropdown-menu {
 | 
				
			||||||
	list-style: none !important; /*修复 Dropdown 下拉菜单样式问题 2022.03.04*/
 | 
					    list-style: none !important;
 | 
				
			||||||
}
 | 
					    /*修复 Dropdown 下拉菜单样式问题 2022.03.04*/
 | 
				
			||||||
.el-dropdown-menu .el-dropdown-menu__item {
 | 
					 | 
				
			||||||
	white-space: nowrap;
 | 
					 | 
				
			||||||
	&:not(.is-disabled):hover {
 | 
					 | 
				
			||||||
		background-color: var(--el-dropdown-menuItem-hover-fill);
 | 
					 | 
				
			||||||
		color: var(--el-dropdown-menuItem-hover-color);
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/* Steps 步骤条
 | 
					.el-dropdown-menu .el-dropdown-menu__item {
 | 
				
			||||||
------------------------------- */
 | 
					    white-space: nowrap;
 | 
				
			||||||
.el-step__icon-inner {
 | 
					
 | 
				
			||||||
	font-size: 30px !important;
 | 
					    &:not(.is-disabled):hover {
 | 
				
			||||||
	font-weight: 400 !important;
 | 
					        background-color: var(--el-dropdown-menuItem-hover-fill);
 | 
				
			||||||
}
 | 
					        color: var(--el-dropdown-menuItem-hover-color);
 | 
				
			||||||
.el-step__title {
 | 
					    }
 | 
				
			||||||
	font-size: 14px;
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/* Dialog 对话框
 | 
					/* Dialog 对话框
 | 
				
			||||||
------------------------------- */
 | 
					------------------------------- */
 | 
				
			||||||
.el-overlay {
 | 
					.el-overlay {
 | 
				
			||||||
	overflow: hidden;
 | 
					    overflow: hidden;
 | 
				
			||||||
	.el-overlay-dialog {
 | 
					
 | 
				
			||||||
		display: flex;
 | 
					    .el-overlay-dialog {
 | 
				
			||||||
		align-items: center;
 | 
					        display: flex;
 | 
				
			||||||
		justify-content: center;
 | 
					        align-items: center;
 | 
				
			||||||
		position: unset !important;
 | 
					        justify-content: center;
 | 
				
			||||||
		width: 100%;
 | 
					        position: unset !important;
 | 
				
			||||||
		height: 100%;
 | 
					        width: 100%;
 | 
				
			||||||
		.el-dialog {
 | 
					        height: 100%;
 | 
				
			||||||
			margin: 0 auto !important;
 | 
					
 | 
				
			||||||
			position: absolute;
 | 
					        .el-dialog {
 | 
				
			||||||
			.el-dialog__body {
 | 
					            margin: 0 auto !important;
 | 
				
			||||||
				padding: 20px !important;
 | 
					            position: absolute;
 | 
				
			||||||
			}
 | 
					
 | 
				
			||||||
		}
 | 
					            .el-dialog__body {
 | 
				
			||||||
	}
 | 
					                padding: 20px !important;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.el-dialog__body {
 | 
					.el-dialog__body {
 | 
				
			||||||
	max-height: calc(90vh - 111px) !important;
 | 
					    max-height: calc(90vh - 111px) !important;
 | 
				
			||||||
	overflow-y: auto;
 | 
					    overflow-y: auto;
 | 
				
			||||||
	overflow-x: hidden;
 | 
					    overflow-x: hidden;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/* Card 卡片
 | 
					/* Card 卡片
 | 
				
			||||||
------------------------------- */
 | 
					------------------------------- */
 | 
				
			||||||
.el-card__header {
 | 
					.el-card__header {
 | 
				
			||||||
	padding: 15px 20px;
 | 
					    padding: 15px 20px;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/* Table 表格 element plus 2.2.0 版本
 | 
					/* Table 表格 element plus 2.2.0 版本
 | 
				
			||||||
------------------------------- */
 | 
					------------------------------- */
 | 
				
			||||||
.el-table {
 | 
					.el-table {
 | 
				
			||||||
	.el-button.is-text {
 | 
					    .el-button.is-text {
 | 
				
			||||||
		padding: 0;
 | 
					        padding: 0;
 | 
				
			||||||
	}
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/* scrollbar
 | 
					/* scrollbar
 | 
				
			||||||
------------------------------- */
 | 
					------------------------------- */
 | 
				
			||||||
.el-scrollbar__bar {
 | 
					.el-scrollbar__bar {
 | 
				
			||||||
	z-index: 4;
 | 
					    z-index: 4;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/*防止页面切换时,滚动条高度不变的问题(滚动条高度非滚动条滚动高度)*/
 | 
					/*防止页面切换时,滚动条高度不变的问题(滚动条高度非滚动条滚动高度)*/
 | 
				
			||||||
.el-scrollbar__wrap {
 | 
					.el-scrollbar__wrap {
 | 
				
			||||||
	max-height: 100%;
 | 
					    max-height: 100%;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.el-select-dropdown .el-scrollbar__wrap {
 | 
					.el-select-dropdown .el-scrollbar__wrap {
 | 
				
			||||||
	overflow-x: scroll !important;
 | 
					    overflow-x: scroll !important;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/*修复Select 选择器高度问题*/
 | 
					/*修复Select 选择器高度问题*/
 | 
				
			||||||
.el-select-dropdown__wrap {
 | 
					.el-select-dropdown__wrap {
 | 
				
			||||||
	max-height: 274px !important;
 | 
					    max-height: 274px !important;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/*修复Cascader 级联选择器高度问题*/
 | 
					/*修复Cascader 级联选择器高度问题*/
 | 
				
			||||||
.el-cascader-menu__wrap.el-scrollbar__wrap {
 | 
					.el-cascader-menu__wrap.el-scrollbar__wrap {
 | 
				
			||||||
	height: 204px !important;
 | 
					    height: 204px !important;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/*用于界面高度自适应(main.vue),区分 scrollbar__view,防止其它使用 scrollbar 的地方出现滚动条消失*/
 | 
					/*用于界面高度自适应(main.vue),区分 scrollbar__view,防止其它使用 scrollbar 的地方出现滚动条消失*/
 | 
				
			||||||
.layout-container-view .el-scrollbar__view {
 | 
					.layout-container-view .el-scrollbar__view {
 | 
				
			||||||
	height: 100%;
 | 
					    height: 100%;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/*防止分栏布局二级菜单很多时,滚动条消失问题*/
 | 
					/*防止分栏布局二级菜单很多时,滚动条消失问题*/
 | 
				
			||||||
.layout-columns-warp .layout-aside .el-scrollbar__view {
 | 
					.layout-columns-warp .layout-aside .el-scrollbar__view {
 | 
				
			||||||
	height: unset !important;
 | 
					    height: unset !important;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/* Pagination 分页
 | 
					/* Pagination 分页
 | 
				
			||||||
------------------------------- */
 | 
					------------------------------- */
 | 
				
			||||||
.el-pagination__editor {
 | 
					.el-pagination__editor {
 | 
				
			||||||
	margin-right: 8px;
 | 
					    margin-right: 8px;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/*深色模式时分页高亮问题*/
 | 
					/*深色模式时分页高亮问题*/
 | 
				
			||||||
.el-pagination.is-background .btn-next.is-active,
 | 
					.el-pagination.is-background .btn-next.is-active,
 | 
				
			||||||
.el-pagination.is-background .btn-prev.is-active,
 | 
					.el-pagination.is-background .btn-prev.is-active,
 | 
				
			||||||
.el-pagination.is-background .el-pager li.is-active {
 | 
					.el-pagination.is-background .el-pager li.is-active {
 | 
				
			||||||
	background-color: var(--el-color-primary) !important;
 | 
					    background-color: var(--el-color-primary) !important;
 | 
				
			||||||
	color: var(--el-color-white) !important;
 | 
					    color: var(--el-color-white) !important;
 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/* Drawer 抽屉
 | 
					 | 
				
			||||||
------------------------------- */
 | 
					 | 
				
			||||||
.el-drawer {
 | 
					 | 
				
			||||||
	--el-drawer-padding-primary: unset !important;
 | 
					 | 
				
			||||||
	.el-drawer__header {
 | 
					 | 
				
			||||||
		padding: 0 15px !important;
 | 
					 | 
				
			||||||
		height: 50px;
 | 
					 | 
				
			||||||
		display: flex;
 | 
					 | 
				
			||||||
		align-items: center;
 | 
					 | 
				
			||||||
		margin-bottom: 0 !important;
 | 
					 | 
				
			||||||
		border-bottom: 1px solid var(--el-border-color);
 | 
					 | 
				
			||||||
		color: var(--el-text-color-primary);
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	.el-drawer__body {
 | 
					 | 
				
			||||||
		width: 100%;
 | 
					 | 
				
			||||||
		height: 100%;
 | 
					 | 
				
			||||||
		overflow: auto;
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/* Breadcrumb 面包屑
 | 
					/* Breadcrumb 面包屑
 | 
				
			||||||
------------------------------- */
 | 
					------------------------------- */
 | 
				
			||||||
.el-breadcrumb__inner a:hover,
 | 
					.el-breadcrumb__inner a:hover,
 | 
				
			||||||
.el-breadcrumb__inner.is-link:hover {
 | 
					.el-breadcrumb__inner.is-link:hover {
 | 
				
			||||||
	color: var(--el-color-primary);
 | 
					    color: var(--el-color-primary);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.el-breadcrumb__inner a,
 | 
					.el-breadcrumb__inner a,
 | 
				
			||||||
.el-breadcrumb__inner.is-link {
 | 
					.el-breadcrumb__inner.is-link {
 | 
				
			||||||
	color: var(--bg-topBarColor);
 | 
					    color: var(--bg-topBarColor);
 | 
				
			||||||
	font-weight: normal;
 | 
					    font-weight: normal;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// el-tooltip使用自定义主题时的样式
 | 
					// el-tooltip使用自定义主题时的样式
 | 
				
			||||||
.el-popper.is-customized {
 | 
					.el-popper.is-customized {
 | 
				
			||||||
    /* Set padding to ensure the height is 32px */
 | 
					    /* Set padding to ensure the height is 32px */
 | 
				
			||||||
  //   padding: 6px 12px;
 | 
					    //   padding: 6px 12px;
 | 
				
			||||||
    background: linear-gradient(90deg, rgb(159, 229, 151), rgb(204, 229, 129));
 | 
					    background: linear-gradient(90deg, rgb(159, 229, 151), rgb(204, 229, 129));
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.el-popper.is-customized .el-popper__arrow::before {
 | 
					.el-popper.is-customized .el-popper__arrow::before {
 | 
				
			||||||
    background: linear-gradient(45deg, #b2e68d, #bce689);
 | 
					    background: linear-gradient(45deg, #b2e68d, #bce689);
 | 
				
			||||||
    right: 0;
 | 
					    right: 0;
 | 
				
			||||||
@@ -343,7 +361,9 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.el-dialog {
 | 
					.el-dialog {
 | 
				
			||||||
    border-radius: 6px; /* 设置圆角 */
 | 
					    border-radius: 6px;
 | 
				
			||||||
    box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1); /* 添加轻微阴影效果 */
 | 
					    /* 设置圆角 */
 | 
				
			||||||
 | 
					    box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
 | 
				
			||||||
 | 
					    /* 添加轻微阴影效果 */
 | 
				
			||||||
    border: 1px solid var(--el-border-color-lighter);
 | 
					    border: 1px solid var(--el-border-color-lighter);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -1,8 +1,8 @@
 | 
				
			|||||||
@import './app.scss';
 | 
					@use './app.scss';
 | 
				
			||||||
@import './base.scss';
 | 
					@use './base.scss';
 | 
				
			||||||
@import './other.scss';
 | 
					@use './other.scss';
 | 
				
			||||||
@import './element.scss';
 | 
					@use './element.scss';
 | 
				
			||||||
@import './media/media.scss';
 | 
					@use './media/media.scss';
 | 
				
			||||||
@import './waves.scss';
 | 
					@use './waves.scss';
 | 
				
			||||||
@import './dark.scss';
 | 
					@use './dark.scss';
 | 
				
			||||||
@import './iconSelector.scss';
 | 
					@use './iconSelector.scss';
 | 
				
			||||||
@@ -1,94 +1,109 @@
 | 
				
			|||||||
@import './index.scss';
 | 
					@use './index.scss' as index;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/* 页面宽度小于768px
 | 
					/* 页面宽度小于768px
 | 
				
			||||||
------------------------------- */
 | 
					------------------------------- */
 | 
				
			||||||
@media screen and (max-width: $sm) {
 | 
					@media screen and (max-width: index.$sm) {
 | 
				
			||||||
	.big-data-down-left {
 | 
					    .big-data-down-left {
 | 
				
			||||||
		width: 100% !important;
 | 
					        width: 100% !important;
 | 
				
			||||||
		flex-direction: unset !important;
 | 
					        flex-direction: unset !important;
 | 
				
			||||||
		flex-wrap: wrap;
 | 
					        flex-wrap: wrap;
 | 
				
			||||||
		.flex-warp-item {
 | 
					
 | 
				
			||||||
			min-height: 196.24px;
 | 
					        .flex-warp-item {
 | 
				
			||||||
			padding: 0 7.5px 15px 15px !important;
 | 
					            min-height: 196.24px;
 | 
				
			||||||
			.flex-warp-item-box {
 | 
					            padding: 0 7.5px 15px 15px !important;
 | 
				
			||||||
				border: none !important;
 | 
					
 | 
				
			||||||
				border-bottom: 1px solid #ebeef5 !important;
 | 
					            .flex-warp-item-box {
 | 
				
			||||||
			}
 | 
					                border: none !important;
 | 
				
			||||||
		}
 | 
					                border-bottom: 1px solid #ebeef5 !important;
 | 
				
			||||||
	}
 | 
					            }
 | 
				
			||||||
	.big-data-down-center {
 | 
					        }
 | 
				
			||||||
		width: 100% !important;
 | 
					    }
 | 
				
			||||||
		.big-data-down-center-one,
 | 
					
 | 
				
			||||||
		.big-data-down-center-two {
 | 
					    .big-data-down-center {
 | 
				
			||||||
			min-height: 196.24px;
 | 
					        width: 100% !important;
 | 
				
			||||||
			padding-left: 15px !important;
 | 
					
 | 
				
			||||||
			.big-data-down-center-one-content {
 | 
					        .big-data-down-center-one,
 | 
				
			||||||
				border: none !important;
 | 
					        .big-data-down-center-two {
 | 
				
			||||||
				border-bottom: 1px solid #ebeef5 !important;
 | 
					            min-height: 196.24px;
 | 
				
			||||||
			}
 | 
					            padding-left: 15px !important;
 | 
				
			||||||
			.flex-warp-item-box {
 | 
					
 | 
				
			||||||
				@extend .big-data-down-center-one-content;
 | 
					            .big-data-down-center-one-content {
 | 
				
			||||||
			}
 | 
					                border: none !important;
 | 
				
			||||||
		}
 | 
					                border-bottom: 1px solid #ebeef5 !important;
 | 
				
			||||||
	}
 | 
					            }
 | 
				
			||||||
	.big-data-down-right {
 | 
					
 | 
				
			||||||
		.flex-warp-item {
 | 
					            .flex-warp-item-box {
 | 
				
			||||||
			.flex-warp-item-box {
 | 
					                @extend .big-data-down-center-one-content;
 | 
				
			||||||
				border: none !important;
 | 
					            }
 | 
				
			||||||
				border-bottom: 1px solid #ebeef5 !important;
 | 
					        }
 | 
				
			||||||
			}
 | 
					    }
 | 
				
			||||||
			&:nth-of-type(2) {
 | 
					
 | 
				
			||||||
				padding-left: 15px !important;
 | 
					    .big-data-down-right {
 | 
				
			||||||
			}
 | 
					        .flex-warp-item {
 | 
				
			||||||
			&:last-of-type {
 | 
					            .flex-warp-item-box {
 | 
				
			||||||
				.flex-warp-item-box {
 | 
					                border: none !important;
 | 
				
			||||||
					border: none !important;
 | 
					                border-bottom: 1px solid #ebeef5 !important;
 | 
				
			||||||
				}
 | 
					            }
 | 
				
			||||||
			}
 | 
					
 | 
				
			||||||
		}
 | 
					            &:nth-of-type(2) {
 | 
				
			||||||
	}
 | 
					                padding-left: 15px !important;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            &:last-of-type {
 | 
				
			||||||
 | 
					                .flex-warp-item-box {
 | 
				
			||||||
 | 
					                    border: none !important;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/* 页面宽度大于768px小于1200px
 | 
					/* 页面宽度大于768px小于1200px
 | 
				
			||||||
------------------------------- */
 | 
					------------------------------- */
 | 
				
			||||||
@media screen and (min-width: $sm) and (max-width: $lg) {
 | 
					@media screen and (min-width: index.$sm) and (max-width: index.$lg) {
 | 
				
			||||||
	.chart-warp-bottom {
 | 
					    .chart-warp-bottom {
 | 
				
			||||||
		.big-data-down-left {
 | 
					        .big-data-down-left {
 | 
				
			||||||
			width: 50% !important;
 | 
					            width: 50% !important;
 | 
				
			||||||
		}
 | 
					        }
 | 
				
			||||||
		.big-data-down-center {
 | 
					
 | 
				
			||||||
			width: 50% !important;
 | 
					        .big-data-down-center {
 | 
				
			||||||
		}
 | 
					            width: 50% !important;
 | 
				
			||||||
		.big-data-down-right {
 | 
					        }
 | 
				
			||||||
			.flex-warp-item {
 | 
					
 | 
				
			||||||
				width: 50% !important;
 | 
					        .big-data-down-right {
 | 
				
			||||||
				&:nth-of-type(2) {
 | 
					            .flex-warp-item {
 | 
				
			||||||
					padding-left: 7.5px !important;
 | 
					                width: 50% !important;
 | 
				
			||||||
				}
 | 
					
 | 
				
			||||||
			}
 | 
					                &:nth-of-type(2) {
 | 
				
			||||||
		}
 | 
					                    padding-left: 7.5px !important;
 | 
				
			||||||
	}
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/* 页面宽度小于1200px
 | 
					/* 页面宽度小于1200px
 | 
				
			||||||
------------------------------- */
 | 
					------------------------------- */
 | 
				
			||||||
@media screen and (max-width: $lg) {
 | 
					@media screen and (max-width: index.$lg) {
 | 
				
			||||||
	.chart-warp-top {
 | 
					    .chart-warp-top {
 | 
				
			||||||
		.up-left {
 | 
					        .up-left {
 | 
				
			||||||
			display: none;
 | 
					            display: none;
 | 
				
			||||||
		}
 | 
					        }
 | 
				
			||||||
	}
 | 
					    }
 | 
				
			||||||
	.chart-warp-bottom {
 | 
					
 | 
				
			||||||
		overflow-y: auto !important;
 | 
					    .chart-warp-bottom {
 | 
				
			||||||
		flex-wrap: wrap;
 | 
					        overflow-y: auto !important;
 | 
				
			||||||
		.big-data-down-right {
 | 
					        flex-wrap: wrap;
 | 
				
			||||||
			width: 100% !important;
 | 
					
 | 
				
			||||||
			flex-direction: unset !important;
 | 
					        .big-data-down-right {
 | 
				
			||||||
			flex-wrap: wrap;
 | 
					            width: 100% !important;
 | 
				
			||||||
			.flex-warp-item {
 | 
					            flex-direction: unset !important;
 | 
				
			||||||
				min-height: 196.24px;
 | 
					            flex-wrap: wrap;
 | 
				
			||||||
				padding: 0 7.5px 15px 15px !important;
 | 
					
 | 
				
			||||||
			}
 | 
					            .flex-warp-item {
 | 
				
			||||||
		}
 | 
					                min-height: 196.24px;
 | 
				
			||||||
	}
 | 
					                padding: 0 7.5px 15px 15px !important;
 | 
				
			||||||
}
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -1,10 +1,10 @@
 | 
				
			|||||||
@import './index.scss';
 | 
					@use './index.scss' as index;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/* 页面宽度小于576px
 | 
					/* 页面宽度小于576px
 | 
				
			||||||
------------------------------- */
 | 
					------------------------------- */
 | 
				
			||||||
@media screen and (max-width: $xs) {
 | 
					@media screen and (max-width: index.$xs) {
 | 
				
			||||||
	.el-cascader__dropdown.el-popper {
 | 
					    .el-cascader__dropdown.el-popper {
 | 
				
			||||||
		overflow: auto;
 | 
					        overflow: auto;
 | 
				
			||||||
		max-width: 100%;
 | 
					        max-width: 100%;
 | 
				
			||||||
	}
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -1,12 +1,13 @@
 | 
				
			|||||||
@import './index.scss';
 | 
					@use './index.scss';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/* 页面宽度小于800px
 | 
					/* 页面宽度小于800px
 | 
				
			||||||
------------------------------- */
 | 
					------------------------------- */
 | 
				
			||||||
@media screen and (max-width: 800px) {
 | 
					@media screen and (max-width: 800px) {
 | 
				
			||||||
	.el-dialog {
 | 
					    .el-dialog {
 | 
				
			||||||
		width: 90% !important;
 | 
					        width: 90% !important;
 | 
				
			||||||
	}
 | 
					    }
 | 
				
			||||||
	.el-dialog.is-fullscreen {
 | 
					
 | 
				
			||||||
		width: 100% !important;
 | 
					    .el-dialog.is-fullscreen {
 | 
				
			||||||
	}
 | 
					        width: 100% !important;
 | 
				
			||||||
}
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -1,35 +1,38 @@
 | 
				
			|||||||
@import './index.scss';
 | 
					@use './index.scss' as index;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/* 页面宽度小于768px
 | 
					/* 页面宽度小于768px
 | 
				
			||||||
------------------------------- */
 | 
					------------------------------- */
 | 
				
			||||||
@media screen and (max-width: $sm) {
 | 
					@media screen and (max-width: index.$sm) {
 | 
				
			||||||
	.error {
 | 
					    .error {
 | 
				
			||||||
		.error-flex {
 | 
					        .error-flex {
 | 
				
			||||||
			flex-direction: column-reverse !important;
 | 
					            flex-direction: column-reverse !important;
 | 
				
			||||||
			height: auto !important;
 | 
					            height: auto !important;
 | 
				
			||||||
			width: 100% !important;
 | 
					            width: 100% !important;
 | 
				
			||||||
		}
 | 
					        }
 | 
				
			||||||
		.right,
 | 
					
 | 
				
			||||||
		.left {
 | 
					        .right,
 | 
				
			||||||
			flex: unset !important;
 | 
					        .left {
 | 
				
			||||||
			display: flex !important;
 | 
					            flex: unset !important;
 | 
				
			||||||
		}
 | 
					            display: flex !important;
 | 
				
			||||||
		.left-item {
 | 
					        }
 | 
				
			||||||
			margin: auto !important;
 | 
					
 | 
				
			||||||
		}
 | 
					        .left-item {
 | 
				
			||||||
		.right img {
 | 
					            margin: auto !important;
 | 
				
			||||||
			max-width: 450px !important;
 | 
					        }
 | 
				
			||||||
			@extend .left-item;
 | 
					
 | 
				
			||||||
		}
 | 
					        .right img {
 | 
				
			||||||
	}
 | 
					            max-width: 450px !important;
 | 
				
			||||||
 | 
					            @extend .left-item;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/* 页面宽度大于768px小于992px
 | 
					/* 页面宽度大于768px小于992px
 | 
				
			||||||
------------------------------- */
 | 
					------------------------------- */
 | 
				
			||||||
@media screen and (min-width: $sm) and (max-width: $md) {
 | 
					@media screen and (min-width: index.$sm) and (max-width: index.$md) {
 | 
				
			||||||
	.error {
 | 
					    .error {
 | 
				
			||||||
		.error-flex {
 | 
					        .error-flex {
 | 
				
			||||||
			padding-left: 30px !important;
 | 
					            padding-left: 30px !important;
 | 
				
			||||||
		}
 | 
					        }
 | 
				
			||||||
	}
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -1,13 +1,14 @@
 | 
				
			|||||||
@import './index.scss';
 | 
					@use './index.scss' as index;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/* 页面宽度小于576px
 | 
					/* 页面宽度小于576px
 | 
				
			||||||
------------------------------- */
 | 
					------------------------------- */
 | 
				
			||||||
@media screen and (max-width: $xs) {
 | 
					@media screen and (max-width: index.$xs) {
 | 
				
			||||||
	.el-form-item__label {
 | 
					    .el-form-item__label {
 | 
				
			||||||
		width: 100% !important;
 | 
					        width: 100% !important;
 | 
				
			||||||
		text-align: left !important;
 | 
					        text-align: left !important;
 | 
				
			||||||
	}
 | 
					    }
 | 
				
			||||||
	.el-form-item__content {
 | 
					
 | 
				
			||||||
		margin-left: 0 !important;
 | 
					    .el-form-item__content {
 | 
				
			||||||
	}
 | 
					        margin-left: 0 !important;
 | 
				
			||||||
}
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -1,10 +1,11 @@
 | 
				
			|||||||
@import './index.scss';
 | 
					@use './index.scss' as index;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/* 页面宽度小于768px
 | 
					/* 页面宽度小于768px
 | 
				
			||||||
------------------------------- */
 | 
					------------------------------- */
 | 
				
			||||||
@media screen and (max-width: $sm) {
 | 
					@media screen and (max-width: index.$sm) {
 | 
				
			||||||
	.home-warning-media,
 | 
					
 | 
				
			||||||
	.home-dynamic-media {
 | 
					    .home-warning-media,
 | 
				
			||||||
		margin-top: 15px;
 | 
					    .home-dynamic-media {
 | 
				
			||||||
	}
 | 
					        margin-top: 15px;
 | 
				
			||||||
}
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -1,55 +1,61 @@
 | 
				
			|||||||
@import './index.scss';
 | 
					@use './index.scss' as index;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/* 页面宽度小于576px
 | 
					/* 页面宽度小于576px
 | 
				
			||||||
------------------------------- */
 | 
					------------------------------- */
 | 
				
			||||||
@media screen and (max-width: $xs) {
 | 
					@media screen and (max-width: index.$xs) {
 | 
				
			||||||
	// MessageBox 弹框
 | 
					
 | 
				
			||||||
	.el-message-box {
 | 
					    // MessageBox 弹框
 | 
				
			||||||
		width: 80% !important;
 | 
					    .el-message-box {
 | 
				
			||||||
	}
 | 
					        width: 80% !important;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/* 页面宽度小于768px
 | 
					/* 页面宽度小于768px
 | 
				
			||||||
------------------------------- */
 | 
					------------------------------- */
 | 
				
			||||||
@media screen and (max-width: $sm) {
 | 
					@media screen and (max-width: index.$sm) {
 | 
				
			||||||
	// Breadcrumb 面包屑
 | 
					
 | 
				
			||||||
	.layout-navbars-breadcrumb-hide {
 | 
					    // Breadcrumb 面包屑
 | 
				
			||||||
		display: none;
 | 
					    .layout-navbars-breadcrumb-hide {
 | 
				
			||||||
	}
 | 
					        display: none;
 | 
				
			||||||
	// 外链视图
 | 
					    }
 | 
				
			||||||
	.layout-view-link {
 | 
					
 | 
				
			||||||
		a {
 | 
					    // 外链视图
 | 
				
			||||||
			max-width: 80%;
 | 
					    .layout-view-link {
 | 
				
			||||||
			text-align: center;
 | 
					        a {
 | 
				
			||||||
		}
 | 
					            max-width: 80%;
 | 
				
			||||||
	}
 | 
					            text-align: center;
 | 
				
			||||||
	// 菜单搜索
 | 
					        }
 | 
				
			||||||
	.layout-search-dialog {
 | 
					    }
 | 
				
			||||||
		.el-autocomplete {
 | 
					
 | 
				
			||||||
			width: 80% !important;
 | 
					    // 菜单搜索
 | 
				
			||||||
		}
 | 
					    .layout-search-dialog {
 | 
				
			||||||
	}
 | 
					        .el-autocomplete {
 | 
				
			||||||
 | 
					            width: 80% !important;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/* 页面宽度小于1000px
 | 
					/* 页面宽度小于1000px
 | 
				
			||||||
------------------------------- */
 | 
					------------------------------- */
 | 
				
			||||||
@media screen and (max-width: 1000px) {
 | 
					@media screen and (max-width: 1000px) {
 | 
				
			||||||
	// 布局配置
 | 
					
 | 
				
			||||||
	.layout-drawer-content-flex {
 | 
					    // 布局配置
 | 
				
			||||||
		position: relative;
 | 
					    .layout-drawer-content-flex {
 | 
				
			||||||
		&::after {
 | 
					        position: relative;
 | 
				
			||||||
			content: '手机版不支持切换布局';
 | 
					
 | 
				
			||||||
			position: absolute;
 | 
					        &::after {
 | 
				
			||||||
			top: 0;
 | 
					            content: '手机版不支持切换布局';
 | 
				
			||||||
			right: 0;
 | 
					            position: absolute;
 | 
				
			||||||
			bottom: 0;
 | 
					            top: 0;
 | 
				
			||||||
			left: 0;
 | 
					            right: 0;
 | 
				
			||||||
			z-index: 1;
 | 
					            bottom: 0;
 | 
				
			||||||
			text-align: center;
 | 
					            left: 0;
 | 
				
			||||||
			height: 140px;
 | 
					            z-index: 1;
 | 
				
			||||||
			line-height: 140px;
 | 
					            text-align: center;
 | 
				
			||||||
			background: rgba(255, 255, 255, 0.9);
 | 
					            height: 140px;
 | 
				
			||||||
			color: #666666;
 | 
					            line-height: 140px;
 | 
				
			||||||
		}
 | 
					            background: rgba(255, 255, 255, 0.9);
 | 
				
			||||||
	}
 | 
					            color: #666666;
 | 
				
			||||||
}
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -1,21 +1,23 @@
 | 
				
			|||||||
@import './index.scss';
 | 
					@use './index.scss' as index;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/* 页面宽度小于576px
 | 
					/* 页面宽度小于576px
 | 
				
			||||||
------------------------------- */
 | 
					------------------------------- */
 | 
				
			||||||
@media screen and (max-width: $xs) {
 | 
					@media screen and (max-width: index.$xs) {
 | 
				
			||||||
	.login-container {
 | 
					    .login-container {
 | 
				
			||||||
		.login-content {
 | 
					        .login-content {
 | 
				
			||||||
			width: 90% !important;
 | 
					            width: 90% !important;
 | 
				
			||||||
			padding: 20px 0 !important;
 | 
					            padding: 20px 0 !important;
 | 
				
			||||||
		}
 | 
					        }
 | 
				
			||||||
		.login-content-form-btn {
 | 
					
 | 
				
			||||||
			width: 100% !important;
 | 
					        .login-content-form-btn {
 | 
				
			||||||
			padding: 12px 0 !important;
 | 
					            width: 100% !important;
 | 
				
			||||||
		}
 | 
					            padding: 12px 0 !important;
 | 
				
			||||||
		.login-copyright {
 | 
					        }
 | 
				
			||||||
			.login-copyright-msg {
 | 
					
 | 
				
			||||||
				white-space: unset !important;
 | 
					        .login-copyright {
 | 
				
			||||||
			}
 | 
					            .login-copyright-msg {
 | 
				
			||||||
		}
 | 
					                white-space: unset !important;
 | 
				
			||||||
	}
 | 
					            }
 | 
				
			||||||
}
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -1,12 +1,12 @@
 | 
				
			|||||||
@import './login.scss';
 | 
					@use './login.scss';
 | 
				
			||||||
@import './error.scss';
 | 
					@use './error.scss';
 | 
				
			||||||
@import './layout.scss';
 | 
					@use './layout.scss';
 | 
				
			||||||
@import './personal.scss';
 | 
					@use './personal.scss';
 | 
				
			||||||
@import './tagsView.scss';
 | 
					@use './tagsView.scss';
 | 
				
			||||||
@import './home.scss';
 | 
					@use './home.scss';
 | 
				
			||||||
@import './chart.scss';
 | 
					@use './chart.scss';
 | 
				
			||||||
@import './form.scss';
 | 
					@use './form.scss';
 | 
				
			||||||
@import './scrollbar.scss';
 | 
					@use './scrollbar.scss';
 | 
				
			||||||
@import './pagination.scss';
 | 
					@use './pagination.scss';
 | 
				
			||||||
@import './dialog.scss';
 | 
					@use './dialog.scss';
 | 
				
			||||||
@import './cityLinkage.scss';
 | 
					@use './cityLinkage.scss';
 | 
				
			||||||
@@ -1,15 +1,16 @@
 | 
				
			|||||||
@import './index.scss';
 | 
					@use './index.scss' as index;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/* 页面宽度小于576px
 | 
					/* 页面宽度小于576px
 | 
				
			||||||
------------------------------- */
 | 
					------------------------------- */
 | 
				
			||||||
@media screen and (max-width: $xs) {
 | 
					@media screen and (max-width: index.$xs) {
 | 
				
			||||||
	.el-pager,
 | 
					
 | 
				
			||||||
	.el-pagination__jump {
 | 
					    .el-pager,
 | 
				
			||||||
		display: none !important;
 | 
					    .el-pagination__jump {
 | 
				
			||||||
	}
 | 
					        display: none !important;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// 默认居中对齐
 | 
					// 默认居中对齐
 | 
				
			||||||
.el-pagination {
 | 
					.el-pagination {
 | 
				
			||||||
	text-align: center !important;
 | 
					    text-align: center !important;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -1,16 +1,18 @@
 | 
				
			|||||||
@import './index.scss';
 | 
					@use './index.scss' as index;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/* 页面宽度小于768px
 | 
					/* 页面宽度小于768px
 | 
				
			||||||
------------------------------- */
 | 
					------------------------------- */
 | 
				
			||||||
@media screen and (max-width: $sm) {
 | 
					@media screen and (max-width: index.$sm) {
 | 
				
			||||||
	.personal-info {
 | 
					    .personal-info {
 | 
				
			||||||
		padding-left: 0 !important;
 | 
					        padding-left: 0 !important;
 | 
				
			||||||
		margin-top: 15px;
 | 
					        margin-top: 15px;
 | 
				
			||||||
	}
 | 
					    }
 | 
				
			||||||
	.personal-recommend-col {
 | 
					
 | 
				
			||||||
		margin-bottom: 15px;
 | 
					    .personal-recommend-col {
 | 
				
			||||||
		&:last-of-type {
 | 
					        margin-bottom: 15px;
 | 
				
			||||||
			margin-bottom: 0;
 | 
					
 | 
				
			||||||
		}
 | 
					        &:last-of-type {
 | 
				
			||||||
	}
 | 
					            margin-bottom: 0;
 | 
				
			||||||
}
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -1,56 +1,66 @@
 | 
				
			|||||||
@import './index.scss';
 | 
					@use './index.scss' as index;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/* 页面宽度小于768px
 | 
					/* 页面宽度小于768px
 | 
				
			||||||
------------------------------- */
 | 
					------------------------------- */
 | 
				
			||||||
@media screen and (max-width: $sm) {
 | 
					@media screen and (max-width: index.$sm) {
 | 
				
			||||||
	// 滚动条的宽度
 | 
					
 | 
				
			||||||
	::-webkit-scrollbar {
 | 
					    // 滚动条的宽度
 | 
				
			||||||
		width: 3px !important;
 | 
					    ::-webkit-scrollbar {
 | 
				
			||||||
		height: 3px !important;
 | 
					        width: 3px !important;
 | 
				
			||||||
	}
 | 
					        height: 3px !important;
 | 
				
			||||||
	::-webkit-scrollbar-track-piece {
 | 
					    }
 | 
				
			||||||
		background-color: var(--bg-main-color);
 | 
					
 | 
				
			||||||
	}
 | 
					    ::-webkit-scrollbar-track-piece {
 | 
				
			||||||
	// 滚动条的设置
 | 
					        background-color: var(--bg-main-color);
 | 
				
			||||||
	::-webkit-scrollbar-thumb {
 | 
					    }
 | 
				
			||||||
		background-color: rgba(144, 147, 153, 0.3);
 | 
					
 | 
				
			||||||
		background-clip: padding-box;
 | 
					    // 滚动条的设置
 | 
				
			||||||
		min-height: 28px;
 | 
					    ::-webkit-scrollbar-thumb {
 | 
				
			||||||
		border-radius: 5px;
 | 
					        background-color: rgba(144, 147, 153, 0.3);
 | 
				
			||||||
		transition: 0.3s background-color;
 | 
					        background-clip: padding-box;
 | 
				
			||||||
	}
 | 
					        min-height: 28px;
 | 
				
			||||||
	::-webkit-scrollbar-thumb:hover {
 | 
					        border-radius: 5px;
 | 
				
			||||||
		background-color: rgba(144, 147, 153, 0.5);
 | 
					        transition: 0.3s background-color;
 | 
				
			||||||
	}
 | 
					    }
 | 
				
			||||||
	// element plus scrollbar
 | 
					
 | 
				
			||||||
	.el-scrollbar__bar.is-vertical {
 | 
					    ::-webkit-scrollbar-thumb:hover {
 | 
				
			||||||
		width: 2px !important;
 | 
					        background-color: rgba(144, 147, 153, 0.5);
 | 
				
			||||||
	}
 | 
					    }
 | 
				
			||||||
	.el-scrollbar__bar.is-horizontal {
 | 
					
 | 
				
			||||||
		height: 2px !important;
 | 
					    // element plus scrollbar
 | 
				
			||||||
	}
 | 
					    .el-scrollbar__bar.is-vertical {
 | 
				
			||||||
 | 
					        width: 2px !important;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    .el-scrollbar__bar.is-horizontal {
 | 
				
			||||||
 | 
					        height: 2px !important;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/* 页面宽度大于768px
 | 
					/* 页面宽度大于768px
 | 
				
			||||||
------------------------------- */
 | 
					------------------------------- */
 | 
				
			||||||
@media screen and (min-width: 769px) {
 | 
					@media screen and (min-width: 769px) {
 | 
				
			||||||
	// 滚动条的宽度
 | 
					
 | 
				
			||||||
	::-webkit-scrollbar {
 | 
					    // 滚动条的宽度
 | 
				
			||||||
		width: 7px;
 | 
					    ::-webkit-scrollbar {
 | 
				
			||||||
		height: 7px;
 | 
					        width: 7px;
 | 
				
			||||||
	}
 | 
					        height: 7px;
 | 
				
			||||||
	::-webkit-scrollbar-track-piece {
 | 
					    }
 | 
				
			||||||
		background-color: var(--bg-main-color);
 | 
					
 | 
				
			||||||
	}
 | 
					    ::-webkit-scrollbar-track-piece {
 | 
				
			||||||
	// 滚动条的设置
 | 
					        background-color: var(--bg-main-color);
 | 
				
			||||||
	::-webkit-scrollbar-thumb {
 | 
					    }
 | 
				
			||||||
		background-color: rgba(144, 147, 153, 0.3);
 | 
					
 | 
				
			||||||
		background-clip: padding-box;
 | 
					    // 滚动条的设置
 | 
				
			||||||
		min-height: 28px;
 | 
					    ::-webkit-scrollbar-thumb {
 | 
				
			||||||
		border-radius: 5px;
 | 
					        background-color: rgba(144, 147, 153, 0.3);
 | 
				
			||||||
		transition: 0.3s background-color;
 | 
					        background-clip: padding-box;
 | 
				
			||||||
	}
 | 
					        min-height: 28px;
 | 
				
			||||||
	::-webkit-scrollbar-thumb:hover {
 | 
					        border-radius: 5px;
 | 
				
			||||||
		background-color: rgba(144, 147, 153, 0.5);
 | 
					        transition: 0.3s background-color;
 | 
				
			||||||
	}
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    ::-webkit-scrollbar-thumb:hover {
 | 
				
			||||||
 | 
					        background-color: rgba(144, 147, 153, 0.5);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -1,11 +1,11 @@
 | 
				
			|||||||
@import './index.scss';
 | 
					@use './index.scss' as index;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/* 页面宽度小于768px
 | 
					/* 页面宽度小于768px
 | 
				
			||||||
------------------------------- */
 | 
					------------------------------- */
 | 
				
			||||||
@media screen and (max-width: $sm) {
 | 
					@media screen and (max-width: index.$sm) {
 | 
				
			||||||
	.tags-view-form {
 | 
					    .tags-view-form {
 | 
				
			||||||
		.tags-view-form-col {
 | 
					        .tags-view-form-col {
 | 
				
			||||||
			margin-bottom: 20px;
 | 
					            margin-bottom: 20px;
 | 
				
			||||||
		}
 | 
					        }
 | 
				
			||||||
	}
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -1,32 +1,32 @@
 | 
				
			|||||||
/* 第三方图标字体间距/大小设置
 | 
					/* 第三方图标字体间距/大小设置
 | 
				
			||||||
------------------------------- */
 | 
					------------------------------- */
 | 
				
			||||||
@mixin generalIcon {
 | 
					@mixin generalIcon {
 | 
				
			||||||
	font-size: 14px !important;
 | 
					    font-size: 14px !important;
 | 
				
			||||||
	display: inline-block;
 | 
					    display: inline-block;
 | 
				
			||||||
	vertical-align: middle;
 | 
					    vertical-align: middle;
 | 
				
			||||||
	margin-right: 5px;
 | 
					    margin-right: 5px;
 | 
				
			||||||
	width: 24px;
 | 
					    width: 24px;
 | 
				
			||||||
	text-align: center;
 | 
					    text-align: center;
 | 
				
			||||||
	justify-content: center;
 | 
					    justify-content: center;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/* 文本不换行
 | 
					/* 文本不换行
 | 
				
			||||||
------------------------------- */
 | 
					------------------------------- */
 | 
				
			||||||
@mixin text-no-wrap() {
 | 
					@mixin text-no-wrap() {
 | 
				
			||||||
	text-overflow: ellipsis;
 | 
					    text-overflow: ellipsis;
 | 
				
			||||||
	overflow: hidden;
 | 
					    overflow: hidden;
 | 
				
			||||||
	white-space: nowrap;
 | 
					    white-space: nowrap;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/* 多行文本溢出
 | 
					/* 多行文本溢出
 | 
				
			||||||
  ------------------------------- */
 | 
					  ------------------------------- */
 | 
				
			||||||
@mixin text-ellipsis($line: 2) {
 | 
					@mixin text-ellipsis($line: 2) {
 | 
				
			||||||
	overflow: hidden;
 | 
					    overflow: hidden;
 | 
				
			||||||
	word-break: break-all;
 | 
					    word-break: break-all;
 | 
				
			||||||
	text-overflow: ellipsis;
 | 
					    text-overflow: ellipsis;
 | 
				
			||||||
	display: -webkit-box;
 | 
					    display: -webkit-box;
 | 
				
			||||||
	-webkit-line-clamp: $line;
 | 
					    -webkit-line-clamp: $line;
 | 
				
			||||||
	-webkit-box-orient: vertical;
 | 
					    -webkit-box-orient: vertical;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/* 滚动条(页面未使用) div 中使用:
 | 
					/* 滚动条(页面未使用) div 中使用:
 | 
				
			||||||
@@ -35,22 +35,26 @@
 | 
				
			|||||||
//   @include scrollBar;
 | 
					//   @include scrollBar;
 | 
				
			||||||
// }
 | 
					// }
 | 
				
			||||||
@mixin scrollBar {
 | 
					@mixin scrollBar {
 | 
				
			||||||
	// 滚动条凹槽的颜色,还可以设置边框属性
 | 
					
 | 
				
			||||||
	&::-webkit-scrollbar-track-piece {
 | 
					    // 滚动条凹槽的颜色,还可以设置边框属性
 | 
				
			||||||
		background-color: #f8f8f8;
 | 
					    &::-webkit-scrollbar-track-piece {
 | 
				
			||||||
	}
 | 
					        background-color: #f8f8f8;
 | 
				
			||||||
	// 滚动条的宽度
 | 
					    }
 | 
				
			||||||
	&::-webkit-scrollbar {
 | 
					
 | 
				
			||||||
		width: 9px;
 | 
					    // 滚动条的宽度
 | 
				
			||||||
		height: 9px;
 | 
					    &::-webkit-scrollbar {
 | 
				
			||||||
	}
 | 
					        width: 9px;
 | 
				
			||||||
	// 滚动条的设置
 | 
					        height: 9px;
 | 
				
			||||||
	&::-webkit-scrollbar-thumb {
 | 
					    }
 | 
				
			||||||
		background-color: #dddddd;
 | 
					
 | 
				
			||||||
		background-clip: padding-box;
 | 
					    // 滚动条的设置
 | 
				
			||||||
		min-height: 28px;
 | 
					    &::-webkit-scrollbar-thumb {
 | 
				
			||||||
	}
 | 
					        background-color: #dddddd;
 | 
				
			||||||
	&::-webkit-scrollbar-thumb:hover {
 | 
					        background-clip: padding-box;
 | 
				
			||||||
		background-color: #bbb;
 | 
					        min-height: 28px;
 | 
				
			||||||
	}
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    &::-webkit-scrollbar-thumb:hover {
 | 
				
			||||||
 | 
					        background-color: #bbb;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -1,28 +1,31 @@
 | 
				
			|||||||
/* wangeditor富文本编辑器
 | 
					/* wangeditor富文本编辑器
 | 
				
			||||||
------------------------------- */
 | 
					------------------------------- */
 | 
				
			||||||
.w-e-toolbar {
 | 
					.w-e-toolbar {
 | 
				
			||||||
	border: 1px solid #ebeef5 !important;
 | 
					    border: 1px solid #ebeef5 !important;
 | 
				
			||||||
	border-bottom: 1px solid #ebeef5 !important;
 | 
					    border-bottom: 1px solid #ebeef5 !important;
 | 
				
			||||||
	border-top-left-radius: 3px;
 | 
					    border-top-left-radius: 3px;
 | 
				
			||||||
	border-top-right-radius: 3px;
 | 
					    border-top-right-radius: 3px;
 | 
				
			||||||
	z-index: 2 !important;
 | 
					    z-index: 2 !important;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.w-e-text-container {
 | 
					.w-e-text-container {
 | 
				
			||||||
	border: 1px solid #ebeef5 !important;
 | 
					    border: 1px solid #ebeef5 !important;
 | 
				
			||||||
	border-top: none !important;
 | 
					    border-top: none !important;
 | 
				
			||||||
	border-bottom-left-radius: 3px;
 | 
					    border-bottom-left-radius: 3px;
 | 
				
			||||||
	border-bottom-right-radius: 3px;
 | 
					    border-bottom-right-radius: 3px;
 | 
				
			||||||
	z-index: 1 !important;
 | 
					    z-index: 1 !important;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/* web端自定义截屏
 | 
					/* web端自定义截屏
 | 
				
			||||||
------------------------------- */
 | 
					------------------------------- */
 | 
				
			||||||
#screenShotContainer {
 | 
					#screenShotContainer {
 | 
				
			||||||
	z-index: 9998 !important;
 | 
					    z-index: 9998 !important;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#toolPanel {
 | 
					#toolPanel {
 | 
				
			||||||
	height: 42px !important;
 | 
					    height: 42px !important;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#optionPanel {
 | 
					#optionPanel {
 | 
				
			||||||
	height: 37px !important;
 | 
					    height: 37px !important;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
							
								
								
									
										1
									
								
								mayfly_go_web/src/types/pinia.d.ts
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								mayfly_go_web/src/types/pinia.d.ts
									
									
									
									
										vendored
									
									
								
							@@ -52,6 +52,7 @@ declare interface ThemeConfigState {
 | 
				
			|||||||
        logoIcon: string;
 | 
					        logoIcon: string;
 | 
				
			||||||
        globalI18n: string;
 | 
					        globalI18n: string;
 | 
				
			||||||
        globalComponentSize: string;
 | 
					        globalComponentSize: string;
 | 
				
			||||||
 | 
					        terminalTheme: string;
 | 
				
			||||||
        terminalForeground: string;
 | 
					        terminalForeground: string;
 | 
				
			||||||
        terminalBackground: string;
 | 
					        terminalBackground: string;
 | 
				
			||||||
        terminalCursor: string;
 | 
					        terminalCursor: string;
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										2
									
								
								mayfly_go_web/src/types/shim.d.ts
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								mayfly_go_web/src/types/shim.d.ts
									
									
									
									
										vendored
									
									
								
							@@ -1,6 +1,6 @@
 | 
				
			|||||||
// 申明外部 npm 插件模块
 | 
					// 申明外部 npm 插件模块
 | 
				
			||||||
declare module 'sql-formatter';
 | 
					 | 
				
			||||||
declare module 'jsoneditor';
 | 
					declare module 'jsoneditor';
 | 
				
			||||||
declare module 'asciinema-player';
 | 
					declare module 'asciinema-player';
 | 
				
			||||||
declare module 'vue-grid-layout';
 | 
					declare module 'vue-grid-layout';
 | 
				
			||||||
declare module 'splitpanes';
 | 
					declare module 'splitpanes';
 | 
				
			||||||
 | 
					declare module 'uuid';
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -98,4 +98,3 @@ export default {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
</style>
 | 
					</style>
 | 
				
			||||||
@/router/staticRouter
 | 
					 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										149
									
								
								mayfly_go_web/src/views/flow/ProcInstEdit.vue
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										149
									
								
								mayfly_go_web/src/views/flow/ProcInstEdit.vue
									
									
									
									
									
										Executable file
									
								
							@@ -0,0 +1,149 @@
 | 
				
			|||||||
 | 
					<template>
 | 
				
			||||||
 | 
					    <div>
 | 
				
			||||||
 | 
					        <el-drawer :title="props.title" v-model="visible" :before-close="cancel" :destroy-on-close="true" :close-on-click-modal="false" size="50%">
 | 
				
			||||||
 | 
					            <template #header>
 | 
				
			||||||
 | 
					                <DrawerHeader :header="title" :back="cancel" />
 | 
				
			||||||
 | 
					            </template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            <el-form :model="form" ref="formRef" :rules="rules" label-width="auto">
 | 
				
			||||||
 | 
					                <el-form-item prop="bizType" label="业务类型">
 | 
				
			||||||
 | 
					                    <EnumSelect v-model="form.bizType" :enums="FlowBizType" placeholder="请选择业务类型" />
 | 
				
			||||||
 | 
					                </el-form-item>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                <el-form-item prop="remark" label="备注">
 | 
				
			||||||
 | 
					                    <el-input v-model.trim="form.remark" type="textarea" placeholder="备注" auto-complete="off" clearable></el-input>
 | 
				
			||||||
 | 
					                </el-form-item>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                <el-divider content-position="left">业务信息</el-divider>
 | 
				
			||||||
 | 
					                <component
 | 
				
			||||||
 | 
					                    ref="bizFormRef"
 | 
				
			||||||
 | 
					                    v-if="form.bizType"
 | 
				
			||||||
 | 
					                    :is="bizComponents[form.bizType]"
 | 
				
			||||||
 | 
					                    v-model:bizForm="form.bizForm"
 | 
				
			||||||
 | 
					                    @changeResourceCode="changeResourceCode"
 | 
				
			||||||
 | 
					                >
 | 
				
			||||||
 | 
					                </component>
 | 
				
			||||||
 | 
					            </el-form>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            <span v-if="flowProcdef || !state.form.procdefId">
 | 
				
			||||||
 | 
					                <el-divider content-position="left">审批节点</el-divider>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                <ProcdefTasks v-if="flowProcdef" :procdef="flowProcdef" />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                <el-result v-if="!state.form.procdefId" icon="error" title="不存在审批节点" sub-title="该资源无需审批操作"> </el-result>
 | 
				
			||||||
 | 
					            </span>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            <template #footer>
 | 
				
			||||||
 | 
					                <div>
 | 
				
			||||||
 | 
					                    <el-button @click="cancel()">取 消</el-button>
 | 
				
			||||||
 | 
					                    <el-button type="primary" :loading="saveBtnLoading" @click="btnOk" :disabled="!state.form.procdefId">确 定</el-button>
 | 
				
			||||||
 | 
					                </div>
 | 
				
			||||||
 | 
					            </template>
 | 
				
			||||||
 | 
					        </el-drawer>
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<script lang="ts" setup>
 | 
				
			||||||
 | 
					import { toRefs, reactive, defineAsyncComponent, shallowReactive, useTemplateRef } from 'vue';
 | 
				
			||||||
 | 
					import { procdefApi, procinstApi } from './api';
 | 
				
			||||||
 | 
					import { ElMessage } from 'element-plus';
 | 
				
			||||||
 | 
					import DrawerHeader from '@/components/drawer-header/DrawerHeader.vue';
 | 
				
			||||||
 | 
					import { FlowBizType } from './enums';
 | 
				
			||||||
 | 
					import EnumSelect from '@/components/enumselect/EnumSelect.vue';
 | 
				
			||||||
 | 
					import ProcdefTasks from './components/ProcdefTasks.vue';
 | 
				
			||||||
 | 
					import RedisRunCmdFlowBizForm from './flowbiz/redis/RedisRunCmdFlowBizForm.vue';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const DbSqlExecFlowBizForm = defineAsyncComponent(() => import('./flowbiz/dbms/DbSqlExecFlowBizForm.vue'));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const props = defineProps({
 | 
				
			||||||
 | 
					    title: {
 | 
				
			||||||
 | 
					        type: String,
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const visible = defineModel<boolean>('visible', { default: false });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					//定义事件
 | 
				
			||||||
 | 
					const emit = defineEmits(['cancel', 'val-change']);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const formRef: any = useTemplateRef('formRef');
 | 
				
			||||||
 | 
					const bizFormRef: any = useTemplateRef('bizFormRef');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// 业务组件
 | 
				
			||||||
 | 
					const bizComponents: any = shallowReactive({
 | 
				
			||||||
 | 
					    db_sql_exec_flow: DbSqlExecFlowBizForm,
 | 
				
			||||||
 | 
					    redis_run_cmd_flow: RedisRunCmdFlowBizForm,
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const rules = {
 | 
				
			||||||
 | 
					    bizType: [
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            required: true,
 | 
				
			||||||
 | 
					            message: '请选择流程业务类型',
 | 
				
			||||||
 | 
					            trigger: ['change', 'blur'],
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					    ],
 | 
				
			||||||
 | 
					    remark: [
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            required: true,
 | 
				
			||||||
 | 
					            message: '请输入申请备注',
 | 
				
			||||||
 | 
					            trigger: ['change', 'blur'],
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					    ],
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const defaultForm = {
 | 
				
			||||||
 | 
					    bizType: FlowBizType.DbSqlExec.value,
 | 
				
			||||||
 | 
					    procdefId: -1,
 | 
				
			||||||
 | 
					    status: null,
 | 
				
			||||||
 | 
					    remark: '',
 | 
				
			||||||
 | 
					    bizForm: {},
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const state = reactive({
 | 
				
			||||||
 | 
					    tasks: [] as any,
 | 
				
			||||||
 | 
					    form: { ...defaultForm },
 | 
				
			||||||
 | 
					    flowProcdef: null as any,
 | 
				
			||||||
 | 
					    sortable: '' as any,
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const { form, flowProcdef } = toRefs(state);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const { isFetching: saveBtnLoading, execute: procinstStart } = procinstApi.start.useApi(form);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const changeResourceCode = async (resourceType: any, code: string) => {
 | 
				
			||||||
 | 
					    state.flowProcdef = await procdefApi.getByResource.request({ resourceType, resourceCode: code });
 | 
				
			||||||
 | 
					    if (!state.flowProcdef) {
 | 
				
			||||||
 | 
					        state.form.procdefId = 0;
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					        state.form.procdefId = state.flowProcdef.id;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const btnOk = async () => {
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					        await formRef.value.validate();
 | 
				
			||||||
 | 
					        await bizFormRef.value.validateBizForm();
 | 
				
			||||||
 | 
					    } catch (e: any) {
 | 
				
			||||||
 | 
					        ElMessage.error('请正确填写信息');
 | 
				
			||||||
 | 
					        return false;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    await procinstStart();
 | 
				
			||||||
 | 
					    ElMessage.success('流程发起成功');
 | 
				
			||||||
 | 
					    emit('val-change', state.form);
 | 
				
			||||||
 | 
					    //重置表单域
 | 
				
			||||||
 | 
					    cancel();
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const cancel = () => {
 | 
				
			||||||
 | 
					    visible.value = false;
 | 
				
			||||||
 | 
					    emit('cancel');
 | 
				
			||||||
 | 
					    state.flowProcdef = null;
 | 
				
			||||||
 | 
					    formRef.value.resetFields();
 | 
				
			||||||
 | 
					    bizFormRef.value.resetBizForm();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    state.form = { ...defaultForm };
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					</script>
 | 
				
			||||||
 | 
					<style lang="scss"></style>
 | 
				
			||||||
							
								
								
									
										257
									
								
								mayfly_go_web/src/views/flow/ProcdefEdit.vue
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										257
									
								
								mayfly_go_web/src/views/flow/ProcdefEdit.vue
									
									
									
									
									
										Executable file
									
								
							@@ -0,0 +1,257 @@
 | 
				
			|||||||
 | 
					<template>
 | 
				
			||||||
 | 
					    <div>
 | 
				
			||||||
 | 
					        <el-drawer @open="initSort" :title="title" v-model="visible" :before-close="cancel" :destroy-on-close="true" :close-on-click-modal="false" size="40%">
 | 
				
			||||||
 | 
					            <template #header>
 | 
				
			||||||
 | 
					                <DrawerHeader :header="title" :back="cancel" />
 | 
				
			||||||
 | 
					            </template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            <el-form :model="form" ref="formRef" :rules="rules" label-width="auto">
 | 
				
			||||||
 | 
					                <el-form-item prop="name" label="名称">
 | 
				
			||||||
 | 
					                    <el-input v-model.trim="form.name" placeholder="请输入流程名称" auto-complete="off" clearable></el-input>
 | 
				
			||||||
 | 
					                </el-form-item>
 | 
				
			||||||
 | 
					                <el-form-item prop="defKey" label="key">
 | 
				
			||||||
 | 
					                    <el-input :disabled="form.id" v-model.trim="form.defKey" placeholder="请输入流程key" auto-complete="off" clearable></el-input>
 | 
				
			||||||
 | 
					                </el-form-item>
 | 
				
			||||||
 | 
					                <el-form-item prop="status" label="状态">
 | 
				
			||||||
 | 
					                    <el-select v-model="form.status" placeholder="请选择状态">
 | 
				
			||||||
 | 
					                        <el-option v-for="item in ProcdefStatus" :key="item.value" :label="item.label" :value="item.value"> </el-option>
 | 
				
			||||||
 | 
					                    </el-select>
 | 
				
			||||||
 | 
					                </el-form-item>
 | 
				
			||||||
 | 
					                <el-form-item prop="condition" label="触发条件">
 | 
				
			||||||
 | 
					                    <template #label>
 | 
				
			||||||
 | 
					                        触发条件
 | 
				
			||||||
 | 
					                        <el-tooltip content="go template语法。若输出结果为1,则表示触发该审批流程" placement="top">
 | 
				
			||||||
 | 
					                            <el-icon>
 | 
				
			||||||
 | 
					                                <question-filled />
 | 
				
			||||||
 | 
					                            </el-icon>
 | 
				
			||||||
 | 
					                        </el-tooltip>
 | 
				
			||||||
 | 
					                    </template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    <el-input
 | 
				
			||||||
 | 
					                        v-model="form.condition"
 | 
				
			||||||
 | 
					                        :rows="10"
 | 
				
			||||||
 | 
					                        type="textarea"
 | 
				
			||||||
 | 
					                        placeholder="触发条件, 返回值=1, 则表示触发该审批流程"
 | 
				
			||||||
 | 
					                        auto-complete="off"
 | 
				
			||||||
 | 
					                        clearable
 | 
				
			||||||
 | 
					                    ></el-input>
 | 
				
			||||||
 | 
					                </el-form-item>
 | 
				
			||||||
 | 
					                <el-form-item prop="remark" label="备注">
 | 
				
			||||||
 | 
					                    <el-input v-model.trim="form.remark" placeholder="备注" auto-complete="off" clearable></el-input>
 | 
				
			||||||
 | 
					                </el-form-item>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                <el-form-item ref="tagSelectRef" prop="codePaths" label="关联资源">
 | 
				
			||||||
 | 
					                    <tag-tree-check height="300px" v-model="form.codePaths" :tag-type="[TagResourceTypeEnum.DbName.value, TagResourceTypeEnum.Redis.value]" />
 | 
				
			||||||
 | 
					                </el-form-item>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                <el-divider content-position="left">审批节点</el-divider>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                <el-table ref="taskTableRef" :data="tasks" row-key="taskKey" stripe style="width: 100%">
 | 
				
			||||||
 | 
					                    <el-table-column prop="name" label="名称" min-width="100px">
 | 
				
			||||||
 | 
					                        <template #header>
 | 
				
			||||||
 | 
					                            <el-button class="ml0" type="primary" circle size="small" icon="Plus" @click="addTask()"> </el-button>
 | 
				
			||||||
 | 
					                            <span class="ml10">节点名称</span>
 | 
				
			||||||
 | 
					                            <el-tooltip content="点击指定节点可进行拖拽排序" placement="top">
 | 
				
			||||||
 | 
					                                <el-icon class="ml5">
 | 
				
			||||||
 | 
					                                    <question-filled />
 | 
				
			||||||
 | 
					                                </el-icon>
 | 
				
			||||||
 | 
					                            </el-tooltip>
 | 
				
			||||||
 | 
					                        </template>
 | 
				
			||||||
 | 
					                        <template #default="scope">
 | 
				
			||||||
 | 
					                            <el-input v-model="scope.row.name"> </el-input>
 | 
				
			||||||
 | 
					                        </template>
 | 
				
			||||||
 | 
					                    </el-table-column>
 | 
				
			||||||
 | 
					                    <el-table-column prop="userId" label="审核人员" min-width="150px" show-overflow-tooltip>
 | 
				
			||||||
 | 
					                        <template #default="scope">
 | 
				
			||||||
 | 
					                            <AccountSelectFormItem v-model="scope.row.userId" label="" />
 | 
				
			||||||
 | 
					                        </template>
 | 
				
			||||||
 | 
					                    </el-table-column>
 | 
				
			||||||
 | 
					                    <el-table-column label="操作" width="60px">
 | 
				
			||||||
 | 
					                        <template #default="scope">
 | 
				
			||||||
 | 
					                            <el-link @click="deleteTask(scope.$index)" class="ml5" type="danger" icon="delete" plain></el-link>
 | 
				
			||||||
 | 
					                        </template>
 | 
				
			||||||
 | 
					                    </el-table-column>
 | 
				
			||||||
 | 
					                </el-table>
 | 
				
			||||||
 | 
					            </el-form>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            <template #footer>
 | 
				
			||||||
 | 
					                <div>
 | 
				
			||||||
 | 
					                    <el-button @click="cancel()">取 消</el-button>
 | 
				
			||||||
 | 
					                    <el-button type="primary" :loading="saveBtnLoading" @click="btnOk">确 定</el-button>
 | 
				
			||||||
 | 
					                </div>
 | 
				
			||||||
 | 
					            </template>
 | 
				
			||||||
 | 
					        </el-drawer>
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<script lang="ts" setup>
 | 
				
			||||||
 | 
					import { toRefs, reactive, watch, ref, nextTick } from 'vue';
 | 
				
			||||||
 | 
					import { procdefApi } from './api';
 | 
				
			||||||
 | 
					import { ElMessage } from 'element-plus';
 | 
				
			||||||
 | 
					import DrawerHeader from '@/components/drawer-header/DrawerHeader.vue';
 | 
				
			||||||
 | 
					import AccountSelectFormItem from '@/views/system/account/components/AccountSelectFormItem.vue';
 | 
				
			||||||
 | 
					import Sortable from 'sortablejs';
 | 
				
			||||||
 | 
					import { randomUuid } from '../../common/utils/string';
 | 
				
			||||||
 | 
					import { ProcdefStatus } from './enums';
 | 
				
			||||||
 | 
					import TagTreeCheck from '../ops/component/TagTreeCheck.vue';
 | 
				
			||||||
 | 
					import { TagResourceTypeEnum } from '@/common/commonEnum';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const props = defineProps({
 | 
				
			||||||
 | 
					    data: {
 | 
				
			||||||
 | 
					        type: [Boolean, Object],
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    title: {
 | 
				
			||||||
 | 
					        type: String,
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const visible = defineModel<boolean>('visible', { default: false });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					//定义事件
 | 
				
			||||||
 | 
					const emit = defineEmits(['cancel', 'val-change']);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const formRef: any = ref(null);
 | 
				
			||||||
 | 
					const taskTableRef: any = ref(null);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const rules = {
 | 
				
			||||||
 | 
					    name: [
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            required: true,
 | 
				
			||||||
 | 
					            message: '请输入流程名称',
 | 
				
			||||||
 | 
					            trigger: ['change', 'blur'],
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					    ],
 | 
				
			||||||
 | 
					    defKey: [
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            required: true,
 | 
				
			||||||
 | 
					            message: '请输入流程key',
 | 
				
			||||||
 | 
					            trigger: ['change', 'blur'],
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					    ],
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const state = reactive({
 | 
				
			||||||
 | 
					    tasks: [] as any,
 | 
				
			||||||
 | 
					    form: {
 | 
				
			||||||
 | 
					        id: null,
 | 
				
			||||||
 | 
					        name: null,
 | 
				
			||||||
 | 
					        defKey: null,
 | 
				
			||||||
 | 
					        status: null,
 | 
				
			||||||
 | 
					        condition: '',
 | 
				
			||||||
 | 
					        remark: null,
 | 
				
			||||||
 | 
					        // 流程的审批节点任务
 | 
				
			||||||
 | 
					        tasks: '',
 | 
				
			||||||
 | 
					        codePaths: [],
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    sortable: '' as any,
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const { form, tasks } = toRefs(state);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const { isFetching: saveBtnLoading, execute: saveFlowDefExec } = procdefApi.save.useApi(form);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					watch(props, (newValue: any) => {
 | 
				
			||||||
 | 
					    if (newValue.data) {
 | 
				
			||||||
 | 
					        state.form = { ...newValue.data };
 | 
				
			||||||
 | 
					        state.form.codePaths = newValue.data.tags?.map((tag: any) => tag.codePath);
 | 
				
			||||||
 | 
					        const tasks = JSON.parse(state.form.tasks);
 | 
				
			||||||
 | 
					        tasks.forEach((t: any) => {
 | 
				
			||||||
 | 
					            t.userId = Number.parseInt(t.userId);
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					        state.tasks = tasks;
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					        state.form = { status: ProcdefStatus.Enable.value } as any;
 | 
				
			||||||
 | 
					        state.form.condition = `{{/* DBMS-执行sql规则;  param参数描述如下 */}}
 | 
				
			||||||
 | 
					{{/* stmtType: select / read / insert / update / delete / ddl ;  */}}
 | 
				
			||||||
 | 
					{{ if eq .bizType "db_sql_exec_flow"}}
 | 
				
			||||||
 | 
					    {{/* 不是select和read语句时,开启流程审批 */}}
 | 
				
			||||||
 | 
					    {{ if and (ne .param.stmtType "select") (ne .param.stmtType "read") }}
 | 
				
			||||||
 | 
					        1
 | 
				
			||||||
 | 
					    {{ end }}
 | 
				
			||||||
 | 
					{{ end }}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					{{/* Redis-执行命令规则;   param参数描述如下 */}}
 | 
				
			||||||
 | 
					{{/* cmdType: read(读命令) / write(写命令);  */}}
 | 
				
			||||||
 | 
					{{/* cmd: get/set/hset...等 */}}
 | 
				
			||||||
 | 
					{{ if eq .bizType "redis_run_cmd_flow"}}
 | 
				
			||||||
 | 
					    {{ if eq .param.cmdType "write" }}
 | 
				
			||||||
 | 
					        1
 | 
				
			||||||
 | 
					    {{ end }}
 | 
				
			||||||
 | 
					{{ end }}`;
 | 
				
			||||||
 | 
					        state.tasks = [];
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const initSort = () => {
 | 
				
			||||||
 | 
					    nextTick(() => {
 | 
				
			||||||
 | 
					        const table = taskTableRef.value.$el.querySelector('table > tbody') as any;
 | 
				
			||||||
 | 
					        state.sortable = Sortable.create(table, {
 | 
				
			||||||
 | 
					            animation: 200,
 | 
				
			||||||
 | 
					            //拖拽结束事件
 | 
				
			||||||
 | 
					            onEnd: (evt) => {
 | 
				
			||||||
 | 
					                const curRow = state.tasks.splice(evt.oldIndex, 1)[0];
 | 
				
			||||||
 | 
					                state.tasks.splice(evt.newIndex, 0, curRow);
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const addTask = () => {
 | 
				
			||||||
 | 
					    state.tasks.push({ taskKey: randomUuid() });
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const deleteTask = (idx: any) => {
 | 
				
			||||||
 | 
					    state.tasks.splice(idx, 1);
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const btnOk = async () => {
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					        await formRef.value.validate();
 | 
				
			||||||
 | 
					    } catch (e: any) {
 | 
				
			||||||
 | 
					        ElMessage.error('请正确填写信息');
 | 
				
			||||||
 | 
					        return false;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const checkRes = checkTasks();
 | 
				
			||||||
 | 
					    if (checkRes.err) {
 | 
				
			||||||
 | 
					        ElMessage.error(checkRes.err);
 | 
				
			||||||
 | 
					        return false;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    state.form.tasks = JSON.stringify(checkRes.tasks);
 | 
				
			||||||
 | 
					    await saveFlowDefExec();
 | 
				
			||||||
 | 
					    ElMessage.success('操作成功');
 | 
				
			||||||
 | 
					    emit('val-change', state.form);
 | 
				
			||||||
 | 
					    //重置表单域
 | 
				
			||||||
 | 
					    formRef.value.resetFields();
 | 
				
			||||||
 | 
					    state.form = {} as any;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const checkTasks = () => {
 | 
				
			||||||
 | 
					    if (state.tasks?.length == 0) {
 | 
				
			||||||
 | 
					        return { err: '请完善审批节点任务' };
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const tasks = [];
 | 
				
			||||||
 | 
					    for (let i = 0; i < state.tasks.length; i++) {
 | 
				
			||||||
 | 
					        const task = { ...state.tasks[i] };
 | 
				
			||||||
 | 
					        if (!task.name || !task.userId) {
 | 
				
			||||||
 | 
					            return { err: `请完善第${i + 1}个审批节点任务信息` };
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        // 转为字符串(方便后续万一需要调整啥的)
 | 
				
			||||||
 | 
					        task.userId = `${task.userId}`;
 | 
				
			||||||
 | 
					        if (!task.taskKey) {
 | 
				
			||||||
 | 
					            task.taskKey = randomUuid();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        tasks.push(task);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return { tasks: tasks };
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const cancel = () => {
 | 
				
			||||||
 | 
					    visible.value = false;
 | 
				
			||||||
 | 
					    emit('cancel');
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					</script>
 | 
				
			||||||
 | 
					<style lang="scss"></style>
 | 
				
			||||||
							
								
								
									
										147
									
								
								mayfly_go_web/src/views/flow/ProcdefList.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										147
									
								
								mayfly_go_web/src/views/flow/ProcdefList.vue
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,147 @@
 | 
				
			|||||||
 | 
					<template>
 | 
				
			||||||
 | 
					    <div>
 | 
				
			||||||
 | 
					        <page-table
 | 
				
			||||||
 | 
					            ref="pageTableRef"
 | 
				
			||||||
 | 
					            :page-api="procdefApi.list"
 | 
				
			||||||
 | 
					            :search-items="searchItems"
 | 
				
			||||||
 | 
					            v-model:query-form="query"
 | 
				
			||||||
 | 
					            :show-selection="true"
 | 
				
			||||||
 | 
					            v-model:selection-data="selectionData"
 | 
				
			||||||
 | 
					            :columns="columns"
 | 
				
			||||||
 | 
					        >
 | 
				
			||||||
 | 
					            <template #tableHeader>
 | 
				
			||||||
 | 
					                <el-button v-auth="perms.save" type="primary" icon="plus" @click="editFlowDef(false)">添加</el-button>
 | 
				
			||||||
 | 
					                <el-button v-auth="perms.del" :disabled="state.selectionData.length < 1" @click="deleteProcdef()" type="danger" icon="delete">删除</el-button>
 | 
				
			||||||
 | 
					            </template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            <template #tasks="{ data }">
 | 
				
			||||||
 | 
					                <el-link @click="showProcdefTasks(data)" icon="view" type="primary" :underline="false"> </el-link>
 | 
				
			||||||
 | 
					            </template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            <template #codePaths="{ data }">
 | 
				
			||||||
 | 
					                <TagCodePath :path="data.tags?.map((tag: any) => tag.codePath)" />
 | 
				
			||||||
 | 
					            </template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            <template #action="{ data }">
 | 
				
			||||||
 | 
					                <el-button link v-if="actionBtns[perms.save]" @click="editFlowDef(data)" type="primary">编辑</el-button>
 | 
				
			||||||
 | 
					            </template>
 | 
				
			||||||
 | 
					        </page-table>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        <el-dialog v-model="flowTasksDialog.visible" :title="flowTasksDialog.title">
 | 
				
			||||||
 | 
					            <procdef-tasks :tasks="flowTasksDialog.tasks" />
 | 
				
			||||||
 | 
					        </el-dialog>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        <procdef-edit v-model:visible="flowDefEditor.visible" :title="flowDefEditor.title" v-model:data="flowDefEditor.data" @val-change="valChange()" />
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<script lang="ts" setup>
 | 
				
			||||||
 | 
					import { ref, toRefs, reactive, onMounted, Ref } from 'vue';
 | 
				
			||||||
 | 
					import { procdefApi } from './api';
 | 
				
			||||||
 | 
					import { ElMessage, ElMessageBox } from 'element-plus';
 | 
				
			||||||
 | 
					import PageTable from '@/components/pagetable/PageTable.vue';
 | 
				
			||||||
 | 
					import { TableColumn } from '@/components/pagetable';
 | 
				
			||||||
 | 
					import { hasPerms } from '@/components/auth/auth';
 | 
				
			||||||
 | 
					import { SearchItem } from '@/components/SearchForm';
 | 
				
			||||||
 | 
					import ProcdefEdit from './ProcdefEdit.vue';
 | 
				
			||||||
 | 
					import ProcdefTasks from './components/ProcdefTasks.vue';
 | 
				
			||||||
 | 
					import { ProcdefStatus } from './enums';
 | 
				
			||||||
 | 
					import TagCodePath from '../ops/component/TagCodePath.vue';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const perms = {
 | 
				
			||||||
 | 
					    save: 'flow:procdef:save',
 | 
				
			||||||
 | 
					    del: 'flow:procdef:del',
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const searchItems = [SearchItem.input('name', '名称'), SearchItem.input('defKey', 'key')];
 | 
				
			||||||
 | 
					const columns = [
 | 
				
			||||||
 | 
					    TableColumn.new('name', '名称'),
 | 
				
			||||||
 | 
					    TableColumn.new('defKey', 'key'),
 | 
				
			||||||
 | 
					    TableColumn.new('status', '状态').typeTag(ProcdefStatus),
 | 
				
			||||||
 | 
					    TableColumn.new('remark', '备注'),
 | 
				
			||||||
 | 
					    TableColumn.new('tasks', '审批节点').isSlot().alignCenter().setMinWidth(60),
 | 
				
			||||||
 | 
					    TableColumn.new('codePaths', '关联资源').isSlot().setMinWidth('250px'),
 | 
				
			||||||
 | 
					    TableColumn.new('creator', '创建账号'),
 | 
				
			||||||
 | 
					    TableColumn.new('createTime', '创建时间').isTime(),
 | 
				
			||||||
 | 
					];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// 该用户拥有的的操作列按钮权限
 | 
				
			||||||
 | 
					const actionBtns: any = hasPerms([perms.save, perms.del]);
 | 
				
			||||||
 | 
					const actionColumn = TableColumn.new('action', '操作').isSlot().fixedRight().setMinWidth(160).noShowOverflowTooltip().alignCenter();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const pageTableRef: Ref<any> = ref(null);
 | 
				
			||||||
 | 
					const state = reactive({
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * 选中的数据
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    selectionData: [],
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * 查询条件
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    query: {
 | 
				
			||||||
 | 
					        name: '',
 | 
				
			||||||
 | 
					        pageNum: 1,
 | 
				
			||||||
 | 
					        pageSize: 0,
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    flowDefEditor: {
 | 
				
			||||||
 | 
					        title: '新建流程定义',
 | 
				
			||||||
 | 
					        visible: false,
 | 
				
			||||||
 | 
					        data: null as any,
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    flowTasksDialog: {
 | 
				
			||||||
 | 
					        title: '',
 | 
				
			||||||
 | 
					        visible: false,
 | 
				
			||||||
 | 
					        tasks: '',
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const { selectionData, query, flowDefEditor, flowTasksDialog } = toRefs(state);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					onMounted(() => {
 | 
				
			||||||
 | 
					    if (Object.keys(actionBtns).length > 0) {
 | 
				
			||||||
 | 
					        columns.push(actionColumn);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const search = async () => {
 | 
				
			||||||
 | 
					    pageTableRef.value.search();
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const showProcdefTasks = (procdef: any) => {
 | 
				
			||||||
 | 
					    state.flowTasksDialog.tasks = procdef.tasks;
 | 
				
			||||||
 | 
					    state.flowTasksDialog.title = procdef.name + '-审批节点';
 | 
				
			||||||
 | 
					    state.flowTasksDialog.visible = true;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const editFlowDef = (data: any) => {
 | 
				
			||||||
 | 
					    if (!data) {
 | 
				
			||||||
 | 
					        state.flowDefEditor.data = null;
 | 
				
			||||||
 | 
					        state.flowDefEditor.title = '新建流程定义';
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					        state.flowDefEditor.data = data;
 | 
				
			||||||
 | 
					        state.flowDefEditor.title = '编辑流程定义';
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    state.flowDefEditor.visible = true;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const valChange = () => {
 | 
				
			||||||
 | 
					    state.flowDefEditor.visible = false;
 | 
				
			||||||
 | 
					    search();
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const deleteProcdef = async () => {
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					        await ElMessageBox.confirm(`确定删除【${state.selectionData.map((x: any) => x.name).join(', ')}】的流程定义?`, '提示', {
 | 
				
			||||||
 | 
					            confirmButtonText: '确定',
 | 
				
			||||||
 | 
					            cancelButtonText: '取消',
 | 
				
			||||||
 | 
					            type: 'warning',
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					        await procdefApi.del.request({ id: state.selectionData.map((x: any) => x.id).join(',') });
 | 
				
			||||||
 | 
					        ElMessage.success('删除成功');
 | 
				
			||||||
 | 
					        search();
 | 
				
			||||||
 | 
					    } catch (err) {
 | 
				
			||||||
 | 
					        //
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					</script>
 | 
				
			||||||
 | 
					<style lang="scss"></style>
 | 
				
			||||||
							
								
								
									
										163
									
								
								mayfly_go_web/src/views/flow/ProcinstDetail.vue
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										163
									
								
								mayfly_go_web/src/views/flow/ProcinstDetail.vue
									
									
									
									
									
										Executable file
									
								
							@@ -0,0 +1,163 @@
 | 
				
			|||||||
 | 
					<template>
 | 
				
			||||||
 | 
					    <div>
 | 
				
			||||||
 | 
					        <el-drawer :title="props.title" v-model="visible" :before-close="cancel" size="50%" :close-on-click-modal="!props.instTaskId">
 | 
				
			||||||
 | 
					            <template #header>
 | 
				
			||||||
 | 
					                <DrawerHeader :header="title" :back="cancel" />
 | 
				
			||||||
 | 
					            </template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            <div>
 | 
				
			||||||
 | 
					                <el-divider content-position="left">流程信息</el-divider>
 | 
				
			||||||
 | 
					                <el-descriptions :column="3" border>
 | 
				
			||||||
 | 
					                    <el-descriptions-item label="流程名">{{ procinst.procdefName }}</el-descriptions-item>
 | 
				
			||||||
 | 
					                    <el-descriptions-item label="业务">
 | 
				
			||||||
 | 
					                        <enum-tag :enums="FlowBizType" :value="procinst.bizType"></enum-tag>
 | 
				
			||||||
 | 
					                    </el-descriptions-item>
 | 
				
			||||||
 | 
					                    <el-descriptions-item label="发起人">
 | 
				
			||||||
 | 
					                        <AccountInfo :account-id="procinst.creatorId" :username="procinst.creator" />
 | 
				
			||||||
 | 
					                    </el-descriptions-item>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    <el-descriptions-item label="流程状态">
 | 
				
			||||||
 | 
					                        <enum-tag :enums="ProcinstStatus" :value="procinst.status"></enum-tag>
 | 
				
			||||||
 | 
					                    </el-descriptions-item>
 | 
				
			||||||
 | 
					                    <el-descriptions-item label="业务状态">
 | 
				
			||||||
 | 
					                        <enum-tag :enums="ProcinstBizStatus" :value="procinst.bizStatus"></enum-tag>
 | 
				
			||||||
 | 
					                    </el-descriptions-item>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    <el-descriptions-item label="发起时间">{{ formatDate(procinst.createTime) }}</el-descriptions-item>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    <div v-if="procinst.duration">
 | 
				
			||||||
 | 
					                        <el-descriptions-item label="结束时间">{{ formatDate(procinst.endTime) }}</el-descriptions-item>
 | 
				
			||||||
 | 
					                        <el-descriptions-item label="持续时间">{{ formatTime(procinst.duration) }}</el-descriptions-item>
 | 
				
			||||||
 | 
					                    </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    <el-descriptions-item label="备注">
 | 
				
			||||||
 | 
					                        {{ procinst.remark }}
 | 
				
			||||||
 | 
					                    </el-descriptions-item>
 | 
				
			||||||
 | 
					                </el-descriptions>
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            <div>
 | 
				
			||||||
 | 
					                <el-divider content-position="left">审批节点</el-divider>
 | 
				
			||||||
 | 
					                <procdef-tasks :tasks="procinst?.procdef?.tasks" :procinst-tasks="procinst.procinstTasks" />
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            <div>
 | 
				
			||||||
 | 
					                <el-divider content-position="left">业务信息</el-divider>
 | 
				
			||||||
 | 
					                <component v-if="procinst.bizType" ref="keyValueRef" :is="bizComponents[procinst.bizType]" :procinst="procinst"> </component>
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            <div v-if="props.instTaskId">
 | 
				
			||||||
 | 
					                <el-divider content-position="left">审批表单</el-divider>
 | 
				
			||||||
 | 
					                <el-form :model="form" label-width="auto">
 | 
				
			||||||
 | 
					                    <el-form-item prop="status" label="结果" required>
 | 
				
			||||||
 | 
					                        <el-select v-model="form.status" placeholder="请选择审批结果">
 | 
				
			||||||
 | 
					                            <el-option :label="ProcinstTaskStatus.Pass.label" :value="ProcinstTaskStatus.Pass.value"> </el-option>
 | 
				
			||||||
 | 
					                            <!-- <el-option :label="ProcinstTaskStatus.Back.label" :value="ProcinstTaskStatus.Back.value"> </el-option> -->
 | 
				
			||||||
 | 
					                            <el-option :label="ProcinstTaskStatus.Reject.label" :value="ProcinstTaskStatus.Reject.value"> </el-option>
 | 
				
			||||||
 | 
					                        </el-select>
 | 
				
			||||||
 | 
					                    </el-form-item>
 | 
				
			||||||
 | 
					                    <el-form-item prop="remark" label="备注">
 | 
				
			||||||
 | 
					                        <el-input v-model.trim="form.remark" placeholder="备注" type="textarea" clearable></el-input>
 | 
				
			||||||
 | 
					                    </el-form-item>
 | 
				
			||||||
 | 
					                </el-form>
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            <template #footer v-if="props.instTaskId">
 | 
				
			||||||
 | 
					                <div>
 | 
				
			||||||
 | 
					                    <el-button @click="cancel()">取 消</el-button>
 | 
				
			||||||
 | 
					                    <el-button type="primary" :loading="saveBtnLoading" @click="btnOk">确 定</el-button>
 | 
				
			||||||
 | 
					                </div>
 | 
				
			||||||
 | 
					            </template>
 | 
				
			||||||
 | 
					        </el-drawer>
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<script lang="ts" setup>
 | 
				
			||||||
 | 
					import { toRefs, reactive, watch, defineAsyncComponent, shallowReactive } from 'vue';
 | 
				
			||||||
 | 
					import { procinstApi } from './api';
 | 
				
			||||||
 | 
					import { ElMessage } from 'element-plus';
 | 
				
			||||||
 | 
					import DrawerHeader from '@/components/drawer-header/DrawerHeader.vue';
 | 
				
			||||||
 | 
					import { FlowBizType, ProcinstBizStatus, ProcinstTaskStatus, ProcinstStatus } from './enums';
 | 
				
			||||||
 | 
					import ProcdefTasks from './components/ProcdefTasks.vue';
 | 
				
			||||||
 | 
					import { formatTime } from '@/common/utils/format';
 | 
				
			||||||
 | 
					import EnumTag from '@/components/enumtag/EnumTag.vue';
 | 
				
			||||||
 | 
					import AccountInfo from '@/views/system/account/components/AccountInfo.vue';
 | 
				
			||||||
 | 
					import { formatDate } from '@/common/utils/format';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const DbSqlExecBiz = defineAsyncComponent(() => import('./flowbiz/dbms/DbSqlExecBiz.vue'));
 | 
				
			||||||
 | 
					const RedisRunCmdBiz = defineAsyncComponent(() => import('./flowbiz/redis/RedisRunCmdBiz.vue'));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const props = defineProps({
 | 
				
			||||||
 | 
					    procinstId: {
 | 
				
			||||||
 | 
					        type: Number,
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    // 流程实例任务id(存在则展示审批相关信息)
 | 
				
			||||||
 | 
					    instTaskId: {
 | 
				
			||||||
 | 
					        type: Number,
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    title: {
 | 
				
			||||||
 | 
					        type: String,
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const visible = defineModel<boolean>('visible', { default: false });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					//定义事件
 | 
				
			||||||
 | 
					const emit = defineEmits(['cancel', 'val-change']);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// 业务组件
 | 
				
			||||||
 | 
					const bizComponents: any = shallowReactive({
 | 
				
			||||||
 | 
					    db_sql_exec_flow: DbSqlExecBiz,
 | 
				
			||||||
 | 
					    redis_run_cmd_flow: RedisRunCmdBiz,
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const state = reactive({
 | 
				
			||||||
 | 
					    procinst: {} as any,
 | 
				
			||||||
 | 
					    tasks: [] as any,
 | 
				
			||||||
 | 
					    form: {
 | 
				
			||||||
 | 
					        status: ProcinstTaskStatus.Pass.value,
 | 
				
			||||||
 | 
					        remark: '',
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    saveBtnLoading: false,
 | 
				
			||||||
 | 
					    sortable: '' as any,
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const { procinst, form, saveBtnLoading } = toRefs(state);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					watch(
 | 
				
			||||||
 | 
					    () => props.procinstId,
 | 
				
			||||||
 | 
					    async (newValue: any) => {
 | 
				
			||||||
 | 
					        if (newValue) {
 | 
				
			||||||
 | 
					            state.procinst = await procinstApi.detail.request({ id: newValue });
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            state.procinst = {};
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const btnOk = async () => {
 | 
				
			||||||
 | 
					    const status = state.form.status;
 | 
				
			||||||
 | 
					    let api = procinstApi.completeTask;
 | 
				
			||||||
 | 
					    if (status === ProcinstTaskStatus.Back.value) {
 | 
				
			||||||
 | 
					        api = procinstApi.backTask;
 | 
				
			||||||
 | 
					    } else if (status === ProcinstTaskStatus.Reject.value) {
 | 
				
			||||||
 | 
					        api = procinstApi.rejectTask;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					        state.saveBtnLoading = true;
 | 
				
			||||||
 | 
					        await api.request({ id: props.instTaskId, remark: state.form.remark });
 | 
				
			||||||
 | 
					        ElMessage.success('操作成功');
 | 
				
			||||||
 | 
					        cancel();
 | 
				
			||||||
 | 
					        emit('val-change');
 | 
				
			||||||
 | 
					    } finally {
 | 
				
			||||||
 | 
					        state.saveBtnLoading = false;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const cancel = () => {
 | 
				
			||||||
 | 
					    visible.value = false;
 | 
				
			||||||
 | 
					    emit('cancel');
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					</script>
 | 
				
			||||||
 | 
					<style lang="scss"></style>
 | 
				
			||||||
							
								
								
									
										137
									
								
								mayfly_go_web/src/views/flow/ProcinstList.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										137
									
								
								mayfly_go_web/src/views/flow/ProcinstList.vue
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,137 @@
 | 
				
			|||||||
 | 
					<template>
 | 
				
			||||||
 | 
					    <div>
 | 
				
			||||||
 | 
					        <page-table
 | 
				
			||||||
 | 
					            ref="pageTableRef"
 | 
				
			||||||
 | 
					            :page-api="procinstApi.list"
 | 
				
			||||||
 | 
					            :search-items="searchItems"
 | 
				
			||||||
 | 
					            v-model:query-form="query"
 | 
				
			||||||
 | 
					            v-model:selection-data="selectionData"
 | 
				
			||||||
 | 
					            :columns="columns"
 | 
				
			||||||
 | 
					        >
 | 
				
			||||||
 | 
					            <template #tableHeader>
 | 
				
			||||||
 | 
					                <el-button type="primary" icon="plus" @click="startProcInst()">发起流程</el-button>
 | 
				
			||||||
 | 
					            </template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            <template #action="{ data }">
 | 
				
			||||||
 | 
					                <el-button link @click="showProcinst(data)" type="primary">查看</el-button>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                <el-popconfirm
 | 
				
			||||||
 | 
					                    v-if="data.status == ProcinstStatus.Active.value || data.status == ProcinstStatus.Suspended.value"
 | 
				
			||||||
 | 
					                    title="确认取消该流程?"
 | 
				
			||||||
 | 
					                    width="160"
 | 
				
			||||||
 | 
					                    @confirm="procinstCancel(data)"
 | 
				
			||||||
 | 
					                >
 | 
				
			||||||
 | 
					                    <template #reference>
 | 
				
			||||||
 | 
					                        <el-button link type="warning">取消</el-button>
 | 
				
			||||||
 | 
					                    </template>
 | 
				
			||||||
 | 
					                </el-popconfirm>
 | 
				
			||||||
 | 
					            </template>
 | 
				
			||||||
 | 
					        </page-table>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        <ProcinstDetail
 | 
				
			||||||
 | 
					            v-model:visible="procinstDetail.visible"
 | 
				
			||||||
 | 
					            :title="procinstDetail.title"
 | 
				
			||||||
 | 
					            :procinst-id="procinstDetail.procinstId"
 | 
				
			||||||
 | 
					            :inst-task-id="procinstDetail.instTaskId"
 | 
				
			||||||
 | 
					            @val-change="valChange()"
 | 
				
			||||||
 | 
					            @cancel="procinstDetail.procinstId = 0"
 | 
				
			||||||
 | 
					        />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        <ProcInstEdit v-model:visible="procinstEdit.visible" :title="procinstEdit.title" @val-change="search" />
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<script lang="ts" setup>
 | 
				
			||||||
 | 
					import { ref, toRefs, reactive, Ref } from 'vue';
 | 
				
			||||||
 | 
					import { procinstApi } from './api';
 | 
				
			||||||
 | 
					import PageTable from '@/components/pagetable/PageTable.vue';
 | 
				
			||||||
 | 
					import { TableColumn } from '@/components/pagetable';
 | 
				
			||||||
 | 
					import { SearchItem } from '@/components/SearchForm';
 | 
				
			||||||
 | 
					import ProcinstDetail from './ProcinstDetail.vue';
 | 
				
			||||||
 | 
					import { FlowBizType, ProcinstBizStatus, ProcinstStatus } from './enums';
 | 
				
			||||||
 | 
					import { ElMessage } from 'element-plus';
 | 
				
			||||||
 | 
					import { formatTime } from '@/common/utils/format';
 | 
				
			||||||
 | 
					import ProcInstEdit from './ProcInstEdit.vue';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const searchItems = [
 | 
				
			||||||
 | 
					    SearchItem.select('status', '流程状态').withEnum(ProcinstStatus),
 | 
				
			||||||
 | 
					    SearchItem.select('bizType', '业务类型').withEnum(FlowBizType),
 | 
				
			||||||
 | 
					    SearchItem.input('bizKey', '业务key'),
 | 
				
			||||||
 | 
					];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const columns = [
 | 
				
			||||||
 | 
					    TableColumn.new('bizType', '业务').typeTag(FlowBizType),
 | 
				
			||||||
 | 
					    TableColumn.new('remark', '备注'),
 | 
				
			||||||
 | 
					    TableColumn.new('creator', '发起人'),
 | 
				
			||||||
 | 
					    TableColumn.new('bizKey', '业务key'),
 | 
				
			||||||
 | 
					    TableColumn.new('procdefName', '流程名'),
 | 
				
			||||||
 | 
					    TableColumn.new('status', '流程状态').typeTag(ProcinstStatus),
 | 
				
			||||||
 | 
					    TableColumn.new('bizStatus', '业务状态').typeTag(ProcinstBizStatus),
 | 
				
			||||||
 | 
					    TableColumn.new('createTime', '发起时间').isTime(),
 | 
				
			||||||
 | 
					    TableColumn.new('endTime', '结束时间').isTime(),
 | 
				
			||||||
 | 
					    TableColumn.new('duration', '持续时间').setFormatFunc((data: any, prop: string) => {
 | 
				
			||||||
 | 
					        const duration = data[prop];
 | 
				
			||||||
 | 
					        if (!duration) {
 | 
				
			||||||
 | 
					            return '';
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        return formatTime(duration);
 | 
				
			||||||
 | 
					    }),
 | 
				
			||||||
 | 
					    // TableColumn.new('bizHandleRes', '业务处理结果'),
 | 
				
			||||||
 | 
					    TableColumn.new('action', '操作').isSlot().fixedRight().setMinWidth(160).noShowOverflowTooltip().alignCenter(),
 | 
				
			||||||
 | 
					];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const pageTableRef: Ref<any> = ref(null);
 | 
				
			||||||
 | 
					const state = reactive({
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * 选中的数据
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    selectionData: [],
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * 查询条件
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    query: {
 | 
				
			||||||
 | 
					        status: null,
 | 
				
			||||||
 | 
					        bizType: '',
 | 
				
			||||||
 | 
					        pageNum: 1,
 | 
				
			||||||
 | 
					        pageSize: 0,
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    procinstDetail: {
 | 
				
			||||||
 | 
					        title: '查看流程',
 | 
				
			||||||
 | 
					        visible: false,
 | 
				
			||||||
 | 
					        procinstId: 0,
 | 
				
			||||||
 | 
					        instTaskId: 0,
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    procinstEdit: {
 | 
				
			||||||
 | 
					        title: '发起流程',
 | 
				
			||||||
 | 
					        visible: false,
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const { selectionData, query, procinstDetail, procinstEdit } = toRefs(state);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const search = async () => {
 | 
				
			||||||
 | 
					    pageTableRef.value.search();
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const procinstCancel = async (data: any) => {
 | 
				
			||||||
 | 
					    await procinstApi.cancel.request({ id: data.id });
 | 
				
			||||||
 | 
					    ElMessage.success('操作成功');
 | 
				
			||||||
 | 
					    search();
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const showProcinst = (data: any) => {
 | 
				
			||||||
 | 
					    state.procinstDetail.procinstId = data.id;
 | 
				
			||||||
 | 
					    state.procinstDetail.title = '流程查看';
 | 
				
			||||||
 | 
					    state.procinstDetail.visible = true;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const startProcInst = () => {
 | 
				
			||||||
 | 
					    state.procinstEdit.visible = true;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const valChange = () => {
 | 
				
			||||||
 | 
					    state.procinstDetail.visible = false;
 | 
				
			||||||
 | 
					    search();
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					</script>
 | 
				
			||||||
 | 
					<style lang="scss"></style>
 | 
				
			||||||
							
								
								
									
										111
									
								
								mayfly_go_web/src/views/flow/ProcinstTaskList.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										111
									
								
								mayfly_go_web/src/views/flow/ProcinstTaskList.vue
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,111 @@
 | 
				
			|||||||
 | 
					<template>
 | 
				
			||||||
 | 
					    <div>
 | 
				
			||||||
 | 
					        <page-table
 | 
				
			||||||
 | 
					            ref="pageTableRef"
 | 
				
			||||||
 | 
					            :page-api="procinstApi.tasks"
 | 
				
			||||||
 | 
					            :search-items="searchItems"
 | 
				
			||||||
 | 
					            v-model:query-form="query"
 | 
				
			||||||
 | 
					            v-model:selection-data="selectionData"
 | 
				
			||||||
 | 
					            :columns="columns"
 | 
				
			||||||
 | 
					        >
 | 
				
			||||||
 | 
					            <template #tableHeader>
 | 
				
			||||||
 | 
					                <!-- <el-button v-auth="perms.addAccount" type="primary" icon="plus" @click="editFlowDef(false)">添加</el-button> -->
 | 
				
			||||||
 | 
					            </template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            <template #action="{ data }">
 | 
				
			||||||
 | 
					                <el-button link @click="showProcinst(data, false)" type="primary">查看</el-button>
 | 
				
			||||||
 | 
					                <el-button v-if="data.status == ProcinstTaskStatus.Process.value" link @click="showProcinst(data, true)" type="primary">审核</el-button>
 | 
				
			||||||
 | 
					            </template>
 | 
				
			||||||
 | 
					        </page-table>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        <ProcinstDetail
 | 
				
			||||||
 | 
					            v-model:visible="procinstDetail.visible"
 | 
				
			||||||
 | 
					            :title="procinstDetail.title"
 | 
				
			||||||
 | 
					            :procinst-id="procinstDetail.procinstId"
 | 
				
			||||||
 | 
					            :inst-task-id="procinstDetail.instTaskId"
 | 
				
			||||||
 | 
					            @val-change="valChange()"
 | 
				
			||||||
 | 
					            @cancel="procinstDetail.procinstId = 0"
 | 
				
			||||||
 | 
					        />
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<script lang="ts" setup>
 | 
				
			||||||
 | 
					import { ref, toRefs, reactive, Ref } from 'vue';
 | 
				
			||||||
 | 
					import { procinstApi } from './api';
 | 
				
			||||||
 | 
					import PageTable from '@/components/pagetable/PageTable.vue';
 | 
				
			||||||
 | 
					import { TableColumn } from '@/components/pagetable';
 | 
				
			||||||
 | 
					import { SearchItem } from '@/components/SearchForm';
 | 
				
			||||||
 | 
					import ProcinstDetail from './ProcinstDetail.vue';
 | 
				
			||||||
 | 
					import { FlowBizType, ProcinstStatus, ProcinstTaskStatus } from './enums';
 | 
				
			||||||
 | 
					import { formatTime } from '@/common/utils/format';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const searchItems = [SearchItem.select('status', '任务状态').withEnum(ProcinstTaskStatus), SearchItem.select('bizType', '业务类型').withEnum(FlowBizType)];
 | 
				
			||||||
 | 
					const columns = [
 | 
				
			||||||
 | 
					    TableColumn.new('procinst.bizType', '业务').typeTag(FlowBizType),
 | 
				
			||||||
 | 
					    TableColumn.new('procinst.remark', '备注'),
 | 
				
			||||||
 | 
					    TableColumn.new('procinst.creator', '发起人'),
 | 
				
			||||||
 | 
					    TableColumn.new('procinst.status', '流程状态').typeTag(ProcinstStatus),
 | 
				
			||||||
 | 
					    TableColumn.new('status', '任务状态').typeTag(ProcinstTaskStatus),
 | 
				
			||||||
 | 
					    TableColumn.new('procinst.bizKey', '业务key'),
 | 
				
			||||||
 | 
					    TableColumn.new('procinst.procdefName', '流程名'),
 | 
				
			||||||
 | 
					    TableColumn.new('taskName', '当前节点'),
 | 
				
			||||||
 | 
					    TableColumn.new('procinst.createTime', '发起时间').isTime(),
 | 
				
			||||||
 | 
					    TableColumn.new('createTime', '开始时间').isTime(),
 | 
				
			||||||
 | 
					    TableColumn.new('endTime', '结束时间').isTime(),
 | 
				
			||||||
 | 
					    TableColumn.new('duration', '持续时间').setFormatFunc((data: any, prop: string) => {
 | 
				
			||||||
 | 
					        const duration = data[prop];
 | 
				
			||||||
 | 
					        if (!duration) {
 | 
				
			||||||
 | 
					            return '';
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        return formatTime(duration);
 | 
				
			||||||
 | 
					    }),
 | 
				
			||||||
 | 
					    TableColumn.new('action', '操作').isSlot().fixedRight().setMinWidth(160).noShowOverflowTooltip().alignCenter(),
 | 
				
			||||||
 | 
					];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const pageTableRef: Ref<any> = ref(null);
 | 
				
			||||||
 | 
					const state = reactive({
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * 选中的数据
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    selectionData: [],
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * 查询条件
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    query: {
 | 
				
			||||||
 | 
					        status: ProcinstTaskStatus.Process.value,
 | 
				
			||||||
 | 
					        bizType: '',
 | 
				
			||||||
 | 
					        pageNum: 1,
 | 
				
			||||||
 | 
					        pageSize: 0,
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    procinstDetail: {
 | 
				
			||||||
 | 
					        title: '查看流程',
 | 
				
			||||||
 | 
					        visible: false,
 | 
				
			||||||
 | 
					        procinstId: 0,
 | 
				
			||||||
 | 
					        instTaskId: 0,
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const { selectionData, query, procinstDetail } = toRefs(state);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const search = async () => {
 | 
				
			||||||
 | 
					    pageTableRef.value.search();
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const showProcinst = (data: any, audit: boolean) => {
 | 
				
			||||||
 | 
					    state.procinstDetail.procinstId = data.procinstId;
 | 
				
			||||||
 | 
					    if (!audit) {
 | 
				
			||||||
 | 
					        state.procinstDetail.instTaskId = 0;
 | 
				
			||||||
 | 
					        state.procinstDetail.title = '流程查看';
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					        state.procinstDetail.instTaskId = data.id;
 | 
				
			||||||
 | 
					        state.procinstDetail.title = '流程审批';
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    state.procinstDetail.visible = true;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const valChange = () => {
 | 
				
			||||||
 | 
					    state.procinstDetail.visible = false;
 | 
				
			||||||
 | 
					    search();
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					</script>
 | 
				
			||||||
 | 
					<style lang="scss"></style>
 | 
				
			||||||
							
								
								
									
										21
									
								
								mayfly_go_web/src/views/flow/api.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								mayfly_go_web/src/views/flow/api.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,21 @@
 | 
				
			|||||||
 | 
					import Api from '@/common/Api';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const procdefApi = {
 | 
				
			||||||
 | 
					    list: Api.newGet('/flow/procdefs'),
 | 
				
			||||||
 | 
					    getByResource: Api.newGet('/flow/procdefs/{resourceType}/{resourceCode}'),
 | 
				
			||||||
 | 
					    save: Api.newPost('/flow/procdefs'),
 | 
				
			||||||
 | 
					    del: Api.newDelete('/flow/procdefs/{id}'),
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const procinstApi = {
 | 
				
			||||||
 | 
					    list: Api.newGet('/flow/procinsts'),
 | 
				
			||||||
 | 
					    start: Api.newPost('/flow/procinsts/start'),
 | 
				
			||||||
 | 
					    detail: Api.newGet('/flow/procinsts/{id}'),
 | 
				
			||||||
 | 
					    cancel: Api.newPost('/flow/procinsts/{id}/cancel'),
 | 
				
			||||||
 | 
					    tasks: Api.newGet('/flow/procinsts/tasks'),
 | 
				
			||||||
 | 
					    completeTask: Api.newPost('/flow/procinsts/tasks/complete'),
 | 
				
			||||||
 | 
					    backTask: Api.newPost('/flow/procinsts/tasks/back'),
 | 
				
			||||||
 | 
					    rejectTask: Api.newPost('/flow/procinsts/tasks/reject'),
 | 
				
			||||||
 | 
					    save: Api.newPost('/flow/procdefs'),
 | 
				
			||||||
 | 
					    del: Api.newDelete('/flow/procdefs/{id}'),
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
@@ -0,0 +1,33 @@
 | 
				
			|||||||
 | 
					<template>
 | 
				
			||||||
 | 
					    <el-form-item :label="props.label">
 | 
				
			||||||
 | 
					        <el-select style="width: 100%" v-model="procdefKey" filterable placeholder="绑定流程则开启对应审批流程" v-bind="$attrs" clearable>
 | 
				
			||||||
 | 
					            <el-option v-for="item in procdefs" :key="item.defKey" :label="`${item.defKey} [${item.name}]`" :value="item.defKey"> </el-option>
 | 
				
			||||||
 | 
					        </el-select>
 | 
				
			||||||
 | 
					    </el-form-item>
 | 
				
			||||||
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<script lang="ts" setup>
 | 
				
			||||||
 | 
					import { onMounted, ref } from 'vue';
 | 
				
			||||||
 | 
					import { procdefApi } from '../api';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const props = defineProps({
 | 
				
			||||||
 | 
					    label: {
 | 
				
			||||||
 | 
					        type: String,
 | 
				
			||||||
 | 
					        default: '工单流程',
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					onMounted(() => {
 | 
				
			||||||
 | 
					    getProcdefs();
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const procdefKey = defineModel('modelValue');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const procdefs: any = ref([]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const getProcdefs = () => {
 | 
				
			||||||
 | 
					    procdefApi.list.request({ pageSize: 200 }).then((res) => {
 | 
				
			||||||
 | 
					        procdefs.value = res.list;
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					</script>
 | 
				
			||||||
							
								
								
									
										134
									
								
								mayfly_go_web/src/views/flow/components/ProcdefTasks.vue
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										134
									
								
								mayfly_go_web/src/views/flow/components/ProcdefTasks.vue
									
									
									
									
									
										Executable file
									
								
							@@ -0,0 +1,134 @@
 | 
				
			|||||||
 | 
					<template>
 | 
				
			||||||
 | 
					    <el-steps align-center :active="stepActive">
 | 
				
			||||||
 | 
					        <el-step v-for="task in tasksArr" :status="getStepStatus(task)" :title="task.name" :key="task.taskKey">
 | 
				
			||||||
 | 
					            <template #description>
 | 
				
			||||||
 | 
					                <div>{{ `${task.accountUsername}(${task.accountName})` }}</div>
 | 
				
			||||||
 | 
					                <div v-if="task.completeTime">{{ `${formatDate(task.completeTime)}` }}</div>
 | 
				
			||||||
 | 
					                <div v-if="task.remark">{{ task.remark }}</div>
 | 
				
			||||||
 | 
					            </template>
 | 
				
			||||||
 | 
					        </el-step>
 | 
				
			||||||
 | 
					    </el-steps>
 | 
				
			||||||
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<script lang="ts" setup>
 | 
				
			||||||
 | 
					import { toRefs, reactive, watch, onMounted } from 'vue';
 | 
				
			||||||
 | 
					import { accountApi } from '../../system/api';
 | 
				
			||||||
 | 
					import { ProcinstTaskStatus } from '../enums';
 | 
				
			||||||
 | 
					import { formatDate } from '@/common/utils/format';
 | 
				
			||||||
 | 
					import { ElSteps, ElStep } from 'element-plus';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const props = defineProps({
 | 
				
			||||||
 | 
					    // 流程定义任务
 | 
				
			||||||
 | 
					    tasks: {
 | 
				
			||||||
 | 
					        type: [String, Object],
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    procdef: {
 | 
				
			||||||
 | 
					        type: [Object],
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    // 流程实例任务列表
 | 
				
			||||||
 | 
					    procinstTasks: {
 | 
				
			||||||
 | 
					        type: [Array],
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const state = reactive({
 | 
				
			||||||
 | 
					    tasksArr: [] as any,
 | 
				
			||||||
 | 
					    stepActive: 0,
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const { tasksArr, stepActive } = toRefs(state);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					watch(
 | 
				
			||||||
 | 
					    () => props.tasks,
 | 
				
			||||||
 | 
					    (newValue: any) => {
 | 
				
			||||||
 | 
					        parseTasks(newValue);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					watch(
 | 
				
			||||||
 | 
					    () => props.procinstTasks,
 | 
				
			||||||
 | 
					    () => {
 | 
				
			||||||
 | 
					        parseTasks(props.tasks);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					watch(
 | 
				
			||||||
 | 
					    () => props.procdef,
 | 
				
			||||||
 | 
					    async (newValue: any) => {
 | 
				
			||||||
 | 
					        if (newValue) {
 | 
				
			||||||
 | 
					            parseTasksByKey(newValue);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					onMounted(() => {
 | 
				
			||||||
 | 
					    if (props.procdef) {
 | 
				
			||||||
 | 
					        parseTasksByKey(props.procdef);
 | 
				
			||||||
 | 
					        return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    parseTasks(props.tasks);
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const parseTasksByKey = async (procdef: any) => {
 | 
				
			||||||
 | 
					    parseTasks(procdef.tasks);
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const parseTasks = async (tasksStr: any) => {
 | 
				
			||||||
 | 
					    if (!tasksStr) return;
 | 
				
			||||||
 | 
					    const tasks = JSON.parse(tasksStr);
 | 
				
			||||||
 | 
					    const userIds = tasks.map((x: any) => x.userId);
 | 
				
			||||||
 | 
					    const usersRes = await accountApi.querySimple.request({ ids: [...new Set(userIds)].join(','), pageSize: 50 });
 | 
				
			||||||
 | 
					    const users = usersRes.list;
 | 
				
			||||||
 | 
					    // 将数组转换为 Map 结构,以 id 为 key
 | 
				
			||||||
 | 
					    const userMap = users.reduce((acc: any, obj: any) => {
 | 
				
			||||||
 | 
					        acc.set(obj.id, obj);
 | 
				
			||||||
 | 
					        return acc;
 | 
				
			||||||
 | 
					    }, new Map());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // 流程实例任务(用于显示完成时间,完成到哪一步等)
 | 
				
			||||||
 | 
					    let instTasksMap: any;
 | 
				
			||||||
 | 
					    if (props.procinstTasks) {
 | 
				
			||||||
 | 
					        state.stepActive = props.procinstTasks.length - 1;
 | 
				
			||||||
 | 
					        instTasksMap = props.procinstTasks.reduce((acc: any, obj: any) => {
 | 
				
			||||||
 | 
					            acc.set(obj.taskKey, obj);
 | 
				
			||||||
 | 
					            return acc;
 | 
				
			||||||
 | 
					        }, new Map());
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    for (let task of tasks) {
 | 
				
			||||||
 | 
					        const user = userMap.get(Number.parseInt(task.userId));
 | 
				
			||||||
 | 
					        task.accountUsername = user.username;
 | 
				
			||||||
 | 
					        task.accountName = user.name;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // 存在实例任务,则赋值实例任务对应的完成时间和备注
 | 
				
			||||||
 | 
					        const instTask = instTasksMap?.get(task.taskKey);
 | 
				
			||||||
 | 
					        if (instTask) {
 | 
				
			||||||
 | 
					            task.status = instTask.status;
 | 
				
			||||||
 | 
					            task.completeTime = instTask.endTime;
 | 
				
			||||||
 | 
					            task.remark = instTask.remark;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    state.tasksArr = tasks;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const getStepStatus = (task: any): any => {
 | 
				
			||||||
 | 
					    const taskStatus = task.status;
 | 
				
			||||||
 | 
					    if (!taskStatus) {
 | 
				
			||||||
 | 
					        return 'wait';
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (taskStatus == ProcinstTaskStatus.Pass.value) {
 | 
				
			||||||
 | 
					        return 'success';
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    if (taskStatus == ProcinstTaskStatus.Process.value) {
 | 
				
			||||||
 | 
					        return 'proccess';
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    if (taskStatus == ProcinstTaskStatus.Back.value || taskStatus == ProcinstTaskStatus.Reject.value) {
 | 
				
			||||||
 | 
					        return 'error';
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return 'wait';
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					</script>
 | 
				
			||||||
 | 
					<style lang="scss"></style>
 | 
				
			||||||
							
								
								
									
										34
									
								
								mayfly_go_web/src/views/flow/enums.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								mayfly_go_web/src/views/flow/enums.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,34 @@
 | 
				
			|||||||
 | 
					import { EnumValue } from '@/common/Enum';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const ProcdefStatus = {
 | 
				
			||||||
 | 
					    Enable: EnumValue.of(1, '启用').setTagType('success'),
 | 
				
			||||||
 | 
					    Disable: EnumValue.of(-1, '禁用').setTagType('warning'),
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const ProcinstStatus = {
 | 
				
			||||||
 | 
					    Active: EnumValue.of(1, '执行中').setTagType('primary'),
 | 
				
			||||||
 | 
					    Completed: EnumValue.of(2, '完成').setTagType('success'),
 | 
				
			||||||
 | 
					    Suspended: EnumValue.of(-1, '挂起').setTagType('warning'),
 | 
				
			||||||
 | 
					    Terminated: EnumValue.of(-2, '终止').setTagType('danger'),
 | 
				
			||||||
 | 
					    Cancelled: EnumValue.of(-3, '取消').setTagType('warning'),
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const ProcinstBizStatus = {
 | 
				
			||||||
 | 
					    Wait: EnumValue.of(1, '待处理').setTagType('primary'),
 | 
				
			||||||
 | 
					    Success: EnumValue.of(2, '处理成功').setTagType('success'),
 | 
				
			||||||
 | 
					    Fail: EnumValue.of(-2, '处理失败').setTagType('danger'),
 | 
				
			||||||
 | 
					    No: EnumValue.of(-1, '不处理').setTagType('warning'),
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const ProcinstTaskStatus = {
 | 
				
			||||||
 | 
					    Process: EnumValue.of(1, '待处理').setTagType('primary'),
 | 
				
			||||||
 | 
					    Pass: EnumValue.of(2, '通过').setTagType('success'),
 | 
				
			||||||
 | 
					    Reject: EnumValue.of(-1, '拒绝').setTagType('danger'),
 | 
				
			||||||
 | 
					    Back: EnumValue.of(-2, '驳回').setTagType('warning'),
 | 
				
			||||||
 | 
					    Canceled: EnumValue.of(-3, '取消').setTagType('warning'),
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const FlowBizType = {
 | 
				
			||||||
 | 
					    DbSqlExec: EnumValue.of('db_sql_exec_flow', 'DBMS-执行SQL').setTagType('warning'),
 | 
				
			||||||
 | 
					    RedisRunWriteCmd: EnumValue.of('redis_run_cmd_flow', 'Redis-执行命令').setTagType('danger'),
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
							
								
								
									
										103
									
								
								mayfly_go_web/src/views/flow/flowbiz/dbms/DbSqlExecBiz.vue
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										103
									
								
								mayfly_go_web/src/views/flow/flowbiz/dbms/DbSqlExecBiz.vue
									
									
									
									
									
										Executable file
									
								
							@@ -0,0 +1,103 @@
 | 
				
			|||||||
 | 
					<template>
 | 
				
			||||||
 | 
					    <div>
 | 
				
			||||||
 | 
					        <el-descriptions :column="3" border>
 | 
				
			||||||
 | 
					            <el-descriptions-item :span="3" label="标签"><TagCodePath :path="db.codePaths" /></el-descriptions-item>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            <el-descriptions-item :span="1" label="名称">{{ db?.name }}</el-descriptions-item>
 | 
				
			||||||
 | 
					            <el-descriptions-item :span="1" label="主机">
 | 
				
			||||||
 | 
					                <SvgIcon :name="getDbDialect(db?.type).getInfo().icon" :size="20" />
 | 
				
			||||||
 | 
					                {{ `${db?.host}:${db?.port}` }}
 | 
				
			||||||
 | 
					            </el-descriptions-item>
 | 
				
			||||||
 | 
					            <el-descriptions-item :span="1" label="数据库">{{ dbName }}</el-descriptions-item>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            <el-descriptions-item label="执行SQL">
 | 
				
			||||||
 | 
					                <monaco-editor height="300px" language="sql" v-model="sql" :options="{ readOnly: true }" />
 | 
				
			||||||
 | 
					            </el-descriptions-item>
 | 
				
			||||||
 | 
					        </el-descriptions>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        <div v-if="runRes && runRes.length > 0">
 | 
				
			||||||
 | 
					            <el-divider content-position="left">处理结果</el-divider>
 | 
				
			||||||
 | 
					            <el-table :data="runRes" :max-height="400">
 | 
				
			||||||
 | 
					                <el-table-column prop="sql" label="SQL" show-overflow-tooltip />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                <el-table-column prop="res" label="执行结果" :min-width="30" show-overflow-tooltip>
 | 
				
			||||||
 | 
					                    <template #default="scope">
 | 
				
			||||||
 | 
					                        <el-popover placement="top" :width="400" trigger="hover">
 | 
				
			||||||
 | 
					                            <template #reference>
 | 
				
			||||||
 | 
					                                <el-link icon="view" :type="scope.row.errorMsg ? 'danger' : 'success'" :underline="false"> </el-link>
 | 
				
			||||||
 | 
					                            </template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                            <el-text v-if="scope.row.errorMsg">{{ scope.row.errorMsg }}</el-text>
 | 
				
			||||||
 | 
					                            <el-table v-else :data="scope.row.res" size="small">
 | 
				
			||||||
 | 
					                                <el-table-column v-for="col in scope.row.columns" :key="col.name" :label="col.name" :prop="col.name" />
 | 
				
			||||||
 | 
					                            </el-table>
 | 
				
			||||||
 | 
					                        </el-popover>
 | 
				
			||||||
 | 
					                    </template>
 | 
				
			||||||
 | 
					                </el-table-column>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                <!-- <el-table-column prop="errorMsg" label="错误信息" :min-width="60" show-overflow-tooltip /> -->
 | 
				
			||||||
 | 
					            </el-table>
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<script lang="ts" setup>
 | 
				
			||||||
 | 
					import { toRefs, reactive, watch, onMounted } from 'vue';
 | 
				
			||||||
 | 
					import { dbApi } from '@/views/ops/db/api';
 | 
				
			||||||
 | 
					import MonacoEditor from '@/components/monaco/MonacoEditor.vue';
 | 
				
			||||||
 | 
					import { getDbDialect } from '@/views/ops/db/dialect';
 | 
				
			||||||
 | 
					import { tagApi } from '@/views/ops/tag/api';
 | 
				
			||||||
 | 
					import { TagResourceTypeEnum } from '@/common/commonEnum';
 | 
				
			||||||
 | 
					import TagCodePath from '@/views/ops/component/TagCodePath.vue';
 | 
				
			||||||
 | 
					import SvgIcon from '@/components/svgIcon/index.vue';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const props = defineProps({
 | 
				
			||||||
 | 
					    procinst: {
 | 
				
			||||||
 | 
					        type: [Object],
 | 
				
			||||||
 | 
					        default: () => {},
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const state = reactive({
 | 
				
			||||||
 | 
					    // sqlExec: {
 | 
				
			||||||
 | 
					    //     sql: '',
 | 
				
			||||||
 | 
					    // } as any,
 | 
				
			||||||
 | 
					    db: {} as any,
 | 
				
			||||||
 | 
					    dbName: '',
 | 
				
			||||||
 | 
					    sql: '',
 | 
				
			||||||
 | 
					    runRes: [],
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const { db, dbName, sql, runRes } = toRefs(state);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					onMounted(() => {
 | 
				
			||||||
 | 
					    parseBizForm(props.procinst.bizForm);
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					watch(
 | 
				
			||||||
 | 
					    () => props.procinst.bizForm,
 | 
				
			||||||
 | 
					    (newValue: any) => {
 | 
				
			||||||
 | 
					        parseBizForm(newValue);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const parseBizForm = async (bizFormStr: string) => {
 | 
				
			||||||
 | 
					    if (props.procinst.bizHandleRes) {
 | 
				
			||||||
 | 
					        state.runRes = JSON.parse(props.procinst.bizHandleRes);
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					        state.runRes = [];
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const bizForm = JSON.parse(bizFormStr);
 | 
				
			||||||
 | 
					    state.sql = bizForm.sql;
 | 
				
			||||||
 | 
					    state.dbName = bizForm.dbName;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const dbRes = await dbApi.dbs.request({ id: bizForm.dbId });
 | 
				
			||||||
 | 
					    state.db = dbRes.list?.[0];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    tagApi.listByQuery.request({ type: TagResourceTypeEnum.DbName.value, codes: state.db.code }).then((res) => {
 | 
				
			||||||
 | 
					        state.db.codePaths = res.map((item: any) => item.codePath);
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					</script>
 | 
				
			||||||
 | 
					<style lang="scss"></style>
 | 
				
			||||||
							
								
								
									
										83
									
								
								mayfly_go_web/src/views/flow/flowbiz/dbms/DbSqlExecFlowBizForm.vue
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										83
									
								
								mayfly_go_web/src/views/flow/flowbiz/dbms/DbSqlExecFlowBizForm.vue
									
									
									
									
									
										Executable file
									
								
							@@ -0,0 +1,83 @@
 | 
				
			|||||||
 | 
					<template>
 | 
				
			||||||
 | 
					    <el-form :model="bizForm" ref="formRef" :rules="rules" label-width="auto">
 | 
				
			||||||
 | 
					        <el-form-item prop="dbId" label="数据库" required>
 | 
				
			||||||
 | 
					            <db-select-tree
 | 
				
			||||||
 | 
					                placeholder="请选择数据库"
 | 
				
			||||||
 | 
					                v-model:db-id="bizForm.dbId"
 | 
				
			||||||
 | 
					                v-model:db-name="bizForm.dbName"
 | 
				
			||||||
 | 
					                v-model:db-type="dbType"
 | 
				
			||||||
 | 
					                @select-db="changeResourceCode"
 | 
				
			||||||
 | 
					            />
 | 
				
			||||||
 | 
					        </el-form-item>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        <el-form-item prop="sql" label="SQL" required>
 | 
				
			||||||
 | 
					            <div class="w100">
 | 
				
			||||||
 | 
					                <monaco-editor height="300px" language="sql" v-model="bizForm.sql" />
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					        </el-form-item>
 | 
				
			||||||
 | 
					    </el-form>
 | 
				
			||||||
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<script lang="ts" setup>
 | 
				
			||||||
 | 
					import { ref, watch } from 'vue';
 | 
				
			||||||
 | 
					import DbSelectTree from '@/views/ops/db/component/DbSelectTree.vue';
 | 
				
			||||||
 | 
					import MonacoEditor from '@/components/monaco/MonacoEditor.vue';
 | 
				
			||||||
 | 
					import { registerDbCompletionItemProvider } from '@/views/ops/db/db';
 | 
				
			||||||
 | 
					import { TagResourceTypeEnum } from '@/common/commonEnum';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const rules = {
 | 
				
			||||||
 | 
					    dbId: [
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            required: true,
 | 
				
			||||||
 | 
					            message: '请选择数据库',
 | 
				
			||||||
 | 
					            trigger: ['change', 'blur'],
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					    ],
 | 
				
			||||||
 | 
					    sql: [
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            required: true,
 | 
				
			||||||
 | 
					            message: '请输入执行SQL',
 | 
				
			||||||
 | 
					            trigger: ['change', 'blur'],
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					    ],
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const emit = defineEmits(['changeResourceCode']);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const formRef: any = ref(null);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const bizForm = defineModel<any>('bizForm', {
 | 
				
			||||||
 | 
					    default: {
 | 
				
			||||||
 | 
					        dbId: 0,
 | 
				
			||||||
 | 
					        dbName: '',
 | 
				
			||||||
 | 
					        sql: '',
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const dbType = ref('');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					watch(
 | 
				
			||||||
 | 
					    () => bizForm.value.dbId,
 | 
				
			||||||
 | 
					    () => {
 | 
				
			||||||
 | 
					        registerDbCompletionItemProvider(bizForm.value.dbId, bizForm.value.dbName, [bizForm.value.dbName], dbType.value);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const changeResourceCode = async (db: any) => {
 | 
				
			||||||
 | 
					    emit('changeResourceCode', TagResourceTypeEnum.Db.value, db.code);
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const validateBizForm = async () => {
 | 
				
			||||||
 | 
					    return formRef.value.validate();
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const resetBizForm = () => {
 | 
				
			||||||
 | 
					    //重置表单域
 | 
				
			||||||
 | 
					    formRef.value.resetFields();
 | 
				
			||||||
 | 
					    bizForm.value.dbId = 0;
 | 
				
			||||||
 | 
					    bizForm.value.dbName = '';
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					defineExpose({ validateBizForm, resetBizForm });
 | 
				
			||||||
 | 
					</script>
 | 
				
			||||||
 | 
					<style lang="scss"></style>
 | 
				
			||||||
							
								
								
									
										89
									
								
								mayfly_go_web/src/views/flow/flowbiz/redis/RedisRunCmdBiz.vue
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										89
									
								
								mayfly_go_web/src/views/flow/flowbiz/redis/RedisRunCmdBiz.vue
									
									
									
									
									
										Executable file
									
								
							@@ -0,0 +1,89 @@
 | 
				
			|||||||
 | 
					<template>
 | 
				
			||||||
 | 
					    <div>
 | 
				
			||||||
 | 
					        <el-descriptions :column="3" border>
 | 
				
			||||||
 | 
					            <el-descriptions-item :span="3" label="标签"><TagCodePath :path="redis.codePaths" /></el-descriptions-item>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            <el-descriptions-item :span="2" label="编号">{{ redis?.code }}</el-descriptions-item>
 | 
				
			||||||
 | 
					            <el-descriptions-item :span="1" label="名称">{{ redis?.name }}</el-descriptions-item>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            <el-descriptions-item :span="1" label="主机">{{ `${redis?.host}` }}</el-descriptions-item>
 | 
				
			||||||
 | 
					            <el-descriptions-item :span="1" label="库">{{ state.db }}</el-descriptions-item>
 | 
				
			||||||
 | 
					            <el-descriptions-item :span="1" label="mode">
 | 
				
			||||||
 | 
					                {{ redis.mode }}
 | 
				
			||||||
 | 
					            </el-descriptions-item>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            <el-descriptions-item :span="3" label="执行Cmd">
 | 
				
			||||||
 | 
					                <el-input type="textarea" disabled v-model="cmd" rows="5" />
 | 
				
			||||||
 | 
					            </el-descriptions-item>
 | 
				
			||||||
 | 
					        </el-descriptions>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        <div v-if="runRes && runRes.length > 0">
 | 
				
			||||||
 | 
					            <el-divider content-position="left">处理结果</el-divider>
 | 
				
			||||||
 | 
					            <el-table :data="runRes" :max-height="400">
 | 
				
			||||||
 | 
					                <el-table-column prop="cmd" label="命令" show-overflow-tooltip />
 | 
				
			||||||
 | 
					                <el-table-column prop="res" label="执行结果" :min-width="50" show-overflow-tooltip> </el-table-column>
 | 
				
			||||||
 | 
					            </el-table>
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<script lang="ts" setup>
 | 
				
			||||||
 | 
					import { toRefs, reactive, watch, onMounted } from 'vue';
 | 
				
			||||||
 | 
					import { redisApi } from '@/views/ops/redis/api';
 | 
				
			||||||
 | 
					import TagCodePath from '@/views/ops/component/TagCodePath.vue';
 | 
				
			||||||
 | 
					import { tagApi } from '@/views/ops/tag/api';
 | 
				
			||||||
 | 
					import { TagResourceTypeEnum } from '@/common/commonEnum';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const props = defineProps({
 | 
				
			||||||
 | 
					    procinst: {
 | 
				
			||||||
 | 
					        type: [Object],
 | 
				
			||||||
 | 
					        default: () => {},
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const state = reactive({
 | 
				
			||||||
 | 
					    cmd: '',
 | 
				
			||||||
 | 
					    runRes: [],
 | 
				
			||||||
 | 
					    db: 0,
 | 
				
			||||||
 | 
					    redis: {} as any,
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const { cmd, redis, runRes } = toRefs(state);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					onMounted(() => {
 | 
				
			||||||
 | 
					    parseRunCmdForm(props.procinst.bizForm);
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					watch(
 | 
				
			||||||
 | 
					    () => props.procinst.bizForm,
 | 
				
			||||||
 | 
					    (newValue: any) => {
 | 
				
			||||||
 | 
					        parseRunCmdForm(newValue);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const parseRunCmdForm = async (bizFormStr: string) => {
 | 
				
			||||||
 | 
					    if (props.procinst.bizHandleRes) {
 | 
				
			||||||
 | 
					        state.runRes = JSON.parse(props.procinst.bizHandleRes);
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					        state.runRes = [];
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (!bizFormStr) {
 | 
				
			||||||
 | 
					        return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    const bizForm = JSON.parse(bizFormStr);
 | 
				
			||||||
 | 
					    state.cmd = bizForm.cmd;
 | 
				
			||||||
 | 
					    state.db = bizForm.db;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const res = await redisApi.redisList.request({ id: bizForm.id });
 | 
				
			||||||
 | 
					    if (!res.list) {
 | 
				
			||||||
 | 
					        return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    state.redis = res.list?.[0];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    tagApi.listByQuery.request({ type: TagResourceTypeEnum.Redis.value, codes: state.redis.code }).then((res) => {
 | 
				
			||||||
 | 
					        state.redis.codePaths = res.map((item: any) => item.codePath);
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					</script>
 | 
				
			||||||
 | 
					<style lang="scss"></style>
 | 
				
			||||||
							
								
								
									
										148
									
								
								mayfly_go_web/src/views/flow/flowbiz/redis/RedisRunCmdFlowBizForm.vue
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										148
									
								
								mayfly_go_web/src/views/flow/flowbiz/redis/RedisRunCmdFlowBizForm.vue
									
									
									
									
									
										Executable file
									
								
							@@ -0,0 +1,148 @@
 | 
				
			|||||||
 | 
					<template>
 | 
				
			||||||
 | 
					    <el-form :model="bizForm" ref="formRef" :rules="rules" label-width="auto">
 | 
				
			||||||
 | 
					        <el-form-item prop="id" label="库" required>
 | 
				
			||||||
 | 
					            <TagTreeResourceSelect
 | 
				
			||||||
 | 
					                v-bind="$attrs"
 | 
				
			||||||
 | 
					                v-model="selectRedis"
 | 
				
			||||||
 | 
					                @change="changeRedis"
 | 
				
			||||||
 | 
					                :resource-type="TagResourceTypeEnum.Redis.value"
 | 
				
			||||||
 | 
					                :tag-path-node-type="NodeTypeTagPath"
 | 
				
			||||||
 | 
					                placeholder="请选择Redis实例与库"
 | 
				
			||||||
 | 
					            >
 | 
				
			||||||
 | 
					            </TagTreeResourceSelect>
 | 
				
			||||||
 | 
					        </el-form-item>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        <el-form-item prop="cmd" label="CMD" required>
 | 
				
			||||||
 | 
					            <el-input type="textarea" v-model="bizForm.cmd" placeholder="如: SET 'key' 'value'; 多条命令;分割" :rows="5" />
 | 
				
			||||||
 | 
					        </el-form-item>
 | 
				
			||||||
 | 
					    </el-form>
 | 
				
			||||||
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<script lang="ts" setup>
 | 
				
			||||||
 | 
					import { computed, ref } from 'vue';
 | 
				
			||||||
 | 
					import { TagResourceTypeEnum } from '@/common/commonEnum';
 | 
				
			||||||
 | 
					import TagTreeResourceSelect from '@/views/ops/component/TagTreeResourceSelect.vue';
 | 
				
			||||||
 | 
					import { NodeType, TagTreeNode } from '@/views/ops/component/tag';
 | 
				
			||||||
 | 
					import { redisApi } from '@/views/ops/redis/api';
 | 
				
			||||||
 | 
					import { sleep } from '@/common/utils/loading';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const rules = {
 | 
				
			||||||
 | 
					    id: [
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            required: true,
 | 
				
			||||||
 | 
					            message: '请选择Redis实例',
 | 
				
			||||||
 | 
					            trigger: ['change', 'blur'],
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					    ],
 | 
				
			||||||
 | 
					    cmd: [
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            required: true,
 | 
				
			||||||
 | 
					            message: '请输入执行CMD',
 | 
				
			||||||
 | 
					            trigger: ['change', 'blur'],
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					    ],
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// tagpath 节点类型
 | 
				
			||||||
 | 
					const NodeTypeTagPath = new NodeType(TagTreeNode.TagPath).withLoadNodesFunc(async (parentNode: TagTreeNode) => {
 | 
				
			||||||
 | 
					    const res = await redisApi.redisList.request({ tagPath: parentNode.key });
 | 
				
			||||||
 | 
					    if (!res.total) {
 | 
				
			||||||
 | 
					        return [];
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const redisInfos = res.list;
 | 
				
			||||||
 | 
					    await sleep(100);
 | 
				
			||||||
 | 
					    return redisInfos.map((x: any) => {
 | 
				
			||||||
 | 
					        x.tagPath = parentNode.key;
 | 
				
			||||||
 | 
					        return new TagTreeNode(`${x.code}`, x.name, NodeTypeRedis).withParams(x);
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// redis实例节点类型
 | 
				
			||||||
 | 
					const NodeTypeRedis = new NodeType(1).withLoadNodesFunc(async (parentNode: TagTreeNode) => {
 | 
				
			||||||
 | 
					    const redisInfo = parentNode.params;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let dbs: TagTreeNode[] = redisInfo.db.split(',').map((x: string) => {
 | 
				
			||||||
 | 
					        return new TagTreeNode(x, `db${x}`, 2 as any).withIsLeaf(true).withParams({
 | 
				
			||||||
 | 
					            id: redisInfo.id,
 | 
				
			||||||
 | 
					            db: x,
 | 
				
			||||||
 | 
					            name: `db${x}`,
 | 
				
			||||||
 | 
					            keys: 0,
 | 
				
			||||||
 | 
					            tagPath: redisInfo.tagPath,
 | 
				
			||||||
 | 
					            redisName: redisInfo.name,
 | 
				
			||||||
 | 
					            code: redisInfo.code,
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (redisInfo.mode == 'cluster') {
 | 
				
			||||||
 | 
					        return dbs;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const res = await redisApi.redisInfo.request({ id: redisInfo.id, host: redisInfo.host, section: 'Keyspace' });
 | 
				
			||||||
 | 
					    for (let db in res.Keyspace) {
 | 
				
			||||||
 | 
					        for (let d of dbs) {
 | 
				
			||||||
 | 
					            if (db == d.params.name) {
 | 
				
			||||||
 | 
					                d.params.keys = res.Keyspace[db]?.split(',')[0]?.split('=')[1] || 0;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    // 替换label
 | 
				
			||||||
 | 
					    dbs.forEach((e: any) => {
 | 
				
			||||||
 | 
					        e.label = `${e.params.name}`;
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    return dbs;
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const emit = defineEmits(['changeResourceCode']);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const formRef: any = ref(null);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const bizForm = defineModel<any>('bizForm', {
 | 
				
			||||||
 | 
					    default: {
 | 
				
			||||||
 | 
					        id: 0,
 | 
				
			||||||
 | 
					        db: 0,
 | 
				
			||||||
 | 
					        cmd: '',
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const redisName = ref('');
 | 
				
			||||||
 | 
					const tagPath = ref('');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const selectRedis = computed({
 | 
				
			||||||
 | 
					    get: () => {
 | 
				
			||||||
 | 
					        return redisName.value ? `${tagPath.value} > ${redisName.value} > db${bizForm.value.db}` : '';
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    set: () => {
 | 
				
			||||||
 | 
					        //
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const changeRedis = (nodeData: TagTreeNode) => {
 | 
				
			||||||
 | 
					    const params = nodeData.params;
 | 
				
			||||||
 | 
					    tagPath.value = params.tagPath;
 | 
				
			||||||
 | 
					    redisName.value = params.redisName;
 | 
				
			||||||
 | 
					    bizForm.value.id = params.id;
 | 
				
			||||||
 | 
					    bizForm.value.db = parseInt(params.db);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    changeResourceCode(params.code);
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const changeResourceCode = async (redisCode: any) => {
 | 
				
			||||||
 | 
					    emit('changeResourceCode', TagResourceTypeEnum.Redis.value, redisCode);
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const validateBizForm = async () => {
 | 
				
			||||||
 | 
					    return formRef.value.validate();
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const resetBizForm = () => {
 | 
				
			||||||
 | 
					    //重置表单域
 | 
				
			||||||
 | 
					    formRef.value.resetFields();
 | 
				
			||||||
 | 
					    bizForm.value.id = 0;
 | 
				
			||||||
 | 
					    bizForm.value.db = 0;
 | 
				
			||||||
 | 
					    bizForm.value.cmd = '';
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					defineExpose({ validateBizForm, resetBizForm });
 | 
				
			||||||
 | 
					</script>
 | 
				
			||||||
 | 
					<style lang="scss"></style>
 | 
				
			||||||
@@ -1,121 +1,587 @@
 | 
				
			|||||||
<template>
 | 
					<template>
 | 
				
			||||||
    <div class="home-container">
 | 
					    <div class="home-container personal">
 | 
				
			||||||
        <el-row :gutter="15">
 | 
					        <el-row :gutter="15">
 | 
				
			||||||
            <el-col :sm="6" class="mb15">
 | 
					            <!-- 个人信息 -->
 | 
				
			||||||
                <div @click="toPage({ id: 'personal' })" class="home-card-item home-card-first">
 | 
					            <el-col :xs="24" :sm="16">
 | 
				
			||||||
                    <div class="flex-margin flex">
 | 
					                <el-card shadow="hover" header="个人信息">
 | 
				
			||||||
                        <img :src="userInfo.photo" />
 | 
					                    <div class="personal-user">
 | 
				
			||||||
                        <div class="home-card-first-right ml15">
 | 
					                        <div class="personal-user-left">
 | 
				
			||||||
                            <div class="flex-margin">
 | 
					                            <el-upload
 | 
				
			||||||
                                <div class="home-card-first-right-title">{{ `${currentTime}, ${userInfo.username}` }}</div>
 | 
					                                class="h100 personal-user-left-upload"
 | 
				
			||||||
                            </div>
 | 
					                                :action="getUploadFileUrl(`avatar_${userInfo.username}`)"
 | 
				
			||||||
 | 
					                                :limit="1"
 | 
				
			||||||
 | 
					                                :show-file-list="false"
 | 
				
			||||||
 | 
					                                :before-upload="beforeAvatarUpload"
 | 
				
			||||||
 | 
					                                :on-success="handleAvatarSuccess"
 | 
				
			||||||
 | 
					                                accept=".png,.jpg,.jpeg"
 | 
				
			||||||
 | 
					                            >
 | 
				
			||||||
 | 
					                                <img :src="userInfo.photo" />
 | 
				
			||||||
 | 
					                            </el-upload>
 | 
				
			||||||
 | 
					                        </div>
 | 
				
			||||||
 | 
					                        <div class="personal-user-right">
 | 
				
			||||||
 | 
					                            <el-row>
 | 
				
			||||||
 | 
					                                <el-col :span="24" class="personal-title mb18"
 | 
				
			||||||
 | 
					                                    >{{ currentTime }},{{ userInfo.name }},生活变的再糟糕,也不妨碍我变得更好!
 | 
				
			||||||
 | 
					                                </el-col>
 | 
				
			||||||
 | 
					                                <el-col :span="24">
 | 
				
			||||||
 | 
					                                    <el-row>
 | 
				
			||||||
 | 
					                                        <el-col :xs="24" :sm="12" class="personal-item mb6">
 | 
				
			||||||
 | 
					                                            <div class="personal-item-label">用户名:</div>
 | 
				
			||||||
 | 
					                                            <div class="personal-item-value">{{ userInfo.username }}</div>
 | 
				
			||||||
 | 
					                                        </el-col>
 | 
				
			||||||
 | 
					                                        <el-col :xs="24" :sm="12" class="personal-item mb6">
 | 
				
			||||||
 | 
					                                            <div class="personal-item-label">角色:</div>
 | 
				
			||||||
 | 
					                                            <div class="personal-item-value">{{ roleInfo }}</div>
 | 
				
			||||||
 | 
					                                        </el-col>
 | 
				
			||||||
 | 
					                                    </el-row>
 | 
				
			||||||
 | 
					                                </el-col>
 | 
				
			||||||
 | 
					                                <el-col :span="24">
 | 
				
			||||||
 | 
					                                    <el-row>
 | 
				
			||||||
 | 
					                                        <el-col :xs="24" :sm="12" class="personal-item mb6">
 | 
				
			||||||
 | 
					                                            <div class="personal-item-label">上次登录IP:</div>
 | 
				
			||||||
 | 
					                                            <div class="personal-item-value">{{ userInfo.lastLoginIp }}</div>
 | 
				
			||||||
 | 
					                                        </el-col>
 | 
				
			||||||
 | 
					                                        <el-col :xs="24" :sm="12" class="personal-item mb6">
 | 
				
			||||||
 | 
					                                            <div class="personal-item-label">上次登录时间:</div>
 | 
				
			||||||
 | 
					                                            <div class="personal-item-value">{{ formatDate(userInfo.lastLoginTime) }}</div>
 | 
				
			||||||
 | 
					                                        </el-col>
 | 
				
			||||||
 | 
					                                    </el-row>
 | 
				
			||||||
 | 
					                                </el-col>
 | 
				
			||||||
 | 
					                            </el-row>
 | 
				
			||||||
                        </div>
 | 
					                        </div>
 | 
				
			||||||
                    </div>
 | 
					                    </div>
 | 
				
			||||||
                </div>
 | 
					                </el-card>
 | 
				
			||||||
            </el-col>
 | 
					            </el-col>
 | 
				
			||||||
            <el-col :sm="3" class="mb15" v-for="(v, k) in topCardItemList as any" :key="k">
 | 
					
 | 
				
			||||||
                <div @click="toPage(v)" class="home-card-item home-card-item-box" :style="{ background: v.color }">
 | 
					            <!-- 消息通知 -->
 | 
				
			||||||
                    <div class="home-card-item-flex">
 | 
					            <el-col :xs="24" :sm="8" class="pl15 personal-info">
 | 
				
			||||||
                        <div class="home-card-item-title pb3">{{ v.title }}</div>
 | 
					                <el-card shadow="hover">
 | 
				
			||||||
                        <div class="home-card-item-title-num pb6" :id="v.id"></div>
 | 
					                    <template #header>
 | 
				
			||||||
 | 
					                        <span>消息通知</span>
 | 
				
			||||||
 | 
					                        <span @click="showMsgs" class="personal-info-more">更多</span>
 | 
				
			||||||
 | 
					                    </template>
 | 
				
			||||||
 | 
					                    <div class="personal-info-box">
 | 
				
			||||||
 | 
					                        <ul class="personal-info-ul">
 | 
				
			||||||
 | 
					                            <li v-for="(v, k) in state.msgs as any" :key="k" class="personal-info-li">
 | 
				
			||||||
 | 
					                                <a class="personal-info-li-title">{{ `[${getMsgTypeDesc(v.type)}] ${v.msg}` }}</a>
 | 
				
			||||||
 | 
					                            </li>
 | 
				
			||||||
 | 
					                        </ul>
 | 
				
			||||||
                    </div>
 | 
					                    </div>
 | 
				
			||||||
                    <i :class="v.icon" :style="{ color: v.iconColor }"></i>
 | 
					                </el-card>
 | 
				
			||||||
                </div>
 | 
					 | 
				
			||||||
            </el-col>
 | 
					            </el-col>
 | 
				
			||||||
        </el-row>
 | 
					        </el-row>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        <el-row :gutter="20" class="mt20 resource-info">
 | 
				
			||||||
 | 
					            <el-col :sm="12">
 | 
				
			||||||
 | 
					                <el-card shadow="hover">
 | 
				
			||||||
 | 
					                    <template #header>
 | 
				
			||||||
 | 
					                        <el-row justify="center">
 | 
				
			||||||
 | 
					                            <div class="resource-num pointer-icon" @click="toPage('machine')">
 | 
				
			||||||
 | 
					                                <SvgIcon
 | 
				
			||||||
 | 
					                                    class="mb5 mr5"
 | 
				
			||||||
 | 
					                                    :size="28"
 | 
				
			||||||
 | 
					                                    :name="TagResourceTypeEnum.Machine.extra.icon"
 | 
				
			||||||
 | 
					                                    :color="TagResourceTypeEnum.Machine.extra.iconColor"
 | 
				
			||||||
 | 
					                                />
 | 
				
			||||||
 | 
					                                <span class="">{{ state.machine.num }}</span>
 | 
				
			||||||
 | 
					                            </div>
 | 
				
			||||||
 | 
					                        </el-row>
 | 
				
			||||||
 | 
					                    </template>
 | 
				
			||||||
 | 
					                    <el-row>
 | 
				
			||||||
 | 
					                        <el-col :sm="24">
 | 
				
			||||||
 | 
					                            <el-table :data="state.machine.opLogs" :height="state.resourceOpTableHeight" stripe size="small" empty-text="暂无操作记录">
 | 
				
			||||||
 | 
					                                <el-table-column prop="createTime" show-overflow-tooltip width="135">
 | 
				
			||||||
 | 
					                                    <template #default="scope">
 | 
				
			||||||
 | 
					                                        {{ formatDate(scope.row.createTime) }}
 | 
				
			||||||
 | 
					                                    </template>
 | 
				
			||||||
 | 
					                                </el-table-column>
 | 
				
			||||||
 | 
					                                <el-table-column prop="codePath" min-width="400" show-overflow-tooltip>
 | 
				
			||||||
 | 
					                                    <template #default="scope">
 | 
				
			||||||
 | 
					                                        <TagCodePath :path="scope.row.codePath" :tagInfos="state.machine.tagInfos" />
 | 
				
			||||||
 | 
					                                    </template>
 | 
				
			||||||
 | 
					                                </el-table-column>
 | 
				
			||||||
 | 
					                                <el-table-column width="30">
 | 
				
			||||||
 | 
					                                    <template #default="scope">
 | 
				
			||||||
 | 
					                                        <el-link @click="toPage('machine', scope.row.codePath)" type="primary" icon="Position"></el-link>
 | 
				
			||||||
 | 
					                                    </template>
 | 
				
			||||||
 | 
					                                </el-table-column>
 | 
				
			||||||
 | 
					                            </el-table>
 | 
				
			||||||
 | 
					                        </el-col>
 | 
				
			||||||
 | 
					                    </el-row>
 | 
				
			||||||
 | 
					                </el-card>
 | 
				
			||||||
 | 
					            </el-col>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            <el-col :sm="12">
 | 
				
			||||||
 | 
					                <el-card shadow="hover">
 | 
				
			||||||
 | 
					                    <template #header>
 | 
				
			||||||
 | 
					                        <el-row justify="center">
 | 
				
			||||||
 | 
					                            <div class="resource-num pointer-icon" @click="toPage('db')">
 | 
				
			||||||
 | 
					                                <SvgIcon class="mb5 mr5" :size="28" :name="TagResourceTypeEnum.Db.extra.icon" :color="TagResourceTypeEnum.Db.extra.iconColor" />
 | 
				
			||||||
 | 
					                                <span class="">{{ state.db.num }}</span>
 | 
				
			||||||
 | 
					                            </div>
 | 
				
			||||||
 | 
					                        </el-row>
 | 
				
			||||||
 | 
					                    </template>
 | 
				
			||||||
 | 
					                    <el-row>
 | 
				
			||||||
 | 
					                        <el-col :sm="24">
 | 
				
			||||||
 | 
					                            <el-table :data="state.db.opLogs" :height="state.resourceOpTableHeight" stripe size="small" empty-text="暂无操作记录">
 | 
				
			||||||
 | 
					                                <el-table-column prop="createTime" show-overflow-tooltip min-width="135">
 | 
				
			||||||
 | 
					                                    <template #default="scope">
 | 
				
			||||||
 | 
					                                        {{ formatDate(scope.row.createTime) }}
 | 
				
			||||||
 | 
					                                    </template>
 | 
				
			||||||
 | 
					                                </el-table-column>
 | 
				
			||||||
 | 
					                                <el-table-column prop="codePath" min-width="380" show-overflow-tooltip>
 | 
				
			||||||
 | 
					                                    <template #default="scope">
 | 
				
			||||||
 | 
					                                        <TagCodePath :path="scope.row.codePath" :tagInfos="state.db.tagInfos" />
 | 
				
			||||||
 | 
					                                    </template>
 | 
				
			||||||
 | 
					                                </el-table-column>
 | 
				
			||||||
 | 
					                                <el-table-column width="30">
 | 
				
			||||||
 | 
					                                    <template #default="scope">
 | 
				
			||||||
 | 
					                                        <el-link @click="toPage('db', scope.row.codePath)" type="primary" icon="Position"></el-link>
 | 
				
			||||||
 | 
					                                    </template>
 | 
				
			||||||
 | 
					                                </el-table-column>
 | 
				
			||||||
 | 
					                            </el-table>
 | 
				
			||||||
 | 
					                        </el-col>
 | 
				
			||||||
 | 
					                    </el-row>
 | 
				
			||||||
 | 
					                </el-card>
 | 
				
			||||||
 | 
					            </el-col>
 | 
				
			||||||
 | 
					        </el-row>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        <el-row :gutter="20" class="mt20 resource-info">
 | 
				
			||||||
 | 
					            <el-col :sm="12">
 | 
				
			||||||
 | 
					                <el-card shadow="hover">
 | 
				
			||||||
 | 
					                    <template #header>
 | 
				
			||||||
 | 
					                        <el-row justify="center">
 | 
				
			||||||
 | 
					                            <div class="resource-num pointer-icon" @click="toPage('redis')">
 | 
				
			||||||
 | 
					                                <SvgIcon
 | 
				
			||||||
 | 
					                                    class="mb5 mr5"
 | 
				
			||||||
 | 
					                                    :size="28"
 | 
				
			||||||
 | 
					                                    :name="TagResourceTypeEnum.Redis.extra.icon"
 | 
				
			||||||
 | 
					                                    :color="TagResourceTypeEnum.Redis.extra.iconColor"
 | 
				
			||||||
 | 
					                                />
 | 
				
			||||||
 | 
					                                <span class="">{{ state.redis.num }}</span>
 | 
				
			||||||
 | 
					                            </div>
 | 
				
			||||||
 | 
					                        </el-row>
 | 
				
			||||||
 | 
					                    </template>
 | 
				
			||||||
 | 
					                    <el-row>
 | 
				
			||||||
 | 
					                        <el-col :sm="24">
 | 
				
			||||||
 | 
					                            <el-table :data="state.redis.opLogs" :height="state.resourceOpTableHeight" stripe size="small" empty-text="暂无操作记录">
 | 
				
			||||||
 | 
					                                <el-table-column prop="createTime" show-overflow-tooltip min-width="135">
 | 
				
			||||||
 | 
					                                    <template #default="scope">
 | 
				
			||||||
 | 
					                                        {{ formatDate(scope.row.createTime) }}
 | 
				
			||||||
 | 
					                                    </template>
 | 
				
			||||||
 | 
					                                </el-table-column>
 | 
				
			||||||
 | 
					                                <el-table-column prop="codePath" min-width="380" show-overflow-tooltip>
 | 
				
			||||||
 | 
					                                    <template #default="scope">
 | 
				
			||||||
 | 
					                                        <TagCodePath :path="scope.row.codePath" :tagInfos="state.redis.tagInfos" />
 | 
				
			||||||
 | 
					                                    </template>
 | 
				
			||||||
 | 
					                                </el-table-column>
 | 
				
			||||||
 | 
					                                <el-table-column width="30">
 | 
				
			||||||
 | 
					                                    <template #default="scope">
 | 
				
			||||||
 | 
					                                        <el-link @click="toPage('redis', scope.row.codePath)" type="primary" icon="Position"></el-link>
 | 
				
			||||||
 | 
					                                    </template>
 | 
				
			||||||
 | 
					                                </el-table-column>
 | 
				
			||||||
 | 
					                            </el-table>
 | 
				
			||||||
 | 
					                        </el-col>
 | 
				
			||||||
 | 
					                    </el-row>
 | 
				
			||||||
 | 
					                </el-card>
 | 
				
			||||||
 | 
					            </el-col>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            <el-col :sm="12">
 | 
				
			||||||
 | 
					                <el-card shadow="hover">
 | 
				
			||||||
 | 
					                    <template #header>
 | 
				
			||||||
 | 
					                        <el-row justify="center">
 | 
				
			||||||
 | 
					                            <div class="resource-num pointer-icon" @click="toPage('mongo')">
 | 
				
			||||||
 | 
					                                <SvgIcon
 | 
				
			||||||
 | 
					                                    class="mb5 mr5"
 | 
				
			||||||
 | 
					                                    :size="28"
 | 
				
			||||||
 | 
					                                    :name="TagResourceTypeEnum.Mongo.extra.icon"
 | 
				
			||||||
 | 
					                                    :color="TagResourceTypeEnum.Mongo.extra.iconColor"
 | 
				
			||||||
 | 
					                                />
 | 
				
			||||||
 | 
					                                <span class="">{{ state.mongo.num }}</span>
 | 
				
			||||||
 | 
					                            </div>
 | 
				
			||||||
 | 
					                        </el-row>
 | 
				
			||||||
 | 
					                    </template>
 | 
				
			||||||
 | 
					                    <el-row>
 | 
				
			||||||
 | 
					                        <el-col :sm="24">
 | 
				
			||||||
 | 
					                            <el-table :data="state.mongo.opLogs" :height="state.resourceOpTableHeight" stripe size="small" empty-text="暂无操作记录">
 | 
				
			||||||
 | 
					                                <el-table-column prop="createTime" show-overflow-tooltip min-width="135">
 | 
				
			||||||
 | 
					                                    <template #default="scope">
 | 
				
			||||||
 | 
					                                        {{ formatDate(scope.row.createTime) }}
 | 
				
			||||||
 | 
					                                    </template>
 | 
				
			||||||
 | 
					                                </el-table-column>
 | 
				
			||||||
 | 
					                                <el-table-column prop="codePath" min-width="380" show-overflow-tooltip>
 | 
				
			||||||
 | 
					                                    <template #default="scope">
 | 
				
			||||||
 | 
					                                        <TagCodePath :path="scope.row.codePath" :tagInfos="state.mongo.tagInfos" />
 | 
				
			||||||
 | 
					                                    </template>
 | 
				
			||||||
 | 
					                                </el-table-column>
 | 
				
			||||||
 | 
					                                <el-table-column width="30">
 | 
				
			||||||
 | 
					                                    <template #default="scope">
 | 
				
			||||||
 | 
					                                        <el-link @click="toPage('mongo', scope.row.codePath)" type="primary" icon="Position"></el-link>
 | 
				
			||||||
 | 
					                                    </template>
 | 
				
			||||||
 | 
					                                </el-table-column>
 | 
				
			||||||
 | 
					                            </el-table>
 | 
				
			||||||
 | 
					                        </el-col>
 | 
				
			||||||
 | 
					                    </el-row>
 | 
				
			||||||
 | 
					                </el-card>
 | 
				
			||||||
 | 
					            </el-col>
 | 
				
			||||||
 | 
					        </el-row>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        <el-dialog width="900px" title="消息" v-model="msgDialog.visible">
 | 
				
			||||||
 | 
					            <el-table border :data="msgDialog.msgs.list" size="small">
 | 
				
			||||||
 | 
					                <el-table-column property="type" label="类型" width="60">
 | 
				
			||||||
 | 
					                    <template #default="scope">
 | 
				
			||||||
 | 
					                        {{ getMsgTypeDesc(scope.row.type) }}
 | 
				
			||||||
 | 
					                    </template>
 | 
				
			||||||
 | 
					                </el-table-column>
 | 
				
			||||||
 | 
					                <el-table-column property="msg" label="消息"></el-table-column>
 | 
				
			||||||
 | 
					                <el-table-column property="createTime" label="时间" width="150">
 | 
				
			||||||
 | 
					                    <template #default="scope">
 | 
				
			||||||
 | 
					                        {{ formatDate(scope.row.createTime) }}
 | 
				
			||||||
 | 
					                    </template>
 | 
				
			||||||
 | 
					                </el-table-column>
 | 
				
			||||||
 | 
					            </el-table>
 | 
				
			||||||
 | 
					            <el-row type="flex" class="mt5" justify="center">
 | 
				
			||||||
 | 
					                <el-pagination
 | 
				
			||||||
 | 
					                    small
 | 
				
			||||||
 | 
					                    @current-change="searchMsg"
 | 
				
			||||||
 | 
					                    style="text-align: center"
 | 
				
			||||||
 | 
					                    background
 | 
				
			||||||
 | 
					                    layout="prev, pager, next, total, jumper"
 | 
				
			||||||
 | 
					                    :total="msgDialog.msgs.total"
 | 
				
			||||||
 | 
					                    v-model:current-page="msgDialog.query.pageNum"
 | 
				
			||||||
 | 
					                    :page-size="msgDialog.query.pageSize"
 | 
				
			||||||
 | 
					                />
 | 
				
			||||||
 | 
					            </el-row>
 | 
				
			||||||
 | 
					        </el-dialog>
 | 
				
			||||||
    </div>
 | 
					    </div>
 | 
				
			||||||
</template>
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<script lang="ts" setup>
 | 
					<script lang="ts" setup>
 | 
				
			||||||
import { toRefs, reactive, onMounted, nextTick, computed } from 'vue';
 | 
					import { computed, onMounted, reactive, toRefs } from 'vue';
 | 
				
			||||||
// import * as echarts from 'echarts';
 | 
					// import * as echarts from 'echarts';
 | 
				
			||||||
import { CountUp } from 'countup.js';
 | 
					import { formatAxis, formatDate } from '@/common/utils/format';
 | 
				
			||||||
import { formatAxis } from '@/common/utils/format';
 | 
					 | 
				
			||||||
import { indexApi } from './api';
 | 
					import { indexApi } from './api';
 | 
				
			||||||
import { useRouter } from 'vue-router';
 | 
					import { useRouter } from 'vue-router';
 | 
				
			||||||
import { storeToRefs } from 'pinia';
 | 
					import { storeToRefs } from 'pinia';
 | 
				
			||||||
import { useUserInfo } from '@/store/userInfo';
 | 
					import { useUserInfo } from '@/store/userInfo';
 | 
				
			||||||
 | 
					import { personApi } from '../personal/api';
 | 
				
			||||||
 | 
					import SvgIcon from '@/components/svgIcon/index.vue';
 | 
				
			||||||
 | 
					import { TagResourceTypeEnum } from '@/common/commonEnum';
 | 
				
			||||||
 | 
					import { resourceOpLogApi } from '../ops/tag/api';
 | 
				
			||||||
 | 
					import TagCodePath from '../ops/component/TagCodePath.vue';
 | 
				
			||||||
 | 
					import { useAutoOpenResource } from '@/store/autoOpenResource';
 | 
				
			||||||
 | 
					import { getAllTagInfoByCodePaths } from '../ops/component/tag';
 | 
				
			||||||
 | 
					import { ElMessage } from 'element-plus';
 | 
				
			||||||
 | 
					import { getFileUrl, getUploadFileUrl } from '@/common/request';
 | 
				
			||||||
 | 
					import { saveUser } from '@/common/utils/storage';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const router = useRouter();
 | 
					const router = useRouter();
 | 
				
			||||||
const { userInfo } = storeToRefs(useUserInfo());
 | 
					const { userInfo } = storeToRefs(useUserInfo());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const state = reactive({
 | 
					const state = reactive({
 | 
				
			||||||
    topCardItemList: [
 | 
					    accountInfo: {
 | 
				
			||||||
        {
 | 
					        roles: [],
 | 
				
			||||||
            title: 'Linux机器',
 | 
					    },
 | 
				
			||||||
            id: 'machineNum',
 | 
					    msgs: [],
 | 
				
			||||||
            color: '#F95959',
 | 
					    msgDialog: {
 | 
				
			||||||
 | 
					        visible: false,
 | 
				
			||||||
 | 
					        query: {
 | 
				
			||||||
 | 
					            pageSize: 10,
 | 
				
			||||||
 | 
					            pageNum: 1,
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
        {
 | 
					        msgs: {
 | 
				
			||||||
            title: '数据库',
 | 
					            list: [],
 | 
				
			||||||
            id: 'dbNum',
 | 
					            total: null,
 | 
				
			||||||
            color: '#8595F4',
 | 
					 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
        {
 | 
					    },
 | 
				
			||||||
            title: 'redis',
 | 
					    resourceOpTableHeight: 180,
 | 
				
			||||||
            id: 'redisNum',
 | 
					    defaultLogSize: 5,
 | 
				
			||||||
            color: '#1abc9c',
 | 
					    machine: {
 | 
				
			||||||
        },
 | 
					        num: 0,
 | 
				
			||||||
        {
 | 
					        opLogs: [],
 | 
				
			||||||
            title: 'Mongo',
 | 
					        tagInfos: {},
 | 
				
			||||||
            id: 'mongoNum',
 | 
					    },
 | 
				
			||||||
            color: '#FEBB50',
 | 
					    db: {
 | 
				
			||||||
        },
 | 
					        num: 0,
 | 
				
			||||||
    ],
 | 
					        opLogs: [],
 | 
				
			||||||
 | 
					        tagInfos: {},
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    redis: {
 | 
				
			||||||
 | 
					        num: 0,
 | 
				
			||||||
 | 
					        opLogs: [],
 | 
				
			||||||
 | 
					        tagInfos: {},
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    mongo: {
 | 
				
			||||||
 | 
					        num: 0,
 | 
				
			||||||
 | 
					        opLogs: [],
 | 
				
			||||||
 | 
					        tagInfos: {},
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const { topCardItemList } = toRefs(state);
 | 
					const { msgDialog } = toRefs(state);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const roleInfo = computed(() => {
 | 
				
			||||||
 | 
					    if (state.accountInfo.roles.length == 0) {
 | 
				
			||||||
 | 
					        return '';
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    return state.accountInfo.roles.map((val: any) => val.roleName).join('、');
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// 当前时间提示语
 | 
					// 当前时间提示语
 | 
				
			||||||
const currentTime = computed(() => {
 | 
					const currentTime = computed(() => {
 | 
				
			||||||
    return formatAxis(new Date());
 | 
					    return formatAxis(new Date());
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// 页面加载时
 | 
				
			||||||
 | 
					onMounted(() => {
 | 
				
			||||||
 | 
					    initData();
 | 
				
			||||||
 | 
					    getAccountInfo();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    getMsgs().then((res) => {
 | 
				
			||||||
 | 
					        state.msgs = res.list;
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const showMsgs = async () => {
 | 
				
			||||||
 | 
					    state.msgDialog.query.pageNum = 1;
 | 
				
			||||||
 | 
					    searchMsg();
 | 
				
			||||||
 | 
					    state.msgDialog.visible = true;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const searchMsg = async () => {
 | 
				
			||||||
 | 
					    state.msgDialog.msgs = await getMsgs();
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const getMsgTypeDesc = (type: number) => {
 | 
				
			||||||
 | 
					    if (type == 1) {
 | 
				
			||||||
 | 
					        return '登录';
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    if (type == 2) {
 | 
				
			||||||
 | 
					        return '通知';
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const getAccountInfo = async () => {
 | 
				
			||||||
 | 
					    state.accountInfo = await personApi.accountInfo.request();
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const getMsgs = async () => {
 | 
				
			||||||
 | 
					    return await personApi.getMsgs.request(state.msgDialog.query);
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const beforeAvatarUpload = (rawFile: any) => {
 | 
				
			||||||
 | 
					    if (rawFile.size >= 512 * 1024) {
 | 
				
			||||||
 | 
					        ElMessage.error('头像不能超过512KB!');
 | 
				
			||||||
 | 
					        return false;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    return true;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const handleAvatarSuccess = (response: any, uploadFile: any) => {
 | 
				
			||||||
 | 
					    userInfo.value.photo = URL.createObjectURL(uploadFile.raw);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const newUser = { ...userInfo.value };
 | 
				
			||||||
 | 
					    newUser.photo = getFileUrl(`avatar_${userInfo.value.username}`);
 | 
				
			||||||
 | 
					    // 存储用户信息到浏览器缓存
 | 
				
			||||||
 | 
					    saveUser(newUser);
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// 初始化数字滚动
 | 
					// 初始化数字滚动
 | 
				
			||||||
const initNumCountUp = async () => {
 | 
					const initData = async () => {
 | 
				
			||||||
    const res: any = await indexApi.getIndexCount.request();
 | 
					    resourceOpLogApi.getAccountResourceOpLogs
 | 
				
			||||||
    nextTick(() => {
 | 
					        .request({ resourceType: TagResourceTypeEnum.MachineAuthCert.value, pageSize: state.defaultLogSize })
 | 
				
			||||||
        new CountUp('mongoNum', res.mongoNum).start();
 | 
					        .then(async (res: any) => {
 | 
				
			||||||
        new CountUp('machineNum', res.machineNum).start();
 | 
					            const tagInfos = await getAllTagInfoByCodePaths(res.list?.map((item: any) => item.codePath));
 | 
				
			||||||
        new CountUp('dbNum', res.dbNum).start();
 | 
					            state.machine.tagInfos = tagInfos;
 | 
				
			||||||
        new CountUp('redisNum', res.redisNum).start();
 | 
					            state.machine.opLogs = res.list;
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    resourceOpLogApi.getAccountResourceOpLogs
 | 
				
			||||||
 | 
					        .request({ resourceType: TagResourceTypeEnum.DbName.value, pageSize: state.defaultLogSize })
 | 
				
			||||||
 | 
					        .then(async (res: any) => {
 | 
				
			||||||
 | 
					            const tagInfos = await getAllTagInfoByCodePaths(res.list?.map((item: any) => item.codePath));
 | 
				
			||||||
 | 
					            state.db.tagInfos = tagInfos;
 | 
				
			||||||
 | 
					            state.db.opLogs = res.list;
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    resourceOpLogApi.getAccountResourceOpLogs
 | 
				
			||||||
 | 
					        .request({ resourceType: TagResourceTypeEnum.Redis.value, pageSize: state.defaultLogSize })
 | 
				
			||||||
 | 
					        .then(async (res: any) => {
 | 
				
			||||||
 | 
					            const tagInfos = await getAllTagInfoByCodePaths(res.list?.map((item: any) => item.codePath));
 | 
				
			||||||
 | 
					            state.redis.tagInfos = tagInfos;
 | 
				
			||||||
 | 
					            state.redis.opLogs = res.list;
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    resourceOpLogApi.getAccountResourceOpLogs
 | 
				
			||||||
 | 
					        .request({ resourceType: TagResourceTypeEnum.Mongo.value, pageSize: state.defaultLogSize })
 | 
				
			||||||
 | 
					        .then(async (res: any) => {
 | 
				
			||||||
 | 
					            const tagInfos = await getAllTagInfoByCodePaths(res.list?.map((item: any) => item.codePath));
 | 
				
			||||||
 | 
					            state.mongo.tagInfos = tagInfos;
 | 
				
			||||||
 | 
					            state.mongo.opLogs = res.list;
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    indexApi.machineDashbord.request().then((res: any) => {
 | 
				
			||||||
 | 
					        state.machine.num = res.machineNum;
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    indexApi.dbDashbord.request().then((res: any) => {
 | 
				
			||||||
 | 
					        state.db.num = res.dbNum;
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    indexApi.redisDashbord.request().then((res: any) => {
 | 
				
			||||||
 | 
					        state.redis.num = res.redisNum;
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    indexApi.mongoDashbord.request().then((res: any) => {
 | 
				
			||||||
 | 
					        state.mongo.num = res.mongoNum;
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const toPage = (item: any) => {
 | 
					const toPage = (item: any, codePath = '') => {
 | 
				
			||||||
    switch (item.id) {
 | 
					    let path;
 | 
				
			||||||
 | 
					    switch (item) {
 | 
				
			||||||
        case 'personal': {
 | 
					        case 'personal': {
 | 
				
			||||||
            router.push('/personal');
 | 
					            router.push('/personal');
 | 
				
			||||||
            break;
 | 
					            break;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        case 'mongoNum': {
 | 
					        case 'mongo': {
 | 
				
			||||||
            router.push('/mongo/mongo-data-operation');
 | 
					            useAutoOpenResource().setMongoCodePath(codePath);
 | 
				
			||||||
 | 
					            path = '/mongo/mongo-data-operation';
 | 
				
			||||||
            break;
 | 
					            break;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        case 'machineNum': {
 | 
					        case 'machine': {
 | 
				
			||||||
            router.push('/machine/machines');
 | 
					            useAutoOpenResource().setMachineCodePath(codePath);
 | 
				
			||||||
 | 
					            path = '/machine/machines-op';
 | 
				
			||||||
            break;
 | 
					            break;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        case 'dbNum': {
 | 
					        case 'db': {
 | 
				
			||||||
            router.push('/dbms/sql-exec');
 | 
					            useAutoOpenResource().setDbCodePath(codePath);
 | 
				
			||||||
 | 
					            path = '/dbms/sql-exec';
 | 
				
			||||||
            break;
 | 
					            break;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        case 'redisNum': {
 | 
					        case 'redis': {
 | 
				
			||||||
            router.push('/redis/data-operation');
 | 
					            useAutoOpenResource().setRedisCodePath(codePath);
 | 
				
			||||||
 | 
					            path = '/redis/data-operation';
 | 
				
			||||||
            break;
 | 
					            break;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
// 页面加载时
 | 
					    router.push({ path });
 | 
				
			||||||
onMounted(() => {
 | 
					};
 | 
				
			||||||
    initNumCountUp();
 | 
					 | 
				
			||||||
    // initHomeLaboratory();
 | 
					 | 
				
			||||||
    // initHomeOvertime();
 | 
					 | 
				
			||||||
});
 | 
					 | 
				
			||||||
</script>
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<style scoped lang="scss">
 | 
					<style scoped lang="scss">
 | 
				
			||||||
 | 
					@use '@/theme/mixins/index.scss' as mixins;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.personal {
 | 
				
			||||||
 | 
					    .personal-user {
 | 
				
			||||||
 | 
					        height: 130px;
 | 
				
			||||||
 | 
					        display: flex;
 | 
				
			||||||
 | 
					        align-items: center;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        .personal-user-left {
 | 
				
			||||||
 | 
					            width: 100px;
 | 
				
			||||||
 | 
					            height: 130px;
 | 
				
			||||||
 | 
					            border-radius: 3px;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            ::v-deep(.el-upload) {
 | 
				
			||||||
 | 
					                height: 100%;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            .personal-user-left-upload {
 | 
				
			||||||
 | 
					                img {
 | 
				
			||||||
 | 
					                    width: 100%;
 | 
				
			||||||
 | 
					                    height: 100%;
 | 
				
			||||||
 | 
					                    border-radius: 3px;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                &:hover {
 | 
				
			||||||
 | 
					                    img {
 | 
				
			||||||
 | 
					                        animation: logoAnimation 0.3s ease-in-out;
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        .personal-user-right {
 | 
				
			||||||
 | 
					            flex: 1;
 | 
				
			||||||
 | 
					            padding: 0 15px;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            .personal-title {
 | 
				
			||||||
 | 
					                font-size: 18px;
 | 
				
			||||||
 | 
					                @include mixins.text-ellipsis(1);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            .personal-item {
 | 
				
			||||||
 | 
					                display: flex;
 | 
				
			||||||
 | 
					                align-items: center;
 | 
				
			||||||
 | 
					                font-size: 13px;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                .personal-item-label {
 | 
				
			||||||
 | 
					                    color: gray;
 | 
				
			||||||
 | 
					                    @include mixins.text-ellipsis(1);
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                .personal-item-value {
 | 
				
			||||||
 | 
					                    @include mixins.text-ellipsis(1);
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    .personal-info {
 | 
				
			||||||
 | 
					        .personal-info-more {
 | 
				
			||||||
 | 
					            float: right;
 | 
				
			||||||
 | 
					            color: gray;
 | 
				
			||||||
 | 
					            font-size: 13px;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            &:hover {
 | 
				
			||||||
 | 
					                color: var(--el-color-primary);
 | 
				
			||||||
 | 
					                cursor: pointer;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        .personal-info-box {
 | 
				
			||||||
 | 
					            height: 130px;
 | 
				
			||||||
 | 
					            overflow: hidden;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            .personal-info-ul {
 | 
				
			||||||
 | 
					                list-style: none;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                .personal-info-li {
 | 
				
			||||||
 | 
					                    font-size: 13px;
 | 
				
			||||||
 | 
					                    padding-bottom: 10px;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    .personal-info-li-title {
 | 
				
			||||||
 | 
					                        display: inline-block;
 | 
				
			||||||
 | 
					                        @include mixins.text-ellipsis(1);
 | 
				
			||||||
 | 
					                        color: grey;
 | 
				
			||||||
 | 
					                        text-decoration: none;
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    & a:hover {
 | 
				
			||||||
 | 
					                        color: var(--el-color-primary);
 | 
				
			||||||
 | 
					                        cursor: pointer;
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.resource-info {
 | 
				
			||||||
 | 
					    text-align: center;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    ::v-deep(.el-card__header) {
 | 
				
			||||||
 | 
					        padding: 2px 20px;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    .resource-num {
 | 
				
			||||||
 | 
					        font-weight: 700;
 | 
				
			||||||
 | 
					        font-size: 2vw;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.home-container {
 | 
					.home-container {
 | 
				
			||||||
    overflow-x: hidden;
 | 
					    overflow-x: hidden;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -166,7 +632,7 @@ onMounted(() => {
 | 
				
			|||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            .home-card-item-title-num {
 | 
					            .home-card-item-title-num {
 | 
				
			||||||
                font-size: 18px;
 | 
					                font-size: 2vw;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            .home-card-item-tip-num {
 | 
					            .home-card-item-tip-num {
 | 
				
			||||||
@@ -174,124 +640,5 @@ onMounted(() => {
 | 
				
			|||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					 | 
				
			||||||
    .home-card-first {
 | 
					 | 
				
			||||||
        background: var(--bg-main-color);
 | 
					 | 
				
			||||||
        border: 1px solid var(--el-border-color-light, #ebeef5);
 | 
					 | 
				
			||||||
        display: flex;
 | 
					 | 
				
			||||||
        align-items: center;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        img {
 | 
					 | 
				
			||||||
            width: 60px;
 | 
					 | 
				
			||||||
            height: 60px;
 | 
					 | 
				
			||||||
            border-radius: 100%;
 | 
					 | 
				
			||||||
            border: 2px solid var(--el-color-primary-light-5);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        .home-card-first-right {
 | 
					 | 
				
			||||||
            flex: 1;
 | 
					 | 
				
			||||||
            display: flex;
 | 
					 | 
				
			||||||
            flex-direction: column;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            .home-card-first-right-msg {
 | 
					 | 
				
			||||||
                font-size: 13px;
 | 
					 | 
				
			||||||
                color: gray;
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    .home-monitor {
 | 
					 | 
				
			||||||
        height: 200px;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        .flex-warp-item {
 | 
					 | 
				
			||||||
            width: 50%;
 | 
					 | 
				
			||||||
            height: 100px;
 | 
					 | 
				
			||||||
            display: flex;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            .flex-warp-item-box {
 | 
					 | 
				
			||||||
                margin: auto;
 | 
					 | 
				
			||||||
                height: auto;
 | 
					 | 
				
			||||||
                text-align: center;
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    .home-warning-card {
 | 
					 | 
				
			||||||
        height: 292px;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        ::v-deep(.el-card) {
 | 
					 | 
				
			||||||
            height: 100%;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    .home-dynamic {
 | 
					 | 
				
			||||||
        height: 200px;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        .home-dynamic-item {
 | 
					 | 
				
			||||||
            display: flex;
 | 
					 | 
				
			||||||
            width: 100%;
 | 
					 | 
				
			||||||
            height: 60px;
 | 
					 | 
				
			||||||
            overflow: hidden;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            &:first-of-type {
 | 
					 | 
				
			||||||
                .home-dynamic-item-line {
 | 
					 | 
				
			||||||
                    i {
 | 
					 | 
				
			||||||
                        color: orange !important;
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            .home-dynamic-item-left {
 | 
					 | 
				
			||||||
                text-align: right;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                .home-dynamic-item-left-time1 {
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                .home-dynamic-item-left-time2 {
 | 
					 | 
				
			||||||
                    font-size: 13px;
 | 
					 | 
				
			||||||
                    color: gray;
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            .home-dynamic-item-line {
 | 
					 | 
				
			||||||
                height: 60px;
 | 
					 | 
				
			||||||
                border-right: 2px dashed #dfdfdf;
 | 
					 | 
				
			||||||
                margin: 0 20px;
 | 
					 | 
				
			||||||
                position: relative;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                i {
 | 
					 | 
				
			||||||
                    color: var(--el-color-primary);
 | 
					 | 
				
			||||||
                    font-size: 12px;
 | 
					 | 
				
			||||||
                    position: absolute;
 | 
					 | 
				
			||||||
                    top: 1px;
 | 
					 | 
				
			||||||
                    left: -6px;
 | 
					 | 
				
			||||||
                    transform: rotate(46deg);
 | 
					 | 
				
			||||||
                    background: white;
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            .home-dynamic-item-right {
 | 
					 | 
				
			||||||
                flex: 1;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                .home-dynamic-item-right-title {
 | 
					 | 
				
			||||||
                    i {
 | 
					 | 
				
			||||||
                        margin-right: 5px;
 | 
					 | 
				
			||||||
                        border: 1px solid #dfdfdf;
 | 
					 | 
				
			||||||
                        width: 20px;
 | 
					 | 
				
			||||||
                        height: 20px;
 | 
					 | 
				
			||||||
                        border-radius: 100%;
 | 
					 | 
				
			||||||
                        padding: 3px 2px 2px;
 | 
					 | 
				
			||||||
                        text-align: center;
 | 
					 | 
				
			||||||
                        color: var(--el-color-primary);
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                .home-dynamic-item-right-label {
 | 
					 | 
				
			||||||
                    font-size: 13px;
 | 
					 | 
				
			||||||
                    color: gray;
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
</style>
 | 
					</style>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,6 +1,8 @@
 | 
				
			|||||||
import Api from '@/common/Api';
 | 
					import Api from '@/common/Api';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const indexApi = {
 | 
					export const indexApi = {
 | 
				
			||||||
    getIndexCount: Api.newGet("/common/index/count"),
 | 
					    machineDashbord: Api.newGet('/machines/dashbord'),
 | 
				
			||||||
}
 | 
					    dbDashbord: Api.newGet('/dbs/dashbord'),
 | 
				
			||||||
 | 
					    redisDashbord: Api.newGet('/redis/dashbord'),
 | 
				
			||||||
 | 
					    mongoDashbord: Api.newGet('/mongos/dashbord'),
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -132,7 +132,7 @@ import { nextTick, onMounted, ref, toRefs, reactive, computed } from 'vue';
 | 
				
			|||||||
import { useRoute, useRouter } from 'vue-router';
 | 
					import { useRoute, useRouter } from 'vue-router';
 | 
				
			||||||
import { ElMessage } from 'element-plus';
 | 
					import { ElMessage } from 'element-plus';
 | 
				
			||||||
import { initRouter } from '@/router/index';
 | 
					import { initRouter } from '@/router/index';
 | 
				
			||||||
import { saveToken, saveUser } from '@/common/utils/storage';
 | 
					import { getRefreshToken, saveRefreshToken, saveToken, saveUser } from '@/common/utils/storage';
 | 
				
			||||||
import { formatAxis } from '@/common/utils/format';
 | 
					import { formatAxis } from '@/common/utils/format';
 | 
				
			||||||
import openApi from '@/common/openApi';
 | 
					import openApi from '@/common/openApi';
 | 
				
			||||||
import { RsaEncrypt } from '@/common/rsa';
 | 
					import { RsaEncrypt } from '@/common/rsa';
 | 
				
			||||||
@@ -144,6 +144,7 @@ import { personApi } from '@/views/personal/api';
 | 
				
			|||||||
import { AccountUsernamePattern } from '@/common/pattern';
 | 
					import { AccountUsernamePattern } from '@/common/pattern';
 | 
				
			||||||
import { getToken } from '@/common/utils/storage';
 | 
					import { getToken } from '@/common/utils/storage';
 | 
				
			||||||
import { useThemeConfig } from '@/store/themeConfig';
 | 
					import { useThemeConfig } from '@/store/themeConfig';
 | 
				
			||||||
 | 
					import { getFileUrl } from '@/common/request';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const rules = {
 | 
					const rules = {
 | 
				
			||||||
    username: [{ required: true, message: '请输入用户名', trigger: 'blur' }],
 | 
					    username: [{ required: true, message: '请输入用户名', trigger: 'blur' }],
 | 
				
			||||||
@@ -279,19 +280,20 @@ const login = () => {
 | 
				
			|||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const otpVerify = async () => {
 | 
					const otpVerify = async () => {
 | 
				
			||||||
    otpFormRef.value.validate(async (valid: boolean) => {
 | 
					    try {
 | 
				
			||||||
        if (!valid) {
 | 
					        await otpFormRef.value.validate();
 | 
				
			||||||
            return false;
 | 
					    } catch (e: any) {
 | 
				
			||||||
        }
 | 
					        return false;
 | 
				
			||||||
        try {
 | 
					    }
 | 
				
			||||||
            state.loading.otpConfirm = true;
 | 
					
 | 
				
			||||||
            const accessToken = await openApi.otpVerify(state.otpDialog.form);
 | 
					    try {
 | 
				
			||||||
            await signInSuccess(accessToken);
 | 
					        state.loading.otpConfirm = true;
 | 
				
			||||||
            state.otpDialog.visible = false;
 | 
					        const res = await openApi.otpVerify(state.otpDialog.form);
 | 
				
			||||||
        } finally {
 | 
					        await signInSuccess(res.token, res.refresh_token);
 | 
				
			||||||
            state.loading.otpConfirm = false;
 | 
					        state.otpDialog.visible = false;
 | 
				
			||||||
        }
 | 
					    } finally {
 | 
				
			||||||
    });
 | 
					        state.loading.otpConfirm = false;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// 登录
 | 
					// 登录
 | 
				
			||||||
@@ -327,46 +329,55 @@ const onSignIn = async () => {
 | 
				
			|||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const updateUserInfo = async () => {
 | 
					const updateUserInfo = async () => {
 | 
				
			||||||
    baseInfoFormRef.value.validate(async (valid: boolean) => {
 | 
					    try {
 | 
				
			||||||
        if (!valid) {
 | 
					        await baseInfoFormRef.value.validate();
 | 
				
			||||||
            return false;
 | 
					    } catch (e: any) {
 | 
				
			||||||
        }
 | 
					        return false;
 | 
				
			||||||
        try {
 | 
					    }
 | 
				
			||||||
            state.loading.updateUserConfirm = true;
 | 
					
 | 
				
			||||||
            const form = state.baseInfoDialog.form;
 | 
					    try {
 | 
				
			||||||
            await personApi.updateAccount.request(state.baseInfoDialog.form);
 | 
					        state.loading.updateUserConfirm = true;
 | 
				
			||||||
            state.baseInfoDialog.visible = false;
 | 
					        const form = state.baseInfoDialog.form;
 | 
				
			||||||
            useUserInfo().userInfo.username = form.username;
 | 
					        await personApi.updateAccount.request(state.baseInfoDialog.form);
 | 
				
			||||||
            useUserInfo().userInfo.name = form.name;
 | 
					        state.baseInfoDialog.visible = false;
 | 
				
			||||||
            await toIndex();
 | 
					        useUserInfo().userInfo.username = form.username;
 | 
				
			||||||
        } finally {
 | 
					        useUserInfo().userInfo.name = form.name;
 | 
				
			||||||
            state.loading.updateUserConfirm = false;
 | 
					        await toIndex();
 | 
				
			||||||
        }
 | 
					    } finally {
 | 
				
			||||||
    });
 | 
					        state.loading.updateUserConfirm = false;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const loginResDeal = (loginRes: any) => {
 | 
					const loginResDeal = async (loginRes: any) => {
 | 
				
			||||||
    state.loginRes = loginRes;
 | 
					    state.loginRes = loginRes;
 | 
				
			||||||
    // 用户信息
 | 
					    // 用户信息
 | 
				
			||||||
    const userInfos = {
 | 
					    const userInfos = {
 | 
				
			||||||
        name: loginRes.name,
 | 
					        name: loginRes.name,
 | 
				
			||||||
        username: loginRes.username,
 | 
					        username: loginRes.username,
 | 
				
			||||||
        // 头像
 | 
					 | 
				
			||||||
        photo: letterAvatar(loginRes.username),
 | 
					 | 
				
			||||||
        time: new Date().getTime(),
 | 
					        time: new Date().getTime(),
 | 
				
			||||||
        lastLoginTime: loginRes.lastLoginTime,
 | 
					        lastLoginTime: loginRes.lastLoginTime,
 | 
				
			||||||
        lastLoginIp: loginRes.lastLoginIp,
 | 
					        lastLoginIp: loginRes.lastLoginIp,
 | 
				
			||||||
 | 
					        photo: '',
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const avatarFileKey = `avatar_${loginRes.username}`;
 | 
				
			||||||
 | 
					    const avatarFileDetail = await openApi.getFileDetail([avatarFileKey]);
 | 
				
			||||||
 | 
					    // 说明存在头像文件
 | 
				
			||||||
 | 
					    if (avatarFileDetail.length > 0) {
 | 
				
			||||||
 | 
					        userInfos.photo = getFileUrl(avatarFileKey);
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					        userInfos.photo = letterAvatar(loginRes.username);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // 存储用户信息到浏览器缓存
 | 
					    // 存储用户信息到浏览器缓存
 | 
				
			||||||
    saveUser(userInfos);
 | 
					    saveUser(userInfos);
 | 
				
			||||||
    // 1、请注意执行顺序(存储用户信息到vuex)
 | 
					    // 1、请注意执行顺序(存储用户信息到vuex)
 | 
				
			||||||
    useUserInfo().setUserInfo(userInfos);
 | 
					    useUserInfo().setUserInfo(userInfos);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const token = loginRes.token;
 | 
					    const token = loginRes.token;
 | 
				
			||||||
    // 如果不需要    otp校验,则该token即为accessToken,否则为otp校验token
 | 
					    // 如果不需要otp校验,则该token即为accessToken,否则为otp校验token
 | 
				
			||||||
    if (loginRes.otp == -1) {
 | 
					    if (loginRes.otp == -1) {
 | 
				
			||||||
        signInSuccess(token);
 | 
					        signInSuccess(token, loginRes.refresh_token);
 | 
				
			||||||
        return;
 | 
					        return;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -379,12 +390,16 @@ const loginResDeal = (loginRes: any) => {
 | 
				
			|||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// 登录成功后的跳转
 | 
					// 登录成功后的跳转
 | 
				
			||||||
const signInSuccess = async (accessToken: string = '') => {
 | 
					const signInSuccess = async (accessToken: string = '', refreshToken = '') => {
 | 
				
			||||||
    if (!accessToken) {
 | 
					    if (!accessToken) {
 | 
				
			||||||
        accessToken = getToken();
 | 
					        accessToken = getToken();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					    if (!refreshToken) {
 | 
				
			||||||
 | 
					        refreshToken = getRefreshToken();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
    // 存储 token 到浏览器缓存
 | 
					    // 存储 token 到浏览器缓存
 | 
				
			||||||
    saveToken(accessToken);
 | 
					    saveToken(accessToken);
 | 
				
			||||||
 | 
					    saveRefreshToken(refreshToken);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // 初始化路由
 | 
					    // 初始化路由
 | 
				
			||||||
    await initRouter();
 | 
					    await initRouter();
 | 
				
			||||||
@@ -415,26 +430,27 @@ const toIndex = async () => {
 | 
				
			|||||||
    }, 300);
 | 
					    }, 300);
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const changePwd = () => {
 | 
					const changePwd = async () => {
 | 
				
			||||||
    changePwdFormRef.value.validate(async (valid: boolean) => {
 | 
					    try {
 | 
				
			||||||
        if (!valid) {
 | 
					        await changePwdFormRef.value.validate();
 | 
				
			||||||
            return false;
 | 
					    } catch (e: any) {
 | 
				
			||||||
        }
 | 
					        return false;
 | 
				
			||||||
        try {
 | 
					    }
 | 
				
			||||||
            state.loading.changePwd = true;
 | 
					
 | 
				
			||||||
            const form = state.changePwdDialog.form;
 | 
					    try {
 | 
				
			||||||
            const changePwdReq: any = { ...form };
 | 
					        state.loading.changePwd = true;
 | 
				
			||||||
            changePwdReq.oldPassword = await RsaEncrypt(form.oldPassword);
 | 
					        const form = state.changePwdDialog.form;
 | 
				
			||||||
            changePwdReq.newPassword = await RsaEncrypt(form.newPassword);
 | 
					        const changePwdReq: any = { ...form };
 | 
				
			||||||
            await openApi.changePwd(changePwdReq);
 | 
					        changePwdReq.oldPassword = await RsaEncrypt(form.oldPassword);
 | 
				
			||||||
            ElMessage.success('密码修改成功, 新密码已填充至登录密码框');
 | 
					        changePwdReq.newPassword = await RsaEncrypt(form.newPassword);
 | 
				
			||||||
            state.loginForm.password = state.changePwdDialog.form.newPassword;
 | 
					        await openApi.changePwd(changePwdReq);
 | 
				
			||||||
            state.changePwdDialog.visible = false;
 | 
					        ElMessage.success('密码修改成功, 新密码已填充至登录密码框');
 | 
				
			||||||
            getCaptcha();
 | 
					        state.loginForm.password = state.changePwdDialog.form.newPassword;
 | 
				
			||||||
        } finally {
 | 
					        state.changePwdDialog.visible = false;
 | 
				
			||||||
            state.loading.changePwd = false;
 | 
					        getCaptcha();
 | 
				
			||||||
        }
 | 
					    } finally {
 | 
				
			||||||
    });
 | 
					        state.loading.changePwd = false;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const cancelChangePwd = () => {
 | 
					const cancelChangePwd = () => {
 | 
				
			||||||
 
 | 
				
			|||||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user