mirror of
				https://gitee.com/dromara/mayfly-go
				synced 2025-11-04 08:20:25 +08:00 
			
		
		
		
	Compare commits
	
		
			107 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					dd4ac390de | ||
| 
						 | 
					0bd7d38c23 | ||
| 
						 | 
					ead3b0d0d8 | ||
| 
						 | 
					4b973b22a4 | ||
| 
						 | 
					e4e68d02bc | ||
| 
						 | 
					ef8822d671 | ||
| 
						 | 
					8e75e1f6ef | ||
| 
						 | 
					08c381fa60 | ||
| 
						 | 
					d7a10d4032 | ||
| 
						 | 
					c324a030f9 | ||
| 
						 | 
					b618b8f93b | ||
| 
						 | 
					4d2e110e1e | ||
| 
						 | 
					ecd79a2e15 | ||
| 
						 | 
					f4f297d3f7 | ||
| 
						 | 
					b5549c0fae | ||
| 
						 | 
					929bfb3200 | ||
| 
						 | 
					7d3593a944 | ||
| 
						 | 
					9e0db2bc99 | ||
| 
						 | 
					25b0d276b3 | ||
| 
						 | 
					0cb7a7cf83 | ||
| 
						 | 
					52f72400ba | ||
| 
						 | 
					0eaff33168 | ||
| 
						 | 
					086dbf278b | ||
| 
						 | 
					57a5e237ae | ||
| 
						 | 
					eee6cf7b14 | ||
| 
						 | 
					b9c6ac8d6d | ||
| 
						 | 
					618d782af3 | ||
| 
						 | 
					d0ac7de4cb | ||
| 
						 | 
					baf8053613 | ||
| 
						 | 
					b973d63331 | ||
| 
						 | 
					85b64d7e8d | ||
| 
						 | 
					86ad183c41 | ||
| 
						 | 
					f7b685cfad | ||
| 
						 | 
					649116a0b8 | ||
| 
						 | 
					899a3a8243 | ||
| 
						 | 
					d51cd4b289 | ||
| 
						 | 
					537b179e78 | ||
| 
						 | 
					1e5b1868ab | ||
| 
						 | 
					245406673c | ||
| 
						 | 
					51fa197af6 | ||
| 
						 | 
					649b2bb165 | ||
| 
						 | 
					3634c902d0 | ||
| 
						 | 
					756e580469 | ||
| 
						 | 
					4e1350d1cc | ||
| 
						 | 
					2e969d46fb | ||
| 
						 | 
					a5bcbe151d | ||
| 
						 | 
					c4abba361a | ||
| 
						 | 
					24b46b1133 | ||
| 
						 | 
					3ae7e0de75 | ||
| 
						 | 
					c2ee4f9955 | ||
| 
						 | 
					2479412334 | ||
| 
						 | 
					6da8d7fd67 | ||
| 
						 | 
					0f596a712d | ||
| 
						 | 
					8f37b71d7f | ||
| 
						 | 
					5083b2bdfe | ||
| 
						 | 
					155ae65b4a | ||
| 
						 | 
					ffacfc3ae8 | ||
| 
						 | 
					b1ab66ecf9 | ||
| 
						 | 
					f5bb0cad3e | ||
| 
						 | 
					a0de5afcb0 | ||
| 
						 | 
					358d33d60e | ||
| 
						 | 
					062d28b6e6 | ||
| 
						 | 
					513f8ea012 | ||
| 
						 | 
					179b58e557 | ||
| 
						 | 
					b7450f8869 | ||
| 
						 | 
					7f9e972828 | ||
| 
						 | 
					7b51705f4e | ||
| 
						 | 
					6bd9e5333d | ||
| 
						 | 
					112d735ac0 | ||
| 
						 | 
					52553ed53f | ||
| 
						 | 
					70d84e32d1 | ||
| 
						 | 
					3269dfa5d6 | ||
| 
						 | 
					183a6e4905 | ||
| 
						 | 
					5463ae9d7e | ||
| 
						 | 
					f25bdb07ce | ||
| 
						 | 
					aa5c08d564 | ||
| 
						 | 
					dc9a2985f3 | ||
| 
						 | 
					f4ac6d8360 | ||
| 
						 | 
					3266039aaf | ||
| 
						 | 
					e3f4c298b0 | ||
| 
						 | 
					fa58f6d2de | ||
| 
						 | 
					ae5a1fd7de | ||
| 
						 | 
					c240079df4 | ||
| 
						 | 
					aca4e6751e | ||
| 
						 | 
					ce32fc7f2c | ||
| 
						 | 
					d423572e01 | ||
| 
						 | 
					d9807b1bf0 | ||
| 
						 | 
					1bc53b4c80 | ||
| 
						 | 
					6bc2603a4d | ||
| 
						 | 
					e2c929aae1 | ||
| 
						 | 
					0d155d592b | ||
| 
						 | 
					ae510ff1ff | ||
| 
						 | 
					5b0654ad2c | ||
| 
						 | 
					466f97ecbe | ||
| 
						 | 
					27a14c22d7 | ||
| 
						 | 
					4709edcd1c | ||
| 
						 | 
					414de9f2eb | ||
| 
						 | 
					a53e7e7dab | ||
| 
						 | 
					7fa6628dc5 | ||
| 
						 | 
					62c25afea8 | ||
| 
						 | 
					481b622e3b | ||
| 
						 | 
					64f8f9a200 | ||
| 
						 | 
					0eca951465 | ||
| 
						 | 
					ef4e34c584 | ||
| 
						 | 
					d91acbc7ee | ||
| 
						 | 
					b397d1022e | ||
| 
						 | 
					b42a98aff5 | 
							
								
								
									
										6
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										6
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							@@ -17,4 +17,10 @@
 | 
				
			|||||||
*/node_modules/
 | 
					*/node_modules/
 | 
				
			||||||
**/vendor/
 | 
					**/vendor/
 | 
				
			||||||
.idea
 | 
					.idea
 | 
				
			||||||
 | 
					.vscode
 | 
				
			||||||
out
 | 
					out
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					server/docs/docker-compose
 | 
				
			||||||
 | 
					server/config.yml
 | 
				
			||||||
 | 
					server/ip2region.xdb
 | 
				
			||||||
 | 
					mayfly-go.log
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										41
									
								
								Dockerfile
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										41
									
								
								Dockerfile
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,41 @@
 | 
				
			|||||||
 | 
					# 构建前端资源
 | 
				
			||||||
 | 
					FROM node:18-alpine3.16 as fe-builder
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					WORKDIR /mayfly
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					COPY mayfly_go_web .
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					RUN yarn
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					RUN yarn build
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# 构建后端资源
 | 
				
			||||||
 | 
					FROM golang:1.21.0 as be-builder
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					ENV GOPROXY https://goproxy.cn
 | 
				
			||||||
 | 
					WORKDIR /mayfly
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Copy the go source for building server
 | 
				
			||||||
 | 
					COPY server .
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					RUN go mod download
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					COPY --from=fe-builder /mayfly/dist /mayfly/static/static
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Build
 | 
				
			||||||
 | 
					RUN GO111MODULE=on CGO_ENABLED=0 GOOS=linux \
 | 
				
			||||||
 | 
					    go build -a \
 | 
				
			||||||
 | 
					    -o mayfly-go main.go
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					FROM alpine:3.16
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					RUN apk add --no-cache ca-certificates bash expat
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					ENV TZ=Asia/Shanghai
 | 
				
			||||||
 | 
					RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					WORKDIR /mayfly
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					COPY --from=be-builder /mayfly/mayfly-go /usr/local/bin/mayfly-go
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					CMD ["mayfly-go"]
 | 
				
			||||||
							
								
								
									
										48
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										48
									
								
								README.md
									
									
									
									
									
								
							@@ -13,75 +13,91 @@
 | 
				
			|||||||
    <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.18%2B-yellow.svg" alt="golang"/>
 | 
					    <img src="https://img.shields.io/badge/Golang-1.20%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">
 | 
				
			||||||
  </a>
 | 
					  </a>
 | 
				
			||||||
</p>
 | 
					</p>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 | 
				
			||||||
### 介绍
 | 
					### 介绍
 | 
				
			||||||
web版 **linux(终端[终端回放] 文件 脚本 进程)、数据库(mysql postgres)、redis(单机 哨兵 集群)、mongo统一管理操作平台**
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					web 版 **linux(终端[终端回放] 文件 脚本 进程)、数据库(mysql postgres)、redis(单机 哨兵 集群)、mongo 统一管理操作平台**
 | 
				
			||||||
 | 
					
 | 
				
			||||||
### 开发语言与主要框架
 | 
					### 开发语言与主要框架
 | 
				
			||||||
 | 
					
 | 
				
			||||||
- 前端:typescript、vue3、element-plus
 | 
					- 前端:typescript、vue3、element-plus
 | 
				
			||||||
- 后端:golang、gin、gorm
 | 
					- 后端:golang、gin、gorm
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 | 
				
			||||||
### 交流及问题反馈加 QQ 群
 | 
					### 交流及问题反馈加 QQ 群
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<a target="_blank" href="https://qm.qq.com/cgi-bin/qm/qr?k=IdJSHW0jTMhmWFHBUS9a83wxtrxDDhFj&jump_from=webapi">119699946</a>
 | 
					<a target="_blank" href="https://qm.qq.com/cgi-bin/qm/qr?k=IdJSHW0jTMhmWFHBUS9a83wxtrxDDhFj&jump_from=webapi">119699946</a>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 | 
				
			||||||
### 系统相关资料
 | 
					### 系统相关资料
 | 
				
			||||||
- 项目文档: https://objs.gitee.io/mayfly-go-docs
 | 
					 | 
				
			||||||
- 系统操作视频: https://space.bilibili.com/484091081/channel/collectiondetail?sid=392854
 | 
					 | 
				
			||||||
- 部署文档:https://objs.gitee.io/mayfly-go-docs/download
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- 项目文档: https://www.yuque.com/may-fly/mayfly-go
 | 
				
			||||||
 | 
					- 系统操作视频: https://space.bilibili.com/484091081/channel/collectiondetail?sid=392854
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### 演示环境
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					http://go.mayfly.run
 | 
				
			||||||
 | 
					账号/密码:test/test123.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
### 系统核心功能截图
 | 
					### 系统核心功能截图
 | 
				
			||||||
 | 
					
 | 
				
			||||||
##### 记录操作记录
 | 
					##### 记录操作记录
 | 
				
			||||||

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

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

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

 | 
					
 | 
				
			||||||

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

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

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

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

 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 | 
				
			||||||
##### 系统管理
 | 
					##### 系统管理
 | 
				
			||||||
 | 
					
 | 
				
			||||||
##### 账号管理
 | 
					##### 账号管理
 | 
				
			||||||
 | 
					
 | 
				
			||||||

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

 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
##### 资源管理
 | 
					##### 资源管理
 | 
				
			||||||
 | 
					
 | 
				
			||||||

 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					**其他更多功能&操作指南可查看在线文档**: https://www.yuque.com/may-fly/mayfly-go
 | 
				
			||||||
 | 
					
 | 
				
			||||||
**其他更多功能&操作指南可查看在线文档**:  https://objs.gitee.io/mayfly-go-docs
 | 
					#### 💌 支持作者
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					如果觉得项目不错,或者已经在使用了,希望你可以去 <a target="_blank" href="https://github.com/may-fly/mayfly-go">Github</a> 或者 <a target="_blank" href="https://gitee.com/objs/mayfly-go">Gitee</a> 帮我点个 ⭐ Star,这将是对我极大的鼓励与支持。
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -74,8 +74,8 @@ function build() {
 | 
				
			|||||||
    # fi
 | 
					    # fi
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if [ "${copyDocScript}" == "1" ] ; then
 | 
					    if [ "${copyDocScript}" == "1" ] ; then
 | 
				
			||||||
        echo_green "拷贝脚本等资源文件[config.yml、mayfly-go.sql、readme.txt、startup.sh、shutdown.sh]"
 | 
					        echo_green "拷贝脚本等资源文件[config.yml.example、mayfly-go.sql、readme.txt、startup.sh、shutdown.sh]"
 | 
				
			||||||
        cp ${server_folder}/config.yml ${toFolder}
 | 
					        cp ${server_folder}/config.yml.example ${toFolder}
 | 
				
			||||||
        cp ${server_folder}/mayfly-go.sql ${toFolder}
 | 
					        cp ${server_folder}/mayfly-go.sql ${toFolder}
 | 
				
			||||||
        cp ${server_folder}/readme.txt ${toFolder}
 | 
					        cp ${server_folder}/readme.txt ${toFolder}
 | 
				
			||||||
        cp ${server_folder}/startup.sh ${toFolder}
 | 
					        cp ${server_folder}/startup.sh ${toFolder}
 | 
				
			||||||
@@ -104,9 +104,8 @@ function buildMac() {
 | 
				
			|||||||
function buildDocker() {
 | 
					function buildDocker() {
 | 
				
			||||||
    echo_yellow "-------------------构建docker镜像开始-------------------"
 | 
					    echo_yellow "-------------------构建docker镜像开始-------------------"
 | 
				
			||||||
    imageVersion=$1
 | 
					    imageVersion=$1
 | 
				
			||||||
    cd ${server_folder}
 | 
					 | 
				
			||||||
    imageName="mayflygo/mayfly-go:${imageVersion}"
 | 
					    imageName="mayflygo/mayfly-go:${imageVersion}"
 | 
				
			||||||
    docker build -t "${imageName}" .
 | 
					    docker build --platform linux/amd64 -t "${imageName}" .
 | 
				
			||||||
    echo_green "docker镜像构建完成->[${imageName}]"
 | 
					    echo_green "docker镜像构建完成->[${imageName}]"
 | 
				
			||||||
    echo_yellow "-------------------构建docker镜像结束-------------------"
 | 
					    echo_yellow "-------------------构建docker镜像结束-------------------"
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -114,7 +113,6 @@ function buildDocker() {
 | 
				
			|||||||
function buildxDocker() {
 | 
					function buildxDocker() {
 | 
				
			||||||
    echo_yellow "-------------------docker buildx构建镜像开始-------------------"
 | 
					    echo_yellow "-------------------docker buildx构建镜像开始-------------------"
 | 
				
			||||||
    imageVersion=$1
 | 
					    imageVersion=$1
 | 
				
			||||||
    cd ${server_folder}
 | 
					 | 
				
			||||||
    imageName="ccr.ccs.tencentyun.com/mayfly/mayfly-go:${imageVersion}"
 | 
					    imageName="ccr.ccs.tencentyun.com/mayfly/mayfly-go:${imageVersion}"
 | 
				
			||||||
    docker buildx build --push --platform linux/amd64,linux/arm64 -t "${imageName}" .
 | 
					    docker buildx build --push --platform linux/amd64,linux/arm64 -t "${imageName}" .
 | 
				
			||||||
    echo_green "docker多版本镜像构建完成->[${imageName}]"
 | 
					    echo_green "docker多版本镜像构建完成->[${imageName}]"
 | 
				
			||||||
@@ -147,6 +145,11 @@ function runBuild() {
 | 
				
			|||||||
        # 进入目标路径,并赋值全路径
 | 
					        # 进入目标路径,并赋值全路径
 | 
				
			||||||
        cd ${toPath}
 | 
					        cd ${toPath}
 | 
				
			||||||
        toPath=`pwd`
 | 
					        toPath=`pwd`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # read -p "是否构建前端[0|其他->否 1->是 2->构建并拷贝至server/static/static]: " runBuildWeb
 | 
				
			||||||
 | 
					        runBuildWeb="2"
 | 
				
			||||||
 | 
					        # 编译web前端
 | 
				
			||||||
 | 
					        buildWeb ${runBuildWeb}
 | 
				
			||||||
    fi
 | 
					    fi
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if [[ "${buildType}" == "5" ]] || [[ "${buildType}" == "6" ]] ; then
 | 
					    if [[ "${buildType}" == "5" ]] || [[ "${buildType}" == "6" ]] ; then
 | 
				
			||||||
@@ -157,12 +160,6 @@ function runBuild() {
 | 
				
			|||||||
        fi
 | 
					        fi
 | 
				
			||||||
    fi
 | 
					    fi
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 | 
				
			||||||
    # read -p "是否构建前端[0|其他->否 1->是 2->构建并拷贝至server/static/static]: " runBuildWeb
 | 
					 | 
				
			||||||
    runBuildWeb="2"
 | 
					 | 
				
			||||||
    # 编译web前端
 | 
					 | 
				
			||||||
    buildWeb ${runBuildWeb}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    case ${buildType} in
 | 
					    case ${buildType} in
 | 
				
			||||||
         "1")
 | 
					         "1")
 | 
				
			||||||
            buildLinuxAmd64 ${toPath} ${copyDocScript}
 | 
					            buildLinuxAmd64 ${toPath} ${copyDocScript}
 | 
				
			||||||
@@ -190,11 +187,13 @@ function runBuild() {
 | 
				
			|||||||
        ;;
 | 
					        ;;
 | 
				
			||||||
    esac
 | 
					    esac
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if [[ "${buildType}" != "5" ]] && [[ "${buildType}" != "6" ]] ; then
 | 
				
			||||||
        echo_green "删除['${server_folder}/static/static']下静态资源文件."
 | 
					        echo_green "删除['${server_folder}/static/static']下静态资源文件."
 | 
				
			||||||
        # 删除静态资源文件,保留一个favicon.ico,否则后端启动会报错
 | 
					        # 删除静态资源文件,保留一个favicon.ico,否则后端启动会报错
 | 
				
			||||||
        rm -rf ${server_folder}/static/static/assets
 | 
					        rm -rf ${server_folder}/static/static/assets
 | 
				
			||||||
        rm -rf ${server_folder}/static/static/config.js
 | 
					        rm -rf ${server_folder}/static/static/config.js
 | 
				
			||||||
        rm -rf ${server_folder}/static/static/index.html
 | 
					        rm -rf ${server_folder}/static/static/index.html
 | 
				
			||||||
 | 
					    fi
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
runBuild
 | 
					runBuild
 | 
				
			||||||
@@ -2,19 +2,15 @@ version: "3.9"
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
services:
 | 
					services:
 | 
				
			||||||
  mysql:
 | 
					  mysql:
 | 
				
			||||||
    image: "mysql:5.7"
 | 
					    image: "mysql:8"
 | 
				
			||||||
    container_name: mayfly-go-mysql
 | 
					    container_name: mayfly-go-mysql
 | 
				
			||||||
    environment:
 | 
					    environment:
 | 
				
			||||||
      MYSQL_ROOT_PASSWORD: 111049
 | 
					      MYSQL_ROOT_PASSWORD: 111049
 | 
				
			||||||
      MYSQL_DATABASE: mayfly-go
 | 
					      MYSQL_DATABASE: mayfly-go
 | 
				
			||||||
      TZ: Asia/Shanghai
 | 
					      TZ: Asia/Shanghai
 | 
				
			||||||
    volumes:
 | 
					    volumes:
 | 
				
			||||||
      - ./docs/docker-compose/mysql/data/mydir:/mydir
 | 
					      - ./server/docs/docker-compose/mysql/data/mydir:/mydir
 | 
				
			||||||
      - ./docs/docker-compose/mysql/data/datadir:/var/lib/mysql
 | 
					      - ./server/docs/docker-compose/mysql/data/datadir:/var/lib/mysql
 | 
				
			||||||
      # 在宿主机编写 /apps/mysql/conf/my.cnf
 | 
					 | 
				
			||||||
      - ./docs/docker-compose/mysql/my.cnf:/etc/my.cnf
 | 
					 | 
				
			||||||
      # 数据库还原目录 可将需要还原的sql文件放在这里
 | 
					 | 
				
			||||||
      - ./docs/docker-compose/mysql/init:/docker-entrypoint-initdb.d
 | 
					 | 
				
			||||||
    restart: always
 | 
					    restart: always
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  server:
 | 
					  server:
 | 
				
			||||||
@@ -28,6 +24,8 @@ services:
 | 
				
			|||||||
    environment:
 | 
					    environment:
 | 
				
			||||||
      TZ: Asia/Shanghai
 | 
					      TZ: Asia/Shanghai
 | 
				
			||||||
      WAIT_HOSTS: mysql:3306
 | 
					      WAIT_HOSTS: mysql:3306
 | 
				
			||||||
 | 
					    volumes:
 | 
				
			||||||
 | 
					      - ./server/config.yml.example:/mayfly/config.yml
 | 
				
			||||||
    depends_on:
 | 
					    depends_on:
 | 
				
			||||||
      - mysql
 | 
					      - mysql
 | 
				
			||||||
    restart: always
 | 
					    restart: always
 | 
				
			||||||
@@ -1,6 +1,6 @@
 | 
				
			|||||||
module.exports = {
 | 
					module.exports = {
 | 
				
			||||||
    // 一行最多多少个字符
 | 
					    // 一行最多多少个字符
 | 
				
			||||||
	printWidth: 150,
 | 
					    printWidth: 160,
 | 
				
			||||||
    // 指定每个缩进级别的空格数
 | 
					    // 指定每个缩进级别的空格数
 | 
				
			||||||
    tabWidth: 4,
 | 
					    tabWidth: 4,
 | 
				
			||||||
    // 使用制表符而不是空格缩进行
 | 
					    // 使用制表符而不是空格缩进行
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										5164
									
								
								mayfly_go_web/package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										5164
									
								
								mayfly_go_web/package-lock.json
									
									
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							@@ -10,28 +10,31 @@
 | 
				
			|||||||
  },
 | 
					  },
 | 
				
			||||||
  "dependencies": {
 | 
					  "dependencies": {
 | 
				
			||||||
    "@element-plus/icons-vue": "^2.1.0",
 | 
					    "@element-plus/icons-vue": "^2.1.0",
 | 
				
			||||||
    "asciinema-player": "^3.3.0",
 | 
					    "asciinema-player": "^3.5.0",
 | 
				
			||||||
    "axios": "^1.4.0",
 | 
					    "axios": "^1.5.0",
 | 
				
			||||||
    "countup.js": "^2.0.7",
 | 
					    "countup.js": "^2.7.0",
 | 
				
			||||||
    "cropperjs": "^1.5.11",
 | 
					    "cropperjs": "^1.5.11",
 | 
				
			||||||
    "echarts": "^5.4.0",
 | 
					    "echarts": "^5.4.0",
 | 
				
			||||||
    "element-plus": "^2.3.5",
 | 
					    "element-plus": "^2.3.12",
 | 
				
			||||||
    "jsencrypt": "^3.3.1",
 | 
					    "jsencrypt": "^3.3.1",
 | 
				
			||||||
    "lodash": "^4.17.21",
 | 
					    "lodash": "^4.17.21",
 | 
				
			||||||
    "mitt": "^3.0.0",
 | 
					    "mitt": "^3.0.1",
 | 
				
			||||||
    "monaco-editor": "^0.38.0",
 | 
					    "monaco-editor": "^0.43.0",
 | 
				
			||||||
    "monaco-sql-languages": "^0.11.0",
 | 
					    "monaco-sql-languages": "^0.11.0",
 | 
				
			||||||
    "monaco-themes": "^0.4.4",
 | 
					    "monaco-themes": "^0.4.4",
 | 
				
			||||||
    "nprogress": "^0.2.0",
 | 
					    "nprogress": "^0.2.0",
 | 
				
			||||||
    "pinia": "^2.1.3",
 | 
					    "pinia": "^2.1.6",
 | 
				
			||||||
 | 
					    "qrcode.vue": "^3.4.0",
 | 
				
			||||||
    "screenfull": "^6.0.2",
 | 
					    "screenfull": "^6.0.2",
 | 
				
			||||||
    "sortablejs": "^1.13.0",
 | 
					    "sortablejs": "^1.13.0",
 | 
				
			||||||
    "sql-formatter": "^12.1.2",
 | 
					    "sql-formatter": "^12.1.2",
 | 
				
			||||||
    "vue": "^3.3.4",
 | 
					    "vue": "^3.3.4",
 | 
				
			||||||
    "vue-clipboard3": "^1.0.1",
 | 
					    "vue-clipboard3": "^1.0.1",
 | 
				
			||||||
    "vue-router": "^4.2.2",
 | 
					    "vue-router": "^4.2.4",
 | 
				
			||||||
    "xterm": "^5.2.1",
 | 
					    "xterm": "^5.3.0",
 | 
				
			||||||
    "xterm-addon-fit": "^0.7.0"
 | 
					    "xterm-addon-fit": "^0.8.0",
 | 
				
			||||||
 | 
					    "xterm-addon-search": "^0.13.0",
 | 
				
			||||||
 | 
					    "xterm-addon-web-links": "^0.9.0"
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  "devDependencies": {
 | 
					  "devDependencies": {
 | 
				
			||||||
    "@types/lodash": "^4.14.178",
 | 
					    "@types/lodash": "^4.14.178",
 | 
				
			||||||
@@ -49,7 +52,7 @@
 | 
				
			|||||||
    "sass": "^1.62.0",
 | 
					    "sass": "^1.62.0",
 | 
				
			||||||
    "sass-loader": "^13.2.0",
 | 
					    "sass-loader": "^13.2.0",
 | 
				
			||||||
    "typescript": "^5.0.2",
 | 
					    "typescript": "^5.0.2",
 | 
				
			||||||
    "vite": "^4.3.9",
 | 
					    "vite": "^4.4.9",
 | 
				
			||||||
    "vue-eslint-parser": "^9.1.1"
 | 
					    "vue-eslint-parser": "^9.1.1"
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  "browserslist": [
 | 
					  "browserslist": [
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,24 +1,25 @@
 | 
				
			|||||||
window.globalConfig = {
 | 
					window.globalConfig = {
 | 
				
			||||||
    // 默认为空,以访问根目录为api请求地址。若前后端分离部署可单独配置该后端api请求地址
 | 
					    // 默认为空,以访问根目录为api请求地址。若前后端分离部署可单独配置该后端api请求地址
 | 
				
			||||||
    "BaseApiUrl": "",
 | 
					    BaseApiUrl: '',
 | 
				
			||||||
    "BaseWsUrl": ""
 | 
					    BaseWsUrl: '',
 | 
				
			||||||
}
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// index.html添加百秒级时间戳,防止被浏览器缓存
 | 
					// index.html添加百秒级时间戳,防止被浏览器缓存
 | 
				
			||||||
!function () {
 | 
					// !(function () {
 | 
				
			||||||
    let t = "t=" + new Date().getTime().toString().substring(0, 8)
 | 
					//     let t = 't=' + new Date().getTime().toString().substring(0, 8);
 | 
				
			||||||
    let search = location.search;
 | 
					//     let search = location.search;
 | 
				
			||||||
    let m = search && search.match(/t=\d*/g)
 | 
					//     let m = search && search.match(/t=\d*/g);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (m[0]) {
 | 
					//     console.log(location);
 | 
				
			||||||
        if (m[0] !== t) {
 | 
					//     if (m[0]) {
 | 
				
			||||||
            location.search = search.replace(m[0], t)
 | 
					//         if (m[0] !== t) {
 | 
				
			||||||
        }
 | 
					//             location.search = search.replace(m[0], t);
 | 
				
			||||||
    } else {
 | 
					//         }
 | 
				
			||||||
        if (search.indexOf('?') > -1) {
 | 
					//     } else {
 | 
				
			||||||
            location.search = search + '&' + t
 | 
					//         if (search.indexOf('?') > -1) {
 | 
				
			||||||
        } else {
 | 
					//             location.search = search + '&' + t;
 | 
				
			||||||
            location.search = t
 | 
					//         } else {
 | 
				
			||||||
        }
 | 
					//             location.search = t;
 | 
				
			||||||
    }
 | 
					//         }
 | 
				
			||||||
}()
 | 
					//     }
 | 
				
			||||||
 | 
					// })();
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -44,7 +44,7 @@ onMounted(() => {
 | 
				
			|||||||
        });
 | 
					        });
 | 
				
			||||||
        // 获取缓存中的布局配置
 | 
					        // 获取缓存中的布局配置
 | 
				
			||||||
        if (getLocal('themeConfig')) {
 | 
					        if (getLocal('themeConfig')) {
 | 
				
			||||||
            themeConfigStores.setThemeConfig({ themeConfig: getLocal('themeConfig') })
 | 
					            themeConfigStores.setThemeConfig({ themeConfig: getLocal('themeConfig') });
 | 
				
			||||||
            document.documentElement.style.cssText = getLocal('themeConfigStyle');
 | 
					            document.documentElement.style.cssText = getLocal('themeConfigStyle');
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
@@ -52,7 +52,7 @@ onMounted(() => {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
// 页面销毁时,关闭监听布局配置
 | 
					// 页面销毁时,关闭监听布局配置
 | 
				
			||||||
onUnmounted(() => {
 | 
					onUnmounted(() => {
 | 
				
			||||||
    mittBus.off('openSetingsDrawer', () => { });
 | 
					    mittBus.off('openSetingsDrawer', () => {});
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// 监听路由的变化,设置网站标题
 | 
					// 监听路由的变化,设置网站标题
 | 
				
			||||||
 
 | 
				
			|||||||
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							@@ -1,4 +1,4 @@
 | 
				
			|||||||
import request from './request'
 | 
					import request from './request';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * 可用于各模块定义各自api请求
 | 
					 * 可用于各模块定义各自api请求
 | 
				
			||||||
@@ -27,23 +27,13 @@ class Api {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * 操作该权限,即请求对应的url
 | 
					     * 请求对应的该api
 | 
				
			||||||
     * @param {Object} param 请求该api的参数
 | 
					     * @param {Object} param 请求该api的参数
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    request(param: any = null, options: any = null): Promise<any> {
 | 
					    request(param: any = null, options: any = null, headers: any = null): Promise<any> {
 | 
				
			||||||
        return request.send(this, param, options);
 | 
					        return request.request(this.method, this.url, param, headers, options);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
    * 操作该权限,即请求对应的url
 | 
					 | 
				
			||||||
    * @param {Object} param 请求该api的参数
 | 
					 | 
				
			||||||
    * @param headers headers
 | 
					 | 
				
			||||||
    */
 | 
					 | 
				
			||||||
    requestWithHeaders(param: any, headers: any): Promise<any> {
 | 
					 | 
				
			||||||
        return request.sendWithHeaders(this, param, headers);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**    静态方法     **/
 | 
					    /**    静态方法     **/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
@@ -51,7 +41,7 @@ class Api {
 | 
				
			|||||||
     * @param url url
 | 
					     * @param url url
 | 
				
			||||||
     * @param method 请求方法(get,post,put,delete...)
 | 
					     * @param method 请求方法(get,post,put,delete...)
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    static create(url: string, method: string) :Api {
 | 
					    static create(url: string, method: string): Api {
 | 
				
			||||||
        return new Api(url, method);
 | 
					        return new Api(url, method);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -88,5 +78,4 @@ class Api {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default Api;
 | 
				
			||||||
export default Api
 | 
					 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,37 +1,94 @@
 | 
				
			|||||||
 | 
					export interface EnumValueTag {
 | 
				
			||||||
 | 
					    color?: string;
 | 
				
			||||||
 | 
					    type?: string;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * 枚举类
 | 
					 * 枚举值
 | 
				
			||||||
 * @author meilin.huang
 | 
					 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
export class Enum {
 | 
					export class EnumValue {
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * 添加枚举字段
 | 
					     * 枚举值
 | 
				
			||||||
     * 
 | 
					 | 
				
			||||||
     * @param {string} field  枚举字段名
 | 
					 | 
				
			||||||
     * @param {string} label  枚举名称
 | 
					 | 
				
			||||||
     * @param {Object} value  枚举值
 | 
					 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    add(field: string, label: string, value: any) {
 | 
					    value: any;
 | 
				
			||||||
        this[field] = { label, value }
 | 
					
 | 
				
			||||||
        return this
 | 
					    /**
 | 
				
			||||||
 | 
					     * 枚举描述
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    label: string;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * 展示的标签信息
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    tag: EnumValueTag;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    constructor(value: any, label: string) {
 | 
				
			||||||
 | 
					        this.value = value;
 | 
				
			||||||
 | 
					        this.label = label;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    setTagType(type: string = 'primary'): EnumValue {
 | 
				
			||||||
 | 
					        this.tag = { type };
 | 
				
			||||||
 | 
					        return this;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    tagTypeInfo(): EnumValue {
 | 
				
			||||||
 | 
					        return this.setTagType('info');
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    tagTypeSuccess(): EnumValue {
 | 
				
			||||||
 | 
					        return this.setTagType('success');
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    tagTypeDanger(): EnumValue {
 | 
				
			||||||
 | 
					        return this.setTagType('danger');
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    tagTypeWarning(): EnumValue {
 | 
				
			||||||
 | 
					        return this.setTagType('warning');
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    setTagColor(color: string): EnumValue {
 | 
				
			||||||
 | 
					        this.tag = { color };
 | 
				
			||||||
 | 
					        return this;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public static of(value: any, label: string): EnumValue {
 | 
				
			||||||
 | 
					        return new EnumValue(value, label);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * 根据枚举value获取其label
 | 
					     * 根据枚举值获取指定枚举值对象
 | 
				
			||||||
     *
 | 
					     *
 | 
				
			||||||
     * @param {Object} value 
 | 
					     * @param enumValues 所有枚举值
 | 
				
			||||||
 | 
					     * @param value 需要匹配的枚举值
 | 
				
			||||||
 | 
					     * @returns 枚举值对象
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    getLabelByValue(value: any) {
 | 
					    static getEnumByValue(enumValues: EnumValue[], value: any): EnumValue | null {
 | 
				
			||||||
        // 字段不存在返回‘’
 | 
					        for (let enumValue of enumValues) {
 | 
				
			||||||
        if (value === undefined || value === null) {
 | 
					            if (enumValue.value == value) {
 | 
				
			||||||
            return ''
 | 
					                return enumValue;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        for (const i in this) {
 | 
					 | 
				
			||||||
            const e: any = this[i]
 | 
					 | 
				
			||||||
            if (e && e.value === value) {
 | 
					 | 
				
			||||||
                return e.label
 | 
					 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					        return null;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return ''
 | 
					    /**
 | 
				
			||||||
 | 
					     * 根据枚举值获取枚举描述
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @param enums 枚举对象
 | 
				
			||||||
 | 
					     * @param value 枚举值
 | 
				
			||||||
 | 
					     * @returns 枚举描述
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    static getLabelByValue(enums: any, value: any) {
 | 
				
			||||||
 | 
					        const enumValues = Object.values(enums) as any;
 | 
				
			||||||
 | 
					        for (let enumValue of enumValues) {
 | 
				
			||||||
 | 
					            if (enumValue['value'] == value) {
 | 
				
			||||||
 | 
					                return enumValue['label'];
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        return '';
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default EnumValue;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,9 +1,8 @@
 | 
				
			|||||||
class SocketBuilder {
 | 
					class SocketBuilder {
 | 
				
			||||||
 | 
					 | 
				
			||||||
    websocket: WebSocket;
 | 
					    websocket: WebSocket;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    constructor(url: string) {
 | 
					    constructor(url: string) {
 | 
				
			||||||
      if (typeof (WebSocket) === "undefined") {
 | 
					        if (typeof WebSocket === 'undefined') {
 | 
				
			||||||
            throw new Error('不支持websocket');
 | 
					            throw new Error('不支持websocket');
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        if (!url) {
 | 
					        if (!url) {
 | 
				
			||||||
@@ -39,8 +38,6 @@ class SocketBuilder {
 | 
				
			|||||||
    build() {
 | 
					    build() {
 | 
				
			||||||
        return this.websocket;
 | 
					        return this.websocket;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					}
 | 
				
			||||||
 | 
					 | 
				
			||||||
  
 | 
					 | 
				
			||||||
  export default SocketBuilder;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default SocketBuilder;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -5,7 +5,7 @@ class AssertError extends Error {
 | 
				
			|||||||
    constructor(message: string) {
 | 
					    constructor(message: string) {
 | 
				
			||||||
        super(message);
 | 
					        super(message);
 | 
				
			||||||
        // 错误类名
 | 
					        // 错误类名
 | 
				
			||||||
        this.name = "AssertError";
 | 
					        this.name = 'AssertError';
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -15,7 +15,7 @@ class AssertError extends Error {
 | 
				
			|||||||
 * @param condition 条件表达式
 | 
					 * @param condition 条件表达式
 | 
				
			||||||
 * @param msg 错误消息
 | 
					 * @param msg 错误消息
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
 export function isTrue(condition: boolean, msg: string) {
 | 
					export function isTrue(condition: boolean, msg: string) {
 | 
				
			||||||
    if (!condition) {
 | 
					    if (!condition) {
 | 
				
			||||||
        throw new AssertError(msg);
 | 
					        throw new AssertError(msg);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@@ -27,8 +27,8 @@ class AssertError extends Error {
 | 
				
			|||||||
 * @param obj 对象1
 | 
					 * @param obj 对象1
 | 
				
			||||||
 * @param msg 错误消息
 | 
					 * @param msg 错误消息
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
 export function notBlank(obj: any, msg: string) {
 | 
					export function notBlank(obj: any, msg: string) {
 | 
				
			||||||
    isTrue(obj, msg)
 | 
					    isTrue(obj, msg);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
@@ -38,7 +38,7 @@ class AssertError extends Error {
 | 
				
			|||||||
 * @param obj2 对象2
 | 
					 * @param obj2 对象2
 | 
				
			||||||
 * @param msg 错误消息
 | 
					 * @param msg 错误消息
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
 export function isEquals(obj1: any, obj2: any, msg: string) {
 | 
					export function isEquals(obj1: any, obj2: any, msg: string) {
 | 
				
			||||||
    isTrue(obj1 === obj2, msg);
 | 
					    isTrue(obj1 === obj2, msg);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -50,16 +50,16 @@ class AssertError extends Error {
 | 
				
			|||||||
 */
 | 
					 */
 | 
				
			||||||
export function notNull(obj: any, msg: string) {
 | 
					export function notNull(obj: any, msg: string) {
 | 
				
			||||||
    if (obj == null || obj == undefined) {
 | 
					    if (obj == null || obj == undefined) {
 | 
				
			||||||
        throw new AssertError(msg)
 | 
					        throw new AssertError(msg);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
* 断言字符串不能为空
 | 
					 * 断言字符串不能为空
 | 
				
			||||||
* 
 | 
					 *
 | 
				
			||||||
* @param str 字符串
 | 
					 * @param str 字符串
 | 
				
			||||||
* @param msg 错误提示
 | 
					 * @param msg 错误提示
 | 
				
			||||||
*/
 | 
					 */
 | 
				
			||||||
export function notEmpty(str: string, msg: string) {
 | 
					export function notEmpty(str: string, msg: string) {
 | 
				
			||||||
    if (str == null || str == undefined || str == '') {
 | 
					    if (str == null || str == undefined || str == '') {
 | 
				
			||||||
        throw new AssertError(msg);
 | 
					        throw new AssertError(msg);
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -11,7 +11,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.4.3'
 | 
					    version: 'v1.5.2',
 | 
				
			||||||
}
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default config
 | 
					export default config;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,26 +1,19 @@
 | 
				
			|||||||
// import * as echarts from 'echarts'
 | 
					// import * as echarts from 'echarts'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// 引入 echarts 核心模块,核心模块提供了 echarts 使用必须要的接口。
 | 
					// 引入 echarts 核心模块,核心模块提供了 echarts 使用必须要的接口。
 | 
				
			||||||
import * as echarts from "echarts/core";
 | 
					import * as echarts from 'echarts/core';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/** 图表后缀都为 Chart  */
 | 
					/** 图表后缀都为 Chart  */
 | 
				
			||||||
import { PieChart } from "echarts/charts";
 | 
					import { PieChart } from 'echarts/charts';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// 引入提示框,标题,直角坐标系,数据集,内置数据转换器组件,组件后缀都为 Component
 | 
					// 引入提示框,标题,直角坐标系,数据集,内置数据转换器组件,组件后缀都为 Component
 | 
				
			||||||
import {
 | 
					import { TitleComponent, TooltipComponent, GridComponent, DatasetComponent, TransformComponent, LegendComponent } from 'echarts/components';
 | 
				
			||||||
  TitleComponent,
 | 
					 | 
				
			||||||
  TooltipComponent,
 | 
					 | 
				
			||||||
  GridComponent,
 | 
					 | 
				
			||||||
  DatasetComponent,
 | 
					 | 
				
			||||||
  TransformComponent,
 | 
					 | 
				
			||||||
  LegendComponent,
 | 
					 | 
				
			||||||
} from "echarts/components";
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
// 标签自动布局,全局过渡动画等特性
 | 
					// 标签自动布局,全局过渡动画等特性
 | 
				
			||||||
import { LabelLayout, UniversalTransition } from "echarts/features";
 | 
					import { LabelLayout, UniversalTransition } from 'echarts/features';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// 引入 Canvas 渲染器,注意引入 CanvasRenderer 或者 SVGRenderer 是必须的一步
 | 
					// 引入 Canvas 渲染器,注意引入 CanvasRenderer 或者 SVGRenderer 是必须的一步
 | 
				
			||||||
import { CanvasRenderer } from "echarts/renderers";
 | 
					import { CanvasRenderer } from 'echarts/renderers';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// 注册必须的组件
 | 
					// 注册必须的组件
 | 
				
			||||||
echarts.use([
 | 
					echarts.use([
 | 
				
			||||||
@@ -30,15 +23,15 @@ echarts.use([
 | 
				
			|||||||
    DatasetComponent,
 | 
					    DatasetComponent,
 | 
				
			||||||
    TransformComponent,
 | 
					    TransformComponent,
 | 
				
			||||||
    LegendComponent,
 | 
					    LegendComponent,
 | 
				
			||||||
//   BarChart,
 | 
					    //   BarChart,
 | 
				
			||||||
    LabelLayout,
 | 
					    LabelLayout,
 | 
				
			||||||
    UniversalTransition,
 | 
					    UniversalTransition,
 | 
				
			||||||
    CanvasRenderer,
 | 
					    CanvasRenderer,
 | 
				
			||||||
//   LineChart,
 | 
					    //   LineChart,
 | 
				
			||||||
    PieChart,
 | 
					    PieChart,
 | 
				
			||||||
]);
 | 
					]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default function(dom: any, theme: any = null,  option: any) {
 | 
					export default function (dom: any, theme: any = null, option: any) {
 | 
				
			||||||
    let chart = echarts.init(dom, theme);
 | 
					    let chart = echarts.init(dom, theme);
 | 
				
			||||||
    chart.setOption(option);
 | 
					    chart.setOption(option);
 | 
				
			||||||
    return chart;
 | 
					    return chart;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,27 +0,0 @@
 | 
				
			|||||||
interface BaseEnum {
 | 
					 | 
				
			||||||
    name: string
 | 
					 | 
				
			||||||
    value: any
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const success: BaseEnum = {
 | 
					 | 
				
			||||||
    name: 'success',
 | 
					 | 
				
			||||||
    value: 200
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export enum ResultEnum {
 | 
					 | 
				
			||||||
    SUCCESS = 200,
 | 
					 | 
				
			||||||
    ERROR = 400,
 | 
					 | 
				
			||||||
    PARAM_ERROR = 405,
 | 
					 | 
				
			||||||
    SERVER_ERROR = 500,
 | 
					 | 
				
			||||||
    NO_PERMISSION = 501
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
// /**
 | 
					 | 
				
			||||||
//  * 全局公共枚举类
 | 
					 | 
				
			||||||
//  */
 | 
					 | 
				
			||||||
// export default {
 | 
					 | 
				
			||||||
//   // uri请求方法
 | 
					 | 
				
			||||||
//   requestMethod: new Enum().add('GET', 'GET', 1).add('POST', 'POST', 2).add('PUT', 'PUT', 3).add('DELETE', 'DELETE', 4),
 | 
					 | 
				
			||||||
//   // 结果枚举
 | 
					 | 
				
			||||||
//   ResultEnum: new Enum().add('SUCCESS', '操作成功', 200).add('ERROR', '操作失败', 400).add('PARAM_ERROR', '参数错误', 405).add('SERVER_ERROR', '服务器异常', 500)
 | 
					 | 
				
			||||||
//     .add('NO_PERMISSION', '没有权限', 501)
 | 
					 | 
				
			||||||
// }
 | 
					 | 
				
			||||||
@@ -1,11 +1,16 @@
 | 
				
			|||||||
import Api from './Api'
 | 
					import request from './request';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default {
 | 
					export default {
 | 
				
			||||||
    login: Api.newPost("/sys/accounts/login"),
 | 
					    login: (param: any) => request.post('/auth/accounts/login', param),
 | 
				
			||||||
    changePwd: Api.newPost("/sys/accounts/change-pwd"),
 | 
					    otpVerify: (param: any) => request.post('/auth/accounts/otp-verify', param),
 | 
				
			||||||
    getPublicKey: Api.newGet("/common/public-key"),
 | 
					    getPublicKey: () => request.get('/common/public-key'),
 | 
				
			||||||
    getConfigValue: Api.newGet("/sys/configs/value"),
 | 
					    getConfigValue: (params: any) => request.get('/sys/configs/value', params),
 | 
				
			||||||
    captcha: Api.newGet("/sys/captcha"),
 | 
					    oauth2LoginConfig: () => request.get('/auth/oauth2-config'),
 | 
				
			||||||
    logout: Api.newPost("/sys/accounts/logout/{token}"),
 | 
					    changePwd: (param: any) => request.post('/sys/accounts/change-pwd', param),
 | 
				
			||||||
    getPermissions: Api.newGet("/sys/accounts/permissions")
 | 
					    captcha: () => request.get('/sys/captcha'),
 | 
				
			||||||
}
 | 
					    logout: () => request.post('/auth/accounts/logout'),
 | 
				
			||||||
 | 
					    getPermissions: () => request.get('/sys/accounts/permissions'),
 | 
				
			||||||
 | 
					    oauth2Callback: (params: any) => request.get('/auth/oauth2/callback', params),
 | 
				
			||||||
 | 
					    getLdapEnabled: () => request.get('/auth/ldap/enabled'),
 | 
				
			||||||
 | 
					    ldapLogin: (param: any) => request.post('/auth/ldap/login', param),
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										4
									
								
								mayfly_go_web/src/common/pattern.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								mayfly_go_web/src/common/pattern.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,4 @@
 | 
				
			|||||||
 | 
					export const AccountUsernamePattern = {
 | 
				
			||||||
 | 
					    pattern: /^[a-zA-Z0-9_]{5,20}$/g,
 | 
				
			||||||
 | 
					    message: '只允许输入5-20位大小写字母、数字、下划线',
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
@@ -1,7 +1,5 @@
 | 
				
			|||||||
import router from "../router";
 | 
					import router from '../router';
 | 
				
			||||||
import Axios from 'axios';
 | 
					import Axios from 'axios';
 | 
				
			||||||
import { ResultEnum } from './enums'
 | 
					 | 
				
			||||||
import Api from './Api';
 | 
					 | 
				
			||||||
import config from './config';
 | 
					import config from './config';
 | 
				
			||||||
import { getSession } from './utils/storage';
 | 
					import { getSession } from './utils/storage';
 | 
				
			||||||
import { templateResolve } from './utils/string';
 | 
					import { templateResolve } from './utils/string';
 | 
				
			||||||
@@ -22,8 +20,16 @@ export interface Result {
 | 
				
			|||||||
    data?: any;
 | 
					    data?: any;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const baseUrl: string = config.baseApiUrl
 | 
					enum ResultEnum {
 | 
				
			||||||
const baseWsUrl: string = config.baseWsUrl
 | 
					    SUCCESS = 200,
 | 
				
			||||||
 | 
					    ERROR = 400,
 | 
				
			||||||
 | 
					    PARAM_ERROR = 405,
 | 
				
			||||||
 | 
					    SERVER_ERROR = 500,
 | 
				
			||||||
 | 
					    NO_PERMISSION = 501,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const baseUrl: string = config.baseApiUrl;
 | 
				
			||||||
 | 
					const baseWsUrl: string = config.baseWsUrl;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * 通知错误消息
 | 
					 * 通知错误消息
 | 
				
			||||||
@@ -37,60 +43,72 @@ function notifyErrorMsg(msg: string) {
 | 
				
			|||||||
// create an axios instance
 | 
					// create an axios instance
 | 
				
			||||||
const service = Axios.create({
 | 
					const service = Axios.create({
 | 
				
			||||||
    baseURL: baseUrl, // url = base url + request url
 | 
					    baseURL: baseUrl, // url = base url + request url
 | 
				
			||||||
    timeout: 20000 // request timeout
 | 
					    timeout: 20000, // request timeout
 | 
				
			||||||
})
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// request interceptor
 | 
					// request interceptor
 | 
				
			||||||
service.interceptors.request.use(
 | 
					service.interceptors.request.use(
 | 
				
			||||||
    (config: any) => {
 | 
					    (config: any) => {
 | 
				
			||||||
        // do something before request is sent
 | 
					        // do something before request is sent
 | 
				
			||||||
        const token = getSession("token")
 | 
					        const token = getSession('token');
 | 
				
			||||||
        if (token) {
 | 
					        if (token) {
 | 
				
			||||||
            // 设置token
 | 
					            // 设置token
 | 
				
			||||||
            config.headers['Authorization'] = token
 | 
					            config.headers['Authorization'] = token;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        return config
 | 
					        return config;
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    error => {
 | 
					    (error) => {
 | 
				
			||||||
        return Promise.reject(error)
 | 
					        return Promise.reject(error);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
)
 | 
					);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// response interceptor
 | 
					// response interceptor
 | 
				
			||||||
service.interceptors.response.use(
 | 
					service.interceptors.response.use(
 | 
				
			||||||
    response => {
 | 
					    (response) => {
 | 
				
			||||||
        // 获取请求返回结果
 | 
					        // 获取请求返回结果
 | 
				
			||||||
        const data: Result = response.data;
 | 
					        const data: Result = response.data;
 | 
				
			||||||
 | 
					        if (data.code === ResultEnum.SUCCESS) {
 | 
				
			||||||
 | 
					            return data.data;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
        // 如果提示没有权限,则移除token,使其重新登录
 | 
					        // 如果提示没有权限,则移除token,使其重新登录
 | 
				
			||||||
        if (data.code === ResultEnum.NO_PERMISSION) {
 | 
					        if (data.code === ResultEnum.NO_PERMISSION) {
 | 
				
			||||||
            router.push({
 | 
					            router.push({
 | 
				
			||||||
                path: '/401',
 | 
					                path: '/401',
 | 
				
			||||||
            });
 | 
					            });
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        if (data.code === ResultEnum.SUCCESS) {
 | 
					 | 
				
			||||||
            return data.data;
 | 
					 | 
				
			||||||
        } else {
 | 
					 | 
				
			||||||
        return Promise.reject(data);
 | 
					        return Promise.reject(data);
 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    (e: any) => {
 | 
					    (e: any) => {
 | 
				
			||||||
 | 
					        const rejectPromise = Promise.reject(e);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const statusCode = e.response?.status;
 | 
				
			||||||
 | 
					        if (statusCode == 500) {
 | 
				
			||||||
 | 
					            notifyErrorMsg('服务器未知异常');
 | 
				
			||||||
 | 
					            return rejectPromise;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (statusCode == 404) {
 | 
				
			||||||
 | 
					            notifyErrorMsg('请求接口未找到');
 | 
				
			||||||
 | 
					            return rejectPromise;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (e.message) {
 | 
					        if (e.message) {
 | 
				
			||||||
            // 对响应错误做点什么
 | 
					            // 对响应错误做点什么
 | 
				
			||||||
            if (e.message.indexOf('timeout') != -1) {
 | 
					            if (e.message.indexOf('timeout') != -1) {
 | 
				
			||||||
                notifyErrorMsg('网络超时');
 | 
					                notifyErrorMsg('网络请求超时');
 | 
				
			||||||
            } else if (e.message == 'Network Error') {
 | 
					                return rejectPromise;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (e.message == 'Network Error') {
 | 
				
			||||||
                notifyErrorMsg('网络连接错误');
 | 
					                notifyErrorMsg('网络连接错误');
 | 
				
			||||||
            } else if (e.message.indexOf('404')) {
 | 
					                return rejectPromise;
 | 
				
			||||||
                notifyErrorMsg('请求接口找不到');
 | 
					 | 
				
			||||||
            } else {
 | 
					 | 
				
			||||||
                if (e.response.data) ElMessage.error(e.response.statusText);
 | 
					 | 
				
			||||||
                else notifyErrorMsg('接口路径找不到');
 | 
					 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return Promise.reject(e)
 | 
					        notifyErrorMsg('网络请求错误');
 | 
				
			||||||
 | 
					        return rejectPromise;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
)
 | 
					);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * 请求uri
 | 
					 * 请求uri
 | 
				
			||||||
@@ -101,19 +119,18 @@ service.interceptors.response.use(
 | 
				
			|||||||
 * @param {Object} params 参数
 | 
					 * @param {Object} params 参数
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
function request(method: string, url: string, params: any = null, headers: any = null, options: any = null): Promise<any> {
 | 
					function request(method: string, url: string, params: any = null, headers: any = null, options: any = null): Promise<any> {
 | 
				
			||||||
    if (!url)
 | 
					    if (!url) throw new Error('请求url不能为空');
 | 
				
			||||||
        throw new Error('请求url不能为空');
 | 
					 | 
				
			||||||
    // 简单判断该url是否是restful风格
 | 
					    // 简单判断该url是否是restful风格
 | 
				
			||||||
    if (url.indexOf("{") != -1) {
 | 
					    if (url.indexOf('{') != -1) {
 | 
				
			||||||
        url = templateResolve(url, params);
 | 
					        url = templateResolve(url, params);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    const query: any = {
 | 
					    const query: any = {
 | 
				
			||||||
        method,
 | 
					        method,
 | 
				
			||||||
        url: url,
 | 
					        url: url,
 | 
				
			||||||
        ...options
 | 
					        ...options,
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
    if (headers) {
 | 
					    if (headers) {
 | 
				
			||||||
        query.headers = headers
 | 
					        query.headers = headers;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // post和put使用json格式传参
 | 
					    // post和put使用json格式传参
 | 
				
			||||||
@@ -122,32 +139,39 @@ function request(method: string, url: string, params: any = null, headers: any =
 | 
				
			|||||||
    } else {
 | 
					    } else {
 | 
				
			||||||
        query.params = params;
 | 
					        query.params = params;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    return service.request(query).then(res => res)
 | 
					    return service
 | 
				
			||||||
        .catch(e => {
 | 
					        .request(query)
 | 
				
			||||||
 | 
					        .then((res) => res)
 | 
				
			||||||
 | 
					        .catch((e) => {
 | 
				
			||||||
            // 如果返回的code不为成功,则会返回对应的错误msg,则直接统一通知即可
 | 
					            // 如果返回的code不为成功,则会返回对应的错误msg,则直接统一通知即可
 | 
				
			||||||
            if (e.msg) {
 | 
					            if (e.msg) {
 | 
				
			||||||
                notifyErrorMsg(e.msg)
 | 
					                notifyErrorMsg(e.msg);
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            return Promise.reject(e);
 | 
					            return Promise.reject(e);
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * 根据api执行对应接口
 | 
					 * get请求uri
 | 
				
			||||||
 * @param api Api实例
 | 
					 * 该方法已处理请求结果中code != 200的message提示,如需其他错误处理(取消加载状态,重置对象状态等等),可catch继续处理
 | 
				
			||||||
 * @param params 请求参数
 | 
					 *
 | 
				
			||||||
 | 
					 * @param {Object} url   uri
 | 
				
			||||||
 | 
					 * @param {Object} params 参数
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
function send(api: Api, params: any, options: any): Promise<any> {
 | 
					function get(url: string, params: any = null, headers: any = null, options: any = null): Promise<any> {
 | 
				
			||||||
    return request(api.method, api.url, params, null, options);
 | 
					    return request('get', url, params, headers, options);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					function post(url: string, params: any = null, headers: any = null, options: any = null): Promise<any> {
 | 
				
			||||||
 * 根据api执行对应接口
 | 
					    return request('post', url, params, headers, options);
 | 
				
			||||||
 * @param api Api实例
 | 
					}
 | 
				
			||||||
 * @param params 请求参数
 | 
					
 | 
				
			||||||
 */
 | 
					function put(url: string, params: any = null, headers: any = null, options: any = null): Promise<any> {
 | 
				
			||||||
function sendWithHeaders(api: Api, params: any, headers: any): Promise<any> {
 | 
					    return request('put', url, params, headers, options);
 | 
				
			||||||
    return request(api.method, api.url, params, headers, null);
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function del(url: string, params: any = null, headers: any = null, options: any = null): Promise<any> {
 | 
				
			||||||
 | 
					    return request('delete', url, params, headers, options);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function getApiUrl(url: string) {
 | 
					function getApiUrl(url: string) {
 | 
				
			||||||
@@ -155,10 +179,11 @@ function getApiUrl(url: string) {
 | 
				
			|||||||
    return baseUrl + url + '?token=' + getSession('token');
 | 
					    return baseUrl + url + '?token=' + getSession('token');
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 | 
				
			||||||
export default {
 | 
					export default {
 | 
				
			||||||
    request,
 | 
					    request,
 | 
				
			||||||
    send,
 | 
					    get,
 | 
				
			||||||
    sendWithHeaders,
 | 
					    post,
 | 
				
			||||||
    getApiUrl
 | 
					    put,
 | 
				
			||||||
}
 | 
					    del,
 | 
				
			||||||
 | 
					    getApiUrl,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,17 +1,17 @@
 | 
				
			|||||||
import openApi from './openApi';
 | 
					import openApi from './openApi';
 | 
				
			||||||
import JSEncrypt from 'jsencrypt'
 | 
					import JSEncrypt from 'jsencrypt';
 | 
				
			||||||
import { notBlank } from './assert';
 | 
					import { notBlank } from './assert';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
var encryptor: any = null
 | 
					var encryptor: any = null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export async function getRsaPublicKey() {
 | 
					export async function getRsaPublicKey() {
 | 
				
			||||||
    let publicKey = sessionStorage.getItem('RsaPublicKey')
 | 
					    let publicKey = sessionStorage.getItem('RsaPublicKey');
 | 
				
			||||||
    if (publicKey) {
 | 
					    if (publicKey) {
 | 
				
			||||||
        return publicKey
 | 
					        return publicKey;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    publicKey = await openApi.getPublicKey.request() as string
 | 
					    publicKey = (await openApi.getPublicKey()) as string;
 | 
				
			||||||
    sessionStorage.setItem('RsaPublicKey', publicKey)
 | 
					    sessionStorage.setItem('RsaPublicKey', publicKey);
 | 
				
			||||||
    return publicKey
 | 
					    return publicKey;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
@@ -23,14 +23,14 @@ export async function getRsaPublicKey() {
 | 
				
			|||||||
export async function RsaEncrypt(value: any) {
 | 
					export async function RsaEncrypt(value: any) {
 | 
				
			||||||
    // 不存在则返回空值
 | 
					    // 不存在则返回空值
 | 
				
			||||||
    if (!value) {
 | 
					    if (!value) {
 | 
				
			||||||
        return ""
 | 
					        return '';
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    if (encryptor != null) {
 | 
					    if (encryptor != null && sessionStorage.getItem('RsaPublicKey') != null) {
 | 
				
			||||||
        return encryptor.encrypt(value)
 | 
					        return encryptor.encrypt(value);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    encryptor = new JSEncrypt()
 | 
					    encryptor = new JSEncrypt();
 | 
				
			||||||
    const publicKey = await getRsaPublicKey() as string;
 | 
					    const publicKey = (await getRsaPublicKey()) as string;
 | 
				
			||||||
    notBlank(publicKey, "获取公钥失败")
 | 
					    notBlank(publicKey, '获取公钥失败');
 | 
				
			||||||
    encryptor.setPublicKey(publicKey)//设置公钥
 | 
					    encryptor.setPublicKey(publicKey); //设置公钥
 | 
				
			||||||
    return encryptor.encrypt(value)
 | 
					    return encryptor.encrypt(value);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -1,8 +1,7 @@
 | 
				
			|||||||
 | 
					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 { getSession } from '@/common/utils/storage.ts';
 | 
					import { getSession } from '@/common/utils/storage';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default {
 | 
					export default {
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
@@ -38,8 +37,9 @@ export default {
 | 
				
			|||||||
                    title: message.title,
 | 
					                    title: message.title,
 | 
				
			||||||
                    message: message.msg,
 | 
					                    message: message.msg,
 | 
				
			||||||
                    type: mtype as any,
 | 
					                    type: mtype as any,
 | 
				
			||||||
 | 
					                });
 | 
				
			||||||
            })
 | 
					            })
 | 
				
			||||||
            })
 | 
					            .open((event: any) => console.log(event))
 | 
				
			||||||
            .open((event: any) => console.log(event)).build();
 | 
					            .build();
 | 
				
			||||||
    }
 | 
					    },
 | 
				
			||||||
}
 | 
					};
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,8 +1,9 @@
 | 
				
			|||||||
import openApi from './openApi';
 | 
					import openApi from './openApi';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// 登录是否使用验证码配置key
 | 
					// 登录是否使用验证码配置key
 | 
				
			||||||
const UseLoginCaptchaConfigKey = "UseLoginCaptcha"
 | 
					const AccountLoginSecurity = 'AccountLoginSecurity';
 | 
				
			||||||
const UseWartermarkConfigKey = "UseWartermark"
 | 
					const UseLoginCaptchaConfigKey = 'UseLoginCaptcha';
 | 
				
			||||||
 | 
					const UseWartermarkConfigKey = 'UseWartermark';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * 获取系统配置值
 | 
					 * 获取系统配置值
 | 
				
			||||||
@@ -10,8 +11,8 @@ const UseWartermarkConfigKey = "UseWartermark"
 | 
				
			|||||||
 * @param key 配置key
 | 
					 * @param key 配置key
 | 
				
			||||||
 * @returns 配置值
 | 
					 * @returns 配置值
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
export async function getConfigValue(key: string) : Promise<string> {
 | 
					export async function getConfigValue(key: string): Promise<string> {
 | 
				
			||||||
    return await openApi.getConfigValue.request({key}) as string
 | 
					    return (await openApi.getConfigValue({ key })) as string;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
@@ -21,12 +22,25 @@ export async function getConfigValue(key: string) : Promise<string> {
 | 
				
			|||||||
 * @param defaultValue 默认值
 | 
					 * @param defaultValue 默认值
 | 
				
			||||||
 * @returns 是否为ture,1: true;其他: false
 | 
					 * @returns 是否为ture,1: true;其他: false
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
export async function getBoolConfigValue(key :string, defaultValue :boolean) : Promise<boolean> {
 | 
					export async function getBoolConfigValue(key: string, defaultValue: boolean): Promise<boolean> {
 | 
				
			||||||
    const value = await getConfigValue(key)
 | 
					    const value = await getConfigValue(key);
 | 
				
			||||||
 | 
					    return convertBool(value, defaultValue);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * 获取账号登录安全配置
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @returns
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					export async function getAccountLoginSecurity(): Promise<any> {
 | 
				
			||||||
 | 
					    const value = await getConfigValue(AccountLoginSecurity);
 | 
				
			||||||
    if (!value) {
 | 
					    if (!value) {
 | 
				
			||||||
        return defaultValue;
 | 
					        return null;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    return value == "1";
 | 
					    const jsonValue = JSON.parse(value);
 | 
				
			||||||
 | 
					    jsonValue.useCaptcha = convertBool(jsonValue.useCaptcha, true);
 | 
				
			||||||
 | 
					    jsonValue.useOtp = convertBool(jsonValue.useOtp, true);
 | 
				
			||||||
 | 
					    return jsonValue;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
@@ -34,8 +48,8 @@ export async function getBoolConfigValue(key :string, defaultValue :boolean) : P
 | 
				
			|||||||
 *
 | 
					 *
 | 
				
			||||||
 * @returns
 | 
					 * @returns
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
export async function useLoginCaptcha() : Promise<boolean> {
 | 
					export async function useLoginCaptcha(): Promise<boolean> {
 | 
				
			||||||
    return await getBoolConfigValue(UseLoginCaptchaConfigKey, true)
 | 
					    return await getBoolConfigValue(UseLoginCaptchaConfigKey, true);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
@@ -43,6 +57,24 @@ export async function useLoginCaptcha() : Promise<boolean> {
 | 
				
			|||||||
 *
 | 
					 *
 | 
				
			||||||
 * @returns
 | 
					 * @returns
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
 export async function useWartermark() : Promise<boolean> {
 | 
					export async function useWartermark(): Promise<boolean> {
 | 
				
			||||||
    return await getBoolConfigValue(UseWartermarkConfigKey, true)
 | 
					    return await getBoolConfigValue(UseWartermarkConfigKey, true);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function convertBool(value: string, defaultValue: boolean) {
 | 
				
			||||||
 | 
					    if (!value) {
 | 
				
			||||||
 | 
					        return defaultValue;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    return value == '1' || value == 'true';
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * 获取LDAP登录配置
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @returns
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					export async function getLdapEnabled(): Promise<any> {
 | 
				
			||||||
 | 
					    const value = await openApi.getLdapEnabled();
 | 
				
			||||||
 | 
					    return convertBool(value, false);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,27 +1,27 @@
 | 
				
			|||||||
export function dateFormat2(fmt: string, date: Date) {
 | 
					export function dateFormat2(fmt: string, date: Date) {
 | 
				
			||||||
    let ret;
 | 
					    let ret;
 | 
				
			||||||
    const opt = {
 | 
					    const opt = {
 | 
				
			||||||
        "y+": date.getFullYear().toString(),        // 年
 | 
					        'y+': date.getFullYear().toString(), // 年
 | 
				
			||||||
        "M+": (date.getMonth() + 1).toString(),     // 月
 | 
					        'M+': (date.getMonth() + 1).toString(), // 月
 | 
				
			||||||
        "d+": date.getDate().toString(),            // 日
 | 
					        'd+': date.getDate().toString(), // 日
 | 
				
			||||||
        "H+": date.getHours().toString(),           // 时
 | 
					        'H+': date.getHours().toString(), // 时
 | 
				
			||||||
        "m+": date.getMinutes().toString(),         // 分
 | 
					        'm+': date.getMinutes().toString(), // 分
 | 
				
			||||||
        "s+": date.getSeconds().toString()          // 秒
 | 
					        's+': date.getSeconds().toString(), // 秒
 | 
				
			||||||
        // 有其他格式化字符需求可以继续添加,必须转化成字符串
 | 
					        // 有其他格式化字符需求可以继续添加,必须转化成字符串
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
    for (const k in opt) {
 | 
					    for (const k in opt) {
 | 
				
			||||||
        ret = new RegExp("(" + k + ")").exec(fmt);
 | 
					        ret = new RegExp('(' + k + ')').exec(fmt);
 | 
				
			||||||
        if (ret) {
 | 
					        if (ret) {
 | 
				
			||||||
            fmt = fmt.replace(ret[1], (ret[1].length == 1) ? (opt[k]) : (opt[k].padStart(ret[1].length, "0")))
 | 
					            fmt = fmt.replace(ret[1], ret[1].length == 1 ? opt[k] : opt[k].padStart(ret[1].length, '0'));
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    return fmt;
 | 
					    return fmt;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function dateStrFormat(fmt: string, dateStr: string) {
 | 
					export function dateStrFormat(fmt: string, dateStr: string) {
 | 
				
			||||||
    return dateFormat2(fmt, new Date(dateStr))
 | 
					    return dateFormat2(fmt, new Date(dateStr));
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function dateFormat(dateStr: string) {
 | 
					export function dateFormat(dateStr: string) {
 | 
				
			||||||
    return dateFormat2('yyyy-MM-dd HH:mm:ss',new Date(dateStr))
 | 
					    return dateFormat2('yyyy-MM-dd HH:mm:ss', new Date(dateStr));
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -6,21 +6,25 @@ export function exportCsv(filename: string, columns: string[], datas: []) {
 | 
				
			|||||||
        let dataValueArr: any = [];
 | 
					        let dataValueArr: any = [];
 | 
				
			||||||
        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) {
 | 
				
			||||||
 | 
					                dataValueArr.push('');
 | 
				
			||||||
 | 
					                continue;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            if (typeof val == 'string' && val) {
 | 
					            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);
 | 
					                dataValueArr.push(val + '\t');
 | 
				
			||||||
            } else {
 | 
					            } else {
 | 
				
			||||||
                dataValueArr.push(val);
 | 
					                dataValueArr.push(val + '\t');
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        cvsData.push(dataValueArr);
 | 
					        cvsData.push(dataValueArr);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@@ -29,7 +33,7 @@ export function exportCsv(filename: string, columns: string[], datas: []) {
 | 
				
			|||||||
    let link = document.createElement('a');
 | 
					    let link = document.createElement('a');
 | 
				
			||||||
    let exportContent = '\uFEFF';
 | 
					    let exportContent = '\uFEFF';
 | 
				
			||||||
    let blob = new Blob([exportContent + csvString], {
 | 
					    let blob = new Blob([exportContent + csvString], {
 | 
				
			||||||
        type: 'text/plain;charset=utrf-8',
 | 
					        type: 'text/plain;charset=utf-8',
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
    link.id = 'download-csv';
 | 
					    link.id = 'download-csv';
 | 
				
			||||||
    link.setAttribute('href', URL.createObjectURL(blob));
 | 
					    link.setAttribute('href', URL.createObjectURL(blob));
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -116,8 +116,7 @@ export function formatDate(date: Date, format: string) {
 | 
				
			|||||||
        '3': '三',
 | 
					        '3': '三',
 | 
				
			||||||
        '4': '四',
 | 
					        '4': '四',
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
    if (/(W+)/.test(format))
 | 
					    if (/(W+)/.test(format)) format = format.replace(RegExp.$1, RegExp.$1.length > 1 ? (RegExp.$1.length > 2 ? '星期' + week[we] : '周' + week[we]) : week[we]);
 | 
				
			||||||
        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]);
 | 
					    if (/(Q+)/.test(format)) format = format.replace(RegExp.$1, RegExp.$1.length == 4 ? '第' + quarter[qut] + '季度' : quarter[qut]);
 | 
				
			||||||
    for (let k in opt) {
 | 
					    for (let k in opt) {
 | 
				
			||||||
        let r = new RegExp('(' + k + ')').exec(format);
 | 
					        let r = new RegExp('(' + k + ')').exec(format);
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,5 +1,5 @@
 | 
				
			|||||||
import { nextTick } from 'vue';
 | 
					import { nextTick } from 'vue';
 | 
				
			||||||
import loadingCss from "@/theme/loading.scss?inline"
 | 
					import loadingCss from '@/theme/loading.scss?inline';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// 定义方法
 | 
					// 定义方法
 | 
				
			||||||
export const NextLoading = {
 | 
					export const NextLoading = {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,7 +1,5 @@
 | 
				
			|||||||
// 字体图标 url
 | 
					// 字体图标 url
 | 
				
			||||||
const cssCdnUrlList: Array<string> = [
 | 
					const cssCdnUrlList: Array<string> = [];
 | 
				
			||||||
	
 | 
					 | 
				
			||||||
];
 | 
					 | 
				
			||||||
// 第三方 js url
 | 
					// 第三方 js url
 | 
				
			||||||
const jsCdnUrlList: Array<string> = [];
 | 
					const jsCdnUrlList: Array<string> = [];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -36,21 +36,19 @@ export function clearSession() {
 | 
				
			|||||||
    window.sessionStorage.clear();
 | 
					    window.sessionStorage.clear();
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export function getUserInfo4Session() {
 | 
					export function getUserInfo4Session() {
 | 
				
			||||||
    return getSession("userInfo")
 | 
					    return getSession('userInfo');
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function setUserInfo2Session(userinfo: any) {
 | 
					export function setUserInfo2Session(userinfo: any) {
 | 
				
			||||||
    setSession("userInfo", userinfo)
 | 
					    setSession('userInfo', userinfo);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// 获取是否开启水印
 | 
					// 获取是否开启水印
 | 
				
			||||||
export function getUseWatermark4Session() {
 | 
					export function getUseWatermark4Session() {
 | 
				
			||||||
    return getSession("useWatermark")
 | 
					    return getSession('useWatermark');
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function setUseWatermark2Session(useWatermark: boolean) {
 | 
					export function setUseWatermark2Session(useWatermark: boolean) {
 | 
				
			||||||
    setSession("useWatermark", useWatermark)
 | 
					    setSession('useWatermark', useWatermark);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -12,7 +12,7 @@ export function templateResolve(template: string, param: any) {
 | 
				
			|||||||
        if (value != null || value != undefined) {
 | 
					        if (value != null || value != undefined) {
 | 
				
			||||||
            return value;
 | 
					            return value;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        return "";
 | 
					        return '';
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -21,11 +21,34 @@ export function letterAvatar(name: string, size = 60, color = '') {
 | 
				
			|||||||
    name = name || '';
 | 
					    name = name || '';
 | 
				
			||||||
    size = size || 60;
 | 
					    size = size || 60;
 | 
				
			||||||
    var colours = [
 | 
					    var colours = [
 | 
				
			||||||
        "#1abc9c", "#2ecc71", "#3498db", "#9b59b6", "#34495e", "#16a085", "#27ae60", "#2980b9", "#8e44ad", "#2c3e50",
 | 
					            '#1abc9c',
 | 
				
			||||||
        "#f1c40f", "#e67e22", "#e74c3c", "#00bcd4", "#95a5a6", "#f39c12", "#d35400", "#c0392b", "#bdc3c7", "#7f8c8d"
 | 
					            '#2ecc71',
 | 
				
			||||||
 | 
					            '#3498db',
 | 
				
			||||||
 | 
					            '#9b59b6',
 | 
				
			||||||
 | 
					            '#34495e',
 | 
				
			||||||
 | 
					            '#16a085',
 | 
				
			||||||
 | 
					            '#27ae60',
 | 
				
			||||||
 | 
					            '#2980b9',
 | 
				
			||||||
 | 
					            '#8e44ad',
 | 
				
			||||||
 | 
					            '#2c3e50',
 | 
				
			||||||
 | 
					            '#f1c40f',
 | 
				
			||||||
 | 
					            '#e67e22',
 | 
				
			||||||
 | 
					            '#e74c3c',
 | 
				
			||||||
 | 
					            '#00bcd4',
 | 
				
			||||||
 | 
					            '#95a5a6',
 | 
				
			||||||
 | 
					            '#f39c12',
 | 
				
			||||||
 | 
					            '#d35400',
 | 
				
			||||||
 | 
					            '#c0392b',
 | 
				
			||||||
 | 
					            '#bdc3c7',
 | 
				
			||||||
 | 
					            '#7f8c8d',
 | 
				
			||||||
        ],
 | 
					        ],
 | 
				
			||||||
        nameSplit = String(name).split(' '),
 | 
					        nameSplit = String(name).split(' '),
 | 
				
			||||||
        initials, charIndex, colourIndex, canvas, context, dataURI;
 | 
					        initials,
 | 
				
			||||||
 | 
					        charIndex,
 | 
				
			||||||
 | 
					        colourIndex,
 | 
				
			||||||
 | 
					        canvas,
 | 
				
			||||||
 | 
					        context,
 | 
				
			||||||
 | 
					        dataURI;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (nameSplit.length == 1) {
 | 
					    if (nameSplit.length == 1) {
 | 
				
			||||||
        initials = nameSplit[0] ? nameSplit[0].charAt(0) : '?';
 | 
					        initials = nameSplit[0] ? nameSplit[0].charAt(0) : '?';
 | 
				
			||||||
@@ -33,23 +56,76 @@ export function letterAvatar(name: string, size = 60, color = '') {
 | 
				
			|||||||
        initials = nameSplit[0].charAt(0) + nameSplit[1].charAt(0);
 | 
					        initials = nameSplit[0].charAt(0) + nameSplit[1].charAt(0);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    if (window.devicePixelRatio) {
 | 
					    if (window.devicePixelRatio) {
 | 
				
			||||||
        size = (size * window.devicePixelRatio);
 | 
					        size = size * window.devicePixelRatio;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    initials = initials.toLocaleUpperCase()
 | 
					    initials = initials.toLocaleUpperCase();
 | 
				
			||||||
    charIndex = (initials == '?' ? 72 : initials.charCodeAt(0)) - 64;
 | 
					    charIndex = (initials == '?' ? 72 : initials.charCodeAt(0)) - 64;
 | 
				
			||||||
    colourIndex = charIndex % 20;
 | 
					    colourIndex = charIndex % 20;
 | 
				
			||||||
    canvas = document.createElement('canvas');
 | 
					    canvas = document.createElement('canvas');
 | 
				
			||||||
    canvas.width = size;
 | 
					    canvas.width = size;
 | 
				
			||||||
    canvas.height = size;
 | 
					    canvas.height = size;
 | 
				
			||||||
    context = canvas.getContext("2d") as any;
 | 
					    context = canvas.getContext('2d') as any;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    context.fillStyle = color ? color : colours[colourIndex - 1];
 | 
					    context.fillStyle = color ? color : colours[colourIndex - 1];
 | 
				
			||||||
    context.fillRect(0, 0, canvas.width, canvas.height);
 | 
					    context.fillRect(0, 0, canvas.width, canvas.height);
 | 
				
			||||||
    context.font = Math.round(canvas.width / 2) + "px 'Microsoft Yahei'";
 | 
					    context.font = Math.round(canvas.width / 2) + "px 'Microsoft Yahei'";
 | 
				
			||||||
    context.textAlign = "center";
 | 
					    context.textAlign = 'center';
 | 
				
			||||||
    context.fillStyle = "#FFF";
 | 
					    context.fillStyle = '#FFF';
 | 
				
			||||||
    context.fillText(initials, size / 2, size / 1.5);
 | 
					    context.fillText(initials, size / 2, size / 1.5);
 | 
				
			||||||
    dataURI = canvas.toDataURL();
 | 
					    dataURI = canvas.toDataURL();
 | 
				
			||||||
    canvas = null;
 | 
					    canvas = null;
 | 
				
			||||||
    return dataURI;
 | 
					    return dataURI;
 | 
				
			||||||
};
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * 计算文本所占用的宽度(px) -> 该种方式较为准确
 | 
				
			||||||
 | 
					 * 使用span标签包裹内容,然后计算span的宽度 width: px
 | 
				
			||||||
 | 
					 * @param str
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					export function getTextWidth(str: string) {
 | 
				
			||||||
 | 
					    let width = 0;
 | 
				
			||||||
 | 
					    let html = document.createElement('span');
 | 
				
			||||||
 | 
					    html.innerText = str;
 | 
				
			||||||
 | 
					    html.className = 'getTextWidth';
 | 
				
			||||||
 | 
					    document?.querySelector('body')?.appendChild(html);
 | 
				
			||||||
 | 
					    width = (document?.querySelector('.getTextWidth') as any).offsetWidth;
 | 
				
			||||||
 | 
					    document?.querySelector('.getTextWidth')?.remove();
 | 
				
			||||||
 | 
					    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;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,6 +1,6 @@
 | 
				
			|||||||
import { nextTick } from 'vue';
 | 
					import { nextTick } from 'vue';
 | 
				
			||||||
import * as svg from '@element-plus/icons-vue';
 | 
					import * as svg from '@element-plus/icons-vue';
 | 
				
			||||||
import iconfontJson from '@/assets/iconfont/iconfont.json'
 | 
					import iconfontJson from '@/assets/iconfont/iconfont.json';
 | 
				
			||||||
import SvgIcon from '@/components/svgIcon/index.vue';
 | 
					import SvgIcon from '@/components/svgIcon/index.vue';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
@@ -52,7 +52,7 @@ const getLocalAliIconfont = () => {
 | 
				
			|||||||
            resolve(iconfontJson.glyphs.map((x: any) => prefix + x.font_class));
 | 
					            resolve(iconfontJson.glyphs.map((x: any) => prefix + x.font_class));
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
}
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// 初始化获取 css 样式,获取 element plus 自带图标
 | 
					// 初始化获取 css 样式,获取 element plus 自带图标
 | 
				
			||||||
const elementPlusIconfont = () => {
 | 
					const elementPlusIconfont = () => {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -163,8 +163,7 @@ export function verifyPasswordStrength(val: string) {
 | 
				
			|||||||
    // 中:字母+数字,字母+特殊字符,数字+特殊字符
 | 
					    // 中:字母+数字,字母+特殊字符,数字+特殊字符
 | 
				
			||||||
    if (/^(?![a-zA-z]+$)(?!\d+$)(?![!@#$%^&\.*]+$)[a-zA-Z\d!@#$%^&\.*]{6,16}$/.test(val)) v = '中';
 | 
					    if (/^(?![a-zA-z]+$)(?!\d+$)(?![!@#$%^&\.*]+$)[a-zA-Z\d!@#$%^&\.*]{6,16}$/.test(val)) v = '中';
 | 
				
			||||||
    // 强:字母+数字+特殊字符
 | 
					    // 强:字母+数字+特殊字符
 | 
				
			||||||
    if (/^(?![a-zA-z]+$)(?!\d+$)(?![!@#$%^&\.*]+$)(?![a-zA-z\d]+$)(?![a-zA-z!@#$%^&\.*]+$)(?![\d!@#$%^&\.*]+$)[a-zA-Z\d!@#$%^&\.*]{6,16}$/.test(val))
 | 
					    if (/^(?![a-zA-z]+$)(?!\d+$)(?![!@#$%^&\.*]+$)(?![a-zA-z\d]+$)(?![a-zA-z!@#$%^&\.*]+$)(?![\d!@#$%^&\.*]+$)[a-zA-Z\d!@#$%^&\.*]{6,16}$/.test(val)) v = '强';
 | 
				
			||||||
        v = '强';
 | 
					 | 
				
			||||||
    // 返回结果
 | 
					    // 返回结果
 | 
				
			||||||
    return v;
 | 
					    return v;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -172,11 +171,7 @@ export function verifyPasswordStrength(val: string) {
 | 
				
			|||||||
// IP地址
 | 
					// IP地址
 | 
				
			||||||
export function verifyIPAddress(val: string) {
 | 
					export function verifyIPAddress(val: string) {
 | 
				
			||||||
    // false: IP地址不正确
 | 
					    // false: IP地址不正确
 | 
				
			||||||
    if (
 | 
					    if (!/^(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])$/.test(val))
 | 
				
			||||||
        !/^(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])$/.test(
 | 
					 | 
				
			||||||
            val
 | 
					 | 
				
			||||||
        )
 | 
					 | 
				
			||||||
    )
 | 
					 | 
				
			||||||
        return false;
 | 
					        return false;
 | 
				
			||||||
    // true: IP地址正确
 | 
					    // true: IP地址正确
 | 
				
			||||||
    else return true;
 | 
					    else return true;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,5 +1,5 @@
 | 
				
			|||||||
import { getUseWatermark4Session, getUserInfo4Session } from '@/common/utils/storage.ts';
 | 
					import { getUseWatermark4Session, getUserInfo4Session } from '@/common/utils/storage';
 | 
				
			||||||
import { dateFormat2 } from '@/common/utils/date.ts'
 | 
					import { dateFormat2 } from '@/common/utils/date';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// 页面添加水印效果
 | 
					// 页面添加水印效果
 | 
				
			||||||
const setWatermark = (str: any) => {
 | 
					const setWatermark = (str: any) => {
 | 
				
			||||||
@@ -44,17 +44,17 @@ function del() {
 | 
				
			|||||||
const watermark = {
 | 
					const watermark = {
 | 
				
			||||||
    use: () => {
 | 
					    use: () => {
 | 
				
			||||||
        setTimeout(() => {
 | 
					        setTimeout(() => {
 | 
				
			||||||
            const userinfo = getUserInfo4Session()
 | 
					            const userinfo = getUserInfo4Session();
 | 
				
			||||||
            if (userinfo && getUseWatermark4Session()) {
 | 
					            if (userinfo && getUseWatermark4Session()) {
 | 
				
			||||||
                set(`${userinfo.username} ${dateFormat2('yyyy-MM-dd HH:mm:ss', new Date())}`)
 | 
					                set(`${userinfo.username} ${dateFormat2('yyyy-MM-dd HH:mm:ss', new Date())}`);
 | 
				
			||||||
            } else {
 | 
					            } else {
 | 
				
			||||||
                del();
 | 
					                del();
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }, 1500)
 | 
					        }, 1500);
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    // 设置水印
 | 
					    // 设置水印
 | 
				
			||||||
    set: (str: any) => {
 | 
					    set: (str: any) => {
 | 
				
			||||||
        set(str)
 | 
					        set(str);
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    // 删除水印
 | 
					    // 删除水印
 | 
				
			||||||
    del: () => {
 | 
					    del: () => {
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										25
									
								
								mayfly_go_web/src/components/auth/auth.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								mayfly_go_web/src/components/auth/auth.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,25 @@
 | 
				
			|||||||
 | 
					import { useUserInfo } from '@/store/userInfo';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * 判断当前用户是否拥有指定权限
 | 
				
			||||||
 | 
					 * @param code 权限code
 | 
				
			||||||
 | 
					 * @returns
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					export function hasPerm(code: string) {
 | 
				
			||||||
 | 
					    return useUserInfo().userInfo.permissions.some((v: any) => v === code);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * 判断用户是否拥有权限对象里对应的code
 | 
				
			||||||
 | 
					 * @param perms { save: "xxx:save"}
 | 
				
			||||||
 | 
					 * @returns {"xxx:save": true}  key->permission code
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					export function hasPerms(permCodes: any[]) {
 | 
				
			||||||
 | 
					    const res = {};
 | 
				
			||||||
 | 
					    for (let permCode of permCodes) {
 | 
				
			||||||
 | 
					        if (hasPerm(permCode)) {
 | 
				
			||||||
 | 
					            res[permCode] = true;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    return res;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -1,12 +1,24 @@
 | 
				
			|||||||
<template>
 | 
					<template>
 | 
				
			||||||
    <transition name="el-zoom-in-center">
 | 
					    <transition name="el-zoom-in-center">
 | 
				
			||||||
        <div aria-hidden="true" class="el-dropdown__popper el-popper is-light is-pure custom-contextmenu" role="tooltip"
 | 
					        <div
 | 
				
			||||||
            data-popper-placement="bottom" :style="`top: ${dropdowns.y + 5}px;left: ${dropdowns.x}px;`" :key="Math.random()"
 | 
					            aria-hidden="true"
 | 
				
			||||||
            v-show="state.isShow">
 | 
					            class="el-dropdown__popper el-popper is-light is-pure custom-contextmenu"
 | 
				
			||||||
 | 
					            role="tooltip"
 | 
				
			||||||
 | 
					            data-popper-placement="bottom"
 | 
				
			||||||
 | 
					            :style="`top: ${dropdowns.y + 5}px;left: ${dropdowns.x}px;`"
 | 
				
			||||||
 | 
					            :key="Math.random()"
 | 
				
			||||||
 | 
					            v-show="state.isShow"
 | 
				
			||||||
 | 
					        >
 | 
				
			||||||
            <ul class="el-dropdown-menu">
 | 
					            <ul class="el-dropdown-menu">
 | 
				
			||||||
                <template v-for="(v, k) in state.dropdownList">
 | 
					                <template v-for="(v, k) in state.dropdownList">
 | 
				
			||||||
                    <li class="el-dropdown-menu__item" aria-disabled="false" tabindex="-1" :key="k" v-if="!v.affix"
 | 
					                    <li
 | 
				
			||||||
                        @click="onCurrentContextmenuClick(v.contextMenuClickId)">
 | 
					                        class="el-dropdown-menu__item"
 | 
				
			||||||
 | 
					                        aria-disabled="false"
 | 
				
			||||||
 | 
					                        tabindex="-1"
 | 
				
			||||||
 | 
					                        :key="k"
 | 
				
			||||||
 | 
					                        v-if="!v.affix"
 | 
				
			||||||
 | 
					                        @click="onCurrentContextmenuClick(v.contextMenuClickId)"
 | 
				
			||||||
 | 
					                    >
 | 
				
			||||||
                        <SvgIcon :name="v.icon" />
 | 
					                        <SvgIcon :name="v.icon" />
 | 
				
			||||||
                        <span>{{ v.txt }}</span>
 | 
					                        <span>{{ v.txt }}</span>
 | 
				
			||||||
                    </li>
 | 
					                    </li>
 | 
				
			||||||
@@ -43,9 +55,7 @@ const emit = defineEmits(['currentContextmenuClick']);
 | 
				
			|||||||
// 定义变量内容
 | 
					// 定义变量内容
 | 
				
			||||||
const state = reactive({
 | 
					const state = reactive({
 | 
				
			||||||
    isShow: false,
 | 
					    isShow: false,
 | 
				
			||||||
    dropdownList: [
 | 
					    dropdownList: [{ contextMenuClickId: 0, txt: '刷新', affix: false, icon: 'RefreshRight' }],
 | 
				
			||||||
        { contextMenuClickId: 0, txt: '刷新', affix: false, icon: 'RefreshRight' },
 | 
					 | 
				
			||||||
    ],
 | 
					 | 
				
			||||||
    item: {} as any,
 | 
					    item: {} as any,
 | 
				
			||||||
    arrowLeft: 10,
 | 
					    arrowLeft: 10,
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
@@ -100,7 +110,7 @@ watch(
 | 
				
			|||||||
watch(
 | 
					watch(
 | 
				
			||||||
    () => props.items,
 | 
					    () => props.items,
 | 
				
			||||||
    (x: any) => {
 | 
					    (x: any) => {
 | 
				
			||||||
        state.dropdownList = x
 | 
					        state.dropdownList = x;
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        deep: true,
 | 
					        deep: true,
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										60
									
								
								mayfly_go_web/src/components/enumtag/EnumTag.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										60
									
								
								mayfly_go_web/src/components/enumtag/EnumTag.vue
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,60 @@
 | 
				
			|||||||
 | 
					<template>
 | 
				
			||||||
 | 
					    <el-tag v-bind="$attrs" :type="type" :color="color" effect="plain">{{ enumLabel }}</el-tag>
 | 
				
			||||||
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<script lang="ts" setup>
 | 
				
			||||||
 | 
					import { toRefs, watch, reactive, onMounted } from 'vue';
 | 
				
			||||||
 | 
					import EnumValue from '@/common/Enum';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const props = defineProps({
 | 
				
			||||||
 | 
					    enums: {
 | 
				
			||||||
 | 
					        type: Object, // 需要为EnumValue类型
 | 
				
			||||||
 | 
					        required: true,
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    value: {
 | 
				
			||||||
 | 
					        type: [Object, String, Number],
 | 
				
			||||||
 | 
					        required: true,
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const defaultType = 'primary';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const state = reactive({
 | 
				
			||||||
 | 
					    type: defaultType,
 | 
				
			||||||
 | 
					    color: '',
 | 
				
			||||||
 | 
					    enumLabel: '',
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const { type, color, enumLabel } = toRefs(state);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// 监听该值是否改变,改变则需要将其枚举值与标签进行调整
 | 
				
			||||||
 | 
					watch(
 | 
				
			||||||
 | 
					    () => props.value,
 | 
				
			||||||
 | 
					    (newValue: any) => {
 | 
				
			||||||
 | 
					        convert(newValue);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					onMounted(() => {
 | 
				
			||||||
 | 
					    convert(props.value);
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const convert = (value: any) => {
 | 
				
			||||||
 | 
					    const enumValue = EnumValue.getEnumByValue(Object.values(props.enums as any) as any, value) as any;
 | 
				
			||||||
 | 
					    if (!enumValue) {
 | 
				
			||||||
 | 
					        state.enumLabel = '-';
 | 
				
			||||||
 | 
					        state.type = 'danger';
 | 
				
			||||||
 | 
					        state.color = '';
 | 
				
			||||||
 | 
					        return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    state.enumLabel = enumValue?.label || '';
 | 
				
			||||||
 | 
					    if (enumValue.tag) {
 | 
				
			||||||
 | 
					        state.color = enumValue.tag.color;
 | 
				
			||||||
 | 
					        state.type = enumValue.tag.type;
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					        state.type = defaultType;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					</script>
 | 
				
			||||||
 | 
					<style scoped lang="scss"></style>
 | 
				
			||||||
@@ -12,10 +12,7 @@
 | 
				
			|||||||
            @blur="onIconBlur"
 | 
					            @blur="onIconBlur"
 | 
				
			||||||
        >
 | 
					        >
 | 
				
			||||||
            <template #prepend>
 | 
					            <template #prepend>
 | 
				
			||||||
				<SvgIcon
 | 
					                <SvgIcon :name="state.fontIconPrefix === '' ? prepend : state.fontIconPrefix" class="font14" />
 | 
				
			||||||
					:name="state.fontIconPrefix === '' ? prepend : state.fontIconPrefix"
 | 
					 | 
				
			||||||
					class="font14"
 | 
					 | 
				
			||||||
				/>
 | 
					 | 
				
			||||||
            </template>
 | 
					            </template>
 | 
				
			||||||
        </el-input>
 | 
					        </el-input>
 | 
				
			||||||
        <el-popover
 | 
					        <el-popover
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,5 +1,5 @@
 | 
				
			|||||||
<template>
 | 
					<template>
 | 
				
			||||||
    <div class="monaco-editor" style="border: 1px solid #ccc;">
 | 
					    <div class="monaco-editor" style="border: 1px solid var(--el-border-color-light, #ebeef5)">
 | 
				
			||||||
        <div class="monaco-editor-content" ref="monacoTextarea" :style="{ height: height }"></div>
 | 
					        <div class="monaco-editor-content" ref="monacoTextarea" :style="{ height: height }"></div>
 | 
				
			||||||
        <el-select v-if="canChangeMode" class="code-mode-select" v-model="languageMode" @change="changeLanguage">
 | 
					        <el-select v-if="canChangeMode" class="code-mode-select" v-model="languageMode" @change="changeLanguage">
 | 
				
			||||||
            <el-option v-for="mode in languageArr" :key="mode.value" :label="mode.label" :value="mode.value"> </el-option>
 | 
					            <el-option v-for="mode in languageArr" :key="mode.value" :label="mode.label" :value="mode.value"> </el-option>
 | 
				
			||||||
@@ -9,10 +9,33 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
<script lang="ts" setup>
 | 
					<script lang="ts" setup>
 | 
				
			||||||
import { ref, watch, toRefs, reactive, onMounted, onBeforeUnmount } from 'vue';
 | 
					import { ref, watch, toRefs, reactive, onMounted, onBeforeUnmount } from 'vue';
 | 
				
			||||||
import EditorWorker from 'monaco-editor/esm/vs/editor/editor.worker.js?worker';
 | 
					// import * as monaco from 'monaco-editor';
 | 
				
			||||||
import * as monaco from 'monaco-editor';
 | 
					import * as monaco from 'monaco-editor/esm/vs/editor/editor.api';
 | 
				
			||||||
 | 
					// 相关语言
 | 
				
			||||||
 | 
					import 'monaco-editor/esm/vs/basic-languages/shell/shell.contribution.js';
 | 
				
			||||||
 | 
					import 'monaco-editor/esm/vs/basic-languages/yaml/yaml.contribution.js';
 | 
				
			||||||
 | 
					import 'monaco-editor/esm/vs/basic-languages/dockerfile/dockerfile.contribution.js';
 | 
				
			||||||
 | 
					import 'monaco-editor/esm/vs/basic-languages/javascript/javascript.contribution.js';
 | 
				
			||||||
 | 
					import 'monaco-editor/esm/vs/basic-languages/html/html.contribution.js';
 | 
				
			||||||
 | 
					import 'monaco-editor/esm/vs/basic-languages/css/css.contribution.js';
 | 
				
			||||||
 | 
					import 'monaco-editor/esm/vs/basic-languages/python/python.contribution.js';
 | 
				
			||||||
 | 
					import 'monaco-editor/esm/vs/basic-languages/markdown/markdown.contribution.js';
 | 
				
			||||||
 | 
					import 'monaco-editor/esm/vs/basic-languages/java/java.contribution.js';
 | 
				
			||||||
 | 
					import 'monaco-editor/esm/vs/basic-languages/sql/sql.contribution.js';
 | 
				
			||||||
 | 
					import 'monaco-editor/esm/vs/language/json/monaco.contribution';
 | 
				
			||||||
 | 
					// 右键菜单
 | 
				
			||||||
 | 
					import 'monaco-editor/esm/vs/editor/contrib/contextmenu/browser/contextmenu.js';
 | 
				
			||||||
 | 
					import 'monaco-editor/esm/vs/editor/contrib/caretOperations/browser/caretOperations.js';
 | 
				
			||||||
 | 
					import 'monaco-editor/esm/vs/editor/contrib/clipboard//browser/clipboard.js';
 | 
				
			||||||
 | 
					import 'monaco-editor/esm/vs/editor/contrib/find/browser/findController.js';
 | 
				
			||||||
 | 
					import 'monaco-editor/esm/vs/editor/contrib/format//browser/formatActions.js';
 | 
				
			||||||
 | 
					// 提示
 | 
				
			||||||
 | 
					import 'monaco-editor/esm/vs/editor/contrib/suggest/browser/suggestController.js';
 | 
				
			||||||
 | 
					import 'monaco-editor/esm/vs/editor/contrib/suggest/browser/suggestInlineCompletions.js';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import { editor, languages } from 'monaco-editor';
 | 
					import { editor, languages } from 'monaco-editor';
 | 
				
			||||||
import JsonWorker from 'monaco-editor/esm/vs/language/json/json.worker?worker'
 | 
					import EditorWorker from 'monaco-editor/esm/vs/editor/editor.worker.js?worker';
 | 
				
			||||||
 | 
					import JsonWorker from 'monaco-editor/esm/vs/language/json/json.worker?worker';
 | 
				
			||||||
// 主题仓库 https://github.com/brijeshb42/monaco-themes
 | 
					// 主题仓库 https://github.com/brijeshb42/monaco-themes
 | 
				
			||||||
// 主题例子 https://editor.bitwiser.in/
 | 
					// 主题例子 https://editor.bitwiser.in/
 | 
				
			||||||
// import Monokai from 'monaco-themes/themes/Monokai.json'
 | 
					// import Monokai from 'monaco-themes/themes/Monokai.json'
 | 
				
			||||||
@@ -21,10 +44,15 @@ import JsonWorker from 'monaco-editor/esm/vs/language/json/json.worker?worker'
 | 
				
			|||||||
// import bop from 'monaco-themes/themes/Birds of Paradise.json'
 | 
					// import bop from 'monaco-themes/themes/Birds of Paradise.json'
 | 
				
			||||||
// import krTheme from 'monaco-themes/themes/krTheme.json'
 | 
					// import krTheme from 'monaco-themes/themes/krTheme.json'
 | 
				
			||||||
// import Dracula from 'monaco-themes/themes/Dracula.json'
 | 
					// import Dracula from 'monaco-themes/themes/Dracula.json'
 | 
				
			||||||
import SolarizedLight from 'monaco-themes/themes/Solarized-light.json'
 | 
					import SolarizedLight from 'monaco-themes/themes/Solarized-light.json';
 | 
				
			||||||
import { language as shellLan } from 'monaco-editor/esm/vs/basic-languages/shell/shell.js';
 | 
					import { language as shellLan } from 'monaco-editor/esm/vs/basic-languages/shell/shell.js';
 | 
				
			||||||
import { ElOption, ElSelect } from 'element-plus';
 | 
					import { ElOption, ElSelect } from 'element-plus';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { storeToRefs } from 'pinia';
 | 
				
			||||||
 | 
					import { useThemeConfig } from '@/store/themeConfig';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const { themeConfig } = storeToRefs(useThemeConfig());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const props = defineProps({
 | 
					const props = defineProps({
 | 
				
			||||||
    modelValue: {
 | 
					    modelValue: {
 | 
				
			||||||
        type: String,
 | 
					        type: String,
 | 
				
			||||||
@@ -49,10 +77,10 @@ const props = defineProps({
 | 
				
			|||||||
        type: Object,
 | 
					        type: Object,
 | 
				
			||||||
        default: null,
 | 
					        default: null,
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
})
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
//定义事件
 | 
					//定义事件
 | 
				
			||||||
const emit = defineEmits(['update:modelValue'])
 | 
					const emit = defineEmits(['update:modelValue']);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const languageArr = [
 | 
					const languageArr = [
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
@@ -109,11 +137,11 @@ const options = {
 | 
				
			|||||||
    language: 'shell',
 | 
					    language: 'shell',
 | 
				
			||||||
    theme: 'SolarizedLight',
 | 
					    theme: 'SolarizedLight',
 | 
				
			||||||
    automaticLayout: true, //自适应宽高布局
 | 
					    automaticLayout: true, //自适应宽高布局
 | 
				
			||||||
    foldingStrategy: 'indentation',//代码可分小段折叠
 | 
					    foldingStrategy: 'indentation', //代码可分小段折叠
 | 
				
			||||||
    roundedSelection: false, // 禁用选择文本背景的圆角
 | 
					    roundedSelection: false, // 禁用选择文本背景的圆角
 | 
				
			||||||
    matchBrackets: 'near',
 | 
					    matchBrackets: 'near',
 | 
				
			||||||
    linkedEditing: true,
 | 
					    linkedEditing: true,
 | 
				
			||||||
    cursorBlinking: 'smooth',// 光标闪烁样式
 | 
					    cursorBlinking: 'smooth', // 光标闪烁样式
 | 
				
			||||||
    mouseWheelZoom: true, // 在按住Ctrl键的同时使用鼠标滚轮时,在编辑器中缩放字体
 | 
					    mouseWheelZoom: true, // 在按住Ctrl键的同时使用鼠标滚轮时,在编辑器中缩放字体
 | 
				
			||||||
    overviewRulerBorder: false, // 不要滚动条的边框
 | 
					    overviewRulerBorder: false, // 不要滚动条的边框
 | 
				
			||||||
    tabSize: 4, // tab 缩进长度
 | 
					    tabSize: 4, // tab 缩进长度
 | 
				
			||||||
@@ -125,15 +153,13 @@ const options = {
 | 
				
			|||||||
    minimap: {
 | 
					    minimap: {
 | 
				
			||||||
        enabled: false, // 不要小地图
 | 
					        enabled: false, // 不要小地图
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
}
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const state = reactive({
 | 
					const state = reactive({
 | 
				
			||||||
    languageMode: 'shell',
 | 
					    languageMode: 'shell',
 | 
				
			||||||
})
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const {
 | 
					const { languageMode } = toRefs(state);
 | 
				
			||||||
    languageMode,
 | 
					 | 
				
			||||||
} = toRefs(state)
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
onMounted(() => {
 | 
					onMounted(() => {
 | 
				
			||||||
    state.languageMode = props.language;
 | 
					    state.languageMode = props.language;
 | 
				
			||||||
@@ -149,19 +175,33 @@ onBeforeUnmount(() => {
 | 
				
			|||||||
    if (completionItemProvider) {
 | 
					    if (completionItemProvider) {
 | 
				
			||||||
        completionItemProvider.dispose();
 | 
					        completionItemProvider.dispose();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
})
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
watch(() => props.modelValue, (newValue: any) => {
 | 
					watch(
 | 
				
			||||||
 | 
					    () => props.modelValue,
 | 
				
			||||||
 | 
					    (newValue: any) => {
 | 
				
			||||||
        if (!monacoEditorIns.hasTextFocus()) {
 | 
					        if (!monacoEditorIns.hasTextFocus()) {
 | 
				
			||||||
            state.languageMode = props.language;
 | 
					            state.languageMode = props.language;
 | 
				
			||||||
            monacoEditorIns?.setValue(newValue);
 | 
					            monacoEditorIns?.setValue(newValue);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
})
 | 
					    }
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
watch(() => props.language, (newValue: any) => {
 | 
					watch(
 | 
				
			||||||
 | 
					    () => props.language,
 | 
				
			||||||
 | 
					    (newValue: any) => {
 | 
				
			||||||
        changeLanguage(newValue);
 | 
					        changeLanguage(newValue);
 | 
				
			||||||
})
 | 
					    }
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// 监听 themeConfig editorTheme配置文件的变化
 | 
				
			||||||
 | 
					watch(
 | 
				
			||||||
 | 
					    () => themeConfig.value.editorTheme,
 | 
				
			||||||
 | 
					    (val) => {
 | 
				
			||||||
 | 
					        console.log('monaco editor theme change: ', val);
 | 
				
			||||||
 | 
					        monaco?.editor?.setTheme(val);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const monacoTextarea: any = ref(null);
 | 
					const monacoTextarea: any = ref(null);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -171,53 +211,49 @@ let completionItemProvider: any = null;
 | 
				
			|||||||
self.MonacoEnvironment = {
 | 
					self.MonacoEnvironment = {
 | 
				
			||||||
    getWorker(_: any, label: string) {
 | 
					    getWorker(_: any, label: string) {
 | 
				
			||||||
        if (label === 'json') {
 | 
					        if (label === 'json') {
 | 
				
			||||||
            return new JsonWorker()
 | 
					            return new JsonWorker();
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        return new EditorWorker();
 | 
					        return new EditorWorker();
 | 
				
			||||||
    }
 | 
					    },
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const initMonacoEditorIns = () => {
 | 
					const initMonacoEditorIns = () => {
 | 
				
			||||||
    console.log('初始化monaco编辑器')
 | 
					    console.log('初始化monaco编辑器');
 | 
				
			||||||
    // options参数参考 https://microsoft.github.io/monaco-editor/api/interfaces/monaco.editor.IStandaloneEditorConstructionOptions.html#language
 | 
					    // options参数参考 https://microsoft.github.io/monaco-editor/api/interfaces/monaco.editor.IStandaloneEditorConstructionOptions.html#language
 | 
				
			||||||
    // 初始化一些主题
 | 
					    // 初始化一些主题
 | 
				
			||||||
    monaco.editor.defineTheme('SolarizedLight', SolarizedLight);
 | 
					    monaco.editor.defineTheme('SolarizedLight', SolarizedLight);
 | 
				
			||||||
    options.language = state.languageMode;
 | 
					    options.language = state.languageMode;
 | 
				
			||||||
    // 从localStorage中获取,通过store可能存在父子组件都使用store报错
 | 
					    options.theme = themeConfig.value.editorTheme;
 | 
				
			||||||
    options.theme = JSON.parse(localStorage.getItem('themeConfig') as string).editorTheme || 'vs';
 | 
					 | 
				
			||||||
    monacoEditorIns = monaco.editor.create(monacoTextarea.value, Object.assign(options, props.options as any));
 | 
					    monacoEditorIns = monaco.editor.create(monacoTextarea.value, Object.assign(options, props.options as any));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // 监听内容改变,双向绑定
 | 
					    // 监听内容改变,双向绑定
 | 
				
			||||||
    monacoEditorIns.onDidChangeModelContent(() => {
 | 
					    monacoEditorIns.onDidChangeModelContent(() => {
 | 
				
			||||||
        emit('update:modelValue', monacoEditorIns.getModel()?.getValue());
 | 
					        emit('update:modelValue', monacoEditorIns.getModel()?.getValue());
 | 
				
			||||||
    })
 | 
					    });
 | 
				
			||||||
 | 
					 | 
				
			||||||
    // 动态设置主题
 | 
					 | 
				
			||||||
    // monaco.editor.setTheme('hc-black');
 | 
					 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const changeLanguage = (value: any) => {
 | 
					const changeLanguage = (value: any) => {
 | 
				
			||||||
    console.log('change lan');
 | 
					    console.log('change lan');
 | 
				
			||||||
    // 获取当前的文档模型
 | 
					    // 获取当前的文档模型
 | 
				
			||||||
    let oldModel = monacoEditorIns.getModel()
 | 
					    let oldModel = monacoEditorIns.getModel();
 | 
				
			||||||
    if (!oldModel) {
 | 
					    if (!oldModel) {
 | 
				
			||||||
        return;
 | 
					        return;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    // 创建一个新的文档模型
 | 
					    // 创建一个新的文档模型
 | 
				
			||||||
    let newModel = monaco.editor.createModel(oldModel.getValue(), value)
 | 
					    let newModel = monaco.editor.createModel(oldModel.getValue(), value);
 | 
				
			||||||
    // 设置成新的
 | 
					    // 设置成新的
 | 
				
			||||||
    monacoEditorIns.setModel(newModel)
 | 
					    monacoEditorIns.setModel(newModel);
 | 
				
			||||||
    // 销毁旧的模型
 | 
					    // 销毁旧的模型
 | 
				
			||||||
    if (oldModel) {
 | 
					    if (oldModel) {
 | 
				
			||||||
        oldModel.dispose()
 | 
					        oldModel.dispose();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    registerCompletionItemProvider();
 | 
					    registerCompletionItemProvider();
 | 
				
			||||||
}
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const setEditorValue = (value: any) => {
 | 
					const setEditorValue = (value: any) => {
 | 
				
			||||||
    monacoEditorIns.getModel()?.setValue(value)
 | 
					    monacoEditorIns.getModel()?.setValue(value);
 | 
				
			||||||
}
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * 注册联想补全提示
 | 
					 * 注册联想补全提示
 | 
				
			||||||
@@ -227,44 +263,43 @@ const registerCompletionItemProvider = () => {
 | 
				
			|||||||
        completionItemProvider.dispose();
 | 
					        completionItemProvider.dispose();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    if (state.languageMode == 'shell') {
 | 
					    if (state.languageMode == 'shell') {
 | 
				
			||||||
        registeShell()
 | 
					        registeShell();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const registeShell = () => {
 | 
					const registeShell = () => {
 | 
				
			||||||
    completionItemProvider = monaco.languages.registerCompletionItemProvider('shell', {
 | 
					    completionItemProvider = monaco.languages.registerCompletionItemProvider('shell', {
 | 
				
			||||||
        provideCompletionItems: async () => {
 | 
					        provideCompletionItems: async () => {
 | 
				
			||||||
            let suggestions: languages.CompletionItem[] = []
 | 
					            let suggestions: languages.CompletionItem[] = [];
 | 
				
			||||||
            shellLan.keywords.forEach((item: any) => {
 | 
					            shellLan.keywords.forEach((item: any) => {
 | 
				
			||||||
                suggestions.push({
 | 
					                suggestions.push({
 | 
				
			||||||
                    label: item,
 | 
					                    label: item,
 | 
				
			||||||
                    kind: monaco.languages.CompletionItemKind.Keyword,
 | 
					                    kind: monaco.languages.CompletionItemKind.Keyword,
 | 
				
			||||||
                    insertText: item,
 | 
					                    insertText: item,
 | 
				
			||||||
                } as any);
 | 
					                } as any);
 | 
				
			||||||
            })
 | 
					            });
 | 
				
			||||||
            shellLan.builtins.forEach((item: any) => {
 | 
					            shellLan.builtins.forEach((item: any) => {
 | 
				
			||||||
                suggestions.push({
 | 
					                suggestions.push({
 | 
				
			||||||
                    label: item,
 | 
					                    label: item,
 | 
				
			||||||
                    kind: monaco.languages.CompletionItemKind.Property,
 | 
					                    kind: monaco.languages.CompletionItemKind.Property,
 | 
				
			||||||
                    insertText: item,
 | 
					                    insertText: item,
 | 
				
			||||||
                } as any);
 | 
					                } as any);
 | 
				
			||||||
            })
 | 
					            });
 | 
				
			||||||
            return {
 | 
					            return {
 | 
				
			||||||
                suggestions: suggestions
 | 
					                suggestions: suggestions,
 | 
				
			||||||
            };
 | 
					            };
 | 
				
			||||||
        }
 | 
					        },
 | 
				
			||||||
    })
 | 
					    });
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const format = () => {
 | 
					const format = () => {
 | 
				
			||||||
    /*
 | 
					    /*
 | 
				
			||||||
    触发自动格式化;
 | 
					    触发自动格式化;
 | 
				
			||||||
   */
 | 
					   */
 | 
				
			||||||
    monacoEditorIns.trigger('', 'editor.action.formatDocument', '')
 | 
					    monacoEditorIns.trigger('', 'editor.action.formatDocument', '');
 | 
				
			||||||
}
 | 
					};
 | 
				
			||||||
 | 
					 | 
				
			||||||
defineExpose({ format })
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					defineExpose({ format });
 | 
				
			||||||
</script>
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<style lang="scss">
 | 
					<style lang="scss">
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										437
									
								
								mayfly_go_web/src/components/pagetable/PageTable.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										437
									
								
								mayfly_go_web/src/components/pagetable/PageTable.vue
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,437 @@
 | 
				
			|||||||
 | 
					<template>
 | 
				
			||||||
 | 
					    <div class="page-table">
 | 
				
			||||||
 | 
					        <!-- 
 | 
				
			||||||
 | 
					            实现:通过我们配置好的 查询条件
 | 
				
			||||||
 | 
					                首先去创建form表单,根据我们配置的查询条件去做一个循环判断,展示出不用类型所对应不同的输入框
 | 
				
			||||||
 | 
					                比如:text对应普通的输入框,select对应下拉选择,dateTime对应日期时间选择器
 | 
				
			||||||
 | 
					                在使用时,父组件会传来一个queryForm空的对象,
 | 
				
			||||||
 | 
					                循环出来的输入框会绑定表格配置中的prop字段绑定在queryForm对象中
 | 
				
			||||||
 | 
					         -->
 | 
				
			||||||
 | 
					        <el-card>
 | 
				
			||||||
 | 
					            <div class="query" ref="queryRef">
 | 
				
			||||||
 | 
					                <div>
 | 
				
			||||||
 | 
					                    <div v-if="props.query.length > 0">
 | 
				
			||||||
 | 
					                        <el-form :model="props.queryForm" label-width="auto" :size="props.size">
 | 
				
			||||||
 | 
					                            <el-row
 | 
				
			||||||
 | 
					                                v-for="i in Math.ceil((props.query.length + 1) / (defaultQueryCount + 1))"
 | 
				
			||||||
 | 
					                                :key="i"
 | 
				
			||||||
 | 
					                                v-show="i == 1 || isOpenMoreQuery"
 | 
				
			||||||
 | 
					                                :class="i > 1 && isOpenMoreQuery ? 'is-open' : ''"
 | 
				
			||||||
 | 
					                            >
 | 
				
			||||||
 | 
					                                <el-form-item
 | 
				
			||||||
 | 
					                                    :label="item.label"
 | 
				
			||||||
 | 
					                                    style="margin-right: 12px; margin-bottom: 0px"
 | 
				
			||||||
 | 
					                                    v-for="item in getRowQueryItem(i)"
 | 
				
			||||||
 | 
					                                    :key="item.prop"
 | 
				
			||||||
 | 
					                                >
 | 
				
			||||||
 | 
					                                    <!-- 这里只获取指定个数的筛选条件 -->
 | 
				
			||||||
 | 
					                                    <el-input
 | 
				
			||||||
 | 
					                                        v-model="queryForm[item.prop]"
 | 
				
			||||||
 | 
					                                        :placeholder="'输入' + item.label + '关键字'"
 | 
				
			||||||
 | 
					                                        clearable
 | 
				
			||||||
 | 
					                                        v-if="item.type == 'text'"
 | 
				
			||||||
 | 
					                                    ></el-input>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                                    <el-select-v2
 | 
				
			||||||
 | 
					                                        v-model="queryForm[item.prop]"
 | 
				
			||||||
 | 
					                                        :options="item.options"
 | 
				
			||||||
 | 
					                                        clearable
 | 
				
			||||||
 | 
					                                        :placeholder="'选择' + item.label + '关键字'"
 | 
				
			||||||
 | 
					                                        v-else-if="item.type == 'select'"
 | 
				
			||||||
 | 
					                                    />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                                    <el-date-picker
 | 
				
			||||||
 | 
					                                        v-model="queryForm[item.prop]"
 | 
				
			||||||
 | 
					                                        clearable
 | 
				
			||||||
 | 
					                                        type="datetimerange"
 | 
				
			||||||
 | 
					                                        format="YYYY-MM-DD hh:mm:ss"
 | 
				
			||||||
 | 
					                                        value-format="x"
 | 
				
			||||||
 | 
					                                        range-separator="至"
 | 
				
			||||||
 | 
					                                        start-placeholder="开始时间"
 | 
				
			||||||
 | 
					                                        end-placeholder="结束时间"
 | 
				
			||||||
 | 
					                                        v-else-if="item.type == 'date'"
 | 
				
			||||||
 | 
					                                    />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                                    <template v-else-if="item.slot == 'queryBtns'">
 | 
				
			||||||
 | 
					                                        <template v-if="props.query?.length > defaultQueryCount">
 | 
				
			||||||
 | 
					                                            <el-button
 | 
				
			||||||
 | 
					                                                @click="isOpenMoreQuery = !isOpenMoreQuery"
 | 
				
			||||||
 | 
					                                                v-if="!isOpenMoreQuery"
 | 
				
			||||||
 | 
					                                                icon="ArrowDownBold"
 | 
				
			||||||
 | 
					                                                circle
 | 
				
			||||||
 | 
					                                            ></el-button>
 | 
				
			||||||
 | 
					                                            <el-button @click="isOpenMoreQuery = !isOpenMoreQuery" v-else icon="ArrowUpBold" circle></el-button>
 | 
				
			||||||
 | 
					                                        </template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                                        <el-button @click="queryData()" type="primary" icon="search" plain>查询</el-button>
 | 
				
			||||||
 | 
					                                        <el-button @click="reset()" icon="RefreshRight">重置</el-button>
 | 
				
			||||||
 | 
					                                    </template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                                    <slot :name="item.slot"></slot>
 | 
				
			||||||
 | 
					                                </el-form-item>
 | 
				
			||||||
 | 
					                            </el-row>
 | 
				
			||||||
 | 
					                        </el-form>
 | 
				
			||||||
 | 
					                    </div>
 | 
				
			||||||
 | 
					                </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                <div class="slot">
 | 
				
			||||||
 | 
					                    <!-- 查询栏右侧slot插槽(用来添加表格其他操作,比如,新增数据,删除数据等其他操作) -->
 | 
				
			||||||
 | 
					                    <slot name="queryRight"></slot>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    <!-- 
 | 
				
			||||||
 | 
					                    动态表头显示,根据表格每条配置项中的show字段来决定改列是否显示或者隐藏 
 | 
				
			||||||
 | 
					                    columns 就是我们表格配置的数组对象
 | 
				
			||||||
 | 
					                    -->
 | 
				
			||||||
 | 
					                    <el-popover
 | 
				
			||||||
 | 
					                        placement="bottom"
 | 
				
			||||||
 | 
					                        title="表格配置"
 | 
				
			||||||
 | 
					                        popper-style="max-height: 550px; overflow: auto; max-width: 450px"
 | 
				
			||||||
 | 
					                        width="auto"
 | 
				
			||||||
 | 
					                        trigger="click"
 | 
				
			||||||
 | 
					                    >
 | 
				
			||||||
 | 
					                        <div v-for="(item, index) in props.columns" :key="index">
 | 
				
			||||||
 | 
					                            <el-checkbox v-model="item.show" :label="item.label" :true-label="true" :false-label="false" />
 | 
				
			||||||
 | 
					                        </div>
 | 
				
			||||||
 | 
					                        <template #reference>
 | 
				
			||||||
 | 
					                            <!-- 一个Element Plus中的图标 -->
 | 
				
			||||||
 | 
					                            <el-button icon="Operation" :size="props.size"></el-button>
 | 
				
			||||||
 | 
					                        </template>
 | 
				
			||||||
 | 
					                    </el-popover>
 | 
				
			||||||
 | 
					                </div>
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            <el-table
 | 
				
			||||||
 | 
					                v-bind="$attrs"
 | 
				
			||||||
 | 
					                :max-height="tableMaxHeight"
 | 
				
			||||||
 | 
					                @selection-change="handleSelectionChange"
 | 
				
			||||||
 | 
					                :data="props.data"
 | 
				
			||||||
 | 
					                highlight-current-row
 | 
				
			||||||
 | 
					                v-loading="loadingData"
 | 
				
			||||||
 | 
					                :size="props.size"
 | 
				
			||||||
 | 
					            >
 | 
				
			||||||
 | 
					                <el-table-column v-if="props.showSelection" type="selection" width="40" />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                <template v-for="(item, index) in columns">
 | 
				
			||||||
 | 
					                    <el-table-column
 | 
				
			||||||
 | 
					                        :key="index"
 | 
				
			||||||
 | 
					                        v-if="item.show"
 | 
				
			||||||
 | 
					                        :prop="item.prop"
 | 
				
			||||||
 | 
					                        :label="item.label"
 | 
				
			||||||
 | 
					                        :fixed="item.fixed"
 | 
				
			||||||
 | 
					                        :align="item.align"
 | 
				
			||||||
 | 
					                        :show-overflow-tooltip="item.showOverflowTooltip"
 | 
				
			||||||
 | 
					                        :min-width="item.minWidth"
 | 
				
			||||||
 | 
					                        :sortable="item.sortable || false"
 | 
				
			||||||
 | 
					                        :type="item.type"
 | 
				
			||||||
 | 
					                        :width="item.width"
 | 
				
			||||||
 | 
					                    >
 | 
				
			||||||
 | 
					                        <!-- 插槽:预留功能 -->
 | 
				
			||||||
 | 
					                        <template #default="scope" v-if="item.slot">
 | 
				
			||||||
 | 
					                            <slot :name="item.prop" :data="scope.row"></slot>
 | 
				
			||||||
 | 
					                        </template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                        <!-- 枚举类型使用tab展示 -->
 | 
				
			||||||
 | 
					                        <template #default="scope" v-else-if="item.type == 'tag'">
 | 
				
			||||||
 | 
					                            <enum-tag :size="props.size" :enums="item.typeParam" :value="scope.row[item.prop]"></enum-tag>
 | 
				
			||||||
 | 
					                        </template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                        <template #default="scope" v-else>
 | 
				
			||||||
 | 
					                            <!-- 配置了美化文本按钮以及文本内容大于指定长度,则显示美化按钮 -->
 | 
				
			||||||
 | 
					                            <el-popover
 | 
				
			||||||
 | 
					                                v-if="item.isBeautify && scope.row[item.prop]?.length > 35"
 | 
				
			||||||
 | 
					                                effect="light"
 | 
				
			||||||
 | 
					                                trigger="click"
 | 
				
			||||||
 | 
					                                placement="top"
 | 
				
			||||||
 | 
					                                width="600px"
 | 
				
			||||||
 | 
					                            >
 | 
				
			||||||
 | 
					                                <template #default>
 | 
				
			||||||
 | 
					                                    <el-input :autosize="{ minRows: 3, maxRows: 15 }" disabled v-model="formatVal" type="textarea" />
 | 
				
			||||||
 | 
					                                </template>
 | 
				
			||||||
 | 
					                                <template #reference>
 | 
				
			||||||
 | 
					                                    <el-link
 | 
				
			||||||
 | 
					                                        @click="formatText(scope.row[item.prop])"
 | 
				
			||||||
 | 
					                                        :underline="false"
 | 
				
			||||||
 | 
					                                        type="success"
 | 
				
			||||||
 | 
					                                        icon="MagicStick"
 | 
				
			||||||
 | 
					                                        class="mr5"
 | 
				
			||||||
 | 
					                                    ></el-link>
 | 
				
			||||||
 | 
					                                </template>
 | 
				
			||||||
 | 
					                            </el-popover>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                            <span>{{ item.getValueByData(scope.row) }}</span>
 | 
				
			||||||
 | 
					                        </template>
 | 
				
			||||||
 | 
					                    </el-table-column>
 | 
				
			||||||
 | 
					                </template>
 | 
				
			||||||
 | 
					            </el-table>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            <el-row style="margin-top: 20px" type="flex" justify="end">
 | 
				
			||||||
 | 
					                <el-pagination
 | 
				
			||||||
 | 
					                    :small="props.size == 'small'"
 | 
				
			||||||
 | 
					                    @current-change="handlePageChange"
 | 
				
			||||||
 | 
					                    @size-change="handleSizeChange"
 | 
				
			||||||
 | 
					                    style="text-align: right"
 | 
				
			||||||
 | 
					                    layout="prev, pager, next, total, sizes, jumper"
 | 
				
			||||||
 | 
					                    :total="props.total"
 | 
				
			||||||
 | 
					                    v-model:current-page="state.pageNum"
 | 
				
			||||||
 | 
					                    v-model:page-size="state.pageSize"
 | 
				
			||||||
 | 
					                    :page-sizes="pageSizes"
 | 
				
			||||||
 | 
					                />
 | 
				
			||||||
 | 
					            </el-row>
 | 
				
			||||||
 | 
					        </el-card>
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<script lang="ts" setup>
 | 
				
			||||||
 | 
					import { toRefs, watch, reactive, onMounted } from 'vue';
 | 
				
			||||||
 | 
					import { TableColumn, TableQuery } from './index';
 | 
				
			||||||
 | 
					import EnumTag from '@/components/enumtag/EnumTag.vue';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const emit = defineEmits(['update:queryForm', 'update:pageNum', 'update:pageSize', 'update:selectionData', 'pageChange'])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const props = defineProps({
 | 
				
			||||||
 | 
					    size: {
 | 
				
			||||||
 | 
					        type: String,
 | 
				
			||||||
 | 
					        default: '',
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    inputWidth: {
 | 
				
			||||||
 | 
					        type: [Number, String],
 | 
				
			||||||
 | 
					        default: 0,
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    // 是否显示选择列
 | 
				
			||||||
 | 
					    showSelection: {
 | 
				
			||||||
 | 
					        type: Boolean,
 | 
				
			||||||
 | 
					        default: false,
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    // 当前选择的数据
 | 
				
			||||||
 | 
					    selectionData: {
 | 
				
			||||||
 | 
					        type: Array<any>
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    // 列信息
 | 
				
			||||||
 | 
					    columns: {
 | 
				
			||||||
 | 
					        type: Array<TableColumn>,
 | 
				
			||||||
 | 
					        default: function () {
 | 
				
			||||||
 | 
					            return [];
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        required: true,
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    // 表格数据
 | 
				
			||||||
 | 
					    data: {
 | 
				
			||||||
 | 
					        type: Array,
 | 
				
			||||||
 | 
					        required: true,
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    total: {
 | 
				
			||||||
 | 
					        type: [Number],
 | 
				
			||||||
 | 
					        default: 0,
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    pageNum: {
 | 
				
			||||||
 | 
					        type: Number,
 | 
				
			||||||
 | 
					        default: 1,
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    pageSize: {
 | 
				
			||||||
 | 
					        type: [Number],
 | 
				
			||||||
 | 
					        default: 10,
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    // 查询条件配置
 | 
				
			||||||
 | 
					    query: {
 | 
				
			||||||
 | 
					        type: Array<TableQuery>,
 | 
				
			||||||
 | 
					        default: function () {
 | 
				
			||||||
 | 
					            return [];
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    // 绑定的查询表单
 | 
				
			||||||
 | 
					    queryForm: {
 | 
				
			||||||
 | 
					        type: Object,
 | 
				
			||||||
 | 
					        default: function () {
 | 
				
			||||||
 | 
					            return {};
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const state = reactive({
 | 
				
			||||||
 | 
					    pageSizes: [] as any, // 可选每页显示的数据量
 | 
				
			||||||
 | 
					    pageSize: 10,
 | 
				
			||||||
 | 
					    pageNum: 1,
 | 
				
			||||||
 | 
					    isOpenMoreQuery: false,
 | 
				
			||||||
 | 
					    defaultQueryCount: 2, // 默认显示的查询参数个数,展开后每行显示查询条件个数为该值加1。第一行用最后一列来占用按钮
 | 
				
			||||||
 | 
					    queryForm: {} as any,
 | 
				
			||||||
 | 
					    loadingData: false,
 | 
				
			||||||
 | 
					    // 输入框宽度
 | 
				
			||||||
 | 
					    inputWidth: "200px" as any,
 | 
				
			||||||
 | 
					    formatVal: '', // 格式化后的值
 | 
				
			||||||
 | 
					    tableMaxHeight: window.innerHeight - 240 + 'px',
 | 
				
			||||||
 | 
					})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const {
 | 
				
			||||||
 | 
					    pageSizes,
 | 
				
			||||||
 | 
					    isOpenMoreQuery,
 | 
				
			||||||
 | 
					    defaultQueryCount,
 | 
				
			||||||
 | 
					    queryForm,
 | 
				
			||||||
 | 
					    loadingData,
 | 
				
			||||||
 | 
					    inputWidth,
 | 
				
			||||||
 | 
					    formatVal,
 | 
				
			||||||
 | 
					    tableMaxHeight,
 | 
				
			||||||
 | 
					} = toRefs(state)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					watch(() => props.queryForm, (newValue: any) => {
 | 
				
			||||||
 | 
					    state.queryForm = newValue;
 | 
				
			||||||
 | 
					})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					watch(() => props.pageNum, (newValue: any) => {
 | 
				
			||||||
 | 
					    state.pageNum = newValue;
 | 
				
			||||||
 | 
					})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					watch(() => props.pageSize, (newValue: any) => {
 | 
				
			||||||
 | 
					    state.pageSize = newValue;
 | 
				
			||||||
 | 
					})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					watch(() => props.data, (newValue: any) => {
 | 
				
			||||||
 | 
					    if (newValue && newValue.length > 0) {
 | 
				
			||||||
 | 
					        props.columns.forEach(item => {
 | 
				
			||||||
 | 
					            if (item.autoWidth && item.show) {
 | 
				
			||||||
 | 
					                item.autoCalculateMinWidth(props.data);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        })
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					onMounted(() => {
 | 
				
			||||||
 | 
					    const pageSize = props.pageSize;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    state.pageNum = props.pageNum;
 | 
				
			||||||
 | 
					    state.pageSize = pageSize;
 | 
				
			||||||
 | 
					    state.queryForm = props.queryForm;
 | 
				
			||||||
 | 
					    state.pageSizes = [pageSize, pageSize * 2, pageSize * 3, pageSize * 4, pageSize * 5];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // 如果没传输入框宽度,则根据组件size设置默认宽度
 | 
				
			||||||
 | 
					    if (!props.inputWidth) {
 | 
				
			||||||
 | 
					        state.inputWidth = props.size == 'small' ? '150px' : '200px';
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					        state.inputWidth = props.inputWidth;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    window.addEventListener('resize', () => {
 | 
				
			||||||
 | 
					        calcuTableHeight();
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const calcuTableHeight = () => {
 | 
				
			||||||
 | 
					    state.tableMaxHeight = window.innerHeight - 240 + 'px';
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const formatText = (data: any)=> {
 | 
				
			||||||
 | 
					    state.formatVal = '';
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					        state.formatVal = JSON.stringify(JSON.parse(data), null, 4);
 | 
				
			||||||
 | 
					    }  catch (e) {
 | 
				
			||||||
 | 
					        state.formatVal = data;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const getRowQueryItem = (row: number) => {
 | 
				
			||||||
 | 
					    // 第一行需要加个查询等按钮列
 | 
				
			||||||
 | 
					    if (row === 1) {
 | 
				
			||||||
 | 
					        const res = props.query.slice(row - 1, defaultQueryCount.value);
 | 
				
			||||||
 | 
					        // 查询等按钮列
 | 
				
			||||||
 | 
					        res.push(TableQuery.slot("", "", "queryBtns"));
 | 
				
			||||||
 | 
					        return res
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    const columnCount = defaultQueryCount.value + 1;
 | 
				
			||||||
 | 
					    return props.query.slice((row - 1) * columnCount - 1, row * columnCount - 1);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const handleSelectionChange = (val: any) => {
 | 
				
			||||||
 | 
					    emit('update:selectionData', val);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const handlePageChange = () => {
 | 
				
			||||||
 | 
					    emit('update:pageNum', state.pageNum);
 | 
				
			||||||
 | 
					    execQuery();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const handleSizeChange = () => {
 | 
				
			||||||
 | 
					    changePageNum(1);
 | 
				
			||||||
 | 
					    emit('update:pageSize', state.pageSize);
 | 
				
			||||||
 | 
					    execQuery();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const queryData = () => {
 | 
				
			||||||
 | 
					    changePageNum(1);
 | 
				
			||||||
 | 
					    execQuery();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const reset = () => {
 | 
				
			||||||
 | 
					    // 将查询参数绑定的值置空,并重新粗发查询接口
 | 
				
			||||||
 | 
					    for (let qi of props.query) {
 | 
				
			||||||
 | 
					        state.queryForm[qi.prop] = null;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    changePageNum(1);
 | 
				
			||||||
 | 
					    emit('update:queryForm', state.queryForm);
 | 
				
			||||||
 | 
					    execQuery();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const changePageNum = (pageNum: number) => {
 | 
				
			||||||
 | 
					    state.pageNum = pageNum;
 | 
				
			||||||
 | 
					    emit('update:pageNum', state.pageNum);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const execQuery = () => {
 | 
				
			||||||
 | 
					    emit('pageChange');
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * 是否正在加载数据
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					const loading = (loading: boolean) => {
 | 
				
			||||||
 | 
					    state.loadingData = loading;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					defineExpose({ loading })
 | 
				
			||||||
 | 
					</script>
 | 
				
			||||||
 | 
					<style scoped lang="scss">
 | 
				
			||||||
 | 
					.page-table {
 | 
				
			||||||
 | 
					    .query {
 | 
				
			||||||
 | 
					        margin-bottom: 10px;
 | 
				
			||||||
 | 
					        overflow: hidden;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        .is-open {
 | 
				
			||||||
 | 
					            // padding: 10px 0;
 | 
				
			||||||
 | 
					            max-height: 200px;
 | 
				
			||||||
 | 
					            margin-top: 10px;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        display: flex;
 | 
				
			||||||
 | 
					        align-items: flex-start;
 | 
				
			||||||
 | 
					        justify-content: space-between;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        .slot {
 | 
				
			||||||
 | 
					            display: flex;
 | 
				
			||||||
 | 
					            justify-content: flex-end;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    .page {
 | 
				
			||||||
 | 
					        margin-top: 10px;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					::v-deep(.el-form-item__label) {
 | 
				
			||||||
 | 
					    font-weight: bold;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.el-select-v2 {
 | 
				
			||||||
 | 
					    width: v-bind(inputWidth);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.el-input {
 | 
				
			||||||
 | 
					    width: v-bind(inputWidth);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.el-select {
 | 
				
			||||||
 | 
					    width: v-bind(inputWidth);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.el-date-editor {
 | 
				
			||||||
 | 
					    width: 380px !important;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					</style>
 | 
				
			||||||
							
								
								
									
										309
									
								
								mayfly_go_web/src/components/pagetable/index.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										309
									
								
								mayfly_go_web/src/components/pagetable/index.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,309 @@
 | 
				
			|||||||
 | 
					import EnumValue from '@/common/Enum';
 | 
				
			||||||
 | 
					import { dateFormat } from '@/common/utils/date';
 | 
				
			||||||
 | 
					import { getTextWidth } from '@/common/utils/string';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export class TableColumn {
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * 属性字段
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    prop: string;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * 显示表头
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    label: string;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * 是否自动计算宽度
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    autoWidth: boolean = true;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * 自动计算宽度时,累加该值(可能列值会进行转换 如添加图标等,宽度需要比计算出来的更宽些)
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    addWidth: number = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * 最小宽度
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    minWidth: number | string;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * 是否插槽,是的话插槽名则为prop属性名
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    slot: boolean = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    showOverflowTooltip: boolean = true;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    sortable: boolean = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * 官方:对应列的类型。 如果设置了selection则显示多选框;
 | 
				
			||||||
 | 
					     * 如果设置了 index 则显示该行的索引(从 1 开始计算);
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * 新增 tag类型,用于枚举值转换后用tag进行展示
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    type: string;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * 类型展示需要的额外参数,如枚举转换的EnumValue值等
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    typeParam: any;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    width: number | string;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fixed: any;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    align: string = 'left';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * 指定格式化函数对原始值进行格式化,如时间格式化等
 | 
				
			||||||
 | 
					     * param1: data, param2: prop
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    formatFunc: Function;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * 是否显示该列
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    show: boolean = true;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * 是否展示美化按钮(主要用于美化json文本等)
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    isBeautify: boolean = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    constructor(prop: string, label: string) {
 | 
				
			||||||
 | 
					        this.prop = prop;
 | 
				
			||||||
 | 
					        this.label = label;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * 获取该列在指定行数据中的值
 | 
				
			||||||
 | 
					     * @param rowData 该行对应的数据
 | 
				
			||||||
 | 
					     * @returns 该列对应的值
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    getValueByData(rowData: any) {
 | 
				
			||||||
 | 
					        if (this.formatFunc) {
 | 
				
			||||||
 | 
					            return this.formatFunc(rowData, this.prop);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        return rowData[this.prop];
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    static new(prop: string, label: string): TableColumn {
 | 
				
			||||||
 | 
					        return new TableColumn(prop, label);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    noShowOverflowTooltip(): TableColumn {
 | 
				
			||||||
 | 
					        this.showOverflowTooltip = false;
 | 
				
			||||||
 | 
					        return this;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    setMinWidth(minWidth: number | string): TableColumn {
 | 
				
			||||||
 | 
					        this.minWidth = minWidth;
 | 
				
			||||||
 | 
					        this.autoWidth = false;
 | 
				
			||||||
 | 
					        return this;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    setAddWidth(addWidth: number): TableColumn {
 | 
				
			||||||
 | 
					        this.addWidth = addWidth;
 | 
				
			||||||
 | 
					        return this;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * 居中对齐
 | 
				
			||||||
 | 
					     * @returns this
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    alignCenter(): TableColumn {
 | 
				
			||||||
 | 
					        this.align = 'center';
 | 
				
			||||||
 | 
					        return this;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * 使用标签类型展示该列(用于枚举值友好展示)
 | 
				
			||||||
 | 
					     * @param param 枚举对象
 | 
				
			||||||
 | 
					     * @returns this
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    typeTag(param: any): TableColumn {
 | 
				
			||||||
 | 
					        this.type = 'tag';
 | 
				
			||||||
 | 
					        this.typeParam = param;
 | 
				
			||||||
 | 
					        return this;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    typeText(): TableColumn {
 | 
				
			||||||
 | 
					        this.type = 'text';
 | 
				
			||||||
 | 
					        return this;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    typeJson(): TableColumn {
 | 
				
			||||||
 | 
					        this.type = 'jsonText';
 | 
				
			||||||
 | 
					        return this;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * 标识该列为插槽
 | 
				
			||||||
 | 
					     * @returns this
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    isSlot(): TableColumn {
 | 
				
			||||||
 | 
					        this.slot = true;
 | 
				
			||||||
 | 
					        return this;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * 设置该列的格式化回调函数
 | 
				
			||||||
 | 
					     * @param func 格式化回调函数(参数为 -> data: 该行对应的数据,prop: 该列对应的prop属性值)
 | 
				
			||||||
 | 
					     * @returns
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    setFormatFunc(func: Function): TableColumn {
 | 
				
			||||||
 | 
					        this.formatFunc = func;
 | 
				
			||||||
 | 
					        return this;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * 为时间字段,则使用默认时间格式函数
 | 
				
			||||||
 | 
					     * @returns this
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    isTime(): TableColumn {
 | 
				
			||||||
 | 
					        this.setFormatFunc((data: any, prop: string) => {
 | 
				
			||||||
 | 
					            return dateFormat(data[prop]);
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					        return this;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * 标识该列枚举类,需进行枚举值转换
 | 
				
			||||||
 | 
					     * @returns this
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    isEnum(enums: any): TableColumn {
 | 
				
			||||||
 | 
					        this.setFormatFunc((data: any, prop: string) => {
 | 
				
			||||||
 | 
					            return EnumValue.getLabelByValue(enums, data[prop]);
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					        return this;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fixedRight(): TableColumn {
 | 
				
			||||||
 | 
					        this.fixed = 'right';
 | 
				
			||||||
 | 
					        return this;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fixedLeft(): TableColumn {
 | 
				
			||||||
 | 
					        this.fixed = 'left';
 | 
				
			||||||
 | 
					        return this;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    canBeautify(): TableColumn {
 | 
				
			||||||
 | 
					        this.isBeautify = true;
 | 
				
			||||||
 | 
					        return this;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * 自动计算最小宽度
 | 
				
			||||||
 | 
					     * @param str 字符串
 | 
				
			||||||
 | 
					     * @param tableData 表数据
 | 
				
			||||||
 | 
					     * @param label 表头label也参与宽度计算
 | 
				
			||||||
 | 
					     * @returns 列宽度
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    autoCalculateMinWidth = (tableData: any) => {
 | 
				
			||||||
 | 
					        const prop = this.prop;
 | 
				
			||||||
 | 
					        const label = this.label;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (!tableData || !tableData.length || tableData.length === 0 || tableData === undefined) {
 | 
				
			||||||
 | 
					            return 0;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        let maxWidthText = '';
 | 
				
			||||||
 | 
					        let maxWidthValue;
 | 
				
			||||||
 | 
					        // 为了兼容formatFunc格式化回调函数
 | 
				
			||||||
 | 
					        let maxData;
 | 
				
			||||||
 | 
					        // 获取该列中最长的数据(内容)
 | 
				
			||||||
 | 
					        for (let i = 0; i < tableData.length; i++) {
 | 
				
			||||||
 | 
					            let nowData = tableData[i];
 | 
				
			||||||
 | 
					            let nowValue = nowData[prop];
 | 
				
			||||||
 | 
					            if (!nowValue) {
 | 
				
			||||||
 | 
					                continue;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            // 转为字符串比较长度
 | 
				
			||||||
 | 
					            let nowText = nowValue + '';
 | 
				
			||||||
 | 
					            if (nowText.length > maxWidthText.length) {
 | 
				
			||||||
 | 
					                maxWidthText = nowText;
 | 
				
			||||||
 | 
					                maxWidthValue = nowValue;
 | 
				
			||||||
 | 
					                maxData = nowData;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        if (this.formatFunc && maxWidthValue) {
 | 
				
			||||||
 | 
					            maxWidthText = this.formatFunc(maxData, prop) + '';
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        // 需要加上表格的内间距等,视情况加
 | 
				
			||||||
 | 
					        const contentWidth: number = getTextWidth(maxWidthText) + 30;
 | 
				
			||||||
 | 
					        // 获取label的宽度,取较大的宽度
 | 
				
			||||||
 | 
					        const columnWidth: number = getTextWidth(label) + 60;
 | 
				
			||||||
 | 
					        const flexWidth: number = contentWidth > columnWidth ? contentWidth : columnWidth;
 | 
				
			||||||
 | 
					        // 设置上限与累加需要额外增加的宽度
 | 
				
			||||||
 | 
					        this.minWidth = (flexWidth > 400 ? 400 : flexWidth) + this.addWidth;
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export class TableQuery {
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * 属性字段
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    prop: string;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * 显示表头
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    label: string;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * 查询类型,text、select、date
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    type: string;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * select可选值
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    options: any;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * 插槽名
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    slot: string;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    constructor(prop: string, label: string) {
 | 
				
			||||||
 | 
					        this.prop = prop;
 | 
				
			||||||
 | 
					        this.label = label;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    static new(prop: string, label: string): TableQuery {
 | 
				
			||||||
 | 
					        return new TableQuery(prop, label);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    static text(prop: string, label: string): TableQuery {
 | 
				
			||||||
 | 
					        const tq = new TableQuery(prop, label);
 | 
				
			||||||
 | 
					        tq.type = 'text';
 | 
				
			||||||
 | 
					        return tq;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    static select(prop: string, label: string): TableQuery {
 | 
				
			||||||
 | 
					        const tq = new TableQuery(prop, label);
 | 
				
			||||||
 | 
					        tq.type = 'select';
 | 
				
			||||||
 | 
					        return tq;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    static date(prop: string, label: string): TableQuery {
 | 
				
			||||||
 | 
					        const tq = new TableQuery(prop, label);
 | 
				
			||||||
 | 
					        tq.type = 'date';
 | 
				
			||||||
 | 
					        return tq;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    static slot(prop: string, label: string, slotName: string): TableQuery {
 | 
				
			||||||
 | 
					        const tq = new TableQuery(prop, label);
 | 
				
			||||||
 | 
					        tq.slot = slotName;
 | 
				
			||||||
 | 
					        return tq;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    setOptions(options: any): TableQuery {
 | 
				
			||||||
 | 
					        this.options = options;
 | 
				
			||||||
 | 
					        return this;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -35,7 +35,7 @@ const props = defineProps({
 | 
				
			|||||||
    isEle: {
 | 
					    isEle: {
 | 
				
			||||||
        type: Boolean,
 | 
					        type: Boolean,
 | 
				
			||||||
        default: true,
 | 
					        default: true,
 | 
				
			||||||
    }
 | 
					    },
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// 在线链接、本地引入地址前缀
 | 
					// 在线链接、本地引入地址前缀
 | 
				
			||||||
@@ -48,7 +48,7 @@ const getIconName = computed(() => {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
// 用于判断 element plus 自带 svg 图标的显示、隐藏。不存在 空格分隔的icon name即为element plus自带icon
 | 
					// 用于判断 element plus 自带 svg 图标的显示、隐藏。不存在 空格分隔的icon name即为element plus自带icon
 | 
				
			||||||
const isShowIconSvg = computed(() => {
 | 
					const isShowIconSvg = computed(() => {
 | 
				
			||||||
    const ss = props?.name?.split(" ")
 | 
					    const ss = props?.name?.split(' ');
 | 
				
			||||||
    if (!ss) {
 | 
					    if (!ss) {
 | 
				
			||||||
        return true;
 | 
					        return true;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@@ -56,13 +56,13 @@ const isShowIconSvg = computed(() => {
 | 
				
			|||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const isIconfont = () => {
 | 
					const isIconfont = () => {
 | 
				
			||||||
    return props?.name?.startsWith("iconfont")
 | 
					    return props?.name?.startsWith('iconfont');
 | 
				
			||||||
}
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const getIconfontName = () => {
 | 
					const getIconfontName = () => {
 | 
				
			||||||
    // iconfont icon-xxxx 获取icon-xxx即可
 | 
					    // iconfont icon-xxxx 获取icon-xxx即可
 | 
				
			||||||
    return props?.name?.split(" ")[1]
 | 
					    return props?.name?.split(' ')[1];
 | 
				
			||||||
}
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// 用于判断在线链接、本地引入等图标显示、隐藏
 | 
					// 用于判断在线链接、本地引入等图标显示、隐藏
 | 
				
			||||||
const isShowIconImg = computed(() => {
 | 
					const isShowIconImg = computed(() => {
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										286
									
								
								mayfly_go_web/src/components/terminal/TerminalBody.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										286
									
								
								mayfly_go_web/src/components/terminal/TerminalBody.vue
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,286 @@
 | 
				
			|||||||
 | 
					<template>
 | 
				
			||||||
 | 
					    <div id="terminal-body" :style="{ height, background: themeConfig.terminalBackground }">
 | 
				
			||||||
 | 
					        <div ref="terminalRef" class="terminal" />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        <TerminalSearch ref="terminalSearchRef" :search-addon="state.addon.search" @close="focus" />
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<script lang="ts" setup>
 | 
				
			||||||
 | 
					import 'xterm/css/xterm.css';
 | 
				
			||||||
 | 
					import { Terminal } from 'xterm';
 | 
				
			||||||
 | 
					import { FitAddon } from 'xterm-addon-fit';
 | 
				
			||||||
 | 
					import { SearchAddon } from 'xterm-addon-search';
 | 
				
			||||||
 | 
					import { WebLinksAddon } from 'xterm-addon-web-links';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { storeToRefs } from 'pinia';
 | 
				
			||||||
 | 
					import { useThemeConfig } from '@/store/themeConfig';
 | 
				
			||||||
 | 
					import { ref, nextTick, reactive, onMounted, onBeforeUnmount, watch } from 'vue';
 | 
				
			||||||
 | 
					import TerminalSearch from './TerminalSearch.vue';
 | 
				
			||||||
 | 
					import { debounce } from 'lodash';
 | 
				
			||||||
 | 
					import { TerminalStatus } from './common';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const props = defineProps({
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * 初始化执行命令
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    cmd: { type: String },
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * 连接url
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    socketUrl: {
 | 
				
			||||||
 | 
					        type: String,
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * 高度
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    height: {
 | 
				
			||||||
 | 
					        type: [String, Number],
 | 
				
			||||||
 | 
					        default: '100%',
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const emit = defineEmits(['statusChange']);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const terminalRef: any = ref(null);
 | 
				
			||||||
 | 
					const terminalSearchRef: any = ref(null);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const { themeConfig } = storeToRefs(useThemeConfig());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// 终端实例
 | 
				
			||||||
 | 
					let term: Terminal;
 | 
				
			||||||
 | 
					let socket: WebSocket;
 | 
				
			||||||
 | 
					let pingInterval: any;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const state = reactive({
 | 
				
			||||||
 | 
					    // 插件
 | 
				
			||||||
 | 
					    addon: {
 | 
				
			||||||
 | 
					        fit: null as any,
 | 
				
			||||||
 | 
					        search: null as any,
 | 
				
			||||||
 | 
					        weblinks: null as any,
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    status: TerminalStatus.NoConnected,
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					onMounted(() => {
 | 
				
			||||||
 | 
					    nextTick(() => {
 | 
				
			||||||
 | 
					        init();
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					watch(
 | 
				
			||||||
 | 
					    () => state.status,
 | 
				
			||||||
 | 
					    () => {
 | 
				
			||||||
 | 
					        emit('statusChange', state.status);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					onBeforeUnmount(() => {
 | 
				
			||||||
 | 
					    close();
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function init() {
 | 
				
			||||||
 | 
					    if (term) {
 | 
				
			||||||
 | 
					        console.log('重新连接...');
 | 
				
			||||||
 | 
					        close();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    term = new Terminal({
 | 
				
			||||||
 | 
					        fontSize: themeConfig.value.terminalFontSize || 15,
 | 
				
			||||||
 | 
					        fontWeight: themeConfig.value.terminalFontWeight || 'normal',
 | 
				
			||||||
 | 
					        fontFamily: 'JetBrainsMono, monaco, Consolas, Lucida Console, monospace',
 | 
				
			||||||
 | 
					        cursorBlink: true,
 | 
				
			||||||
 | 
					        disableStdin: false,
 | 
				
			||||||
 | 
					        allowProposedApi: true,
 | 
				
			||||||
 | 
					        theme: {
 | 
				
			||||||
 | 
					            foreground: themeConfig.value.terminalForeground || '#7e9192', //字体
 | 
				
			||||||
 | 
					            background: themeConfig.value.terminalBackground || '#002833', //背景色
 | 
				
			||||||
 | 
					            cursor: themeConfig.value.terminalCursor || '#268F81', //设置光标
 | 
				
			||||||
 | 
					            // cursorAccent: "red",  // 光标停止颜色
 | 
				
			||||||
 | 
					        } as any,
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    term.open(terminalRef.value);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // 注册自适应组件
 | 
				
			||||||
 | 
					    const fitAddon = new FitAddon();
 | 
				
			||||||
 | 
					    state.addon.fit = fitAddon;
 | 
				
			||||||
 | 
					    term.loadAddon(fitAddon);
 | 
				
			||||||
 | 
					    fitTerminal();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // 注册搜索组件
 | 
				
			||||||
 | 
					    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();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * 连接成功
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					const onConnected = () => {
 | 
				
			||||||
 | 
					    // 注册心跳
 | 
				
			||||||
 | 
					    pingInterval = setInterval(sendPing, 15000);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // 注册 terminal 事件
 | 
				
			||||||
 | 
					    term.onResize((event) => sendResize(event.cols, event.rows));
 | 
				
			||||||
 | 
					    term.onData((event) => sendCmd(event));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // 注册自定义快捷键
 | 
				
			||||||
 | 
					    term.attachCustomKeyEventHandler((event: KeyboardEvent) => {
 | 
				
			||||||
 | 
					        // 注册搜索键 ctrl + f
 | 
				
			||||||
 | 
					        if (event.key === 'f' && (event.ctrlKey || event.metaKey) && event.type === 'keydown') {
 | 
				
			||||||
 | 
					            event.preventDefault();
 | 
				
			||||||
 | 
					            terminalSearchRef.value.open();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return true;
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    state.status = TerminalStatus.Connected;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // resize
 | 
				
			||||||
 | 
					    sendResize(term.cols, term.rows);
 | 
				
			||||||
 | 
					    // 注册窗口大小监听器
 | 
				
			||||||
 | 
					    window.addEventListener('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() {
 | 
				
			||||||
 | 
					    if (props.socketUrl) {
 | 
				
			||||||
 | 
					        socket = new WebSocket(props.socketUrl);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // 监听socket连接
 | 
				
			||||||
 | 
					    socket.onopen = () => {
 | 
				
			||||||
 | 
					        onConnected();
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // 监听socket错误信息
 | 
				
			||||||
 | 
					    socket.onerror = (e: Event) => {
 | 
				
			||||||
 | 
					        term.writeln('\r\n\x1b[31m提示: 连接错误...');
 | 
				
			||||||
 | 
					        state.status = TerminalStatus.Error;
 | 
				
			||||||
 | 
					        console.log('连接错误', e);
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    socket.onclose = (e: CloseEvent) => {
 | 
				
			||||||
 | 
					        console.log('terminal socket close...', e.reason);
 | 
				
			||||||
 | 
					        // 关闭窗口大小监听器
 | 
				
			||||||
 | 
					        window.removeEventListener('resize', debounce(fitTerminal, 100));
 | 
				
			||||||
 | 
					        // 清除 ping
 | 
				
			||||||
 | 
					        pingInterval && clearInterval(pingInterval);
 | 
				
			||||||
 | 
					        state.status = TerminalStatus.Disconnected;
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // 监听socket消息
 | 
				
			||||||
 | 
					    socket.onmessage = getMessage;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function getMessage(msg: any) {
 | 
				
			||||||
 | 
					    // msg.data是真正后端返回的数据
 | 
				
			||||||
 | 
					    term.write(msg.data);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					enum MsgType {
 | 
				
			||||||
 | 
					    Resize = 1,
 | 
				
			||||||
 | 
					    Data = 2,
 | 
				
			||||||
 | 
					    Ping = 3,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const send = (msg: any) => {
 | 
				
			||||||
 | 
					    state.status == TerminalStatus.Connected && socket.send(JSON.stringify(msg));
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const sendResize = (cols: number, rows: number) => {
 | 
				
			||||||
 | 
					    send({
 | 
				
			||||||
 | 
					        type: MsgType.Resize,
 | 
				
			||||||
 | 
					        Cols: cols,
 | 
				
			||||||
 | 
					        Rows: rows,
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const sendPing = () => {
 | 
				
			||||||
 | 
					    send({
 | 
				
			||||||
 | 
					        type: MsgType.Ping,
 | 
				
			||||||
 | 
					        msg: 'ping',
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function sendCmd(key: any) {
 | 
				
			||||||
 | 
					    send({
 | 
				
			||||||
 | 
					        type: MsgType.Data,
 | 
				
			||||||
 | 
					        msg: key,
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function closeSocket() {
 | 
				
			||||||
 | 
					    // 关闭 websocket
 | 
				
			||||||
 | 
					    socket && socket.readyState === 1 && socket.close();
 | 
				
			||||||
 | 
					    // 清除 ping
 | 
				
			||||||
 | 
					    pingInterval && clearInterval(pingInterval);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function close() {
 | 
				
			||||||
 | 
					    console.log('in terminal body close');
 | 
				
			||||||
 | 
					    closeSocket();
 | 
				
			||||||
 | 
					    if (term) {
 | 
				
			||||||
 | 
					        state.addon.search.dispose();
 | 
				
			||||||
 | 
					        state.addon.fit.dispose();
 | 
				
			||||||
 | 
					        state.addon.weblinks.dispose();
 | 
				
			||||||
 | 
					        term.dispose();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const getStatus = (): TerminalStatus => {
 | 
				
			||||||
 | 
					    return state.status;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					defineExpose({ init, fitTerminal, focus, clear, close, getStatus });
 | 
				
			||||||
 | 
					</script>
 | 
				
			||||||
 | 
					<style lang="scss">
 | 
				
			||||||
 | 
					#terminal-body {
 | 
				
			||||||
 | 
					    background: #212529;
 | 
				
			||||||
 | 
					    width: 100%;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    .terminal {
 | 
				
			||||||
 | 
					        width: 100%;
 | 
				
			||||||
 | 
					        height: 100%;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        .xterm .xterm-viewport {
 | 
				
			||||||
 | 
					            overflow-y: hidden;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					</style>
 | 
				
			||||||
							
								
								
									
										309
									
								
								mayfly_go_web/src/components/terminal/TerminalDialog.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										309
									
								
								mayfly_go_web/src/components/terminal/TerminalDialog.vue
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,309 @@
 | 
				
			|||||||
 | 
					<template>
 | 
				
			||||||
 | 
					    <div>
 | 
				
			||||||
 | 
					        <div class="terminal-dialog-container" v-for="openTerminal of terminals" :key="openTerminal.terminalId">
 | 
				
			||||||
 | 
					            <el-dialog
 | 
				
			||||||
 | 
					                title="终端"
 | 
				
			||||||
 | 
					                v-model="openTerminal.visible"
 | 
				
			||||||
 | 
					                top="32px"
 | 
				
			||||||
 | 
					                class="terminal-dialog"
 | 
				
			||||||
 | 
					                width="75%"
 | 
				
			||||||
 | 
					                :close-on-click-modal="false"
 | 
				
			||||||
 | 
					                :modal="true"
 | 
				
			||||||
 | 
					                :show-close="false"
 | 
				
			||||||
 | 
					                :fullscreen="openTerminal.fullscreen"
 | 
				
			||||||
 | 
					            >
 | 
				
			||||||
 | 
					                <template #header>
 | 
				
			||||||
 | 
					                    <div class="terminal-title-wrapper">
 | 
				
			||||||
 | 
					                        <!-- 左侧 -->
 | 
				
			||||||
 | 
					                        <div class="title-left-fixed">
 | 
				
			||||||
 | 
					                            <!-- title信息 -->
 | 
				
			||||||
 | 
					                            <div>
 | 
				
			||||||
 | 
					                                <slot name="headerTitle" :terminalInfo="openTerminal">
 | 
				
			||||||
 | 
					                                    {{ openTerminal.headerTitle }}
 | 
				
			||||||
 | 
					                                </slot>
 | 
				
			||||||
 | 
					                            </div>
 | 
				
			||||||
 | 
					                        </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                        <!-- 右侧 -->
 | 
				
			||||||
 | 
					                        <div class="title-right-fixed">
 | 
				
			||||||
 | 
					                            <el-popconfirm @confirm="reConnect(openTerminal.terminalId)" title="确认重新连接?">
 | 
				
			||||||
 | 
					                                <template #reference>
 | 
				
			||||||
 | 
					                                    <div class="mr15 pointer">
 | 
				
			||||||
 | 
					                                        <el-tag v-if="openTerminal.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-popover placement="bottom" :width="200" trigger="hover">
 | 
				
			||||||
 | 
					                                <template #reference>
 | 
				
			||||||
 | 
					                                    <SvgIcon name="QuestionFilled" :size="20" class="pointer-icon mr10" />
 | 
				
			||||||
 | 
					                                </template>
 | 
				
			||||||
 | 
					                                <div>ctrl | command + f (搜索)</div>
 | 
				
			||||||
 | 
					                                <div class="mt5">点击连接状态可重连</div>
 | 
				
			||||||
 | 
					                            </el-popover>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                            <SvgIcon
 | 
				
			||||||
 | 
					                                name="ArrowDown"
 | 
				
			||||||
 | 
					                                v-if="props.visibleMinimize"
 | 
				
			||||||
 | 
					                                @click="minimize(openTerminal.terminalId)"
 | 
				
			||||||
 | 
					                                :size="20"
 | 
				
			||||||
 | 
					                                class="pointer-icon mr10"
 | 
				
			||||||
 | 
					                                title="最小化"
 | 
				
			||||||
 | 
					                            />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                            <!-- <SvgIcon name="FullScreen" @click="handlerFullScreen(openTerminal)" :size="20" class="pointer-icon mr10" title="全屏|退出全屏" /> -->
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                            <SvgIcon name="Close" class="pointer-icon" @click="close(openTerminal.terminalId)" title="关闭" :size="20" />
 | 
				
			||||||
 | 
					                        </div>
 | 
				
			||||||
 | 
					                    </div>
 | 
				
			||||||
 | 
					                </template>
 | 
				
			||||||
 | 
					                <div class="terminal-wrapper" style="height: calc(100vh - 215px)">
 | 
				
			||||||
 | 
					                    <TerminalBody
 | 
				
			||||||
 | 
					                        @status-change="terminalStatusChange(openTerminal.terminalId, $event)"
 | 
				
			||||||
 | 
					                        :ref="(el) => setTerminalRef(el, openTerminal.terminalId)"
 | 
				
			||||||
 | 
					                        :cmd="openTerminal.cmd"
 | 
				
			||||||
 | 
					                        :socket-url="openTerminal.socketUrl"
 | 
				
			||||||
 | 
					                    />
 | 
				
			||||||
 | 
					                </div>
 | 
				
			||||||
 | 
					            </el-dialog>
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        <!-- 终端最小化 -->
 | 
				
			||||||
 | 
					        <div class="terminal-minimize-container">
 | 
				
			||||||
 | 
					            <el-card
 | 
				
			||||||
 | 
					                v-for="minimizeTerminal of minimizeTerminals"
 | 
				
			||||||
 | 
					                :key="minimizeTerminal.terminalId"
 | 
				
			||||||
 | 
					                :class="`terminal-minimize-item pointer ${minimizeTerminal.styleClass}`"
 | 
				
			||||||
 | 
					                size="small"
 | 
				
			||||||
 | 
					                @click="maximize(minimizeTerminal.terminalId)"
 | 
				
			||||||
 | 
					            >
 | 
				
			||||||
 | 
					                <el-tooltip effect="customized" :content="minimizeTerminal.desc" placement="top">
 | 
				
			||||||
 | 
					                    <span>
 | 
				
			||||||
 | 
					                        {{ minimizeTerminal.title }}
 | 
				
			||||||
 | 
					                    </span>
 | 
				
			||||||
 | 
					                </el-tooltip>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                <!-- 关闭按钮 -->
 | 
				
			||||||
 | 
					                <SvgIcon name="CloseBold" @click.stop="closeMinimizeTerminal(minimizeTerminal.terminalId)" class="ml10 pointer-icon fr" :size="20" />
 | 
				
			||||||
 | 
					            </el-card>
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<script lang="ts" setup>
 | 
				
			||||||
 | 
					import { toRefs, reactive } from 'vue';
 | 
				
			||||||
 | 
					import TerminalBody from '@/components/terminal/TerminalBody.vue';
 | 
				
			||||||
 | 
					import SvgIcon from '@/components/svgIcon/index.vue';
 | 
				
			||||||
 | 
					import { TerminalStatus } from './common';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const props = defineProps({
 | 
				
			||||||
 | 
					    visibleMinimize: {
 | 
				
			||||||
 | 
					        type: Boolean,
 | 
				
			||||||
 | 
					        default: false,
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const emit = defineEmits(['close', 'minimize']);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const openTerminalRefs: any = {};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					terminal对象信息:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					visible: false,
 | 
				
			||||||
 | 
					machineId: null as any,
 | 
				
			||||||
 | 
					terminalId: null as any,
 | 
				
			||||||
 | 
					machine: {} as any,
 | 
				
			||||||
 | 
					fullscreen: false,
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const state = reactive({
 | 
				
			||||||
 | 
					    terminals: {} as any, // key -> terminalId  value -> terminal
 | 
				
			||||||
 | 
					    minimizeTerminals: {} as any, // key -> terminalId  value -> 简易terminal
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const { terminals, minimizeTerminals } = toRefs(state);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const setTerminalRef = (el: any, terminalId: any) => {
 | 
				
			||||||
 | 
					    if (terminalId) {
 | 
				
			||||||
 | 
					        openTerminalRefs[terminalId] = el;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function open(terminalInfo: any, cmd: string = '') {
 | 
				
			||||||
 | 
					    let terminalId = terminalInfo.terminalId;
 | 
				
			||||||
 | 
					    if (!terminalId) {
 | 
				
			||||||
 | 
					        terminalId = Date.now();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    state.terminals[terminalId] = {
 | 
				
			||||||
 | 
					        ...terminalInfo,
 | 
				
			||||||
 | 
					        terminalId,
 | 
				
			||||||
 | 
					        visible: true,
 | 
				
			||||||
 | 
					        cmd,
 | 
				
			||||||
 | 
					        status: TerminalStatus.NoConnected,
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const terminalStatusChange = (terminalId: string, status: TerminalStatus) => {
 | 
				
			||||||
 | 
					    const terminal = state.terminals[terminalId];
 | 
				
			||||||
 | 
					    if (terminal) {
 | 
				
			||||||
 | 
					        terminal.status = status;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const minTerminal = state.minimizeTerminals[terminalId];
 | 
				
			||||||
 | 
					    if (!minTerminal) {
 | 
				
			||||||
 | 
					        return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    minTerminal.styleClass = getTerminalStatysStyleClass(terminalId, status);
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const getTerminalStatysStyleClass = (terminalId: any, status: any = null) => {
 | 
				
			||||||
 | 
					    if (status == null) {
 | 
				
			||||||
 | 
					        status = openTerminalRefs[terminalId].getStatus();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    if (status == TerminalStatus.Connected) {
 | 
				
			||||||
 | 
					        return 'terminal-status-success';
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (status == TerminalStatus.NoConnected) {
 | 
				
			||||||
 | 
					        return 'terminal-status-no-connect';
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return 'terminal-status-error';
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const reConnect = (terminalId: any) => {
 | 
				
			||||||
 | 
					    openTerminalRefs[terminalId].init();
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function close(terminalId: any) {
 | 
				
			||||||
 | 
					    console.log('in terminal dialog close');
 | 
				
			||||||
 | 
					    delete state.terminals[terminalId];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // 关闭终端,并删除终端ref
 | 
				
			||||||
 | 
					    const terminalRef = openTerminalRefs[terminalId];
 | 
				
			||||||
 | 
					    terminalRef && terminalRef.close();
 | 
				
			||||||
 | 
					    delete openTerminalRefs[terminalId];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    emit('close', terminalId);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function minimize(terminalId: number) {
 | 
				
			||||||
 | 
					    console.log('in terminal dialog minimize: ', terminalId);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const terminal = state.terminals[terminalId];
 | 
				
			||||||
 | 
					    if (!terminal) {
 | 
				
			||||||
 | 
					        console.warn('不存在该终端信息: ', terminalId);
 | 
				
			||||||
 | 
					        return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    terminal.visible = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const minTerminalInfo = {
 | 
				
			||||||
 | 
					        terminalId: terminal.terminalId,
 | 
				
			||||||
 | 
					        title: terminal.minTitle, // 截取terminalId最后两位区分多个terminal
 | 
				
			||||||
 | 
					        desc: terminal.minDesc,
 | 
				
			||||||
 | 
					        styleClass: getTerminalStatysStyleClass(terminalId),
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					    state.minimizeTerminals[terminalId] = minTerminalInfo;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    emit('minimize', minTerminalInfo);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function maximize(terminalId: any) {
 | 
				
			||||||
 | 
					    console.log('in terminal dialog maximize: ', terminalId);
 | 
				
			||||||
 | 
					    const minTerminal = state.minimizeTerminals[terminalId];
 | 
				
			||||||
 | 
					    if (!minTerminal) {
 | 
				
			||||||
 | 
					        console.log('no min terminal...');
 | 
				
			||||||
 | 
					        return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    delete state.minimizeTerminals[terminalId];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // 显示终端信息
 | 
				
			||||||
 | 
					    state.terminals[terminalId].visible = true;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const terminalRef = openTerminalRefs[terminalId];
 | 
				
			||||||
 | 
					    // fit
 | 
				
			||||||
 | 
					    setTimeout(() => {
 | 
				
			||||||
 | 
					        terminalRef.fitTerminal();
 | 
				
			||||||
 | 
					        terminalRef.focus();
 | 
				
			||||||
 | 
					    }, 250);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const closeMinimizeTerminal = (terminalId: any) => {
 | 
				
			||||||
 | 
					    delete state.minimizeTerminals[terminalId];
 | 
				
			||||||
 | 
					    close(terminalId);
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					defineExpose({
 | 
				
			||||||
 | 
					    open,
 | 
				
			||||||
 | 
					    close,
 | 
				
			||||||
 | 
					    minimize,
 | 
				
			||||||
 | 
					    maximize,
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<style lang="scss">
 | 
				
			||||||
 | 
					.terminal-dialog-container {
 | 
				
			||||||
 | 
					    .el-dialog__header {
 | 
				
			||||||
 | 
					        padding: 10px;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // .terminal-dialog {
 | 
				
			||||||
 | 
					    //     height: calc(100vh - 200px) !important;
 | 
				
			||||||
 | 
					    // }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    .el-overlay .el-overlay-dialog .el-dialog .el-dialog__body {
 | 
				
			||||||
 | 
					        padding: 0px !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;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.terminal-minimize-container {
 | 
				
			||||||
 | 
					    position: absolute;
 | 
				
			||||||
 | 
					    right: 16px;
 | 
				
			||||||
 | 
					    bottom: 16px;
 | 
				
			||||||
 | 
					    z-index: 10;
 | 
				
			||||||
 | 
					    display: flex;
 | 
				
			||||||
 | 
					    flex-wrap: wrap-reverse;
 | 
				
			||||||
 | 
					    justify-content: flex-end;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    .terminal-minimize-item {
 | 
				
			||||||
 | 
					        min-width: 120px;
 | 
				
			||||||
 | 
					        // box-shadow: 0 3px 4px #dee2e6;
 | 
				
			||||||
 | 
					        border-radius: 4px;
 | 
				
			||||||
 | 
					        margin: 1px 1px;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    .terminal-status-error {
 | 
				
			||||||
 | 
					        box-shadow: 0 3px 4px var(--el-color-danger);
 | 
				
			||||||
 | 
					        border-color: var(--el-color-danger);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    .terminal-status-no-connect {
 | 
				
			||||||
 | 
					        box-shadow: 0 3px 4px var(--el-color-warning);
 | 
				
			||||||
 | 
					        border-color: var(--el-color-warning);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    .terminal-status-success {
 | 
				
			||||||
 | 
					        box-shadow: 0 3px 4px var(--el-color-success);
 | 
				
			||||||
 | 
					        border-color: var(--el-color-success);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    .el-card__body {
 | 
				
			||||||
 | 
					        padding: 15px !important;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					</style>
 | 
				
			||||||
							
								
								
									
										149
									
								
								mayfly_go_web/src/components/terminal/TerminalSearch.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										149
									
								
								mayfly_go_web/src/components/terminal/TerminalSearch.vue
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,149 @@
 | 
				
			|||||||
 | 
					<template>
 | 
				
			||||||
 | 
					    <div id="search-card" v-show="search.visible" @keydown.esc="closeSearch">
 | 
				
			||||||
 | 
					        <el-card title="搜索" size="small">
 | 
				
			||||||
 | 
					            <!-- 搜索框 -->
 | 
				
			||||||
 | 
					            <el-input
 | 
				
			||||||
 | 
					                class="search-input"
 | 
				
			||||||
 | 
					                ref="searchInputRef"
 | 
				
			||||||
 | 
					                placeholder="请输入查找内容,回车搜索"
 | 
				
			||||||
 | 
					                v-model="search.value"
 | 
				
			||||||
 | 
					                @keyup.enter.native="searchKeywords(true)"
 | 
				
			||||||
 | 
					                clearable
 | 
				
			||||||
 | 
					            >
 | 
				
			||||||
 | 
					            </el-input>
 | 
				
			||||||
 | 
					            <!-- 选项 -->
 | 
				
			||||||
 | 
					            <div class="search-options">
 | 
				
			||||||
 | 
					                <el-row>
 | 
				
			||||||
 | 
					                    <el-col :span="12">
 | 
				
			||||||
 | 
					                        <el-checkbox class="usn" v-model="search.regex"> 正则匹配 </el-checkbox>
 | 
				
			||||||
 | 
					                    </el-col>
 | 
				
			||||||
 | 
					                    <el-col :span="12">
 | 
				
			||||||
 | 
					                        <el-checkbox class="usn" v-model="search.words"> 单词全匹配 </el-checkbox>
 | 
				
			||||||
 | 
					                    </el-col>
 | 
				
			||||||
 | 
					                    <el-col :span="12">
 | 
				
			||||||
 | 
					                        <el-checkbox class="usn" v-model="search.matchCase"> 区分大小写 </el-checkbox>
 | 
				
			||||||
 | 
					                    </el-col>
 | 
				
			||||||
 | 
					                    <el-col :span="12">
 | 
				
			||||||
 | 
					                        <el-checkbox class="usn" v-model="search.incremental"> 增量查找 </el-checkbox>
 | 
				
			||||||
 | 
					                    </el-col>
 | 
				
			||||||
 | 
					                </el-row>
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					            <!-- 按钮 -->
 | 
				
			||||||
 | 
					            <div class="search-buttons">
 | 
				
			||||||
 | 
					                <el-button class="terminal-search-button search-button-prev" type="primary" size="small" @click="searchKeywords(false)"> 上一个 </el-button>
 | 
				
			||||||
 | 
					                <el-button class="terminal-search-button search-button-next" type="primary" size="small" @click="searchKeywords(true)"> 下一个 </el-button>
 | 
				
			||||||
 | 
					                <el-button class="terminal-search-button search-button-next" type="primary" size="small" @click="closeSearch"> 关闭 </el-button>
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					        </el-card>
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					</template>
 | 
				
			||||||
 | 
					<script lang="ts" setup>
 | 
				
			||||||
 | 
					import { ref, toRefs, nextTick, reactive } from 'vue';
 | 
				
			||||||
 | 
					import { ElMessage } from 'element-plus';
 | 
				
			||||||
 | 
					import { SearchAddon, ISearchOptions } from 'xterm-addon-search';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const props = defineProps({
 | 
				
			||||||
 | 
					    searchAddon: {
 | 
				
			||||||
 | 
					        type: [SearchAddon],
 | 
				
			||||||
 | 
					        require: true,
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const state = reactive({
 | 
				
			||||||
 | 
					    search: {
 | 
				
			||||||
 | 
					        visible: false,
 | 
				
			||||||
 | 
					        value: '',
 | 
				
			||||||
 | 
					        regex: false,
 | 
				
			||||||
 | 
					        words: false,
 | 
				
			||||||
 | 
					        matchCase: false,
 | 
				
			||||||
 | 
					        incremental: false,
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const { search } = toRefs(state);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const emit = defineEmits(['close']);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const searchInputRef: any = ref(null);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function open() {
 | 
				
			||||||
 | 
					    const visible = state.search.visible;
 | 
				
			||||||
 | 
					    state.search.visible = !visible;
 | 
				
			||||||
 | 
					    console.log(state.search.visible);
 | 
				
			||||||
 | 
					    if (!visible) {
 | 
				
			||||||
 | 
					        nextTick(() => {
 | 
				
			||||||
 | 
					            searchInputRef.value.focus();
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function closeSearch() {
 | 
				
			||||||
 | 
					    state.search.visible = false;
 | 
				
			||||||
 | 
					    state.search.value = '';
 | 
				
			||||||
 | 
					    props.searchAddon?.clearDecorations();
 | 
				
			||||||
 | 
					    emit('close');
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function searchKeywords(direction: any) {
 | 
				
			||||||
 | 
					    if (!state.search.value) {
 | 
				
			||||||
 | 
					        return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    const option = {
 | 
				
			||||||
 | 
					        regex: state.search.regex,
 | 
				
			||||||
 | 
					        wholeWord: state.search.words,
 | 
				
			||||||
 | 
					        caseSensitive: state.search.matchCase,
 | 
				
			||||||
 | 
					        incremental: state.search.incremental,
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					    let res;
 | 
				
			||||||
 | 
					    if (direction) {
 | 
				
			||||||
 | 
					        res = props.searchAddon?.findNext(state.search.value, getSearchOptions(option));
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					        res = props.searchAddon?.findPrevious(state.search.value, getSearchOptions(option));
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    if (!res) {
 | 
				
			||||||
 | 
					        ElMessage.info('未查询到匹配项');
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const getSearchOptions = (searchOptions?: ISearchOptions): ISearchOptions => {
 | 
				
			||||||
 | 
					    return {
 | 
				
			||||||
 | 
					        ...searchOptions,
 | 
				
			||||||
 | 
					        decorations: {
 | 
				
			||||||
 | 
					            matchOverviewRuler: '#888888',
 | 
				
			||||||
 | 
					            activeMatchColorOverviewRuler: '#ffff00',
 | 
				
			||||||
 | 
					            matchBackground: '#888888',
 | 
				
			||||||
 | 
					            activeMatchBackground: '#ffff00',
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					defineExpose({ open });
 | 
				
			||||||
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<style lang="scss" scoped>
 | 
				
			||||||
 | 
					#search-card {
 | 
				
			||||||
 | 
					    position: absolute;
 | 
				
			||||||
 | 
					    top: 60px;
 | 
				
			||||||
 | 
					    right: 20px;
 | 
				
			||||||
 | 
					    z-index: 1200;
 | 
				
			||||||
 | 
					    width: 270px;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    .search-input {
 | 
				
			||||||
 | 
					        width: 240px;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    .search-options {
 | 
				
			||||||
 | 
					        margin: 12px 0;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    .search-buttons {
 | 
				
			||||||
 | 
					        margin-top: 5px;
 | 
				
			||||||
 | 
					        display: flex;
 | 
				
			||||||
 | 
					        justify-content: flex-end;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    .terminal-search-button {
 | 
				
			||||||
 | 
					        margin-left: 10px;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					</style>
 | 
				
			||||||
							
								
								
									
										6
									
								
								mayfly_go_web/src/components/terminal/common.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								mayfly_go_web/src/components/terminal/common.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,6 @@
 | 
				
			|||||||
 | 
					export enum TerminalStatus {
 | 
				
			||||||
 | 
					    Error = -1,
 | 
				
			||||||
 | 
					    NoConnected = 0,
 | 
				
			||||||
 | 
					    Connected = 1,
 | 
				
			||||||
 | 
					    Disconnected = 2,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -1,21 +1,22 @@
 | 
				
			|||||||
import type { App } from 'vue';
 | 
					import type { App } from 'vue';
 | 
				
			||||||
import { useUserInfo } from '@/store/userInfo';
 | 
					import { useUserInfo } from '@/store/userInfo';
 | 
				
			||||||
import { judementSameArr } from '@/common/utils/arrayOperation';
 | 
					import { judementSameArr } from '@/common/utils/arrayOperation';
 | 
				
			||||||
 | 
					import { hasPerm } from '@/components/auth/auth';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// 用户权限指令
 | 
					// 用户权限指令
 | 
				
			||||||
export function authDirective(app: App) {
 | 
					export function authDirective(app: App) {
 | 
				
			||||||
    // 单个权限验证(v-auth="xxx")
 | 
					    // 单个权限验证(v-auth="xxx")
 | 
				
			||||||
    app.directive('auth', {
 | 
					    app.directive('auth', {
 | 
				
			||||||
        mounted(el, binding) {
 | 
					        mounted(el, binding) {
 | 
				
			||||||
            if (!useUserInfo().userInfo.permissions.some((v: any) => v === binding.value)) {
 | 
					            if (!hasPerm(binding.value)) {
 | 
				
			||||||
                parseNoAuth(el, binding);
 | 
					                parseNoAuth(el, binding);
 | 
				
			||||||
            };
 | 
					            }
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
    // 多个权限验证,满足一个则显示(v-auths="[xxx,xxx]")
 | 
					    // 多个权限验证,满足一个则显示(v-auths="[xxx,xxx]")
 | 
				
			||||||
    app.directive('auths', {
 | 
					    app.directive('auths', {
 | 
				
			||||||
        mounted(el, binding) {
 | 
					        mounted(el, binding) {
 | 
				
			||||||
            const value = binding.value
 | 
					            const value = binding.value;
 | 
				
			||||||
            let flag = false;
 | 
					            let flag = false;
 | 
				
			||||||
            useUserInfo().userInfo.permissions.map((val: any) => {
 | 
					            useUserInfo().userInfo.permissions.map((val: any) => {
 | 
				
			||||||
                value.map((v: any) => {
 | 
					                value.map((v: any) => {
 | 
				
			||||||
@@ -32,7 +33,7 @@ export function authDirective(app: App) {
 | 
				
			|||||||
        mounted(el, binding) {
 | 
					        mounted(el, binding) {
 | 
				
			||||||
            if (!judementSameArr(binding.value, useUserInfo().userInfo.permissions)) {
 | 
					            if (!judementSameArr(binding.value, useUserInfo().userInfo.permissions)) {
 | 
				
			||||||
                parseNoAuth(el, binding);
 | 
					                parseNoAuth(el, binding);
 | 
				
			||||||
            };
 | 
					            }
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -54,8 +55,8 @@ const parseNoAuth = (el: any, binding: any) => {
 | 
				
			|||||||
        // 移除该元素
 | 
					        // 移除该元素
 | 
				
			||||||
        el.parentNode.removeChild(el);
 | 
					        el.parentNode.removeChild(el);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const disableClickFn = (event: any) => {
 | 
					const disableClickFn = (event: any) => {
 | 
				
			||||||
    event && event.stopImmediatePropagation();
 | 
					    event && event.stopImmediatePropagation();
 | 
				
			||||||
}
 | 
					};
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -44,7 +44,7 @@ export function wavesDirective(app: App) {
 | 
				
			|||||||
            el.addEventListener('mousedown', onCurrentClick, false);
 | 
					            el.addEventListener('mousedown', onCurrentClick, false);
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
        unmounted(el) {
 | 
					        unmounted(el) {
 | 
				
			||||||
            el.addEventListener('mousedown', () => { });
 | 
					            el.addEventListener('mousedown', () => {});
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -9,22 +9,20 @@ import { registElSvgIcon } from '@/common/utils/svgIcons';
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
import ElementPlus from 'element-plus';
 | 
					import ElementPlus from 'element-plus';
 | 
				
			||||||
import 'element-plus/dist/index.css';
 | 
					import 'element-plus/dist/index.css';
 | 
				
			||||||
import zhCn from 'element-plus/es/locale/lang/zh-cn'
 | 
					import 'element-plus/theme-chalk/dark/css-vars.css';
 | 
				
			||||||
 | 
					import zhCn from 'element-plus/es/locale/lang/zh-cn';
 | 
				
			||||||
import { ElMessage } from 'element-plus';
 | 
					import { ElMessage } from 'element-plus';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import '@/theme/index.scss';
 | 
					import '@/theme/index.scss';
 | 
				
			||||||
import '@/assets/font/font.css'
 | 
					import '@/assets/font/font.css';
 | 
				
			||||||
import '@/assets/iconfont/iconfont.js'
 | 
					import '@/assets/iconfont/iconfont.js';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const app = createApp(App);
 | 
					const app = createApp(App);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
registElSvgIcon(app);
 | 
					registElSvgIcon(app);
 | 
				
			||||||
directive(app);
 | 
					directive(app);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
app.use(pinia)
 | 
					app.use(pinia).use(router).use(ElementPlus, { size: globalComponentSize, locale: zhCn }).mount('#app');
 | 
				
			||||||
    .use(router)
 | 
					 | 
				
			||||||
    .use(ElementPlus, { size: globalComponentSize, locale: zhCn })
 | 
					 | 
				
			||||||
    .mount('#app');
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
// 屏蔽警告信息
 | 
					// 屏蔽警告信息
 | 
				
			||||||
app.config.warnHandler = () => null;
 | 
					app.config.warnHandler = () => null;
 | 
				
			||||||
@@ -32,8 +30,8 @@ app.config.warnHandler = () => null;
 | 
				
			|||||||
app.config.errorHandler = function (err: any, vm, info) {
 | 
					app.config.errorHandler = function (err: any, vm, info) {
 | 
				
			||||||
    // 如果是断言错误,则进行提示即可
 | 
					    // 如果是断言错误,则进行提示即可
 | 
				
			||||||
    if (err.name == 'AssertError') {
 | 
					    if (err.name == 'AssertError') {
 | 
				
			||||||
        ElMessage.error(err.message)
 | 
					        ElMessage.error(err.message);
 | 
				
			||||||
    } else {
 | 
					    } else {
 | 
				
			||||||
        console.error(err, info)
 | 
					        console.error(err, info);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					};
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -2,9 +2,9 @@ import { createRouter, createWebHashHistory, RouteRecordRaw } from 'vue-router';
 | 
				
			|||||||
import NProgress from 'nprogress';
 | 
					import NProgress from 'nprogress';
 | 
				
			||||||
import 'nprogress/nprogress.css';
 | 
					import 'nprogress/nprogress.css';
 | 
				
			||||||
import { getSession, clearSession } from '@/common/utils/storage';
 | 
					import { getSession, clearSession } from '@/common/utils/storage';
 | 
				
			||||||
import { templateResolve } from '@/common/utils/string'
 | 
					import { templateResolve } from '@/common/utils/string';
 | 
				
			||||||
import { NextLoading } from '@/common/utils/loading';
 | 
					import { NextLoading } from '@/common/utils/loading';
 | 
				
			||||||
import { dynamicRoutes, staticRoutes, pathMatch } from './route'
 | 
					import { dynamicRoutes, staticRoutes, pathMatch } from './route';
 | 
				
			||||||
import openApi from '@/common/openApi';
 | 
					import openApi from '@/common/openApi';
 | 
				
			||||||
import sockets from '@/common/sockets';
 | 
					import sockets from '@/common/sockets';
 | 
				
			||||||
import pinia from '@/store/index';
 | 
					import pinia from '@/store/index';
 | 
				
			||||||
@@ -18,7 +18,7 @@ import { useKeepALiveNames } from '@/store/keepAliveNames';
 | 
				
			|||||||
 * @method import.meta.glob
 | 
					 * @method import.meta.glob
 | 
				
			||||||
 * @link 参考:https://cn.vitejs.dev/guide/features.html#json
 | 
					 * @link 参考:https://cn.vitejs.dev/guide/features.html#json
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
const viewsModules: any = import.meta.glob('../views/**/*.{vue,tsx}');
 | 
					const viewsModules: any = import.meta.glob(['../views/**/*.{vue,tsx}', '!../views/layout/**/*.{vue,tsx}']);
 | 
				
			||||||
const dynamicViewsModules: Record<string, Function> = Object.assign({}, { ...viewsModules });
 | 
					const dynamicViewsModules: Record<string, Function> = Object.assign({}, { ...viewsModules });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// 添加静态路由
 | 
					// 添加静态路由
 | 
				
			||||||
@@ -33,17 +33,17 @@ export function initAllFun() {
 | 
				
			|||||||
    const token = getSession('token'); // 获取浏览器缓存 token 值
 | 
					    const token = getSession('token'); // 获取浏览器缓存 token 值
 | 
				
			||||||
    if (!token) {
 | 
					    if (!token) {
 | 
				
			||||||
        // 无 token 停止执行下一步
 | 
					        // 无 token 停止执行下一步
 | 
				
			||||||
        return false
 | 
					        return false;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    useUserInfo().setUserInfo({});
 | 
					    useUserInfo().setUserInfo({});
 | 
				
			||||||
    router.addRoute(pathMatch); // 添加404界面
 | 
					    router.addRoute(pathMatch); // 添加404界面
 | 
				
			||||||
    resetRoute(); // 删除/重置路由
 | 
					    resetRoute(); // 删除/重置路由
 | 
				
			||||||
    // 添加动态路由
 | 
					    // 添加动态路由
 | 
				
			||||||
    setFilterRouteEnd().forEach((route: any) => {
 | 
					    setFilterRouteEnd().forEach((route: any) => {
 | 
				
			||||||
        router.addRoute((route as unknown) as RouteRecordRaw);
 | 
					        router.addRoute(route as unknown as RouteRecordRaw);
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
    // 过滤权限菜单
 | 
					    // 过滤权限菜单
 | 
				
			||||||
    useRoutesList().setRoutesList(setFilterMenuFun(dynamicRoutes[0].children, useUserInfo().userInfo.menus))
 | 
					    useRoutesList().setRoutesList(setFilterMenuFun(dynamicRoutes[0].children, useUserInfo().userInfo.menus));
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// 后端控制路由:执行路由数据初始化
 | 
					// 后端控制路由:执行路由数据初始化
 | 
				
			||||||
@@ -52,7 +52,7 @@ export async function initBackEndControlRoutesFun() {
 | 
				
			|||||||
    const token = getSession('token'); // 获取浏览器缓存 token 值
 | 
					    const token = getSession('token'); // 获取浏览器缓存 token 值
 | 
				
			||||||
    if (!token) {
 | 
					    if (!token) {
 | 
				
			||||||
        // 无 token 停止执行下一步
 | 
					        // 无 token 停止执行下一步
 | 
				
			||||||
        return false
 | 
					        return false;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    useUserInfo().setUserInfo({});
 | 
					    useUserInfo().setUserInfo({});
 | 
				
			||||||
    // 获取路由
 | 
					    // 获取路由
 | 
				
			||||||
@@ -63,59 +63,59 @@ export async function initBackEndControlRoutesFun() {
 | 
				
			|||||||
    resetRoute(); // 删除/重置路由
 | 
					    resetRoute(); // 删除/重置路由
 | 
				
			||||||
    // 添加动态路由
 | 
					    // 添加动态路由
 | 
				
			||||||
    formatTwoStageRoutes(formatFlatteningRoutes(dynamicRoutes)).forEach((route: any) => {
 | 
					    formatTwoStageRoutes(formatFlatteningRoutes(dynamicRoutes)).forEach((route: any) => {
 | 
				
			||||||
        router.addRoute((route as unknown) as RouteRecordRaw);
 | 
					        router.addRoute(route as unknown as RouteRecordRaw);
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
    useRoutesList().setRoutesList(dynamicRoutes[0].children)
 | 
					    useRoutesList().setRoutesList(dynamicRoutes[0].children);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// 后端控制路由,isRequestRoutes 为 true,则开启后端控制路由
 | 
					// 后端控制路由,isRequestRoutes 为 true,则开启后端控制路由
 | 
				
			||||||
export async function getBackEndControlRoutes() {
 | 
					export async function getBackEndControlRoutes() {
 | 
				
			||||||
    try {
 | 
					    try {
 | 
				
			||||||
        const menuAndPermission = await openApi.getPermissions.request();
 | 
					        const menuAndPermission = await openApi.getPermissions();
 | 
				
			||||||
        // 赋值权限码,用于控制按钮等
 | 
					        // 赋值权限码,用于控制按钮等
 | 
				
			||||||
        useUserInfo().userInfo.permissions = menuAndPermission.permissions;
 | 
					        useUserInfo().userInfo.permissions = menuAndPermission.permissions;
 | 
				
			||||||
        return menuAndPermission.menus;
 | 
					        return menuAndPermission.menus;
 | 
				
			||||||
    } catch (e: any) {
 | 
					    } catch (e: any) {
 | 
				
			||||||
        console.error(e);
 | 
					        console.error(e);
 | 
				
			||||||
        return []
 | 
					        return [];
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// 后端控制路由,后端返回路由 转换为vue route
 | 
					// 后端控制路由,后端返回路由 转换为vue route
 | 
				
			||||||
export function backEndRouterConverter(routes: any, parentPath: string = "/") {
 | 
					export function backEndRouterConverter(routes: any, parentPath: string = '/') {
 | 
				
			||||||
    if (!routes) return;
 | 
					    if (!routes) return;
 | 
				
			||||||
    return routes.map((item: any) => {
 | 
					    return routes.map((item: any) => {
 | 
				
			||||||
        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);
 | 
				
			||||||
            delete item.meta['component']
 | 
					            delete item.meta['component'];
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        // route.path == resource.code
 | 
					        // route.path == resource.code
 | 
				
			||||||
        let path = item.code
 | 
					        let path = item.code;
 | 
				
			||||||
        // 如果不是以 / 开头,则路径需要拼接父路径
 | 
					        // 如果不是以 / 开头,则路径需要拼接父路径
 | 
				
			||||||
        if (!path.startsWith("/")) {
 | 
					        if (!path.startsWith('/')) {
 | 
				
			||||||
            path = parentPath + "/" + path;
 | 
					            path = parentPath + '/' + path;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        item.path = path
 | 
					        item.path = path;
 | 
				
			||||||
        delete item['code']
 | 
					        delete item['code'];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // route.meta.title == resource.name
 | 
					        // route.meta.title == resource.name
 | 
				
			||||||
        item.meta.title = item.name
 | 
					        item.meta.title = item.name;
 | 
				
			||||||
        delete item['name']
 | 
					        delete item['name'];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // route.name == resource.meta.routeName
 | 
					        // route.name == resource.meta.routeName
 | 
				
			||||||
        item.name = item.meta.routeName
 | 
					        item.name = item.meta.routeName;
 | 
				
			||||||
        delete item.meta['routeName']
 | 
					        delete item.meta['routeName'];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // route.redirect == resource.meta.redirect
 | 
					        // route.redirect == resource.meta.redirect
 | 
				
			||||||
        if (item.meta.redirect) {
 | 
					        if (item.meta.redirect) {
 | 
				
			||||||
            item.redirect = item.meta.redirect
 | 
					            item.redirect = item.meta.redirect;
 | 
				
			||||||
            delete item.meta['redirect']
 | 
					            delete item.meta['redirect'];
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        item.children && backEndRouterConverter(item.children, item.path);
 | 
					        item.children && backEndRouterConverter(item.children, item.path);
 | 
				
			||||||
        return item;
 | 
					        return item;
 | 
				
			||||||
@@ -178,9 +178,9 @@ export function formatTwoStageRoutes(arr: any) {
 | 
				
			|||||||
// 判断路由code 是否包含当前登录用户menus字段中,menus为字符串code数组
 | 
					// 判断路由code 是否包含当前登录用户menus字段中,menus为字符串code数组
 | 
				
			||||||
export function hasAnth(menus: any, route: any) {
 | 
					export function hasAnth(menus: any, route: any) {
 | 
				
			||||||
    if (route.meta && route.meta.code) {
 | 
					    if (route.meta && route.meta.code) {
 | 
				
			||||||
        return menus.includes(route.meta.code)
 | 
					        return menus.includes(route.meta.code);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    return true
 | 
					    return true;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// 递归过滤有权限的路由
 | 
					// 递归过滤有权限的路由
 | 
				
			||||||
@@ -190,7 +190,7 @@ export function setFilterMenuFun(routes: any, menus: any) {
 | 
				
			|||||||
        const item = { ...route };
 | 
					        const item = { ...route };
 | 
				
			||||||
        if (hasAnth(menus, item)) {
 | 
					        if (hasAnth(menus, item)) {
 | 
				
			||||||
            if (item.children) {
 | 
					            if (item.children) {
 | 
				
			||||||
                item.children = setFilterMenuFun(item.children, menus)
 | 
					                item.children = setFilterMenuFun(item.children, menus);
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            menu.push(item);
 | 
					            menu.push(item);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
@@ -206,11 +206,11 @@ export function setFilterRoute(chil: any) {
 | 
				
			|||||||
        if (route.meta.code) {
 | 
					        if (route.meta.code) {
 | 
				
			||||||
            useUserInfo().userInfo.menus.forEach((m: any) => {
 | 
					            useUserInfo().userInfo.menus.forEach((m: any) => {
 | 
				
			||||||
                if (route.meta.code == m) {
 | 
					                if (route.meta.code == m) {
 | 
				
			||||||
                    filterRoute.push({ ...route })
 | 
					                    filterRoute.push({ ...route });
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            })
 | 
					            });
 | 
				
			||||||
        } else {
 | 
					        } else {
 | 
				
			||||||
            filterRoute.push({ ...route })
 | 
					            filterRoute.push({ ...route });
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
    return filterRoute;
 | 
					    return filterRoute;
 | 
				
			||||||
@@ -253,11 +253,11 @@ router.beforeEach(async (to, from, next) => {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    // 如果有标题参数,则再原标题后加上参数来区别
 | 
					    // 如果有标题参数,则再原标题后加上参数来区别
 | 
				
			||||||
    if (to.meta.titleRename) {
 | 
					    if (to.meta.titleRename) {
 | 
				
			||||||
        to.meta.title = templateResolve(to.meta.title as string, to.query)
 | 
					        to.meta.title = templateResolve(to.meta.title as string, to.query);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const token = getSession('token');
 | 
					    const token = getSession('token');
 | 
				
			||||||
    if (to.path === '/login' && !token) {
 | 
					    if ((to.path === '/login' || to.path == '/oauth2/callback') && !token) {
 | 
				
			||||||
        next();
 | 
					        next();
 | 
				
			||||||
        NProgress.done();
 | 
					        NProgress.done();
 | 
				
			||||||
        return;
 | 
					        return;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,5 +1,5 @@
 | 
				
			|||||||
import { RouteRecordRaw } from 'vue-router';
 | 
					import { RouteRecordRaw } from 'vue-router';
 | 
				
			||||||
import Layout from '@/views/layout/index.vue'
 | 
					import Layout from '@/views/layout/index.vue';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// 定义动态路由
 | 
					// 定义动态路由
 | 
				
			||||||
export const dynamicRoutes = [
 | 
					export const dynamicRoutes = [
 | 
				
			||||||
@@ -11,7 +11,7 @@ export const dynamicRoutes = [
 | 
				
			|||||||
        meta: {
 | 
					        meta: {
 | 
				
			||||||
            isKeepAlive: true,
 | 
					            isKeepAlive: true,
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
        children: []
 | 
					        children: [],
 | 
				
			||||||
        //     children: [
 | 
					        //     children: [
 | 
				
			||||||
        //         {
 | 
					        //         {
 | 
				
			||||||
        //             path: '/home',
 | 
					        //             path: '/home',
 | 
				
			||||||
@@ -143,6 +143,14 @@ export const staticRoutes: Array<RouteRecordRaw> = [
 | 
				
			|||||||
            title: '没有权限',
 | 
					            title: '没有权限',
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        path: '/oauth2/callback',
 | 
				
			||||||
 | 
					        name: 'oauth2Callback',
 | 
				
			||||||
 | 
					        component: () => import('@/views/oauth/Oauth2Callback.vue'),
 | 
				
			||||||
 | 
					        meta: {
 | 
				
			||||||
 | 
					            title: 'oauth2回调',
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        path: '/machine/terminal',
 | 
					        path: '/machine/terminal',
 | 
				
			||||||
        name: 'machineTerminal',
 | 
					        name: 'machineTerminal',
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -24,13 +24,13 @@ export const useThemeConfig = defineStore('themeConfig', {
 | 
				
			|||||||
            // 默认顶栏导航背景颜色,请注意:需要同时修改 `/@/theme/common/var.scss` 对应的值
 | 
					            // 默认顶栏导航背景颜色,请注意:需要同时修改 `/@/theme/common/var.scss` 对应的值
 | 
				
			||||||
            topBar: '#ffffff',
 | 
					            topBar: '#ffffff',
 | 
				
			||||||
            // 默认菜单导航背景颜色,请注意:需要同时修改 `/@/theme/common/var.scss` 对应的值
 | 
					            // 默认菜单导航背景颜色,请注意:需要同时修改 `/@/theme/common/var.scss` 对应的值
 | 
				
			||||||
            menuBar: '#545c64',
 | 
					            menuBar: '#FFFFFF',
 | 
				
			||||||
            // 默认分栏菜单背景颜色,请注意:需要同时修改 `/@/theme/common/var.scss` 对应的值
 | 
					            // 默认分栏菜单背景颜色,请注意:需要同时修改 `/@/theme/common/var.scss` 对应的值
 | 
				
			||||||
            columnsMenuBar: '#545c64',
 | 
					            columnsMenuBar: '#545c64',
 | 
				
			||||||
            // 默认顶栏导航字体颜色,请注意:需要同时修改 `/@/theme/common/var.scss` 对应的值
 | 
					            // 默认顶栏导航字体颜色,请注意:需要同时修改 `/@/theme/common/var.scss` 对应的值
 | 
				
			||||||
            topBarColor: '#606266',
 | 
					            topBarColor: '#606266',
 | 
				
			||||||
            // 默认菜单导航字体颜色,请注意:需要同时修改 `/@/theme/common/var.scss` 对应的值
 | 
					            // 默认菜单导航字体颜色,请注意:需要同时修改 `/@/theme/common/var.scss` 对应的值
 | 
				
			||||||
            menuBarColor: '#eaeaea',
 | 
					            menuBarColor: '#606266',
 | 
				
			||||||
            // 默认分栏菜单字体颜色,请注意:需要同时修改 `/@/theme/common/var.scss` 对应的值
 | 
					            // 默认分栏菜单字体颜色,请注意:需要同时修改 `/@/theme/common/var.scss` 对应的值
 | 
				
			||||||
            columnsMenuBarColor: '#e6e6e6',
 | 
					            columnsMenuBarColor: '#e6e6e6',
 | 
				
			||||||
            // 是否开启顶栏背景颜色渐变
 | 
					            // 是否开启顶栏背景颜色渐变
 | 
				
			||||||
@@ -81,6 +81,8 @@ export const useThemeConfig = defineStore('themeConfig', {
 | 
				
			|||||||
            isSortableTagsView: true,
 | 
					            isSortableTagsView: true,
 | 
				
			||||||
            // 是否开启 Footer 底部版权信息
 | 
					            // 是否开启 Footer 底部版权信息
 | 
				
			||||||
            isFooter: false,
 | 
					            isFooter: false,
 | 
				
			||||||
 | 
					            // 是否暗模式
 | 
				
			||||||
 | 
					            isDark: false,
 | 
				
			||||||
            // 是否开启灰色模式
 | 
					            // 是否开启灰色模式
 | 
				
			||||||
            isGrayscale: false,
 | 
					            isGrayscale: false,
 | 
				
			||||||
            // 是否开启色弱模式
 | 
					            // 是否开启色弱模式
 | 
				
			||||||
@@ -116,7 +118,6 @@ export const useThemeConfig = defineStore('themeConfig', {
 | 
				
			|||||||
            // 编辑器主题
 | 
					            // 编辑器主题
 | 
				
			||||||
            editorTheme: 'vs',
 | 
					            editorTheme: 'vs',
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 | 
				
			||||||
            /* 后端控制路由
 | 
					            /* 后端控制路由
 | 
				
			||||||
            ------------------------------- */
 | 
					            ------------------------------- */
 | 
				
			||||||
            // 是否开启后端控制路由
 | 
					            // 是否开启后端控制路由
 | 
				
			||||||
@@ -140,4 +141,4 @@ export const useThemeConfig = defineStore('themeConfig', {
 | 
				
			|||||||
            this.themeConfig = data.themeConfig;
 | 
					            this.themeConfig = data.themeConfig;
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
})
 | 
					});
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -8,12 +8,12 @@ export const useUserInfo = defineStore('userInfo', {
 | 
				
			|||||||
    actions: {
 | 
					    actions: {
 | 
				
			||||||
        // 设置用户信息
 | 
					        // 设置用户信息
 | 
				
			||||||
        async setUserInfo(data: object) {
 | 
					        async setUserInfo(data: object) {
 | 
				
			||||||
            const ui = getSession('userInfo')
 | 
					            const ui = getSession('userInfo');
 | 
				
			||||||
            if (ui) {
 | 
					            if (ui) {
 | 
				
			||||||
                this.userInfo = ui;
 | 
					                this.userInfo = ui;
 | 
				
			||||||
            } else {
 | 
					            } else {
 | 
				
			||||||
                this.userInfo = data;
 | 
					                this.userInfo = data;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
    }
 | 
					    },
 | 
				
			||||||
})
 | 
					});
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -7,6 +7,24 @@
 | 
				
			|||||||
    outline: none !important;
 | 
					    outline: none !important;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					:root {
 | 
				
			||||||
 | 
						--color-white: #ffffff;
 | 
				
			||||||
 | 
						--bg-main-color: #f8f8f8;
 | 
				
			||||||
 | 
						--bg-color: #f5f5ff;
 | 
				
			||||||
 | 
					    --bg-menuBarActiveColor: #0000000a;  // 菜单栏激活时的背景色
 | 
				
			||||||
 | 
						--border-color-light: #f1f2f3;
 | 
				
			||||||
 | 
						--el-color-primary-lighter: #ecf5ff;
 | 
				
			||||||
 | 
						--color-success-lighter: #f0f9eb;
 | 
				
			||||||
 | 
						--color-warning-lighter: #fdf6ec;
 | 
				
			||||||
 | 
						--color-danger-lighter: #fef0f0;
 | 
				
			||||||
 | 
						--color-dark-hover: #0000001a;
 | 
				
			||||||
 | 
						--color-menu-hover: rgba(0, 0, 0, 0.2);
 | 
				
			||||||
 | 
						--color-user-hover: rgba(0, 0, 0, 0.04);
 | 
				
			||||||
 | 
						--color-seting-main: #e9eef3;
 | 
				
			||||||
 | 
						--color-seting-aside: #d3dce6;
 | 
				
			||||||
 | 
						--color-seting-header: #b3c0d1;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
html,
 | 
					html,
 | 
				
			||||||
body,
 | 
					body,
 | 
				
			||||||
#app {
 | 
					#app {
 | 
				
			||||||
@@ -18,7 +36,7 @@ body,
 | 
				
			|||||||
    font-weight: 450;
 | 
					    font-weight: 450;
 | 
				
			||||||
    -webkit-font-smoothing: antialiased;
 | 
					    -webkit-font-smoothing: antialiased;
 | 
				
			||||||
    -webkit-tap-highlight-color: transparent;
 | 
					    -webkit-tap-highlight-color: transparent;
 | 
				
			||||||
    background-color: #f8f8f8;
 | 
					    background-color: var(--bg-main-color);
 | 
				
			||||||
    font-size: 14px;
 | 
					    font-size: 14px;
 | 
				
			||||||
    overflow: hidden;
 | 
					    overflow: hidden;
 | 
				
			||||||
    position: relative;
 | 
					    position: relative;
 | 
				
			||||||
@@ -53,7 +71,7 @@ body,
 | 
				
			|||||||
        padding: 0 !important;
 | 
					        padding: 0 !important;
 | 
				
			||||||
        overflow: hidden;
 | 
					        overflow: hidden;
 | 
				
			||||||
        width: 100%;
 | 
					        width: 100%;
 | 
				
			||||||
        background-color: #f8f8f8;
 | 
					        background-color: var(--bg-main-color);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    .el-scrollbar {
 | 
					    .el-scrollbar {
 | 
				
			||||||
@@ -65,11 +83,11 @@ body,
 | 
				
			|||||||
        width: 100%;
 | 
					        width: 100%;
 | 
				
			||||||
        height: 100%;
 | 
					        height: 100%;
 | 
				
			||||||
        border-radius: 4px;
 | 
					        border-radius: 4px;
 | 
				
			||||||
        border: 1px solid #ebeef5;
 | 
					        border: 1px solid var(--el-border-color-light, #ebeef5);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    .layout-el-aside-br-color {
 | 
					    .layout-el-aside-br-color {
 | 
				
			||||||
        border-right: 1px solid rgb(238, 238, 238);
 | 
					        border-right: 1px solid var(--el-border-color-light, #ebeef5);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    .layout-aside-width-default {
 | 
					    .layout-aside-width-default {
 | 
				
			||||||
@@ -116,7 +134,7 @@ body,
 | 
				
			|||||||
        display: flex;
 | 
					        display: flex;
 | 
				
			||||||
        align-items: center;
 | 
					        align-items: center;
 | 
				
			||||||
        margin-bottom: 0 !important;
 | 
					        margin-bottom: 0 !important;
 | 
				
			||||||
        border-bottom: 1px solid rgb(230, 230, 230);
 | 
					        border-bottom: 1px solid var(--el-border-color-light, #ebeef5);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    .el-divider {
 | 
					    .el-divider {
 | 
				
			||||||
@@ -128,7 +146,7 @@ body,
 | 
				
			|||||||
------------------------------- */
 | 
					------------------------------- */
 | 
				
			||||||
#nprogress {
 | 
					#nprogress {
 | 
				
			||||||
    .bar {
 | 
					    .bar {
 | 
				
			||||||
        background: var(--color-primary) !important;
 | 
					        background: var(--el-color-primary) !important;
 | 
				
			||||||
        z-index: 9999999 !important;
 | 
					        z-index: 9999999 !important;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -195,23 +213,23 @@ body,
 | 
				
			|||||||
/* 颜色值
 | 
					/* 颜色值
 | 
				
			||||||
------------------------------- */
 | 
					------------------------------- */
 | 
				
			||||||
.color-primary {
 | 
					.color-primary {
 | 
				
			||||||
    color: var(--color-primary);
 | 
					    color: var(--el-color-primary);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.color-success {
 | 
					.color-success {
 | 
				
			||||||
    color: var(--color-success);
 | 
					    color: var(--el-color-success);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.color-warning {
 | 
					.color-warning {
 | 
				
			||||||
    color: var(--color-warning);
 | 
					    color: var(--el-color-warning);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.color-danger {
 | 
					.color-danger {
 | 
				
			||||||
    color: var(--color-danger);
 | 
					    color: var(--el-color-danger);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.color-info {
 | 
					.color-info {
 | 
				
			||||||
    color: var(--color-info);
 | 
					    color: var(--el-color-info);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/* 字体大小全局样式
 | 
					/* 字体大小全局样式
 | 
				
			||||||
@@ -262,17 +280,17 @@ body,
 | 
				
			|||||||
::-webkit-scrollbar {
 | 
					::-webkit-scrollbar {
 | 
				
			||||||
    width: 4px;
 | 
					    width: 4px;
 | 
				
			||||||
    height: 8px;
 | 
					    height: 8px;
 | 
				
			||||||
    background-color: #F5F5F5;
 | 
					    background-color: var(--el-border-color-light, #ebeef5);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
::-webkit-scrollbar-track {
 | 
					::-webkit-scrollbar-track {
 | 
				
			||||||
    -webkit-box-shadow: inset 0 0 6px rgba(0, 0, 0, 0.3);
 | 
					    -webkit-box-shadow: inset 0 0 6px rgba(0, 0, 0, 0.3);
 | 
				
			||||||
    background-color: #F5F5F5;
 | 
					    background-color: var(--el-border-color-light, #ebeef5);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
::-webkit-scrollbar-thumb {
 | 
					::-webkit-scrollbar-thumb {
 | 
				
			||||||
    -webkit-box-shadow: inset 0 0 6px rgba(0, 0, 0, .3);
 | 
					    -webkit-box-shadow: inset 0 0 6px rgba(0, 0, 0, .3);
 | 
				
			||||||
    background-color: #F5F5F5;
 | 
					    background-color: var(--el-border-color-light, #ebeef5);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.el-menu .fa {
 | 
					.el-menu .fa {
 | 
				
			||||||
@@ -317,11 +335,10 @@ body,
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
.toolbar {
 | 
					.toolbar {
 | 
				
			||||||
    width: 100%;
 | 
					    width: 100%;
 | 
				
			||||||
    padding: 6px;
 | 
					    padding: 4px;
 | 
				
			||||||
    background-color: #ffffff;
 | 
					 | 
				
			||||||
    overflow: hidden;
 | 
					    overflow: hidden;
 | 
				
			||||||
    line-height: 32px;
 | 
					    line-height: 24px;
 | 
				
			||||||
    border: 1px solid #e6ebf5;
 | 
					    border: 1px solid var(--el-border-color-light, #ebeef5);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.fl {
 | 
					.fl {
 | 
				
			||||||
@@ -345,3 +362,15 @@ body,
 | 
				
			|||||||
.f12 {
 | 
					.f12 {
 | 
				
			||||||
    font-size: 12px
 | 
					    font-size: 12px
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.pointer {
 | 
				
			||||||
 | 
					    cursor: pointer;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.pointer-icon {
 | 
				
			||||||
 | 
					    cursor: pointer;
 | 
				
			||||||
 | 
					    transition: color 0.3s;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					.pointer-icon:hover {
 | 
				
			||||||
 | 
					    color: var(--el-color-primary); /* 鼠标移动到图标时的颜色 */
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -1,2 +1 @@
 | 
				
			|||||||
@import 'common/transition.scss';
 | 
					@import 'common/transition.scss';
 | 
				
			||||||
@import 'common/var.scss';
 | 
					 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,129 +0,0 @@
 | 
				
			|||||||
/**
 | 
					 | 
				
			||||||
* scss 怎么动态创建变量
 | 
					 | 
				
			||||||
* 本来想用 @function,@for 好像不可以动态创建
 | 
					 | 
				
			||||||
* 2020.12.19 lyt 记录
 | 
					 | 
				
			||||||
**/
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/* 定义初始颜色
 | 
					 | 
				
			||||||
------------------------------- */
 | 
					 | 
				
			||||||
$--color-primary: #409eff !default;
 | 
					 | 
				
			||||||
$--color-whites: #ffffff !default;
 | 
					 | 
				
			||||||
$--color-blacks: #000000 !default;
 | 
					 | 
				
			||||||
$--color-primary-light-1: mix($--color-whites, $--color-primary, 10%) !default;
 | 
					 | 
				
			||||||
$--color-primary-light-2: mix($--color-whites, $--color-primary, 20%) !default;
 | 
					 | 
				
			||||||
$--color-primary-light-3: mix($--color-whites, $--color-primary, 30%) !default;
 | 
					 | 
				
			||||||
$--color-primary-light-4: mix($--color-whites, $--color-primary, 40%) !default;
 | 
					 | 
				
			||||||
$--color-primary-light-5: mix($--color-whites, $--color-primary, 50%) !default;
 | 
					 | 
				
			||||||
$--color-primary-light-6: mix($--color-whites, $--color-primary, 60%) !default;
 | 
					 | 
				
			||||||
$--color-primary-light-7: mix($--color-whites, $--color-primary, 70%) !default;
 | 
					 | 
				
			||||||
$--color-primary-light-8: mix($--color-whites, $--color-primary, 80%) !default;
 | 
					 | 
				
			||||||
$--color-primary-light-9: mix($--color-whites, $--color-primary, 90%) !default;
 | 
					 | 
				
			||||||
$--color-success: #67c23a !default;
 | 
					 | 
				
			||||||
$--color-success-light-1: mix($--color-whites, $--color-success, 10%) !default;
 | 
					 | 
				
			||||||
$--color-success-light-2: mix($--color-whites, $--color-success, 20%) !default;
 | 
					 | 
				
			||||||
$--color-success-light-3: mix($--color-whites, $--color-success, 30%) !default;
 | 
					 | 
				
			||||||
$--color-success-light-4: mix($--color-whites, $--color-success, 40%) !default;
 | 
					 | 
				
			||||||
$--color-success-light-5: mix($--color-whites, $--color-success, 50%) !default;
 | 
					 | 
				
			||||||
$--color-success-light-6: mix($--color-whites, $--color-success, 60%) !default;
 | 
					 | 
				
			||||||
$--color-success-light-7: mix($--color-whites, $--color-success, 70%) !default;
 | 
					 | 
				
			||||||
$--color-success-light-8: mix($--color-whites, $--color-success, 80%) !default;
 | 
					 | 
				
			||||||
$--color-success-light-9: mix($--color-whites, $--color-success, 90%) !default;
 | 
					 | 
				
			||||||
$--color-info: #909399 !default;
 | 
					 | 
				
			||||||
$--color-info-light-1: mix($--color-whites, $--color-info, 10%) !default;
 | 
					 | 
				
			||||||
$--color-info-light-2: mix($--color-whites, $--color-info, 20%) !default;
 | 
					 | 
				
			||||||
$--color-info-light-3: mix($--color-whites, $--color-info, 30%) !default;
 | 
					 | 
				
			||||||
$--color-info-light-4: mix($--color-whites, $--color-info, 40%) !default;
 | 
					 | 
				
			||||||
$--color-info-light-5: mix($--color-whites, $--color-info, 50%) !default;
 | 
					 | 
				
			||||||
$--color-info-light-6: mix($--color-whites, $--color-info, 60%) !default;
 | 
					 | 
				
			||||||
$--color-info-light-7: mix($--color-whites, $--color-info, 70%) !default;
 | 
					 | 
				
			||||||
$--color-info-light-8: mix($--color-whites, $--color-info, 80%) !default;
 | 
					 | 
				
			||||||
$--color-info-light-9: mix($--color-whites, $--color-info, 90%) !default;
 | 
					 | 
				
			||||||
$--color-warning: #e6a23c !default;
 | 
					 | 
				
			||||||
$--color-warning-light-1: mix($--color-whites, $--color-warning, 10%) !default;
 | 
					 | 
				
			||||||
$--color-warning-light-2: mix($--color-whites, $--color-warning, 20%) !default;
 | 
					 | 
				
			||||||
$--color-warning-light-3: mix($--color-whites, $--color-warning, 30%) !default;
 | 
					 | 
				
			||||||
$--color-warning-light-4: mix($--color-whites, $--color-warning, 40%) !default;
 | 
					 | 
				
			||||||
$--color-warning-light-5: mix($--color-whites, $--color-warning, 50%) !default;
 | 
					 | 
				
			||||||
$--color-warning-light-6: mix($--color-whites, $--color-warning, 60%) !default;
 | 
					 | 
				
			||||||
$--color-warning-light-7: mix($--color-whites, $--color-warning, 70%) !default;
 | 
					 | 
				
			||||||
$--color-warning-light-8: mix($--color-whites, $--color-warning, 80%) !default;
 | 
					 | 
				
			||||||
$--color-warning-light-9: mix($--color-whites, $--color-warning, 90%) !default;
 | 
					 | 
				
			||||||
$--color-danger: #f56c6c !default;
 | 
					 | 
				
			||||||
$--color-danger-light-1: mix($--color-whites, $--color-danger, 10%) !default;
 | 
					 | 
				
			||||||
$--color-danger-light-2: mix($--color-whites, $--color-danger, 20%) !default;
 | 
					 | 
				
			||||||
$--color-danger-light-3: mix($--color-whites, $--color-danger, 30%) !default;
 | 
					 | 
				
			||||||
$--color-danger-light-4: mix($--color-whites, $--color-danger, 40%) !default;
 | 
					 | 
				
			||||||
$--color-danger-light-5: mix($--color-whites, $--color-danger, 50%) !default;
 | 
					 | 
				
			||||||
$--color-danger-light-6: mix($--color-whites, $--color-danger, 60%) !default;
 | 
					 | 
				
			||||||
$--color-danger-light-7: mix($--color-whites, $--color-danger, 70%) !default;
 | 
					 | 
				
			||||||
$--color-danger-light-8: mix($--color-whites, $--color-danger, 80%) !default;
 | 
					 | 
				
			||||||
$--color-danger-light-9: mix($--color-whites, $--color-danger, 90%) !default;
 | 
					 | 
				
			||||||
$--bg-topBar: #ffffff;
 | 
					 | 
				
			||||||
$--bg-menuBar: #545c64;
 | 
					 | 
				
			||||||
$--bg-columnsMenuBar: #545c64;
 | 
					 | 
				
			||||||
$--bg-topBarColor: #606266;
 | 
					 | 
				
			||||||
$--bg-menuBarColor: #eaeaea;
 | 
					 | 
				
			||||||
$--bg-columnsMenuBarColor: #e6e6e6;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/* 赋值给:root
 | 
					 | 
				
			||||||
------------------------------- */
 | 
					 | 
				
			||||||
:root {
 | 
					 | 
				
			||||||
	--color-primary: #{$--color-primary};
 | 
					 | 
				
			||||||
	--color-whites: #{$--color-whites};
 | 
					 | 
				
			||||||
	--color-blacks: #{$--color-blacks};
 | 
					 | 
				
			||||||
	--color-primary-light-1: #{$--color-primary-light-1};
 | 
					 | 
				
			||||||
	--color-primary-light-2: #{$--color-primary-light-2};
 | 
					 | 
				
			||||||
	--color-primary-light-3: #{$--color-primary-light-3};
 | 
					 | 
				
			||||||
	--color-primary-light-4: #{$--color-primary-light-4};
 | 
					 | 
				
			||||||
	--color-primary-light-5: #{$--color-primary-light-5};
 | 
					 | 
				
			||||||
	--color-primary-light-6: #{$--color-primary-light-6};
 | 
					 | 
				
			||||||
	--color-primary-light-7: #{$--color-primary-light-7};
 | 
					 | 
				
			||||||
	--color-primary-light-8: #{$--color-primary-light-8};
 | 
					 | 
				
			||||||
	--color-primary-light-9: #{$--color-primary-light-9};
 | 
					 | 
				
			||||||
	--color-success: #{$--color-success};
 | 
					 | 
				
			||||||
	--color-success-light-1: #{$--color-success-light-1};
 | 
					 | 
				
			||||||
	--color-success-light-2: #{$--color-success-light-2};
 | 
					 | 
				
			||||||
	--color-success-light-3: #{$--color-success-light-3};
 | 
					 | 
				
			||||||
	--color-success-light-4: #{$--color-success-light-4};
 | 
					 | 
				
			||||||
	--color-success-light-5: #{$--color-success-light-5};
 | 
					 | 
				
			||||||
	--color-success-light-6: #{$--color-success-light-6};
 | 
					 | 
				
			||||||
	--color-success-light-7: #{$--color-success-light-7};
 | 
					 | 
				
			||||||
	--color-success-light-8: #{$--color-success-light-8};
 | 
					 | 
				
			||||||
	--color-success-light-9: #{$--color-success-light-9};
 | 
					 | 
				
			||||||
	--color-info: #{$--color-info};
 | 
					 | 
				
			||||||
	--color-info-light-1: #{$--color-info-light-1};
 | 
					 | 
				
			||||||
	--color-info-light-2: #{$--color-info-light-2};
 | 
					 | 
				
			||||||
	--color-info-light-3: #{$--color-info-light-3};
 | 
					 | 
				
			||||||
	--color-info-light-4: #{$--color-info-light-4};
 | 
					 | 
				
			||||||
	--color-info-light-5: #{$--color-info-light-5};
 | 
					 | 
				
			||||||
	--color-info-light-6: #{$--color-info-light-6};
 | 
					 | 
				
			||||||
	--color-info-light-7: #{$--color-info-light-7};
 | 
					 | 
				
			||||||
	--color-info-light-8: #{$--color-info-light-8};
 | 
					 | 
				
			||||||
	--color-info-light-9: #{$--color-info-light-9};
 | 
					 | 
				
			||||||
	--color-warning: #{$--color-warning};
 | 
					 | 
				
			||||||
	--color-warning-light-1: #{$--color-warning-light-1};
 | 
					 | 
				
			||||||
	--color-warning-light-2: #{$--color-warning-light-2};
 | 
					 | 
				
			||||||
	--color-warning-light-3: #{$--color-warning-light-3};
 | 
					 | 
				
			||||||
	--color-warning-light-4: #{$--color-warning-light-4};
 | 
					 | 
				
			||||||
	--color-warning-light-5: #{$--color-warning-light-5};
 | 
					 | 
				
			||||||
	--color-warning-light-6: #{$--color-warning-light-6};
 | 
					 | 
				
			||||||
	--color-warning-light-7: #{$--color-warning-light-7};
 | 
					 | 
				
			||||||
	--color-warning-light-8: #{$--color-warning-light-8};
 | 
					 | 
				
			||||||
	--color-warning-light-9: #{$--color-warning-light-9};
 | 
					 | 
				
			||||||
	--color-danger: #{$--color-danger};
 | 
					 | 
				
			||||||
	--color-danger-light-1: #{$--color-danger-light-1};
 | 
					 | 
				
			||||||
	--color-danger-light-2: #{$--color-danger-light-2};
 | 
					 | 
				
			||||||
	--color-danger-light-3: #{$--color-danger-light-3};
 | 
					 | 
				
			||||||
	--color-danger-light-4: #{$--color-danger-light-4};
 | 
					 | 
				
			||||||
	--color-danger-light-5: #{$--color-danger-light-5};
 | 
					 | 
				
			||||||
	--color-danger-light-6: #{$--color-danger-light-6};
 | 
					 | 
				
			||||||
	--color-danger-light-7: #{$--color-danger-light-7};
 | 
					 | 
				
			||||||
	--color-danger-light-8: #{$--color-danger-light-8};
 | 
					 | 
				
			||||||
	--color-danger-light-9: #{$--color-danger-light-9};
 | 
					 | 
				
			||||||
	--bg-topBar: #{$--bg-topBar};
 | 
					 | 
				
			||||||
	--bg-menuBar: #{$--bg-menuBar};
 | 
					 | 
				
			||||||
	--bg-columnsMenuBar: #{$--bg-columnsMenuBar};
 | 
					 | 
				
			||||||
	--bg-topBarColor: #{$--bg-topBarColor};
 | 
					 | 
				
			||||||
	--bg-menuBarColor: #{$--bg-menuBarColor};
 | 
					 | 
				
			||||||
	--bg-columnsMenuBarColor: #{$--bg-columnsMenuBarColor};
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
							
								
								
									
										27
									
								
								mayfly_go_web/src/theme/dark.scss
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								mayfly_go_web/src/theme/dark.scss
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,27 @@
 | 
				
			|||||||
 | 
					html.dark {
 | 
				
			||||||
 | 
					    // 变量(自定义时,只需修改这里的值)
 | 
				
			||||||
 | 
						--next-bg-main: #1f1f1f;
 | 
				
			||||||
 | 
						--next-color-white: #ffffff;
 | 
				
			||||||
 | 
						--next-color-disabled: #191919;
 | 
				
			||||||
 | 
						--next-color-bar: #dadada;
 | 
				
			||||||
 | 
						--next-color-primary: #303030;
 | 
				
			||||||
 | 
						--next-border-color: #424242;
 | 
				
			||||||
 | 
						--next-border-black: #333333;
 | 
				
			||||||
 | 
						--next-border-columns: #2a2a2a;
 | 
				
			||||||
 | 
						--next-color-seting: #505050;
 | 
				
			||||||
 | 
						--next-text-color-regular: #9b9da1;
 | 
				
			||||||
 | 
						--next-text-color-placeholder: #7a7a7a;
 | 
				
			||||||
 | 
						--next-color-hover: #3c3c3c;
 | 
				
			||||||
 | 
						--next-color-hover-rgba: rgba(0, 0, 0, 0.3);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /* 自定义深色背景颜色 */
 | 
				
			||||||
 | 
					    // root
 | 
				
			||||||
 | 
						--bg-main-color: var(--next-bg-main) !important;
 | 
				
			||||||
 | 
						--bg-topBar: var(--next-color-disabled) !important;
 | 
				
			||||||
 | 
						--bg-topBarColor: var(--next-color-bar) !important;
 | 
				
			||||||
 | 
						--bg-menuBar: var(--next-color-disabled) !important;
 | 
				
			||||||
 | 
						--bg-menuBarColor: var(--next-color-bar) !important;
 | 
				
			||||||
 | 
						--bg-menuBarActiveColor: var(--next-color-hover-rgba) !important;
 | 
				
			||||||
 | 
						--bg-columnsMenuBar: var(--next-color-disabled) !important;
 | 
				
			||||||
 | 
						--bg-columnsMenuBarColor: var(--next-color-bar) !important;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							@@ -4,4 +4,5 @@
 | 
				
			|||||||
@import './element.scss';
 | 
					@import './element.scss';
 | 
				
			||||||
@import './media/media.scss';
 | 
					@import './media/media.scss';
 | 
				
			||||||
@import './waves.scss';
 | 
					@import './waves.scss';
 | 
				
			||||||
 | 
					@import './dark.scss';
 | 
				
			||||||
@import './iconSelector.scss';
 | 
					@import './iconSelector.scss';
 | 
				
			||||||
@@ -15,7 +15,7 @@
 | 
				
			|||||||
.loading-next .loading-next-box-warp .loading-next-box-item {
 | 
					.loading-next .loading-next-box-warp .loading-next-box-item {
 | 
				
			||||||
	width: 33.333333%;
 | 
						width: 33.333333%;
 | 
				
			||||||
	height: 33.333333%;
 | 
						height: 33.333333%;
 | 
				
			||||||
	background: var(--color-primary);
 | 
						background: var(--el-color-primary);
 | 
				
			||||||
	float: left;
 | 
						float: left;
 | 
				
			||||||
	animation: loading-next-animation 1.2s infinite ease;
 | 
						animation: loading-next-animation 1.2s infinite ease;
 | 
				
			||||||
	border-radius: 1px;
 | 
						border-radius: 1px;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -9,7 +9,7 @@
 | 
				
			|||||||
		height: 3px !important;
 | 
							height: 3px !important;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	::-webkit-scrollbar-track-piece {
 | 
						::-webkit-scrollbar-track-piece {
 | 
				
			||||||
		background-color: #f8f8f8;
 | 
							background-color: var(--bg-main-color);
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	// 滚动条的设置
 | 
						// 滚动条的设置
 | 
				
			||||||
	::-webkit-scrollbar-thumb {
 | 
						::-webkit-scrollbar-thumb {
 | 
				
			||||||
@@ -40,7 +40,7 @@
 | 
				
			|||||||
		height: 7px;
 | 
							height: 7px;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	::-webkit-scrollbar-track-piece {
 | 
						::-webkit-scrollbar-track-piece {
 | 
				
			||||||
		background-color: #f8f8f8;
 | 
							background-color: var(--bg-main-color);
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	// 滚动条的设置
 | 
						// 滚动条的设置
 | 
				
			||||||
	::-webkit-scrollbar-thumb {
 | 
						::-webkit-scrollbar-thumb {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,34 +0,0 @@
 | 
				
			|||||||
/* Button 按钮
 | 
					 | 
				
			||||||
------------------------------- */
 | 
					 | 
				
			||||||
@mixin Button($main, $c1, $c2) {
 | 
					 | 
				
			||||||
	color: set-color($main);
 | 
					 | 
				
			||||||
	background: set-color($c1);
 | 
					 | 
				
			||||||
	border-color: set-color($c2);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/* Radio 单选框、Checkbox 多选框
 | 
					 | 
				
			||||||
------------------------------- */
 | 
					 | 
				
			||||||
@mixin RadioCheckbox($name) {
 | 
					 | 
				
			||||||
	background-color: set-color($name);
 | 
					 | 
				
			||||||
	border-color: set-color($name);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/* Tag 标签
 | 
					 | 
				
			||||||
------------------------------- */
 | 
					 | 
				
			||||||
@mixin Tag($main, $c1, $c2) {
 | 
					 | 
				
			||||||
	color: set-color($main);
 | 
					 | 
				
			||||||
	background-color: set-color($c1);
 | 
					 | 
				
			||||||
	border-color: set-color($c2);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@mixin TagDark($main, $c1) {
 | 
					 | 
				
			||||||
	color: set-color($main);
 | 
					 | 
				
			||||||
	background-color: set-color($c1);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/* Alert 警告
 | 
					 | 
				
			||||||
------------------------------- */
 | 
					 | 
				
			||||||
@mixin Alert($main, $c1, $c2) {
 | 
					 | 
				
			||||||
	color: set-color($main);
 | 
					 | 
				
			||||||
	background: set-color($c1);
 | 
					 | 
				
			||||||
	border: 1px solid set-color($c2);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@@ -1,5 +0,0 @@
 | 
				
			|||||||
/* 颜色调用函数
 | 
					 | 
				
			||||||
------------------------------- */
 | 
					 | 
				
			||||||
@function set-color($key) {
 | 
					 | 
				
			||||||
	@return var(--color-#{$key});
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@@ -1,3 +1,15 @@
 | 
				
			|||||||
 | 
					/* 第三方图标字体间距/大小设置
 | 
				
			||||||
 | 
					------------------------------- */
 | 
				
			||||||
 | 
					@mixin generalIcon {
 | 
				
			||||||
 | 
						font-size: 14px !important;
 | 
				
			||||||
 | 
						display: inline-block;
 | 
				
			||||||
 | 
						vertical-align: middle;
 | 
				
			||||||
 | 
						margin-right: 5px;
 | 
				
			||||||
 | 
						width: 24px;
 | 
				
			||||||
 | 
						text-align: center;
 | 
				
			||||||
 | 
						justify-content: center;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/* 文本不换行
 | 
					/* 文本不换行
 | 
				
			||||||
------------------------------- */
 | 
					------------------------------- */
 | 
				
			||||||
@mixin text-no-wrap() {
 | 
					@mixin text-no-wrap() {
 | 
				
			||||||
							
								
								
									
										3
									
								
								mayfly_go_web/src/types/pinia.d.ts
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										3
									
								
								mayfly_go_web/src/types/pinia.d.ts
									
									
									
									
										vendored
									
									
								
							@@ -1,5 +1,5 @@
 | 
				
			|||||||
declare interface UserInfoState<T = any> {
 | 
					declare interface UserInfoState<T = any> {
 | 
				
			||||||
    userInfo: any
 | 
					    userInfo: any;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
declare interface ThemeConfigState {
 | 
					declare interface ThemeConfigState {
 | 
				
			||||||
@@ -37,6 +37,7 @@ declare interface ThemeConfigState {
 | 
				
			|||||||
        isCacheTagsView: boolean;
 | 
					        isCacheTagsView: boolean;
 | 
				
			||||||
        isSortableTagsView: boolean;
 | 
					        isSortableTagsView: boolean;
 | 
				
			||||||
        isFooter: boolean;
 | 
					        isFooter: boolean;
 | 
				
			||||||
 | 
					        isDark: boolean;
 | 
				
			||||||
        isGrayscale: boolean;
 | 
					        isGrayscale: boolean;
 | 
				
			||||||
        isInvert: boolean;
 | 
					        isInvert: boolean;
 | 
				
			||||||
        isWartermark: boolean;
 | 
					        isWartermark: boolean;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -7,8 +7,7 @@
 | 
				
			|||||||
                        <img :src="userInfo.photo" />
 | 
					                        <img :src="userInfo.photo" />
 | 
				
			||||||
                        <div class="home-card-first-right ml15">
 | 
					                        <div class="home-card-first-right ml15">
 | 
				
			||||||
                            <div class="flex-margin">
 | 
					                            <div class="flex-margin">
 | 
				
			||||||
                                <div class="home-card-first-right-title">{{ `${currentTime}, ${userInfo.username}`
 | 
					                                <div class="home-card-first-right-title">{{ `${currentTime}, ${userInfo.username}` }}</div>
 | 
				
			||||||
                                }}</div>
 | 
					 | 
				
			||||||
                            </div>
 | 
					                            </div>
 | 
				
			||||||
                        </div>
 | 
					                        </div>
 | 
				
			||||||
                    </div>
 | 
					                    </div>
 | 
				
			||||||
@@ -31,7 +30,7 @@
 | 
				
			|||||||
import { toRefs, reactive, onMounted, nextTick, computed } from 'vue';
 | 
					import { toRefs, reactive, onMounted, nextTick, computed } from 'vue';
 | 
				
			||||||
// import * as echarts from 'echarts';
 | 
					// import * as echarts from 'echarts';
 | 
				
			||||||
import { CountUp } from 'countup.js';
 | 
					import { CountUp } from 'countup.js';
 | 
				
			||||||
import { formatAxis } from '@/common/utils/format.ts';
 | 
					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';
 | 
				
			||||||
@@ -65,9 +64,7 @@ const state = reactive({
 | 
				
			|||||||
    ],
 | 
					    ],
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const {
 | 
					const { topCardItemList } = toRefs(state);
 | 
				
			||||||
    topCardItemList,
 | 
					 | 
				
			||||||
} = toRefs(state)
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
// 当前时间提示语
 | 
					// 当前时间提示语
 | 
				
			||||||
const currentTime = computed(() => {
 | 
					const currentTime = computed(() => {
 | 
				
			||||||
@@ -179,8 +176,8 @@ onMounted(() => {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    .home-card-first {
 | 
					    .home-card-first {
 | 
				
			||||||
        background: white;
 | 
					        background: var(--bg-main-color);
 | 
				
			||||||
        border: 1px solid #ebeef5;
 | 
					        border: 1px solid var(--el-border-color-light, #ebeef5);
 | 
				
			||||||
        display: flex;
 | 
					        display: flex;
 | 
				
			||||||
        align-items: center;
 | 
					        align-items: center;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -188,7 +185,7 @@ onMounted(() => {
 | 
				
			|||||||
            width: 60px;
 | 
					            width: 60px;
 | 
				
			||||||
            height: 60px;
 | 
					            height: 60px;
 | 
				
			||||||
            border-radius: 100%;
 | 
					            border-radius: 100%;
 | 
				
			||||||
            border: 2px solid var(--color-primary-light-5);
 | 
					            border: 2px solid var(--el-color-primary-light-5);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        .home-card-first-right {
 | 
					        .home-card-first-right {
 | 
				
			||||||
@@ -247,7 +244,8 @@ onMounted(() => {
 | 
				
			|||||||
            .home-dynamic-item-left {
 | 
					            .home-dynamic-item-left {
 | 
				
			||||||
                text-align: right;
 | 
					                text-align: right;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                .home-dynamic-item-left-time1 {}
 | 
					                .home-dynamic-item-left-time1 {
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                .home-dynamic-item-left-time2 {
 | 
					                .home-dynamic-item-left-time2 {
 | 
				
			||||||
                    font-size: 13px;
 | 
					                    font-size: 13px;
 | 
				
			||||||
@@ -262,7 +260,7 @@ onMounted(() => {
 | 
				
			|||||||
                position: relative;
 | 
					                position: relative;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                i {
 | 
					                i {
 | 
				
			||||||
                    color: var(--color-primary);
 | 
					                    color: var(--el-color-primary);
 | 
				
			||||||
                    font-size: 12px;
 | 
					                    font-size: 12px;
 | 
				
			||||||
                    position: absolute;
 | 
					                    position: absolute;
 | 
				
			||||||
                    top: 1px;
 | 
					                    top: 1px;
 | 
				
			||||||
@@ -284,7 +282,7 @@ onMounted(() => {
 | 
				
			|||||||
                        border-radius: 100%;
 | 
					                        border-radius: 100%;
 | 
				
			||||||
                        padding: 3px 2px 2px;
 | 
					                        padding: 3px 2px 2px;
 | 
				
			||||||
                        text-align: center;
 | 
					                        text-align: center;
 | 
				
			||||||
                        color: var(--color-primary);
 | 
					                        color: var(--el-color-primary);
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -2,11 +2,18 @@
 | 
				
			|||||||
    <div class="layout-columns-aside">
 | 
					    <div class="layout-columns-aside">
 | 
				
			||||||
        <el-scrollbar>
 | 
					        <el-scrollbar>
 | 
				
			||||||
            <ul>
 | 
					            <ul>
 | 
				
			||||||
                <li v-for="(v, k) in state.columnsAsideList" :key="k" @click="onColumnsAsideMenuClick(v, k)" :ref="
 | 
					                <li
 | 
				
			||||||
 | 
					                    v-for="(v, k) in state.columnsAsideList"
 | 
				
			||||||
 | 
					                    :key="k"
 | 
				
			||||||
 | 
					                    @click="onColumnsAsideMenuClick(v, k)"
 | 
				
			||||||
 | 
					                    :ref="
 | 
				
			||||||
                        (el) => {
 | 
					                        (el) => {
 | 
				
			||||||
                            if (el) columnsAsideOffsetTopRefs[k] = el;
 | 
					                            if (el) columnsAsideOffsetTopRefs[k] = el;
 | 
				
			||||||
                        }
 | 
					                        }
 | 
				
			||||||
                " :class="{ 'layout-columns-active': state.liIndex === k }" :title="v.meta.title">
 | 
					                    "
 | 
				
			||||||
 | 
					                    :class="{ 'layout-columns-active': state.liIndex === k }"
 | 
				
			||||||
 | 
					                    :title="v.meta.title"
 | 
				
			||||||
 | 
					                >
 | 
				
			||||||
                    <div class="layout-columns-aside-li-box" v-if="!v.meta.link || (v.meta.link && v.meta.linkType == 1)">
 | 
					                    <div class="layout-columns-aside-li-box" v-if="!v.meta.link || (v.meta.link && v.meta.linkType == 1)">
 | 
				
			||||||
                        <i :class="v.meta.icon"></i>
 | 
					                        <i :class="v.meta.icon"></i>
 | 
				
			||||||
                        <div class="layout-columns-aside-li-box-title font12">
 | 
					                        <div class="layout-columns-aside-li-box-title font12">
 | 
				
			||||||
@@ -166,7 +173,7 @@ onBeforeRouteUpdate((to) => {
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        .columns-round {
 | 
					        .columns-round {
 | 
				
			||||||
            background: var(--color-primary);
 | 
					            background: var(--el-color-primary);
 | 
				
			||||||
            color: #ffffff;
 | 
					            color: #ffffff;
 | 
				
			||||||
            position: absolute;
 | 
					            position: absolute;
 | 
				
			||||||
            left: 50%;
 | 
					            left: 50%;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -67,7 +67,7 @@ import { storeToRefs } from 'pinia';
 | 
				
			|||||||
import { useThemeConfig } from '@/store/themeConfig';
 | 
					import { useThemeConfig } from '@/store/themeConfig';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// 定义变量内容
 | 
					// 定义变量内容
 | 
				
			||||||
const layoutLockScreenDateRef = ref<null>();
 | 
					const layoutLockScreenDateRef = ref<any>();
 | 
				
			||||||
const layoutLockScreenInputRef = ref();
 | 
					const layoutLockScreenInputRef = ref();
 | 
				
			||||||
const storesThemeConfig = useThemeConfig();
 | 
					const storesThemeConfig = useThemeConfig();
 | 
				
			||||||
const { themeConfig } = storeToRefs(storesThemeConfig);
 | 
					const { themeConfig } = storeToRefs(storesThemeConfig);
 | 
				
			||||||
@@ -213,7 +213,7 @@ onUnmounted(() => {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
.layout-lock-screen-img {
 | 
					.layout-lock-screen-img {
 | 
				
			||||||
    @extend .layout-lock-screen-fixed;
 | 
					    @extend .layout-lock-screen-fixed;
 | 
				
			||||||
	background-image: url('@/assets/image/bg-login.png');
 | 
					    background: url('@/assets/image/bg-login.png') no-repeat;
 | 
				
			||||||
    background-size: 100% 100%;
 | 
					    background-size: 100% 100%;
 | 
				
			||||||
    z-index: 9999991;
 | 
					    z-index: 9999991;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -3,7 +3,9 @@
 | 
				
			|||||||
        <img src="@/assets/image/logo.svg" class="layout-logo-medium-img" />
 | 
					        <img src="@/assets/image/logo.svg" class="layout-logo-medium-img" />
 | 
				
			||||||
        <span>
 | 
					        <span>
 | 
				
			||||||
            {{ `${themeConfig.globalTitle}` }}
 | 
					            {{ `${themeConfig.globalTitle}` }}
 | 
				
			||||||
            <sub><span style="font-size: 10px;color:goldenrod">{{ ` ${config.version}` }}</span></sub>
 | 
					            <sub
 | 
				
			||||||
 | 
					                ><span style="font-size: 10px; color: goldenrod">{{ ` ${config.version}` }}</span></sub
 | 
				
			||||||
 | 
					            >
 | 
				
			||||||
        </span>
 | 
					        </span>
 | 
				
			||||||
    </div>
 | 
					    </div>
 | 
				
			||||||
    <div class="layout-logo-size" v-else @click="onThemeConfigChange">
 | 
					    <div class="layout-logo-size" v-else @click="onThemeConfigChange">
 | 
				
			||||||
@@ -41,14 +43,14 @@ const onThemeConfigChange = () => {
 | 
				
			|||||||
    align-items: center;
 | 
					    align-items: center;
 | 
				
			||||||
    justify-content: center;
 | 
					    justify-content: center;
 | 
				
			||||||
    box-shadow: rgb(0 21 41 / 2%) 0px 1px 4px;
 | 
					    box-shadow: rgb(0 21 41 / 2%) 0px 1px 4px;
 | 
				
			||||||
    color: var(--color-primary);
 | 
					    color: var(--el-color-primary);
 | 
				
			||||||
    font-size: 16px;
 | 
					    font-size: 16px;
 | 
				
			||||||
    cursor: pointer;
 | 
					    cursor: pointer;
 | 
				
			||||||
    animation: logoAnimation 0.3s ease-in-out;
 | 
					    animation: logoAnimation 0.3s ease-in-out;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    &:hover {
 | 
					    &:hover {
 | 
				
			||||||
        span {
 | 
					        span {
 | 
				
			||||||
            color: var(--color-primary-light-2);
 | 
					            color: var(--el-color-primary-light-2);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -32,7 +32,7 @@ export default {
 | 
				
			|||||||
        watch(
 | 
					        watch(
 | 
				
			||||||
            () => route.path,
 | 
					            () => route.path,
 | 
				
			||||||
            () => {
 | 
					            () => {
 | 
				
			||||||
                proxy.$refs.layoutDefaultsScrollbarRef.wrap$.scrollTop = 0;
 | 
					                proxy.$refs.layoutScrollbarRef.wrapRef.scrollTop = 0;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        );
 | 
					        );
 | 
				
			||||||
        return {
 | 
					        return {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -20,7 +20,6 @@ import Logo from '@/views/layout/logo/index.vue';
 | 
				
			|||||||
import Horizontal from '@/views/layout/navMenu/horizontal.vue';
 | 
					import Horizontal from '@/views/layout/navMenu/horizontal.vue';
 | 
				
			||||||
import mittBus from '@/common/utils/mitt';
 | 
					import mittBus from '@/common/utils/mitt';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 | 
				
			||||||
const { themeConfig } = storeToRefs(useThemeConfig());
 | 
					const { themeConfig } = storeToRefs(useThemeConfig());
 | 
				
			||||||
const { routesList } = storeToRefs(useRoutesList());
 | 
					const { routesList } = storeToRefs(useRoutesList());
 | 
				
			||||||
const route = useRoute();
 | 
					const route = useRoute();
 | 
				
			||||||
@@ -106,6 +105,6 @@ onUnmounted(() => {
 | 
				
			|||||||
    padding-right: 15px;
 | 
					    padding-right: 15px;
 | 
				
			||||||
    background: var(--bg-topBar);
 | 
					    background: var(--bg-topBar);
 | 
				
			||||||
    overflow: hidden;
 | 
					    overflow: hidden;
 | 
				
			||||||
    border-bottom: 1px solid #f1f2f3;
 | 
					    border-bottom: 1px solid var(--el-border-color-light, #ebeef5);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
</style>
 | 
					</style>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,47 +1,48 @@
 | 
				
			|||||||
<template>
 | 
					<template>
 | 
				
			||||||
    <div class="layout-breadcrumb-seting">
 | 
					    <div class="layout-breadcrumb-seting">
 | 
				
			||||||
        <el-drawer title="布局设置" v-model="themeConfig.isDrawer" direction="rtl" destroy-on-close size="240px"
 | 
					        <el-drawer title="布局设置" v-model="themeConfig.isDrawer" direction="rtl" destroy-on-close size="240px" @close="onDrawerClose">
 | 
				
			||||||
            @close="onDrawerClose">
 | 
					 | 
				
			||||||
            <el-scrollbar class="layout-breadcrumb-seting-bar">
 | 
					            <el-scrollbar class="layout-breadcrumb-seting-bar">
 | 
				
			||||||
                <!-- 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"
 | 
					                        <el-color-picker v-model="themeConfig.terminalForeground" size="small" @change="onColorPickerChange('terminalForeground')">
 | 
				
			||||||
                            @change="onColorPickerChange('terminalForeground')">
 | 
					 | 
				
			||||||
                        </el-color-picker>
 | 
					                        </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">背景颜色</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"
 | 
					                        <el-color-picker v-model="themeConfig.terminalBackground" size="small" @change="onColorPickerChange('terminalBackground')">
 | 
				
			||||||
                            @change="onColorPickerChange('terminalBackground')">
 | 
					 | 
				
			||||||
                        </el-color-picker>
 | 
					                        </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">cursor颜色</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.terminalCursor" size="small"
 | 
					                        <el-color-picker v-model="themeConfig.terminalCursor" size="small" @change="onColorPickerChange('terminalCursor')"> </el-color-picker>
 | 
				
			||||||
                            @change="onColorPickerChange('terminalCursor')">
 | 
					 | 
				
			||||||
                        </el-color-picker>
 | 
					 | 
				
			||||||
                    </div>
 | 
					                    </div>
 | 
				
			||||||
                </div>
 | 
					                </div>
 | 
				
			||||||
                <div class="layout-breadcrumb-seting-bar-flex mt15">
 | 
					                <div class="layout-breadcrumb-seting-bar-flex mt15">
 | 
				
			||||||
                    <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 v-model="themeConfig.terminalFontSize" controls-position="right" :min="12"
 | 
					                        <el-input-number
 | 
				
			||||||
                            :max="24" @change="setLocalThemeConfig" size="small" style="width: 90px">
 | 
					                            v-model="themeConfig.terminalFontSize"
 | 
				
			||||||
 | 
					                            controls-position="right"
 | 
				
			||||||
 | 
					                            :min="12"
 | 
				
			||||||
 | 
					                            :max="24"
 | 
				
			||||||
 | 
					                            @change="setLocalThemeConfig"
 | 
				
			||||||
 | 
					                            size="small"
 | 
				
			||||||
 | 
					                            style="width: 90px"
 | 
				
			||||||
 | 
					                        >
 | 
				
			||||||
                        </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 mt15">
 | 
				
			||||||
                    <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"
 | 
					                        <el-select @change="setLocalThemeConfig" v-model="themeConfig.terminalFontWeight" size="small" style="width: 90px">
 | 
				
			||||||
                            style="width: 90px">
 | 
					 | 
				
			||||||
                            <el-option label="normal" value="normal"> </el-option>
 | 
					                            <el-option label="normal" value="normal"> </el-option>
 | 
				
			||||||
                            <el-option label="bold" value="bold"> </el-option>
 | 
					                            <el-option label="bold" value="bold"> </el-option>
 | 
				
			||||||
                        </el-select>
 | 
					                        </el-select>
 | 
				
			||||||
@@ -52,8 +53,7 @@
 | 
				
			|||||||
                <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-select @change="setLocalThemeConfig" v-model="themeConfig.editorTheme" size="small"
 | 
					                        <el-select @change="setLocalThemeConfig" v-model="themeConfig.editorTheme" size="small" style="width: 130px">
 | 
				
			||||||
                            style="width: 130px">
 | 
					 | 
				
			||||||
                            <el-option label="vs" value="vs"> </el-option>
 | 
					                            <el-option label="vs" value="vs"> </el-option>
 | 
				
			||||||
                            <el-option label="vs-dark" value="vs-dark"> </el-option>
 | 
					                            <el-option label="vs-dark" value="vs-dark"> </el-option>
 | 
				
			||||||
                            <el-option label="SolarizedLight" value="SolarizedLight"> </el-option>
 | 
					                            <el-option label="SolarizedLight" value="SolarizedLight"> </el-option>
 | 
				
			||||||
@@ -66,36 +66,31 @@
 | 
				
			|||||||
                <div class="layout-breadcrumb-seting-bar-flex">
 | 
					                <div class="layout-breadcrumb-seting-bar-flex">
 | 
				
			||||||
                    <div class="layout-breadcrumb-seting-bar-flex-label">primary</div>
 | 
					                    <div class="layout-breadcrumb-seting-bar-flex-label">primary</div>
 | 
				
			||||||
                    <div class="layout-breadcrumb-seting-bar-flex-value">
 | 
					                    <div class="layout-breadcrumb-seting-bar-flex-value">
 | 
				
			||||||
                        <el-color-picker v-model="themeConfig.primary" size="small"
 | 
					                        <el-color-picker v-model="themeConfig.primary" size="small" @change="onColorPickerChange('primary')"> </el-color-picker>
 | 
				
			||||||
                            @change="onColorPickerChange('primary')"> </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">success</div>
 | 
					                    <div class="layout-breadcrumb-seting-bar-flex-label">success</div>
 | 
				
			||||||
                    <div class="layout-breadcrumb-seting-bar-flex-value">
 | 
					                    <div class="layout-breadcrumb-seting-bar-flex-value">
 | 
				
			||||||
                        <el-color-picker v-model="themeConfig.success" size="small"
 | 
					                        <el-color-picker v-model="themeConfig.success" size="small" @change="onColorPickerChange('success')"> </el-color-picker>
 | 
				
			||||||
                            @change="onColorPickerChange('success')"> </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">info</div>
 | 
					                    <div class="layout-breadcrumb-seting-bar-flex-label">info</div>
 | 
				
			||||||
                    <div class="layout-breadcrumb-seting-bar-flex-value">
 | 
					                    <div class="layout-breadcrumb-seting-bar-flex-value">
 | 
				
			||||||
                        <el-color-picker v-model="themeConfig.info" size="small" @change="onColorPickerChange('info')">
 | 
					                        <el-color-picker v-model="themeConfig.info" size="small" @change="onColorPickerChange('info')"> </el-color-picker>
 | 
				
			||||||
                        </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">warning</div>
 | 
					                    <div class="layout-breadcrumb-seting-bar-flex-label">warning</div>
 | 
				
			||||||
                    <div class="layout-breadcrumb-seting-bar-flex-value">
 | 
					                    <div class="layout-breadcrumb-seting-bar-flex-value">
 | 
				
			||||||
                        <el-color-picker v-model="themeConfig.warning" size="small"
 | 
					                        <el-color-picker v-model="themeConfig.warning" size="small" @change="onColorPickerChange('warning')"> </el-color-picker>
 | 
				
			||||||
                            @change="onColorPickerChange('warning')"> </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">danger</div>
 | 
					                    <div class="layout-breadcrumb-seting-bar-flex-label">danger</div>
 | 
				
			||||||
                    <div class="layout-breadcrumb-seting-bar-flex-value">
 | 
					                    <div class="layout-breadcrumb-seting-bar-flex-value">
 | 
				
			||||||
                        <el-color-picker v-model="themeConfig.danger" size="small" @change="onColorPickerChange('danger')">
 | 
					                        <el-color-picker v-model="themeConfig.danger" size="small" @change="onColorPickerChange('danger')"> </el-color-picker>
 | 
				
			||||||
                        </el-color-picker>
 | 
					 | 
				
			||||||
                    </div>
 | 
					                    </div>
 | 
				
			||||||
                </div>
 | 
					                </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -104,46 +99,37 @@
 | 
				
			|||||||
                <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.topBar" size="small"
 | 
					                        <el-color-picker v-model="themeConfig.topBar" size="small" @change="onBgColorPickerChange('topBar')"> </el-color-picker>
 | 
				
			||||||
                            @change="onBgColorPickerChange('topBar')"> </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">菜单背景</div>
 | 
				
			||||||
                    <div class="layout-breadcrumb-seting-bar-flex-value">
 | 
					                    <div class="layout-breadcrumb-seting-bar-flex-value">
 | 
				
			||||||
                        <el-color-picker v-model="themeConfig.menuBar" size="small"
 | 
					                        <el-color-picker v-model="themeConfig.menuBar" size="small" @change="onBgColorPickerChange('menuBar')"> </el-color-picker>
 | 
				
			||||||
                            @change="onBgColorPickerChange('menuBar')"> </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">分栏菜单背景</div>
 | 
				
			||||||
                    <div class="layout-breadcrumb-seting-bar-flex-value">
 | 
					                    <div class="layout-breadcrumb-seting-bar-flex-value">
 | 
				
			||||||
                        <el-color-picker v-model="themeConfig.columnsMenuBar" size="small"
 | 
					                        <el-color-picker v-model="themeConfig.columnsMenuBar" size="small" @change="onBgColorPickerChange('columnsMenuBar')"> </el-color-picker>
 | 
				
			||||||
                            @change="onBgColorPickerChange('columnsMenuBar')">
 | 
					 | 
				
			||||||
                        </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">顶栏默认字体颜色</div>
 | 
				
			||||||
                    <div class="layout-breadcrumb-seting-bar-flex-value">
 | 
					                    <div class="layout-breadcrumb-seting-bar-flex-value">
 | 
				
			||||||
                        <el-color-picker v-model="themeConfig.topBarColor" size="small"
 | 
					                        <el-color-picker v-model="themeConfig.topBarColor" size="small" @change="onBgColorPickerChange('topBarColor')"> </el-color-picker>
 | 
				
			||||||
                            @change="onBgColorPickerChange('topBarColor')">
 | 
					 | 
				
			||||||
                        </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">菜单默认字体颜色</div>
 | 
				
			||||||
                    <div class="layout-breadcrumb-seting-bar-flex-value">
 | 
					                    <div class="layout-breadcrumb-seting-bar-flex-value">
 | 
				
			||||||
                        <el-color-picker v-model="themeConfig.menuBarColor" size="small"
 | 
					                        <el-color-picker v-model="themeConfig.menuBarColor" size="small" @change="onBgColorPickerChange('menuBarColor')"> </el-color-picker>
 | 
				
			||||||
                            @change="onBgColorPickerChange('menuBarColor')">
 | 
					 | 
				
			||||||
                        </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">分栏菜单默认字体颜色</div>
 | 
				
			||||||
                    <div class="layout-breadcrumb-seting-bar-flex-value">
 | 
					                    <div class="layout-breadcrumb-seting-bar-flex-value">
 | 
				
			||||||
                        <el-color-picker v-model="themeConfig.columnsMenuBarColor" size="small"
 | 
					                        <el-color-picker v-model="themeConfig.columnsMenuBarColor" size="small" @change="onBgColorPickerChange('columnsMenuBarColor')">
 | 
				
			||||||
                            @change="onBgColorPickerChange('columnsMenuBarColor')">
 | 
					 | 
				
			||||||
                        </el-color-picker>
 | 
					                        </el-color-picker>
 | 
				
			||||||
                    </div>
 | 
					                    </div>
 | 
				
			||||||
                </div>
 | 
					                </div>
 | 
				
			||||||
@@ -162,15 +148,13 @@
 | 
				
			|||||||
                <div class="layout-breadcrumb-seting-bar-flex mt14">
 | 
					                <div class="layout-breadcrumb-seting-bar-flex mt14">
 | 
				
			||||||
                    <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-switch v-model="themeConfig.isColumnsMenuBarColorGradual"
 | 
					                        <el-switch v-model="themeConfig.isColumnsMenuBarColorGradual" @change="onColumnsMenuBarGradualChange"></el-switch>
 | 
				
			||||||
                            @change="onColumnsMenuBarGradualChange"></el-switch>
 | 
					 | 
				
			||||||
                    </div>
 | 
					                    </div>
 | 
				
			||||||
                </div>
 | 
					                </div>
 | 
				
			||||||
                <div class="layout-breadcrumb-seting-bar-flex mt14">
 | 
					                <div class="layout-breadcrumb-seting-bar-flex mt14">
 | 
				
			||||||
                    <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-switch v-model="themeConfig.isMenuBarColorHighlight"
 | 
					                        <el-switch v-model="themeConfig.isMenuBarColorHighlight" @change="onMenuBarHighlightChange"></el-switch>
 | 
				
			||||||
                            @change="onMenuBarHighlightChange"></el-switch>
 | 
					 | 
				
			||||||
                    </div>
 | 
					                    </div>
 | 
				
			||||||
                </div>
 | 
					                </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -194,12 +178,10 @@
 | 
				
			|||||||
                        <el-switch v-model="themeConfig.isFixedHeader" @change="onIsFixedHeaderChange"></el-switch>
 | 
					                        <el-switch v-model="themeConfig.isFixedHeader" @change="onIsFixedHeaderChange"></el-switch>
 | 
				
			||||||
                    </div>
 | 
					                    </div>
 | 
				
			||||||
                </div>
 | 
					                </div>
 | 
				
			||||||
                <div class="layout-breadcrumb-seting-bar-flex mt15"
 | 
					                <div class="layout-breadcrumb-seting-bar-flex mt15" :style="{ opacity: themeConfig.layout !== 'classic' ? 0.5 : 1 }">
 | 
				
			||||||
                    :style="{ opacity: themeConfig.layout !== 'classic' ? 0.5 : 1 }">
 | 
					 | 
				
			||||||
                    <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-switch v-model="themeConfig.isClassicSplitMenu" :disabled="themeConfig.layout !== 'classic'"
 | 
					                        <el-switch v-model="themeConfig.isClassicSplitMenu" :disabled="themeConfig.layout !== 'classic'" @change="onClassicSplitMenuChange">
 | 
				
			||||||
                            @change="onClassicSplitMenuChange">
 | 
					 | 
				
			||||||
                        </el-switch>
 | 
					                        </el-switch>
 | 
				
			||||||
                    </div>
 | 
					                    </div>
 | 
				
			||||||
                </div>
 | 
					                </div>
 | 
				
			||||||
@@ -212,8 +194,15 @@
 | 
				
			|||||||
                <div class="layout-breadcrumb-seting-bar-flex mt11">
 | 
					                <div class="layout-breadcrumb-seting-bar-flex mt11">
 | 
				
			||||||
                    <div class="layout-breadcrumb-seting-bar-flex-label">自动锁屏(s/秒)</div>
 | 
					                    <div class="layout-breadcrumb-seting-bar-flex-label">自动锁屏(s/秒)</div>
 | 
				
			||||||
                    <div class="layout-breadcrumb-seting-bar-flex-value">
 | 
					                    <div class="layout-breadcrumb-seting-bar-flex-value">
 | 
				
			||||||
                        <el-input-number v-model="themeConfig.lockScreenTime" controls-position="right" :min="0" :max="9999"
 | 
					                        <el-input-number
 | 
				
			||||||
                            @change="setLocalThemeConfig" size="small" style="width: 90px">
 | 
					                            v-model="themeConfig.lockScreenTime"
 | 
				
			||||||
 | 
					                            controls-position="right"
 | 
				
			||||||
 | 
					                            :min="0"
 | 
				
			||||||
 | 
					                            :max="9999"
 | 
				
			||||||
 | 
					                            @change="setLocalThemeConfig"
 | 
				
			||||||
 | 
					                            size="small"
 | 
				
			||||||
 | 
					                            style="width: 90px"
 | 
				
			||||||
 | 
					                        >
 | 
				
			||||||
                        </el-input-number>
 | 
					                        </el-input-number>
 | 
				
			||||||
                    </div>
 | 
					                    </div>
 | 
				
			||||||
                </div>
 | 
					                </div>
 | 
				
			||||||
@@ -226,12 +215,14 @@
 | 
				
			|||||||
                        <el-switch v-model="themeConfig.isShowLogo" @change="onIsShowLogoChange"></el-switch>
 | 
					                        <el-switch v-model="themeConfig.isShowLogo" @change="onIsShowLogoChange"></el-switch>
 | 
				
			||||||
                    </div>
 | 
					                    </div>
 | 
				
			||||||
                </div>
 | 
					                </div>
 | 
				
			||||||
                <div class="layout-breadcrumb-seting-bar-flex mt15"
 | 
					                <div class="layout-breadcrumb-seting-bar-flex mt15" :style="{ opacity: themeConfig.layout === 'transverse' ? 0.5 : 1 }">
 | 
				
			||||||
                    :style="{ opacity: themeConfig.layout === 'transverse' ? 0.5 : 1 }">
 | 
					 | 
				
			||||||
                    <div class="layout-breadcrumb-seting-bar-flex-label">开启Breadcrumb</div>
 | 
					                    <div class="layout-breadcrumb-seting-bar-flex-label">开启Breadcrumb</div>
 | 
				
			||||||
                    <div class="layout-breadcrumb-seting-bar-flex-value">
 | 
					                    <div class="layout-breadcrumb-seting-bar-flex-value">
 | 
				
			||||||
                        <el-switch v-model="themeConfig.isBreadcrumb" :disabled="themeConfig.layout === 'transverse'"
 | 
					                        <el-switch
 | 
				
			||||||
                            @change="onIsBreadcrumbChange"></el-switch>
 | 
					                            v-model="themeConfig.isBreadcrumb"
 | 
				
			||||||
 | 
					                            :disabled="themeConfig.layout === 'transverse'"
 | 
				
			||||||
 | 
					                            @change="onIsBreadcrumbChange"
 | 
				
			||||||
 | 
					                        ></el-switch>
 | 
				
			||||||
                    </div>
 | 
					                    </div>
 | 
				
			||||||
                </div>
 | 
					                </div>
 | 
				
			||||||
                <div class="layout-breadcrumb-seting-bar-flex mt15">
 | 
					                <div class="layout-breadcrumb-seting-bar-flex mt15">
 | 
				
			||||||
@@ -288,8 +279,7 @@
 | 
				
			|||||||
                <div class="layout-breadcrumb-seting-bar-flex mt15">
 | 
					                <div class="layout-breadcrumb-seting-bar-flex mt15">
 | 
				
			||||||
                    <div class="layout-breadcrumb-seting-bar-flex-label">Tagsview 风格</div>
 | 
					                    <div class="layout-breadcrumb-seting-bar-flex-label">Tagsview 风格</div>
 | 
				
			||||||
                    <div class="layout-breadcrumb-seting-bar-flex-value">
 | 
					                    <div class="layout-breadcrumb-seting-bar-flex-value">
 | 
				
			||||||
                        <el-select v-model="themeConfig.tagsStyle" placeholder="请选择" size="small" style="width: 90px"
 | 
					                        <el-select v-model="themeConfig.tagsStyle" placeholder="请选择" size="small" style="width: 90px" @change="setLocalThemeConfig">
 | 
				
			||||||
                            @change="setLocalThemeConfig">
 | 
					 | 
				
			||||||
                            <el-option label="风格1" value="tags-style-one"></el-option>
 | 
					                            <el-option label="风格1" value="tags-style-one"></el-option>
 | 
				
			||||||
                            <el-option label="风格2" value="tags-style-two"></el-option>
 | 
					                            <el-option label="风格2" value="tags-style-two"></el-option>
 | 
				
			||||||
                            <el-option label="风格3" value="tags-style-three"></el-option>
 | 
					                            <el-option label="风格3" value="tags-style-three"></el-option>
 | 
				
			||||||
@@ -299,8 +289,7 @@
 | 
				
			|||||||
                <div class="layout-breadcrumb-seting-bar-flex mt15">
 | 
					                <div class="layout-breadcrumb-seting-bar-flex mt15">
 | 
				
			||||||
                    <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 v-model="themeConfig.animation" placeholder="请选择" size="small" style="width: 90px"
 | 
					                        <el-select v-model="themeConfig.animation" placeholder="请选择" size="small" style="width: 90px" @change="setLocalThemeConfig">
 | 
				
			||||||
                            @change="setLocalThemeConfig">
 | 
					 | 
				
			||||||
                            <el-option label="slide-right" value="slide-right"></el-option>
 | 
					                            <el-option label="slide-right" value="slide-right"></el-option>
 | 
				
			||||||
                            <el-option label="slide-left" value="slide-left"></el-option>
 | 
					                            <el-option label="slide-left" value="slide-left"></el-option>
 | 
				
			||||||
                            <el-option label="opacitys" value="opacitys"></el-option>
 | 
					                            <el-option label="opacitys" value="opacitys"></el-option>
 | 
				
			||||||
@@ -310,8 +299,7 @@
 | 
				
			|||||||
                <div class="layout-breadcrumb-seting-bar-flex mt15 mb28">
 | 
					                <div class="layout-breadcrumb-seting-bar-flex mt15 mb28">
 | 
				
			||||||
                    <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 v-model="themeConfig.columnsAsideStyle" placeholder="请选择" size="small"
 | 
					                        <el-select v-model="themeConfig.columnsAsideStyle" placeholder="请选择" size="small" style="width: 90px" @change="setLocalThemeConfig">
 | 
				
			||||||
                            style="width: 90px" @change="setLocalThemeConfig">
 | 
					 | 
				
			||||||
                            <el-option label="圆角" value="columns-round"></el-option>
 | 
					                            <el-option label="圆角" value="columns-round"></el-option>
 | 
				
			||||||
                            <el-option label="卡片" value="columns-card"></el-option>
 | 
					                            <el-option label="卡片" value="columns-card"></el-option>
 | 
				
			||||||
                        </el-select>
 | 
					                        </el-select>
 | 
				
			||||||
@@ -323,16 +311,14 @@
 | 
				
			|||||||
                <div class="layout-drawer-content-flex">
 | 
					                <div class="layout-drawer-content-flex">
 | 
				
			||||||
                    <!-- defaults 布局 -->
 | 
					                    <!-- defaults 布局 -->
 | 
				
			||||||
                    <div class="layout-drawer-content-item" @click="onSetLayout('defaults')">
 | 
					                    <div class="layout-drawer-content-item" @click="onSetLayout('defaults')">
 | 
				
			||||||
                        <section class="el-container el-circular"
 | 
					                        <section class="el-container el-circular" :class="{ 'drawer-layout-active': themeConfig.layout === 'defaults' }">
 | 
				
			||||||
                            :class="{ 'drawer-layout-active': themeConfig.layout === 'defaults' }">
 | 
					 | 
				
			||||||
                            <aside class="el-aside" style="width: 20px"></aside>
 | 
					                            <aside class="el-aside" style="width: 20px"></aside>
 | 
				
			||||||
                            <section class="el-container is-vertical">
 | 
					                            <section class="el-container is-vertical">
 | 
				
			||||||
                                <header class="el-header" style="height: 10px"></header>
 | 
					                                <header class="el-header" style="height: 10px"></header>
 | 
				
			||||||
                                <main class="el-main"></main>
 | 
					                                <main class="el-main"></main>
 | 
				
			||||||
                            </section>
 | 
					                            </section>
 | 
				
			||||||
                        </section>
 | 
					                        </section>
 | 
				
			||||||
                        <div class="layout-tips-warp"
 | 
					                        <div class="layout-tips-warp" :class="{ 'layout-tips-warp-active': themeConfig.layout === 'defaults' }">
 | 
				
			||||||
                            :class="{ 'layout-tips-warp-active': themeConfig.layout === 'defaults' }">
 | 
					 | 
				
			||||||
                            <div class="layout-tips-box">
 | 
					                            <div class="layout-tips-box">
 | 
				
			||||||
                                <p class="layout-tips-txt">默认</p>
 | 
					                                <p class="layout-tips-txt">默认</p>
 | 
				
			||||||
                            </div>
 | 
					                            </div>
 | 
				
			||||||
@@ -340,8 +326,7 @@
 | 
				
			|||||||
                    </div>
 | 
					                    </div>
 | 
				
			||||||
                    <!-- classic 布局 -->
 | 
					                    <!-- classic 布局 -->
 | 
				
			||||||
                    <div class="layout-drawer-content-item" @click="onSetLayout('classic')">
 | 
					                    <div class="layout-drawer-content-item" @click="onSetLayout('classic')">
 | 
				
			||||||
                        <section class="el-container is-vertical el-circular"
 | 
					                        <section class="el-container is-vertical el-circular" :class="{ 'drawer-layout-active': themeConfig.layout === 'classic' }">
 | 
				
			||||||
                            :class="{ 'drawer-layout-active': themeConfig.layout === 'classic' }">
 | 
					 | 
				
			||||||
                            <header class="el-header" style="height: 10px"></header>
 | 
					                            <header class="el-header" style="height: 10px"></header>
 | 
				
			||||||
                            <section class="el-container">
 | 
					                            <section class="el-container">
 | 
				
			||||||
                                <aside class="el-aside" style="width: 20px"></aside>
 | 
					                                <aside class="el-aside" style="width: 20px"></aside>
 | 
				
			||||||
@@ -350,8 +335,7 @@
 | 
				
			|||||||
                                </section>
 | 
					                                </section>
 | 
				
			||||||
                            </section>
 | 
					                            </section>
 | 
				
			||||||
                        </section>
 | 
					                        </section>
 | 
				
			||||||
                        <div class="layout-tips-warp"
 | 
					                        <div class="layout-tips-warp" :class="{ 'layout-tips-warp-active': themeConfig.layout === 'classic' }">
 | 
				
			||||||
                            :class="{ 'layout-tips-warp-active': themeConfig.layout === 'classic' }">
 | 
					 | 
				
			||||||
                            <div class="layout-tips-box">
 | 
					                            <div class="layout-tips-box">
 | 
				
			||||||
                                <p class="layout-tips-txt">经典</p>
 | 
					                                <p class="layout-tips-txt">经典</p>
 | 
				
			||||||
                            </div>
 | 
					                            </div>
 | 
				
			||||||
@@ -359,8 +343,7 @@
 | 
				
			|||||||
                    </div>
 | 
					                    </div>
 | 
				
			||||||
                    <!-- transverse 布局 -->
 | 
					                    <!-- transverse 布局 -->
 | 
				
			||||||
                    <div class="layout-drawer-content-item" @click="onSetLayout('transverse')">
 | 
					                    <div class="layout-drawer-content-item" @click="onSetLayout('transverse')">
 | 
				
			||||||
                        <section class="el-container is-vertical el-circular"
 | 
					                        <section class="el-container is-vertical el-circular" :class="{ 'drawer-layout-active': themeConfig.layout === 'transverse' }">
 | 
				
			||||||
                            :class="{ 'drawer-layout-active': themeConfig.layout === 'transverse' }">
 | 
					 | 
				
			||||||
                            <header class="el-header" style="height: 10px"></header>
 | 
					                            <header class="el-header" style="height: 10px"></header>
 | 
				
			||||||
                            <section class="el-container">
 | 
					                            <section class="el-container">
 | 
				
			||||||
                                <section class="el-container is-vertical">
 | 
					                                <section class="el-container is-vertical">
 | 
				
			||||||
@@ -368,8 +351,7 @@
 | 
				
			|||||||
                                </section>
 | 
					                                </section>
 | 
				
			||||||
                            </section>
 | 
					                            </section>
 | 
				
			||||||
                        </section>
 | 
					                        </section>
 | 
				
			||||||
                        <div class="layout-tips-warp"
 | 
					                        <div class="layout-tips-warp" :class="{ 'layout-tips-warp-active': themeConfig.layout === 'transverse' }">
 | 
				
			||||||
                            :class="{ 'layout-tips-warp-active': themeConfig.layout === 'transverse' }">
 | 
					 | 
				
			||||||
                            <div class="layout-tips-box">
 | 
					                            <div class="layout-tips-box">
 | 
				
			||||||
                                <p class="layout-tips-txt">横向</p>
 | 
					                                <p class="layout-tips-txt">横向</p>
 | 
				
			||||||
                            </div>
 | 
					                            </div>
 | 
				
			||||||
@@ -377,8 +359,7 @@
 | 
				
			|||||||
                    </div>
 | 
					                    </div>
 | 
				
			||||||
                    <!-- columns 布局 -->
 | 
					                    <!-- columns 布局 -->
 | 
				
			||||||
                    <div class="layout-drawer-content-item" @click="onSetLayout('columns')">
 | 
					                    <div class="layout-drawer-content-item" @click="onSetLayout('columns')">
 | 
				
			||||||
                        <section class="el-container el-circular"
 | 
					                        <section class="el-container el-circular" :class="{ 'drawer-layout-active': themeConfig.layout === 'columns' }">
 | 
				
			||||||
                            :class="{ 'drawer-layout-active': themeConfig.layout === 'columns' }">
 | 
					 | 
				
			||||||
                            <aside class="el-aside-dark" style="width: 10px"></aside>
 | 
					                            <aside class="el-aside-dark" style="width: 10px"></aside>
 | 
				
			||||||
                            <aside class="el-aside" style="width: 20px"></aside>
 | 
					                            <aside class="el-aside" style="width: 20px"></aside>
 | 
				
			||||||
                            <section class="el-container is-vertical">
 | 
					                            <section class="el-container is-vertical">
 | 
				
			||||||
@@ -386,8 +367,7 @@
 | 
				
			|||||||
                                <main class="el-main"></main>
 | 
					                                <main class="el-main"></main>
 | 
				
			||||||
                            </section>
 | 
					                            </section>
 | 
				
			||||||
                        </section>
 | 
					                        </section>
 | 
				
			||||||
                        <div class="layout-tips-warp"
 | 
					                        <div class="layout-tips-warp" :class="{ 'layout-tips-warp-active': themeConfig.layout === 'columns' }">
 | 
				
			||||||
                            :class="{ 'layout-tips-warp-active': themeConfig.layout === 'columns' }">
 | 
					 | 
				
			||||||
                            <div class="layout-tips-box">
 | 
					                            <div class="layout-tips-box">
 | 
				
			||||||
                                <p class="layout-tips-txt">分栏</p>
 | 
					                                <p class="layout-tips-txt">分栏</p>
 | 
				
			||||||
                            </div>
 | 
					                            </div>
 | 
				
			||||||
@@ -395,10 +375,15 @@
 | 
				
			|||||||
                    </div>
 | 
					                    </div>
 | 
				
			||||||
                </div>
 | 
					                </div>
 | 
				
			||||||
                <div class="copy-config">
 | 
					                <div class="copy-config">
 | 
				
			||||||
                    <el-alert title="点击下方按钮,复制布局配置去 /src/store/modules/themeConfig.ts中修改" type="warning" :closable="false">
 | 
					                    <el-alert title="点击下方按钮,复制布局配置去 /src/store/modules/themeConfig.ts中修改" type="warning" :closable="false"> </el-alert>
 | 
				
			||||||
                    </el-alert>
 | 
					                    <el-button
 | 
				
			||||||
                    <el-button size="small" class="copy-config-btn" icon="el-icon-document-copy" type="primary"
 | 
					                        size="small"
 | 
				
			||||||
                        ref="copyConfigBtnRef" @click="onCopyConfigClick($event.target)">一键复制配置
 | 
					                        class="copy-config-btn"
 | 
				
			||||||
 | 
					                        icon="el-icon-document-copy"
 | 
				
			||||||
 | 
					                        type="primary"
 | 
				
			||||||
 | 
					                        ref="copyConfigBtnRef"
 | 
				
			||||||
 | 
					                        @click="onCopyConfigClick($event.target)"
 | 
				
			||||||
 | 
					                        >一键复制配置
 | 
				
			||||||
                    </el-button>
 | 
					                    </el-button>
 | 
				
			||||||
                </div>
 | 
					                </div>
 | 
				
			||||||
            </el-scrollbar>
 | 
					            </el-scrollbar>
 | 
				
			||||||
@@ -412,8 +397,8 @@ import { ElMessage } from 'element-plus';
 | 
				
			|||||||
import ClipboardJS from 'clipboard';
 | 
					import ClipboardJS from 'clipboard';
 | 
				
			||||||
import { storeToRefs } from 'pinia';
 | 
					import { storeToRefs } from 'pinia';
 | 
				
			||||||
import { useThemeConfig } from '@/store/themeConfig';
 | 
					import { useThemeConfig } from '@/store/themeConfig';
 | 
				
			||||||
import { getLightColor } from '@/common/utils/theme.ts';
 | 
					import { getLightColor } from '@/common/utils/theme';
 | 
				
			||||||
import { setLocal, getLocal, removeLocal } from '@/common/utils/storage.ts';
 | 
					import { setLocal, getLocal, removeLocal } from '@/common/utils/storage';
 | 
				
			||||||
import mittBus from '@/common/utils/mitt';
 | 
					import mittBus from '@/common/utils/mitt';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const copyConfigBtnRef = ref();
 | 
					const copyConfigBtnRef = ref();
 | 
				
			||||||
@@ -428,7 +413,7 @@ const onColorPickerChange = (color: string) => {
 | 
				
			|||||||
const setPropertyFun = (color: string, targetVal: any) => {
 | 
					const setPropertyFun = (color: string, targetVal: any) => {
 | 
				
			||||||
    document.documentElement.style.setProperty(color, targetVal);
 | 
					    document.documentElement.style.setProperty(color, targetVal);
 | 
				
			||||||
    for (let i = 1; i <= 9; i++) {
 | 
					    for (let i = 1; i <= 9; i++) {
 | 
				
			||||||
        document.documentElement.style.setProperty(`${color}-light-${i}`, getLightColor(targetVal, i / 10));
 | 
					        document.documentElement.style.setProperty(`${color}-light-${i}`, getLightColor(targetVal, i / 10) as any);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
// 2、菜单 / 顶栏
 | 
					// 2、菜单 / 顶栏
 | 
				
			||||||
@@ -449,11 +434,7 @@ const onMenuBarGradualChange = () => {
 | 
				
			|||||||
};
 | 
					};
 | 
				
			||||||
// 2、菜单 / 顶栏 --> 分栏菜单背景渐变
 | 
					// 2、菜单 / 顶栏 --> 分栏菜单背景渐变
 | 
				
			||||||
const onColumnsMenuBarGradualChange = () => {
 | 
					const onColumnsMenuBarGradualChange = () => {
 | 
				
			||||||
    setGraduaFun(
 | 
					    setGraduaFun('.layout-container .layout-columns-aside', themeConfig.value.isColumnsMenuBarColorGradual, themeConfig.value.columnsMenuBar);
 | 
				
			||||||
        '.layout-container .layout-columns-aside',
 | 
					 | 
				
			||||||
        themeConfig.value.isColumnsMenuBarColorGradual,
 | 
					 | 
				
			||||||
        themeConfig.value.columnsMenuBar
 | 
					 | 
				
			||||||
    );
 | 
					 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
// 2、菜单 / 顶栏 --> 背景渐变函数
 | 
					// 2、菜单 / 顶栏 --> 背景渐变函数
 | 
				
			||||||
const setGraduaFun = (el: string, bool: boolean, color: string) => {
 | 
					const setGraduaFun = (el: string, bool: boolean, color: string) => {
 | 
				
			||||||
@@ -522,17 +503,14 @@ const onSortableTagsViewChange = () => {
 | 
				
			|||||||
    mittBus.emit('openOrCloseSortable');
 | 
					    mittBus.emit('openOrCloseSortable');
 | 
				
			||||||
    setLocalThemeConfig();
 | 
					    setLocalThemeConfig();
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
// 4、界面显示 --> 灰色模式/色弱模式
 | 
					// 4、界面显示 --> 暗模式/灰色模式/色弱模式
 | 
				
			||||||
const onAddFilterChange = (attr: string) => {
 | 
					const onAddFilterChange = (attr: string) => {
 | 
				
			||||||
    if (attr === 'grayscale') {
 | 
					    if (attr === 'grayscale') {
 | 
				
			||||||
        if (themeConfig.value.isGrayscale) themeConfig.value.isInvert = false;
 | 
					        if (themeConfig.value.isGrayscale) themeConfig.value.isInvert = false;
 | 
				
			||||||
    } else {
 | 
					    } else {
 | 
				
			||||||
        if (themeConfig.value.isInvert) themeConfig.value.isGrayscale = false;
 | 
					        if (themeConfig.value.isInvert) themeConfig.value.isGrayscale = false;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    const cssAttr =
 | 
					    const cssAttr = attr === 'grayscale' ? `grayscale(${themeConfig.value.isGrayscale ? 1 : 0})` : `invert(${themeConfig.value.isInvert ? '80%' : '0%'})`;
 | 
				
			||||||
        attr === 'grayscale'
 | 
					 | 
				
			||||||
            ? `grayscale(${themeConfig.value.isGrayscale ? 1 : 0})`
 | 
					 | 
				
			||||||
            : `invert(${themeConfig.value.isInvert ? '80%' : '0%'})`;
 | 
					 | 
				
			||||||
    const appEle: any = document.querySelector('#app');
 | 
					    const appEle: any = document.querySelector('#app');
 | 
				
			||||||
    appEle.setAttribute('style', `filter: ${cssAttr}`);
 | 
					    appEle.setAttribute('style', `filter: ${cssAttr}`);
 | 
				
			||||||
    setLocalThemeConfig();
 | 
					    setLocalThemeConfig();
 | 
				
			||||||
@@ -549,49 +527,37 @@ const onSetLayout = (layout: string) => {
 | 
				
			|||||||
};
 | 
					};
 | 
				
			||||||
// 设置布局切换,重置主题样式
 | 
					// 设置布局切换,重置主题样式
 | 
				
			||||||
const initSetLayoutChange = () => {
 | 
					const initSetLayoutChange = () => {
 | 
				
			||||||
 | 
					    // themeConfig.value.menuBar = '#FFFFFF';
 | 
				
			||||||
 | 
					    // themeConfig.value.menuBarColor = '#606266';
 | 
				
			||||||
 | 
					    // themeConfig.value.topBar = '#ffffff';
 | 
				
			||||||
 | 
					    // themeConfig.value.topBarColor = '#606266';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (themeConfig.value.layout === 'classic') {
 | 
					    if (themeConfig.value.layout === 'classic') {
 | 
				
			||||||
        themeConfig.value.isShowLogo = true;
 | 
					        themeConfig.value.isShowLogo = true;
 | 
				
			||||||
        themeConfig.value.isBreadcrumb = true;
 | 
					        themeConfig.value.isBreadcrumb = true;
 | 
				
			||||||
        themeConfig.value.isCollapse = false;
 | 
					        themeConfig.value.isCollapse = false;
 | 
				
			||||||
        themeConfig.value.isClassicSplitMenu = false;
 | 
					        themeConfig.value.isClassicSplitMenu = false;
 | 
				
			||||||
        themeConfig.value.menuBar = '#FFFFFF';
 | 
					 | 
				
			||||||
        themeConfig.value.menuBarColor = '#606266';
 | 
					 | 
				
			||||||
        themeConfig.value.topBar = '#ffffff';
 | 
					 | 
				
			||||||
        themeConfig.value.topBarColor = '#606266';
 | 
					 | 
				
			||||||
        initLayoutChangeFun();
 | 
					 | 
				
			||||||
    } else if (themeConfig.value.layout === 'transverse') {
 | 
					    } else if (themeConfig.value.layout === 'transverse') {
 | 
				
			||||||
        themeConfig.value.isShowLogo = true;
 | 
					        themeConfig.value.isShowLogo = true;
 | 
				
			||||||
        themeConfig.value.isBreadcrumb = false;
 | 
					        themeConfig.value.isBreadcrumb = false;
 | 
				
			||||||
        themeConfig.value.isCollapse = false;
 | 
					        themeConfig.value.isCollapse = false;
 | 
				
			||||||
        themeConfig.value.isTagsview = false;
 | 
					        themeConfig.value.isTagsview = true;
 | 
				
			||||||
        themeConfig.value.isClassicSplitMenu = false;
 | 
					        themeConfig.value.isClassicSplitMenu = false;
 | 
				
			||||||
        themeConfig.value.menuBarColor = '#FFFFFF';
 | 
					 | 
				
			||||||
        themeConfig.value.topBar = '#545c64';
 | 
					 | 
				
			||||||
        themeConfig.value.topBarColor = '#FFFFFF';
 | 
					 | 
				
			||||||
        initLayoutChangeFun();
 | 
					 | 
				
			||||||
    } else if (themeConfig.value.layout === 'columns') {
 | 
					    } else if (themeConfig.value.layout === 'columns') {
 | 
				
			||||||
        themeConfig.value.isShowLogo = true;
 | 
					        themeConfig.value.isShowLogo = true;
 | 
				
			||||||
        themeConfig.value.isBreadcrumb = true;
 | 
					        themeConfig.value.isBreadcrumb = true;
 | 
				
			||||||
        themeConfig.value.isCollapse = false;
 | 
					        themeConfig.value.isCollapse = false;
 | 
				
			||||||
        themeConfig.value.isTagsview = true;
 | 
					        themeConfig.value.isTagsview = true;
 | 
				
			||||||
        themeConfig.value.isClassicSplitMenu = false;
 | 
					        themeConfig.value.isClassicSplitMenu = false;
 | 
				
			||||||
        themeConfig.value.menuBar = '#FFFFFF';
 | 
					 | 
				
			||||||
        themeConfig.value.menuBarColor = '#606266';
 | 
					 | 
				
			||||||
        themeConfig.value.topBar = '#ffffff';
 | 
					 | 
				
			||||||
        themeConfig.value.topBarColor = '#606266';
 | 
					 | 
				
			||||||
        initLayoutChangeFun();
 | 
					 | 
				
			||||||
    } else {
 | 
					    } else {
 | 
				
			||||||
        themeConfig.value.isShowLogo = false;
 | 
					        themeConfig.value.isShowLogo = false;
 | 
				
			||||||
        themeConfig.value.isBreadcrumb = true;
 | 
					        themeConfig.value.isBreadcrumb = true;
 | 
				
			||||||
        themeConfig.value.isCollapse = false;
 | 
					        themeConfig.value.isCollapse = false;
 | 
				
			||||||
        themeConfig.value.isTagsview = true;
 | 
					        themeConfig.value.isTagsview = true;
 | 
				
			||||||
        themeConfig.value.isClassicSplitMenu = false;
 | 
					        themeConfig.value.isClassicSplitMenu = false;
 | 
				
			||||||
        themeConfig.value.menuBar = '#545c64';
 | 
					 | 
				
			||||||
        themeConfig.value.menuBarColor = '#eaeaea';
 | 
					 | 
				
			||||||
        themeConfig.value.topBar = '#FFFFFF';
 | 
					 | 
				
			||||||
        themeConfig.value.topBarColor = '#606266';
 | 
					 | 
				
			||||||
        initLayoutChangeFun();
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    initLayoutChangeFun();
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
// 设置布局切换函数
 | 
					// 设置布局切换函数
 | 
				
			||||||
const initLayoutChangeFun = () => {
 | 
					const initLayoutChangeFun = () => {
 | 
				
			||||||
@@ -660,6 +626,7 @@ onMounted(() => {
 | 
				
			|||||||
            onMenuBarHighlightChange();
 | 
					            onMenuBarHighlightChange();
 | 
				
			||||||
            themeConfig.value.isCollapse = false;
 | 
					            themeConfig.value.isCollapse = false;
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        window.addEventListener('load', () => {
 | 
					        window.addEventListener('load', () => {
 | 
				
			||||||
            // 刷新页面时,设置了值,直接取缓存中的值进行初始化
 | 
					            // 刷新页面时,设置了值,直接取缓存中的值进行初始化
 | 
				
			||||||
            setTimeout(() => {
 | 
					            setTimeout(() => {
 | 
				
			||||||
@@ -691,7 +658,7 @@ onMounted(() => {
 | 
				
			|||||||
                }
 | 
					                }
 | 
				
			||||||
                // // 语言国际化
 | 
					                // // 语言国际化
 | 
				
			||||||
                // if (getLocal('themeConfig')) proxy.$i18n.locale = getLocal('themeConfig').globalI18n;
 | 
					                // if (getLocal('themeConfig')) proxy.$i18n.locale = getLocal('themeConfig').globalI18n;
 | 
				
			||||||
            }, 1100);
 | 
					            }, 100);
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
@@ -701,7 +668,7 @@ onUnmounted(() => {
 | 
				
			|||||||
    mittBus.off('layoutMobileResize');
 | 
					    mittBus.off('layoutMobileResize');
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
defineExpose({openDrawer})
 | 
					defineExpose({ openDrawer });
 | 
				
			||||||
</script>
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<style scoped lang="scss">
 | 
					<style scoped lang="scss">
 | 
				
			||||||
@@ -767,7 +734,7 @@ defineExpose({openDrawer})
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
            .drawer-layout-active {
 | 
					            .drawer-layout-active {
 | 
				
			||||||
                border: 1px solid;
 | 
					                border: 1px solid;
 | 
				
			||||||
                border-color: var(--color-primary);
 | 
					                border-color: var(--el-color-primary);
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            .layout-tips-warp,
 | 
					            .layout-tips-warp,
 | 
				
			||||||
@@ -778,7 +745,7 @@ defineExpose({openDrawer})
 | 
				
			|||||||
                top: 50%;
 | 
					                top: 50%;
 | 
				
			||||||
                transform: translate(-50%, -50%);
 | 
					                transform: translate(-50%, -50%);
 | 
				
			||||||
                border: 1px solid;
 | 
					                border: 1px solid;
 | 
				
			||||||
                border-color: var(--color-primary-light-4);
 | 
					                border-color: var(--el-color-primary-light-4);
 | 
				
			||||||
                border-radius: 100%;
 | 
					                border-radius: 100%;
 | 
				
			||||||
                padding: 4px;
 | 
					                padding: 4px;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -788,7 +755,7 @@ defineExpose({openDrawer})
 | 
				
			|||||||
                    height: 30px;
 | 
					                    height: 30px;
 | 
				
			||||||
                    z-index: 9;
 | 
					                    z-index: 9;
 | 
				
			||||||
                    border: 1px solid;
 | 
					                    border: 1px solid;
 | 
				
			||||||
                    border-color: var(--color-primary-light-4);
 | 
					                    border-color: var(--el-color-primary-light-4);
 | 
				
			||||||
                    border-radius: 100%;
 | 
					                    border-radius: 100%;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    .layout-tips-txt {
 | 
					                    .layout-tips-txt {
 | 
				
			||||||
@@ -799,7 +766,7 @@ defineExpose({openDrawer})
 | 
				
			|||||||
                        line-height: 1;
 | 
					                        line-height: 1;
 | 
				
			||||||
                        letter-spacing: 2px;
 | 
					                        letter-spacing: 2px;
 | 
				
			||||||
                        white-space: nowrap;
 | 
					                        white-space: nowrap;
 | 
				
			||||||
                        color: var(--color-primary-light-4);
 | 
					                        color: var(--el-color-primary-light-4);
 | 
				
			||||||
                        text-align: center;
 | 
					                        text-align: center;
 | 
				
			||||||
                        transform: rotate(30deg);
 | 
					                        transform: rotate(30deg);
 | 
				
			||||||
                        left: -1px;
 | 
					                        left: -1px;
 | 
				
			||||||
@@ -813,14 +780,14 @@ defineExpose({openDrawer})
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
            .layout-tips-warp-active {
 | 
					            .layout-tips-warp-active {
 | 
				
			||||||
                border: 1px solid;
 | 
					                border: 1px solid;
 | 
				
			||||||
                border-color: var(--color-primary);
 | 
					                border-color: var(--el-color-primary);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                .layout-tips-box {
 | 
					                .layout-tips-box {
 | 
				
			||||||
                    border: 1px solid;
 | 
					                    border: 1px solid;
 | 
				
			||||||
                    border-color: var(--color-primary);
 | 
					                    border-color: var(--el-color-primary);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    .layout-tips-txt {
 | 
					                    .layout-tips-txt {
 | 
				
			||||||
                        color: var(--color-primary) !important;
 | 
					                        color: var(--el-color-primary) !important;
 | 
				
			||||||
                        background-color: #e9eef3 !important;
 | 
					                        background-color: #e9eef3 !important;
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
@@ -830,20 +797,20 @@ defineExpose({openDrawer})
 | 
				
			|||||||
                .el-circular {
 | 
					                .el-circular {
 | 
				
			||||||
                    transition: all 0.3s ease-in-out;
 | 
					                    transition: all 0.3s ease-in-out;
 | 
				
			||||||
                    border: 1px solid;
 | 
					                    border: 1px solid;
 | 
				
			||||||
                    border-color: var(--color-primary);
 | 
					                    border-color: var(--el-color-primary);
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                .layout-tips-warp {
 | 
					                .layout-tips-warp {
 | 
				
			||||||
                    transition: all 0.3s ease-in-out;
 | 
					                    transition: all 0.3s ease-in-out;
 | 
				
			||||||
                    border-color: var(--color-primary);
 | 
					                    border-color: var(--el-color-primary);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    .layout-tips-box {
 | 
					                    .layout-tips-box {
 | 
				
			||||||
                        transition: inherit;
 | 
					                        transition: inherit;
 | 
				
			||||||
                        border-color: var(--color-primary);
 | 
					                        border-color: var(--el-color-primary);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                        .layout-tips-txt {
 | 
					                        .layout-tips-txt {
 | 
				
			||||||
                            transition: inherit;
 | 
					                            transition: inherit;
 | 
				
			||||||
                            color: var(--color-primary) !important;
 | 
					                            color: var(--el-color-primary) !important;
 | 
				
			||||||
                            background-color: #e9eef3 !important;
 | 
					                            background-color: #e9eef3 !important;
 | 
				
			||||||
                        }
 | 
					                        }
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,6 +1,16 @@
 | 
				
			|||||||
<template>
 | 
					<template>
 | 
				
			||||||
    <div class="layout-navbars-breadcrumb-user" :style="{ flex: layoutUserFlexNum }">
 | 
					    <div class="layout-navbars-breadcrumb-user" :style="{ flex: layoutUserFlexNum }">
 | 
				
			||||||
        <el-dropdown :show-timeout="70" :hide-timeout="50" trigger="click" @command="onComponentSizeChange">
 | 
					        <div class="layout-navbars-breadcrumb-user-icon">
 | 
				
			||||||
 | 
					            <el-switch
 | 
				
			||||||
 | 
					                @change="switchDark(state.isDark)"
 | 
				
			||||||
 | 
					                v-model="state.isDark"
 | 
				
			||||||
 | 
					                active-action-icon="Moon"
 | 
				
			||||||
 | 
					                inactive-action-icon="Sunny"
 | 
				
			||||||
 | 
					                style="--el-switch-off-color: #c4c9c4; --el-switch-on-color: #2c2c2c"
 | 
				
			||||||
 | 
					                class="dark-icon"
 | 
				
			||||||
 | 
					            />
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					        <!-- <el-dropdown :show-timeout="70" :hide-timeout="50" trigger="click" @command="onComponentSizeChange">
 | 
				
			||||||
            <div class="layout-navbars-breadcrumb-user-icon">
 | 
					            <div class="layout-navbars-breadcrumb-user-icon">
 | 
				
			||||||
                <el-icon title="组件大小">
 | 
					                <el-icon title="组件大小">
 | 
				
			||||||
                    <plus />
 | 
					                    <plus />
 | 
				
			||||||
@@ -13,7 +23,7 @@
 | 
				
			|||||||
                    <el-dropdown-item command="small" :disabled="state.disabledSize === 'small'">小型</el-dropdown-item>
 | 
					                    <el-dropdown-item command="small" :disabled="state.disabledSize === 'small'">小型</el-dropdown-item>
 | 
				
			||||||
                </el-dropdown-menu>
 | 
					                </el-dropdown-menu>
 | 
				
			||||||
            </template>
 | 
					            </template>
 | 
				
			||||||
        </el-dropdown>
 | 
					        </el-dropdown> -->
 | 
				
			||||||
        <div class="layout-navbars-breadcrumb-user-icon" @click="onSearchClick">
 | 
					        <div class="layout-navbars-breadcrumb-user-icon" @click="onSearchClick">
 | 
				
			||||||
            <el-icon title="菜单搜索">
 | 
					            <el-icon title="菜单搜索">
 | 
				
			||||||
                <search />
 | 
					                <search />
 | 
				
			||||||
@@ -25,8 +35,7 @@
 | 
				
			|||||||
            </el-icon>
 | 
					            </el-icon>
 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
        <div class="layout-navbars-breadcrumb-user-icon">
 | 
					        <div class="layout-navbars-breadcrumb-user-icon">
 | 
				
			||||||
            <el-popover placement="bottom" trigger="click" :visible="state.isShowUserNewsPopover" :width="300"
 | 
					            <el-popover placement="bottom" trigger="click" :visible="state.isShowUserNewsPopover" :width="300" popper-class="el-popover-pupop-user-news">
 | 
				
			||||||
                popper-class="el-popover-pupop-user-news">
 | 
					 | 
				
			||||||
                <template #reference>
 | 
					                <template #reference>
 | 
				
			||||||
                    <el-badge :is-dot="false" @click="state.isShowUserNewsPopover = !state.isShowUserNewsPopover">
 | 
					                    <el-badge :is-dot="false" @click="state.isShowUserNewsPopover = !state.isShowUserNewsPopover">
 | 
				
			||||||
                        <el-icon title="消息">
 | 
					                        <el-icon title="消息">
 | 
				
			||||||
@@ -66,22 +75,24 @@
 | 
				
			|||||||
</template>
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<script setup lang="ts" name="layoutBreadcrumbUser">
 | 
					<script setup lang="ts" name="layoutBreadcrumbUser">
 | 
				
			||||||
import { ref, computed, reactive, onMounted } from 'vue';
 | 
					import { ref, computed, reactive, onMounted, nextTick } from 'vue';
 | 
				
			||||||
import { useRouter } from 'vue-router';
 | 
					import { useRouter } from 'vue-router';
 | 
				
			||||||
import { ElMessageBox, ElMessage } from 'element-plus';
 | 
					import { ElMessageBox, ElMessage } from 'element-plus';
 | 
				
			||||||
import screenfull from 'screenfull';
 | 
					import screenfull from 'screenfull';
 | 
				
			||||||
import { resetRoute } from '@/router/index.ts';
 | 
					import { resetRoute } from '@/router/index';
 | 
				
			||||||
import { storeToRefs } from 'pinia';
 | 
					import { storeToRefs } from 'pinia';
 | 
				
			||||||
import { useUserInfo } from '@/store/userInfo';
 | 
					import { useUserInfo } from '@/store/userInfo';
 | 
				
			||||||
import { useThemeConfig } from '@/store/themeConfig';
 | 
					import { useThemeConfig } from '@/store/themeConfig';
 | 
				
			||||||
import { clearSession, setLocal, getLocal, removeLocal } from '@/common/utils/storage.ts';
 | 
					import { clearSession, setLocal, getLocal, removeLocal } from '@/common/utils/storage';
 | 
				
			||||||
import UserNews from '@/views/layout/navBars/breadcrumb/userNews.vue';
 | 
					import UserNews from '@/views/layout/navBars/breadcrumb/userNews.vue';
 | 
				
			||||||
import SearchMenu from '@/views/layout/navBars/breadcrumb/search.vue';
 | 
					import SearchMenu from '@/views/layout/navBars/breadcrumb/search.vue';
 | 
				
			||||||
import mittBus from '@/common/utils/mitt';
 | 
					import mittBus from '@/common/utils/mitt';
 | 
				
			||||||
 | 
					import openApi from '@/common/openApi';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const router = useRouter();
 | 
					const router = useRouter();
 | 
				
			||||||
const searchRef = ref();
 | 
					const searchRef = ref();
 | 
				
			||||||
const state = reactive({
 | 
					const state = reactive({
 | 
				
			||||||
 | 
					    isDark: false,
 | 
				
			||||||
    isScreenfull: false,
 | 
					    isScreenfull: false,
 | 
				
			||||||
    isShowUserNewsPopover: false,
 | 
					    isShowUserNewsPopover: false,
 | 
				
			||||||
    disabledI18n: 'zh-cn',
 | 
					    disabledI18n: 'zh-cn',
 | 
				
			||||||
@@ -122,8 +133,9 @@ const onHandleCommandClick = (path: string) => {
 | 
				
			|||||||
            showCancelButton: true,
 | 
					            showCancelButton: true,
 | 
				
			||||||
            confirmButtonText: '确定',
 | 
					            confirmButtonText: '确定',
 | 
				
			||||||
            cancelButtonText: '取消',
 | 
					            cancelButtonText: '取消',
 | 
				
			||||||
            beforeClose: (action, instance, done) => {
 | 
					            beforeClose: async (action, instance, done) => {
 | 
				
			||||||
                if (action === 'confirm') {
 | 
					                if (action === 'confirm') {
 | 
				
			||||||
 | 
					                    await openApi.logout();
 | 
				
			||||||
                    instance.confirmButtonLoading = true;
 | 
					                    instance.confirmButtonLoading = true;
 | 
				
			||||||
                    instance.confirmButtonText = '退出中';
 | 
					                    instance.confirmButtonText = '退出中';
 | 
				
			||||||
                    setTimeout(() => {
 | 
					                    setTimeout(() => {
 | 
				
			||||||
@@ -145,12 +157,25 @@ const onHandleCommandClick = (path: string) => {
 | 
				
			|||||||
                    ElMessage.success('安全退出成功!');
 | 
					                    ElMessage.success('安全退出成功!');
 | 
				
			||||||
                }, 300);
 | 
					                }, 300);
 | 
				
			||||||
            })
 | 
					            })
 | 
				
			||||||
            .catch(() => { });
 | 
					            .catch(() => {});
 | 
				
			||||||
    } else {
 | 
					    } else {
 | 
				
			||||||
        router.push(path);
 | 
					        router.push(path);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const switchDark = (isDark: boolean) => {
 | 
				
			||||||
 | 
					    themeConfig.value.isDark = isDark;
 | 
				
			||||||
 | 
					    setLocal('themeConfig', themeConfig.value);
 | 
				
			||||||
 | 
					    const body = document.documentElement as HTMLElement;
 | 
				
			||||||
 | 
					    if (isDark) {
 | 
				
			||||||
 | 
					        body.setAttribute('class', 'dark');
 | 
				
			||||||
 | 
					        themeConfig.value.editorTheme = 'vs-dark';
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					        body.setAttribute('class', '');
 | 
				
			||||||
 | 
					        themeConfig.value.editorTheme = 'SolarizedLight';
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// // 菜单搜索点击
 | 
					// // 菜单搜索点击
 | 
				
			||||||
const onSearchClick = () => {
 | 
					const onSearchClick = () => {
 | 
				
			||||||
    searchRef.value.openSearch();
 | 
					    searchRef.value.openSearch();
 | 
				
			||||||
@@ -187,6 +212,10 @@ const initComponentSize = () => {
 | 
				
			|||||||
// 页面加载时
 | 
					// 页面加载时
 | 
				
			||||||
onMounted(() => {
 | 
					onMounted(() => {
 | 
				
			||||||
    if (getLocal('themeConfig')) {
 | 
					    if (getLocal('themeConfig')) {
 | 
				
			||||||
 | 
					        const isDark = themeConfig.value.isDark;
 | 
				
			||||||
 | 
					        state.isDark = isDark;
 | 
				
			||||||
 | 
					        switchDark(isDark);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        initComponentSize();
 | 
					        initComponentSize();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
@@ -244,4 +273,5 @@ onMounted(() => {
 | 
				
			|||||||
    ::v-deep(.el-badge__content.is-fixed) {
 | 
					    ::v-deep(.el-badge__content.is-fixed) {
 | 
				
			||||||
        top: 12px;
 | 
					        top: 12px;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}</style>
 | 
					}
 | 
				
			||||||
 | 
					</style>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -39,9 +39,7 @@ export default {
 | 
				
			|||||||
            state.newsList = [];
 | 
					            state.newsList = [];
 | 
				
			||||||
        };
 | 
					        };
 | 
				
			||||||
        // 前往通知中心点击
 | 
					        // 前往通知中心点击
 | 
				
			||||||
        const toMsgCenter = () => {
 | 
					        const toMsgCenter = () => {};
 | 
				
			||||||
            
 | 
					 | 
				
			||||||
        };
 | 
					 | 
				
			||||||
        return {
 | 
					        return {
 | 
				
			||||||
            onAllReadClick,
 | 
					            onAllReadClick,
 | 
				
			||||||
            toMsgCenter,
 | 
					            toMsgCenter,
 | 
				
			||||||
@@ -62,7 +60,7 @@ export default {
 | 
				
			|||||||
        height: 35px;
 | 
					        height: 35px;
 | 
				
			||||||
        align-items: center;
 | 
					        align-items: center;
 | 
				
			||||||
        .head-box-btn {
 | 
					        .head-box-btn {
 | 
				
			||||||
            color: var(--color-primary);
 | 
					            color: var(--el-color-primary);
 | 
				
			||||||
            font-size: 13px;
 | 
					            font-size: 13px;
 | 
				
			||||||
            cursor: pointer;
 | 
					            cursor: pointer;
 | 
				
			||||||
            opacity: 0.8;
 | 
					            opacity: 0.8;
 | 
				
			||||||
@@ -90,7 +88,7 @@ export default {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
    .foot-box {
 | 
					    .foot-box {
 | 
				
			||||||
        height: 35px;
 | 
					        height: 35px;
 | 
				
			||||||
        color: var(--color-primary);
 | 
					        color: var(--el-color-primary);
 | 
				
			||||||
        font-size: 13px;
 | 
					        font-size: 13px;
 | 
				
			||||||
        cursor: pointer;
 | 
					        cursor: pointer;
 | 
				
			||||||
        opacity: 0.8;
 | 
					        opacity: 0.8;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -2,29 +2,43 @@
 | 
				
			|||||||
    <div class="layout-navbars-tagsview" :class="{ 'layout-navbars-tagsview-shadow': themeConfig.layout === 'classic' }">
 | 
					    <div class="layout-navbars-tagsview" :class="{ 'layout-navbars-tagsview-shadow': themeConfig.layout === 'classic' }">
 | 
				
			||||||
        <el-scrollbar ref="scrollbarRef" @wheel.prevent="onHandleScroll">
 | 
					        <el-scrollbar ref="scrollbarRef" @wheel.prevent="onHandleScroll">
 | 
				
			||||||
            <ul class="layout-navbars-tagsview-ul" :class="setTagsStyle" ref="tagsUlRef">
 | 
					            <ul class="layout-navbars-tagsview-ul" :class="setTagsStyle" ref="tagsUlRef">
 | 
				
			||||||
                <li v-for="(v, k) in state.tagsViewList" :key="k" class="layout-navbars-tagsview-ul-li" :data-name="v.name"
 | 
					                <li
 | 
				
			||||||
                    :class="{ 'is-active': isActive(v) }" @contextmenu.prevent="onContextmenu(v, $event)"
 | 
					                    v-for="(v, k) in state.tagsViewList"
 | 
				
			||||||
                    @click="onTagsClick(v, k)" :ref="
 | 
					                    :key="k"
 | 
				
			||||||
 | 
					                    class="layout-navbars-tagsview-ul-li"
 | 
				
			||||||
 | 
					                    :data-name="v.name"
 | 
				
			||||||
 | 
					                    :class="{ 'is-active': isActive(v) }"
 | 
				
			||||||
 | 
					                    @contextmenu.prevent="onContextmenu(v, $event)"
 | 
				
			||||||
 | 
					                    @click="onTagsClick(v, k)"
 | 
				
			||||||
 | 
					                    :ref="
 | 
				
			||||||
                        (el) => {
 | 
					                        (el) => {
 | 
				
			||||||
                            if (el) tagsRefs[k] = el;
 | 
					                            if (el) tagsRefs[k] = el;
 | 
				
			||||||
                        }
 | 
					                        }
 | 
				
			||||||
                    ">
 | 
					                    "
 | 
				
			||||||
                    <SvgIcon name="iconfont icon-tag-view-active" class="layout-navbars-tagsview-ul-li-iconfont font14"
 | 
					                >
 | 
				
			||||||
                        v-if="isActive(v)" />
 | 
					                    <SvgIcon name="iconfont icon-tag-view-active" class="layout-navbars-tagsview-ul-li-iconfont font14" v-if="isActive(v)" />
 | 
				
			||||||
                    <SvgIcon :name="v.meta.icon" class="layout-navbars-tagsview-ul-li-iconfont"
 | 
					                    <SvgIcon :name="v.meta.icon" class="layout-navbars-tagsview-ul-li-iconfont" v-if="!isActive(v) && themeConfig.isTagsviewIcon" />
 | 
				
			||||||
                        v-if="!isActive(v) && themeConfig.isTagsviewIcon" />
 | 
					 | 
				
			||||||
                    <span>{{ v.meta.title }}</span>
 | 
					                    <span>{{ v.meta.title }}</span>
 | 
				
			||||||
                    <template v-if="isActive(v)">
 | 
					                    <template v-if="isActive(v)">
 | 
				
			||||||
                        <SvgIcon name="RefreshRight" class="font14 ml5 layout-navbars-tagsview-ul-li-refresh"
 | 
					                        <SvgIcon
 | 
				
			||||||
                            @click.stop="refreshCurrentTagsView($route.fullPath)" />
 | 
					                            name="RefreshRight"
 | 
				
			||||||
                        <SvgIcon name="Close" class="font14 layout-navbars-tagsview-ul-li-icon layout-icon-active"
 | 
					                            class="font14 ml5 layout-navbars-tagsview-ul-li-refresh"
 | 
				
			||||||
 | 
					                            @click.stop="refreshCurrentTagsView($route.fullPath)"
 | 
				
			||||||
 | 
					                        />
 | 
				
			||||||
 | 
					                        <SvgIcon
 | 
				
			||||||
 | 
					                            name="Close"
 | 
				
			||||||
 | 
					                            class="font14 layout-navbars-tagsview-ul-li-icon layout-icon-active"
 | 
				
			||||||
                            v-if="!v.meta.isAffix"
 | 
					                            v-if="!v.meta.isAffix"
 | 
				
			||||||
                            @click.stop="closeCurrentTagsView(themeConfig.isShareTagsView ? v.path : v.path)" />
 | 
					                            @click.stop="closeCurrentTagsView(themeConfig.isShareTagsView ? v.path : v.path)"
 | 
				
			||||||
 | 
					                        />
 | 
				
			||||||
                    </template>
 | 
					                    </template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    <SvgIcon name="Close" class="font14 layout-navbars-tagsview-ul-li-icon layout-icon-three"
 | 
					                    <SvgIcon
 | 
				
			||||||
 | 
					                        name="Close"
 | 
				
			||||||
 | 
					                        class="font14 layout-navbars-tagsview-ul-li-icon layout-icon-three"
 | 
				
			||||||
                        v-if="!v.meta.isAffix"
 | 
					                        v-if="!v.meta.isAffix"
 | 
				
			||||||
                        @click.stop="closeCurrentTagsView(themeConfig.isShareTagsView ? v.path : v.path)" />
 | 
					                        @click.stop="closeCurrentTagsView(themeConfig.isShareTagsView ? v.path : v.path)"
 | 
				
			||||||
 | 
					                    />
 | 
				
			||||||
                </li>
 | 
					                </li>
 | 
				
			||||||
            </ul>
 | 
					            </ul>
 | 
				
			||||||
        </el-scrollbar>
 | 
					        </el-scrollbar>
 | 
				
			||||||
@@ -107,7 +121,7 @@ const addTagsView = (path: string, to: any = null) => {
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const tagView = { ...to }
 | 
					    const tagView = { ...to };
 | 
				
			||||||
    // 防止Converting circular structure to JSON错误
 | 
					    // 防止Converting circular structure to JSON错误
 | 
				
			||||||
    tagView.matched = null;
 | 
					    tagView.matched = null;
 | 
				
			||||||
    tagView.redirectedFrom = null;
 | 
					    tagView.redirectedFrom = null;
 | 
				
			||||||
@@ -135,7 +149,7 @@ const closeCurrentTagsView = (path: string) => {
 | 
				
			|||||||
                    let next;
 | 
					                    let next;
 | 
				
			||||||
                    // 最后一个且高亮时
 | 
					                    // 最后一个且高亮时
 | 
				
			||||||
                    if (state.tagsViewList.length === k) {
 | 
					                    if (state.tagsViewList.length === k) {
 | 
				
			||||||
                        next = k !== arr.length ? arr[k] : arr[arr.length - 1]
 | 
					                        next = k !== arr.length ? arr[k] : arr[arr.length - 1];
 | 
				
			||||||
                    } else {
 | 
					                    } else {
 | 
				
			||||||
                        next = arr[k];
 | 
					                        next = arr[k];
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
@@ -366,8 +380,8 @@ onBeforeRouteUpdate((to) => {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
<style scoped lang="scss">
 | 
					<style scoped lang="scss">
 | 
				
			||||||
.layout-navbars-tagsview {
 | 
					.layout-navbars-tagsview {
 | 
				
			||||||
    background-color: var(--el-color-white);
 | 
					    background-color: var(--bg-main-color);
 | 
				
			||||||
    border-bottom: 1px solid var(--next-border-color-light);
 | 
					    border-bottom: 1px solid var(--el-border-color-light, #ebeef5);
 | 
				
			||||||
    position: relative;
 | 
					    position: relative;
 | 
				
			||||||
    z-index: 4;
 | 
					    z-index: 4;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -2,29 +2,47 @@
 | 
				
			|||||||
    <div>
 | 
					    <div>
 | 
				
			||||||
        <el-form ref="loginFormRef" :model="loginForm" :rules="rules" class="login-content-form" size="large">
 | 
					        <el-form ref="loginFormRef" :model="loginForm" :rules="rules" class="login-content-form" size="large">
 | 
				
			||||||
            <el-form-item prop="username">
 | 
					            <el-form-item prop="username">
 | 
				
			||||||
                <el-input type="text" placeholder="请输入用户名" prefix-icon="user" v-model="loginForm.username" clearable
 | 
					                <el-input type="text" placeholder="请输入用户名" prefix-icon="user" v-model="loginForm.username" clearable autocomplete="off"> </el-input>
 | 
				
			||||||
                    autocomplete="off">
 | 
					 | 
				
			||||||
                </el-input>
 | 
					 | 
				
			||||||
            </el-form-item>
 | 
					            </el-form-item>
 | 
				
			||||||
            <el-form-item prop="password">
 | 
					            <el-form-item prop="password">
 | 
				
			||||||
                <el-input type="password" placeholder="请输入密码" prefix-icon="lock" v-model="loginForm.password"
 | 
					                <el-input
 | 
				
			||||||
                    autocomplete="off" @keyup.enter="login" show-password>
 | 
					                    type="password"
 | 
				
			||||||
 | 
					                    placeholder="请输入密码"
 | 
				
			||||||
 | 
					                    prefix-icon="lock"
 | 
				
			||||||
 | 
					                    v-model="loginForm.password"
 | 
				
			||||||
 | 
					                    autocomplete="off"
 | 
				
			||||||
 | 
					                    @keyup.enter="login"
 | 
				
			||||||
 | 
					                    show-password
 | 
				
			||||||
 | 
					                >
 | 
				
			||||||
                </el-input>
 | 
					                </el-input>
 | 
				
			||||||
            </el-form-item>
 | 
					            </el-form-item>
 | 
				
			||||||
            <el-form-item v-if="isUseLoginCaptcha" prop="captcha">
 | 
					            <el-form-item v-if="accountLoginSecurity.useCaptcha" prop="captcha">
 | 
				
			||||||
                <el-row :gutter="15">
 | 
					                <el-row :gutter="15">
 | 
				
			||||||
                    <el-col :span="16">
 | 
					                    <el-col :span="16">
 | 
				
			||||||
                        <el-input type="text" maxlength="6" placeholder="请输入验证码" prefix-icon="position"
 | 
					                        <el-input
 | 
				
			||||||
                            v-model="loginForm.captcha" clearable autocomplete="off" @keyup.enter="login"></el-input>
 | 
					                            type="text"
 | 
				
			||||||
 | 
					                            maxlength="6"
 | 
				
			||||||
 | 
					                            placeholder="请输入验证码"
 | 
				
			||||||
 | 
					                            prefix-icon="position"
 | 
				
			||||||
 | 
					                            v-model="loginForm.captcha"
 | 
				
			||||||
 | 
					                            clearable
 | 
				
			||||||
 | 
					                            autocomplete="off"
 | 
				
			||||||
 | 
					                            @keyup.enter="login"
 | 
				
			||||||
 | 
					                        ></el-input>
 | 
				
			||||||
                    </el-col>
 | 
					                    </el-col>
 | 
				
			||||||
                    <el-col :span="8">
 | 
					                    <el-col :span="8">
 | 
				
			||||||
                        <div class="login-content-code">
 | 
					                        <div class="login-content-code">
 | 
				
			||||||
                            <img class="login-content-code-img" @click="getCaptcha" width="130px" height="40px"
 | 
					                            <img class="login-content-code-img" @click="getCaptcha" width="130px" height="40px" :src="captchaImage" style="cursor: pointer" />
 | 
				
			||||||
                                :src="captchaImage" style="cursor: pointer" />
 | 
					 | 
				
			||||||
                        </div>
 | 
					                        </div>
 | 
				
			||||||
                    </el-col>
 | 
					                    </el-col>
 | 
				
			||||||
                </el-row>
 | 
					                </el-row>
 | 
				
			||||||
            </el-form-item>
 | 
					            </el-form-item>
 | 
				
			||||||
 | 
					            <el-form-item v-if="ldapEnabled" prop="ldapLogin">
 | 
				
			||||||
 | 
					                <el-checkbox v-model="loginForm.ldapLogin" label="LDAP 登录" size="small" />
 | 
				
			||||||
 | 
					            </el-form-item>
 | 
				
			||||||
 | 
					            <span v-if="showLoginFailTips" style="color: #f56c6c; font-size: 12px">
 | 
				
			||||||
 | 
					                提示:登录失败超过{{ accountLoginSecurity.loginFailCount }}次后将被限制{{ accountLoginSecurity.loginFailMin }}分钟内不可再次登录
 | 
				
			||||||
 | 
					            </span>
 | 
				
			||||||
            <el-form-item>
 | 
					            <el-form-item>
 | 
				
			||||||
                <el-button type="primary" class="login-content-submit" round @click="login" :loading="loading.signIn">
 | 
					                <el-button type="primary" class="login-content-submit" round @click="login" :loading="loading.signIn">
 | 
				
			||||||
                    <span>登 录</span>
 | 
					                    <span>登 录</span>
 | 
				
			||||||
@@ -32,20 +50,21 @@
 | 
				
			|||||||
            </el-form-item>
 | 
					            </el-form-item>
 | 
				
			||||||
        </el-form>
 | 
					        </el-form>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        <el-dialog title="修改密码" v-model="changePwdDialog.visible" :close-on-click-modal="false" width="450px"
 | 
					        <el-dialog title="修改密码" v-model="changePwdDialog.visible" :close-on-click-modal="false" width="450px" :destroy-on-close="true">
 | 
				
			||||||
            :destroy-on-close="true">
 | 
					            <el-form :model="changePwdDialog.form" :rules="changePwdDialog.rules" ref="changePwdFormRef" label-width="auto">
 | 
				
			||||||
            <el-form :model="changePwdDialog.form" :rules="changePwdDialog.rules" ref="changePwdFormRef"
 | 
					 | 
				
			||||||
                label-width="65px">
 | 
					 | 
				
			||||||
                <el-form-item prop="username" label="用户名" required>
 | 
					                <el-form-item prop="username" label="用户名" required>
 | 
				
			||||||
                    <el-input v-model.trim="changePwdDialog.form.username" disabled></el-input>
 | 
					                    <el-input v-model.trim="changePwdDialog.form.username" disabled></el-input>
 | 
				
			||||||
                </el-form-item>
 | 
					                </el-form-item>
 | 
				
			||||||
                <el-form-item prop="oldPassword" label="旧密码" required>
 | 
					                <el-form-item prop="oldPassword" label="旧密码" required>
 | 
				
			||||||
                    <el-input v-model.trim="changePwdDialog.form.oldPassword" autocomplete="new-password"
 | 
					                    <el-input v-model.trim="changePwdDialog.form.oldPassword" autocomplete="new-password" type="password"></el-input>
 | 
				
			||||||
                        type="password"></el-input>
 | 
					 | 
				
			||||||
                </el-form-item>
 | 
					                </el-form-item>
 | 
				
			||||||
                <el-form-item prop="newPassword" label="新密码" required>
 | 
					                <el-form-item prop="newPassword" label="新密码" required>
 | 
				
			||||||
                    <el-input v-model.trim="changePwdDialog.form.newPassword" placeholder="须为8位以上且包含字⺟⼤⼩写+数字+特殊符号"
 | 
					                    <el-input
 | 
				
			||||||
                        type="password" autocomplete="new-password"></el-input>
 | 
					                        v-model.trim="changePwdDialog.form.newPassword"
 | 
				
			||||||
 | 
					                        placeholder="须为8位以上且包含字⺟⼤⼩写+数字+特殊符号"
 | 
				
			||||||
 | 
					                        type="password"
 | 
				
			||||||
 | 
					                        autocomplete="new-password"
 | 
				
			||||||
 | 
					                    ></el-input>
 | 
				
			||||||
                </el-form-item>
 | 
					                </el-form-item>
 | 
				
			||||||
            </el-form>
 | 
					            </el-form>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -56,6 +75,55 @@
 | 
				
			|||||||
                </div>
 | 
					                </div>
 | 
				
			||||||
            </template>
 | 
					            </template>
 | 
				
			||||||
        </el-dialog>
 | 
					        </el-dialog>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        <el-dialog
 | 
				
			||||||
 | 
					            title="OTP校验"
 | 
				
			||||||
 | 
					            v-model="otpDialog.visible"
 | 
				
			||||||
 | 
					            @close="loading.signIn = false"
 | 
				
			||||||
 | 
					            :close-on-click-modal="false"
 | 
				
			||||||
 | 
					            width="350px"
 | 
				
			||||||
 | 
					            :destroy-on-close="true"
 | 
				
			||||||
 | 
					        >
 | 
				
			||||||
 | 
					            <el-form ref="otpFormRef" :model="otpDialog.form" :rules="otpDialog.rules" @submit.native.prevent label-width="auto">
 | 
				
			||||||
 | 
					                <el-form-item v-if="otpDialog.otpUrl" label="二维码">
 | 
				
			||||||
 | 
					                    <qrcode-vue :value="otpDialog.otpUrl" :size="200" level="H" />
 | 
				
			||||||
 | 
					                </el-form-item>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                <el-form-item prop="code" label="OTP" required>
 | 
				
			||||||
 | 
					                    <el-input
 | 
				
			||||||
 | 
					                        style="width: 220px"
 | 
				
			||||||
 | 
					                        ref="otpCodeInputRef"
 | 
				
			||||||
 | 
					                        v-model.trim="otpDialog.form.code"
 | 
				
			||||||
 | 
					                        clearable
 | 
				
			||||||
 | 
					                        @keyup.enter="otpVerify"
 | 
				
			||||||
 | 
					                        placeholder="请输入令牌APP中显示的授权码"
 | 
				
			||||||
 | 
					                    ></el-input>
 | 
				
			||||||
 | 
					                </el-form-item>
 | 
				
			||||||
 | 
					            </el-form>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            <template #footer>
 | 
				
			||||||
 | 
					                <div class="dialog-footer">
 | 
				
			||||||
 | 
					                    <el-button @click="otpVerify" type="primary" :loading="loading.otpConfirm">确 定</el-button>
 | 
				
			||||||
 | 
					                </div>
 | 
				
			||||||
 | 
					            </template>
 | 
				
			||||||
 | 
					        </el-dialog>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        <el-dialog title="修改基本信息" v-model="baseInfoDialog.visible" :close-on-click-modal="false" width="450px" :destroy-on-close="true">
 | 
				
			||||||
 | 
					            <el-form :model="baseInfoDialog.form" :rules="baseInfoDialog.rules" ref="baseInfoFormRef" label-width="auto">
 | 
				
			||||||
 | 
					                <el-form-item prop="username" label="用户名" required>
 | 
				
			||||||
 | 
					                    <el-input v-model.trim="baseInfoDialog.form.username"></el-input>
 | 
				
			||||||
 | 
					                </el-form-item>
 | 
				
			||||||
 | 
					                <el-form-item prop="name" label="姓名" required>
 | 
				
			||||||
 | 
					                    <el-input v-model.trim="baseInfoDialog.form.name"></el-input>
 | 
				
			||||||
 | 
					                </el-form-item>
 | 
				
			||||||
 | 
					            </el-form>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            <template #footer>
 | 
				
			||||||
 | 
					                <div class="dialog-footer">
 | 
				
			||||||
 | 
					                    <el-button @click="updateUserInfo()" type="primary" :loading="loading.updateUserConfirm">确 定</el-button>
 | 
				
			||||||
 | 
					                </div>
 | 
				
			||||||
 | 
					            </template>
 | 
				
			||||||
 | 
					        </el-dialog>
 | 
				
			||||||
    </div>
 | 
					    </div>
 | 
				
			||||||
</template>
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -64,34 +132,48 @@ 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 { setSession, setUserInfo2Session, setUseWatermark2Session } from '@/common/utils/storage';
 | 
					import { getSession, setSession, setUserInfo2Session, setUseWatermark2Session } 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';
 | 
				
			||||||
import { useLoginCaptcha, useWartermark } from '@/common/sysconfig';
 | 
					import { getAccountLoginSecurity, getLdapEnabled, useWartermark } from '@/common/sysconfig';
 | 
				
			||||||
import { letterAvatar } from '@/common/utils/string';
 | 
					import { letterAvatar } from '@/common/utils/string';
 | 
				
			||||||
import { useUserInfo } from '@/store/userInfo';
 | 
					import { useUserInfo } from '@/store/userInfo';
 | 
				
			||||||
 | 
					import QrcodeVue from 'qrcode.vue';
 | 
				
			||||||
 | 
					import { personApi } from '@/views/personal/api';
 | 
				
			||||||
 | 
					import { AccountUsernamePattern } from '@/common/pattern';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const rules = {
 | 
					const rules = {
 | 
				
			||||||
    username: [{ required: true, message: '请输入用户名', trigger: 'blur' }],
 | 
					    username: [{ required: true, message: '请输入用户名', trigger: 'blur' }],
 | 
				
			||||||
    password: [{ required: true, message: '请输入密码', trigger: 'blur' }],
 | 
					    password: [{ required: true, message: '请输入密码', trigger: 'blur' }],
 | 
				
			||||||
    captcha: [{ required: true, message: '请输入验证码', trigger: 'blur' }],
 | 
					    captcha: [{ required: true, message: '请输入验证码', trigger: 'blur' }],
 | 
				
			||||||
}
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const route = useRoute();
 | 
					const route = useRoute();
 | 
				
			||||||
const router = useRouter();
 | 
					const router = useRouter();
 | 
				
			||||||
const loginFormRef: any = ref(null);
 | 
					const loginFormRef: any = ref(null);
 | 
				
			||||||
const changePwdFormRef: any = ref(null);
 | 
					const changePwdFormRef: any = ref(null);
 | 
				
			||||||
 | 
					const otpFormRef: any = ref(null);
 | 
				
			||||||
 | 
					const otpCodeInputRef: any = ref(null);
 | 
				
			||||||
 | 
					const baseInfoFormRef: any = ref(null);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const state = reactive({
 | 
					const state = reactive({
 | 
				
			||||||
    isUseLoginCaptcha: false,
 | 
					    accountLoginSecurity: {
 | 
				
			||||||
 | 
					        useCaptcha: true,
 | 
				
			||||||
 | 
					        useOtp: false,
 | 
				
			||||||
 | 
					        loginFailCount: 5,
 | 
				
			||||||
 | 
					        loginFailMin: 10,
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    showLoginFailTips: false,
 | 
				
			||||||
    captchaImage: '',
 | 
					    captchaImage: '',
 | 
				
			||||||
    loginForm: {
 | 
					    loginForm: {
 | 
				
			||||||
        username: '',
 | 
					        username: '',
 | 
				
			||||||
        password: '',
 | 
					        password: '',
 | 
				
			||||||
        captcha: '',
 | 
					        captcha: '',
 | 
				
			||||||
        cid: '',
 | 
					        cid: '',
 | 
				
			||||||
 | 
					        ldapLogin: false,
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					    loginRes: {} as any,
 | 
				
			||||||
    changePwdDialog: {
 | 
					    changePwdDialog: {
 | 
				
			||||||
        visible: false,
 | 
					        visible: false,
 | 
				
			||||||
        form: {
 | 
					        form: {
 | 
				
			||||||
@@ -110,34 +192,67 @@ const state = reactive({
 | 
				
			|||||||
            ],
 | 
					            ],
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					    otpDialog: {
 | 
				
			||||||
 | 
					        visible: false,
 | 
				
			||||||
 | 
					        otpUrl: '',
 | 
				
			||||||
 | 
					        form: {
 | 
				
			||||||
 | 
					            code: '',
 | 
				
			||||||
 | 
					            otpToken: '',
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        rules: {
 | 
				
			||||||
 | 
					            code: [{ required: true, message: '请输入OTP授权码', trigger: 'blur' }],
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    baseInfoDialog: {
 | 
				
			||||||
 | 
					        visible: false,
 | 
				
			||||||
 | 
					        form: {
 | 
				
			||||||
 | 
					            username: '',
 | 
				
			||||||
 | 
					            name: '',
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        rules: {
 | 
				
			||||||
 | 
					            username: [
 | 
				
			||||||
 | 
					                { required: true, message: '请输入用户名', trigger: 'blur' },
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    pattern: AccountUsernamePattern.pattern,
 | 
				
			||||||
 | 
					                    message: AccountUsernamePattern.message,
 | 
				
			||||||
 | 
					                    trigger: ['blur'],
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					            ],
 | 
				
			||||||
 | 
					            name: [{ required: true, message: '请输入姓名', trigger: 'blur' }],
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
    loading: {
 | 
					    loading: {
 | 
				
			||||||
        signIn: false,
 | 
					        signIn: false,
 | 
				
			||||||
        changePwd: false,
 | 
					        changePwd: false,
 | 
				
			||||||
 | 
					        otpConfirm: false,
 | 
				
			||||||
 | 
					        updateUserConfirm: false,
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					    ldapEnabled: false,
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const {
 | 
					const { accountLoginSecurity, showLoginFailTips, captchaImage, loginForm, changePwdDialog, otpDialog, baseInfoDialog, loading, ldapEnabled } = toRefs(state);
 | 
				
			||||||
    isUseLoginCaptcha,
 | 
					 | 
				
			||||||
    captchaImage,
 | 
					 | 
				
			||||||
    loginForm,
 | 
					 | 
				
			||||||
    changePwdDialog,
 | 
					 | 
				
			||||||
    loading,
 | 
					 | 
				
			||||||
} = toRefs(state)
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
onMounted(async () => {
 | 
					onMounted(async () => {
 | 
				
			||||||
    nextTick(async () => {
 | 
					    nextTick(async () => {
 | 
				
			||||||
        state.isUseLoginCaptcha = await useLoginCaptcha();
 | 
					        const res = await getAccountLoginSecurity();
 | 
				
			||||||
 | 
					        if (res) {
 | 
				
			||||||
 | 
					            state.accountLoginSecurity = res;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
        getCaptcha();
 | 
					        getCaptcha();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const ldap = await getLdapEnabled();
 | 
				
			||||||
 | 
					        state.ldapEnabled = ldap;
 | 
				
			||||||
 | 
					        state.loginForm.ldapLogin = ldap;
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
    // 移除公钥, 方便后续重新获取
 | 
					    // 移除公钥, 方便后续重新获取
 | 
				
			||||||
    sessionStorage.removeItem('RsaPublicKey');
 | 
					    sessionStorage.removeItem('RsaPublicKey');
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const getCaptcha = async () => {
 | 
					const getCaptcha = async () => {
 | 
				
			||||||
    if (!state.isUseLoginCaptcha) {
 | 
					    if (!state.accountLoginSecurity.useCaptcha) {
 | 
				
			||||||
        return;
 | 
					        return;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    let res: any = await openApi.captcha.request();
 | 
					    let res: any = await openApi.captcha();
 | 
				
			||||||
    state.captchaImage = res.base64Captcha;
 | 
					    state.captchaImage = res.base64Captcha;
 | 
				
			||||||
    state.loginForm.cid = res.cid;
 | 
					    state.loginForm.cid = res.cid;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
@@ -158,6 +273,22 @@ const login = () => {
 | 
				
			|||||||
    });
 | 
					    });
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const otpVerify = async () => {
 | 
				
			||||||
 | 
					    otpFormRef.value.validate(async (valid: boolean) => {
 | 
				
			||||||
 | 
					        if (!valid) {
 | 
				
			||||||
 | 
					            return false;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        try {
 | 
				
			||||||
 | 
					            state.loading.otpConfirm = true;
 | 
				
			||||||
 | 
					            const accessToken = await openApi.otpVerify(state.otpDialog.form);
 | 
				
			||||||
 | 
					            await signInSuccess(accessToken);
 | 
				
			||||||
 | 
					            state.otpDialog.visible = false;
 | 
				
			||||||
 | 
					        } finally {
 | 
				
			||||||
 | 
					            state.loading.otpConfirm = false;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// 登录
 | 
					// 登录
 | 
				
			||||||
const onSignIn = async () => {
 | 
					const onSignIn = async () => {
 | 
				
			||||||
    state.loading.signIn = true;
 | 
					    state.loading.signIn = true;
 | 
				
			||||||
@@ -166,9 +297,11 @@ const onSignIn = async () => {
 | 
				
			|||||||
    try {
 | 
					    try {
 | 
				
			||||||
        const loginReq = { ...state.loginForm };
 | 
					        const loginReq = { ...state.loginForm };
 | 
				
			||||||
        loginReq.password = await RsaEncrypt(originPwd);
 | 
					        loginReq.password = await RsaEncrypt(originPwd);
 | 
				
			||||||
        loginRes = await openApi.login.request(loginReq);
 | 
					        if (state.loginForm.ldapLogin) {
 | 
				
			||||||
        // 存储 token 到浏览器缓存
 | 
					            loginRes = await openApi.ldapLogin(loginReq);
 | 
				
			||||||
        setSession('token', loginRes.token);
 | 
					        } else {
 | 
				
			||||||
 | 
					            loginRes = await openApi.login(loginReq);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
    } catch (e: any) {
 | 
					    } catch (e: any) {
 | 
				
			||||||
        state.loading.signIn = false;
 | 
					        state.loading.signIn = false;
 | 
				
			||||||
        state.loginForm.captcha = '';
 | 
					        state.loginForm.captcha = '';
 | 
				
			||||||
@@ -180,17 +313,42 @@ const onSignIn = async () => {
 | 
				
			|||||||
            state.changePwdDialog.visible = true;
 | 
					            state.changePwdDialog.visible = true;
 | 
				
			||||||
        } else {
 | 
					        } else {
 | 
				
			||||||
            getCaptcha();
 | 
					            getCaptcha();
 | 
				
			||||||
 | 
					            state.showLoginFailTips = true;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        return;
 | 
					        return;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					    state.showLoginFailTips = false;
 | 
				
			||||||
 | 
					    loginResDeal(loginRes);
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const updateUserInfo = async () => {
 | 
				
			||||||
 | 
					    baseInfoFormRef.value.validate(async (valid: boolean) => {
 | 
				
			||||||
 | 
					        if (!valid) {
 | 
				
			||||||
 | 
					            return false;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        try {
 | 
				
			||||||
 | 
					            state.loading.updateUserConfirm = true;
 | 
				
			||||||
 | 
					            const form = state.baseInfoDialog.form;
 | 
				
			||||||
 | 
					            await personApi.updateAccount.request(state.baseInfoDialog.form);
 | 
				
			||||||
 | 
					            state.baseInfoDialog.visible = false;
 | 
				
			||||||
 | 
					            useUserInfo().userInfo.username = form.username;
 | 
				
			||||||
 | 
					            useUserInfo().userInfo.name = form.name;
 | 
				
			||||||
 | 
					            await toIndex();
 | 
				
			||||||
 | 
					        } finally {
 | 
				
			||||||
 | 
					            state.loading.updateUserConfirm = false;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const loginResDeal = (loginRes: any) => {
 | 
				
			||||||
 | 
					    state.loginRes = loginRes;
 | 
				
			||||||
    // 用户信息
 | 
					    // 用户信息
 | 
				
			||||||
    const userInfos = {
 | 
					    const userInfos = {
 | 
				
			||||||
        name: loginRes.name,
 | 
					        name: loginRes.name,
 | 
				
			||||||
        username: state.loginForm.username,
 | 
					        username: loginRes.username,
 | 
				
			||||||
        // 头像
 | 
					        // 头像
 | 
				
			||||||
        photo: letterAvatar(state.loginForm.username),
 | 
					        photo: letterAvatar(loginRes.username),
 | 
				
			||||||
        time: new Date().getTime(),
 | 
					        time: new Date().getTime(),
 | 
				
			||||||
        permissions: loginRes.permissions,
 | 
					 | 
				
			||||||
        lastLoginTime: loginRes.lastLoginTime,
 | 
					        lastLoginTime: loginRes.lastLoginTime,
 | 
				
			||||||
        lastLoginIp: loginRes.lastLoginIp,
 | 
					        lastLoginIp: loginRes.lastLoginIp,
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
@@ -199,12 +357,42 @@ const onSignIn = async () => {
 | 
				
			|||||||
    setUserInfo2Session(userInfos);
 | 
					    setUserInfo2Session(userInfos);
 | 
				
			||||||
    // 1、请注意执行顺序(存储用户信息到vuex)
 | 
					    // 1、请注意执行顺序(存储用户信息到vuex)
 | 
				
			||||||
    useUserInfo().setUserInfo(userInfos);
 | 
					    useUserInfo().setUserInfo(userInfos);
 | 
				
			||||||
    await initRouter();
 | 
					
 | 
				
			||||||
    signInSuccess();
 | 
					    const token = loginRes.token;
 | 
				
			||||||
 | 
					    // 如果不需要otp校验,则该token即为accessToken,否则为otp校验token
 | 
				
			||||||
 | 
					    if (loginRes.otp == -1) {
 | 
				
			||||||
 | 
					        signInSuccess(token);
 | 
				
			||||||
 | 
					        return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    state.otpDialog.form.otpToken = token;
 | 
				
			||||||
 | 
					    state.otpDialog.otpUrl = loginRes.otpUrl;
 | 
				
			||||||
 | 
					    state.otpDialog.visible = true;
 | 
				
			||||||
 | 
					    setTimeout(() => {
 | 
				
			||||||
 | 
					        otpCodeInputRef.value.focus();
 | 
				
			||||||
 | 
					    }, 400);
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// 登录成功后的跳转
 | 
					// 登录成功后的跳转
 | 
				
			||||||
const signInSuccess = () => {
 | 
					const signInSuccess = async (accessToken: string = '') => {
 | 
				
			||||||
 | 
					    if (!accessToken) {
 | 
				
			||||||
 | 
					        accessToken = getSession('token');
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    // 存储 token 到浏览器缓存
 | 
				
			||||||
 | 
					    setSession('token', accessToken);
 | 
				
			||||||
 | 
					    // 初始化路由
 | 
				
			||||||
 | 
					    await initRouter();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // 判断是否为第一次oauth2登录,是的话需要用户填写姓名和用户名
 | 
				
			||||||
 | 
					    if (state.loginRes.isFirstOauth2Login) {
 | 
				
			||||||
 | 
					        state.baseInfoDialog.form.username = state.loginRes.username;
 | 
				
			||||||
 | 
					        state.baseInfoDialog.visible = true;
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					        await toIndex();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const toIndex = async () => {
 | 
				
			||||||
    // 初始化登录成功时间问候语
 | 
					    // 初始化登录成功时间问候语
 | 
				
			||||||
    let currentTimeInfo = currentTime.value;
 | 
					    let currentTimeInfo = currentTime.value;
 | 
				
			||||||
    // 登录成功,跳到转首页
 | 
					    // 登录成功,跳到转首页
 | 
				
			||||||
@@ -233,7 +421,7 @@ const changePwd = () => {
 | 
				
			|||||||
            const changePwdReq: any = { ...form };
 | 
					            const changePwdReq: any = { ...form };
 | 
				
			||||||
            changePwdReq.oldPassword = await RsaEncrypt(form.oldPassword);
 | 
					            changePwdReq.oldPassword = await RsaEncrypt(form.oldPassword);
 | 
				
			||||||
            changePwdReq.newPassword = await RsaEncrypt(form.newPassword);
 | 
					            changePwdReq.newPassword = await RsaEncrypt(form.newPassword);
 | 
				
			||||||
            await openApi.changePwd.request(changePwdReq);
 | 
					            await openApi.changePwd(changePwdReq);
 | 
				
			||||||
            ElMessage.success('密码修改成功, 新密码已填充至登录密码框');
 | 
					            ElMessage.success('密码修改成功, 新密码已填充至登录密码框');
 | 
				
			||||||
            state.loginForm.password = state.changePwdDialog.form.newPassword;
 | 
					            state.loginForm.password = state.changePwdDialog.form.newPassword;
 | 
				
			||||||
            state.changePwdDialog.visible = false;
 | 
					            state.changePwdDialog.visible = false;
 | 
				
			||||||
@@ -251,6 +439,10 @@ const cancelChangePwd = () => {
 | 
				
			|||||||
    state.changePwdDialog.form.username = '';
 | 
					    state.changePwdDialog.form.username = '';
 | 
				
			||||||
    getCaptcha();
 | 
					    getCaptcha();
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					defineExpose({
 | 
				
			||||||
 | 
					    loginResDeal,
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
</script>
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<style scoped lang="scss">
 | 
					<style scoped lang="scss">
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,8 +1,7 @@
 | 
				
			|||||||
<template>
 | 
					<template>
 | 
				
			||||||
    <el-form class="login-content-form">
 | 
					    <el-form class="login-content-form">
 | 
				
			||||||
        <el-form-item>
 | 
					        <el-form-item>
 | 
				
			||||||
            <el-input type="text" placeholder="请输入手机号" prefix-icon="el-icon-user" v-model="ruleForm.userName" clearable autocomplete="off">
 | 
					            <el-input type="text" placeholder="请输入手机号" prefix-icon="el-icon-user" v-model="ruleForm.userName" clearable autocomplete="off"> </el-input>
 | 
				
			||||||
            </el-input>
 | 
					 | 
				
			||||||
        </el-form-item>
 | 
					        </el-form-item>
 | 
				
			||||||
        <el-form-item>
 | 
					        <el-form-item>
 | 
				
			||||||
            <el-row :gutter="15">
 | 
					            <el-row :gutter="15">
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -9,7 +9,7 @@
 | 
				
			|||||||
                <el-tabs v-model="tabsActiveName" @tab-click="onTabsClick">
 | 
					                <el-tabs v-model="tabsActiveName" @tab-click="onTabsClick">
 | 
				
			||||||
                    <el-tab-pane label="账号密码登录" name="account" :disabled="tabsActiveName === 'account'">
 | 
					                    <el-tab-pane label="账号密码登录" name="account" :disabled="tabsActiveName === 'account'">
 | 
				
			||||||
                        <transition name="el-zoom-in-center">
 | 
					                        <transition name="el-zoom-in-center">
 | 
				
			||||||
                            <Account v-show="isTabPaneShow" />
 | 
					                            <Account v-show="isTabPaneShow" ref="loginForm" />
 | 
				
			||||||
                        </transition>
 | 
					                        </transition>
 | 
				
			||||||
                    </el-tab-pane>
 | 
					                    </el-tab-pane>
 | 
				
			||||||
                    <!-- <el-tab-pane label="手机号登录" name="mobile" :disabled="tabsActiveName === 'mobile'">
 | 
					                    <!-- <el-tab-pane label="手机号登录" name="mobile" :disabled="tabsActiveName === 'mobile'">
 | 
				
			||||||
@@ -18,10 +18,16 @@
 | 
				
			|||||||
                        </transition>
 | 
					                        </transition>
 | 
				
			||||||
                    </el-tab-pane> -->
 | 
					                    </el-tab-pane> -->
 | 
				
			||||||
                </el-tabs>
 | 
					                </el-tabs>
 | 
				
			||||||
                <!-- <div class="mt10">
 | 
					                <div class="mt20" v-show="oauth2LoginConfig.enable">
 | 
				
			||||||
                    <el-button type="text" size="small">第三方登录</el-button>
 | 
					                    <el-button link size="small">第三方登录: </el-button>
 | 
				
			||||||
                    <el-button type="text" size="small">友情链接</el-button>
 | 
					                    <el-tooltip :content="oauth2LoginConfig.name" placement="top-start">
 | 
				
			||||||
                </div> -->
 | 
					                        <el-button link size="small" type="primary" @click="oauth2Login">
 | 
				
			||||||
 | 
					                            <el-icon :size="18">
 | 
				
			||||||
 | 
					                                <Link />
 | 
				
			||||||
 | 
					                            </el-icon>
 | 
				
			||||||
 | 
					                        </el-button>
 | 
				
			||||||
 | 
					                    </el-tooltip>
 | 
				
			||||||
 | 
					                </div>
 | 
				
			||||||
            </div>
 | 
					            </div>
 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
        <!-- <div class="login-copyright">
 | 
					        <!-- <div class="login-copyright">
 | 
				
			||||||
@@ -32,27 +38,58 @@
 | 
				
			|||||||
</template>
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<script lang="ts" setup>
 | 
					<script lang="ts" setup>
 | 
				
			||||||
import { toRefs, reactive } from 'vue';
 | 
					import { toRefs, reactive, onMounted, h, ref } from 'vue';
 | 
				
			||||||
import Account from '@/views/login/component/AccountLogin.vue';
 | 
					import Account from '@/views/login/component/AccountLogin.vue';
 | 
				
			||||||
import { storeToRefs } from 'pinia';
 | 
					import { storeToRefs } from 'pinia';
 | 
				
			||||||
import { useThemeConfig } from '@/store/themeConfig';
 | 
					import { useThemeConfig } from '@/store/themeConfig';
 | 
				
			||||||
 | 
					import openApi from '@/common/openApi';
 | 
				
			||||||
 | 
					import config from '@/common/config';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const { themeConfig } = storeToRefs(useThemeConfig());
 | 
					const { themeConfig } = storeToRefs(useThemeConfig());
 | 
				
			||||||
const state = reactive({
 | 
					const state = reactive({
 | 
				
			||||||
    tabsActiveName: 'account',
 | 
					    tabsActiveName: 'account',
 | 
				
			||||||
    isTabPaneShow: true,
 | 
					    isTabPaneShow: true,
 | 
				
			||||||
 | 
					    oauth2LoginConfig: {
 | 
				
			||||||
 | 
					        name: 'OAuth2登录',
 | 
				
			||||||
 | 
					        enable: false,
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const {
 | 
					const loginForm = ref<{ loginResDeal: (data: any) => void } | null>(null);
 | 
				
			||||||
    isTabPaneShow,
 | 
					 | 
				
			||||||
    tabsActiveName,
 | 
					 | 
				
			||||||
} = toRefs(state)
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const { isTabPaneShow, tabsActiveName, oauth2LoginConfig: oauth2LoginConfig } = toRefs(state);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// 切换密码、手机登录
 | 
					// 切换密码、手机登录
 | 
				
			||||||
const onTabsClick = () => {
 | 
					const onTabsClick = () => {
 | 
				
			||||||
    state.isTabPaneShow = !state.isTabPaneShow;
 | 
					    state.isTabPaneShow = !state.isTabPaneShow;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					onMounted(async () => {
 | 
				
			||||||
 | 
					    state.oauth2LoginConfig = await openApi.oauth2LoginConfig();
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const oauth2Login = () => {
 | 
				
			||||||
 | 
					    const width = 700;
 | 
				
			||||||
 | 
					    const height = 500;
 | 
				
			||||||
 | 
					    var iTop = (window.screen.height - 30 - height) / 2; //获得窗口的垂直位置;
 | 
				
			||||||
 | 
					    var iLeft = (window.screen.width - 10 - width) / 2; //获得窗口的水平位置;
 | 
				
			||||||
 | 
					    // 小窗口打开oauth2鉴权
 | 
				
			||||||
 | 
					    let oauthWindow = window.open(config.baseApiUrl + '/auth/oauth2/login', 'oauth2', `height=${height},width=${width},top=${iTop},left=${iLeft},location=no`);
 | 
				
			||||||
 | 
					    if (oauthWindow) {
 | 
				
			||||||
 | 
					        const handler = (e: any) => {
 | 
				
			||||||
 | 
					            if (e.data.action === 'oauthLogin') {
 | 
				
			||||||
 | 
					                window.removeEventListener('message', handler);
 | 
				
			||||||
 | 
					                loginForm.value!.loginResDeal(e.data);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					        window.addEventListener('message', handler);
 | 
				
			||||||
 | 
					        setInterval(() => {
 | 
				
			||||||
 | 
					            if (oauthWindow!.closed) {
 | 
				
			||||||
 | 
					                window.removeEventListener('message', handler);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }, 1000);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
</script>
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<style scoped lang="scss">
 | 
					<style scoped lang="scss">
 | 
				
			||||||
@@ -70,7 +107,7 @@ const onTabsClick = () => {
 | 
				
			|||||||
        display: flex;
 | 
					        display: flex;
 | 
				
			||||||
        align-items: center;
 | 
					        align-items: center;
 | 
				
			||||||
        font-size: 20px;
 | 
					        font-size: 20px;
 | 
				
			||||||
        color: var(--color-primary);
 | 
					        color: var(--el-color-primary);
 | 
				
			||||||
        letter-spacing: 2px;
 | 
					        letter-spacing: 2px;
 | 
				
			||||||
        width: 90%;
 | 
					        width: 90%;
 | 
				
			||||||
        transform: translateX(-50%);
 | 
					        transform: translateX(-50%);
 | 
				
			||||||
@@ -84,10 +121,10 @@ const onTabsClick = () => {
 | 
				
			|||||||
        left: 50%;
 | 
					        left: 50%;
 | 
				
			||||||
        transform: translate(-50%, -50%) translate3d(0, 0, 0);
 | 
					        transform: translate(-50%, -50%) translate3d(0, 0, 0);
 | 
				
			||||||
        background-color: rgba(255, 255, 255, 0.99);
 | 
					        background-color: rgba(255, 255, 255, 0.99);
 | 
				
			||||||
        box-shadow: 0 2px 12px 0 var(--color-primary-light-5);
 | 
					        box-shadow: 0 2px 12px 0 var(--el-color-primary-light-5);
 | 
				
			||||||
        border-radius: 4px;
 | 
					        border-radius: 4px;
 | 
				
			||||||
        transition: height 0.2s linear;
 | 
					        transition: height 0.2s linear;
 | 
				
			||||||
        height: 480px;
 | 
					        height: 490px;
 | 
				
			||||||
        overflow: hidden;
 | 
					        overflow: hidden;
 | 
				
			||||||
        z-index: 1;
 | 
					        z-index: 1;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										39
									
								
								mayfly_go_web/src/views/oauth/Oauth2Callback.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								mayfly_go_web/src/views/oauth/Oauth2Callback.vue
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,39 @@
 | 
				
			|||||||
 | 
					<template>
 | 
				
			||||||
 | 
					    <div></div>
 | 
				
			||||||
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<script lang="ts" setup>
 | 
				
			||||||
 | 
					import { onMounted } from 'vue';
 | 
				
			||||||
 | 
					import { useRoute } from 'vue-router';
 | 
				
			||||||
 | 
					import { ElMessage } from 'element-plus';
 | 
				
			||||||
 | 
					import openApi from '@/common/openApi';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const route = useRoute();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					onMounted(async () => {
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					        const queryParam = route.query;
 | 
				
			||||||
 | 
					        // 使用hash路由,回调code可能会被设置到search
 | 
				
			||||||
 | 
					        // 如 localhost:8888/?code=xxxx/oauth2/callback,导致route.query获取不到值
 | 
				
			||||||
 | 
					        if (location.search) {
 | 
				
			||||||
 | 
					            const searchParams = location.search.split('?')[1];
 | 
				
			||||||
 | 
					            if (searchParams) {
 | 
				
			||||||
 | 
					                for (let searchParam of searchParams.split('&')) {
 | 
				
			||||||
 | 
					                    const searchParamSplit = searchParam.split('=');
 | 
				
			||||||
 | 
					                    queryParam[searchParamSplit[0]] = searchParamSplit[1];
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const res: any = await openApi.oauth2Callback(queryParam);
 | 
				
			||||||
 | 
					        ElMessage.success('授权认证成功');
 | 
				
			||||||
 | 
					        top?.opener.postMessage(res);
 | 
				
			||||||
 | 
					        window.close();
 | 
				
			||||||
 | 
					    } catch (e: any) {
 | 
				
			||||||
 | 
					        setTimeout(() => {
 | 
				
			||||||
 | 
					            window.close();
 | 
				
			||||||
 | 
					        }, 1500);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					</script>
 | 
				
			||||||
 | 
					<style lang="scss"></style>
 | 
				
			||||||
@@ -1,10 +1,15 @@
 | 
				
			|||||||
<template>
 | 
					<template>
 | 
				
			||||||
    <div style="width: 100%">
 | 
					    <div style="width: 100%">
 | 
				
			||||||
        <el-select @focus="getSshTunnelMachines" @change="change" style="width: 100%" v-model="sshTunnelMachineId"
 | 
					        <el-select
 | 
				
			||||||
            @clear="clear" placeholder="请选择SSH隧道机器" clearable>
 | 
					            @focus="getSshTunnelMachines"
 | 
				
			||||||
            <el-option v-for="item in sshTunnelMachineList" :key="item.id" :label="`${item.ip}:${item.port} [${item.name}]`"
 | 
					            @change="change"
 | 
				
			||||||
                :value="item.id">
 | 
					            style="width: 100%"
 | 
				
			||||||
            </el-option>
 | 
					            v-model="sshTunnelMachineId"
 | 
				
			||||||
 | 
					            @clear="clear"
 | 
				
			||||||
 | 
					            placeholder="请选择SSH隧道机器"
 | 
				
			||||||
 | 
					            clearable
 | 
				
			||||||
 | 
					        >
 | 
				
			||||||
 | 
					            <el-option v-for="item in sshTunnelMachineList" :key="item.id" :label="`${item.ip}:${item.port} [${item.name}]`" :value="item.id"> </el-option>
 | 
				
			||||||
        </el-select>
 | 
					        </el-select>
 | 
				
			||||||
    </div>
 | 
					    </div>
 | 
				
			||||||
</template>
 | 
					</template>
 | 
				
			||||||
@@ -17,10 +22,10 @@ const props = defineProps({
 | 
				
			|||||||
    modelValue: {
 | 
					    modelValue: {
 | 
				
			||||||
        type: Number,
 | 
					        type: Number,
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
})
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
//定义事件
 | 
					//定义事件
 | 
				
			||||||
const emit = defineEmits(['update:modelValue'])
 | 
					const emit = defineEmits(['update:modelValue']);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const state = reactive({
 | 
					const state = reactive({
 | 
				
			||||||
    // 单选则为id,多选为id数组
 | 
					    // 单选则为id,多选为id数组
 | 
				
			||||||
@@ -28,10 +33,7 @@ const state = reactive({
 | 
				
			|||||||
    sshTunnelMachineList: [] as any,
 | 
					    sshTunnelMachineList: [] as any,
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const {
 | 
					const { sshTunnelMachineId, sshTunnelMachineList } = toRefs(state);
 | 
				
			||||||
    sshTunnelMachineId,
 | 
					 | 
				
			||||||
    sshTunnelMachineList,
 | 
					 | 
				
			||||||
} = toRefs(state)
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
onMounted(async () => {
 | 
					onMounted(async () => {
 | 
				
			||||||
    if (!props.modelValue || props.modelValue <= 0) {
 | 
					    if (!props.modelValue || props.modelValue <= 0) {
 | 
				
			||||||
@@ -52,7 +54,7 @@ const getSshTunnelMachines = async () => {
 | 
				
			|||||||
const clear = () => {
 | 
					const clear = () => {
 | 
				
			||||||
    state.sshTunnelMachineId = null;
 | 
					    state.sshTunnelMachineId = null;
 | 
				
			||||||
    change();
 | 
					    change();
 | 
				
			||||||
}
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const change = () => {
 | 
					const change = () => {
 | 
				
			||||||
    emit('update:modelValue', state.sshTunnelMachineId);
 | 
					    emit('update:modelValue', state.sshTunnelMachineId);
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,6 +1,5 @@
 | 
				
			|||||||
<template>
 | 
					<template>
 | 
				
			||||||
    <div
 | 
					    <div style="display: inline-flex; justify-content: center; align-items: center; cursor: pointer; vertical-align: middle">
 | 
				
			||||||
        style="display: inline-flex;  justify-content: center;  align-items: center; cursor: pointer;vertical-align: middle;">
 | 
					 | 
				
			||||||
        <el-popover @show="showTagInfo" placement="top-start" title="标签信息" :width="300" trigger="hover">
 | 
					        <el-popover @show="showTagInfo" placement="top-start" title="标签信息" :width="300" trigger="hover">
 | 
				
			||||||
            <template #reference>
 | 
					            <template #reference>
 | 
				
			||||||
                <el-icon>
 | 
					                <el-icon>
 | 
				
			||||||
@@ -25,20 +24,18 @@ const props = defineProps({
 | 
				
			|||||||
        type: [String],
 | 
					        type: [String],
 | 
				
			||||||
        required: true,
 | 
					        required: true,
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
})
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const state = reactive({
 | 
					const state = reactive({
 | 
				
			||||||
    tagPath: '',
 | 
					    tagPath: '',
 | 
				
			||||||
    tags: [] as any,
 | 
					    tags: [] as any,
 | 
				
			||||||
})
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const {
 | 
					const { tags } = toRefs(state);
 | 
				
			||||||
    tags,
 | 
					 | 
				
			||||||
} = toRefs(state)
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
onMounted(async () => {
 | 
					onMounted(async () => {
 | 
				
			||||||
    state.tagPath = props.tagPath;
 | 
					    state.tagPath = props.tagPath;
 | 
				
			||||||
})
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const showTagInfo = async () => {
 | 
					const showTagInfo = async () => {
 | 
				
			||||||
    if (state.tags && state.tags.length > 0) {
 | 
					    if (state.tags && state.tags.length > 0) {
 | 
				
			||||||
@@ -59,10 +56,7 @@ const showTagInfo = async () => {
 | 
				
			|||||||
        tagPaths.push(nowTag);
 | 
					        tagPaths.push(nowTag);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    state.tags = await tagApi.listByQuery.request({ tagPaths: tagPaths.join(',') });
 | 
					    state.tags = await tagApi.listByQuery.request({ tagPaths: tagPaths.join(',') });
 | 
				
			||||||
}
 | 
					};
 | 
				
			||||||
 | 
					 | 
				
			||||||
</script>
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<style lang="scss">
 | 
					<style lang="scss"></style>
 | 
				
			||||||
 | 
					 | 
				
			||||||
</style>
 | 
					 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,12 +1,22 @@
 | 
				
			|||||||
<template>
 | 
					<template>
 | 
				
			||||||
    <div>
 | 
					    <div>
 | 
				
			||||||
        <el-tree-select @check="changeTag" style="width: 100%" v-model="selectTags" :data="tags" placeholder="请选择关联标签"
 | 
					        <el-tree-select
 | 
				
			||||||
            :render-after-expand="true" :default-expanded-keys="[selectTags]" show-checkbox check-strictly node-key="id"
 | 
					            v-bind="$attrs"
 | 
				
			||||||
 | 
					            @check="changeTag"
 | 
				
			||||||
 | 
					            style="width: 100%"
 | 
				
			||||||
 | 
					            :data="tags"
 | 
				
			||||||
 | 
					            placeholder="请选择关联标签"
 | 
				
			||||||
 | 
					            :render-after-expand="true"
 | 
				
			||||||
 | 
					            :default-expanded-keys="[selectTags]"
 | 
				
			||||||
 | 
					            show-checkbox
 | 
				
			||||||
 | 
					            check-strictly
 | 
				
			||||||
 | 
					            node-key="id"
 | 
				
			||||||
            :props="{
 | 
					            :props="{
 | 
				
			||||||
                value: 'id',
 | 
					                value: 'id',
 | 
				
			||||||
                label: 'codePath',
 | 
					                label: 'codePath',
 | 
				
			||||||
                children: 'children',
 | 
					                children: 'children',
 | 
				
			||||||
            }">
 | 
					            }"
 | 
				
			||||||
 | 
					        >
 | 
				
			||||||
            <template #default="{ data }">
 | 
					            <template #default="{ data }">
 | 
				
			||||||
                <span class="custom-tree-node">
 | 
					                <span class="custom-tree-node">
 | 
				
			||||||
                    <span style="font-size: 13px">
 | 
					                    <span style="font-size: 13px">
 | 
				
			||||||
@@ -23,20 +33,12 @@
 | 
				
			|||||||
</template>
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<script lang="ts" setup>
 | 
					<script lang="ts" setup>
 | 
				
			||||||
import { toRefs, reactive, onMounted } from 'vue';
 | 
					import { useAttrs, toRefs, reactive, onMounted } from 'vue';
 | 
				
			||||||
import { tagApi } from '../tag/api';
 | 
					import { tagApi } from '../tag/api';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const props = defineProps({
 | 
					const attrs = useAttrs();
 | 
				
			||||||
    tagId: {
 | 
					 | 
				
			||||||
        type: Number,
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    tagPath: {
 | 
					 | 
				
			||||||
        type: String,
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
})
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
//定义事件
 | 
					//定义事件
 | 
				
			||||||
const emit = defineEmits(['changeTag', 'update:tagId', 'update:tagPath'])
 | 
					const emit = defineEmits(['changeTag', 'update:tagPath']);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const state = reactive({
 | 
					const state = reactive({
 | 
				
			||||||
    tags: [],
 | 
					    tags: [],
 | 
				
			||||||
@@ -44,29 +46,22 @@ const state = reactive({
 | 
				
			|||||||
    selectTags: null as any,
 | 
					    selectTags: null as any,
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const {
 | 
					const { tags, selectTags } = toRefs(state);
 | 
				
			||||||
    tags,
 | 
					 | 
				
			||||||
    selectTags,
 | 
					 | 
				
			||||||
} = toRefs(state)
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
onMounted(async () => {
 | 
					onMounted(async () => {
 | 
				
			||||||
    if (props.tagId) {
 | 
					    if (attrs.modelValue) {
 | 
				
			||||||
        state.selectTags = props.tagId;
 | 
					        state.selectTags = attrs.modelValue;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    state.tags = await tagApi.getTagTrees.request(null);
 | 
					    state.tags = await tagApi.getTagTrees.request(null);
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const changeTag = (tag: any, checkInfo: any) => {
 | 
					const changeTag = (tag: any, checkInfo: any) => {
 | 
				
			||||||
    if (checkInfo.checkedNodes.length > 0) {
 | 
					    if (checkInfo.checkedNodes.length > 0) {
 | 
				
			||||||
        emit('update:tagId', tag.id);
 | 
					 | 
				
			||||||
        emit('update:tagPath', tag.codePath);
 | 
					        emit('update:tagPath', tag.codePath);
 | 
				
			||||||
        emit('changeTag', tag);
 | 
					        emit('changeTag', tag);
 | 
				
			||||||
    } else {
 | 
					    } else {
 | 
				
			||||||
        emit('update:tagId', null);
 | 
					 | 
				
			||||||
        emit('update:tagPath', null);
 | 
					        emit('update:tagPath', null);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
</script>
 | 
					</script>
 | 
				
			||||||
<style lang="scss">
 | 
					<style lang="scss"></style>
 | 
				
			||||||
 | 
					 | 
				
			||||||
</style>
 | 
					 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,13 +1,24 @@
 | 
				
			|||||||
<template>
 | 
					<template>
 | 
				
			||||||
    <div class="instances-box">
 | 
					    <div class="tag-tree">
 | 
				
			||||||
        <el-row type="flex" justify="space-between">
 | 
					        <el-row type="flex" justify="space-between">
 | 
				
			||||||
            <el-col :span="24" class="el-scrollbar flex-auto" style="overflow: auto">
 | 
					            <el-col :span="24" class="el-scrollbar flex-auto" style="overflow: auto">
 | 
				
			||||||
                <el-input v-model="filterText" placeholder="输入关键字->搜索已展开节点信息" clearable size="small" class="mb5" />
 | 
					                <el-input v-model="filterText" placeholder="输入关键字->搜索已展开节点信息" clearable size="small" class="mb5" />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                <el-tree ref="treeRef" :style="{ maxHeight: state.height, height: state.height, overflow: 'auto' }"
 | 
					                <el-tree
 | 
				
			||||||
                    :highlight-current="true" :indent="7" :load="loadNode" :props="treeProps" lazy node-key="key"
 | 
					                    ref="treeRef"
 | 
				
			||||||
                    :expand-on-click-node="true" :filter-node-method="filterNode" @node-click="treeNodeClick"
 | 
					                    :style="{ maxHeight: state.height, height: state.height, overflow: 'auto' }"
 | 
				
			||||||
                    @node-expand="treeNodeClick" @node-contextmenu="nodeContextmenu">
 | 
					                    :highlight-current="true"
 | 
				
			||||||
 | 
					                    :indent="7"
 | 
				
			||||||
 | 
					                    :load="loadNode"
 | 
				
			||||||
 | 
					                    :props="treeProps"
 | 
				
			||||||
 | 
					                    lazy
 | 
				
			||||||
 | 
					                    node-key="key"
 | 
				
			||||||
 | 
					                    :expand-on-click-node="true"
 | 
				
			||||||
 | 
					                    :filter-node-method="filterNode"
 | 
				
			||||||
 | 
					                    @node-click="treeNodeClick"
 | 
				
			||||||
 | 
					                    @node-expand="treeNodeClick"
 | 
				
			||||||
 | 
					                    @node-contextmenu="nodeContextmenu"
 | 
				
			||||||
 | 
					                >
 | 
				
			||||||
                    <template #default="{ node, data }">
 | 
					                    <template #default="{ node, data }">
 | 
				
			||||||
                        <span>
 | 
					                        <span>
 | 
				
			||||||
                            <span v-if="data.type == TagTreeNode.TagPath">
 | 
					                            <span v-if="data.type == TagTreeNode.TagPath">
 | 
				
			||||||
@@ -24,8 +35,7 @@
 | 
				
			|||||||
                </el-tree>
 | 
					                </el-tree>
 | 
				
			||||||
            </el-col>
 | 
					            </el-col>
 | 
				
			||||||
        </el-row>
 | 
					        </el-row>
 | 
				
			||||||
        <contextmenu :dropdown="state.dropdown" :items="state.contextmenuItems" ref="contextmenuRef"
 | 
					        <contextmenu :dropdown="state.dropdown" :items="state.contextmenuItems" ref="contextmenuRef" @currentContextmenuClick="onCurrentContextmenuClick" />
 | 
				
			||||||
            @currentContextmenuClick="onCurrentContextmenuClick" />
 | 
					 | 
				
			||||||
    </div>
 | 
					    </div>
 | 
				
			||||||
</template>
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -38,7 +48,7 @@ import Contextmenu from '@/components/contextmenu/index.vue';
 | 
				
			|||||||
const props = defineProps({
 | 
					const props = defineProps({
 | 
				
			||||||
    height: {
 | 
					    height: {
 | 
				
			||||||
        type: [Number, String],
 | 
					        type: [Number, String],
 | 
				
			||||||
        default: 0
 | 
					        default: 0,
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    load: {
 | 
					    load: {
 | 
				
			||||||
        type: Function,
 | 
					        type: Function,
 | 
				
			||||||
@@ -47,17 +57,17 @@ const props = defineProps({
 | 
				
			|||||||
    loadContextmenuItems: {
 | 
					    loadContextmenuItems: {
 | 
				
			||||||
        type: Function,
 | 
					        type: Function,
 | 
				
			||||||
        required: false,
 | 
					        required: false,
 | 
				
			||||||
    }
 | 
					    },
 | 
				
			||||||
})
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const treeProps = {
 | 
					const treeProps = {
 | 
				
			||||||
    label: 'name',
 | 
					    label: 'name',
 | 
				
			||||||
    children: 'zones',
 | 
					    children: 'zones',
 | 
				
			||||||
    isLeaf: 'isLeaf',
 | 
					    isLeaf: 'isLeaf',
 | 
				
			||||||
}
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const emit = defineEmits(['nodeClick', 'currentContextmenuClick'])
 | 
					const emit = defineEmits(['nodeClick', 'currentContextmenuClick']);
 | 
				
			||||||
const treeRef: any = ref(null)
 | 
					const treeRef: any = ref(null);
 | 
				
			||||||
const contextmenuRef = ref();
 | 
					const contextmenuRef = ref();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const state = reactive({
 | 
					const state = reactive({
 | 
				
			||||||
@@ -69,49 +79,54 @@ const state = reactive({
 | 
				
			|||||||
    },
 | 
					    },
 | 
				
			||||||
    contextmenuItems: [],
 | 
					    contextmenuItems: [],
 | 
				
			||||||
    opend: {},
 | 
					    opend: {},
 | 
				
			||||||
})
 | 
					});
 | 
				
			||||||
const { filterText } = toRefs(state)
 | 
					const { filterText } = toRefs(state);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
onMounted(async () => {
 | 
					onMounted(async () => {
 | 
				
			||||||
    if (!props.height) {
 | 
					    if (!props.height) {
 | 
				
			||||||
        state.height = window.innerHeight - 147 + 'px';
 | 
					        setHeight();
 | 
				
			||||||
 | 
					        window.onresize = () => setHeight();
 | 
				
			||||||
    } else {
 | 
					    } else {
 | 
				
			||||||
        state.height = props.height;
 | 
					        state.height = props.height;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
})
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const setHeight = () => {
 | 
				
			||||||
 | 
					    state.height = window.innerHeight - 157 + 'px';
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
watch(filterText, (val) => {
 | 
					watch(filterText, (val) => {
 | 
				
			||||||
    treeRef.value?.filter(val)
 | 
					    treeRef.value?.filter(val);
 | 
				
			||||||
})
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const filterNode = (value: string, data: any) => {
 | 
					const filterNode = (value: string, data: any) => {
 | 
				
			||||||
    if (!value) return true
 | 
					    if (!value) return true;
 | 
				
			||||||
    return data.label.includes(value)
 | 
					    return data.label.includes(value);
 | 
				
			||||||
}
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
* 加载树节点
 | 
					 * 加载树节点
 | 
				
			||||||
* @param { Object } node
 | 
					 * @param { Object } node
 | 
				
			||||||
* @param { Object } resolve
 | 
					 * @param { Object } resolve
 | 
				
			||||||
*/
 | 
					 */
 | 
				
			||||||
const loadNode = async (node: any, resolve: any) => {
 | 
					const loadNode = async (node: any, resolve: any) => {
 | 
				
			||||||
    if (typeof resolve !== 'function') {
 | 
					    if (typeof resolve !== 'function') {
 | 
				
			||||||
        return;
 | 
					        return;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    let nodes = []
 | 
					    let nodes = [];
 | 
				
			||||||
    try {
 | 
					    try {
 | 
				
			||||||
        nodes = await props.load(node)
 | 
					        nodes = await props.load(node);
 | 
				
			||||||
    } catch (e: any) {
 | 
					    } catch (e: any) {
 | 
				
			||||||
        console.error(e);
 | 
					        console.error(e);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    return resolve(nodes)
 | 
					    return resolve(nodes);
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const treeNodeClick = (data: any) => {
 | 
					const treeNodeClick = (data: any) => {
 | 
				
			||||||
    emit('nodeClick', data);
 | 
					    emit('nodeClick', data);
 | 
				
			||||||
    // 关闭可能存在的右击菜单
 | 
					    // 关闭可能存在的右击菜单
 | 
				
			||||||
    contextmenuRef.value.closeContextmenu();
 | 
					    contextmenuRef.value.closeContextmenu();
 | 
				
			||||||
}
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// 树节点右击事件
 | 
					// 树节点右击事件
 | 
				
			||||||
const nodeContextmenu = (event: any, data: any) => {
 | 
					const nodeContextmenu = (event: any, data: any) => {
 | 
				
			||||||
@@ -119,7 +134,7 @@ const nodeContextmenu = (event: any, data: any) => {
 | 
				
			|||||||
        return;
 | 
					        return;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    // 加载当前节点是否需要显示右击菜单
 | 
					    // 加载当前节点是否需要显示右击菜单
 | 
				
			||||||
    const items = props.loadContextmenuItems(data)
 | 
					    const items = props.loadContextmenuItems(data);
 | 
				
			||||||
    if (!items || items.length == 0) {
 | 
					    if (!items || items.length == 0) {
 | 
				
			||||||
        return;
 | 
					        return;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@@ -128,17 +143,17 @@ const nodeContextmenu = (event: any, data: any) => {
 | 
				
			|||||||
    state.dropdown.x = clientX;
 | 
					    state.dropdown.x = clientX;
 | 
				
			||||||
    state.dropdown.y = clientY;
 | 
					    state.dropdown.y = clientY;
 | 
				
			||||||
    contextmenuRef.value.openContextmenu(data);
 | 
					    contextmenuRef.value.openContextmenu(data);
 | 
				
			||||||
}
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const onCurrentContextmenuClick = (clickData: any) => {
 | 
					const onCurrentContextmenuClick = (clickData: any) => {
 | 
				
			||||||
    emit('currentContextmenuClick', clickData);
 | 
					    emit('currentContextmenuClick', clickData);
 | 
				
			||||||
}
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const reloadNode = (nodeKey: any) => {
 | 
					const reloadNode = (nodeKey: any) => {
 | 
				
			||||||
    let node = getNode(nodeKey);
 | 
					    let node = getNode(nodeKey);
 | 
				
			||||||
    node.loaded = false;
 | 
					    node.loaded = false;
 | 
				
			||||||
    node.expand();
 | 
					    node.expand();
 | 
				
			||||||
}
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const getNode = (nodeKey: any) => {
 | 
					const getNode = (nodeKey: any) => {
 | 
				
			||||||
    let node = treeRef.value.getNode(nodeKey);
 | 
					    let node = treeRef.value.getNode(nodeKey);
 | 
				
			||||||
@@ -146,18 +161,20 @@ const getNode = (nodeKey: any) => {
 | 
				
			|||||||
        throw new Error('未找到节点: ' + nodeKey);
 | 
					        throw new Error('未找到节点: ' + nodeKey);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    return node;
 | 
					    return node;
 | 
				
			||||||
}
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
defineExpose({
 | 
					defineExpose({
 | 
				
			||||||
    reloadNode,
 | 
					    reloadNode,
 | 
				
			||||||
})
 | 
					});
 | 
				
			||||||
</script>
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<style lang="scss">
 | 
					<style lang="scss" scoped>
 | 
				
			||||||
.instances-box {
 | 
					.tag-tree {
 | 
				
			||||||
    overflow: 'auto';
 | 
					    overflow: 'auto';
 | 
				
			||||||
    position: relative;
 | 
					    position: relative;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    border: 1px solid var(--el-border-color-light, #ebeef5);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    .el-tree {
 | 
					    .el-tree {
 | 
				
			||||||
        display: inline-block;
 | 
					        display: inline-block;
 | 
				
			||||||
        min-width: 100%;
 | 
					        min-width: 100%;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -2,17 +2,17 @@ export class TagTreeNode {
 | 
				
			|||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * 节点id
 | 
					     * 节点id
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    key: any
 | 
					    key: any;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * 节点名称
 | 
					     * 节点名称
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    label: string
 | 
					    label: string;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * 树节点类型
 | 
					     * 树节点类型
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    type: any
 | 
					    type: any;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    isLeaf: boolean = false;
 | 
					    isLeaf: boolean = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,87 +1,66 @@
 | 
				
			|||||||
<template>
 | 
					<template>
 | 
				
			||||||
    <div>
 | 
					    <div>
 | 
				
			||||||
        <el-dialog :title="title" v-model="dialogVisible" :before-close="cancel" :close-on-click-modal="false"
 | 
					        <el-dialog
 | 
				
			||||||
            :destroy-on-close="true" width="38%">
 | 
					            :title="title"
 | 
				
			||||||
            <el-form :model="form" ref="dbForm" :rules="rules" label-width="95px">
 | 
					            v-model="dialogVisible"
 | 
				
			||||||
                <el-tabs v-model="tabActiveName">
 | 
					            @open="open"
 | 
				
			||||||
                    <el-tab-pane label="基础信息" name="basic">
 | 
					            :before-close="cancel"
 | 
				
			||||||
 | 
					            :close-on-click-modal="false"
 | 
				
			||||||
 | 
					            :destroy-on-close="true"
 | 
				
			||||||
 | 
					            width="38%"
 | 
				
			||||||
 | 
					        >
 | 
				
			||||||
 | 
					            <el-form :model="form" ref="dbForm" :rules="rules" label-width="auto">
 | 
				
			||||||
                <el-form-item prop="tagId" label="标签:" required>
 | 
					                <el-form-item prop="tagId" label="标签:" required>
 | 
				
			||||||
                            <tag-select v-model:tag-id="form.tagId" v-model:tag-path="form.tagPath" style="width: 100%" />
 | 
					                    <tag-select v-model="form.tagId" v-model:tag-path="form.tagPath" style="width: 100%" />
 | 
				
			||||||
 | 
					                </el-form-item>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                <el-form-item prop="instanceId" label="数据库实例:" required>
 | 
				
			||||||
 | 
					                    <el-select
 | 
				
			||||||
 | 
					                        :disabled="form.id !== undefined"
 | 
				
			||||||
 | 
					                        remote
 | 
				
			||||||
 | 
					                        :remote-method="getInstances"
 | 
				
			||||||
 | 
					                        @change="getAllDatabase"
 | 
				
			||||||
 | 
					                        v-model="form.instanceId"
 | 
				
			||||||
 | 
					                        placeholder="请输入实例名称搜索并选择实例"
 | 
				
			||||||
 | 
					                        filterable
 | 
				
			||||||
 | 
					                        clearable
 | 
				
			||||||
 | 
					                        class="w100"
 | 
				
			||||||
 | 
					                    >
 | 
				
			||||||
 | 
					                        <el-option v-for="item in state.instances" :key="item.id" :label="`${item.name}`" :value="item.id">
 | 
				
			||||||
 | 
					                            {{ item.name }}
 | 
				
			||||||
 | 
					                            <el-divider direction="vertical" border-style="dashed" />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                            {{ item.type }} / {{ item.host }}:{{ item.port }}
 | 
				
			||||||
 | 
					                            <el-divider direction="vertical" border-style="dashed" />
 | 
				
			||||||
 | 
					                            {{ item.username }}
 | 
				
			||||||
 | 
					                        </el-option>
 | 
				
			||||||
 | 
					                    </el-select>
 | 
				
			||||||
                </el-form-item>
 | 
					                </el-form-item>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                <el-form-item prop="name" label="别名:" required>
 | 
					                <el-form-item prop="name" label="别名:" required>
 | 
				
			||||||
                    <el-input v-model.trim="form.name" placeholder="请输入数据库别名" auto-complete="off"></el-input>
 | 
					                    <el-input v-model.trim="form.name" placeholder="请输入数据库别名" auto-complete="off"></el-input>
 | 
				
			||||||
                </el-form-item>
 | 
					                </el-form-item>
 | 
				
			||||||
                        <el-form-item prop="type" label="类型:" required>
 | 
					
 | 
				
			||||||
                            <el-select style="width: 100%" v-model="form.type" placeholder="请选择数据库类型">
 | 
					 | 
				
			||||||
                                <el-option key="item.id" label="mysql" value="mysql"> </el-option>
 | 
					 | 
				
			||||||
                                <el-option key="item.id" label="postgres" value="postgres"> </el-option>
 | 
					 | 
				
			||||||
                            </el-select>
 | 
					 | 
				
			||||||
                        </el-form-item>
 | 
					 | 
				
			||||||
                        <el-form-item prop="host" label="host:" required>
 | 
					 | 
				
			||||||
                            <el-col :span="18">
 | 
					 | 
				
			||||||
                                <el-input :disabled="form.id !== undefined" v-model.trim="form.host" placeholder="请输入主机ip"
 | 
					 | 
				
			||||||
                                    auto-complete="off"></el-input>
 | 
					 | 
				
			||||||
                            </el-col>
 | 
					 | 
				
			||||||
                            <el-col style="text-align: center" :span="1">:</el-col>
 | 
					 | 
				
			||||||
                            <el-col :span="5">
 | 
					 | 
				
			||||||
                                <el-input type="number" v-model.number="form.port" placeholder="请输入端口"></el-input>
 | 
					 | 
				
			||||||
                            </el-col>
 | 
					 | 
				
			||||||
                        </el-form-item>
 | 
					 | 
				
			||||||
                        <el-form-item prop="username" label="用户名:" required>
 | 
					 | 
				
			||||||
                            <el-input v-model.trim="form.username" placeholder="请输入用户名"></el-input>
 | 
					 | 
				
			||||||
                        </el-form-item>
 | 
					 | 
				
			||||||
                        <el-form-item prop="password" label="密码:">
 | 
					 | 
				
			||||||
                            <el-input type="password" show-password v-model.trim="form.password" placeholder="请输入密码,修改操作可不填"
 | 
					 | 
				
			||||||
                                autocomplete="new-password">
 | 
					 | 
				
			||||||
                                <template v-if="form.id && form.id != 0" #suffix>
 | 
					 | 
				
			||||||
                                    <el-popover @hide="pwd = ''" placement="right" title="原密码" :width="200" trigger="click"
 | 
					 | 
				
			||||||
                                        :content="pwd">
 | 
					 | 
				
			||||||
                                        <template #reference>
 | 
					 | 
				
			||||||
                                            <el-link @click="getDbPwd" :underline="false" type="primary" class="mr5">原密码
 | 
					 | 
				
			||||||
                                            </el-link>
 | 
					 | 
				
			||||||
                                        </template>
 | 
					 | 
				
			||||||
                                    </el-popover>
 | 
					 | 
				
			||||||
                                </template>
 | 
					 | 
				
			||||||
                            </el-input>
 | 
					 | 
				
			||||||
                        </el-form-item>
 | 
					 | 
				
			||||||
                <el-form-item prop="database" label="数据库名:" required>
 | 
					                <el-form-item prop="database" label="数据库名:" required>
 | 
				
			||||||
                            <el-col :span="19">
 | 
					                    <el-select
 | 
				
			||||||
                                <el-select @change="changeDatabase" v-model="databaseList" multiple clearable collapse-tags
 | 
					                        @change="changeDatabase"
 | 
				
			||||||
                                    collapse-tags-tooltip filterable allow-create placeholder="请确保数据库实例信息填写完整后获取库名"
 | 
					                        v-model="databaseList"
 | 
				
			||||||
                                    style="width: 100%">
 | 
					                        multiple
 | 
				
			||||||
 | 
					                        clearable
 | 
				
			||||||
 | 
					                        collapse-tags
 | 
				
			||||||
 | 
					                        collapse-tags-tooltip
 | 
				
			||||||
 | 
					                        filterable
 | 
				
			||||||
 | 
					                        allow-create
 | 
				
			||||||
 | 
					                        placeholder="请确保数据库实例信息填写完整后获取库名"
 | 
				
			||||||
 | 
					                        style="width: 100%"
 | 
				
			||||||
 | 
					                    >
 | 
				
			||||||
                        <el-option v-for="db in allDatabases" :key="db" :label="db" :value="db" />
 | 
					                        <el-option v-for="db in allDatabases" :key="db" :label="db" :value="db" />
 | 
				
			||||||
                    </el-select>
 | 
					                    </el-select>
 | 
				
			||||||
                            </el-col>
 | 
					 | 
				
			||||||
                            <el-col style="text-align: center" :span="1">
 | 
					 | 
				
			||||||
                                <el-divider direction="vertical" border-style="dashed" />
 | 
					 | 
				
			||||||
                            </el-col>
 | 
					 | 
				
			||||||
                            <el-col :span="4">
 | 
					 | 
				
			||||||
                                <el-link @click="getAllDatabase" :underline="false" type="success">获取库名</el-link>
 | 
					 | 
				
			||||||
                            </el-col>
 | 
					 | 
				
			||||||
                </el-form-item>
 | 
					                </el-form-item>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                <el-form-item prop="remark" label="备注:">
 | 
					                <el-form-item prop="remark" label="备注:">
 | 
				
			||||||
                    <el-input v-model.trim="form.remark" auto-complete="off" type="textarea"></el-input>
 | 
					                    <el-input v-model.trim="form.remark" auto-complete="off" type="textarea"></el-input>
 | 
				
			||||||
                </el-form-item>
 | 
					                </el-form-item>
 | 
				
			||||||
                    </el-tab-pane>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    <el-tab-pane label="其他配置" name="other">
 | 
					 | 
				
			||||||
                        <el-form-item prop="params" label="连接参数:">
 | 
					 | 
				
			||||||
                            <el-input v-model.trim="form.params" placeholder="其他连接参数,形如: key1=value1&key2=value2">
 | 
					 | 
				
			||||||
                                <template #suffix>
 | 
					 | 
				
			||||||
                                    <el-link target="_blank" href="https://github.com/go-sql-driver/mysql#parameters"
 | 
					 | 
				
			||||||
                                        :underline="false" type="primary" class="mr5">参数参考</el-link>
 | 
					 | 
				
			||||||
                                </template>
 | 
					 | 
				
			||||||
                            </el-input>
 | 
					 | 
				
			||||||
                        </el-form-item>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                        <el-form-item prop="sshTunnelMachineId" label="SSH隧道:">
 | 
					 | 
				
			||||||
                            <ssh-tunnel-select v-model="form.sshTunnelMachineId" />
 | 
					 | 
				
			||||||
                        </el-form-item>
 | 
					 | 
				
			||||||
                    </el-tab-pane>
 | 
					 | 
				
			||||||
                </el-tabs>
 | 
					 | 
				
			||||||
            </el-form>
 | 
					            </el-form>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            <template #footer>
 | 
					            <template #footer>
 | 
				
			||||||
@@ -98,10 +77,7 @@
 | 
				
			|||||||
import { toRefs, reactive, watch, ref } from 'vue';
 | 
					import { toRefs, reactive, watch, ref } from 'vue';
 | 
				
			||||||
import { dbApi } from './api';
 | 
					import { dbApi } from './api';
 | 
				
			||||||
import { ElMessage } from 'element-plus';
 | 
					import { ElMessage } from 'element-plus';
 | 
				
			||||||
import { notBlank } from '@/common/assert';
 | 
					 | 
				
			||||||
import { RsaEncrypt } from '@/common/rsa';
 | 
					 | 
				
			||||||
import TagSelect from '../component/TagSelect.vue';
 | 
					import TagSelect from '../component/TagSelect.vue';
 | 
				
			||||||
import SshTunnelSelect from '../component/SshTunnelSelect.vue';
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
const props = defineProps({
 | 
					const props = defineProps({
 | 
				
			||||||
    visible: {
 | 
					    visible: {
 | 
				
			||||||
@@ -113,10 +89,10 @@ const props = defineProps({
 | 
				
			|||||||
    title: {
 | 
					    title: {
 | 
				
			||||||
        type: String,
 | 
					        type: String,
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
})
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
//定义事件
 | 
					//定义事件
 | 
				
			||||||
const emit = defineEmits(['update:visible', 'cancel', 'val-change'])
 | 
					const emit = defineEmits(['update:visible', 'cancel', 'val-change']);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const rules = {
 | 
					const rules = {
 | 
				
			||||||
    tagId: [
 | 
					    tagId: [
 | 
				
			||||||
@@ -126,6 +102,15 @@ const rules = {
 | 
				
			|||||||
            trigger: ['change', 'blur'],
 | 
					            trigger: ['change', 'blur'],
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
    ],
 | 
					    ],
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    instanceId: [
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            required: true,
 | 
				
			||||||
 | 
					            message: '请选择数据库实例',
 | 
				
			||||||
 | 
					            trigger: ['change', 'blur'],
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					    ],
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    name: [
 | 
					    name: [
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            required: true,
 | 
					            required: true,
 | 
				
			||||||
@@ -133,27 +118,6 @@ const rules = {
 | 
				
			|||||||
            trigger: ['change', 'blur'],
 | 
					            trigger: ['change', 'blur'],
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
    ],
 | 
					    ],
 | 
				
			||||||
    type: [
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            required: true,
 | 
					 | 
				
			||||||
            message: '请选择数据库类型',
 | 
					 | 
				
			||||||
            trigger: ['change', 'blur'],
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
    ],
 | 
					 | 
				
			||||||
    host: [
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            required: true,
 | 
					 | 
				
			||||||
            message: '请输入主机ip和port',
 | 
					 | 
				
			||||||
            trigger: ['change', 'blur'],
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
    ],
 | 
					 | 
				
			||||||
    username: [
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            required: true,
 | 
					 | 
				
			||||||
            message: '请输入用户名',
 | 
					 | 
				
			||||||
            trigger: ['change', 'blur'],
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
    ],
 | 
					 | 
				
			||||||
    database: [
 | 
					    database: [
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            required: true,
 | 
					            required: true,
 | 
				
			||||||
@@ -161,57 +125,40 @@ const rules = {
 | 
				
			|||||||
            trigger: ['change', 'blur'],
 | 
					            trigger: ['change', 'blur'],
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
    ],
 | 
					    ],
 | 
				
			||||||
}
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const dbForm: any = ref(null);
 | 
					const dbForm: any = ref(null);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const state = reactive({
 | 
					const state = reactive({
 | 
				
			||||||
    dialogVisible: false,
 | 
					    dialogVisible: false,
 | 
				
			||||||
    tabActiveName: 'basic',
 | 
					 | 
				
			||||||
    allDatabases: [] as any,
 | 
					    allDatabases: [] as any,
 | 
				
			||||||
    databaseList: [] as any,
 | 
					    databaseList: [] as any,
 | 
				
			||||||
    form: {
 | 
					    form: {
 | 
				
			||||||
        id: null,
 | 
					        id: null,
 | 
				
			||||||
        tagId: null as any,
 | 
					        tagId: null as any,
 | 
				
			||||||
        tagPath: null as any,
 | 
					        tagPath: null as any,
 | 
				
			||||||
        type: null,
 | 
					 | 
				
			||||||
        name: null,
 | 
					        name: null,
 | 
				
			||||||
        host: '',
 | 
					 | 
				
			||||||
        port: 3306,
 | 
					 | 
				
			||||||
        username: null,
 | 
					 | 
				
			||||||
        password: null,
 | 
					 | 
				
			||||||
        params: null,
 | 
					 | 
				
			||||||
        database: '',
 | 
					        database: '',
 | 
				
			||||||
        remark: '',
 | 
					        remark: '',
 | 
				
			||||||
        sshTunnelMachineId: null as any,
 | 
					        instanceId: null as any,
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    // 原密码
 | 
					 | 
				
			||||||
    pwd: '',
 | 
					 | 
				
			||||||
    btnLoading: false,
 | 
					    btnLoading: false,
 | 
				
			||||||
 | 
					    instances: [] as any,
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const {
 | 
					const { dialogVisible, allDatabases, databaseList, form, btnLoading } = toRefs(state);
 | 
				
			||||||
    dialogVisible,
 | 
					 | 
				
			||||||
    tabActiveName,
 | 
					 | 
				
			||||||
    allDatabases,
 | 
					 | 
				
			||||||
    databaseList,
 | 
					 | 
				
			||||||
    form,
 | 
					 | 
				
			||||||
    pwd,
 | 
					 | 
				
			||||||
    btnLoading,
 | 
					 | 
				
			||||||
} = toRefs(state)
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
watch(props, (newValue: any) => {
 | 
					watch(props, (newValue: any) => {
 | 
				
			||||||
    state.dialogVisible = newValue.visible;
 | 
					    state.dialogVisible = newValue.visible;
 | 
				
			||||||
    if (!state.dialogVisible) {
 | 
					    if (!state.dialogVisible) {
 | 
				
			||||||
        return;
 | 
					        return;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    state.tabActiveName = 'basic';
 | 
					 | 
				
			||||||
    if (newValue.db) {
 | 
					    if (newValue.db) {
 | 
				
			||||||
        state.form = { ...newValue.db };
 | 
					        state.form = { ...newValue.db };
 | 
				
			||||||
        // 将数据库名使用空格切割,获取所有数据库列表
 | 
					        // 将数据库名使用空格切割,获取所有数据库列表
 | 
				
			||||||
        state.databaseList = newValue.db.database.split(' ');
 | 
					        state.databaseList = newValue.db.database.split(' ');
 | 
				
			||||||
    } else {
 | 
					    } else {
 | 
				
			||||||
        state.form = { port: 3306 } as any;
 | 
					        state.form = {} as any;
 | 
				
			||||||
        state.databaseList = [];
 | 
					        state.databaseList = [];
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
@@ -224,27 +171,34 @@ const changeDatabase = () => {
 | 
				
			|||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const getAllDatabase = async () => {
 | 
					const getAllDatabase = async () => {
 | 
				
			||||||
    const reqForm = { ...state.form };
 | 
					    if (state.form.instanceId > 0) {
 | 
				
			||||||
    reqForm.password = await RsaEncrypt(reqForm.password);
 | 
					        state.allDatabases = await dbApi.getAllDatabase.request({ instanceId: state.form.instanceId });
 | 
				
			||||||
    state.allDatabases = await dbApi.getAllDatabase.request(reqForm);
 | 
					    }
 | 
				
			||||||
    ElMessage.success('获取成功, 请选择需要管理操作的数据库');
 | 
					 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const getDbPwd = async () => {
 | 
					const getInstances = async (instanceName: string = '', id = 0) => {
 | 
				
			||||||
    state.pwd = await dbApi.getDbPwd.request({ id: state.form.id });
 | 
					    if (!id && !instanceName) {
 | 
				
			||||||
 | 
					        state.instances = [];
 | 
				
			||||||
 | 
					        return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    const data = await dbApi.instances.request({ id, name: instanceName });
 | 
				
			||||||
 | 
					    if (data) {
 | 
				
			||||||
 | 
					        state.instances = data.list;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const open = async () => {
 | 
				
			||||||
 | 
					    if (state.form.instanceId) {
 | 
				
			||||||
 | 
					        // 根据id获取,因为需要回显实例名称
 | 
				
			||||||
 | 
					        getInstances('', state.form.instanceId);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    await getAllDatabase();
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const btnOk = async () => {
 | 
					const btnOk = async () => {
 | 
				
			||||||
    if (!state.form.id) {
 | 
					 | 
				
			||||||
        notBlank(state.form.password, '新增操作,密码不可为空');
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    dbForm.value.validate(async (valid: boolean) => {
 | 
					    dbForm.value.validate(async (valid: boolean) => {
 | 
				
			||||||
        if (valid) {
 | 
					        if (valid) {
 | 
				
			||||||
            const reqForm = { ...state.form };
 | 
					            const reqForm = { ...state.form };
 | 
				
			||||||
            reqForm.password = await RsaEncrypt(reqForm.password);
 | 
					 | 
				
			||||||
            if (!state.form.sshTunnelMachineId) {
 | 
					 | 
				
			||||||
                reqForm.sshTunnelMachineId = -1;
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            dbApi.saveDb.request(reqForm).then(() => {
 | 
					            dbApi.saveDb.request(reqForm).then(() => {
 | 
				
			||||||
                ElMessage.success('保存成功');
 | 
					                ElMessage.success('保存成功');
 | 
				
			||||||
                emit('val-change', state.form);
 | 
					                emit('val-change', state.form);
 | 
				
			||||||
@@ -265,6 +219,7 @@ const btnOk = async () => {
 | 
				
			|||||||
const resetInputDb = () => {
 | 
					const resetInputDb = () => {
 | 
				
			||||||
    state.databaseList = [];
 | 
					    state.databaseList = [];
 | 
				
			||||||
    state.allDatabases = [];
 | 
					    state.allDatabases = [];
 | 
				
			||||||
 | 
					    state.instances = [];
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const cancel = () => {
 | 
					const cancel = () => {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,328 +1,228 @@
 | 
				
			|||||||
<template>
 | 
					<template>
 | 
				
			||||||
    <div class="db-list">
 | 
					    <div class="db-list">
 | 
				
			||||||
        <el-card>
 | 
					        <page-table
 | 
				
			||||||
            <el-button v-auth="permissions.saveDb" type="primary" icon="plus" @click="editDb(true)">添加</el-button>
 | 
					            ref="pageTableRef"
 | 
				
			||||||
            <el-button v-auth="permissions.saveDb" :disabled="chooseId == null" @click="editDb(false)" type="primary"
 | 
					            :query="queryConfig"
 | 
				
			||||||
                icon="edit">编辑</el-button>
 | 
					            v-model:query-form="query"
 | 
				
			||||||
            <el-button v-auth="permissions.delDb" :disabled="chooseId == null" @click="deleteDb(chooseId)" type="danger"
 | 
					            :show-selection="true"
 | 
				
			||||||
                icon="delete">删除</el-button>
 | 
					            v-model:selection-data="state.selectionData"
 | 
				
			||||||
            <div style="float: right">
 | 
					            :data="datas"
 | 
				
			||||||
                <el-select @focus="getTags" v-model="query.tagPath" placeholder="请选择标签" filterable clearable>
 | 
					            :columns="columns"
 | 
				
			||||||
 | 
					            :total="total"
 | 
				
			||||||
 | 
					            v-model:page-size="query.pageSize"
 | 
				
			||||||
 | 
					            v-model:page-num="query.pageNum"
 | 
				
			||||||
 | 
					            @pageChange="search()"
 | 
				
			||||||
 | 
					        >
 | 
				
			||||||
 | 
					            <template #tagPathSelect>
 | 
				
			||||||
 | 
					                <el-select @focus="getTags" v-model="query.tagPath" placeholder="请选择标签" filterable clearable style="width: 200px">
 | 
				
			||||||
                    <el-option v-for="item in tags" :key="item" :label="item" :value="item"> </el-option>
 | 
					                    <el-option v-for="item in tags" :key="item" :label="item" :value="item"> </el-option>
 | 
				
			||||||
                </el-select>
 | 
					                </el-select>
 | 
				
			||||||
                <el-button type="success" icon="search" @click="search()" class="ml5"></el-button>
 | 
					 | 
				
			||||||
            </div>
 | 
					 | 
				
			||||||
            <el-table :data="datas" ref="table" @current-change="choose" show-overflow-tooltip stripe>
 | 
					 | 
				
			||||||
                <el-table-column label="选择" width="60px">
 | 
					 | 
				
			||||||
                    <template #default="scope">
 | 
					 | 
				
			||||||
                        <el-radio v-model="chooseId" :label="scope.row.id">
 | 
					 | 
				
			||||||
                            <i></i>
 | 
					 | 
				
			||||||
                        </el-radio>
 | 
					 | 
				
			||||||
            </template>
 | 
					            </template>
 | 
				
			||||||
                </el-table-column>
 | 
					
 | 
				
			||||||
                <el-table-column prop="tagPath" label="标签路径" min-width="150" show-overflow-tooltip>
 | 
					            <template #instanceSelect>
 | 
				
			||||||
                    <template #default="scope">
 | 
					                <el-select
 | 
				
			||||||
                        <tag-info :tag-path="scope.row.tagPath" />
 | 
					                    remote
 | 
				
			||||||
 | 
					                    :remote-method="getInstances"
 | 
				
			||||||
 | 
					                    v-model="query.instanceId"
 | 
				
			||||||
 | 
					                    placeholder="输入并选择实例"
 | 
				
			||||||
 | 
					                    filterable
 | 
				
			||||||
 | 
					                    clearable
 | 
				
			||||||
 | 
					                    style="width: 200px"
 | 
				
			||||||
 | 
					                >
 | 
				
			||||||
 | 
					                    <el-option v-for="item in state.instances" :key="item.id" :label="`${item.name}`" :value="item.id">
 | 
				
			||||||
 | 
					                        {{ item.name }}
 | 
				
			||||||
 | 
					                        <el-divider direction="vertical" border-style="dashed" />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                        {{ item.type }} / {{ item.host }}:{{ item.port }}
 | 
				
			||||||
 | 
					                        <el-divider direction="vertical" border-style="dashed" />
 | 
				
			||||||
 | 
					                        {{ item.username }}
 | 
				
			||||||
 | 
					                    </el-option>
 | 
				
			||||||
 | 
					                </el-select>
 | 
				
			||||||
 | 
					            </template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            <template #queryRight>
 | 
				
			||||||
 | 
					                <el-button v-auth="perms.saveDb" type="primary" icon="plus" @click="editDb(false)">添加</el-button>
 | 
				
			||||||
 | 
					                <el-button v-auth="perms.delDb" :disabled="selectionData.length < 1" @click="deleteDb()" type="danger" icon="delete">删除</el-button>
 | 
				
			||||||
 | 
					            </template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            <template #tagPath="{ data }">
 | 
				
			||||||
 | 
					                <tag-info :tag-path="data.tagPath" />
 | 
				
			||||||
                <span class="ml5">
 | 
					                <span class="ml5">
 | 
				
			||||||
                            {{ scope.row.tagPath }}
 | 
					                    {{ data.tagPath }}
 | 
				
			||||||
                </span>
 | 
					                </span>
 | 
				
			||||||
            </template>
 | 
					            </template>
 | 
				
			||||||
                </el-table-column>
 | 
					
 | 
				
			||||||
                <el-table-column prop="name" label="名称" min-width="160" show-overflow-tooltip></el-table-column>
 | 
					            <template #database="{ data }">
 | 
				
			||||||
                <el-table-column min-width="170" label="host:port" show-overflow-tooltip>
 | 
					 | 
				
			||||||
                    <template #default="scope">
 | 
					 | 
				
			||||||
                        {{ `${scope.row.host}:${scope.row.port}` }}
 | 
					 | 
				
			||||||
                    </template>
 | 
					 | 
				
			||||||
                </el-table-column>
 | 
					 | 
				
			||||||
                <el-table-column prop="type" label="类型" min-width="90"></el-table-column>
 | 
					 | 
				
			||||||
                <el-table-column prop="database" label="数据库" min-width="80">
 | 
					 | 
				
			||||||
                    <template #default="scope">
 | 
					 | 
				
			||||||
                <el-popover placement="right" trigger="click" :width="300">
 | 
					                <el-popover placement="right" trigger="click" :width="300">
 | 
				
			||||||
                    <template #reference>
 | 
					                    <template #reference>
 | 
				
			||||||
                                <el-link type="primary" :underline="false" plain @click="selectDb(scope.row.dbs)">查看
 | 
					                        <el-link type="primary" :underline="false" plain @click="selectDb(data.dbs)">查看 </el-link>
 | 
				
			||||||
                                </el-link>
 | 
					 | 
				
			||||||
                    </template>
 | 
					                    </template>
 | 
				
			||||||
                            <el-input v-model="filterDb.param" @keyup="filterSchema" class="w-50 m-2" placeholder="搜索"
 | 
					                    <el-input v-model="filterDb.param" @keyup="filterSchema" class="w-50 m-2" placeholder="搜索" size="small">
 | 
				
			||||||
                                size="small">
 | 
					 | 
				
			||||||
                        <template #prefix>
 | 
					                        <template #prefix>
 | 
				
			||||||
                            <el-icon class="el-input__icon">
 | 
					                            <el-icon class="el-input__icon">
 | 
				
			||||||
                                <search-icon />
 | 
					                                <search-icon />
 | 
				
			||||||
                            </el-icon>
 | 
					                            </el-icon>
 | 
				
			||||||
                        </template>
 | 
					                        </template>
 | 
				
			||||||
                    </el-input>
 | 
					                    </el-input>
 | 
				
			||||||
                            <div class="el-tag--plain el-tag--success" v-for="db in filterDb.list" :key="db"
 | 
					                    <div
 | 
				
			||||||
                                style="border:1px var(--color-success-light-3) solid; margin-top: 3px;border-radius: 5px; padding: 2px;position: relative">
 | 
					                        class="el-tag--plain el-tag--success"
 | 
				
			||||||
 | 
					                        v-for="db in filterDb.list"
 | 
				
			||||||
 | 
					                        :key="db"
 | 
				
			||||||
 | 
					                        style="border: 1px var(--color-success-light-3) solid; margin-top: 3px; border-radius: 5px; padding: 2px; position: relative"
 | 
				
			||||||
 | 
					                    >
 | 
				
			||||||
                        <el-link type="success" plain size="small" :underline="false">{{ db }}</el-link>
 | 
					                        <el-link type="success" plain size="small" :underline="false">{{ db }}</el-link>
 | 
				
			||||||
                                <el-link type="primary" plain size="small" :underline="false"
 | 
					                        <el-link type="primary" plain size="small" :underline="false" @click="showTableInfo(data, db)" style="position: absolute; right: 4px"
 | 
				
			||||||
                                    @click="showTableInfo(scope.row, db)" style="position: absolute; right: 4px">操作
 | 
					                            >操作
 | 
				
			||||||
                        </el-link>
 | 
					                        </el-link>
 | 
				
			||||||
                    </div>
 | 
					                    </div>
 | 
				
			||||||
                </el-popover>
 | 
					                </el-popover>
 | 
				
			||||||
            </template>
 | 
					            </template>
 | 
				
			||||||
                </el-table-column>
 | 
					 | 
				
			||||||
                <el-table-column prop="username" label="用户名" min-width="100"></el-table-column>
 | 
					 | 
				
			||||||
                <el-table-column prop="remark" label="备注" min-width="150" show-overflow-tooltip></el-table-column>
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
                <el-table-column label="操作" min-width="160" fixed="right">
 | 
					            <template #more="{ data }">
 | 
				
			||||||
                    <template #default="scope">
 | 
					                <el-button @click="showInfo(data)" link>详情</el-button>
 | 
				
			||||||
                        <el-link plain size="small" :underline="false" @click="showInfo(scope.row)">
 | 
					                <el-button class="ml5" type="primary" @click="onShowSqlExec(data)" link>SQL执行记录</el-button>
 | 
				
			||||||
                            详情</el-link>
 | 
					 | 
				
			||||||
                        <el-divider direction="vertical" border-style="dashed" />
 | 
					 | 
				
			||||||
                        <el-link class="ml5" type="primary" plain size="small" :underline="false"
 | 
					 | 
				
			||||||
                            @click="onShowSqlExec(scope.row)">
 | 
					 | 
				
			||||||
                            SQL执行记录</el-link>
 | 
					 | 
				
			||||||
            </template>
 | 
					            </template>
 | 
				
			||||||
                </el-table-column>
 | 
					
 | 
				
			||||||
            </el-table>
 | 
					            <template #action="{ data }">
 | 
				
			||||||
            <el-row style="margin-top: 20px" type="flex" justify="end">
 | 
					                <el-button v-if="actionBtns[perms.saveDb]" @click="editDb(data)" type="primary" link>编辑</el-button>
 | 
				
			||||||
                <el-pagination style="text-align: right" @current-change="handlePageChange" :total="total"
 | 
					                <el-button v-if="data.type == 'mysql'" class="ml5" type="primary" @click="onDumpDbs(data)" link>导出</el-button>
 | 
				
			||||||
                    layout="prev, pager, next, total, jumper" v-model:current-page="query.pageNum"
 | 
					            </template>
 | 
				
			||||||
                    :page-size="query.pageSize"></el-pagination>
 | 
					        </page-table>
 | 
				
			||||||
            </el-row>
 | 
					 | 
				
			||||||
        </el-card>
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        <el-dialog width="80%" :title="`${db} 表信息`" :before-close="closeTableInfo" v-model="tableInfoDialog.visible">
 | 
					        <el-dialog width="80%" :title="`${db} 表信息`" :before-close="closeTableInfo" v-model="tableInfoDialog.visible">
 | 
				
			||||||
            <el-row class="mb10">
 | 
					            <db-table-list :db-id="dbId" :db="db" :db-type="state.row.type" />
 | 
				
			||||||
                <el-popover v-model:visible="showDumpInfo" :width="470" placement="right" trigger="click">
 | 
					        </el-dialog>
 | 
				
			||||||
                    <template #reference>
 | 
					
 | 
				
			||||||
                        <el-button class="ml5" type="success" size="small">导出</el-button>
 | 
					        <el-dialog width="620" :title="`${db} 数据库导出`" v-model="exportDialog.visible">
 | 
				
			||||||
                    </template>
 | 
					            <el-row justify="space-between">
 | 
				
			||||||
 | 
					                <el-col :span="9">
 | 
				
			||||||
                    <el-form-item label="导出内容: ">
 | 
					                    <el-form-item label="导出内容: ">
 | 
				
			||||||
                        <el-radio-group v-model="dumpInfo.type">
 | 
					                        <el-checkbox-group v-model="exportDialog.contents" :min="1">
 | 
				
			||||||
                            <el-radio :label="1" size="small">结构</el-radio>
 | 
					                            <el-checkbox label="结构" />
 | 
				
			||||||
                            <el-radio :label="2" size="small">数据</el-radio>
 | 
					                            <el-checkbox label="数据" />
 | 
				
			||||||
                            <el-radio :label="3" size="small">结构+数据</el-radio>
 | 
					                        </el-checkbox-group>
 | 
				
			||||||
 | 
					                    </el-form-item>
 | 
				
			||||||
 | 
					                </el-col>
 | 
				
			||||||
 | 
					                <el-col :span="9">
 | 
				
			||||||
 | 
					                    <el-form-item label="扩展名: ">
 | 
				
			||||||
 | 
					                        <el-radio-group v-model="exportDialog.extName">
 | 
				
			||||||
 | 
					                            <el-radio label="sql" />
 | 
				
			||||||
 | 
					                            <el-radio label="gzip" />
 | 
				
			||||||
                        </el-radio-group>
 | 
					                        </el-radio-group>
 | 
				
			||||||
                    </el-form-item>
 | 
					                    </el-form-item>
 | 
				
			||||||
 | 
					                </el-col>
 | 
				
			||||||
 | 
					            </el-row>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    <el-form-item label="导出表: ">
 | 
					            <el-form-item>
 | 
				
			||||||
                        <el-table @selection-change="handleDumpTableSelectionChange" max-height="300" size="small"
 | 
					                <el-transfer
 | 
				
			||||||
                            :data="tableInfoDialog.infos">
 | 
					                    v-model="exportDialog.value"
 | 
				
			||||||
                            <el-table-column type="selection" width="45" />
 | 
					                    filterable
 | 
				
			||||||
                            <el-table-column property="tableName" label="表名" min-width="150" show-overflow-tooltip>
 | 
					                    filter-placeholder="按数据库名称筛选"
 | 
				
			||||||
                            </el-table-column>
 | 
					                    :titles="['全部数据库', '导出数据库']"
 | 
				
			||||||
                            <el-table-column property="tableComment" label="备注" min-width="150" show-overflow-tooltip>
 | 
					                    :data="exportDialog.data"
 | 
				
			||||||
                            </el-table-column>
 | 
					                    max-height="300"
 | 
				
			||||||
                        </el-table>
 | 
					                    size="small"
 | 
				
			||||||
 | 
					                />
 | 
				
			||||||
            </el-form-item>
 | 
					            </el-form-item>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    <div style="text-align: right">
 | 
					            <template #footer>
 | 
				
			||||||
                        <el-button @click="showDumpInfo = false" size="small">取消</el-button>
 | 
					                <div class="dialog-footer">
 | 
				
			||||||
                        <el-button @click="dump(db)" type="success" size="small">确定</el-button>
 | 
					                    <el-button @click="exportDialog.visible = false">取消</el-button>
 | 
				
			||||||
 | 
					                    <el-button @click="dumpDbs()" type="primary">确定</el-button>
 | 
				
			||||||
                </div>
 | 
					                </div>
 | 
				
			||||||
                </el-popover>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                <el-button type="primary" size="small" @click="openEditTable(false)">创建表</el-button>
 | 
					 | 
				
			||||||
            </el-row>
 | 
					 | 
				
			||||||
            <el-table v-loading="tableInfoDialog.loading" border stripe :data="filterTableInfos" size="small"
 | 
					 | 
				
			||||||
                max-height="680">
 | 
					 | 
				
			||||||
                <el-table-column property="tableName" label="表名" min-width="150" show-overflow-tooltip>
 | 
					 | 
				
			||||||
                    <template #header>
 | 
					 | 
				
			||||||
                        <el-input v-model="tableInfoDialog.tableNameSearch" size="small" placeholder="表名: 输入可过滤"
 | 
					 | 
				
			||||||
                            clearable />
 | 
					 | 
				
			||||||
            </template>
 | 
					            </template>
 | 
				
			||||||
                </el-table-column>
 | 
					 | 
				
			||||||
                <el-table-column property="tableComment" label="备注" min-width="150" show-overflow-tooltip>
 | 
					 | 
				
			||||||
                    <template #header>
 | 
					 | 
				
			||||||
                        <el-input v-model="tableInfoDialog.tableCommentSearch" size="small" placeholder="备注: 输入可过滤"
 | 
					 | 
				
			||||||
                            clearable />
 | 
					 | 
				
			||||||
                    </template>
 | 
					 | 
				
			||||||
                </el-table-column>
 | 
					 | 
				
			||||||
                <el-table-column prop="tableRows" label="Rows" min-width="70" sortable
 | 
					 | 
				
			||||||
                    :sort-method="(a: any, b: any) => parseInt(a.tableRows) - parseInt(b.tableRows)"></el-table-column>
 | 
					 | 
				
			||||||
                <el-table-column property="dataLength" label="数据大小" sortable
 | 
					 | 
				
			||||||
                    :sort-method="(a: any, b: any) => parseInt(a.dataLength) - parseInt(b.dataLength)">
 | 
					 | 
				
			||||||
                    <template #default="scope">
 | 
					 | 
				
			||||||
                        {{ formatByteSize(scope.row.dataLength) }}
 | 
					 | 
				
			||||||
                    </template>
 | 
					 | 
				
			||||||
                </el-table-column>
 | 
					 | 
				
			||||||
                <el-table-column property="indexLength" label="索引大小" sortable
 | 
					 | 
				
			||||||
                    :sort-method="(a: any, b: any) => parseInt(a.indexLength) - parseInt(b.indexLength)">
 | 
					 | 
				
			||||||
                    <template #default="scope">
 | 
					 | 
				
			||||||
                        {{ formatByteSize(scope.row.indexLength) }}
 | 
					 | 
				
			||||||
                    </template>
 | 
					 | 
				
			||||||
                </el-table-column>
 | 
					 | 
				
			||||||
                <el-table-column property="createTime" label="创建时间" min-width="150"> </el-table-column>
 | 
					 | 
				
			||||||
                <el-table-column label="更多信息" min-width="140">
 | 
					 | 
				
			||||||
                    <template #default="scope">
 | 
					 | 
				
			||||||
                        <el-link @click.prevent="showColumns(scope.row)" type="primary">字段</el-link>
 | 
					 | 
				
			||||||
                        <el-link class="ml5" @click.prevent="showTableIndex(scope.row)" type="success">索引</el-link>
 | 
					 | 
				
			||||||
                        <el-link class="ml5"
 | 
					 | 
				
			||||||
                            v-if="tableCreateDialog.enableEditTypes.indexOf(tableCreateDialog.type) > -1"
 | 
					 | 
				
			||||||
                            @click.prevent="openEditTable(scope.row)" type="warning">编辑表</el-link>
 | 
					 | 
				
			||||||
                        <el-link class="ml5" @click.prevent="showCreateDdl(scope.row)" type="info">DDL</el-link>
 | 
					 | 
				
			||||||
                    </template>
 | 
					 | 
				
			||||||
                </el-table-column>
 | 
					 | 
				
			||||||
                <el-table-column label="操作" min-width="80">
 | 
					 | 
				
			||||||
                    <template #default="scope">
 | 
					 | 
				
			||||||
                        <el-link @click.prevent="dropTable(scope.row)" type="danger">删除</el-link>
 | 
					 | 
				
			||||||
                    </template>
 | 
					 | 
				
			||||||
                </el-table-column>
 | 
					 | 
				
			||||||
            </el-table>
 | 
					 | 
				
			||||||
        </el-dialog>
 | 
					        </el-dialog>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        <el-dialog width="90%" :title="`${sqlExecLogDialog.title} - SQL执行记录`" :before-close="onBeforeCloseSqlExecDialog"
 | 
					        <el-dialog
 | 
				
			||||||
            v-model="sqlExecLogDialog.visible">
 | 
					            width="90%"
 | 
				
			||||||
            <div class="toolbar">
 | 
					            :title="`${sqlExecLogDialog.title} - SQL执行记录`"
 | 
				
			||||||
                <el-select v-model="sqlExecLogDialog.query.db" placeholder="请选择数据库" filterable clearable>
 | 
					            :before-close="onBeforeCloseSqlExecDialog"
 | 
				
			||||||
                    <el-option v-for="item in sqlExecLogDialog.dbs" :key="item" :label="`${item}`" :value="item">
 | 
					            :close-on-click-modal="false"
 | 
				
			||||||
                    </el-option>
 | 
					            v-model="sqlExecLogDialog.visible"
 | 
				
			||||||
                </el-select>
 | 
					        >
 | 
				
			||||||
                <el-input v-model="sqlExecLogDialog.query.table" placeholder="请输入表名" clearable class="ml5"
 | 
					            <db-sql-exec-log :db-id="sqlExecLogDialog.dbId" :dbs="sqlExecLogDialog.dbs" />
 | 
				
			||||||
                    style="width: 180px" />
 | 
					 | 
				
			||||||
                <el-select v-model="sqlExecLogDialog.query.type" placeholder="请选择操作类型" clearable class="ml5">
 | 
					 | 
				
			||||||
                    <el-option v-for="item in enums.DbSqlExecTypeEnum as any" :key="item.value" :label="item.label"
 | 
					 | 
				
			||||||
                        :value="item.value"> </el-option>
 | 
					 | 
				
			||||||
                </el-select>
 | 
					 | 
				
			||||||
                <el-button class="ml5" @click="searchSqlExecLog" type="success" icon="search"></el-button>
 | 
					 | 
				
			||||||
            </div>
 | 
					 | 
				
			||||||
            <el-table border stripe :data="sqlExecLogDialog.data" size="small">
 | 
					 | 
				
			||||||
                <el-table-column prop="db" label="数据库" min-width="60" show-overflow-tooltip> </el-table-column>
 | 
					 | 
				
			||||||
                <el-table-column prop="table" label="表" min-width="60" show-overflow-tooltip> </el-table-column>
 | 
					 | 
				
			||||||
                <el-table-column prop="type" label="类型" width="85" show-overflow-tooltip>
 | 
					 | 
				
			||||||
                    <template #default="scope">
 | 
					 | 
				
			||||||
                        <el-tag v-if="scope.row.type == enums.DbSqlExecTypeEnum['UPDATE'].value" color="#E4F5EB"
 | 
					 | 
				
			||||||
                            size="small">UPDATE</el-tag>
 | 
					 | 
				
			||||||
                        <el-tag v-if="scope.row.type == enums.DbSqlExecTypeEnum['DELETE'].value" color="#F9E2AE"
 | 
					 | 
				
			||||||
                            size="small">DELETE</el-tag>
 | 
					 | 
				
			||||||
                        <el-tag v-if="scope.row.type == enums.DbSqlExecTypeEnum['INSERT'].value" color="#A8DEE0"
 | 
					 | 
				
			||||||
                            size="small">INSERT</el-tag>
 | 
					 | 
				
			||||||
                        <el-tag v-if="scope.row.type == enums.DbSqlExecTypeEnum['QUERY'].value" color="#A8DEE0"
 | 
					 | 
				
			||||||
                            size="small">QUERY</el-tag>
 | 
					 | 
				
			||||||
                    </template>
 | 
					 | 
				
			||||||
                </el-table-column>
 | 
					 | 
				
			||||||
                <el-table-column prop="sql" label="SQL" min-width="230" show-overflow-tooltip> </el-table-column>
 | 
					 | 
				
			||||||
                <el-table-column prop="oldValue" label="原值" min-width="150" show-overflow-tooltip> </el-table-column>
 | 
					 | 
				
			||||||
                <el-table-column prop="creator" label="执行人" min-width="60" show-overflow-tooltip> </el-table-column>
 | 
					 | 
				
			||||||
                <el-table-column prop="createTime" label="执行时间" show-overflow-tooltip>
 | 
					 | 
				
			||||||
                    <template #default="scope">
 | 
					 | 
				
			||||||
                        {{ dateFormat(scope.row.createTime) }}
 | 
					 | 
				
			||||||
                    </template>
 | 
					 | 
				
			||||||
                </el-table-column>
 | 
					 | 
				
			||||||
                <el-table-column prop="remark" label="备注" min-width="60" show-overflow-tooltip> </el-table-column>
 | 
					 | 
				
			||||||
                <el-table-column label="操作" min-width="50" fixed="right">
 | 
					 | 
				
			||||||
                    <template #default="scope">
 | 
					 | 
				
			||||||
                        <el-link
 | 
					 | 
				
			||||||
                            v-if="scope.row.type == enums.DbSqlExecTypeEnum['UPDATE'].value || scope.row.type == enums.DbSqlExecTypeEnum['DELETE'].value"
 | 
					 | 
				
			||||||
                            type="primary" plain size="small" :underline="false" @click="onShowRollbackSql(scope.row)">
 | 
					 | 
				
			||||||
                            还原SQL</el-link>
 | 
					 | 
				
			||||||
                    </template>
 | 
					 | 
				
			||||||
                </el-table-column>
 | 
					 | 
				
			||||||
            </el-table>
 | 
					 | 
				
			||||||
            <el-row style="margin-top: 20px" type="flex" justify="end">
 | 
					 | 
				
			||||||
                <el-pagination style="text-align: right" @current-change="handleSqlExecPageChange"
 | 
					 | 
				
			||||||
                    :total="sqlExecLogDialog.total" layout="prev, pager, next, total, jumper"
 | 
					 | 
				
			||||||
                    v-model:current-page="sqlExecLogDialog.query.pageNum" :page-size="sqlExecLogDialog.query.pageSize">
 | 
					 | 
				
			||||||
                </el-pagination>
 | 
					 | 
				
			||||||
            </el-row>
 | 
					 | 
				
			||||||
        </el-dialog>
 | 
					        </el-dialog>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        <el-dialog width="55%" :title="`还原SQL`" v-model="rollbackSqlDialog.visible">
 | 
					        <el-dialog v-model="infoDialog.visible" :before-close="onBeforeCloseInfoDialog" :close-on-click-modal="false">
 | 
				
			||||||
            <el-input type="textarea" :autosize="{ minRows: 15, maxRows: 30 }" v-model="rollbackSqlDialog.sql"
 | 
					 | 
				
			||||||
                size="small"> </el-input>
 | 
					 | 
				
			||||||
        </el-dialog>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        <el-dialog width="40%" :title="`${chooseTableName} 字段信息`" v-model="columnDialog.visible">
 | 
					 | 
				
			||||||
            <el-table border stripe :data="columnDialog.columns" size="small">
 | 
					 | 
				
			||||||
                <el-table-column prop="columnName" label="名称" show-overflow-tooltip> </el-table-column>
 | 
					 | 
				
			||||||
                <el-table-column width="120" prop="columnType" label="类型" show-overflow-tooltip> </el-table-column>
 | 
					 | 
				
			||||||
                <el-table-column width="80" prop="nullable" label="是否可为空" show-overflow-tooltip> </el-table-column>
 | 
					 | 
				
			||||||
                <el-table-column prop="columnComment" label="备注" show-overflow-tooltip> </el-table-column>
 | 
					 | 
				
			||||||
            </el-table>
 | 
					 | 
				
			||||||
        </el-dialog>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        <el-dialog width="40%" :title="`${chooseTableName} 索引信息`" v-model="indexDialog.visible">
 | 
					 | 
				
			||||||
            <el-table border stripe :data="indexDialog.indexs" size="small">
 | 
					 | 
				
			||||||
                <el-table-column prop="indexName" label="索引名" min-width="120" show-overflow-tooltip> </el-table-column>
 | 
					 | 
				
			||||||
                <el-table-column prop="columnName" label="列名" min-width="120" show-overflow-tooltip> </el-table-column>
 | 
					 | 
				
			||||||
                <el-table-column prop="seqInIndex" label="列序列号" show-overflow-tooltip> </el-table-column>
 | 
					 | 
				
			||||||
                <el-table-column prop="indexType" label="类型"> </el-table-column>
 | 
					 | 
				
			||||||
                <el-table-column prop="indexComment" label="备注" min-width="130" show-overflow-tooltip>
 | 
					 | 
				
			||||||
                </el-table-column>
 | 
					 | 
				
			||||||
            </el-table>
 | 
					 | 
				
			||||||
        </el-dialog>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        <el-dialog width="55%" :title="`${chooseTableName} Create-DDL`" v-model="ddlDialog.visible">
 | 
					 | 
				
			||||||
            <el-input disabled type="textarea" :autosize="{ minRows: 15, maxRows: 30 }" v-model="ddlDialog.ddl"
 | 
					 | 
				
			||||||
                size="small"> </el-input>
 | 
					 | 
				
			||||||
        </el-dialog>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        <el-dialog v-model="infoDialog.visible">
 | 
					 | 
				
			||||||
            <el-descriptions title="详情" :column="3" border>
 | 
					            <el-descriptions title="详情" :column="3" border>
 | 
				
			||||||
                <el-descriptions-item :span="1.5" label="id">{{ infoDialog.data.id }}</el-descriptions-item>
 | 
					                <el-descriptions-item :span="3" label="标签路径">{{ infoDialog.data?.tagPath }}</el-descriptions-item>
 | 
				
			||||||
                <el-descriptions-item :span="1.5" label="名称">{{ infoDialog.data.name }}</el-descriptions-item>
 | 
					                <el-descriptions-item :span="2" label="名称">{{ infoDialog.data?.name }}</el-descriptions-item>
 | 
				
			||||||
 | 
					                <el-descriptions-item :span="1" label="id">{{ infoDialog.data?.id }}</el-descriptions-item>
 | 
				
			||||||
 | 
					                <el-descriptions-item :span="3" label="数据库">{{ infoDialog.data?.database }}</el-descriptions-item>
 | 
				
			||||||
 | 
					                <el-descriptions-item :span="3" label="备注">{{ infoDialog.data?.remark }}</el-descriptions-item>
 | 
				
			||||||
 | 
					                <el-descriptions-item :span="2" label="创建时间">{{ dateFormat(infoDialog.data?.createTime) }} </el-descriptions-item>
 | 
				
			||||||
 | 
					                <el-descriptions-item :span="1" label="创建者">{{ infoDialog.data?.creator }}</el-descriptions-item>
 | 
				
			||||||
 | 
					                <el-descriptions-item :span="2" label="更新时间">{{ dateFormat(infoDialog.data?.updateTime) }} </el-descriptions-item>
 | 
				
			||||||
 | 
					                <el-descriptions-item :span="1" label="修改者">{{ infoDialog.data?.modifier }}</el-descriptions-item>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                <el-descriptions-item :span="3" label="标签路径">{{ infoDialog.data.tagPath }}</el-descriptions-item>
 | 
					                <el-descriptions-item :span="3" label="数据库实例名称">{{ infoDialog.instance?.name }}</el-descriptions-item>
 | 
				
			||||||
 | 
					                <el-descriptions-item :span="2" label="主机">{{ infoDialog.instance?.host }}</el-descriptions-item>
 | 
				
			||||||
                <el-descriptions-item :span="2" label="主机">{{ infoDialog.data.host }}</el-descriptions-item>
 | 
					                <el-descriptions-item :span="1" label="端口">{{ infoDialog.instance?.port }}</el-descriptions-item>
 | 
				
			||||||
                <el-descriptions-item :span="1" label="端口">{{ infoDialog.data.port }}</el-descriptions-item>
 | 
					                <el-descriptions-item :span="2" label="用户名">{{ infoDialog.instance?.username }}</el-descriptions-item>
 | 
				
			||||||
 | 
					                <el-descriptions-item :span="1" label="类型">{{ infoDialog.instance?.type }}</el-descriptions-item>
 | 
				
			||||||
                <el-descriptions-item :span="2" label="用户名">{{ infoDialog.data.username }}</el-descriptions-item>
 | 
					 | 
				
			||||||
                <el-descriptions-item :span="1" label="类型">{{ infoDialog.data.type }}</el-descriptions-item>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                <el-descriptions-item :span="3" label="连接参数">{{ infoDialog.data.params }}</el-descriptions-item>
 | 
					 | 
				
			||||||
                <el-descriptions-item :span="3" label="备注">{{ infoDialog.data.remark }}</el-descriptions-item>
 | 
					 | 
				
			||||||
                <el-descriptions-item :span="3" label="数据库">{{ infoDialog.data.database }}</el-descriptions-item>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                <el-descriptions-item :span="3" label="SSH隧道">{{ infoDialog.data.sshTunnelMachineId > 0 ? '是' : '否' }}
 | 
					 | 
				
			||||||
                </el-descriptions-item>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                <el-descriptions-item :span="2" label="创建时间">{{ dateFormat(infoDialog.data.createTime) }}
 | 
					 | 
				
			||||||
                </el-descriptions-item>
 | 
					 | 
				
			||||||
                <el-descriptions-item :span="1" label="创建者">{{ infoDialog.data.creator }}</el-descriptions-item>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                <el-descriptions-item :span="2" label="更新时间">{{ dateFormat(infoDialog.data.updateTime) }}
 | 
					 | 
				
			||||||
                </el-descriptions-item>
 | 
					 | 
				
			||||||
                <el-descriptions-item :span="1" label="修改者">{{ infoDialog.data.modifier }}</el-descriptions-item>
 | 
					 | 
				
			||||||
            </el-descriptions>
 | 
					            </el-descriptions>
 | 
				
			||||||
        </el-dialog>
 | 
					        </el-dialog>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        <db-edit @val-change="valChange" :title="dbEditDialog.title" v-model:visible="dbEditDialog.visible"
 | 
					        <db-edit @val-change="valChange" :title="dbEditDialog.title" v-model:visible="dbEditDialog.visible" v-model:db="dbEditDialog.data"></db-edit>
 | 
				
			||||||
            v-model:db="dbEditDialog.data"></db-edit>
 | 
					 | 
				
			||||||
        <create-table :title="tableCreateDialog.title" :active-name="tableCreateDialog.activeName" :dbId="dbId" :db="db"
 | 
					 | 
				
			||||||
            :data="tableCreateDialog.data" v-model:visible="tableCreateDialog.visible" @submit-sql="onSubmitSql">
 | 
					 | 
				
			||||||
        </create-table>
 | 
					 | 
				
			||||||
    </div>
 | 
					    </div>
 | 
				
			||||||
</template>
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<script lang='ts' setup>
 | 
					<script lang="ts" setup>
 | 
				
			||||||
import { toRefs, reactive, computed, onMounted, defineAsyncComponent } from 'vue';
 | 
					import { ref, toRefs, reactive, onMounted, defineAsyncComponent } from 'vue';
 | 
				
			||||||
import { ElMessage, ElMessageBox } from 'element-plus';
 | 
					import { ElMessage, ElMessageBox } from 'element-plus';
 | 
				
			||||||
import { formatByteSize } from '@/common/utils/format';
 | 
					 | 
				
			||||||
import { dbApi } from './api';
 | 
					import { dbApi } from './api';
 | 
				
			||||||
import enums from './enums';
 | 
					 | 
				
			||||||
import SqlExecBox from './component/SqlExecBox';
 | 
					 | 
				
			||||||
import config from '@/common/config';
 | 
					import config from '@/common/config';
 | 
				
			||||||
import { getSession } from '@/common/utils/storage';
 | 
					import { getSession } from '@/common/utils/storage';
 | 
				
			||||||
import { isTrue } from '@/common/assert';
 | 
					import { isTrue } from '@/common/assert';
 | 
				
			||||||
import { Search as SearchIcon } from '@element-plus/icons-vue'
 | 
					import { Search as SearchIcon } from '@element-plus/icons-vue';
 | 
				
			||||||
import { tagApi } from '../tag/api';
 | 
					 | 
				
			||||||
import { dateFormat } from '@/common/utils/date';
 | 
					import { dateFormat } from '@/common/utils/date';
 | 
				
			||||||
import TagInfo from '../component/TagInfo.vue';
 | 
					import TagInfo from '../component/TagInfo.vue';
 | 
				
			||||||
 | 
					import PageTable from '@/components/pagetable/PageTable.vue';
 | 
				
			||||||
 | 
					import { TableColumn, TableQuery } from '@/components/pagetable';
 | 
				
			||||||
 | 
					import { hasPerms } from '@/components/auth/auth';
 | 
				
			||||||
 | 
					import DbSqlExecLog from './DbSqlExecLog.vue';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const DbEdit = defineAsyncComponent(() => import('./DbEdit.vue'));
 | 
					const DbEdit = defineAsyncComponent(() => import('./DbEdit.vue'));
 | 
				
			||||||
const CreateTable = defineAsyncComponent(() => import('./CreateTable.vue'));
 | 
					const DbTableList = defineAsyncComponent(() => import('./table/DbTableList.vue'));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const permissions = {
 | 
					const perms = {
 | 
				
			||||||
 | 
					    base: 'db',
 | 
				
			||||||
    saveDb: 'db:save',
 | 
					    saveDb: 'db:save',
 | 
				
			||||||
    delDb: 'db:del',
 | 
					    delDb: 'db:del',
 | 
				
			||||||
}
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const queryConfig = [TableQuery.slot('tagPath', '标签', 'tagPathSelect'), TableQuery.slot('instanceId', '实例', 'instanceSelect')];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const columns = ref([
 | 
				
			||||||
 | 
					    TableColumn.new('tagPath', '标签路径').isSlot().setAddWidth(20),
 | 
				
			||||||
 | 
					    TableColumn.new('name', '名称'),
 | 
				
			||||||
 | 
					    TableColumn.new('database', '数据库').isSlot().setMinWidth(70),
 | 
				
			||||||
 | 
					    TableColumn.new('remark', '备注'),
 | 
				
			||||||
 | 
					    TableColumn.new('more', '更多').isSlot().setMinWidth(180).fixedRight(),
 | 
				
			||||||
 | 
					]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// 该用户拥有的的操作列按钮权限
 | 
				
			||||||
 | 
					const actionBtns = hasPerms([perms.base, perms.saveDb]);
 | 
				
			||||||
 | 
					const actionColumn = TableColumn.new('action', '操作').isSlot().setMinWidth(150).fixedRight().alignCenter();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const pageTableRef: any = ref(null);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const state = reactive({
 | 
					const state = reactive({
 | 
				
			||||||
    row: {},
 | 
					    row: {} as any,
 | 
				
			||||||
    dbId: 0,
 | 
					    dbId: 0,
 | 
				
			||||||
    db: '',
 | 
					    db: '',
 | 
				
			||||||
    tags: [],
 | 
					    tags: [],
 | 
				
			||||||
    chooseId: null as any,
 | 
					    instances: [] as any,
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * 选中的数据
 | 
					     * 选中的数据
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    chooseData: null,
 | 
					    selectionData: [],
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * 查询条件
 | 
					     * 查询条件
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    query: {
 | 
					    query: {
 | 
				
			||||||
        tagPath: null,
 | 
					        tagPath: null,
 | 
				
			||||||
 | 
					        instanceId: null,
 | 
				
			||||||
        pageNum: 1,
 | 
					        pageNum: 1,
 | 
				
			||||||
        pageSize: 10,
 | 
					        pageSize: 10,
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
@@ -331,6 +231,10 @@ const state = reactive({
 | 
				
			|||||||
    infoDialog: {
 | 
					    infoDialog: {
 | 
				
			||||||
        visible: false,
 | 
					        visible: false,
 | 
				
			||||||
        data: null as any,
 | 
					        data: null as any,
 | 
				
			||||||
 | 
					        instance: null as any,
 | 
				
			||||||
 | 
					        query: {
 | 
				
			||||||
 | 
					            instanceId: 0,
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    showDumpInfo: false,
 | 
					    showDumpInfo: false,
 | 
				
			||||||
    dumpInfo: {
 | 
					    dumpInfo: {
 | 
				
			||||||
@@ -343,409 +247,199 @@ const state = reactive({
 | 
				
			|||||||
    sqlExecLogDialog: {
 | 
					    sqlExecLogDialog: {
 | 
				
			||||||
        title: '',
 | 
					        title: '',
 | 
				
			||||||
        visible: false,
 | 
					        visible: false,
 | 
				
			||||||
        data: [],
 | 
					 | 
				
			||||||
        total: 0,
 | 
					 | 
				
			||||||
        dbs: [],
 | 
					        dbs: [],
 | 
				
			||||||
        query: {
 | 
					 | 
				
			||||||
        dbId: 0,
 | 
					        dbId: 0,
 | 
				
			||||||
            db: '',
 | 
					 | 
				
			||||||
            table: '',
 | 
					 | 
				
			||||||
            type: null,
 | 
					 | 
				
			||||||
            pageNum: 1,
 | 
					 | 
				
			||||||
            pageSize: 12,
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    rollbackSqlDialog: {
 | 
					 | 
				
			||||||
        visible: false,
 | 
					 | 
				
			||||||
        sql: '',
 | 
					 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    chooseTableName: '',
 | 
					    chooseTableName: '',
 | 
				
			||||||
    tableInfoDialog: {
 | 
					    tableInfoDialog: {
 | 
				
			||||||
        loading: false,
 | 
					 | 
				
			||||||
        visible: false,
 | 
					        visible: false,
 | 
				
			||||||
        infos: [],
 | 
					 | 
				
			||||||
        tableNameSearch: '',
 | 
					 | 
				
			||||||
        tableCommentSearch: '',
 | 
					 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    columnDialog: {
 | 
					    exportDialog: {
 | 
				
			||||||
        visible: false,
 | 
					        visible: false,
 | 
				
			||||||
        columns: [],
 | 
					        dbId: 0,
 | 
				
			||||||
    },
 | 
					        type: 3,
 | 
				
			||||||
    indexDialog: {
 | 
					        data: [] as any,
 | 
				
			||||||
        visible: false,
 | 
					        value: [],
 | 
				
			||||||
        indexs: [],
 | 
					        contents: [] as any,
 | 
				
			||||||
    },
 | 
					        extName: '',
 | 
				
			||||||
    ddlDialog: {
 | 
					 | 
				
			||||||
        visible: false,
 | 
					 | 
				
			||||||
        ddl: '',
 | 
					 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    dbEditDialog: {
 | 
					    dbEditDialog: {
 | 
				
			||||||
        visible: false,
 | 
					        visible: false,
 | 
				
			||||||
        data: null as any,
 | 
					        data: null as any,
 | 
				
			||||||
        title: '新增数据库',
 | 
					        title: '新增数据库',
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    tableCreateDialog: {
 | 
					 | 
				
			||||||
        title: '创建表',
 | 
					 | 
				
			||||||
        visible: false,
 | 
					 | 
				
			||||||
        activeName: '1',
 | 
					 | 
				
			||||||
        type: '',
 | 
					 | 
				
			||||||
        enableEditTypes: ['mysql'], // 支持"编辑表"的数据库类型
 | 
					 | 
				
			||||||
        data: {  // 修改表时,传递修改数据
 | 
					 | 
				
			||||||
            edit: false,
 | 
					 | 
				
			||||||
            row: {},
 | 
					 | 
				
			||||||
            indexs: [],
 | 
					 | 
				
			||||||
            columns: [],
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    filterDb: {
 | 
					    filterDb: {
 | 
				
			||||||
        param: '',
 | 
					        param: '',
 | 
				
			||||||
        cache: [],
 | 
					        cache: [],
 | 
				
			||||||
        list: [],
 | 
					        list: [],
 | 
				
			||||||
    }
 | 
					    },
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const {
 | 
					const { dbId, db, tags, selectionData, query, datas, total, infoDialog, sqlExecLogDialog, tableInfoDialog, exportDialog, dbEditDialog, filterDb } =
 | 
				
			||||||
    dbId,
 | 
					    toRefs(state);
 | 
				
			||||||
    db,
 | 
					 | 
				
			||||||
    tags,
 | 
					 | 
				
			||||||
    chooseId,
 | 
					 | 
				
			||||||
    query,
 | 
					 | 
				
			||||||
    datas,
 | 
					 | 
				
			||||||
    total,
 | 
					 | 
				
			||||||
    infoDialog,
 | 
					 | 
				
			||||||
    showDumpInfo,
 | 
					 | 
				
			||||||
    dumpInfo,
 | 
					 | 
				
			||||||
    sqlExecLogDialog,
 | 
					 | 
				
			||||||
    rollbackSqlDialog,
 | 
					 | 
				
			||||||
    chooseTableName,
 | 
					 | 
				
			||||||
    tableInfoDialog,
 | 
					 | 
				
			||||||
    columnDialog,
 | 
					 | 
				
			||||||
    indexDialog,
 | 
					 | 
				
			||||||
    ddlDialog,
 | 
					 | 
				
			||||||
    dbEditDialog,
 | 
					 | 
				
			||||||
    tableCreateDialog,
 | 
					 | 
				
			||||||
    filterDb,
 | 
					 | 
				
			||||||
} = toRefs(state)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
onMounted(async () => {
 | 
					onMounted(async () => {
 | 
				
			||||||
 | 
					    if (Object.keys(actionBtns).length > 0) {
 | 
				
			||||||
 | 
					        columns.value.push(actionColumn);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
    search();
 | 
					    search();
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const filterTableInfos = computed(() => {
 | 
					 | 
				
			||||||
    const infos = state.tableInfoDialog.infos;
 | 
					 | 
				
			||||||
    const tableNameSearch = state.tableInfoDialog.tableNameSearch;
 | 
					 | 
				
			||||||
    const tableCommentSearch = state.tableInfoDialog.tableCommentSearch;
 | 
					 | 
				
			||||||
    if (!tableNameSearch && !tableCommentSearch) {
 | 
					 | 
				
			||||||
        return infos;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    return infos.filter((data: any) => {
 | 
					 | 
				
			||||||
        let tnMatch = true;
 | 
					 | 
				
			||||||
        let tcMatch = true;
 | 
					 | 
				
			||||||
        if (tableNameSearch) {
 | 
					 | 
				
			||||||
            tnMatch = data.tableName.toLowerCase().includes(tableNameSearch.toLowerCase());
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        if (tableCommentSearch) {
 | 
					 | 
				
			||||||
            tcMatch = data.tableComment.includes(tableCommentSearch);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        return tnMatch && tcMatch;
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
});
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const choose = (item: any) => {
 | 
					 | 
				
			||||||
    if (!item) {
 | 
					 | 
				
			||||||
        return;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    state.chooseId = item.id;
 | 
					 | 
				
			||||||
    state.chooseData = item;
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const search = async () => {
 | 
					const search = async () => {
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					        pageTableRef.value.loading(true);
 | 
				
			||||||
        let res: any = await dbApi.dbs.request(state.query);
 | 
					        let res: any = await dbApi.dbs.request(state.query);
 | 
				
			||||||
        // 切割数据库
 | 
					        // 切割数据库
 | 
				
			||||||
    res.list.forEach((e: any) => {
 | 
					        res.list?.forEach((e: any) => {
 | 
				
			||||||
            e.popoverSelectDbVisible = false;
 | 
					            e.popoverSelectDbVisible = false;
 | 
				
			||||||
            e.dbs = e.database.split(' ');
 | 
					            e.dbs = e.database.split(' ');
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
        state.datas = res.list;
 | 
					        state.datas = res.list;
 | 
				
			||||||
        state.total = res.total;
 | 
					        state.total = res.total;
 | 
				
			||||||
 | 
					    } finally {
 | 
				
			||||||
 | 
					        pageTableRef.value.loading(false);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const handlePageChange = (curPage: number) => {
 | 
					const showInfo = async (info: any) => {
 | 
				
			||||||
    state.query.pageNum = curPage;
 | 
					 | 
				
			||||||
    search();
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const showInfo = (info: any) => {
 | 
					 | 
				
			||||||
    state.infoDialog.data = info;
 | 
					    state.infoDialog.data = info;
 | 
				
			||||||
 | 
					    state.infoDialog.query.instanceId = info.instanceId;
 | 
				
			||||||
 | 
					    const res = await dbApi.getInstance.request(state.infoDialog.query);
 | 
				
			||||||
 | 
					    state.infoDialog.instance = res;
 | 
				
			||||||
    state.infoDialog.visible = true;
 | 
					    state.infoDialog.visible = true;
 | 
				
			||||||
}
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const onBeforeCloseInfoDialog = () => {
 | 
				
			||||||
 | 
					    state.infoDialog.visible = false;
 | 
				
			||||||
 | 
					    state.infoDialog.data = null;
 | 
				
			||||||
 | 
					    state.infoDialog.instance = null;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const getTags = async () => {
 | 
					const getTags = async () => {
 | 
				
			||||||
    state.tags = await tagApi.getAccountTags.request(null);
 | 
					    state.tags = await dbApi.dbTags.request(null);
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const editDb = async (isAdd = false) => {
 | 
					const getInstances = async (instanceName = '') => {
 | 
				
			||||||
    if (isAdd) {
 | 
					    if (!instanceName) {
 | 
				
			||||||
 | 
					        state.instances = [];
 | 
				
			||||||
 | 
					        return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    const data = await dbApi.instances.request({ name: instanceName });
 | 
				
			||||||
 | 
					    if (data) {
 | 
				
			||||||
 | 
					        state.instances = data.list;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const editDb = async (data: any) => {
 | 
				
			||||||
 | 
					    if (!data) {
 | 
				
			||||||
        state.dbEditDialog.data = null;
 | 
					        state.dbEditDialog.data = null;
 | 
				
			||||||
        state.dbEditDialog.title = '新增数据库资源';
 | 
					        state.dbEditDialog.title = '新增数据库资源';
 | 
				
			||||||
    } else {
 | 
					    } else {
 | 
				
			||||||
        state.dbEditDialog.data = state.chooseData;
 | 
					        state.dbEditDialog.data = data;
 | 
				
			||||||
        state.dbEditDialog.title = '修改数据库资源';
 | 
					        state.dbEditDialog.title = '修改数据库资源';
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    state.dbEditDialog.visible = true;
 | 
					    state.dbEditDialog.visible = true;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const valChange = () => {
 | 
					const valChange = () => {
 | 
				
			||||||
    state.chooseData = null;
 | 
					 | 
				
			||||||
    state.chooseId = null;
 | 
					 | 
				
			||||||
    search();
 | 
					    search();
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const deleteDb = async (id: number) => {
 | 
					const deleteDb = async () => {
 | 
				
			||||||
    try {
 | 
					    try {
 | 
				
			||||||
        await ElMessageBox.confirm(`确定删除该库?`, '提示', {
 | 
					        await ElMessageBox.confirm(`确定删除【${state.selectionData.map((x: any) => x.name).join(', ')}】库?`, '提示', {
 | 
				
			||||||
            confirmButtonText: '确定',
 | 
					            confirmButtonText: '确定',
 | 
				
			||||||
            cancelButtonText: '取消',
 | 
					            cancelButtonText: '取消',
 | 
				
			||||||
            type: 'warning',
 | 
					            type: 'warning',
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
        await dbApi.deleteDb.request({ id });
 | 
					        await dbApi.deleteDb.request({ id: state.selectionData.map((x: any) => x.id).join(',') });
 | 
				
			||||||
        ElMessage.success('删除成功');
 | 
					        ElMessage.success('删除成功');
 | 
				
			||||||
        state.chooseData = null;
 | 
					 | 
				
			||||||
        state.chooseId = null;
 | 
					 | 
				
			||||||
        search();
 | 
					        search();
 | 
				
			||||||
    } catch (err) { }
 | 
					    } catch (err) {}
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const onShowSqlExec = async (row: any) => {
 | 
					const onShowSqlExec = async (row: any) => {
 | 
				
			||||||
    state.sqlExecLogDialog.title = `${row.name}[${row.host}:${row.port}]`;
 | 
					    state.sqlExecLogDialog.title = `${row.name}`;
 | 
				
			||||||
    state.sqlExecLogDialog.query.dbId = row.id;
 | 
					    state.sqlExecLogDialog.dbId = row.id;
 | 
				
			||||||
    state.sqlExecLogDialog.dbs = row.database.split(' ');
 | 
					    state.sqlExecLogDialog.dbs = row.database.split(' ');
 | 
				
			||||||
    searchSqlExecLog();
 | 
					 | 
				
			||||||
    state.sqlExecLogDialog.visible = true;
 | 
					    state.sqlExecLogDialog.visible = true;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const onBeforeCloseSqlExecDialog = () => {
 | 
					const onBeforeCloseSqlExecDialog = () => {
 | 
				
			||||||
    state.sqlExecLogDialog.visible = false;
 | 
					    state.sqlExecLogDialog.visible = false;
 | 
				
			||||||
    state.sqlExecLogDialog.data = [];
 | 
					 | 
				
			||||||
    state.sqlExecLogDialog.dbs = [];
 | 
					    state.sqlExecLogDialog.dbs = [];
 | 
				
			||||||
    state.sqlExecLogDialog.total = 0;
 | 
					    state.sqlExecLogDialog.dbId = 0;
 | 
				
			||||||
    state.sqlExecLogDialog.query.dbId = 0;
 | 
					 | 
				
			||||||
    state.sqlExecLogDialog.query.pageNum = 1;
 | 
					 | 
				
			||||||
    state.sqlExecLogDialog.query.table = '';
 | 
					 | 
				
			||||||
    state.sqlExecLogDialog.query.db = '';
 | 
					 | 
				
			||||||
    state.sqlExecLogDialog.query.type = null;
 | 
					 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const searchSqlExecLog = async () => {
 | 
					const onDumpDbs = async (row: any) => {
 | 
				
			||||||
    const res = await dbApi.getSqlExecs.request(state.sqlExecLogDialog.query);
 | 
					    const dbs = row.database.split(' ');
 | 
				
			||||||
    state.sqlExecLogDialog.data = res.list;
 | 
					    const data = [];
 | 
				
			||||||
    state.sqlExecLogDialog.total = res.total;
 | 
					    for (let name of dbs) {
 | 
				
			||||||
};
 | 
					        data.push({
 | 
				
			||||||
 | 
					            key: name,
 | 
				
			||||||
const handleSqlExecPageChange = (curPage: number) => {
 | 
					            label: name,
 | 
				
			||||||
    state.sqlExecLogDialog.query.pageNum = curPage;
 | 
					        });
 | 
				
			||||||
    searchSqlExecLog();
 | 
					    }
 | 
				
			||||||
};
 | 
					    state.exportDialog.value = [];
 | 
				
			||||||
 | 
					    state.exportDialog.data = data;
 | 
				
			||||||
/**
 | 
					    state.exportDialog.dbId = row.id;
 | 
				
			||||||
 * 选择导出数据库表
 | 
					    state.exportDialog.contents = ['结构', '数据'];
 | 
				
			||||||
 */
 | 
					    state.exportDialog.extName = 'sql';
 | 
				
			||||||
const handleDumpTableSelectionChange = (vals: any) => {
 | 
					    state.exportDialog.visible = true;
 | 
				
			||||||
    state.dumpInfo.tables = vals.map((x: any) => x.tableName);
 | 
					 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * 数据库信息导出
 | 
					 * 数据库信息导出
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
const dump = (db: string) => {
 | 
					const dumpDbs = () => {
 | 
				
			||||||
    isTrue(state.dumpInfo.tables.length > 0, '请选择要导出的表');
 | 
					    isTrue(state.exportDialog.value.length > 0, '请添加要导出的数据库');
 | 
				
			||||||
    const a = document.createElement('a');
 | 
					    const a = document.createElement('a');
 | 
				
			||||||
 | 
					    let type = 0;
 | 
				
			||||||
 | 
					    for (let c of state.exportDialog.contents) {
 | 
				
			||||||
 | 
					        if (c == '结构') {
 | 
				
			||||||
 | 
					            type += 1;
 | 
				
			||||||
 | 
					        } else if (c == '数据') {
 | 
				
			||||||
 | 
					            type += 2;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
    a.setAttribute(
 | 
					    a.setAttribute(
 | 
				
			||||||
        'href',
 | 
					        'href',
 | 
				
			||||||
        `${config.baseApiUrl}/dbs/${state.dbId}/dump?db=${db}&type=${state.dumpInfo.type}&tables=${state.dumpInfo.tables.join(
 | 
					        `${config.baseApiUrl}/dbs/${state.exportDialog.dbId}/dump?db=${state.exportDialog.value.join(',')}&type=${type}&extName=${
 | 
				
			||||||
            ','
 | 
					            state.exportDialog.extName
 | 
				
			||||||
        )}&token=${getSession('token')}`
 | 
					        }&token=${getSession('token')}`
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
    a.click();
 | 
					    a.click();
 | 
				
			||||||
    state.showDumpInfo = false;
 | 
					    state.exportDialog.visible = false;
 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const onShowRollbackSql = async (sqlExecLog: any) => {
 | 
					 | 
				
			||||||
    const columns = await dbApi.columnMetadata.request({ id: sqlExecLog.dbId, db: sqlExecLog.db, tableName: sqlExecLog.table });
 | 
					 | 
				
			||||||
    const primaryKey = getPrimaryKey(columns);
 | 
					 | 
				
			||||||
    const oldValue = JSON.parse(sqlExecLog.oldValue);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    const rollbackSqls = [];
 | 
					 | 
				
			||||||
    if (sqlExecLog.type == enums.DbSqlExecTypeEnum['UPDATE'].value) {
 | 
					 | 
				
			||||||
        for (let ov of oldValue) {
 | 
					 | 
				
			||||||
            const setItems = [];
 | 
					 | 
				
			||||||
            for (let key in ov) {
 | 
					 | 
				
			||||||
                if (key == primaryKey) {
 | 
					 | 
				
			||||||
                    continue;
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
                setItems.push(`${key} = ${wrapValue(ov[key])}`);
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            rollbackSqls.push(`UPDATE ${sqlExecLog.table} SET ${setItems.join(', ')} WHERE ${primaryKey} = ${wrapValue(ov[primaryKey])};`);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    } else if (sqlExecLog.type == enums.DbSqlExecTypeEnum['DELETE'].value) {
 | 
					 | 
				
			||||||
        const columnNames = columns.map((c: any) => c.columnName);
 | 
					 | 
				
			||||||
        for (let ov of oldValue) {
 | 
					 | 
				
			||||||
            const values = [];
 | 
					 | 
				
			||||||
            for (let column of columnNames) {
 | 
					 | 
				
			||||||
                values.push(wrapValue(ov[column]));
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            rollbackSqls.push(`INSERT INTO ${sqlExecLog.table} (${columnNames.join(', ')}) VALUES (${values.join(', ')});`);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    state.rollbackSqlDialog.sql = rollbackSqls.join('\n');
 | 
					 | 
				
			||||||
    state.rollbackSqlDialog.visible = true;
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const getPrimaryKey = (columns: any) => {
 | 
					 | 
				
			||||||
    const col = columns.find((c: any) => c.columnKey == 'PRI');
 | 
					 | 
				
			||||||
    if (col) {
 | 
					 | 
				
			||||||
        return col.columnName;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    return columns[0].columnName;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * 包装值,如果值类型为number则直接返回,其他则需要使用''包装
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
const wrapValue = (val: any) => {
 | 
					 | 
				
			||||||
    if (typeof val == 'number') {
 | 
					 | 
				
			||||||
        return val;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    return `'${val}'`;
 | 
					 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const showTableInfo = async (row: any, db: string) => {
 | 
					const showTableInfo = async (row: any, db: string) => {
 | 
				
			||||||
    state.tableInfoDialog.loading = true;
 | 
					 | 
				
			||||||
    state.tableInfoDialog.visible = true;
 | 
					 | 
				
			||||||
    try {
 | 
					 | 
				
			||||||
        state.tableInfoDialog.infos = await dbApi.tableInfos.request({ id: row.id, db });
 | 
					 | 
				
			||||||
        state.tableCreateDialog.type = row.type
 | 
					 | 
				
			||||||
    state.dbId = row.id;
 | 
					    state.dbId = row.id;
 | 
				
			||||||
    state.row = row;
 | 
					    state.row = row;
 | 
				
			||||||
    state.db = db;
 | 
					    state.db = db;
 | 
				
			||||||
    } catch (e) {
 | 
					    state.tableInfoDialog.visible = true;
 | 
				
			||||||
        state.tableInfoDialog.visible = false;
 | 
					 | 
				
			||||||
    } finally {
 | 
					 | 
				
			||||||
        state.tableInfoDialog.loading = false;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const onSubmitSql = async (row: { tableName: string }) => {
 | 
					 | 
				
			||||||
    await openEditTable(row)
 | 
					 | 
				
			||||||
    state.tableInfoDialog.infos = await dbApi.tableInfos.request({ id: state.dbId, db: state.db });
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const closeTableInfo = () => {
 | 
					const closeTableInfo = () => {
 | 
				
			||||||
    state.showDumpInfo = false;
 | 
					    state.showDumpInfo = false;
 | 
				
			||||||
    state.tableInfoDialog.visible = false;
 | 
					    state.tableInfoDialog.visible = false;
 | 
				
			||||||
    state.tableInfoDialog.infos = [];
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const showColumns = async (row: any) => {
 | 
					 | 
				
			||||||
    state.chooseTableName = row.tableName;
 | 
					 | 
				
			||||||
    state.columnDialog.columns = await dbApi.columnMetadata.request({
 | 
					 | 
				
			||||||
        id: state.chooseId,
 | 
					 | 
				
			||||||
        db: state.db,
 | 
					 | 
				
			||||||
        tableName: row.tableName,
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    state.columnDialog.visible = true;
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const showTableIndex = async (row: any) => {
 | 
					 | 
				
			||||||
    state.chooseTableName = row.tableName;
 | 
					 | 
				
			||||||
    state.indexDialog.indexs = await dbApi.tableIndex.request({
 | 
					 | 
				
			||||||
        id: state.chooseId,
 | 
					 | 
				
			||||||
        db: state.db,
 | 
					 | 
				
			||||||
        tableName: row.tableName,
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    state.indexDialog.visible = true;
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const showCreateDdl = async (row: any) => {
 | 
					 | 
				
			||||||
    state.chooseTableName = row.tableName;
 | 
					 | 
				
			||||||
    const res = await dbApi.tableDdl.request({
 | 
					 | 
				
			||||||
        id: state.chooseId,
 | 
					 | 
				
			||||||
        db: state.db,
 | 
					 | 
				
			||||||
        tableName: row.tableName,
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
    state.ddlDialog.ddl = res;
 | 
					 | 
				
			||||||
    state.ddlDialog.visible = true;
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * 删除表
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
const dropTable = async (row: any) => {
 | 
					 | 
				
			||||||
    try {
 | 
					 | 
				
			||||||
        const tableName = row.tableName;
 | 
					 | 
				
			||||||
        await ElMessageBox.confirm(`确定删除'${tableName}'表?`, '提示', {
 | 
					 | 
				
			||||||
            confirmButtonText: '确定',
 | 
					 | 
				
			||||||
            cancelButtonText: '取消',
 | 
					 | 
				
			||||||
            type: 'warning',
 | 
					 | 
				
			||||||
        });
 | 
					 | 
				
			||||||
        SqlExecBox({
 | 
					 | 
				
			||||||
            sql: `DROP TABLE ${tableName}`,
 | 
					 | 
				
			||||||
            dbId: state.chooseId,
 | 
					 | 
				
			||||||
            db: state.db,
 | 
					 | 
				
			||||||
            runSuccessCallback: async () => {
 | 
					 | 
				
			||||||
                state.tableInfoDialog.infos = await dbApi.tableInfos.request({ id: state.chooseId, db: state.db });
 | 
					 | 
				
			||||||
            },
 | 
					 | 
				
			||||||
        });
 | 
					 | 
				
			||||||
    } catch (err) { }
 | 
					 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// 点击查看时初始化数据
 | 
					// 点击查看时初始化数据
 | 
				
			||||||
const selectDb = (row: any) => {
 | 
					const selectDb = (row: any) => {
 | 
				
			||||||
    state.filterDb.param = ''
 | 
					    state.filterDb.param = '';
 | 
				
			||||||
    state.filterDb.cache = row;
 | 
					    state.filterDb.cache = row;
 | 
				
			||||||
    state.filterDb.list = row;
 | 
					    state.filterDb.list = row;
 | 
				
			||||||
}
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// 输入字符过滤schema
 | 
					// 输入字符过滤schema
 | 
				
			||||||
const filterSchema = () => {
 | 
					const filterSchema = () => {
 | 
				
			||||||
    if (state.filterDb.param) {
 | 
					    if (state.filterDb.param) {
 | 
				
			||||||
        state.filterDb.list = state.filterDb.cache.filter((a) => { return String(a).toLowerCase().indexOf(state.filterDb.param) > -1 })
 | 
					        state.filterDb.list = state.filterDb.cache.filter((a) => {
 | 
				
			||||||
 | 
					            return String(a).toLowerCase().indexOf(state.filterDb.param) > -1;
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
    } else {
 | 
					    } else {
 | 
				
			||||||
        state.filterDb.list = state.filterDb.cache;
 | 
					        state.filterDb.list = state.filterDb.cache;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					};
 | 
				
			||||||
 | 
					 | 
				
			||||||
// 打开编辑表
 | 
					 | 
				
			||||||
const openEditTable = async (row: any) => {
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    state.tableCreateDialog.visible = true
 | 
					 | 
				
			||||||
    state.tableCreateDialog.activeName = '1'
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if (row === false) {
 | 
					 | 
				
			||||||
        state.tableCreateDialog.data = { edit: false, row: {}, indexs: [], columns: [] }
 | 
					 | 
				
			||||||
        state.tableCreateDialog.title = '创建表'
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if (row.tableName) {
 | 
					 | 
				
			||||||
        state.tableCreateDialog.title = '修改表'
 | 
					 | 
				
			||||||
        let indexs = await dbApi.tableIndex.request({
 | 
					 | 
				
			||||||
            id: state.chooseId,
 | 
					 | 
				
			||||||
            db: state.db,
 | 
					 | 
				
			||||||
            tableName: row.tableName,
 | 
					 | 
				
			||||||
        });
 | 
					 | 
				
			||||||
        let columns = await dbApi.columnMetadata.request({
 | 
					 | 
				
			||||||
            id: state.chooseId,
 | 
					 | 
				
			||||||
            db: state.db,
 | 
					 | 
				
			||||||
            tableName: row.tableName,
 | 
					 | 
				
			||||||
        });
 | 
					 | 
				
			||||||
        state.tableCreateDialog.data = { edit: true, row, indexs, columns }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
</script>
 | 
					</script>
 | 
				
			||||||
<style lang="scss">
 | 
					<style lang="scss"></style>
 | 
				
			||||||
 | 
					 | 
				
			||||||
</style>
 | 
					 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										168
									
								
								mayfly_go_web/src/views/ops/db/DbSqlExecLog.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										168
									
								
								mayfly_go_web/src/views/ops/db/DbSqlExecLog.vue
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,168 @@
 | 
				
			|||||||
 | 
					<template>
 | 
				
			||||||
 | 
					    <div class="db-sql-exec-log">
 | 
				
			||||||
 | 
					        <page-table
 | 
				
			||||||
 | 
					            height="100%"
 | 
				
			||||||
 | 
					            ref="sqlExecDialogPageTableRef"
 | 
				
			||||||
 | 
					            :query="queryConfig"
 | 
				
			||||||
 | 
					            v-model:query-form="query"
 | 
				
			||||||
 | 
					            :data="data"
 | 
				
			||||||
 | 
					            :columns="columns"
 | 
				
			||||||
 | 
					            :total="total"
 | 
				
			||||||
 | 
					            v-model:page-size="query.pageSize"
 | 
				
			||||||
 | 
					            v-model:page-num="query.pageNum"
 | 
				
			||||||
 | 
					            @pageChange="searchSqlExecLog()"
 | 
				
			||||||
 | 
					        >
 | 
				
			||||||
 | 
					            <template #dbSelect>
 | 
				
			||||||
 | 
					                <el-select v-model="query.db" placeholder="请选择数据库" style="width: 200px" filterable clearable>
 | 
				
			||||||
 | 
					                    <el-option v-for="item in dbs" :key="item" :label="`${item}`" :value="item"> </el-option>
 | 
				
			||||||
 | 
					                </el-select>
 | 
				
			||||||
 | 
					            </template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            <template #action="{ data }">
 | 
				
			||||||
 | 
					                <el-link
 | 
				
			||||||
 | 
					                    v-if="data.type == DbSqlExecTypeEnum.Update.value || data.type == DbSqlExecTypeEnum.Delete.value"
 | 
				
			||||||
 | 
					                    type="primary"
 | 
				
			||||||
 | 
					                    plain
 | 
				
			||||||
 | 
					                    size="small"
 | 
				
			||||||
 | 
					                    :underline="false"
 | 
				
			||||||
 | 
					                    @click="onShowRollbackSql(data)"
 | 
				
			||||||
 | 
					                >
 | 
				
			||||||
 | 
					                    还原SQL</el-link
 | 
				
			||||||
 | 
					                >
 | 
				
			||||||
 | 
					            </template>
 | 
				
			||||||
 | 
					        </page-table>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        <el-dialog width="55%" :title="`还原SQL`" v-model="rollbackSqlDialog.visible">
 | 
				
			||||||
 | 
					            <el-input type="textarea" :autosize="{ minRows: 15, maxRows: 30 }" v-model="rollbackSqlDialog.sql" size="small"> </el-input>
 | 
				
			||||||
 | 
					        </el-dialog>
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<script lang="ts" setup>
 | 
				
			||||||
 | 
					import { ref, toRefs,watch, reactive, computed, onMounted, defineAsyncComponent } from 'vue';
 | 
				
			||||||
 | 
					import { dbApi } from './api';
 | 
				
			||||||
 | 
					import { DbSqlExecTypeEnum } from './enums';
 | 
				
			||||||
 | 
					import PageTable from '@/components/pagetable/PageTable.vue';
 | 
				
			||||||
 | 
					import { TableColumn, TableQuery } from '@/components/pagetable';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const props = defineProps({
 | 
				
			||||||
 | 
					    dbId: {
 | 
				
			||||||
 | 
					        type: [Number],
 | 
				
			||||||
 | 
					        required: true,
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    dbs: {
 | 
				
			||||||
 | 
					        type: [Array<String>],
 | 
				
			||||||
 | 
					        required: true,
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const queryConfig = [
 | 
				
			||||||
 | 
					    TableQuery.slot('db', '数据库', 'dbSelect'),
 | 
				
			||||||
 | 
					    TableQuery.text('table', '表名'),
 | 
				
			||||||
 | 
					    TableQuery.select('type', '操作类型').setOptions(Object.values(DbSqlExecTypeEnum)),
 | 
				
			||||||
 | 
					];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const columns = [
 | 
				
			||||||
 | 
					    TableColumn.new('db', '数据库'),
 | 
				
			||||||
 | 
					    TableColumn.new('table', '表'),
 | 
				
			||||||
 | 
					    TableColumn.new('type', '类型').typeTag(DbSqlExecTypeEnum).setAddWidth(10),
 | 
				
			||||||
 | 
					    TableColumn.new('creator', '执行人'),
 | 
				
			||||||
 | 
					    TableColumn.new('sql', 'SQL').canBeautify(),
 | 
				
			||||||
 | 
					    TableColumn.new('oldValue', '原值').canBeautify(),
 | 
				
			||||||
 | 
					    TableColumn.new('createTime', '执行时间').isTime(),
 | 
				
			||||||
 | 
					    TableColumn.new('remark', '备注'),
 | 
				
			||||||
 | 
					    TableColumn.new('action', '操作').isSlot().setMinWidth(90).fixedRight().alignCenter(),
 | 
				
			||||||
 | 
					];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const state = reactive({
 | 
				
			||||||
 | 
					    data: [],
 | 
				
			||||||
 | 
					    total: 0,
 | 
				
			||||||
 | 
					    dbs: [],
 | 
				
			||||||
 | 
					    query: {
 | 
				
			||||||
 | 
					        dbId: 0,
 | 
				
			||||||
 | 
					        db: '',
 | 
				
			||||||
 | 
					        table: '',
 | 
				
			||||||
 | 
					        type: null,
 | 
				
			||||||
 | 
					        pageNum: 1,
 | 
				
			||||||
 | 
					        pageSize: 10,
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    rollbackSqlDialog: {
 | 
				
			||||||
 | 
					        visible: false,
 | 
				
			||||||
 | 
					        sql: '',
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    filterDb: {
 | 
				
			||||||
 | 
					        param: '',
 | 
				
			||||||
 | 
					        cache: [],
 | 
				
			||||||
 | 
					        list: [],
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const { data, query, total, rollbackSqlDialog } = toRefs(state);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					onMounted(async () => {
 | 
				
			||||||
 | 
					    searchSqlExecLog();
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					watch(props, async (newValue: any) => {
 | 
				
			||||||
 | 
					    await searchSqlExecLog();
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const searchSqlExecLog = async () => {
 | 
				
			||||||
 | 
					    state.query.dbId = props.dbId
 | 
				
			||||||
 | 
					    const res = await dbApi.getSqlExecs.request(state.query);
 | 
				
			||||||
 | 
					    state.data = res.list;
 | 
				
			||||||
 | 
					    state.total = res.total;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const onShowRollbackSql = async (sqlExecLog: any) => {
 | 
				
			||||||
 | 
					    const columns = await dbApi.columnMetadata.request({ id: sqlExecLog.dbId, db: sqlExecLog.db, tableName: sqlExecLog.table });
 | 
				
			||||||
 | 
					    const primaryKey = getPrimaryKey(columns);
 | 
				
			||||||
 | 
					    const oldValue = JSON.parse(sqlExecLog.oldValue);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const rollbackSqls = [];
 | 
				
			||||||
 | 
					    if (sqlExecLog.type == DbSqlExecTypeEnum.Update.value) {
 | 
				
			||||||
 | 
					        for (let ov of oldValue) {
 | 
				
			||||||
 | 
					            const setItems = [];
 | 
				
			||||||
 | 
					            for (let key in ov) {
 | 
				
			||||||
 | 
					                if (key == primaryKey) {
 | 
				
			||||||
 | 
					                    continue;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                setItems.push(`${key} = ${wrapValue(ov[key])}`);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            rollbackSqls.push(`UPDATE ${sqlExecLog.table} SET ${setItems.join(', ')} WHERE ${primaryKey} = ${wrapValue(ov[primaryKey])};`);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    } else if (sqlExecLog.type == DbSqlExecTypeEnum.Delete.value) {
 | 
				
			||||||
 | 
					        const columnNames = columns.map((c: any) => c.columnName);
 | 
				
			||||||
 | 
					        for (let ov of oldValue) {
 | 
				
			||||||
 | 
					            const values = [];
 | 
				
			||||||
 | 
					            for (let column of columnNames) {
 | 
				
			||||||
 | 
					                values.push(wrapValue(ov[column]));
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            rollbackSqls.push(`INSERT INTO ${sqlExecLog.table} (${columnNames.join(', ')}) VALUES (${values.join(', ')});`);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    state.rollbackSqlDialog.sql = rollbackSqls.join('\n');
 | 
				
			||||||
 | 
					    state.rollbackSqlDialog.visible = true;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const getPrimaryKey = (columns: any) => {
 | 
				
			||||||
 | 
					    const col = columns.find((c: any) => c.columnKey == 'PRI');
 | 
				
			||||||
 | 
					    if (col) {
 | 
				
			||||||
 | 
					        return col.columnName;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    return columns[0].columnName;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * 包装值,如果值类型为number则直接返回,其他则需要使用''包装
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					const wrapValue = (val: any) => {
 | 
				
			||||||
 | 
					    if (typeof val == 'number') {
 | 
				
			||||||
 | 
					        return val;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    return `'${val}'`;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					</script>
 | 
				
			||||||
 | 
					<style lang="scss"></style>
 | 
				
			||||||
							
								
								
									
										215
									
								
								mayfly_go_web/src/views/ops/db/InstanceEdit.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										215
									
								
								mayfly_go_web/src/views/ops/db/InstanceEdit.vue
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,215 @@
 | 
				
			|||||||
 | 
					<template>
 | 
				
			||||||
 | 
					    <div>
 | 
				
			||||||
 | 
					        <el-dialog :title="title" v-model="dialogVisible" :before-close="cancel" :close-on-click-modal="false" :destroy-on-close="true" width="38%">
 | 
				
			||||||
 | 
					            <el-form :model="form" ref="dbForm" :rules="rules" label-width="auto">
 | 
				
			||||||
 | 
					                <el-tabs v-model="tabActiveName">
 | 
				
			||||||
 | 
					                    <el-tab-pane label="基础信息" name="basic">
 | 
				
			||||||
 | 
					                        <el-form-item prop="name" label="别名:" required>
 | 
				
			||||||
 | 
					                            <el-input v-model.trim="form.name" placeholder="请输入数据库别名" auto-complete="off"></el-input>
 | 
				
			||||||
 | 
					                        </el-form-item>
 | 
				
			||||||
 | 
					                        <el-form-item prop="type" label="类型:" required>
 | 
				
			||||||
 | 
					                            <el-select style="width: 100%" v-model="form.type" placeholder="请选择数据库类型">
 | 
				
			||||||
 | 
					                                <el-option key="item.id" label="mysql" value="mysql"> </el-option>
 | 
				
			||||||
 | 
					                                <el-option key="item.id" label="postgres" value="postgres"> </el-option>
 | 
				
			||||||
 | 
					                            </el-select>
 | 
				
			||||||
 | 
					                        </el-form-item>
 | 
				
			||||||
 | 
					                        <el-form-item prop="host" label="host:" required>
 | 
				
			||||||
 | 
					                            <el-col :span="18">
 | 
				
			||||||
 | 
					                                <el-input :disabled="form.id !== undefined" v-model.trim="form.host" placeholder="请输入主机ip" auto-complete="off"></el-input>
 | 
				
			||||||
 | 
					                            </el-col>
 | 
				
			||||||
 | 
					                            <el-col style="text-align: center" :span="1">:</el-col>
 | 
				
			||||||
 | 
					                            <el-col :span="5">
 | 
				
			||||||
 | 
					                                <el-input type="number" v-model.number="form.port" placeholder="请输入端口"></el-input>
 | 
				
			||||||
 | 
					                            </el-col>
 | 
				
			||||||
 | 
					                        </el-form-item>
 | 
				
			||||||
 | 
					                        <el-form-item prop="username" label="用户名:" required>
 | 
				
			||||||
 | 
					                            <el-input v-model.trim="form.username" placeholder="请输入用户名"></el-input>
 | 
				
			||||||
 | 
					                        </el-form-item>
 | 
				
			||||||
 | 
					                        <el-form-item prop="password" label="密码:">
 | 
				
			||||||
 | 
					                            <el-input type="password" show-password v-model.trim="form.password" placeholder="请输入密码" autocomplete="new-password">
 | 
				
			||||||
 | 
					                                <template v-if="form.id && form.id != 0" #suffix>
 | 
				
			||||||
 | 
					                                    <el-popover @hide="pwd = ''" placement="right" title="原密码" :width="200" trigger="click" :content="pwd">
 | 
				
			||||||
 | 
					                                        <template #reference>
 | 
				
			||||||
 | 
					                                            <el-link v-auth="'db:instance:save'" @click="getDbPwd" :underline="false" type="primary" class="mr5"
 | 
				
			||||||
 | 
					                                                >原密码
 | 
				
			||||||
 | 
					                                            </el-link>
 | 
				
			||||||
 | 
					                                        </template>
 | 
				
			||||||
 | 
					                                    </el-popover>
 | 
				
			||||||
 | 
					                                </template>
 | 
				
			||||||
 | 
					                            </el-input>
 | 
				
			||||||
 | 
					                        </el-form-item>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                        <el-form-item prop="remark" label="备注:">
 | 
				
			||||||
 | 
					                            <el-input v-model.trim="form.remark" auto-complete="off" type="textarea"></el-input>
 | 
				
			||||||
 | 
					                        </el-form-item>
 | 
				
			||||||
 | 
					                    </el-tab-pane>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    <el-tab-pane label="其他配置" name="other">
 | 
				
			||||||
 | 
					                        <el-form-item prop="params" label="连接参数:">
 | 
				
			||||||
 | 
					                            <el-input v-model.trim="form.params" placeholder="其他连接参数,形如: key1=value1&key2=value2">
 | 
				
			||||||
 | 
					                                <template #suffix>
 | 
				
			||||||
 | 
					                                    <el-link
 | 
				
			||||||
 | 
					                                        target="_blank"
 | 
				
			||||||
 | 
					                                        href="https://github.com/go-sql-driver/mysql#parameters"
 | 
				
			||||||
 | 
					                                        :underline="false"
 | 
				
			||||||
 | 
					                                        type="primary"
 | 
				
			||||||
 | 
					                                        class="mr5"
 | 
				
			||||||
 | 
					                                        >参数参考</el-link
 | 
				
			||||||
 | 
					                                    >
 | 
				
			||||||
 | 
					                                </template>
 | 
				
			||||||
 | 
					                            </el-input>
 | 
				
			||||||
 | 
					                        </el-form-item>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                        <el-form-item prop="sshTunnelMachineId" label="SSH隧道:">
 | 
				
			||||||
 | 
					                            <ssh-tunnel-select v-model="form.sshTunnelMachineId" />
 | 
				
			||||||
 | 
					                        </el-form-item>
 | 
				
			||||||
 | 
					                    </el-tab-pane>
 | 
				
			||||||
 | 
					                </el-tabs>
 | 
				
			||||||
 | 
					            </el-form>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            <template #footer>
 | 
				
			||||||
 | 
					                <div class="dialog-footer">
 | 
				
			||||||
 | 
					                    <el-button @click="cancel()">取 消</el-button>
 | 
				
			||||||
 | 
					                    <el-button type="primary" :loading="btnLoading" @click="btnOk">确 定</el-button>
 | 
				
			||||||
 | 
					                </div>
 | 
				
			||||||
 | 
					            </template>
 | 
				
			||||||
 | 
					        </el-dialog>
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<script lang="ts" setup>
 | 
				
			||||||
 | 
					import { toRefs, reactive, watch, ref } from 'vue';
 | 
				
			||||||
 | 
					import { dbApi } from './api';
 | 
				
			||||||
 | 
					import { ElMessage } from 'element-plus';
 | 
				
			||||||
 | 
					import { notBlank } from '@/common/assert';
 | 
				
			||||||
 | 
					import { RsaEncrypt } from '@/common/rsa';
 | 
				
			||||||
 | 
					import SshTunnelSelect from '../component/SshTunnelSelect.vue';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const props = defineProps({
 | 
				
			||||||
 | 
					    visible: {
 | 
				
			||||||
 | 
					        type: Boolean,
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    data: {
 | 
				
			||||||
 | 
					        type: [Boolean, Object],
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    title: {
 | 
				
			||||||
 | 
					        type: String,
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					//定义事件
 | 
				
			||||||
 | 
					const emit = defineEmits(['update:visible', 'cancel', 'val-change']);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const rules = {
 | 
				
			||||||
 | 
					    name: [
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            required: true,
 | 
				
			||||||
 | 
					            message: '请输入别名',
 | 
				
			||||||
 | 
					            trigger: ['change', 'blur'],
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					    ],
 | 
				
			||||||
 | 
					    type: [
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            required: true,
 | 
				
			||||||
 | 
					            message: '请选择数据库类型',
 | 
				
			||||||
 | 
					            trigger: ['change', 'blur'],
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					    ],
 | 
				
			||||||
 | 
					    host: [
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            required: true,
 | 
				
			||||||
 | 
					            message: '请输入主机ip和port',
 | 
				
			||||||
 | 
					            trigger: ['change', 'blur'],
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					    ],
 | 
				
			||||||
 | 
					    username: [
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            required: true,
 | 
				
			||||||
 | 
					            message: '请输入用户名',
 | 
				
			||||||
 | 
					            trigger: ['change', 'blur'],
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					    ],
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const dbForm: any = ref(null);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const state = reactive({
 | 
				
			||||||
 | 
					    dialogVisible: false,
 | 
				
			||||||
 | 
					    tabActiveName: 'basic',
 | 
				
			||||||
 | 
					    form: {
 | 
				
			||||||
 | 
					        id: null,
 | 
				
			||||||
 | 
					        type: null,
 | 
				
			||||||
 | 
					        name: null,
 | 
				
			||||||
 | 
					        host: '',
 | 
				
			||||||
 | 
					        port: 3306,
 | 
				
			||||||
 | 
					        username: null,
 | 
				
			||||||
 | 
					        password: null,
 | 
				
			||||||
 | 
					        params: null,
 | 
				
			||||||
 | 
					        remark: '',
 | 
				
			||||||
 | 
					        sshTunnelMachineId: null as any,
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    // 原密码
 | 
				
			||||||
 | 
					    pwd: '',
 | 
				
			||||||
 | 
					    // 原用户名
 | 
				
			||||||
 | 
					    oldUserName: null,
 | 
				
			||||||
 | 
					    btnLoading: false,
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const { dialogVisible, tabActiveName, form, pwd, btnLoading } = toRefs(state);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					watch(props, (newValue: any) => {
 | 
				
			||||||
 | 
					    state.dialogVisible = newValue.visible;
 | 
				
			||||||
 | 
					    if (!state.dialogVisible) {
 | 
				
			||||||
 | 
					        return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    state.tabActiveName = 'basic';
 | 
				
			||||||
 | 
					    if (newValue.data) {
 | 
				
			||||||
 | 
					        state.form = { ...newValue.data };
 | 
				
			||||||
 | 
					        state.oldUserName = state.form.username;
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					        state.form = { port: 3306 } as any;
 | 
				
			||||||
 | 
					        state.oldUserName = null;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const getDbPwd = async () => {
 | 
				
			||||||
 | 
					    state.pwd = await dbApi.getInstancePwd.request({ id: state.form.id });
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const btnOk = async () => {
 | 
				
			||||||
 | 
					    if (!state.form.id) {
 | 
				
			||||||
 | 
					        notBlank(state.form.password, '新增操作,密码不可为空');
 | 
				
			||||||
 | 
					    } else if (state.form.username != state.oldUserName) {
 | 
				
			||||||
 | 
					        notBlank(state.form.password, '已修改用户名,请输入密码');
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    dbForm.value.validate(async (valid: boolean) => {
 | 
				
			||||||
 | 
					        if (valid) {
 | 
				
			||||||
 | 
					            const reqForm = { ...state.form };
 | 
				
			||||||
 | 
					            reqForm.password = await RsaEncrypt(reqForm.password);
 | 
				
			||||||
 | 
					            if (!state.form.sshTunnelMachineId) {
 | 
				
			||||||
 | 
					                reqForm.sshTunnelMachineId = -1;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            dbApi.saveInstance.request(reqForm).then(() => {
 | 
				
			||||||
 | 
					                ElMessage.success('保存成功');
 | 
				
			||||||
 | 
					                emit('val-change', state.form);
 | 
				
			||||||
 | 
					                state.btnLoading = true;
 | 
				
			||||||
 | 
					                setTimeout(() => {
 | 
				
			||||||
 | 
					                    state.btnLoading = false;
 | 
				
			||||||
 | 
					                }, 1000);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                cancel();
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            ElMessage.error('请正确填写信息');
 | 
				
			||||||
 | 
					            return false;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const cancel = () => {
 | 
				
			||||||
 | 
					    emit('update:visible', false);
 | 
				
			||||||
 | 
					    emit('cancel');
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					</script>
 | 
				
			||||||
 | 
					<style lang="scss"></style>
 | 
				
			||||||
							
								
								
									
										179
									
								
								mayfly_go_web/src/views/ops/db/InstanceList.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										179
									
								
								mayfly_go_web/src/views/ops/db/InstanceList.vue
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,179 @@
 | 
				
			|||||||
 | 
					<template>
 | 
				
			||||||
 | 
					    <div class="db-list">
 | 
				
			||||||
 | 
					        <page-table
 | 
				
			||||||
 | 
					            ref="pageTableRef"
 | 
				
			||||||
 | 
					            :query="queryConfig"
 | 
				
			||||||
 | 
					            v-model:query-form="query"
 | 
				
			||||||
 | 
					            :show-selection="true"
 | 
				
			||||||
 | 
					            v-model:selection-data="state.selectionData"
 | 
				
			||||||
 | 
					            :data="datas"
 | 
				
			||||||
 | 
					            :columns="columns"
 | 
				
			||||||
 | 
					            :total="total"
 | 
				
			||||||
 | 
					            v-model:page-size="query.pageSize"
 | 
				
			||||||
 | 
					            v-model:page-num="query.pageNum"
 | 
				
			||||||
 | 
					            @pageChange="search()"
 | 
				
			||||||
 | 
					        >
 | 
				
			||||||
 | 
					            <template #queryRight>
 | 
				
			||||||
 | 
					                <el-button v-auth="perms.saveInstance" type="primary" icon="plus" @click="editInstance(false)">添加</el-button>
 | 
				
			||||||
 | 
					                <el-button v-auth="perms.delInstance" :disabled="selectionData.length < 1" @click="deleteInstance()" type="danger" icon="delete"
 | 
				
			||||||
 | 
					                    >删除</el-button
 | 
				
			||||||
 | 
					                >
 | 
				
			||||||
 | 
					            </template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            <template #more="{ data }">
 | 
				
			||||||
 | 
					                <el-button @click="showInfo(data)" link>详情</el-button>
 | 
				
			||||||
 | 
					            </template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            <template #action="{ data }">
 | 
				
			||||||
 | 
					                <el-button v-if="actionBtns[perms.saveInstance]" @click="editInstance(data)" type="primary" link>编辑</el-button>
 | 
				
			||||||
 | 
					            </template>
 | 
				
			||||||
 | 
					        </page-table>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        <el-dialog v-model="infoDialog.visible">
 | 
				
			||||||
 | 
					            <el-descriptions title="详情" :column="3" border>
 | 
				
			||||||
 | 
					                <el-descriptions-item :span="2" label="名称">{{ infoDialog.data.name }}</el-descriptions-item>
 | 
				
			||||||
 | 
					                <el-descriptions-item :span="1" label="id">{{ infoDialog.data.id }}</el-descriptions-item>
 | 
				
			||||||
 | 
					                <el-descriptions-item :span="2" label="主机">{{ infoDialog.data.host }}</el-descriptions-item>
 | 
				
			||||||
 | 
					                <el-descriptions-item :span="1" label="端口">{{ infoDialog.data.port }}</el-descriptions-item>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                <el-descriptions-item :span="2" label="用户名">{{ infoDialog.data.username }}</el-descriptions-item>
 | 
				
			||||||
 | 
					                <el-descriptions-item :span="1" label="类型">{{ infoDialog.data.type }}</el-descriptions-item>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                <el-descriptions-item :span="3" label="连接参数">{{ infoDialog.data.params }}</el-descriptions-item>
 | 
				
			||||||
 | 
					                <el-descriptions-item :span="3" label="备注">{{ infoDialog.data.remark }}</el-descriptions-item>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                <el-descriptions-item :span="3" label="SSH隧道">{{ infoDialog.data.sshTunnelMachineId > 0 ? '是' : '否' }} </el-descriptions-item>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                <el-descriptions-item :span="2" label="创建时间">{{ dateFormat(infoDialog.data.createTime) }} </el-descriptions-item>
 | 
				
			||||||
 | 
					                <el-descriptions-item :span="1" label="创建者">{{ infoDialog.data.creator }}</el-descriptions-item>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                <el-descriptions-item :span="2" label="更新时间">{{ dateFormat(infoDialog.data.updateTime) }} </el-descriptions-item>
 | 
				
			||||||
 | 
					                <el-descriptions-item :span="1" label="修改者">{{ infoDialog.data.modifier }}</el-descriptions-item>
 | 
				
			||||||
 | 
					            </el-descriptions>
 | 
				
			||||||
 | 
					        </el-dialog>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        <instance-edit
 | 
				
			||||||
 | 
					            @val-change="valChange"
 | 
				
			||||||
 | 
					            :title="instanceEditDialog.title"
 | 
				
			||||||
 | 
					            v-model:visible="instanceEditDialog.visible"
 | 
				
			||||||
 | 
					            v-model:data="instanceEditDialog.data"
 | 
				
			||||||
 | 
					        ></instance-edit>
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<script lang="ts" setup>
 | 
				
			||||||
 | 
					import { ref, toRefs, reactive, onMounted, defineAsyncComponent } from 'vue';
 | 
				
			||||||
 | 
					import { ElMessage, ElMessageBox } from 'element-plus';
 | 
				
			||||||
 | 
					import { dbApi } from './api';
 | 
				
			||||||
 | 
					import { dateFormat } from '@/common/utils/date';
 | 
				
			||||||
 | 
					import PageTable from '@/components/pagetable/PageTable.vue';
 | 
				
			||||||
 | 
					import { TableColumn, TableQuery } from '@/components/pagetable';
 | 
				
			||||||
 | 
					import { hasPerms } from '@/components/auth/auth';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const InstanceEdit = defineAsyncComponent(() => import('./InstanceEdit.vue'));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const perms = {
 | 
				
			||||||
 | 
					    saveInstance: 'db:instance:save',
 | 
				
			||||||
 | 
					    delInstance: 'db:instance:del',
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const queryConfig = [TableQuery.text('name', '名称')];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const columns = ref([
 | 
				
			||||||
 | 
					    TableColumn.new('name', '名称'),
 | 
				
			||||||
 | 
					    TableColumn.new('host', 'host:port').setFormatFunc((data: any, _prop: string) => `${data.host}:${data.port}`),
 | 
				
			||||||
 | 
					    TableColumn.new('type', '类型'),
 | 
				
			||||||
 | 
					    TableColumn.new('username', '用户名'),
 | 
				
			||||||
 | 
					    TableColumn.new('remark', '备注'),
 | 
				
			||||||
 | 
					    TableColumn.new('more', '更多').isSlot().setMinWidth(50).fixedRight(),
 | 
				
			||||||
 | 
					]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// 该用户拥有的的操作列按钮权限
 | 
				
			||||||
 | 
					const actionBtns = hasPerms([perms.saveInstance]);
 | 
				
			||||||
 | 
					const actionColumn = TableColumn.new('action', '操作').isSlot().setMinWidth(65).fixedRight().alignCenter();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const pageTableRef: any = ref(null);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const state = reactive({
 | 
				
			||||||
 | 
					    row: {},
 | 
				
			||||||
 | 
					    dbId: 0,
 | 
				
			||||||
 | 
					    db: '',
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * 选中的数据
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    selectionData: [],
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * 查询条件
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    query: {
 | 
				
			||||||
 | 
					        name: null,
 | 
				
			||||||
 | 
					        pageNum: 1,
 | 
				
			||||||
 | 
					        pageSize: 10,
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    datas: [],
 | 
				
			||||||
 | 
					    total: 0,
 | 
				
			||||||
 | 
					    infoDialog: {
 | 
				
			||||||
 | 
					        visible: false,
 | 
				
			||||||
 | 
					        data: null as any,
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    instanceEditDialog: {
 | 
				
			||||||
 | 
					        visible: false,
 | 
				
			||||||
 | 
					        data: null as any,
 | 
				
			||||||
 | 
					        title: '新增数据库实例',
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const { selectionData, query, datas, total, infoDialog, instanceEditDialog } = toRefs(state);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					onMounted(async () => {
 | 
				
			||||||
 | 
					    if (Object.keys(actionBtns).length > 0) {
 | 
				
			||||||
 | 
					        columns.value.push(actionColumn);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    search();
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const search = async () => {
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					        pageTableRef.value.loading(true);
 | 
				
			||||||
 | 
					        let res: any = await dbApi.instances.request(state.query);
 | 
				
			||||||
 | 
					        state.datas = res.list;
 | 
				
			||||||
 | 
					        state.total = res.total;
 | 
				
			||||||
 | 
					    } finally {
 | 
				
			||||||
 | 
					        pageTableRef.value.loading(false);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const showInfo = (info: any) => {
 | 
				
			||||||
 | 
					    state.infoDialog.data = info;
 | 
				
			||||||
 | 
					    state.infoDialog.visible = true;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const editInstance = async (data: any) => {
 | 
				
			||||||
 | 
					    if (!data) {
 | 
				
			||||||
 | 
					        state.instanceEditDialog.data = null;
 | 
				
			||||||
 | 
					        state.instanceEditDialog.title = '新增数据库实例';
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					        state.instanceEditDialog.data = data;
 | 
				
			||||||
 | 
					        state.instanceEditDialog.title = '修改数据库实例';
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    state.instanceEditDialog.visible = true;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const valChange = () => {
 | 
				
			||||||
 | 
					    search();
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const deleteInstance = async () => {
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					        await ElMessageBox.confirm(`确定删除数据库实例【${state.selectionData.map((x: any) => x.name).join(', ')}】?`, '提示', {
 | 
				
			||||||
 | 
					            confirmButtonText: '确定',
 | 
				
			||||||
 | 
					            cancelButtonText: '取消',
 | 
				
			||||||
 | 
					            type: 'warning',
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					        await dbApi.deleteInstance.request({ id: state.selectionData.map((x: any) => x.id).join(',') });
 | 
				
			||||||
 | 
					        ElMessage.success('删除成功');
 | 
				
			||||||
 | 
					        search();
 | 
				
			||||||
 | 
					    } catch (err) {}
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					</script>
 | 
				
			||||||
 | 
					<style lang="scss"></style>
 | 
				
			||||||
@@ -1,13 +1,13 @@
 | 
				
			|||||||
<template>
 | 
					<template>
 | 
				
			||||||
    <div>
 | 
					    <div>
 | 
				
			||||||
        <el-row>
 | 
					        <el-row class="mb5">
 | 
				
			||||||
            <el-col :span="4">
 | 
					            <el-col :span="4">
 | 
				
			||||||
                <el-button type="primary" icon="plus"
 | 
					                <el-button type="primary" icon="plus" @click="addQueryTab({ id: nowDbInst.id, dbs: nowDbInst.databases.split(' ') }, state.db)" size="small"
 | 
				
			||||||
                    @click="addQueryTab({ id: nowDbInst.id, dbs: nowDbInst.databases }, state.db)"
 | 
					                    >新建查询</el-button
 | 
				
			||||||
                    size="small">新建查询</el-button>
 | 
					                >
 | 
				
			||||||
            </el-col>
 | 
					            </el-col>
 | 
				
			||||||
            <el-col :span="20" v-if="state.db">
 | 
					            <el-col :span="20" v-if="state.db">
 | 
				
			||||||
                <el-descriptions :column="4" size="small" border style="height: 10px">
 | 
					                <el-descriptions :column="4" size="small" border style="height: 10px" class="ml5">
 | 
				
			||||||
                    <el-descriptions-item label-align="right" label="tag">{{ nowDbInst.tagPath }}</el-descriptions-item>
 | 
					                    <el-descriptions-item label-align="right" label="tag">{{ nowDbInst.tagPath }}</el-descriptions-item>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    <el-descriptions-item label="实例" label-align="right">
 | 
					                    <el-descriptions-item label="实例" label-align="right">
 | 
				
			||||||
@@ -23,30 +23,29 @@
 | 
				
			|||||||
            </el-col>
 | 
					            </el-col>
 | 
				
			||||||
        </el-row>
 | 
					        </el-row>
 | 
				
			||||||
        <el-row type="flex">
 | 
					        <el-row type="flex">
 | 
				
			||||||
            <el-col :span="4" style="border-left: 1px solid #eee; margin-top: 10px">
 | 
					            <el-col :span="4">
 | 
				
			||||||
                <tag-tree ref="tagTreeRef" @node-click="nodeClick" :load="loadNode"
 | 
					                <tag-tree
 | 
				
			||||||
                    :load-contextmenu-items="getContextmenuItems" @current-contextmenu-click="onCurrentContextmenuClick"
 | 
					                    ref="tagTreeRef"
 | 
				
			||||||
                    :height="state.tagTreeHeight">
 | 
					                    @node-click="nodeClick"
 | 
				
			||||||
 | 
					                    :load="loadNode"
 | 
				
			||||||
 | 
					                    :load-contextmenu-items="getContextmenuItems"
 | 
				
			||||||
 | 
					                    @current-contextmenu-click="onCurrentContextmenuClick"
 | 
				
			||||||
 | 
					                    :height="state.tagTreeHeight"
 | 
				
			||||||
 | 
					                >
 | 
				
			||||||
                    <template #prefix="{ data }">
 | 
					                    <template #prefix="{ data }">
 | 
				
			||||||
                        <span v-if="data.type == NodeType.DbInst">
 | 
					                        <span v-if="data.type == NodeType.DbInst">
 | 
				
			||||||
                            <el-popover placement="right-start" title="数据库实例信息" trigger="hover" :width="210">
 | 
					                            <el-popover placement="right-start" title="数据库实例信息" trigger="hover" :width="210">
 | 
				
			||||||
                                <template #reference>
 | 
					                                <template #reference>
 | 
				
			||||||
                                    <SvgIcon v-if="data.params.type === 'mysql'" name="iconfont icon-op-mysql" :size="18" />
 | 
					                                    <SvgIcon v-if="data.params.type === 'mysql'" name="iconfont icon-op-mysql" :size="18" />
 | 
				
			||||||
                                    <SvgIcon v-if="data.params.type === 'postgres'" name="iconfont icon-op-postgres"
 | 
					                                    <SvgIcon v-if="data.params.type === 'postgres'" name="iconfont icon-op-postgres" :size="18" />
 | 
				
			||||||
                                        :size="18" />
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
                                    <SvgIcon name="InfoFilled" v-else />
 | 
					                                    <SvgIcon name="InfoFilled" v-else />
 | 
				
			||||||
                                </template>
 | 
					                                </template>
 | 
				
			||||||
                                <template #default>
 | 
					                                <template #default>
 | 
				
			||||||
                                    <el-form class="instances-pop-form" label-width="55px" :size="'small'">
 | 
					                                    <el-form class="instances-pop-form" label-width="55px" :size="'small'">
 | 
				
			||||||
                                        <el-form-item label="类型:">{{ data.params.type }}</el-form-item>
 | 
					                                        <el-form-item label="类型:">{{ data.params.type }}</el-form-item>
 | 
				
			||||||
                                        <el-form-item label="链接:">{{ data.params.host }}:{{
 | 
					                                        <el-form-item label="名称:">{{ data.params.name }}</el-form-item>
 | 
				
			||||||
                                            data.params.port
 | 
					                                        <el-form-item v-if="data.params.remark" label="备注:">{{ data.params.remark }}</el-form-item>
 | 
				
			||||||
                                        }}</el-form-item>
 | 
					 | 
				
			||||||
                                        <el-form-item label="用户:">{{ data.params.username }}</el-form-item>
 | 
					 | 
				
			||||||
                                        <el-form-item v-if="data.params.remark" label="备注:">{{
 | 
					 | 
				
			||||||
                                            data.params.remark
 | 
					 | 
				
			||||||
                                        }}</el-form-item>
 | 
					 | 
				
			||||||
                                    </el-form>
 | 
					                                    </el-form>
 | 
				
			||||||
                                </template>
 | 
					                                </template>
 | 
				
			||||||
                            </el-popover>
 | 
					                            </el-popover>
 | 
				
			||||||
@@ -56,28 +55,32 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
                        <SvgIcon name="Calendar" v-if="data.type == NodeType.TableMenu" color="#409eff" />
 | 
					                        <SvgIcon name="Calendar" v-if="data.type == NodeType.TableMenu" color="#409eff" />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                        <el-tooltip v-if="data.type == NodeType.Table" effect="customized"
 | 
					                        <el-tooltip v-if="data.type == NodeType.Table" effect="customized" :content="data.params.tableComment" placement="top-end">
 | 
				
			||||||
                            :content="data.params.tableComment" placement="top-end">
 | 
					 | 
				
			||||||
                            <SvgIcon name="Calendar" color="#409eff" />
 | 
					                            <SvgIcon name="Calendar" color="#409eff" />
 | 
				
			||||||
                        </el-tooltip>
 | 
					                        </el-tooltip>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                        <SvgIcon name="Files" v-if="data.type == NodeType.SqlMenu || data.type == NodeType.Sql"
 | 
					                        <SvgIcon name="Files" v-if="data.type == NodeType.SqlMenu || data.type == NodeType.Sql" color="#f56c6c" />
 | 
				
			||||||
                            color="#f56c6c" />
 | 
					 | 
				
			||||||
                    </template>
 | 
					                    </template>
 | 
				
			||||||
                </tag-tree>
 | 
					                </tag-tree>
 | 
				
			||||||
            </el-col>
 | 
					            </el-col>
 | 
				
			||||||
            <el-col :span="20">
 | 
					            <el-col :span="20">
 | 
				
			||||||
                <el-container id="data-exec" style="border-left: 1px solid #eee; margin-top: 10px">
 | 
					                <el-container id="data-exec" class="mt5 ml5">
 | 
				
			||||||
                    <el-tabs @tab-remove="onRemoveTab" @tab-change="onTabChange" style="width: 100%"
 | 
					                    <el-tabs @tab-remove="onRemoveTab" @tab-change="onTabChange" style="width: 100%" v-model="state.activeName">
 | 
				
			||||||
                        v-model="state.activeName">
 | 
					                        <el-tab-pane closable v-for="dt in state.tabs.values()" :key="dt.key" :label="dt.key" :name="dt.key">
 | 
				
			||||||
 | 
					                            <table-data
 | 
				
			||||||
 | 
					                                v-if="dt.type === TabType.TableData"
 | 
				
			||||||
 | 
					                                @gen-insert-sql="onGenerateInsertSql"
 | 
				
			||||||
 | 
					                                :data="dt"
 | 
				
			||||||
 | 
					                                :table-height="state.dataTabsTableHeight"
 | 
				
			||||||
 | 
					                            ></table-data>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                        <el-tab-pane closable v-for="dt in state.tabs.values()" :key="dt.key" :label="dt.key"
 | 
					                            <query
 | 
				
			||||||
                            :name="dt.key">
 | 
					                                v-else
 | 
				
			||||||
                            <table-data v-if="dt.type === TabType.TableData" @gen-insert-sql="onGenerateInsertSql"
 | 
					                                @save-sql-success="reloadSqls"
 | 
				
			||||||
                                :data="dt" :table-height="state.dataTabsTableHeight"></table-data>
 | 
					                                @delete-sql-success="deleteSqlScript(dt)"
 | 
				
			||||||
 | 
					                                :data="dt"
 | 
				
			||||||
                            <query v-else @save-sql-success="reloadSqls" @delete-sql-success="deleteSqlScript(dt)"
 | 
					                                :editor-height="state.editorHeight"
 | 
				
			||||||
                                :data="dt" :editor-height="state.editorHeight">
 | 
					                            >
 | 
				
			||||||
                            </query>
 | 
					                            </query>
 | 
				
			||||||
                        </el-tab-pane>
 | 
					                        </el-tab-pane>
 | 
				
			||||||
                    </el-tabs>
 | 
					                    </el-tabs>
 | 
				
			||||||
@@ -85,8 +88,7 @@
 | 
				
			|||||||
            </el-col>
 | 
					            </el-col>
 | 
				
			||||||
        </el-row>
 | 
					        </el-row>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        <el-dialog @close="state.genSqlDialog.visible = false" v-model="state.genSqlDialog.visible" title="SQL"
 | 
					        <el-dialog @close="state.genSqlDialog.visible = false" v-model="state.genSqlDialog.visible" title="SQL" width="1000px">
 | 
				
			||||||
            width="1000px">
 | 
					 | 
				
			||||||
            <el-input v-model="state.genSqlDialog.sql" type="textarea" rows="20" />
 | 
					            <el-input v-model="state.genSqlDialog.sql" type="textarea" rows="20" />
 | 
				
			||||||
        </el-dialog>
 | 
					        </el-dialog>
 | 
				
			||||||
    </div>
 | 
					    </div>
 | 
				
			||||||
@@ -96,34 +98,29 @@
 | 
				
			|||||||
import { defineAsyncComponent, onMounted, reactive, ref, toRefs } from 'vue';
 | 
					import { defineAsyncComponent, onMounted, reactive, ref, toRefs } from 'vue';
 | 
				
			||||||
import { ElMessage } from 'element-plus';
 | 
					import { ElMessage } from 'element-plus';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import { language as sqlLanguage } from 'monaco-editor/esm/vs/basic-languages/mysql/mysql.js';
 | 
					import { DbInst, TabInfo, TabType } from './db';
 | 
				
			||||||
import * as monaco from 'monaco-editor';
 | 
					 | 
				
			||||||
import { editor, languages, Position } from 'monaco-editor';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import { DbInst, TabInfo, TabType } from './db'
 | 
					 | 
				
			||||||
import { TagTreeNode } from '../component/tag';
 | 
					import { TagTreeNode } from '../component/tag';
 | 
				
			||||||
import TagTree from '../component/TagTree.vue';
 | 
					import TagTree from '../component/TagTree.vue';
 | 
				
			||||||
import { dbApi } from './api';
 | 
					import { dbApi } from './api';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const Query = defineAsyncComponent(() => import('./component/tab/Query.vue'));
 | 
					const Query = defineAsyncComponent(() => import('./component/tab/Query.vue'));
 | 
				
			||||||
const TableData = defineAsyncComponent(() => import('./component/tab/TableData.vue'));
 | 
					const TableData = defineAsyncComponent(() => import('./component/tab/TableData.vue'));
 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * 树节点类型
 | 
					 * 树节点类型
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
class NodeType {
 | 
					class NodeType {
 | 
				
			||||||
    static DbInst = 1
 | 
					    static DbInst = 1;
 | 
				
			||||||
    static Db = 2
 | 
					    static Db = 2;
 | 
				
			||||||
    static TableMenu = 3;
 | 
					    static TableMenu = 3;
 | 
				
			||||||
    static SqlMenu = 4;
 | 
					    static SqlMenu = 4;
 | 
				
			||||||
    static Table = 5;
 | 
					    static Table = 5;
 | 
				
			||||||
    static Sql = 6;
 | 
					    static Sql = 6;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
class ContextmenuClickId {
 | 
					class ContextmenuClickId {
 | 
				
			||||||
    static ReloadTable = 0
 | 
					    static ReloadTable = 0;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const tagTreeRef: any = ref(null)
 | 
					const tagTreeRef: any = ref(null);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const tabs: Map<string, TabInfo> = new Map();
 | 
					const tabs: Map<string, TabInfo> = new Map();
 | 
				
			||||||
const state = reactive({
 | 
					const state = reactive({
 | 
				
			||||||
@@ -144,12 +141,10 @@ const state = reactive({
 | 
				
			|||||||
    },
 | 
					    },
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const {
 | 
					const { nowDbInst } = toRefs(state);
 | 
				
			||||||
    nowDbInst,
 | 
					 | 
				
			||||||
} = toRefs(state);
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
onMounted(() => {
 | 
					onMounted(() => {
 | 
				
			||||||
    self.completionItemProvider?.dispose()
 | 
					    self.completionItemProvider?.dispose();
 | 
				
			||||||
    setHeight();
 | 
					    setHeight();
 | 
				
			||||||
    // 监听浏览器窗口大小变化,更新对应组件高度
 | 
					    // 监听浏览器窗口大小变化,更新对应组件高度
 | 
				
			||||||
    window.onresize = () => setHeight();
 | 
					    window.onresize = () => setHeight();
 | 
				
			||||||
@@ -160,25 +155,25 @@ onMounted(() => {
 | 
				
			|||||||
 */
 | 
					 */
 | 
				
			||||||
const setHeight = () => {
 | 
					const setHeight = () => {
 | 
				
			||||||
    state.editorHeight = window.innerHeight - 518 + 'px';
 | 
					    state.editorHeight = window.innerHeight - 518 + 'px';
 | 
				
			||||||
    state.dataTabsTableHeight = window.innerHeight - 219 - 36 + 'px';
 | 
					    state.dataTabsTableHeight = window.innerHeight - 256 + 'px';
 | 
				
			||||||
    state.tagTreeHeight = window.innerHeight - 165 + 'px';
 | 
					    state.tagTreeHeight = window.innerHeight - 165 + 'px';
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
* instmap; tagPaht -> info[]
 | 
					 * instmap; tagPaht -> info[]
 | 
				
			||||||
*/
 | 
					 */
 | 
				
			||||||
const instMap: Map<string, any[]> = new Map();
 | 
					const instMap: Map<string, any[]> = new Map();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const getInsts = async () => {
 | 
					const getInsts = async () => {
 | 
				
			||||||
    const res = await dbApi.dbs.request({ pageNum: 1, pageSize: 1000, })
 | 
					    const res = await dbApi.dbs.request({ pageNum: 1, pageSize: 1000 });
 | 
				
			||||||
    if (!res.total) return
 | 
					    if (!res.total) return;
 | 
				
			||||||
    for (const db of res.list) {
 | 
					    for (const db of res.list) {
 | 
				
			||||||
        const tagPath = db.tagPath;
 | 
					        const tagPath = db.tagPath;
 | 
				
			||||||
        let dbInsts = instMap.get(tagPath) || [];
 | 
					        let dbInsts = instMap.get(tagPath) || [];
 | 
				
			||||||
        dbInsts.push(db);
 | 
					        dbInsts.push(db);
 | 
				
			||||||
        instMap.set(tagPath, dbInsts?.sort());
 | 
					        instMap.set(tagPath, dbInsts?.sort());
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * 加载树节点
 | 
					 * 加载树节点
 | 
				
			||||||
@@ -203,7 +198,7 @@ const loadNode = async (node: any) => {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    // 点击tagPath -> 加载数据库实例信息列表
 | 
					    // 点击tagPath -> 加载数据库实例信息列表
 | 
				
			||||||
    if (nodeType === TagTreeNode.TagPath) {
 | 
					    if (nodeType === TagTreeNode.TagPath) {
 | 
				
			||||||
        const dbInfos = instMap.get(data.key)
 | 
					        const dbInfos = instMap.get(data.key);
 | 
				
			||||||
        return dbInfos?.map((x: any) => {
 | 
					        return dbInfos?.map((x: any) => {
 | 
				
			||||||
            return new TagTreeNode(`${data.key}.${x.id}`, x.name, NodeType.DbInst).withParams(x);
 | 
					            return new TagTreeNode(`${data.key}.${x.id}`, x.name, NodeType.DbInst).withParams(x);
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
@@ -212,6 +207,7 @@ const loadNode = async (node: any) => {
 | 
				
			|||||||
    // 点击数据库实例 -> 加载库列表
 | 
					    // 点击数据库实例 -> 加载库列表
 | 
				
			||||||
    if (nodeType === NodeType.DbInst) {
 | 
					    if (nodeType === NodeType.DbInst) {
 | 
				
			||||||
        const dbs = params.database.split(' ')?.sort();
 | 
					        const dbs = params.database.split(' ')?.sort();
 | 
				
			||||||
 | 
					        console.log(dbs);
 | 
				
			||||||
        return dbs.map((x: any) => {
 | 
					        return dbs.map((x: any) => {
 | 
				
			||||||
            return new TagTreeNode(`${data.key}.${x}`, x, NodeType.Db).withParams({
 | 
					            return new TagTreeNode(`${data.key}.${x}`, x, NodeType.Db).withParams({
 | 
				
			||||||
                tagPath: params.tagPath,
 | 
					                tagPath: params.tagPath,
 | 
				
			||||||
@@ -219,15 +215,17 @@ const loadNode = async (node: any) => {
 | 
				
			|||||||
                name: params.name,
 | 
					                name: params.name,
 | 
				
			||||||
                type: params.type,
 | 
					                type: params.type,
 | 
				
			||||||
                dbs: dbs,
 | 
					                dbs: dbs,
 | 
				
			||||||
                db: x
 | 
					                db: x,
 | 
				
			||||||
            })
 | 
					            });
 | 
				
			||||||
        })
 | 
					        });
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // 点击数据库 -> 加载 表&Sql 菜单
 | 
					    // 点击数据库 -> 加载 表&Sql 菜单
 | 
				
			||||||
    if (nodeType === NodeType.Db) {
 | 
					    if (nodeType === NodeType.Db) {
 | 
				
			||||||
        return [new TagTreeNode(`${params.id}.${params.db}.table-menu`, '表', NodeType.TableMenu).withParams(params),
 | 
					        return [
 | 
				
			||||||
        new TagTreeNode(getSqlMenuNodeKey(params.id, params.db), 'SQL', NodeType.SqlMenu).withParams(params)];
 | 
					            new TagTreeNode(`${params.id}.${params.db}.table-menu`, '表', NodeType.TableMenu).withParams(params),
 | 
				
			||||||
 | 
					            new TagTreeNode(getSqlMenuNodeKey(params.id, params.db), 'SQL', NodeType.SqlMenu).withParams(params),
 | 
				
			||||||
 | 
					        ];
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // 点击表菜单 -> 加载表列表
 | 
					    // 点击表菜单 -> 加载表列表
 | 
				
			||||||
@@ -262,30 +260,28 @@ const nodeClick = async (data: any) => {
 | 
				
			|||||||
    if (dataType === NodeType.Sql) {
 | 
					    if (dataType === NodeType.Sql) {
 | 
				
			||||||
        await addQueryTab({ id: params.id, nodeKey: nodeKey, dbs: params.dbs }, params.db, params.sqlName);
 | 
					        await addQueryTab({ id: params.id, nodeKey: nodeKey, dbs: params.dbs }, params.db, params.sqlName);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const getContextmenuItems = (data: any) => {
 | 
					const getContextmenuItems = (data: any) => {
 | 
				
			||||||
    const dataType = data.type;
 | 
					    const dataType = data.type;
 | 
				
			||||||
    if (dataType === NodeType.TableMenu) {
 | 
					    if (dataType === NodeType.TableMenu) {
 | 
				
			||||||
        return [
 | 
					        return [{ contextMenuClickId: ContextmenuClickId.ReloadTable, txt: '刷新', icon: 'RefreshRight' }];
 | 
				
			||||||
            { contextMenuClickId: ContextmenuClickId.ReloadTable, txt: '刷新', icon: 'RefreshRight' }
 | 
					 | 
				
			||||||
        ]
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    return [];
 | 
					    return [];
 | 
				
			||||||
}
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// 当前右击菜单点击事件
 | 
					// 当前右击菜单点击事件
 | 
				
			||||||
const onCurrentContextmenuClick = (clickData: any) => {
 | 
					const onCurrentContextmenuClick = (clickData: any) => {
 | 
				
			||||||
    const clickId = clickData.id;
 | 
					    const clickId = clickData.id;
 | 
				
			||||||
    if (clickId == ContextmenuClickId.ReloadTable) {
 | 
					    if (clickId == ContextmenuClickId.ReloadTable) {
 | 
				
			||||||
        reloadTables(clickData.item.key)
 | 
					        reloadTables(clickData.item.key);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const getTables = async (params: any) => {
 | 
					const getTables = async (params: any) => {
 | 
				
			||||||
    const { id, db } = params;
 | 
					    const { id, db } = params;
 | 
				
			||||||
    let tables = await DbInst.getInst(id).loadTables(db, state.reloadStatus);
 | 
					    let tables = await DbInst.getInst(id).loadTables(db, state.reloadStatus);
 | 
				
			||||||
    state.reloadStatus = false
 | 
					    state.reloadStatus = false;
 | 
				
			||||||
    return tables.map((x: any) => {
 | 
					    return tables.map((x: any) => {
 | 
				
			||||||
        return new TagTreeNode(`${id}.${db}.${x.tableName}`, x.tableName, NodeType.Table).withIsLeaf(true).withParams({
 | 
					        return new TagTreeNode(`${id}.${db}.${x.tableName}`, x.tableName, NodeType.Table).withIsLeaf(true).withParams({
 | 
				
			||||||
            id,
 | 
					            id,
 | 
				
			||||||
@@ -293,8 +289,8 @@ const getTables = async (params: any) => {
 | 
				
			|||||||
            tableName: x.tableName,
 | 
					            tableName: x.tableName,
 | 
				
			||||||
            tableComment: x.tableComment,
 | 
					            tableComment: x.tableComment,
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
    })
 | 
					    });
 | 
				
			||||||
}
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * 加载用户保存的sql脚本
 | 
					 * 加载用户保存的sql脚本
 | 
				
			||||||
@@ -303,7 +299,7 @@ const getTables = async (params: any) => {
 | 
				
			|||||||
 * @param schema
 | 
					 * @param schema
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
const loadSqls = async (id: any, db: string, dbs: any) => {
 | 
					const loadSqls = async (id: any, db: string, dbs: any) => {
 | 
				
			||||||
    const sqls = await dbApi.getSqlNames.request({ id: id, db: db, })
 | 
					    const sqls = await dbApi.getSqlNames.request({ id: id, db: db });
 | 
				
			||||||
    return sqls.map((x: any) => {
 | 
					    return sqls.map((x: any) => {
 | 
				
			||||||
        return new TagTreeNode(`${id}.${db}.${x.name}`, x.name, NodeType.Sql).withIsLeaf(true).withParams({
 | 
					        return new TagTreeNode(`${id}.${db}.${x.name}`, x.name, NodeType.Sql).withIsLeaf(true).withParams({
 | 
				
			||||||
            id,
 | 
					            id,
 | 
				
			||||||
@@ -312,17 +308,17 @@ const loadSqls = async (id: any, db: string, dbs: any) => {
 | 
				
			|||||||
            sqlName: x.name,
 | 
					            sqlName: x.name,
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
}
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// 选择数据库
 | 
					// 选择数据库
 | 
				
			||||||
const changeSchema = (inst: any, schema: string) => {
 | 
					const changeSchema = (inst: any, schema: string) => {
 | 
				
			||||||
    state.nowDbInst = DbInst.getOrNewInst(inst);
 | 
					    state.nowDbInst = DbInst.getOrNewInst(inst);
 | 
				
			||||||
    state.db = schema;
 | 
					    state.db = schema;
 | 
				
			||||||
}
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// 加载选中的表数据,即新增表数据操作tab
 | 
					// 加载选中的表数据,即新增表数据操作tab
 | 
				
			||||||
const loadTableData = async (inst: any, schema: string, tableName: string) => {
 | 
					const loadTableData = async (inst: any, schema: string, tableName: string) => {
 | 
				
			||||||
    changeSchema(inst, schema)
 | 
					    changeSchema(inst, schema);
 | 
				
			||||||
    if (tableName == '') {
 | 
					    if (tableName == '') {
 | 
				
			||||||
        return;
 | 
					        return;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@@ -341,16 +337,16 @@ const loadTableData = async (inst: any, schema: string, tableName: string) => {
 | 
				
			|||||||
    tab.db = schema;
 | 
					    tab.db = schema;
 | 
				
			||||||
    tab.type = TabType.TableData;
 | 
					    tab.type = TabType.TableData;
 | 
				
			||||||
    tab.params = {
 | 
					    tab.params = {
 | 
				
			||||||
        table: tableName
 | 
					        table: tableName,
 | 
				
			||||||
    }
 | 
					    };
 | 
				
			||||||
    state.tabs.set(label, tab)
 | 
					    state.tabs.set(label, tab);
 | 
				
			||||||
}
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// 新建查询panel
 | 
					// 新建查询panel
 | 
				
			||||||
const addQueryTab = async (inst: any, db: string, sqlName: string = '') => {
 | 
					const addQueryTab = async (inst: any, db: string, sqlName: string = '') => {
 | 
				
			||||||
    if (!db || !inst.id) {
 | 
					    if (!db || !inst.id) {
 | 
				
			||||||
        ElMessage.warning('请选择数据库实例及对应的schema')
 | 
					        ElMessage.warning('请选择数据库实例及对应的schema');
 | 
				
			||||||
        return
 | 
					        return;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const dbId = inst.id;
 | 
					    const dbId = inst.id;
 | 
				
			||||||
@@ -364,7 +360,7 @@ const addQueryTab = async (inst: any, db: string, sqlName: string = '') => {
 | 
				
			|||||||
            if (v.type == TabType.Query && !v.params.sqlName) {
 | 
					            if (v.type == TabType.Query && !v.params.sqlName) {
 | 
				
			||||||
                count++;
 | 
					                count++;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        })
 | 
					        });
 | 
				
			||||||
        label = `新查询${count}:${dbId}:${db}`;
 | 
					        label = `新查询${count}:${dbId}:${db}`;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    state.activeName = label;
 | 
					    state.activeName = label;
 | 
				
			||||||
@@ -381,16 +377,15 @@ const addQueryTab = async (inst: any, db: string, sqlName: string = '') => {
 | 
				
			|||||||
    tab.params = {
 | 
					    tab.params = {
 | 
				
			||||||
        sqlName: sqlName,
 | 
					        sqlName: sqlName,
 | 
				
			||||||
        dbs: inst.dbs,
 | 
					        dbs: inst.dbs,
 | 
				
			||||||
    }
 | 
					    };
 | 
				
			||||||
    state.tabs.set(label, tab)
 | 
					    state.tabs.set(label, tab);
 | 
				
			||||||
    registerSqlCompletionItemProvider();
 | 
					};
 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
const onRemoveTab = (targetName: string) => {
 | 
					const onRemoveTab = (targetName: string) => {
 | 
				
			||||||
    let activeName = state.activeName;
 | 
					    let activeName = state.activeName;
 | 
				
			||||||
    const tabNames = [...state.tabs.keys()]
 | 
					    const tabNames = [...state.tabs.keys()];
 | 
				
			||||||
    for (let i = 0; i < tabNames.length; i++) {
 | 
					    for (let i = 0; i < tabNames.length; i++) {
 | 
				
			||||||
        const tabName = tabNames[i]
 | 
					        const tabName = tabNames[i];
 | 
				
			||||||
        if (tabName !== targetName) {
 | 
					        if (tabName !== targetName) {
 | 
				
			||||||
            continue;
 | 
					            continue;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
@@ -414,7 +409,7 @@ const onTabChange = () => {
 | 
				
			|||||||
    const nowTab = state.tabs.get(state.activeName);
 | 
					    const nowTab = state.tabs.get(state.activeName);
 | 
				
			||||||
    state.nowDbInst = DbInst.getInst(nowTab?.dbId);
 | 
					    state.nowDbInst = DbInst.getInst(nowTab?.dbId);
 | 
				
			||||||
    state.db = nowTab?.db as string;
 | 
					    state.db = nowTab?.db as string;
 | 
				
			||||||
}
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const onGenerateInsertSql = async (sql: string) => {
 | 
					const onGenerateInsertSql = async (sql: string) => {
 | 
				
			||||||
    state.genSqlDialog.sql = sql;
 | 
					    state.genSqlDialog.sql = sql;
 | 
				
			||||||
@@ -423,293 +418,21 @@ const onGenerateInsertSql = async (sql: string) => {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
const reloadSqls = (dbId: number, db: string) => {
 | 
					const reloadSqls = (dbId: number, db: string) => {
 | 
				
			||||||
    tagTreeRef.value.reloadNode(getSqlMenuNodeKey(dbId, db));
 | 
					    tagTreeRef.value.reloadNode(getSqlMenuNodeKey(dbId, db));
 | 
				
			||||||
}
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const deleteSqlScript = (ti: TabInfo) => {
 | 
					const deleteSqlScript = (ti: TabInfo) => {
 | 
				
			||||||
    reloadSqls(ti.dbId, ti.db);
 | 
					    reloadSqls(ti.dbId, ti.db);
 | 
				
			||||||
    onRemoveTab(ti.key);
 | 
					    onRemoveTab(ti.key);
 | 
				
			||||||
}
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const getSqlMenuNodeKey = (dbId: number, db: string) => {
 | 
					const getSqlMenuNodeKey = (dbId: number, db: string) => {
 | 
				
			||||||
    return `${dbId}.${db}.sql-menu`
 | 
					    return `${dbId}.${db}.sql-menu`;
 | 
				
			||||||
}
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const reloadTables = (nodeKey: string) => {
 | 
					const reloadTables = (nodeKey: string) => {
 | 
				
			||||||
    state.reloadStatus = true
 | 
					    state.reloadStatus = true;
 | 
				
			||||||
    tagTreeRef.value.reloadNode(nodeKey);
 | 
					    tagTreeRef.value.reloadNode(nodeKey);
 | 
				
			||||||
}
 | 
					};
 | 
				
			||||||
 | 
					 | 
				
			||||||
const registerSqlCompletionItemProvider = () => {
 | 
					 | 
				
			||||||
    // 参考 https://microsoft.github.io/monaco-editor/playground.html#extending-language-services-completion-provider-example
 | 
					 | 
				
			||||||
    self.completionItemProvider = self.completionItemProvider || monaco.languages.registerCompletionItemProvider('sql', {
 | 
					 | 
				
			||||||
        triggerCharacters: ['.', ' '],
 | 
					 | 
				
			||||||
        provideCompletionItems: async (model: editor.ITextModel, position: Position): Promise<languages.CompletionList | null | undefined> => {
 | 
					 | 
				
			||||||
            let word = model.getWordUntilPosition(position);
 | 
					 | 
				
			||||||
            const nowTab = state.tabs.get(state.activeName);
 | 
					 | 
				
			||||||
            if (!nowTab) {
 | 
					 | 
				
			||||||
                return;
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            const { db, dbId } = nowTab;
 | 
					 | 
				
			||||||
            const dbInst = DbInst.getInst(dbId);
 | 
					 | 
				
			||||||
            const { lineNumber, column } = position
 | 
					 | 
				
			||||||
            const { startColumn, endColumn } = word
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            // 当前行文本
 | 
					 | 
				
			||||||
            let lineContent = model.getLineContent(lineNumber);
 | 
					 | 
				
			||||||
            // 注释行不需要代码提示
 | 
					 | 
				
			||||||
            if (lineContent.startsWith('--')) {
 | 
					 | 
				
			||||||
                return { suggestions: [] }
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            let range = {
 | 
					 | 
				
			||||||
                startLineNumber: lineNumber,
 | 
					 | 
				
			||||||
                endLineNumber: lineNumber,
 | 
					 | 
				
			||||||
                startColumn,
 | 
					 | 
				
			||||||
                endColumn,
 | 
					 | 
				
			||||||
            };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            //  光标前文本
 | 
					 | 
				
			||||||
            const textBeforePointer = model.getValueInRange({
 | 
					 | 
				
			||||||
                startLineNumber: lineNumber,
 | 
					 | 
				
			||||||
                startColumn: 0,
 | 
					 | 
				
			||||||
                endLineNumber: lineNumber,
 | 
					 | 
				
			||||||
                endColumn: column
 | 
					 | 
				
			||||||
            })
 | 
					 | 
				
			||||||
            const textBeforePointerMulti = model.getValueInRange({
 | 
					 | 
				
			||||||
                startLineNumber: 1,
 | 
					 | 
				
			||||||
                startColumn: 0,
 | 
					 | 
				
			||||||
                endLineNumber: lineNumber,
 | 
					 | 
				
			||||||
                endColumn: column
 | 
					 | 
				
			||||||
            })
 | 
					 | 
				
			||||||
            // 光标后文本
 | 
					 | 
				
			||||||
            const textAfterPointerMulti = model.getValueInRange({
 | 
					 | 
				
			||||||
                startLineNumber: lineNumber,
 | 
					 | 
				
			||||||
                startColumn: column,
 | 
					 | 
				
			||||||
                endLineNumber: model.getLineCount(),
 | 
					 | 
				
			||||||
                endColumn: model.getLineMaxColumn(model.getLineCount())
 | 
					 | 
				
			||||||
            })
 | 
					 | 
				
			||||||
            // // const nextTokens = textAfterPointer.trim().split(/\s+/)
 | 
					 | 
				
			||||||
            // // const nextToken = nextTokens[0].toLowerCase()
 | 
					 | 
				
			||||||
            const tokens = textBeforePointer.trim().split(/\s+/)
 | 
					 | 
				
			||||||
            const lastToken = tokens[tokens.length - 1].toLowerCase()
 | 
					 | 
				
			||||||
            const secondToken = tokens.length > 2 && tokens[tokens.length - 2].toLowerCase() || ''
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            const dbs = nowTab.params?.dbs?.split(' ') || [];
 | 
					 | 
				
			||||||
            // console.log("光标前文本:=>" + textBeforePointerMulti)
 | 
					 | 
				
			||||||
            // console.log("最后输入的:=>" + lastToken)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            let suggestions: languages.CompletionItem[] = []
 | 
					 | 
				
			||||||
            const tables = await dbInst.loadTables(db);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            async function hintTableColumns(tableName: any, db: any) {
 | 
					 | 
				
			||||||
                let dbHits = await dbInst.loadDbHints(db)
 | 
					 | 
				
			||||||
                let columns = dbHits[tableName]
 | 
					 | 
				
			||||||
                let suggestions: languages.CompletionItem[] = []
 | 
					 | 
				
			||||||
                columns?.forEach((a: string, index: any) => {
 | 
					 | 
				
			||||||
                    // 字段数据格式  字段名 字段注释,  如: create_time  [datetime][创建时间]
 | 
					 | 
				
			||||||
                    const nameAndComment = a.split("  ")
 | 
					 | 
				
			||||||
                    const fieldName = nameAndComment[0]
 | 
					 | 
				
			||||||
                    suggestions.push({
 | 
					 | 
				
			||||||
                        label: {
 | 
					 | 
				
			||||||
                            label: a,
 | 
					 | 
				
			||||||
                            description: 'column'
 | 
					 | 
				
			||||||
                        },
 | 
					 | 
				
			||||||
                        kind: monaco.languages.CompletionItemKind.Property,
 | 
					 | 
				
			||||||
                        detail: '', // 不显示detail, 否则选中时备注等会被遮挡
 | 
					 | 
				
			||||||
                        insertText: fieldName + ' ', // create_time
 | 
					 | 
				
			||||||
                        range,
 | 
					 | 
				
			||||||
                        sortText: 100 + index + '' // 使用表字段声明顺序排序,排序需为字符串类型
 | 
					 | 
				
			||||||
                    });
 | 
					 | 
				
			||||||
                })
 | 
					 | 
				
			||||||
                return suggestions
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            if (lastToken.indexOf('.') > -1 || secondToken.indexOf('.') > -1) {
 | 
					 | 
				
			||||||
                // 如果是.触发代码提示,则进行【 库.表名联想 】 或 【 表别名.表字段联想 】
 | 
					 | 
				
			||||||
                let str = lastToken.substring(0, lastToken.lastIndexOf('.'))
 | 
					 | 
				
			||||||
                if (lastToken.trim().startsWith('.')) {
 | 
					 | 
				
			||||||
                    str = secondToken
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                // 库.表名联想
 | 
					 | 
				
			||||||
                if (dbs && dbs.filter((a: any) => a === str)?.length > 0) {
 | 
					 | 
				
			||||||
                    let tables = await dbInst.loadTables(str)
 | 
					 | 
				
			||||||
                    let suggestions: languages.CompletionItem[] = []
 | 
					 | 
				
			||||||
                    for (let item of tables) {
 | 
					 | 
				
			||||||
                        const { tableName, tableComment } = item
 | 
					 | 
				
			||||||
                        suggestions.push({
 | 
					 | 
				
			||||||
                            label: {
 | 
					 | 
				
			||||||
                                label: tableName + (tableComment ? ' - ' + tableComment : ''),
 | 
					 | 
				
			||||||
                                description: 'table'
 | 
					 | 
				
			||||||
                            },
 | 
					 | 
				
			||||||
                            kind: monaco.languages.CompletionItemKind.File,
 | 
					 | 
				
			||||||
                            insertText: tableName,
 | 
					 | 
				
			||||||
                            range
 | 
					 | 
				
			||||||
                        });
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
                    return { suggestions }
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                let sql = textBeforePointerMulti.split(';')[textBeforePointerMulti.split(';').length - 1] + textAfterPointerMulti.split(';')[0];
 | 
					 | 
				
			||||||
                // 表别名.表字段联想
 | 
					 | 
				
			||||||
                let tableInfo = getTableByAlias(sql, db, str)
 | 
					 | 
				
			||||||
                if (tableInfo.tableName) {
 | 
					 | 
				
			||||||
                    let tableName = tableInfo.tableName
 | 
					 | 
				
			||||||
                    let db = tableInfo.dbName;
 | 
					 | 
				
			||||||
                    // 取出表名并提示
 | 
					 | 
				
			||||||
                    let suggestions = await hintTableColumns(tableName, db);
 | 
					 | 
				
			||||||
                    if (suggestions.length > 0) {
 | 
					 | 
				
			||||||
                        return { suggestions };
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
                return { suggestions: [] }
 | 
					 | 
				
			||||||
            } else {
 | 
					 | 
				
			||||||
                // 如果sql里含有表名,则提示表字段
 | 
					 | 
				
			||||||
                let mat = textBeforePointerMulti.match(/from\n*\s+\n*(\w+)\n*\s+\n*/i)
 | 
					 | 
				
			||||||
                if (mat && mat.length > 1) {
 | 
					 | 
				
			||||||
                    let tableName = mat[1]
 | 
					 | 
				
			||||||
                    // 取出表名并提示
 | 
					 | 
				
			||||||
                    let addSuggestions = await hintTableColumns(tableName, db);
 | 
					 | 
				
			||||||
                    if (addSuggestions.length > 0) {
 | 
					 | 
				
			||||||
                        suggestions = suggestions.concat(addSuggestions)
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            // 表名联想
 | 
					 | 
				
			||||||
            tables.forEach((tableMeta: any) => {
 | 
					 | 
				
			||||||
                const { tableName, tableComment } = tableMeta;
 | 
					 | 
				
			||||||
                suggestions.push({
 | 
					 | 
				
			||||||
                    label: {
 | 
					 | 
				
			||||||
                        label: tableName + ' - ' + tableComment,
 | 
					 | 
				
			||||||
                        description: 'table'
 | 
					 | 
				
			||||||
                    },
 | 
					 | 
				
			||||||
                    kind: monaco.languages.CompletionItemKind.File,
 | 
					 | 
				
			||||||
                    detail: tableComment,
 | 
					 | 
				
			||||||
                    insertText: tableName + ' ',
 | 
					 | 
				
			||||||
                    range
 | 
					 | 
				
			||||||
                });
 | 
					 | 
				
			||||||
            });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            // mysql关键字
 | 
					 | 
				
			||||||
            sqlLanguage.keywords.forEach((item: any) => {
 | 
					 | 
				
			||||||
                suggestions.push({
 | 
					 | 
				
			||||||
                    label: {
 | 
					 | 
				
			||||||
                        label: item,
 | 
					 | 
				
			||||||
                        description: 'keyword'
 | 
					 | 
				
			||||||
                    },
 | 
					 | 
				
			||||||
                    kind: monaco.languages.CompletionItemKind.Keyword,
 | 
					 | 
				
			||||||
                    insertText: item,
 | 
					 | 
				
			||||||
                    range
 | 
					 | 
				
			||||||
                });
 | 
					 | 
				
			||||||
            })
 | 
					 | 
				
			||||||
            // 操作符
 | 
					 | 
				
			||||||
            sqlLanguage.operators.forEach((item: any) => {
 | 
					 | 
				
			||||||
                suggestions.push({
 | 
					 | 
				
			||||||
                    label: {
 | 
					 | 
				
			||||||
                        label: item,
 | 
					 | 
				
			||||||
                        description: 'opt'
 | 
					 | 
				
			||||||
                    },
 | 
					 | 
				
			||||||
                    kind: monaco.languages.CompletionItemKind.Operator,
 | 
					 | 
				
			||||||
                    insertText: item,
 | 
					 | 
				
			||||||
                    range
 | 
					 | 
				
			||||||
                });
 | 
					 | 
				
			||||||
            })
 | 
					 | 
				
			||||||
            // 内置函数
 | 
					 | 
				
			||||||
            sqlLanguage.builtinFunctions.forEach((item: any) => {
 | 
					 | 
				
			||||||
                suggestions.push({
 | 
					 | 
				
			||||||
                    label: {
 | 
					 | 
				
			||||||
                        label: item,
 | 
					 | 
				
			||||||
                        description: 'func'
 | 
					 | 
				
			||||||
                    },
 | 
					 | 
				
			||||||
                    kind: monaco.languages.CompletionItemKind.Function,
 | 
					 | 
				
			||||||
                    insertText: item,
 | 
					 | 
				
			||||||
                    range
 | 
					 | 
				
			||||||
                });
 | 
					 | 
				
			||||||
            })
 | 
					 | 
				
			||||||
            // 内置变量
 | 
					 | 
				
			||||||
            sqlLanguage.builtinVariables.forEach((item: string) => {
 | 
					 | 
				
			||||||
                suggestions.push({
 | 
					 | 
				
			||||||
                    label: {
 | 
					 | 
				
			||||||
                        label: item,
 | 
					 | 
				
			||||||
                        description: 'var'
 | 
					 | 
				
			||||||
                    },
 | 
					 | 
				
			||||||
                    kind: monaco.languages.CompletionItemKind.Variable,
 | 
					 | 
				
			||||||
                    insertText: item,
 | 
					 | 
				
			||||||
                    range
 | 
					 | 
				
			||||||
                });
 | 
					 | 
				
			||||||
            })
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            // 库名提示
 | 
					 | 
				
			||||||
            if (dbs && dbs.length > 0) {
 | 
					 | 
				
			||||||
                dbs.forEach((a: any) => {
 | 
					 | 
				
			||||||
                    suggestions.push({
 | 
					 | 
				
			||||||
                        label: {
 | 
					 | 
				
			||||||
                            label: a,
 | 
					 | 
				
			||||||
                            description: 'schema'
 | 
					 | 
				
			||||||
                        },
 | 
					 | 
				
			||||||
                        kind: monaco.languages.CompletionItemKind.Folder,
 | 
					 | 
				
			||||||
                        insertText: a,
 | 
					 | 
				
			||||||
                        range
 | 
					 | 
				
			||||||
                    });
 | 
					 | 
				
			||||||
                })
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            // 默认提示
 | 
					 | 
				
			||||||
            return {
 | 
					 | 
				
			||||||
                suggestions: suggestions
 | 
					 | 
				
			||||||
            };
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * 根据别名获取sql里的表名
 | 
					 | 
				
			||||||
 * @param sql sql
 | 
					 | 
				
			||||||
 * @param db 默认数据库
 | 
					 | 
				
			||||||
 * @param alias 别名
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
const getTableByAlias = (sql: string, db: string, alias: string): { dbName: string, tableName: string } => {
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // 表别名:表名
 | 
					 | 
				
			||||||
    let result = {};
 | 
					 | 
				
			||||||
    let defName = '';
 | 
					 | 
				
			||||||
    let defResult = {};
 | 
					 | 
				
			||||||
    // 正则匹配取出表名和表别名
 | 
					 | 
				
			||||||
    // 测试sql
 | 
					 | 
				
			||||||
    /*
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    `select * from database.Outvisit l
 | 
					 | 
				
			||||||
left join patient p on l.patid=p.patientid
 | 
					 | 
				
			||||||
join patstatic c on   l.patid=c.patid inner join patphone  ph  on l.patid=ph.patid
 | 
					 | 
				
			||||||
where l.name='kevin' and exsits(select 1 from pharmacywestpas pw where p.outvisitid=l.outvisitid)
 | 
					 | 
				
			||||||
unit all
 | 
					 | 
				
			||||||
select * from invisit v where`.match(/(join|from)\s+(\w*-?\w*\.?\w+)\s*(as)?\s*(\w*)/gi)
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    let match = sql.match(/(join|from)\n*\s+\n*(\w*-?\w*\.?\w+)\s*(as)?\s*(\w*)\n*/gi)
 | 
					 | 
				
			||||||
    if (match && match.length > 0) {
 | 
					 | 
				
			||||||
        match.forEach(a => {
 | 
					 | 
				
			||||||
            // 去掉前缀,取出
 | 
					 | 
				
			||||||
            let t = a.substring(5, a.length)
 | 
					 | 
				
			||||||
                .replaceAll(/\s+/g, ' ')
 | 
					 | 
				
			||||||
                .replaceAll(/\s+as\s+/gi, ' ')
 | 
					 | 
				
			||||||
                .replaceAll(/\r\n/g, ' ').trim()
 | 
					 | 
				
			||||||
                .split(/\s+/);
 | 
					 | 
				
			||||||
            let withDb = t[0].split('.');
 | 
					 | 
				
			||||||
            // 表名是 db名.表名
 | 
					 | 
				
			||||||
            let tName = withDb.length > 1 ? withDb[1] : withDb[0]
 | 
					 | 
				
			||||||
            let dbName = withDb.length > 1 ? withDb[0] : (db || '')
 | 
					 | 
				
			||||||
            if (t.length == 2) {
 | 
					 | 
				
			||||||
                // 表别名:表名
 | 
					 | 
				
			||||||
                result[t[1]] = { tableName: tName, dbName }
 | 
					 | 
				
			||||||
            } else {
 | 
					 | 
				
			||||||
                // 只有表名无别名 取第一个无别名的表为默认表
 | 
					 | 
				
			||||||
                !defName && (defResult = { tableName: tName, dbName: db })
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        })
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    return result[alias] || defResult
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
</script>
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<style lang="scss">
 | 
					<style lang="scss">
 | 
				
			||||||
@@ -735,11 +458,6 @@ select * from invisit v where`.match(/(join|from)\s+(\w*-?\w*\.?\w+)\s*(as)?\s*(
 | 
				
			|||||||
    text-align: center;
 | 
					    text-align: center;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.el-tabs__header {
 | 
					 | 
				
			||||||
    padding: 0 10px;
 | 
					 | 
				
			||||||
    background-color: #fff;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#data-exec {
 | 
					#data-exec {
 | 
				
			||||||
    min-height: calc(100vh - 155px);
 | 
					    min-height: calc(100vh - 155px);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -753,7 +471,7 @@ select * from invisit v where`.match(/(join|from)\s+(\w*-?\w*\.?\w+)\s*(as)?\s*(
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.update_field_active {
 | 
					.update_field_active {
 | 
				
			||||||
    background-color: var(--el-color-success)
 | 
					    background-color: var(--el-color-success);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.instances-pop-form {
 | 
					.instances-pop-form {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,71 +0,0 @@
 | 
				
			|||||||
<template>
 | 
					 | 
				
			||||||
    <div>
 | 
					 | 
				
			||||||
        <el-dialog :title="`${title} 详情`" v-model="dialogVisible" :before-close="cancel" width="90%">
 | 
					 | 
				
			||||||
            <el-table @cell-click="cellClick" :data="data.res">
 | 
					 | 
				
			||||||
                <el-table-column :width="200" :prop="item" :label="item" v-for="item in data.colNames" :key="item">
 | 
					 | 
				
			||||||
                </el-table-column>
 | 
					 | 
				
			||||||
            </el-table>
 | 
					 | 
				
			||||||
        </el-dialog>
 | 
					 | 
				
			||||||
    </div>
 | 
					 | 
				
			||||||
</template>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
<script lang="ts" setup>
 | 
					 | 
				
			||||||
import { watch, toRefs, reactive } from 'vue';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const props = defineProps({
 | 
					 | 
				
			||||||
    visible: {
 | 
					 | 
				
			||||||
        type: Boolean,
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    title: {
 | 
					 | 
				
			||||||
        type: String,
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    data: {
 | 
					 | 
				
			||||||
        type: Object,
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
})
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
//定义事件
 | 
					 | 
				
			||||||
const emit = defineEmits(['update:visible'])
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const state = reactive({
 | 
					 | 
				
			||||||
    dialogVisible: false,
 | 
					 | 
				
			||||||
    data: {
 | 
					 | 
				
			||||||
        res: [],
 | 
					 | 
				
			||||||
        colNames: [],
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
});
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const {
 | 
					 | 
				
			||||||
    dialogVisible,
 | 
					 | 
				
			||||||
    data,
 | 
					 | 
				
			||||||
} = toRefs(state)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
watch(props, async (newValue: any) => {
 | 
					 | 
				
			||||||
    state.dialogVisible = newValue.visible;
 | 
					 | 
				
			||||||
    state.data.res = newValue.data.res;
 | 
					 | 
				
			||||||
    state.data.colNames = newValue.data.colNames;
 | 
					 | 
				
			||||||
});
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const cellClick = (row: any, column: any, cell: any) => {
 | 
					 | 
				
			||||||
    let isDiv = cell.children[0].tagName === 'DIV';
 | 
					 | 
				
			||||||
    let text = cell.children[0].innerText;
 | 
					 | 
				
			||||||
    let div = cell.children[0];
 | 
					 | 
				
			||||||
    if (isDiv) {
 | 
					 | 
				
			||||||
        let input = document.createElement('input');
 | 
					 | 
				
			||||||
        input.setAttribute('value', text);
 | 
					 | 
				
			||||||
        cell.replaceChildren(input);
 | 
					 | 
				
			||||||
        input.focus();
 | 
					 | 
				
			||||||
        input.addEventListener('blur', () => {
 | 
					 | 
				
			||||||
            div.innerText = input.value;
 | 
					 | 
				
			||||||
            cell.replaceChildren(div);
 | 
					 | 
				
			||||||
        });
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const cancel = () => {
 | 
					 | 
				
			||||||
    emit('update:visible', false);
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
</script>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
@@ -2,27 +2,34 @@ import Api from '@/common/Api';
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
export const dbApi = {
 | 
					export const dbApi = {
 | 
				
			||||||
    // 获取权限列表
 | 
					    // 获取权限列表
 | 
				
			||||||
    dbs: Api.newGet("/dbs"),
 | 
					    dbs: Api.newGet('/dbs'),
 | 
				
			||||||
    saveDb: Api.newPost("/dbs"),
 | 
					    dbTags: Api.newGet('/dbs/tags'),
 | 
				
			||||||
    getAllDatabase: Api.newPost("/dbs/databases"),
 | 
					    saveDb: Api.newPost('/dbs'),
 | 
				
			||||||
    getDbPwd: Api.newGet("/dbs/{id}/pwd"),
 | 
					    deleteDb: Api.newDelete('/dbs/{id}'),
 | 
				
			||||||
    deleteDb: Api.newDelete("/dbs/{id}"),
 | 
					    dumpDb: Api.newPost('/dbs/{id}/dump'),
 | 
				
			||||||
    dumpDb: Api.newPost("/dbs/{id}/dump"),
 | 
					    tableInfos: Api.newGet('/dbs/{id}/t-infos'),
 | 
				
			||||||
    tableInfos: Api.newGet("/dbs/{id}/t-infos"),
 | 
					    tableIndex: Api.newGet('/dbs/{id}/t-index'),
 | 
				
			||||||
    tableIndex: Api.newGet("/dbs/{id}/t-index"),
 | 
					    tableDdl: Api.newGet('/dbs/{id}/t-create-ddl'),
 | 
				
			||||||
    tableDdl: Api.newGet("/dbs/{id}/t-create-ddl"),
 | 
					    tableMetadata: Api.newGet('/dbs/{id}/t-metadata'),
 | 
				
			||||||
    tableMetadata: Api.newGet("/dbs/{id}/t-metadata"),
 | 
					    columnMetadata: Api.newGet('/dbs/{id}/c-metadata'),
 | 
				
			||||||
    columnMetadata: Api.newGet("/dbs/{id}/c-metadata"),
 | 
					 | 
				
			||||||
    // 获取表即列提示
 | 
					    // 获取表即列提示
 | 
				
			||||||
    hintTables: Api.newGet("/dbs/{id}/hint-tables"),
 | 
					    hintTables: Api.newGet('/dbs/{id}/hint-tables'),
 | 
				
			||||||
    sqlExec: Api.newPost("/dbs/{id}/exec-sql"),
 | 
					    sqlExec: Api.newPost('/dbs/{id}/exec-sql'),
 | 
				
			||||||
    // 保存sql
 | 
					    // 保存sql
 | 
				
			||||||
    saveSql: Api.newPost("/dbs/{id}/sql"),
 | 
					    saveSql: Api.newPost('/dbs/{id}/sql'),
 | 
				
			||||||
    // 获取保存的sql
 | 
					    // 获取保存的sql
 | 
				
			||||||
    getSql: Api.newGet("/dbs/{id}/sql"),
 | 
					    getSql: Api.newGet('/dbs/{id}/sql'),
 | 
				
			||||||
    // 获取保存的sql names
 | 
					    // 获取保存的sql names
 | 
				
			||||||
    getSqlNames: Api.newGet("/dbs/{id}/sql-names"),
 | 
					    getSqlNames: Api.newGet('/dbs/{id}/sql-names'),
 | 
				
			||||||
    deleteDbSql: Api.newDelete("/dbs/{id}/sql"),
 | 
					    deleteDbSql: Api.newDelete('/dbs/{id}/sql'),
 | 
				
			||||||
    // 获取数据库sql执行记录
 | 
					    // 获取数据库sql执行记录
 | 
				
			||||||
    getSqlExecs: Api.newGet("/dbs/{dbId}/sql-execs"),
 | 
					    getSqlExecs: Api.newGet('/dbs/{dbId}/sql-execs'),
 | 
				
			||||||
}
 | 
					
 | 
				
			||||||
 | 
					    // 获取权限列表
 | 
				
			||||||
 | 
					    instances: Api.newGet('/instances'),
 | 
				
			||||||
 | 
					    getInstance: Api.newGet("/instances/{instanceId}"),
 | 
				
			||||||
 | 
					    getAllDatabase: Api.newGet('/instances/{instanceId}/databases'),
 | 
				
			||||||
 | 
					    saveInstance: Api.newPost('/instances'),
 | 
				
			||||||
 | 
					    getInstancePwd: Api.newGet('/instances/{id}/pwd'),
 | 
				
			||||||
 | 
					    deleteInstance: Api.newDelete('/instances/{id}'),
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,20 +1,42 @@
 | 
				
			|||||||
<template>
 | 
					<template>
 | 
				
			||||||
    <div>
 | 
					    <div>
 | 
				
			||||||
        <el-table @cell-dblclick="(row: any, column: any, cell: any, event: any) => cellClick(row, column, cell)"
 | 
					        <el-table
 | 
				
			||||||
            @sort-change="(sort: any) => onTableSortChange(sort)" @selection-change="onDataSelectionChange"
 | 
					            @cell-dblclick="(row: any, column: any, cell: any, event: any) => cellClick(row, column, cell)"
 | 
				
			||||||
            :data="datas" size="small" :max-height="tableHeight" v-loading="loading" element-loading-text="查询中..."
 | 
					            @sort-change="(sort: any) => onTableSortChange(sort)"
 | 
				
			||||||
            :empty-text="emptyText" stripe border class="mt5">
 | 
					            @selection-change="onDataSelectionChange"
 | 
				
			||||||
 | 
					            :data="datas"
 | 
				
			||||||
 | 
					            size="small"
 | 
				
			||||||
 | 
					            :max-height="tableHeight"
 | 
				
			||||||
 | 
					            v-loading="loading"
 | 
				
			||||||
 | 
					            element-loading-text="查询中..."
 | 
				
			||||||
 | 
					            :empty-text="emptyText"
 | 
				
			||||||
 | 
					            highlight-current-row
 | 
				
			||||||
 | 
					            stripe
 | 
				
			||||||
 | 
					            border
 | 
				
			||||||
 | 
					            class="mt5"
 | 
				
			||||||
 | 
					        >
 | 
				
			||||||
            <el-table-column v-if="datas.length > 0 && table" type="selection" width="35" />
 | 
					            <el-table-column v-if="datas.length > 0 && table" type="selection" width="35" />
 | 
				
			||||||
            <el-table-column min-width="100" :width="DbInst.flexColumnWidth(item, datas)" align="center"
 | 
					
 | 
				
			||||||
                v-for="item in columnNames" :key="item" :prop="item" :label="item" show-overflow-tooltip
 | 
					            <template v-for="(item, index) in columns">
 | 
				
			||||||
                :sortable="sortable">
 | 
					                <el-table-column
 | 
				
			||||||
 | 
					                    min-width="100"
 | 
				
			||||||
 | 
					                    :width="DbInst.flexColumnWidth(item.columnName, datas)"
 | 
				
			||||||
 | 
					                    align="center"
 | 
				
			||||||
 | 
					                    v-if="item.show"
 | 
				
			||||||
 | 
					                    :key="index"
 | 
				
			||||||
 | 
					                    :prop="item.columnName"
 | 
				
			||||||
 | 
					                    :label="item.columnName"
 | 
				
			||||||
 | 
					                    show-overflow-tooltip
 | 
				
			||||||
 | 
					                    :sortable="sortable"
 | 
				
			||||||
 | 
					                >
 | 
				
			||||||
                    <template #header v-if="showColumnTip">
 | 
					                    <template #header v-if="showColumnTip">
 | 
				
			||||||
                        <el-tooltip raw-content placement="top" effect="customized">
 | 
					                        <el-tooltip raw-content placement="top" effect="customized">
 | 
				
			||||||
                            <template #content> {{ getColumnTip(item) }} </template>
 | 
					                            <template #content> {{ getColumnTip(item) }} </template>
 | 
				
			||||||
                        {{ item }}
 | 
					                            {{ item.columnName }}
 | 
				
			||||||
                        </el-tooltip>
 | 
					                        </el-tooltip>
 | 
				
			||||||
                    </template>
 | 
					                    </template>
 | 
				
			||||||
                </el-table-column>
 | 
					                </el-table-column>
 | 
				
			||||||
 | 
					            </template>
 | 
				
			||||||
        </el-table>
 | 
					        </el-table>
 | 
				
			||||||
    </div>
 | 
					    </div>
 | 
				
			||||||
</template>
 | 
					</template>
 | 
				
			||||||
@@ -45,8 +67,8 @@ const props = defineProps({
 | 
				
			|||||||
    data: {
 | 
					    data: {
 | 
				
			||||||
        type: Array,
 | 
					        type: Array,
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    columnNames: {
 | 
					    columns: {
 | 
				
			||||||
        type: Array,
 | 
					        type: Array<any>,
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    sortable: {
 | 
					    sortable: {
 | 
				
			||||||
        type: [String, Boolean],
 | 
					        type: [String, Boolean],
 | 
				
			||||||
@@ -76,7 +98,6 @@ const state = reactive({
 | 
				
			|||||||
    db: '',  // 数据库名
 | 
					    db: '',  // 数据库名
 | 
				
			||||||
    table: '', // 当前的表名
 | 
					    table: '', // 当前的表名
 | 
				
			||||||
    datas: [],
 | 
					    datas: [],
 | 
				
			||||||
    columnNames: [],
 | 
					 | 
				
			||||||
    columns: [],
 | 
					    columns: [],
 | 
				
			||||||
    sortable: false,
 | 
					    sortable: false,
 | 
				
			||||||
    loading: false,
 | 
					    loading: false,
 | 
				
			||||||
@@ -92,7 +113,6 @@ const {
 | 
				
			|||||||
    datas,
 | 
					    datas,
 | 
				
			||||||
    sortable,
 | 
					    sortable,
 | 
				
			||||||
    loading,
 | 
					    loading,
 | 
				
			||||||
    columnNames,
 | 
					 | 
				
			||||||
    showColumnTip,
 | 
					    showColumnTip,
 | 
				
			||||||
} = toRefs(state);
 | 
					} = toRefs(state);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -114,24 +134,16 @@ const setState = (props: any) => {
 | 
				
			|||||||
    state.tableHeight = props.height;
 | 
					    state.tableHeight = props.height;
 | 
				
			||||||
    state.sortable = props.sortable;
 | 
					    state.sortable = props.sortable;
 | 
				
			||||||
    state.loading = props.loading;
 | 
					    state.loading = props.loading;
 | 
				
			||||||
    state.columnNames = props.columnNames;
 | 
					    state.columns = props.columns;
 | 
				
			||||||
    state.showColumnTip = props.showColumnTip;
 | 
					    state.showColumnTip = props.showColumnTip;
 | 
				
			||||||
    state.emptyText = props.emptyText;
 | 
					    state.emptyText = props.emptyText;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const getColumnTip = (columnName: string) => {
 | 
					const getColumnTip = (column: any) => {
 | 
				
			||||||
    // 优先从 table map中获取
 | 
					 | 
				
			||||||
    let columns = getNowDb().getColumns(state.table);
 | 
					 | 
				
			||||||
    if (!columns) {
 | 
					 | 
				
			||||||
        return '';
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    const column = columns.find((c: any) => c.columnName == columnName);
 | 
					 | 
				
			||||||
    const comment = column.columnComment;
 | 
					    const comment = column.columnComment;
 | 
				
			||||||
    return `${column.columnType} ${comment ? ' |  ' + comment : ''}`;
 | 
					    return `${column.columnType} ${comment ? ' |  ' + comment : ''}`;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * 表排序字段变更
 | 
					 * 表排序字段变更
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
@@ -178,7 +190,7 @@ const cellClick = (row: any, column: any, cell: any) => {
 | 
				
			|||||||
                const updateColumn = await dbInst.loadTableColumn(state.db, state.table, property);
 | 
					                const updateColumn = await dbInst.loadTableColumn(state.db, state.table, property);
 | 
				
			||||||
                const newField = {
 | 
					                const newField = {
 | 
				
			||||||
                    div, row,
 | 
					                    div, row,
 | 
				
			||||||
                    fieldName: column.rawColumnKey,
 | 
					                    fieldName: property,
 | 
				
			||||||
                    fieldType: updateColumn.columnType,
 | 
					                    fieldType: updateColumn.columnType,
 | 
				
			||||||
                    oldValue: text,
 | 
					                    oldValue: text,
 | 
				
			||||||
                    newValue: input.value
 | 
					                    newValue: input.value
 | 
				
			||||||
@@ -209,10 +221,10 @@ const cellClick = (row: any, column: any, cell: any) => {
 | 
				
			|||||||
                let fields = primaryKeyFields[0].fields
 | 
					                let fields = primaryKeyFields[0].fields
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                const fieldsParam = fields.filter((a) => {
 | 
					                const fieldsParam = fields.filter((a) => {
 | 
				
			||||||
                    if (a.fieldName === column.rawColumnKey) {
 | 
					                    if (a.fieldName === column.property) {
 | 
				
			||||||
                        a.newValue = input.value
 | 
					                        a.newValue = input.value
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
                    return a.fieldName === column.rawColumnKey
 | 
					                    return a.fieldName === column.property
 | 
				
			||||||
                })
 | 
					                })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                const field = fieldsParam.length > 0 && fieldsParam[0] || {} as FieldsMeta
 | 
					                const field = fieldsParam.length > 0 && fieldsParam[0] || {} as FieldsMeta
 | 
				
			||||||
@@ -222,7 +234,7 @@ const cellClick = (row: any, column: any, cell: any) => {
 | 
				
			|||||||
                    let delIndex: number[] = [];
 | 
					                    let delIndex: number[] = [];
 | 
				
			||||||
                    currentUpdatedFields.forEach((a, i) => {
 | 
					                    currentUpdatedFields.forEach((a, i) => {
 | 
				
			||||||
                        if (a.primaryKey === primaryKeyValue) {
 | 
					                        if (a.primaryKey === primaryKeyValue) {
 | 
				
			||||||
                            a.fields = a.fields && a.fields.length > 0 ? a.fields.filter(f => f.fieldName !== column.rawColumnKey) : [];
 | 
					                            a.fields = a.fields && a.fields.length > 0 ? a.fields.filter(f => f.fieldName !== column.property) : [];
 | 
				
			||||||
                            a.fields.length <= 0 && delIndex.push(i)
 | 
					                            a.fields.length <= 0 && delIndex.push(i)
 | 
				
			||||||
                        }
 | 
					                        }
 | 
				
			||||||
                    });
 | 
					                    });
 | 
				
			||||||
@@ -254,6 +266,7 @@ const cellClick = (row: any, column: any, cell: any) => {
 | 
				
			|||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const submitUpdateFields = () => {
 | 
					const submitUpdateFields = () => {
 | 
				
			||||||
 | 
					    const dbInst = DbInst.getInst(state.dbId)
 | 
				
			||||||
    let currentUpdatedFields = state.updatedFields;
 | 
					    let currentUpdatedFields = state.updatedFields;
 | 
				
			||||||
    if (currentUpdatedFields.length <= 0) {
 | 
					    if (currentUpdatedFields.length <= 0) {
 | 
				
			||||||
        return;
 | 
					        return;
 | 
				
			||||||
@@ -262,24 +275,24 @@ const submitUpdateFields = () => {
 | 
				
			|||||||
    let res = '';
 | 
					    let res = '';
 | 
				
			||||||
    let divs: HTMLElement[] = [];
 | 
					    let divs: HTMLElement[] = [];
 | 
				
			||||||
    currentUpdatedFields.forEach(a => {
 | 
					    currentUpdatedFields.forEach(a => {
 | 
				
			||||||
        let sql = `UPDATE ${state.table} SET `;
 | 
					        let sql = `UPDATE ${dbInst.wrapName(state.table)} SET `;
 | 
				
			||||||
        let primaryKey = a.primaryKey;
 | 
					        let primaryKey = a.primaryKey;
 | 
				
			||||||
        let primaryKeyType = a.primaryKeyType;
 | 
					        let primaryKeyType = a.primaryKeyType;
 | 
				
			||||||
        let primaryKeyName = a.primaryKeyName;
 | 
					        let primaryKeyName = a.primaryKeyName;
 | 
				
			||||||
        a.fields.forEach(f => {
 | 
					        a.fields.forEach(f => {
 | 
				
			||||||
            sql += ` ${f.fieldName} = ${DbInst.wrapColumnValue(f.fieldType, f.newValue)},`
 | 
					            sql += ` ${dbInst.wrapName(f.fieldName)} = ${DbInst.wrapColumnValue(f.fieldType, f.newValue)},`
 | 
				
			||||||
            // 如果修改的字段是主键
 | 
					            // 如果修改的字段是主键
 | 
				
			||||||
            if(f.fieldName === primaryKeyName){
 | 
					            if (f.fieldName === primaryKeyName) {
 | 
				
			||||||
                primaryKey = f.oldValue
 | 
					                primaryKey = f.oldValue
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            divs.push(f.div)
 | 
					            divs.push(f.div)
 | 
				
			||||||
        })
 | 
					        })
 | 
				
			||||||
        sql = sql.substring(0, sql.length - 1)
 | 
					        sql = sql.substring(0, sql.length - 1)
 | 
				
			||||||
        sql += ` WHERE ${primaryKeyName} = ${DbInst.wrapColumnValue(primaryKeyType, primaryKey)} ;`
 | 
					        sql += ` WHERE ${dbInst.wrapName(primaryKeyName)} = ${DbInst.wrapColumnValue(primaryKeyType, primaryKey)} ;`
 | 
				
			||||||
        res += sql;
 | 
					        res += sql;
 | 
				
			||||||
    })
 | 
					    })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    DbInst.getInst(state.dbId).promptExeSql(db, res, () => { }, () => {
 | 
					    dbInst.promptExeSql(db, res, () => { }, () => {
 | 
				
			||||||
        currentUpdatedFields = [];
 | 
					        currentUpdatedFields = [];
 | 
				
			||||||
        divs.forEach(a => {
 | 
					        divs.forEach(a => {
 | 
				
			||||||
            a.classList.remove('update_field_active');
 | 
					            a.classList.remove('update_field_active');
 | 
				
			||||||
@@ -305,10 +318,6 @@ const changeUpdatedField = () => {
 | 
				
			|||||||
    emits('changeUpdatedField', state.updatedFields);
 | 
					    emits('changeUpdatedField', state.updatedFields);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const getNowDb = () => {
 | 
					 | 
				
			||||||
    return DbInst.getInst(state.dbId).getDb(state.db);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const getNowDbInst = () => {
 | 
					const getNowDbInst = () => {
 | 
				
			||||||
    return DbInst.getInst(state.dbId);
 | 
					    return DbInst.getInst(state.dbId);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -317,11 +326,10 @@ defineExpose({
 | 
				
			|||||||
    submitUpdateFields,
 | 
					    submitUpdateFields,
 | 
				
			||||||
    cancelUpdateFields
 | 
					    cancelUpdateFields
 | 
				
			||||||
})
 | 
					})
 | 
				
			||||||
 | 
					 | 
				
			||||||
</script>
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<style lang="scss">
 | 
					<style lang="scss">
 | 
				
			||||||
.update_field_active {
 | 
					.update_field_active {
 | 
				
			||||||
    background-color: var(--el-color-success)
 | 
					    background-color: var(--el-color-success);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
</style>
 | 
					</style>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,44 +1,44 @@
 | 
				
			|||||||
import { h, render, VNode } from 'vue'
 | 
					import { h, render, VNode } from 'vue';
 | 
				
			||||||
import SqlExecDialog from './SqlExecDialog.vue'
 | 
					import SqlExecDialog from './SqlExecDialog.vue';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export type SqlExecProps = {
 | 
					export type SqlExecProps = {
 | 
				
			||||||
    sql: string
 | 
					    sql: string;
 | 
				
			||||||
    dbId: number,
 | 
					    dbId: number;
 | 
				
			||||||
    db: string,
 | 
					    db: string;
 | 
				
			||||||
    runSuccessCallback?: Function,
 | 
					    runSuccessCallback?: Function;
 | 
				
			||||||
    cancelCallback?: Function
 | 
					    cancelCallback?: Function;
 | 
				
			||||||
}
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const boxId = 'sql-exec-id'
 | 
					const boxId = 'sql-exec-id';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const renderBox = (): VNode => {
 | 
					const renderBox = (): VNode => {
 | 
				
			||||||
    const props: SqlExecProps = {
 | 
					    const props: SqlExecProps = {
 | 
				
			||||||
        sql: '',
 | 
					        sql: '',
 | 
				
			||||||
        dbId: 0,
 | 
					        dbId: 0,
 | 
				
			||||||
    } as any
 | 
					    } as any;
 | 
				
			||||||
    const container = document.createElement('div')
 | 
					    const container = document.createElement('div');
 | 
				
			||||||
    container.id = boxId
 | 
					    container.id = boxId;
 | 
				
			||||||
    // 创建 虚拟dom
 | 
					    // 创建 虚拟dom
 | 
				
			||||||
    const boxVNode = h(SqlExecDialog, props)
 | 
					    const boxVNode = h(SqlExecDialog, props);
 | 
				
			||||||
    // 将虚拟dom渲染到 container dom 上
 | 
					    // 将虚拟dom渲染到 container dom 上
 | 
				
			||||||
    render(boxVNode, container)
 | 
					    render(boxVNode, container);
 | 
				
			||||||
    // 最后将 container 追加到 body 上
 | 
					    // 最后将 container 追加到 body 上
 | 
				
			||||||
    document.body.appendChild(container)
 | 
					    document.body.appendChild(container);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return boxVNode
 | 
					    return boxVNode;
 | 
				
			||||||
}
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
let boxInstance: any
 | 
					let boxInstance: any;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const SqlExecBox = (props: SqlExecProps): void => {
 | 
					const SqlExecBox = (props: SqlExecProps): void => {
 | 
				
			||||||
    if (boxInstance) {
 | 
					    if (boxInstance) {
 | 
				
			||||||
        const boxVue = boxInstance.component
 | 
					        const boxVue = boxInstance.component;
 | 
				
			||||||
        // 调用open方法显示弹框,注意不能使用boxVue.ctx来调用组件函数(build打包后ctx会获取不到)
 | 
					        // 调用open方法显示弹框,注意不能使用boxVue.ctx来调用组件函数(build打包后ctx会获取不到)
 | 
				
			||||||
        boxVue.exposed.open(props);
 | 
					        boxVue.exposed.open(props);
 | 
				
			||||||
    } else {
 | 
					    } else {
 | 
				
			||||||
        boxInstance = renderBox()
 | 
					        boxInstance = renderBox();
 | 
				
			||||||
        SqlExecBox(props)
 | 
					        SqlExecBox(props);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default SqlExecBox;
 | 
					export default SqlExecBox;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -36,7 +36,7 @@ const props = defineProps({
 | 
				
			|||||||
    sql: {
 | 
					    sql: {
 | 
				
			||||||
        type: String,
 | 
					        type: String,
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
})
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const remarkInputRef = ref<InputInstance>();
 | 
					const remarkInputRef = ref<InputInstance>();
 | 
				
			||||||
const state = reactive({
 | 
					const state = reactive({
 | 
				
			||||||
@@ -48,12 +48,7 @@ const state = reactive({
 | 
				
			|||||||
    btnLoading: false,
 | 
					    btnLoading: false,
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const {
 | 
					const { dialogVisible, sqlValue, remark, btnLoading } = toRefs(state);
 | 
				
			||||||
    dialogVisible,
 | 
					 | 
				
			||||||
    sqlValue,
 | 
					 | 
				
			||||||
    remark,
 | 
					 | 
				
			||||||
    btnLoading
 | 
					 | 
				
			||||||
} = toRefs(state)
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
state.sqlValue = props.sql as any;
 | 
					state.sqlValue = props.sql as any;
 | 
				
			||||||
let runSuccessCallback: any;
 | 
					let runSuccessCallback: any;
 | 
				
			||||||
@@ -81,7 +76,7 @@ const runSql = async () => {
 | 
				
			|||||||
        for (let re of res.res) {
 | 
					        for (let re of res.res) {
 | 
				
			||||||
            if (re.result !== 'success') {
 | 
					            if (re.result !== 'success') {
 | 
				
			||||||
                ElMessage.error(`${re.sql} \n执行失败: ${re.result}`);
 | 
					                ElMessage.error(`${re.sql} \n执行失败: ${re.result}`);
 | 
				
			||||||
                throw new Error(re.result)
 | 
					                throw new Error(re.result);
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -129,7 +124,7 @@ const open = (props: SqlExecProps) => {
 | 
				
			|||||||
    });
 | 
					    });
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
defineExpose({ open })
 | 
					defineExpose({ open });
 | 
				
			||||||
</script>
 | 
					</script>
 | 
				
			||||||
<style lang="scss">
 | 
					<style lang="scss">
 | 
				
			||||||
.codesql {
 | 
					.codesql {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -3,43 +3,49 @@
 | 
				
			|||||||
        <div>
 | 
					        <div>
 | 
				
			||||||
            <div class="toolbar">
 | 
					            <div class="toolbar">
 | 
				
			||||||
                <div class="fl">
 | 
					                <div class="fl">
 | 
				
			||||||
                    <el-link @click="onRunSql()" :underline="false" class="ml15" icon="VideoPlay">
 | 
					                    <el-link @click="onRunSql()" :underline="false" class="ml15" icon="VideoPlay"> </el-link>
 | 
				
			||||||
                    </el-link>
 | 
					 | 
				
			||||||
                    <el-divider direction="vertical" border-style="dashed" />
 | 
					                    <el-divider direction="vertical" border-style="dashed" />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    <el-tooltip class="box-item" effect="dark" content="format sql" placement="top">
 | 
					                    <el-tooltip class="box-item" effect="dark" content="format sql" placement="top">
 | 
				
			||||||
                        <el-link @click="formatSql()" type="primary" :underline="false" icon="MagicStick">
 | 
					                        <el-link @click="formatSql()" type="primary" :underline="false" icon="MagicStick"> </el-link>
 | 
				
			||||||
                        </el-link>
 | 
					 | 
				
			||||||
                    </el-tooltip>
 | 
					                    </el-tooltip>
 | 
				
			||||||
                    <el-divider direction="vertical" border-style="dashed" />
 | 
					                    <el-divider direction="vertical" border-style="dashed" />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    <el-tooltip class="box-item" effect="dark" content="commit" placement="top">
 | 
					                    <el-tooltip class="box-item" effect="dark" content="commit" placement="top">
 | 
				
			||||||
                        <el-link @click="onCommit()" type="success" :underline="false" icon="CircleCheck">
 | 
					                        <el-link @click="onCommit()" type="success" :underline="false" icon="CircleCheck"> </el-link>
 | 
				
			||||||
                        </el-link>
 | 
					 | 
				
			||||||
                    </el-tooltip>
 | 
					                    </el-tooltip>
 | 
				
			||||||
                    <el-divider direction="vertical" border-style="dashed" />
 | 
					                    <el-divider direction="vertical" border-style="dashed" />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    <el-upload class="sql-file-exec" :before-upload="beforeUpload" :on-success="execSqlFileSuccess"
 | 
					                    <el-upload
 | 
				
			||||||
                        :headers="{ Authorization: token }" :action="getUploadSqlFileUrl()" :show-file-list="false"
 | 
					                        class="sql-file-exec"
 | 
				
			||||||
                        name="file" multiple :limit="100">
 | 
					                        :before-upload="beforeUpload"
 | 
				
			||||||
 | 
					                        :on-success="execSqlFileSuccess"
 | 
				
			||||||
 | 
					                        :headers="{ Authorization: token }"
 | 
				
			||||||
 | 
					                        :action="getUploadSqlFileUrl()"
 | 
				
			||||||
 | 
					                        :show-file-list="false"
 | 
				
			||||||
 | 
					                        name="file"
 | 
				
			||||||
 | 
					                        multiple
 | 
				
			||||||
 | 
					                        :limit="100"
 | 
				
			||||||
 | 
					                    >
 | 
				
			||||||
                        <el-tooltip class="box-item" effect="dark" content="SQL脚本执行" placement="top">
 | 
					                        <el-tooltip class="box-item" effect="dark" content="SQL脚本执行" placement="top">
 | 
				
			||||||
                            <el-link type="success" :underline="false" icon="Document"></el-link>
 | 
					                            <el-link type="success" :underline="false" icon="Document"></el-link>
 | 
				
			||||||
                        </el-tooltip>
 | 
					                        </el-tooltip>
 | 
				
			||||||
                    </el-upload>
 | 
					                    </el-upload>
 | 
				
			||||||
 | 
					                    <el-divider direction="vertical" border-style="dashed" />
 | 
				
			||||||
 | 
					                    <el-tooltip class="box-item" effect="dark" content="limit" placement="top">
 | 
				
			||||||
 | 
					                        <el-link @click="onLimit()" type="success" :underline="false" icon="Operation"> </el-link>
 | 
				
			||||||
 | 
					                    </el-tooltip>
 | 
				
			||||||
                </div>
 | 
					                </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                <div style="float: right" class="fl">
 | 
					                <div style="float: right" class="fl">
 | 
				
			||||||
                    <el-button @click="saveSql()" type="primary" icon="document-add" plain size="small">保存SQL
 | 
					                    <el-button @click="saveSql()" type="primary" icon="document-add" plain size="small">保存SQL </el-button>
 | 
				
			||||||
                    </el-button>
 | 
					                    <el-button v-if="sqlName" @click="deleteSql()" type="danger" icon="delete" plain size="small">删除SQL </el-button>
 | 
				
			||||||
                    <el-button v-if="sqlName" @click="deleteSql()" type="danger" icon="delete" plain size="small">删除SQL
 | 
					 | 
				
			||||||
                    </el-button>
 | 
					 | 
				
			||||||
                </div>
 | 
					                </div>
 | 
				
			||||||
            </div>
 | 
					            </div>
 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        <div class="mt5 sqlEditor">
 | 
					        <div class="mt5 sqlEditor">
 | 
				
			||||||
            <div :id="'MonacoTextarea-' + ti.key" :style="{ height: editorHeight }">
 | 
					            <div :id="'MonacoTextarea-' + ti.key" :style="{ height: editorHeight }"></div>
 | 
				
			||||||
            </div>
 | 
					 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        <div class="editor-move-resize" @mousedown="onDragSetHeight">
 | 
					        <div class="editor-move-resize" @mousedown="onDragSetHeight">
 | 
				
			||||||
@@ -50,31 +56,35 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        <div class="mt5">
 | 
					        <div class="mt5">
 | 
				
			||||||
            <el-row>
 | 
					            <el-row>
 | 
				
			||||||
                <el-link v-if="table" @click="onDeleteData()" class="ml5" type="danger" icon="delete"
 | 
					                <el-link v-if="table" @click="onDeleteData()" class="ml5" type="danger" icon="delete" :underline="false"></el-link>
 | 
				
			||||||
                    :underline="false"></el-link>
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
                <span v-if="execRes.data.length > 0">
 | 
					                <span v-if="execRes.data.length > 0">
 | 
				
			||||||
                    <el-divider direction="vertical" border-style="dashed" />
 | 
					                    <el-divider direction="vertical" border-style="dashed" />
 | 
				
			||||||
                    <el-link type="success" :underline="false" @click="exportData"><span
 | 
					                    <el-link type="success" :underline="false" @click="exportData"><span style="font-size: 12px">导出</span></el-link>
 | 
				
			||||||
                            style="font-size: 12px">导出</span></el-link>
 | 
					 | 
				
			||||||
                </span>
 | 
					                </span>
 | 
				
			||||||
                <span v-if="hasUpdatedFileds">
 | 
					                <span v-if="hasUpdatedFileds">
 | 
				
			||||||
                    <el-divider direction="vertical" border-style="dashed" />
 | 
					                    <el-divider direction="vertical" border-style="dashed" />
 | 
				
			||||||
                    <el-link type="success" :underline="false" @click="submitUpdateFields()"><span
 | 
					                    <el-link type="success" :underline="false" @click="submitUpdateFields()"><span style="font-size: 12px">提交</span></el-link>
 | 
				
			||||||
                            style="font-size: 12px">提交</span></el-link>
 | 
					 | 
				
			||||||
                </span>
 | 
					                </span>
 | 
				
			||||||
                <span v-if="hasUpdatedFileds">
 | 
					                <span v-if="hasUpdatedFileds">
 | 
				
			||||||
                    <el-divider direction="vertical" border-style="dashed" />
 | 
					                    <el-divider direction="vertical" border-style="dashed" />
 | 
				
			||||||
                    <el-link type="warning" :underline="false" @click="cancelUpdateFields"><span
 | 
					                    <el-link type="warning" :underline="false" @click="cancelUpdateFields"><span style="font-size: 12px">取消</span></el-link>
 | 
				
			||||||
                            style="font-size: 12px">取消</span></el-link>
 | 
					 | 
				
			||||||
                </span>
 | 
					                </span>
 | 
				
			||||||
            </el-row>
 | 
					            </el-row>
 | 
				
			||||||
            <db-table ref="dbTableRef" :db-id="state.ti.dbId" :db="state.ti.db" :data="execRes.data" :table="state.table"
 | 
					            <db-table
 | 
				
			||||||
                :column-names="execRes.tableColumn" :loading="loading" :height="tableDataHeight"
 | 
					                ref="dbTableRef"
 | 
				
			||||||
                empty-text="tips: select *开头的单表查询或点击表名默认查询的数据,可双击数据在线修改" @selection-change="onDataSelectionChange"
 | 
					                :db-id="state.ti.dbId"
 | 
				
			||||||
                @change-updated-field="changeUpdatedField"></db-table>
 | 
					                :db="state.ti.db"
 | 
				
			||||||
 | 
					                :data="execRes.data"
 | 
				
			||||||
 | 
					                :table="state.table"
 | 
				
			||||||
 | 
					                :columns="execRes.tableColumn"
 | 
				
			||||||
 | 
					                :loading="loading"
 | 
				
			||||||
 | 
					                :height="tableDataHeight"
 | 
				
			||||||
 | 
					                empty-text="tips: select *开头的单表查询或点击表名默认查询的数据,可双击数据在线修改"
 | 
				
			||||||
 | 
					                @selection-change="onDataSelectionChange"
 | 
				
			||||||
 | 
					                @change-updated-field="changeUpdatedField"
 | 
				
			||||||
 | 
					            ></db-table>
 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
 | 
					 | 
				
			||||||
    </div>
 | 
					    </div>
 | 
				
			||||||
</template>
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -87,25 +97,44 @@ import { isTrue, notBlank } from '@/common/assert';
 | 
				
			|||||||
import { format as sqlFormatter } from 'sql-formatter';
 | 
					import { format as sqlFormatter } from 'sql-formatter';
 | 
				
			||||||
import config from '@/common/config';
 | 
					import config from '@/common/config';
 | 
				
			||||||
import { ElMessage, ElMessageBox } from 'element-plus';
 | 
					import { ElMessage, ElMessageBox } from 'element-plus';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { language as sqlLanguage } from 'monaco-editor/esm/vs/basic-languages/mysql/mysql.js';
 | 
				
			||||||
 | 
					import { language as addSqlLanguage } from '../../lang/mysql.js';
 | 
				
			||||||
import EditorWorker from 'monaco-editor/esm/vs/editor/editor.worker.js?worker';
 | 
					import EditorWorker from 'monaco-editor/esm/vs/editor/editor.worker.js?worker';
 | 
				
			||||||
import * as monaco from 'monaco-editor';
 | 
					// import * as monaco from 'monaco-editor';
 | 
				
			||||||
import { editor } from 'monaco-editor';
 | 
					import * as monaco from 'monaco-editor/esm/vs/editor/editor.api';
 | 
				
			||||||
 | 
					import { editor, languages, Position } from 'monaco-editor';
 | 
				
			||||||
 | 
					// 相关语言
 | 
				
			||||||
 | 
					import 'monaco-editor/esm/vs/basic-languages/sql/sql.contribution.js';
 | 
				
			||||||
 | 
					import 'monaco-editor/esm/vs/editor/contrib/suggest/browser/suggestController.js';
 | 
				
			||||||
 | 
					import 'monaco-editor/esm/vs/editor/contrib/suggest/browser/suggestInlineCompletions.js';
 | 
				
			||||||
 | 
					// 右键菜单
 | 
				
			||||||
 | 
					import 'monaco-editor/esm/vs/editor/contrib/contextmenu/browser/contextmenu.js';
 | 
				
			||||||
 | 
					import 'monaco-editor/esm/vs/editor/contrib/caretOperations/browser/caretOperations.js';
 | 
				
			||||||
 | 
					import 'monaco-editor/esm/vs/editor/contrib/clipboard//browser/clipboard.js';
 | 
				
			||||||
 | 
					import 'monaco-editor/esm/vs/editor/contrib/find/browser/findController.js';
 | 
				
			||||||
 | 
					import 'monaco-editor/esm/vs/editor/contrib/format//browser/formatActions.js';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// 主题仓库 https://github.com/brijeshb42/monaco-themes
 | 
					// 主题仓库 https://github.com/brijeshb42/monaco-themes
 | 
				
			||||||
// 主题例子 https://editor.bitwiser.in/
 | 
					// 主题例子 https://editor.bitwiser.in/
 | 
				
			||||||
import SolarizedLight from 'monaco-themes/themes/Solarized-light.json';
 | 
					import SolarizedLight from 'monaco-themes/themes/Solarized-light.json';
 | 
				
			||||||
import DbTable from '../DbTable.vue'
 | 
					import DbTable from '../DbTable.vue';
 | 
				
			||||||
import { TabInfo } from '../../db';
 | 
					import { DbInst, TabInfo } from '../../db';
 | 
				
			||||||
import { exportCsv } from '@/common/utils/export';
 | 
					import { exportCsv } from '@/common/utils/export';
 | 
				
			||||||
import { dateStrFormat } from '@/common/utils/date';
 | 
					import { dateStrFormat } from '@/common/utils/date';
 | 
				
			||||||
import { dbApi } from '../../api';
 | 
					import { dbApi } from '../../api';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const emits = defineEmits(['saveSqlSuccess', 'deleteSqlSuccess'])
 | 
					const sqlCompletionKeywords = [...sqlLanguage.keywords, ...addSqlLanguage.keywords];
 | 
				
			||||||
 | 
					const sqlCompletionOperators = [...sqlLanguage.operators, ...addSqlLanguage.operators];
 | 
				
			||||||
 | 
					const sqlCompletionBuiltinFunctions = [...sqlLanguage.builtinFunctions, ...addSqlLanguage.builtinFunctions];
 | 
				
			||||||
 | 
					const sqlCompletionBuiltinVariables = [...sqlLanguage.builtinVariables, ...addSqlLanguage.builtinVariables];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const emits = defineEmits(['saveSqlSuccess', 'deleteSqlSuccess']);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const props = defineProps({
 | 
					const props = defineProps({
 | 
				
			||||||
    data: {
 | 
					    data: {
 | 
				
			||||||
        type: TabInfo,
 | 
					        type: TabInfo,
 | 
				
			||||||
        required: true
 | 
					        required: true,
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    // sql脚本名,若有则去加载该sql内容
 | 
					    // sql脚本名,若有则去加载该sql内容
 | 
				
			||||||
    sqlName: {
 | 
					    sqlName: {
 | 
				
			||||||
@@ -114,9 +143,9 @@ const props = defineProps({
 | 
				
			|||||||
    },
 | 
					    },
 | 
				
			||||||
    editorHeight: {
 | 
					    editorHeight: {
 | 
				
			||||||
        type: String,
 | 
					        type: String,
 | 
				
			||||||
        default: '600'
 | 
					        default: '600',
 | 
				
			||||||
    }
 | 
					    },
 | 
				
			||||||
})
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const { themeConfig } = storeToRefs(useThemeConfig());
 | 
					const { themeConfig } = storeToRefs(useThemeConfig());
 | 
				
			||||||
const token = getSession('token');
 | 
					const token = getSession('token');
 | 
				
			||||||
@@ -134,7 +163,7 @@ const state = reactive({
 | 
				
			|||||||
    loading: false, // 是否在加载数据
 | 
					    loading: false, // 是否在加载数据
 | 
				
			||||||
    execRes: {
 | 
					    execRes: {
 | 
				
			||||||
        data: [],
 | 
					        data: [],
 | 
				
			||||||
        tableColumn: []
 | 
					        tableColumn: [],
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    selectionDatas: [] as any,
 | 
					    selectionDatas: [] as any,
 | 
				
			||||||
    editorHeight: '500',
 | 
					    editorHeight: '500',
 | 
				
			||||||
@@ -142,20 +171,23 @@ const state = reactive({
 | 
				
			|||||||
    hasUpdatedFileds: false,
 | 
					    hasUpdatedFileds: false,
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const {
 | 
					const { tableDataHeight, editorHeight, ti, execRes, table, sqlName, loading, hasUpdatedFileds } = toRefs(state);
 | 
				
			||||||
    tableDataHeight,
 | 
					 | 
				
			||||||
    editorHeight,
 | 
					 | 
				
			||||||
    ti,
 | 
					 | 
				
			||||||
    execRes,
 | 
					 | 
				
			||||||
    table,
 | 
					 | 
				
			||||||
    sqlName,
 | 
					 | 
				
			||||||
    loading,
 | 
					 | 
				
			||||||
    hasUpdatedFileds,
 | 
					 | 
				
			||||||
} = toRefs(state);
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
watch(() => props.editorHeight, (newValue: any) => {
 | 
					watch(
 | 
				
			||||||
 | 
					    () => props.editorHeight,
 | 
				
			||||||
 | 
					    (newValue: any) => {
 | 
				
			||||||
        state.editorHeight = newValue;
 | 
					        state.editorHeight = newValue;
 | 
				
			||||||
});
 | 
					    }
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// 监听 themeConfig editorTheme配置文件的变化
 | 
				
			||||||
 | 
					watch(
 | 
				
			||||||
 | 
					    () => themeConfig.value.editorTheme,
 | 
				
			||||||
 | 
					    (val) => {
 | 
				
			||||||
 | 
					        console.log('monaco editor theme change: ', val);
 | 
				
			||||||
 | 
					        monaco?.editor?.setTheme(val);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
onMounted(async () => {
 | 
					onMounted(async () => {
 | 
				
			||||||
    console.log('in query mounted');
 | 
					    console.log('in query mounted');
 | 
				
			||||||
@@ -170,19 +202,21 @@ onMounted(async () => {
 | 
				
			|||||||
        state.sql = res.sql;
 | 
					        state.sql = res.sql;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    nextTick(() => {
 | 
					    nextTick(() => {
 | 
				
			||||||
        setTimeout(() => initMonacoEditor(), 50)
 | 
					        setTimeout(() => initMonacoEditor(), 50);
 | 
				
			||||||
    })
 | 
					    });
 | 
				
			||||||
    await state.ti.getNowDbInst().loadDbHints(state.ti.db);
 | 
					    await state.ti.getNowDbInst().loadDbHints(state.ti.db);
 | 
				
			||||||
})
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
self.MonacoEnvironment = {
 | 
					self.MonacoEnvironment = {
 | 
				
			||||||
    getWorker() {
 | 
					    getWorker() {
 | 
				
			||||||
        return new EditorWorker();
 | 
					        return new EditorWorker();
 | 
				
			||||||
    }
 | 
					    },
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const initMonacoEditor = () => {
 | 
					const initMonacoEditor = () => {
 | 
				
			||||||
    let monacoTextarea = document.getElementById('MonacoTextarea-' + state.ti.key) as HTMLElement
 | 
					    registerSqlCompletionItemProvider();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let monacoTextarea = document.getElementById('MonacoTextarea-' + state.ti.key) as HTMLElement;
 | 
				
			||||||
    // options参数参考 https://microsoft.github.io/monaco-editor/api/interfaces/monaco.editor.IStandaloneEditorConstructionOptions.html#language
 | 
					    // options参数参考 https://microsoft.github.io/monaco-editor/api/interfaces/monaco.editor.IStandaloneEditorConstructionOptions.html#language
 | 
				
			||||||
    // 初始化一些主题
 | 
					    // 初始化一些主题
 | 
				
			||||||
    monaco.editor.defineTheme('SolarizedLight', SolarizedLight);
 | 
					    monaco.editor.defineTheme('SolarizedLight', SolarizedLight);
 | 
				
			||||||
@@ -194,7 +228,7 @@ const initMonacoEditor = () => {
 | 
				
			|||||||
        roundedSelection: false, // 禁用选择文本背景的圆角
 | 
					        roundedSelection: false, // 禁用选择文本背景的圆角
 | 
				
			||||||
        matchBrackets: 'near',
 | 
					        matchBrackets: 'near',
 | 
				
			||||||
        linkedEditing: true,
 | 
					        linkedEditing: true,
 | 
				
			||||||
        cursorBlinking: 'smooth',// 光标闪烁样式
 | 
					        cursorBlinking: 'smooth', // 光标闪烁样式
 | 
				
			||||||
        mouseWheelZoom: true, // 在按住Ctrl键的同时使用鼠标滚轮时,在编辑器中缩放字体
 | 
					        mouseWheelZoom: true, // 在按住Ctrl键的同时使用鼠标滚轮时,在编辑器中缩放字体
 | 
				
			||||||
        overviewRulerBorder: false, // 不要滚动条的边框
 | 
					        overviewRulerBorder: false, // 不要滚动条的边框
 | 
				
			||||||
        tabSize: 2, // tab 缩进长度
 | 
					        tabSize: 2, // tab 缩进长度
 | 
				
			||||||
@@ -219,7 +253,7 @@ const initMonacoEditor = () => {
 | 
				
			|||||||
        keybindingContext: undefined,
 | 
					        keybindingContext: undefined,
 | 
				
			||||||
        keybindings: [
 | 
					        keybindings: [
 | 
				
			||||||
            // chord
 | 
					            // chord
 | 
				
			||||||
            monaco.KeyMod.chord(monaco.KeyMod.CtrlCmd | monaco.KeyCode.KeyR, 0)
 | 
					            monaco.KeyMod.chord(monaco.KeyMod.CtrlCmd | monaco.KeyCode.KeyR, 0),
 | 
				
			||||||
        ],
 | 
					        ],
 | 
				
			||||||
        contextMenuGroupId: 'navigation',
 | 
					        contextMenuGroupId: 'navigation',
 | 
				
			||||||
        contextMenuOrder: 1.5,
 | 
					        contextMenuOrder: 1.5,
 | 
				
			||||||
@@ -229,9 +263,9 @@ const initMonacoEditor = () => {
 | 
				
			|||||||
            try {
 | 
					            try {
 | 
				
			||||||
                await onRunSql();
 | 
					                await onRunSql();
 | 
				
			||||||
            } catch (e: any) {
 | 
					            } catch (e: any) {
 | 
				
			||||||
                e.message && ElMessage.error(e.message)
 | 
					                e.message && ElMessage.error(e.message);
 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // 注册快捷键:ctrl + shift + f 格式化sql
 | 
					    // 注册快捷键:ctrl + shift + f 格式化sql
 | 
				
			||||||
@@ -246,7 +280,7 @@ const initMonacoEditor = () => {
 | 
				
			|||||||
        keybindingContext: undefined,
 | 
					        keybindingContext: undefined,
 | 
				
			||||||
        keybindings: [
 | 
					        keybindings: [
 | 
				
			||||||
            // chord
 | 
					            // chord
 | 
				
			||||||
            monaco.KeyMod.chord(monaco.KeyMod.CtrlCmd | monaco.KeyMod.Shift | monaco.KeyCode.KeyF, 0)
 | 
					            monaco.KeyMod.chord(monaco.KeyMod.CtrlCmd | monaco.KeyMod.Shift | monaco.KeyCode.KeyF, 0),
 | 
				
			||||||
        ],
 | 
					        ],
 | 
				
			||||||
        contextMenuGroupId: 'navigation',
 | 
					        contextMenuGroupId: 'navigation',
 | 
				
			||||||
        contextMenuOrder: 2,
 | 
					        contextMenuOrder: 2,
 | 
				
			||||||
@@ -256,14 +290,11 @@ const initMonacoEditor = () => {
 | 
				
			|||||||
            try {
 | 
					            try {
 | 
				
			||||||
                await formatSql();
 | 
					                await formatSql();
 | 
				
			||||||
            } catch (e: any) {
 | 
					            } catch (e: any) {
 | 
				
			||||||
                e.message && ElMessage.error(e.message)
 | 
					                e.message && ElMessage.error(e.message);
 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // 动态设置主题
 | 
					 | 
				
			||||||
    // monaco.editor.setTheme('hc-black');
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // 如果sql有值,则默认赋值
 | 
					    // 如果sql有值,则默认赋值
 | 
				
			||||||
    if (state.sql) {
 | 
					    if (state.sql) {
 | 
				
			||||||
        monacoEditor.getModel()?.setValue(state.sql);
 | 
					        monacoEditor.getModel()?.setValue(state.sql);
 | 
				
			||||||
@@ -272,18 +303,18 @@ const initMonacoEditor = () => {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * 拖拽改变sql编辑区和查询结果区高度
 | 
					 * 拖拽改变sql编辑区和查询结果区高度
 | 
				
			||||||
*/
 | 
					 */
 | 
				
			||||||
const onDragSetHeight = () => {
 | 
					const onDragSetHeight = () => {
 | 
				
			||||||
    document.onmousemove = (e) => {
 | 
					    document.onmousemove = (e) => {
 | 
				
			||||||
        e.preventDefault();
 | 
					        e.preventDefault();
 | 
				
			||||||
        //得到鼠标拖动的宽高距离:取绝对值
 | 
					        //得到鼠标拖动的宽高距离:取绝对值
 | 
				
			||||||
        state.editorHeight = `${document.getElementById('MonacoTextarea-' + state.ti.key)!.offsetHeight + e.movementY}px`
 | 
					        state.editorHeight = `${document.getElementById('MonacoTextarea-' + state.ti.key)!.offsetHeight + e.movementY}px`;
 | 
				
			||||||
        state.tableDataHeight -= e.movementY
 | 
					        state.tableDataHeight -= e.movementY;
 | 
				
			||||||
    }
 | 
					    };
 | 
				
			||||||
    document.onmouseup = () => {
 | 
					    document.onmouseup = () => {
 | 
				
			||||||
        document.onmousemove = null;
 | 
					        document.onmousemove = null;
 | 
				
			||||||
    }
 | 
					    };
 | 
				
			||||||
}
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * 执行sql
 | 
					 * 执行sql
 | 
				
			||||||
@@ -324,11 +355,17 @@ const onRunSql = async () => {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        const colAndData: any = await state.ti.getNowDbInst().runSql(state.ti.db, sql, execRemark);
 | 
					        const colAndData: any = await state.ti.getNowDbInst().runSql(state.ti.db, sql, execRemark);
 | 
				
			||||||
        if (!colAndData.res || colAndData.res.length === 0) {
 | 
					        if (!colAndData.res || colAndData.res.length === 0) {
 | 
				
			||||||
            ElMessage.warning('未查询到结果集')
 | 
					            ElMessage.warning('未查询到结果集');
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        state.execRes.data = colAndData.res;
 | 
					        state.execRes.data = colAndData.res;
 | 
				
			||||||
        state.execRes.tableColumn = colAndData.colNames;
 | 
					        // 兼容表格字段配置
 | 
				
			||||||
        cancelUpdateFields()
 | 
					        state.execRes.tableColumn = colAndData.colNames.map((x: any) => {
 | 
				
			||||||
 | 
					            return {
 | 
				
			||||||
 | 
					                columnName: x,
 | 
				
			||||||
 | 
					                show: true,
 | 
				
			||||||
 | 
					            };
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					        cancelUpdateFields();
 | 
				
			||||||
    } catch (e: any) {
 | 
					    } catch (e: any) {
 | 
				
			||||||
        state.execRes.data = [];
 | 
					        state.execRes.data = [];
 | 
				
			||||||
        state.execRes.tableColumn = [];
 | 
					        state.execRes.tableColumn = [];
 | 
				
			||||||
@@ -364,15 +401,15 @@ const getSql = () => {
 | 
				
			|||||||
        return res;
 | 
					        return res;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    // 选择选中的sql
 | 
					    // 选择选中的sql
 | 
				
			||||||
    let selection = monacoEditor.getSelection()
 | 
					    let selection = monacoEditor.getSelection();
 | 
				
			||||||
    if (selection) {
 | 
					    if (selection) {
 | 
				
			||||||
        res = monacoEditor.getModel()?.getValueInRange(selection)
 | 
					        res = monacoEditor.getModel()?.getValueInRange(selection);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    // 整个编辑器的sql
 | 
					    // 整个编辑器的sql
 | 
				
			||||||
    if (!res) {
 | 
					    if (!res) {
 | 
				
			||||||
        return monacoEditor.getModel()?.getValue()
 | 
					        return monacoEditor.getModel()?.getValue();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    return res
 | 
					    return res;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const saveSql = async () => {
 | 
					const saveSql = async () => {
 | 
				
			||||||
@@ -386,8 +423,7 @@ const saveSql = async () => {
 | 
				
			|||||||
            const input = await ElMessageBox.prompt('请输入SQL脚本名', 'SQL名', {
 | 
					            const input = await ElMessageBox.prompt('请输入SQL脚本名', 'SQL名', {
 | 
				
			||||||
                confirmButtonText: '确定',
 | 
					                confirmButtonText: '确定',
 | 
				
			||||||
                cancelButtonText: '取消',
 | 
					                cancelButtonText: '取消',
 | 
				
			||||||
                inputPattern:
 | 
					                inputPattern: /\w+/,
 | 
				
			||||||
                    /\w+/,
 | 
					 | 
				
			||||||
                inputErrorMessage: '请输入SQL脚本名',
 | 
					                inputErrorMessage: '请输入SQL脚本名',
 | 
				
			||||||
            });
 | 
					            });
 | 
				
			||||||
            sqlName = input.value;
 | 
					            sqlName = input.value;
 | 
				
			||||||
@@ -405,7 +441,7 @@ const saveSql = async () => {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
const deleteSql = async () => {
 | 
					const deleteSql = async () => {
 | 
				
			||||||
    const sqlName = state.sqlName;
 | 
					    const sqlName = state.sqlName;
 | 
				
			||||||
    notBlank(sqlName, "该sql内容未保存");
 | 
					    notBlank(sqlName, '该sql内容未保存');
 | 
				
			||||||
    const { dbId, db } = state.ti;
 | 
					    const { dbId, db } = state.ti;
 | 
				
			||||||
    try {
 | 
					    try {
 | 
				
			||||||
        await ElMessageBox.confirm(`确定删除【${sqlName}】该SQL内容?`, '提示', {
 | 
					        await ElMessageBox.confirm(`确定删除【${sqlName}】该SQL内容?`, '提示', {
 | 
				
			||||||
@@ -416,21 +452,21 @@ const deleteSql = async () => {
 | 
				
			|||||||
        await dbApi.deleteDbSql.request({ id: dbId, db: db, name: sqlName });
 | 
					        await dbApi.deleteDbSql.request({ id: dbId, db: db, name: sqlName });
 | 
				
			||||||
        ElMessage.success('删除成功');
 | 
					        ElMessage.success('删除成功');
 | 
				
			||||||
        emits('deleteSqlSuccess', dbId, db);
 | 
					        emits('deleteSqlSuccess', dbId, db);
 | 
				
			||||||
    } catch (err) { }
 | 
					    } catch (err) {}
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * 格式化sql
 | 
					 * 格式化sql
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
const formatSql = () => {
 | 
					const formatSql = () => {
 | 
				
			||||||
    let selection = monacoEditor.getSelection()
 | 
					    let selection = monacoEditor.getSelection();
 | 
				
			||||||
    if (!selection) {
 | 
					    if (!selection) {
 | 
				
			||||||
        return;
 | 
					        return;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    let sql = monacoEditor.getModel()?.getValueInRange(selection)
 | 
					    let sql = monacoEditor.getModel()?.getValueInRange(selection);
 | 
				
			||||||
    // 有选中sql则格式化并替换选中sql, 否则格式化编辑器所有内容
 | 
					    // 有选中sql则格式化并替换选中sql, 否则格式化编辑器所有内容
 | 
				
			||||||
    if (sql) {
 | 
					    if (sql) {
 | 
				
			||||||
        replaceSelection(sqlFormatter(sql), selection)
 | 
					        replaceSelection(sqlFormatter(sql), selection);
 | 
				
			||||||
        return;
 | 
					        return;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    monacoEditor.getModel()?.setValue(sqlFormatter(monacoEditor.getValue()));
 | 
					    monacoEditor.getModel()?.setValue(sqlFormatter(monacoEditor.getValue()));
 | 
				
			||||||
@@ -456,29 +492,40 @@ const replaceSelection = (str: string, selection: any) => {
 | 
				
			|||||||
        model.setValue(str);
 | 
					        model.setValue(str);
 | 
				
			||||||
        return;
 | 
					        return;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    const { startLineNumber, endLineNumber, startColumn, endColumn } = selection
 | 
					    const { startLineNumber, endLineNumber, startColumn, endColumn } = selection;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const textBeforeSelection = model.getValueInRange({
 | 
					    const textBeforeSelection = model.getValueInRange({
 | 
				
			||||||
        startLineNumber: 1,
 | 
					        startLineNumber: 1,
 | 
				
			||||||
        startColumn: 0,
 | 
					        startColumn: 0,
 | 
				
			||||||
        endLineNumber: startLineNumber,
 | 
					        endLineNumber: startLineNumber,
 | 
				
			||||||
        endColumn: startColumn,
 | 
					        endColumn: startColumn,
 | 
				
			||||||
    })
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const textAfterSelection = model.getValueInRange({
 | 
					    const textAfterSelection = model.getValueInRange({
 | 
				
			||||||
        startLineNumber: endLineNumber,
 | 
					        startLineNumber: endLineNumber,
 | 
				
			||||||
        startColumn: endColumn,
 | 
					        startColumn: endColumn,
 | 
				
			||||||
        endLineNumber: model.getLineCount(),
 | 
					        endLineNumber: model.getLineCount(),
 | 
				
			||||||
        endColumn: model.getLineMaxColumn(model.getLineCount()),
 | 
					        endColumn: model.getLineMaxColumn(model.getLineCount()),
 | 
				
			||||||
    })
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    monacoEditor.setValue(textBeforeSelection + str + textAfterSelection)
 | 
					    monacoEditor.setValue(textBeforeSelection + str + textAfterSelection);
 | 
				
			||||||
    monacoEditor.focus()
 | 
					    monacoEditor.focus();
 | 
				
			||||||
    monacoEditor.setPosition({
 | 
					    monacoEditor.setPosition({
 | 
				
			||||||
        lineNumber: startLineNumber,
 | 
					        lineNumber: startLineNumber,
 | 
				
			||||||
        column: 0,
 | 
					        column: 0,
 | 
				
			||||||
    })
 | 
					    });
 | 
				
			||||||
}
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const onLimit = () => {
 | 
				
			||||||
 | 
					    let position = monacoEditor.getPosition() as monaco.Position;
 | 
				
			||||||
 | 
					    let newText = ' limit 10';
 | 
				
			||||||
 | 
					    monacoEditor?.getModel()?.applyEdits([
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            range: new monaco.Range(position.lineNumber, position.column, position.lineNumber, position.column),
 | 
				
			||||||
 | 
					            text: newText,
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					    ]);
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * 导出当前页数据
 | 
					 * 导出当前页数据
 | 
				
			||||||
@@ -486,7 +533,11 @@ const replaceSelection = (str: string, selection: any) => {
 | 
				
			|||||||
const exportData = () => {
 | 
					const exportData = () => {
 | 
				
			||||||
    const dataList = state.execRes.data as any;
 | 
					    const dataList = state.execRes.data as any;
 | 
				
			||||||
    isTrue(dataList.length > 0, '没有数据可导出');
 | 
					    isTrue(dataList.length > 0, '没有数据可导出');
 | 
				
			||||||
    exportCsv(`数据查询导出-${dateStrFormat('yyyyMMddHHmm', new Date().toString())}`, state.execRes.tableColumn, dataList)
 | 
					    exportCsv(
 | 
				
			||||||
 | 
					        `数据查询导出-${dateStrFormat('yyyyMMddHHmm', new Date().toString())}`,
 | 
				
			||||||
 | 
					        state.execRes.tableColumn.map((x: any) => x.columnName),
 | 
				
			||||||
 | 
					        dataList
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const beforeUpload = (file: File) => {
 | 
					const beforeUpload = (file: File) => {
 | 
				
			||||||
@@ -505,7 +556,6 @@ const getUploadSqlFileUrl = () => {
 | 
				
			|||||||
    return `${config.baseApiUrl}/dbs/${state.ti.dbId}/exec-sql-file?db=${state.ti.db}`;
 | 
					    return `${config.baseApiUrl}/dbs/${state.ti.dbId}/exec-sql-file?db=${state.ti.db}`;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 | 
				
			||||||
const onDataSelectionChange = (datas: []) => {
 | 
					const onDataSelectionChange = (datas: []) => {
 | 
				
			||||||
    state.selectionDatas = datas;
 | 
					    state.selectionDatas = datas;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
@@ -513,7 +563,7 @@ const onDataSelectionChange = (datas: []) => {
 | 
				
			|||||||
const changeUpdatedField = (updatedFields: []) => {
 | 
					const changeUpdatedField = (updatedFields: []) => {
 | 
				
			||||||
    // 如果存在要更新字段,则显示提交和取消按钮
 | 
					    // 如果存在要更新字段,则显示提交和取消按钮
 | 
				
			||||||
    state.hasUpdatedFileds = updatedFields && updatedFields.length > 0;
 | 
					    state.hasUpdatedFileds = updatedFields && updatedFields.length > 0;
 | 
				
			||||||
}
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * 执行删除数据事件
 | 
					 * 执行删除数据事件
 | 
				
			||||||
@@ -522,7 +572,7 @@ const onDeleteData = async () => {
 | 
				
			|||||||
    const deleteDatas = state.selectionDatas;
 | 
					    const deleteDatas = state.selectionDatas;
 | 
				
			||||||
    isTrue(deleteDatas && deleteDatas.length > 0, '请先选择要删除的数据');
 | 
					    isTrue(deleteDatas && deleteDatas.length > 0, '请先选择要删除的数据');
 | 
				
			||||||
    const { db } = state.ti;
 | 
					    const { db } = state.ti;
 | 
				
			||||||
    const dbInst = state.ti.getNowDbInst()
 | 
					    const dbInst = state.ti.getNowDbInst();
 | 
				
			||||||
    const primaryKey = await dbInst.loadTableColumn(db, state.table);
 | 
					    const primaryKey = await dbInst.loadTableColumn(db, state.table);
 | 
				
			||||||
    const primaryKeyColumnName = primaryKey.columnName;
 | 
					    const primaryKeyColumnName = primaryKey.columnName;
 | 
				
			||||||
    dbInst.promptExeSql(db, dbInst.genDeleteByPrimaryKeysSql(db, state.table, deleteDatas), null, () => {
 | 
					    dbInst.promptExeSql(db, dbInst.genDeleteByPrimaryKeysSql(db, state.table, deleteDatas), null, () => {
 | 
				
			||||||
@@ -535,12 +585,313 @@ const onDeleteData = async () => {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
const submitUpdateFields = () => {
 | 
					const submitUpdateFields = () => {
 | 
				
			||||||
    dbTableRef.value.submitUpdateFields();
 | 
					    dbTableRef.value.submitUpdateFields();
 | 
				
			||||||
}
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const cancelUpdateFields = () => {
 | 
					const cancelUpdateFields = () => {
 | 
				
			||||||
    dbTableRef.value.cancelUpdateFields();
 | 
					    dbTableRef.value.cancelUpdateFields();
 | 
				
			||||||
}
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const registerSqlCompletionItemProvider = () => {
 | 
				
			||||||
 | 
					    // 参考 https://microsoft.github.io/monaco-editor/playground.html#extending-language-services-completion-provider-example
 | 
				
			||||||
 | 
					    self.completionItemProvider =
 | 
				
			||||||
 | 
					        self.completionItemProvider ||
 | 
				
			||||||
 | 
					        monaco.languages.registerCompletionItemProvider('sql', {
 | 
				
			||||||
 | 
					            triggerCharacters: ['.', ' '],
 | 
				
			||||||
 | 
					            provideCompletionItems: async (model: editor.ITextModel, position: Position): Promise<languages.CompletionList | null | undefined> => {
 | 
				
			||||||
 | 
					                let word = model.getWordUntilPosition(position);
 | 
				
			||||||
 | 
					                const nowTab = props.data;
 | 
				
			||||||
 | 
					                if (!nowTab) {
 | 
				
			||||||
 | 
					                    return;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                const { db, dbId } = nowTab;
 | 
				
			||||||
 | 
					                const dbInst = DbInst.getInst(dbId);
 | 
				
			||||||
 | 
					                const { lineNumber, column } = position;
 | 
				
			||||||
 | 
					                const { startColumn, endColumn } = word;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                // 当前行文本
 | 
				
			||||||
 | 
					                let lineContent = model.getLineContent(lineNumber);
 | 
				
			||||||
 | 
					                // 注释行不需要代码提示
 | 
				
			||||||
 | 
					                if (lineContent.startsWith('--')) {
 | 
				
			||||||
 | 
					                    return { suggestions: [] };
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                let range = {
 | 
				
			||||||
 | 
					                    startLineNumber: lineNumber,
 | 
				
			||||||
 | 
					                    endLineNumber: lineNumber,
 | 
				
			||||||
 | 
					                    startColumn,
 | 
				
			||||||
 | 
					                    endColumn,
 | 
				
			||||||
 | 
					                };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                //  光标前文本
 | 
				
			||||||
 | 
					                const textBeforePointer = model.getValueInRange({
 | 
				
			||||||
 | 
					                    startLineNumber: lineNumber,
 | 
				
			||||||
 | 
					                    startColumn: 0,
 | 
				
			||||||
 | 
					                    endLineNumber: lineNumber,
 | 
				
			||||||
 | 
					                    endColumn: column,
 | 
				
			||||||
 | 
					                });
 | 
				
			||||||
 | 
					                const textBeforePointerMulti = model.getValueInRange({
 | 
				
			||||||
 | 
					                    startLineNumber: 1,
 | 
				
			||||||
 | 
					                    startColumn: 0,
 | 
				
			||||||
 | 
					                    endLineNumber: lineNumber,
 | 
				
			||||||
 | 
					                    endColumn: column,
 | 
				
			||||||
 | 
					                });
 | 
				
			||||||
 | 
					                // 光标后文本
 | 
				
			||||||
 | 
					                const textAfterPointerMulti = model.getValueInRange({
 | 
				
			||||||
 | 
					                    startLineNumber: lineNumber,
 | 
				
			||||||
 | 
					                    startColumn: column,
 | 
				
			||||||
 | 
					                    endLineNumber: model.getLineCount(),
 | 
				
			||||||
 | 
					                    endColumn: model.getLineMaxColumn(model.getLineCount()),
 | 
				
			||||||
 | 
					                });
 | 
				
			||||||
 | 
					                // // const nextTokens = textAfterPointer.trim().split(/\s+/)
 | 
				
			||||||
 | 
					                // // const nextToken = nextTokens[0].toLowerCase()
 | 
				
			||||||
 | 
					                const tokens = textBeforePointer.trim().split(/\s+/);
 | 
				
			||||||
 | 
					                let lastToken = tokens[tokens.length - 1].toLowerCase();
 | 
				
			||||||
 | 
					                const secondToken = (tokens.length > 2 && tokens[tokens.length - 2].toLowerCase()) || '';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                const dbs = (nowTab.params && nowTab.params.dbs && nowTab.params.dbs) || [];
 | 
				
			||||||
 | 
					                // console.log("光标前文本:=>" + textBeforePointerMulti)
 | 
				
			||||||
 | 
					                // console.log("最后输入的:=>" + lastToken)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                let suggestions: languages.CompletionItem[] = [];
 | 
				
			||||||
 | 
					                const tables = await dbInst.loadTables(db);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                async function hintTableColumns(tableName: any, db: any) {
 | 
				
			||||||
 | 
					                    let dbHits = await dbInst.loadDbHints(db);
 | 
				
			||||||
 | 
					                    let columns = dbHits[tableName];
 | 
				
			||||||
 | 
					                    let suggestions: languages.CompletionItem[] = [];
 | 
				
			||||||
 | 
					                    columns?.forEach((a: string, index: any) => {
 | 
				
			||||||
 | 
					                        // 字段数据格式  字段名 字段注释,  如: create_time  [datetime][创建时间]
 | 
				
			||||||
 | 
					                        const nameAndComment = a.split('  ');
 | 
				
			||||||
 | 
					                        const fieldName = nameAndComment[0];
 | 
				
			||||||
 | 
					                        suggestions.push({
 | 
				
			||||||
 | 
					                            label: {
 | 
				
			||||||
 | 
					                                label: a,
 | 
				
			||||||
 | 
					                                description: 'column',
 | 
				
			||||||
 | 
					                            },
 | 
				
			||||||
 | 
					                            kind: monaco.languages.CompletionItemKind.Property,
 | 
				
			||||||
 | 
					                            detail: '', // 不显示detail, 否则选中时备注等会被遮挡
 | 
				
			||||||
 | 
					                            insertText: fieldName, // create_time
 | 
				
			||||||
 | 
					                            range,
 | 
				
			||||||
 | 
					                            sortText: 100 + index + '', // 使用表字段声明顺序排序,排序需为字符串类型
 | 
				
			||||||
 | 
					                        });
 | 
				
			||||||
 | 
					                    });
 | 
				
			||||||
 | 
					                    return suggestions;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                if (lastToken.indexOf('.') > -1 || secondToken.indexOf('.') > -1) {
 | 
				
			||||||
 | 
					                    // 如果是.触发代码提示,则进行【 库.表名联想 】 或 【 表别名.表字段联想 】
 | 
				
			||||||
 | 
					                    let str = lastToken.substring(0, lastToken.lastIndexOf('.'));
 | 
				
			||||||
 | 
					                    if (lastToken.trim().startsWith('.')) {
 | 
				
			||||||
 | 
					                        str = secondToken;
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                    // 如果字符串粘连起了如:'a.creator,a.',需要重新取出别名
 | 
				
			||||||
 | 
					                    let aliasArr = lastToken.split(',');
 | 
				
			||||||
 | 
					                    if (aliasArr.length > 1) {
 | 
				
			||||||
 | 
					                        lastToken = aliasArr[aliasArr.length - 1];
 | 
				
			||||||
 | 
					                        str = lastToken.substring(0, lastToken.lastIndexOf('.'));
 | 
				
			||||||
 | 
					                        if (lastToken.trim().startsWith('.')) {
 | 
				
			||||||
 | 
					                            str = secondToken;
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                    // 库.表名联想
 | 
				
			||||||
 | 
					                    if (dbs && dbs.filter((a: any) => a === str)?.length > 0) {
 | 
				
			||||||
 | 
					                        let tables = await dbInst.loadTables(str);
 | 
				
			||||||
 | 
					                        let suggestions: languages.CompletionItem[] = [];
 | 
				
			||||||
 | 
					                        for (let item of tables) {
 | 
				
			||||||
 | 
					                            const { tableName, tableComment } = item;
 | 
				
			||||||
 | 
					                            suggestions.push({
 | 
				
			||||||
 | 
					                                label: {
 | 
				
			||||||
 | 
					                                    label: tableName + (tableComment ? ' - ' + tableComment : ''),
 | 
				
			||||||
 | 
					                                    description: 'table',
 | 
				
			||||||
 | 
					                                },
 | 
				
			||||||
 | 
					                                kind: monaco.languages.CompletionItemKind.File,
 | 
				
			||||||
 | 
					                                insertText: tableName,
 | 
				
			||||||
 | 
					                                range,
 | 
				
			||||||
 | 
					                            });
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                        return { suggestions };
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    let sql = textBeforePointerMulti.split(';')[textBeforePointerMulti.split(';').length - 1] + textAfterPointerMulti.split(';')[0];
 | 
				
			||||||
 | 
					                    // 表别名.表字段联想
 | 
				
			||||||
 | 
					                    let tableInfo = getTableByAlias(sql, db, str);
 | 
				
			||||||
 | 
					                    if (tableInfo.tableName) {
 | 
				
			||||||
 | 
					                        let tableName = tableInfo.tableName;
 | 
				
			||||||
 | 
					                        let db = tableInfo.dbName;
 | 
				
			||||||
 | 
					                        // 取出表名并提示
 | 
				
			||||||
 | 
					                        let suggestions = await hintTableColumns(tableName, db);
 | 
				
			||||||
 | 
					                        if (suggestions.length > 0) {
 | 
				
			||||||
 | 
					                            return { suggestions };
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                    return { suggestions: [] };
 | 
				
			||||||
 | 
					                } else {
 | 
				
			||||||
 | 
					                    // 如果sql里含有表名,则提示表字段
 | 
				
			||||||
 | 
					                    let mat = textBeforePointerMulti.match(/[from|update]\n*\s+\n*(\w+)\n*\s+\n*/i);
 | 
				
			||||||
 | 
					                    if (mat && mat.length > 1) {
 | 
				
			||||||
 | 
					                        let tableName = mat[1];
 | 
				
			||||||
 | 
					                        // 取出表名并提示
 | 
				
			||||||
 | 
					                        let addSuggestions = await hintTableColumns(tableName, db);
 | 
				
			||||||
 | 
					                        if (addSuggestions.length > 0) {
 | 
				
			||||||
 | 
					                            suggestions = suggestions.concat(addSuggestions);
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                // 表名联想
 | 
				
			||||||
 | 
					                tables.forEach((tableMeta: any) => {
 | 
				
			||||||
 | 
					                    const { tableName, tableComment } = tableMeta;
 | 
				
			||||||
 | 
					                    suggestions.push({
 | 
				
			||||||
 | 
					                        label: {
 | 
				
			||||||
 | 
					                            label: tableName + ' - ' + tableComment,
 | 
				
			||||||
 | 
					                            description: 'table',
 | 
				
			||||||
 | 
					                        },
 | 
				
			||||||
 | 
					                        kind: monaco.languages.CompletionItemKind.File,
 | 
				
			||||||
 | 
					                        detail: tableComment,
 | 
				
			||||||
 | 
					                        insertText: tableName + ' ',
 | 
				
			||||||
 | 
					                        range,
 | 
				
			||||||
 | 
					                    });
 | 
				
			||||||
 | 
					                });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                // mysql关键字
 | 
				
			||||||
 | 
					                sqlCompletionKeywords.forEach((item: any) => {
 | 
				
			||||||
 | 
					                    suggestions.push({
 | 
				
			||||||
 | 
					                        label: {
 | 
				
			||||||
 | 
					                            label: item,
 | 
				
			||||||
 | 
					                            description: 'keyword',
 | 
				
			||||||
 | 
					                        },
 | 
				
			||||||
 | 
					                        kind: monaco.languages.CompletionItemKind.Keyword,
 | 
				
			||||||
 | 
					                        insertText: item,
 | 
				
			||||||
 | 
					                        range,
 | 
				
			||||||
 | 
					                    });
 | 
				
			||||||
 | 
					                });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                // 操作符
 | 
				
			||||||
 | 
					                sqlCompletionOperators.forEach((item: any) => {
 | 
				
			||||||
 | 
					                    suggestions.push({
 | 
				
			||||||
 | 
					                        label: {
 | 
				
			||||||
 | 
					                            label: item,
 | 
				
			||||||
 | 
					                            description: 'opt',
 | 
				
			||||||
 | 
					                        },
 | 
				
			||||||
 | 
					                        kind: monaco.languages.CompletionItemKind.Operator,
 | 
				
			||||||
 | 
					                        insertText: item,
 | 
				
			||||||
 | 
					                        range,
 | 
				
			||||||
 | 
					                    });
 | 
				
			||||||
 | 
					                });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                let replacedFunctions = [] as string[];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                // 添加的函数
 | 
				
			||||||
 | 
					                addSqlLanguage.replaceFunctions.forEach((item: any) => {
 | 
				
			||||||
 | 
					                    replacedFunctions.push(item.label);
 | 
				
			||||||
 | 
					                    suggestions.push({
 | 
				
			||||||
 | 
					                        label: {
 | 
				
			||||||
 | 
					                            label: item.label,
 | 
				
			||||||
 | 
					                            description: item.description,
 | 
				
			||||||
 | 
					                        },
 | 
				
			||||||
 | 
					                        kind: monaco.languages.CompletionItemKind.Function,
 | 
				
			||||||
 | 
					                        insertText: item.insertText,
 | 
				
			||||||
 | 
					                        range,
 | 
				
			||||||
 | 
					                    });
 | 
				
			||||||
 | 
					                });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                // 内置函数
 | 
				
			||||||
 | 
					                sqlCompletionBuiltinFunctions.forEach((item: any) => {
 | 
				
			||||||
 | 
					                    replacedFunctions.indexOf(item) < 0 &&
 | 
				
			||||||
 | 
					                        suggestions.push({
 | 
				
			||||||
 | 
					                            label: {
 | 
				
			||||||
 | 
					                                label: item,
 | 
				
			||||||
 | 
					                                description: 'func',
 | 
				
			||||||
 | 
					                            },
 | 
				
			||||||
 | 
					                            kind: monaco.languages.CompletionItemKind.Function,
 | 
				
			||||||
 | 
					                            insertText: item,
 | 
				
			||||||
 | 
					                            range,
 | 
				
			||||||
 | 
					                        });
 | 
				
			||||||
 | 
					                });
 | 
				
			||||||
 | 
					                // 内置变量
 | 
				
			||||||
 | 
					                sqlCompletionBuiltinVariables.forEach((item: string) => {
 | 
				
			||||||
 | 
					                    suggestions.push({
 | 
				
			||||||
 | 
					                        label: {
 | 
				
			||||||
 | 
					                            label: item,
 | 
				
			||||||
 | 
					                            description: 'var',
 | 
				
			||||||
 | 
					                        },
 | 
				
			||||||
 | 
					                        kind: monaco.languages.CompletionItemKind.Variable,
 | 
				
			||||||
 | 
					                        insertText: item,
 | 
				
			||||||
 | 
					                        range,
 | 
				
			||||||
 | 
					                    });
 | 
				
			||||||
 | 
					                });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                // 库名提示
 | 
				
			||||||
 | 
					                if (dbs && dbs.length > 0) {
 | 
				
			||||||
 | 
					                    dbs.forEach((a: any) => {
 | 
				
			||||||
 | 
					                        suggestions.push({
 | 
				
			||||||
 | 
					                            label: {
 | 
				
			||||||
 | 
					                                label: a,
 | 
				
			||||||
 | 
					                                description: 'schema',
 | 
				
			||||||
 | 
					                            },
 | 
				
			||||||
 | 
					                            kind: monaco.languages.CompletionItemKind.Folder,
 | 
				
			||||||
 | 
					                            insertText: a,
 | 
				
			||||||
 | 
					                            range,
 | 
				
			||||||
 | 
					                        });
 | 
				
			||||||
 | 
					                    });
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                // 默认提示
 | 
				
			||||||
 | 
					                return {
 | 
				
			||||||
 | 
					                    suggestions: suggestions,
 | 
				
			||||||
 | 
					                };
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * 根据别名获取sql里的表名
 | 
				
			||||||
 | 
					 * @param sql sql
 | 
				
			||||||
 | 
					 * @param db 默认数据库
 | 
				
			||||||
 | 
					 * @param alias 别名
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					const getTableByAlias = (sql: string, db: string, alias: string): { dbName: string; tableName: string } => {
 | 
				
			||||||
 | 
					    // 表别名:表名
 | 
				
			||||||
 | 
					    let result = {};
 | 
				
			||||||
 | 
					    let defName = '';
 | 
				
			||||||
 | 
					    let defResult = {};
 | 
				
			||||||
 | 
					    // 正则匹配取出表名和表别名
 | 
				
			||||||
 | 
					    // 测试sql
 | 
				
			||||||
 | 
					    /*
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    `select * from database.Outvisit l
 | 
				
			||||||
 | 
					left join patient p on l.patid=p.patientid
 | 
				
			||||||
 | 
					join patstatic c on   l.patid=c.patid inner join patphone  ph  on l.patid=ph.patid
 | 
				
			||||||
 | 
					where l.name='kevin' and exsits(select 1 from pharmacywestpas pw where p.outvisitid=l.outvisitid)
 | 
				
			||||||
 | 
					unit all
 | 
				
			||||||
 | 
					select * from invisit v where`.match(/(join|from)\s+(\w*-?\w*\.?\w+)\s*(as)?\s*(\w*)/gi)
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    let match = sql.match(/(join|from)\n*\s+\n*(\w*-?\w*\.?\w+)\s*(as)?\s*(\w*)\n*/gi);
 | 
				
			||||||
 | 
					    if (match && match.length > 0) {
 | 
				
			||||||
 | 
					        match.forEach((a) => {
 | 
				
			||||||
 | 
					            // 去掉前缀,取出
 | 
				
			||||||
 | 
					            let t = a
 | 
				
			||||||
 | 
					                .substring(5, a.length)
 | 
				
			||||||
 | 
					                .replaceAll(/\s+/g, ' ')
 | 
				
			||||||
 | 
					                .replaceAll(/\s+as\s+/gi, ' ')
 | 
				
			||||||
 | 
					                .replaceAll(/\r\n/g, ' ')
 | 
				
			||||||
 | 
					                .trim()
 | 
				
			||||||
 | 
					                .split(/\s+/);
 | 
				
			||||||
 | 
					            let withDb = t[0].split('.');
 | 
				
			||||||
 | 
					            // 表名是 db名.表名
 | 
				
			||||||
 | 
					            let tName = withDb.length > 1 ? withDb[1] : withDb[0];
 | 
				
			||||||
 | 
					            let dbName = withDb.length > 1 ? withDb[0] : db || '';
 | 
				
			||||||
 | 
					            if (t.length == 2) {
 | 
				
			||||||
 | 
					                // 表别名:表名
 | 
				
			||||||
 | 
					                result[t[1]] = { tableName: tName, dbName };
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					                // 只有表名无别名 取第一个无别名的表为默认表
 | 
				
			||||||
 | 
					                !defName && (defResult = { tableName: tName, dbName: db });
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    return result[alias] || defResult;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
</script>
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<style lang="scss">
 | 
					<style lang="scss">
 | 
				
			||||||
@@ -557,12 +908,11 @@ const cancelUpdateFields = () => {
 | 
				
			|||||||
.sqlEditor {
 | 
					.sqlEditor {
 | 
				
			||||||
    font-size: 8pt;
 | 
					    font-size: 8pt;
 | 
				
			||||||
    font-weight: 600;
 | 
					    font-weight: 600;
 | 
				
			||||||
    border: 1px solid #ccc;
 | 
					    border: 1px solid var(--el-border-color-light, #ebeef5);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 | 
				
			||||||
.update_field_active {
 | 
					.update_field_active {
 | 
				
			||||||
    background-color: var(--el-color-success)
 | 
					    background-color: var(--el-color-success);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.editor-move-resize {
 | 
					.editor-move-resize {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -2,8 +2,29 @@
 | 
				
			|||||||
    <div>
 | 
					    <div>
 | 
				
			||||||
        <el-row>
 | 
					        <el-row>
 | 
				
			||||||
            <el-col :span="8">
 | 
					            <el-col :span="8">
 | 
				
			||||||
                <el-link @click="onRefresh()" icon="refresh" :underline="false" class="ml5">
 | 
					                <el-link @click="onRefresh()" icon="refresh" :underline="false" class="ml5"> </el-link>
 | 
				
			||||||
                </el-link>
 | 
					                <el-divider direction="vertical" border-style="dashed" />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                <el-popover
 | 
				
			||||||
 | 
					                    popper-style="max-height: 550px; overflow: auto; max-width: 450px"
 | 
				
			||||||
 | 
					                    placement="bottom"
 | 
				
			||||||
 | 
					                    width="auto"
 | 
				
			||||||
 | 
					                    title="表格字段配置"
 | 
				
			||||||
 | 
					                    trigger="click"
 | 
				
			||||||
 | 
					                >
 | 
				
			||||||
 | 
					                    <div v-for="(item, index) in columns" :key="index">
 | 
				
			||||||
 | 
					                        <el-checkbox
 | 
				
			||||||
 | 
					                            v-model="item.show"
 | 
				
			||||||
 | 
					                            :label="`${!item.columnComment ? item.columnName : item.columnName + ' [' + item.columnComment + ']'}`"
 | 
				
			||||||
 | 
					                            :true-label="true"
 | 
				
			||||||
 | 
					                            :false-label="false"
 | 
				
			||||||
 | 
					                            size="small"
 | 
				
			||||||
 | 
					                        />
 | 
				
			||||||
 | 
					                    </div>
 | 
				
			||||||
 | 
					                    <template #reference>
 | 
				
			||||||
 | 
					                        <el-link icon="Operation" size="small" :underline="false"></el-link>
 | 
				
			||||||
 | 
					                    </template>
 | 
				
			||||||
 | 
					                </el-popover>
 | 
				
			||||||
                <el-divider direction="vertical" border-style="dashed" />
 | 
					                <el-divider direction="vertical" border-style="dashed" />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                <el-link @click="onShowAddDataDialog()" type="primary" icon="plus" :underline="false"></el-link>
 | 
					                <el-link @click="onShowAddDataDialog()" type="primary" icon="plus" :underline="false"></el-link>
 | 
				
			||||||
@@ -13,8 +34,7 @@
 | 
				
			|||||||
                <el-divider direction="vertical" border-style="dashed" />
 | 
					                <el-divider direction="vertical" border-style="dashed" />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                <el-tooltip class="box-item" effect="dark" content="commit" placement="top">
 | 
					                <el-tooltip class="box-item" effect="dark" content="commit" placement="top">
 | 
				
			||||||
                    <el-link @click="onCommit()" type="success" icon="CircleCheck" :underline="false">
 | 
					                    <el-link @click="onCommit()" type="success" icon="CircleCheck" :underline="false"> </el-link>
 | 
				
			||||||
                    </el-link>
 | 
					 | 
				
			||||||
                </el-tooltip>
 | 
					                </el-tooltip>
 | 
				
			||||||
                <el-divider direction="vertical" border-style="dashed" />
 | 
					                <el-divider direction="vertical" border-style="dashed" />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -37,22 +57,31 @@
 | 
				
			|||||||
                </el-tooltip>
 | 
					                </el-tooltip>
 | 
				
			||||||
            </el-col>
 | 
					            </el-col>
 | 
				
			||||||
            <el-col :span="16">
 | 
					            <el-col :span="16">
 | 
				
			||||||
                <el-input v-model="condition" placeholder="若需条件过滤,可选择列并点击对应的字段并输入需要过滤的内容点击查询按钮即可" clearable
 | 
					                <el-input
 | 
				
			||||||
                    @clear="selectData" size="small" style="width: 100%">
 | 
					                    v-model="condition"
 | 
				
			||||||
 | 
					                    placeholder="若需条件过滤,可选择列并点击对应的字段并输入需要过滤的内容点击查询按钮即可"
 | 
				
			||||||
 | 
					                    clearable
 | 
				
			||||||
 | 
					                    @clear="selectData"
 | 
				
			||||||
 | 
					                    size="small"
 | 
				
			||||||
 | 
					                    style="width: 100%"
 | 
				
			||||||
 | 
					                >
 | 
				
			||||||
                    <template #prepend>
 | 
					                    <template #prepend>
 | 
				
			||||||
                        <el-popover trigger="click" :width="320" placement="right">
 | 
					                        <el-popover :visible="state.condPopVisible" trigger="click" :width="320" placement="right">
 | 
				
			||||||
                            <template #reference>
 | 
					                            <template #reference>
 | 
				
			||||||
                                <el-link type="success" :underline="false">选择列</el-link>
 | 
					                                <el-link @click.stop="state.condPopVisible = !state.condPopVisible" type="success" :underline="false">选择列</el-link>
 | 
				
			||||||
                            </template>
 | 
					                            </template>
 | 
				
			||||||
                            <el-table :data="columns" max-height="500" size="small" @row-click="
 | 
					                            <el-table
 | 
				
			||||||
                                (...event: any) => {
 | 
					                                :data="columns"
 | 
				
			||||||
 | 
					                                max-height="500"
 | 
				
			||||||
 | 
					                                size="small"
 | 
				
			||||||
 | 
					                                @row-click="(...event: any) => {
 | 
				
			||||||
                                onConditionRowClick(event);
 | 
					                                onConditionRowClick(event);
 | 
				
			||||||
                            }
 | 
					                            }
 | 
				
			||||||
                            " style="cursor: pointer">
 | 
					                                "
 | 
				
			||||||
                                <el-table-column property="columnName" label="列名" show-overflow-tooltip>
 | 
					                                style="cursor: pointer"
 | 
				
			||||||
                                </el-table-column>
 | 
					                            >
 | 
				
			||||||
                                <el-table-column property="columnComment" label="备注" show-overflow-tooltip>
 | 
					                                <el-table-column property="columnName" label="列名" show-overflow-tooltip> </el-table-column>
 | 
				
			||||||
                                </el-table-column>
 | 
					                                <el-table-column property="columnComment" label="备注" show-overflow-tooltip> </el-table-column>
 | 
				
			||||||
                            </el-table>
 | 
					                            </el-table>
 | 
				
			||||||
                        </el-popover>
 | 
					                        </el-popover>
 | 
				
			||||||
                    </template>
 | 
					                    </template>
 | 
				
			||||||
@@ -64,16 +93,36 @@
 | 
				
			|||||||
            </el-col>
 | 
					            </el-col>
 | 
				
			||||||
        </el-row>
 | 
					        </el-row>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        <db-table ref="dbTableRef" :db-id="state.ti.dbId" :db="state.ti.db" :data="datas" :table="state.table"
 | 
					        <db-table
 | 
				
			||||||
            :column-names="columnNames" :loading="loading" :height="tableHeight" :show-column-tip="true"
 | 
					            ref="dbTableRef"
 | 
				
			||||||
            :sortable="'custom'" @sort-change="(sort: any) => onTableSortChange(sort)"
 | 
					            :db-id="state.ti.dbId"
 | 
				
			||||||
            @selection-change="onDataSelectionChange" @change-updated-field="changeUpdatedField"></db-table>
 | 
					            :db="state.ti.db"
 | 
				
			||||||
 | 
					            :data="datas"
 | 
				
			||||||
 | 
					            :table="state.table"
 | 
				
			||||||
 | 
					            :columns="columns"
 | 
				
			||||||
 | 
					            :loading="loading"
 | 
				
			||||||
 | 
					            :height="tableHeight"
 | 
				
			||||||
 | 
					            :show-column-tip="true"
 | 
				
			||||||
 | 
					            :sortable="'custom'"
 | 
				
			||||||
 | 
					            @sort-change="(sort: any) => onTableSortChange(sort)"
 | 
				
			||||||
 | 
					            @selection-change="onDataSelectionChange"
 | 
				
			||||||
 | 
					            @change-updated-field="changeUpdatedField"
 | 
				
			||||||
 | 
					        ></db-table>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        <el-row type="flex" class="mt5" justify="center">
 | 
					        <el-row type="flex" class="mt5" justify="center">
 | 
				
			||||||
            <el-pagination small :total="count" @current-change="pageChange()" layout="prev, pager, next, total, jumper"
 | 
					            <el-pagination
 | 
				
			||||||
                v-model:current-page="pageNum" :page-size="DbInst.DefaultLimit"></el-pagination>
 | 
					                small
 | 
				
			||||||
 | 
					                :total="count"
 | 
				
			||||||
 | 
					                @size-change="handleSizeChange"
 | 
				
			||||||
 | 
					                @current-change="pageChange()"
 | 
				
			||||||
 | 
					                layout="prev, pager, next, total, sizes, jumper"
 | 
				
			||||||
 | 
					                v-model:current-page="pageNum"
 | 
				
			||||||
 | 
					                v-model:page-size="pageSize"
 | 
				
			||||||
 | 
					                :page-sizes="pageSizes"
 | 
				
			||||||
 | 
					            ></el-pagination>
 | 
				
			||||||
        </el-row>
 | 
					        </el-row>
 | 
				
			||||||
        <div style=" font-size: 12px; padding: 0 10px; color: #606266"><span>{{ state.sql }}</span>
 | 
					        <div style="font-size: 12px; padding: 0 10px; color: #606266">
 | 
				
			||||||
 | 
					            <span>{{ state.sql }}</span>
 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        <el-dialog v-model="conditionDialog.visible" :title="conditionDialog.title" width="420px">
 | 
					        <el-dialog v-model="conditionDialog.visible" :title="conditionDialog.title" width="420px">
 | 
				
			||||||
@@ -89,7 +138,7 @@
 | 
				
			|||||||
                    </el-select>
 | 
					                    </el-select>
 | 
				
			||||||
                </el-col>
 | 
					                </el-col>
 | 
				
			||||||
                <el-col :span="19">
 | 
					                <el-col :span="19">
 | 
				
			||||||
                    <el-input v-model="conditionDialog.value" :placeholder="conditionDialog.placeholder" />
 | 
					                    <el-input ref="conditionInputRef" v-model="conditionDialog.value" :placeholder="conditionDialog.placeholder" />
 | 
				
			||||||
                </el-col>
 | 
					                </el-col>
 | 
				
			||||||
            </el-row>
 | 
					            </el-row>
 | 
				
			||||||
            <template #footer>
 | 
					            <template #footer>
 | 
				
			||||||
@@ -101,15 +150,22 @@
 | 
				
			|||||||
        </el-dialog>
 | 
					        </el-dialog>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        <el-dialog v-model="addDataDialog.visible" :title="addDataDialog.title" :destroy-on-close="true" width="600px">
 | 
					        <el-dialog v-model="addDataDialog.visible" :title="addDataDialog.title" :destroy-on-close="true" width="600px">
 | 
				
			||||||
            <el-form ref="dataForm" :model="addDataDialog.data" label-width="160px" size="small">
 | 
					            <el-form ref="dataForm" :model="addDataDialog.data" label-width="auto" size="small">
 | 
				
			||||||
                <el-form-item v-for="column in columns" class="w100" :prop="column.columnName" :label="column.columnName"
 | 
					                <el-form-item
 | 
				
			||||||
                    :required="column.nullable != 'YES' && column.columnKey != 'PRI'">
 | 
					                    v-for="column in columns"
 | 
				
			||||||
                    <el-input-number v-if="DbInst.isNumber(column.columnType)"
 | 
					                    class="w100"
 | 
				
			||||||
 | 
					                    :prop="column.columnName"
 | 
				
			||||||
 | 
					                    :label="column.columnName"
 | 
				
			||||||
 | 
					                    :required="column.nullable != 'YES' && column.columnKey != 'PRI'"
 | 
				
			||||||
 | 
					                >
 | 
				
			||||||
 | 
					                    <el-input-number
 | 
				
			||||||
 | 
					                        v-if="DbInst.isNumber(column.columnType)"
 | 
				
			||||||
                        v-model="addDataDialog.data[`${column.columnName}`]"
 | 
					                        v-model="addDataDialog.data[`${column.columnName}`]"
 | 
				
			||||||
                        :placeholder="`${column.columnType}  ${column.columnComment}`" class="w100" />
 | 
					                        :placeholder="`${column.columnType}  ${column.columnComment}`"
 | 
				
			||||||
 | 
					                        class="w100"
 | 
				
			||||||
 | 
					                    />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    <el-input v-else v-model="addDataDialog.data[`${column.columnName}`]"
 | 
					                    <el-input v-else v-model="addDataDialog.data[`${column.columnName}`]" :placeholder="`${column.columnType}  ${column.columnComment}`" />
 | 
				
			||||||
                        :placeholder="`${column.columnType}  ${column.columnComment}`" />
 | 
					 | 
				
			||||||
                </el-form-item>
 | 
					                </el-form-item>
 | 
				
			||||||
            </el-form>
 | 
					            </el-form>
 | 
				
			||||||
            <template #footer>
 | 
					            <template #footer>
 | 
				
			||||||
@@ -123,28 +179,29 @@
 | 
				
			|||||||
</template>
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<script lang="ts" setup>
 | 
					<script lang="ts" setup>
 | 
				
			||||||
import { onMounted, watch, reactive, toRefs, ref, Ref } from 'vue';
 | 
					import { onMounted, watch, reactive, toRefs, ref, Ref, onUnmounted } from 'vue';
 | 
				
			||||||
import { isTrue, notEmpty, notBlank } from '@/common/assert';
 | 
					import { isTrue, notEmpty, notBlank } from '@/common/assert';
 | 
				
			||||||
import { ElMessage } from 'element-plus';
 | 
					import { ElMessage } from 'element-plus';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import { DbInst, TabInfo } from '../../db';
 | 
					import { DbInst, TabInfo } from '../../db';
 | 
				
			||||||
import { exportCsv } from '@/common/utils/export';
 | 
					import { exportCsv } from '@/common/utils/export';
 | 
				
			||||||
import { dateStrFormat } from '@/common/utils/date';
 | 
					import { dateStrFormat } from '@/common/utils/date';
 | 
				
			||||||
import DbTable from '../DbTable.vue'
 | 
					import DbTable from '../DbTable.vue';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const emits = defineEmits(['genInsertSql'])
 | 
					const emits = defineEmits(['genInsertSql']);
 | 
				
			||||||
const dataForm: any = ref(null);
 | 
					const dataForm: any = ref(null);
 | 
				
			||||||
 | 
					const conditionInputRef: any = ref();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const props = defineProps({
 | 
					const props = defineProps({
 | 
				
			||||||
    data: {
 | 
					    data: {
 | 
				
			||||||
        type: TabInfo,
 | 
					        type: TabInfo,
 | 
				
			||||||
        required: true
 | 
					        required: true,
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    tableHeight: {
 | 
					    tableHeight: {
 | 
				
			||||||
        type: [String],
 | 
					        type: [String],
 | 
				
			||||||
        default: '600'
 | 
					        default: '600',
 | 
				
			||||||
    }
 | 
					    },
 | 
				
			||||||
})
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const dbTableRef = ref(null) as Ref;
 | 
					const dbTableRef = ref(null) as Ref;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -156,11 +213,13 @@ const state = reactive({
 | 
				
			|||||||
    orderBy: '',
 | 
					    orderBy: '',
 | 
				
			||||||
    condition: '', // 当前条件框的条件
 | 
					    condition: '', // 当前条件框的条件
 | 
				
			||||||
    loading: false, // 是否在加载数据
 | 
					    loading: false, // 是否在加载数据
 | 
				
			||||||
    columnNames: [],
 | 
					 | 
				
			||||||
    columns: [] as any,
 | 
					    columns: [] as any,
 | 
				
			||||||
    pageNum: 1,
 | 
					    pageNum: 1,
 | 
				
			||||||
 | 
					    pageSize: DbInst.DefaultLimit,
 | 
				
			||||||
 | 
					    pageSizes: [20, 40, 80, 100, 200, 300, 400],
 | 
				
			||||||
    count: 0,
 | 
					    count: 0,
 | 
				
			||||||
    selectionDatas: [] as any,
 | 
					    selectionDatas: [] as any,
 | 
				
			||||||
 | 
					    condPopVisible: false,
 | 
				
			||||||
    conditionDialog: {
 | 
					    conditionDialog: {
 | 
				
			||||||
        title: '',
 | 
					        title: '',
 | 
				
			||||||
        placeholder: '',
 | 
					        placeholder: '',
 | 
				
			||||||
@@ -168,7 +227,7 @@ const state = reactive({
 | 
				
			|||||||
        dataTab: null,
 | 
					        dataTab: null,
 | 
				
			||||||
        visible: false,
 | 
					        visible: false,
 | 
				
			||||||
        condition: '=',
 | 
					        condition: '=',
 | 
				
			||||||
        value: null
 | 
					        value: null,
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    addDataDialog: {
 | 
					    addDataDialog: {
 | 
				
			||||||
        data: {},
 | 
					        data: {},
 | 
				
			||||||
@@ -180,42 +239,49 @@ const state = reactive({
 | 
				
			|||||||
    hasUpdatedFileds: false,
 | 
					    hasUpdatedFileds: false,
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const {
 | 
					const { datas, condition, loading, columns, pageNum, pageSize, pageSizes, count, hasUpdatedFileds, conditionDialog, addDataDialog } = toRefs(state);
 | 
				
			||||||
    datas,
 | 
					 | 
				
			||||||
    condition,
 | 
					 | 
				
			||||||
    loading,
 | 
					 | 
				
			||||||
    columns,
 | 
					 | 
				
			||||||
    columnNames,
 | 
					 | 
				
			||||||
    pageNum,
 | 
					 | 
				
			||||||
    count,
 | 
					 | 
				
			||||||
    hasUpdatedFileds,
 | 
					 | 
				
			||||||
    conditionDialog,
 | 
					 | 
				
			||||||
    addDataDialog,
 | 
					 | 
				
			||||||
} = toRefs(state);
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
watch(() => props.tableHeight, (newValue: any) => {
 | 
					watch(
 | 
				
			||||||
 | 
					    () => props.tableHeight,
 | 
				
			||||||
 | 
					    (newValue: any) => {
 | 
				
			||||||
        state.tableHeight = newValue;
 | 
					        state.tableHeight = newValue;
 | 
				
			||||||
});
 | 
					    }
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
onMounted(async () => {
 | 
					onMounted(async () => {
 | 
				
			||||||
    console.log('in table data mounted');
 | 
					    console.log('in table data mounted');
 | 
				
			||||||
    state.ti = props.data;
 | 
					    state.ti = props.data;
 | 
				
			||||||
    state.tableHeight = props.tableHeight;
 | 
					    state.tableHeight = props.tableHeight;
 | 
				
			||||||
    state.table = state.ti.params.table;
 | 
					    state.table = state.ti.params.table;
 | 
				
			||||||
    notBlank(state.table, "TableData组件params.table信息不能为空")
 | 
					    notBlank(state.table, 'TableData组件params.table信息不能为空');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const columns = await state.ti.getNowDbInst().loadColumns(state.ti.db, state.table);
 | 
					    const columns = await state.ti.getNowDbInst().loadColumns(state.ti.db, state.table);
 | 
				
			||||||
 | 
					    columns.forEach((x: any) => {
 | 
				
			||||||
 | 
					        x.show = true;
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
    state.columns = columns;
 | 
					    state.columns = columns;
 | 
				
			||||||
    state.columnNames = columns.map((t: any) => t.columnName);
 | 
					 | 
				
			||||||
    await onRefresh();
 | 
					    await onRefresh();
 | 
				
			||||||
})
 | 
					
 | 
				
			||||||
 | 
					    // 点击除选择列按钮外,若存在条件弹窗,则关闭该弹窗
 | 
				
			||||||
 | 
					    window.addEventListener('click', handlerWindowClick);
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					onUnmounted(() => {
 | 
				
			||||||
 | 
					    window.removeEventListener('click', handlerWindowClick);
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const handlerWindowClick = () => {
 | 
				
			||||||
 | 
					    if (state.condPopVisible) {
 | 
				
			||||||
 | 
					        state.condPopVisible = false;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const onRefresh = async () => {
 | 
					const onRefresh = async () => {
 | 
				
			||||||
    // 查询条件置空
 | 
					    // 查询条件置空
 | 
				
			||||||
    state.condition = '';
 | 
					    state.condition = '';
 | 
				
			||||||
    state.pageNum = 1;
 | 
					    state.pageNum = 1;
 | 
				
			||||||
    await selectData();
 | 
					    await selectData();
 | 
				
			||||||
}
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * 数据tab修改页数
 | 
					 * 数据tab修改页数
 | 
				
			||||||
@@ -232,9 +298,9 @@ const selectData = async () => {
 | 
				
			|||||||
    const dbInst = state.ti.getNowDbInst();
 | 
					    const dbInst = state.ti.getNowDbInst();
 | 
				
			||||||
    const { db } = state.ti;
 | 
					    const { db } = state.ti;
 | 
				
			||||||
    try {
 | 
					    try {
 | 
				
			||||||
        const countRes = await dbInst.runSql(db, DbInst.getDefaultCountSql(state.table, state.condition));
 | 
					        const countRes = await dbInst.runSql(db, dbInst.getDefaultCountSql(state.table, state.condition));
 | 
				
			||||||
        state.count = countRes.res[0].count;
 | 
					        state.count = countRes.res[0].count;
 | 
				
			||||||
        let sql = dbInst.getDefaultSelectSql(state.table, state.condition, state.orderBy, state.pageNum);
 | 
					        let sql = dbInst.getDefaultSelectSql(state.table, state.condition, state.orderBy, state.pageNum, state.pageSize);
 | 
				
			||||||
        state.sql = sql;
 | 
					        state.sql = sql;
 | 
				
			||||||
        if (state.count > 0) {
 | 
					        if (state.count > 0) {
 | 
				
			||||||
            const colAndData: any = await dbInst.runSql(db, sql);
 | 
					            const colAndData: any = await dbInst.runSql(db, sql);
 | 
				
			||||||
@@ -245,7 +311,13 @@ const selectData = async () => {
 | 
				
			|||||||
    } finally {
 | 
					    } finally {
 | 
				
			||||||
        state.loading = false;
 | 
					        state.loading = false;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const handleSizeChange = async (size: any) => {
 | 
				
			||||||
 | 
					    state.pageNum = 1;
 | 
				
			||||||
 | 
					    state.pageSize = size;
 | 
				
			||||||
 | 
					    await selectData();
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * 导出当前页数据
 | 
					 * 导出当前页数据
 | 
				
			||||||
@@ -253,10 +325,15 @@ const selectData = async () => {
 | 
				
			|||||||
const exportData = () => {
 | 
					const exportData = () => {
 | 
				
			||||||
    const dataList = state.datas as any;
 | 
					    const dataList = state.datas as any;
 | 
				
			||||||
    isTrue(dataList.length > 0, '没有数据可导出');
 | 
					    isTrue(dataList.length > 0, '没有数据可导出');
 | 
				
			||||||
    exportCsv(`数据导出-${state.table}-${dateStrFormat('yyyyMMddHHmm', new Date().toString())}`, state.columnNames, dataList)
 | 
					    let columnNames = [];
 | 
				
			||||||
 | 
					    for (let column of state.columns) {
 | 
				
			||||||
 | 
					        if (column.show) {
 | 
				
			||||||
 | 
					            columnNames.push(column.columnName);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    exportCsv(`数据导出-${state.table}-${dateStrFormat('yyyyMMddHHmm', new Date().toString())}`, columnNames, dataList);
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * 条件查询,点击列信息后显示输入对应的值
 | 
					 * 条件查询,点击列信息后显示输入对应的值
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
@@ -266,6 +343,9 @@ const onConditionRowClick = (event: any) => {
 | 
				
			|||||||
    state.conditionDialog.placeholder = `${row.columnType}  ${row.columnComment}`;
 | 
					    state.conditionDialog.placeholder = `${row.columnType}  ${row.columnComment}`;
 | 
				
			||||||
    state.conditionDialog.columnRow = row;
 | 
					    state.conditionDialog.columnRow = row;
 | 
				
			||||||
    state.conditionDialog.visible = true;
 | 
					    state.conditionDialog.visible = true;
 | 
				
			||||||
 | 
					    setTimeout(() => {
 | 
				
			||||||
 | 
					        conditionInputRef.value.focus();
 | 
				
			||||||
 | 
					    }, 100);
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// 确认条件
 | 
					// 确认条件
 | 
				
			||||||
@@ -302,7 +382,7 @@ const onSelectByCondition = async () => {
 | 
				
			|||||||
    notEmpty(state.condition, '条件不能为空');
 | 
					    notEmpty(state.condition, '条件不能为空');
 | 
				
			||||||
    state.pageNum = 1;
 | 
					    state.pageNum = 1;
 | 
				
			||||||
    await selectData();
 | 
					    await selectData();
 | 
				
			||||||
}
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * 表排序字段变更
 | 
					 * 表排序字段变更
 | 
				
			||||||
@@ -323,7 +403,7 @@ const onDataSelectionChange = (datas: []) => {
 | 
				
			|||||||
const changeUpdatedField = (updatedFields: []) => {
 | 
					const changeUpdatedField = (updatedFields: []) => {
 | 
				
			||||||
    // 如果存在要更新字段,则显示提交和取消按钮
 | 
					    // 如果存在要更新字段,则显示提交和取消按钮
 | 
				
			||||||
    state.hasUpdatedFileds = updatedFields && updatedFields.length > 0;
 | 
					    state.hasUpdatedFileds = updatedFields && updatedFields.length > 0;
 | 
				
			||||||
}
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * 执行删除数据事件
 | 
					 * 执行删除数据事件
 | 
				
			||||||
@@ -332,7 +412,7 @@ const onDeleteData = async () => {
 | 
				
			|||||||
    const deleteDatas = state.selectionDatas;
 | 
					    const deleteDatas = state.selectionDatas;
 | 
				
			||||||
    isTrue(deleteDatas && deleteDatas.length > 0, '请先选择要删除的数据');
 | 
					    isTrue(deleteDatas && deleteDatas.length > 0, '请先选择要删除的数据');
 | 
				
			||||||
    const { db } = state.ti;
 | 
					    const { db } = state.ti;
 | 
				
			||||||
    const dbInst = state.ti.getNowDbInst()
 | 
					    const dbInst = state.ti.getNowDbInst();
 | 
				
			||||||
    dbInst.promptExeSql(db, dbInst.genDeleteByPrimaryKeysSql(db, state.table, deleteDatas), null, () => {
 | 
					    dbInst.promptExeSql(db, dbInst.genDeleteByPrimaryKeysSql(db, state.table, deleteDatas), null, () => {
 | 
				
			||||||
        onRefresh();
 | 
					        onRefresh();
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
@@ -345,40 +425,41 @@ const onGenerateInsertSql = async () => {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
const submitUpdateFields = () => {
 | 
					const submitUpdateFields = () => {
 | 
				
			||||||
    dbTableRef.value.submitUpdateFields();
 | 
					    dbTableRef.value.submitUpdateFields();
 | 
				
			||||||
}
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const cancelUpdateFields = () => {
 | 
					const cancelUpdateFields = () => {
 | 
				
			||||||
    dbTableRef.value.cancelUpdateFields();
 | 
					    dbTableRef.value.cancelUpdateFields();
 | 
				
			||||||
}
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const onShowAddDataDialog = async () => {
 | 
					const onShowAddDataDialog = async () => {
 | 
				
			||||||
    state.addDataDialog.title = `添加'${state.table}'表数据`
 | 
					    state.addDataDialog.title = `添加'${state.table}'表数据`;
 | 
				
			||||||
    state.addDataDialog.visible = true;
 | 
					    state.addDataDialog.visible = true;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const closeAddDataDialog = () => {
 | 
					const closeAddDataDialog = () => {
 | 
				
			||||||
    state.addDataDialog.visible = false;
 | 
					    state.addDataDialog.visible = false;
 | 
				
			||||||
    state.addDataDialog.data = {};
 | 
					    state.addDataDialog.data = {};
 | 
				
			||||||
}
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// 添加新数据行
 | 
					// 添加新数据行
 | 
				
			||||||
const addRow = async () => {
 | 
					const addRow = async () => {
 | 
				
			||||||
    dataForm.value.validate(async (valid: boolean) => {
 | 
					    dataForm.value.validate(async (valid: boolean) => {
 | 
				
			||||||
        if (valid) {
 | 
					        if (valid) {
 | 
				
			||||||
 | 
					            const dbInst = state.ti.getNowDbInst();
 | 
				
			||||||
            const data = state.addDataDialog.data;
 | 
					            const data = state.addDataDialog.data;
 | 
				
			||||||
            // key: 字段名,value: 字段名提示
 | 
					            // key: 字段名,value: 字段名提示
 | 
				
			||||||
            let obj: any = {};
 | 
					            let obj: any = {};
 | 
				
			||||||
            for (let item of state.columns) {
 | 
					            for (let item of state.columns) {
 | 
				
			||||||
                const value = data[item.columnName]
 | 
					                const value = data[item.columnName];
 | 
				
			||||||
                if (!value) {
 | 
					                if (!value) {
 | 
				
			||||||
                    continue
 | 
					                    continue;
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
                obj[`${item.columnName}`] = DbInst.wrapValueByType(value);
 | 
					                obj[`${dbInst.wrapName(item.columnName)}`] = DbInst.wrapValueByType(value);
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            let columnNames = Object.keys(obj).join(',');
 | 
					            let columnNames = Object.keys(obj).join(',');
 | 
				
			||||||
            let values = Object.values(obj).join(',');
 | 
					            let values = Object.values(obj).join(',');
 | 
				
			||||||
            let sql = `INSERT INTO ${state.table} (${columnNames}) VALUES (${values});`;
 | 
					            let sql = `INSERT INTO ${dbInst.wrapName(state.table)} (${columnNames}) VALUES (${values});`;
 | 
				
			||||||
            state.ti.getNowDbInst().promptExeSql(state.ti.db, sql, null, () => {
 | 
					            dbInst.promptExeSql(state.ti.db, sql, null, () => {
 | 
				
			||||||
                closeAddDataDialog();
 | 
					                closeAddDataDialog();
 | 
				
			||||||
                onRefresh();
 | 
					                onRefresh();
 | 
				
			||||||
            });
 | 
					            });
 | 
				
			||||||
@@ -387,13 +468,11 @@ const addRow = async () => {
 | 
				
			|||||||
            return false;
 | 
					            return false;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					 | 
				
			||||||
</script>
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<style lang="scss">
 | 
					<style lang="scss">
 | 
				
			||||||
.update_field_active {
 | 
					.update_field_active {
 | 
				
			||||||
    background-color: var(--el-color-success)
 | 
					    background-color: var(--el-color-success);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
</style>
 | 
					</style>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,5 +1,6 @@
 | 
				
			|||||||
/* eslint-disable no-unused-vars */
 | 
					/* eslint-disable no-unused-vars */
 | 
				
			||||||
import { dbApi } from './api';
 | 
					import { dbApi } from './api';
 | 
				
			||||||
 | 
					import { getTextWidth } from '@/common/utils/string';
 | 
				
			||||||
import SqlExecBox from './component/SqlExecBox';
 | 
					import SqlExecBox from './component/SqlExecBox';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const dbInstCache: Map<number, DbInst> = new Map();
 | 
					const dbInstCache: Map<number, DbInst> = new Map();
 | 
				
			||||||
@@ -8,30 +9,30 @@ export class DbInst {
 | 
				
			|||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * 标签路径
 | 
					     * 标签路径
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    tagPath: string
 | 
					    tagPath: string;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * 实例id
 | 
					     * 实例id
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    id: number
 | 
					    id: number;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * 实例名
 | 
					     * 实例名
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    name: string
 | 
					    name: string;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * 数据库类型, mysql postgres
 | 
					     * 数据库类型, mysql postgres
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    type: string
 | 
					    type: string;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * schema -> db
 | 
					     * schema -> db
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    dbs: Map<string, Db> = new Map()
 | 
					    dbs: Map<string, Db> = new Map();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /** 数据库schema,多个用空格隔开 */
 | 
					    /** 数据库schema,多个用空格隔开 */
 | 
				
			||||||
    databases: string
 | 
					    databases: string;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * 默认查询分页数量
 | 
					     * 默认查询分页数量
 | 
				
			||||||
@@ -45,9 +46,9 @@ export class DbInst {
 | 
				
			|||||||
     */
 | 
					     */
 | 
				
			||||||
    getDb(dbName: string) {
 | 
					    getDb(dbName: string) {
 | 
				
			||||||
        if (!dbName) {
 | 
					        if (!dbName) {
 | 
				
			||||||
            throw new Error('dbName不能为空')
 | 
					            throw new Error('dbName不能为空');
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        let db = this.dbs.get(dbName)
 | 
					        let db = this.dbs.get(dbName);
 | 
				
			||||||
        if (db) {
 | 
					        if (db) {
 | 
				
			||||||
            return db;
 | 
					            return db;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
@@ -120,7 +121,7 @@ export class DbInst {
 | 
				
			|||||||
            return db.tableHints;
 | 
					            return db.tableHints;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        console.log(`load db-hits -> dbName: ${dbName}`);
 | 
					        console.log(`load db-hits -> dbName: ${dbName}`);
 | 
				
			||||||
        const hits = await dbApi.hintTables.request({ id: this.id, db: db.name, })
 | 
					        const hits = await dbApi.hintTables.request({ id: this.id, db: db.name });
 | 
				
			||||||
        db.tableHints = hits;
 | 
					        db.tableHints = hits;
 | 
				
			||||||
        return hits;
 | 
					        return hits;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@@ -140,9 +141,19 @@ export class DbInst {
 | 
				
			|||||||
        });
 | 
					        });
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * 获取count sql
 | 
				
			||||||
 | 
					     * @param table 表名
 | 
				
			||||||
 | 
					     * @param condition 条件
 | 
				
			||||||
 | 
					     * @returns count sql
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    getDefaultCountSql = (table: string, condition?: string) => {
 | 
				
			||||||
 | 
					        return `SELECT COUNT(*) count FROM ${this.wrapName(table)} ${condition ? 'WHERE ' + condition : ''} limit 1`;
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // 获取指定表的默认查询sql
 | 
					    // 获取指定表的默认查询sql
 | 
				
			||||||
    getDefaultSelectSql(table: string, condition: string, orderBy: string, pageNum: number, limit: number = DbInst.DefaultLimit) {
 | 
					    getDefaultSelectSql(table: string, condition: string, orderBy: string, pageNum: number, limit: number = DbInst.DefaultLimit) {
 | 
				
			||||||
        const baseSql = `SELECT * FROM ${table} ${condition ? 'WHERE ' + condition : ''} ${orderBy ? orderBy : ''}`;
 | 
					        const baseSql = `SELECT * FROM ${this.wrapName(table)} ${condition ? 'WHERE ' + condition : ''} ${orderBy ? orderBy : ''}`;
 | 
				
			||||||
        if (this.type == 'mysql') {
 | 
					        if (this.type == 'mysql') {
 | 
				
			||||||
            return `${baseSql} LIMIT ${(pageNum - 1) * limit}, ${limit};`;
 | 
					            return `${baseSql} LIMIT ${(pageNum - 1) * limit}, ${limit};`;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
@@ -169,12 +180,12 @@ export class DbInst {
 | 
				
			|||||||
            let values = [];
 | 
					            let values = [];
 | 
				
			||||||
            for (let column of columns) {
 | 
					            for (let column of columns) {
 | 
				
			||||||
                const colName = column.columnName;
 | 
					                const colName = column.columnName;
 | 
				
			||||||
                colNames.push(colName);
 | 
					                colNames.push(this.wrapName(colName));
 | 
				
			||||||
                values.push(DbInst.wrapValueByType(data[colName]));
 | 
					                values.push(DbInst.wrapValueByType(data[colName]));
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            sqls.push(`INSERT INTO ${table} (${colNames.join(', ')}) VALUES(${values.join(', ')})`);
 | 
					            sqls.push(`INSERT INTO ${this.wrapName(table)} (${colNames.join(', ')}) VALUES(${values.join(', ')})`);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        return sqls.join(';\n') + ';'
 | 
					        return sqls.join(';\n') + ';';
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
@@ -186,7 +197,7 @@ export class DbInst {
 | 
				
			|||||||
        const primaryKey = this.getDb(db).getColumn(table);
 | 
					        const primaryKey = this.getDb(db).getColumn(table);
 | 
				
			||||||
        const primaryKeyColumnName = primaryKey.columnName;
 | 
					        const primaryKeyColumnName = primaryKey.columnName;
 | 
				
			||||||
        const ids = datas.map((d: any) => `${DbInst.wrapColumnValue(primaryKey.columnType, d[primaryKeyColumnName])}`).join(',');
 | 
					        const ids = datas.map((d: any) => `${DbInst.wrapColumnValue(primaryKey.columnType, d[primaryKeyColumnName])}`).join(',');
 | 
				
			||||||
        return `DELETE FROM ${table} WHERE ${primaryKeyColumnName} IN (${ids})`;
 | 
					        return `DELETE FROM ${this.wrapName(table)} WHERE ${this.wrapName(primaryKeyColumnName)} IN (${ids})`;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /*
 | 
					    /*
 | 
				
			||||||
@@ -194,12 +205,30 @@ export class DbInst {
 | 
				
			|||||||
     */
 | 
					     */
 | 
				
			||||||
    promptExeSql = (db: string, sql: string, cancelFunc: any = null, successFunc: any = null) => {
 | 
					    promptExeSql = (db: string, sql: string, cancelFunc: any = null, successFunc: any = null) => {
 | 
				
			||||||
        SqlExecBox({
 | 
					        SqlExecBox({
 | 
				
			||||||
            sql, dbId: this.id, db,
 | 
					            sql,
 | 
				
			||||||
 | 
					            dbId: this.id,
 | 
				
			||||||
 | 
					            db,
 | 
				
			||||||
            runSuccessCallback: successFunc,
 | 
					            runSuccessCallback: successFunc,
 | 
				
			||||||
            cancelCallback: cancelFunc,
 | 
					            cancelCallback: cancelFunc,
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * 包裹数据库表名、字段名等,避免使用关键字为字段名或表名时报错
 | 
				
			||||||
 | 
					     * @param table
 | 
				
			||||||
 | 
					     * @param condition
 | 
				
			||||||
 | 
					     * @returns
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    wrapName = (name: string) => {
 | 
				
			||||||
 | 
					        if (this.type == 'mysql') {
 | 
				
			||||||
 | 
					            return `\`${name}\``;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        if (this.type == 'postgres') {
 | 
				
			||||||
 | 
					            return `"${name}"`;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        return name;
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * 获取或新建dbInst,如果缓存中不存在则新建,否则直接返回
 | 
					     * 获取或新建dbInst,如果缓存中不存在则新建,否则直接返回
 | 
				
			||||||
     * @param inst 数据库实例,后端返回的列表接口中的信息
 | 
					     * @param inst 数据库实例,后端返回的列表接口中的信息
 | 
				
			||||||
@@ -207,7 +236,7 @@ export class DbInst {
 | 
				
			|||||||
     */
 | 
					     */
 | 
				
			||||||
    static getOrNewInst(inst: any) {
 | 
					    static getOrNewInst(inst: any) {
 | 
				
			||||||
        if (!inst) {
 | 
					        if (!inst) {
 | 
				
			||||||
            throw new Error('inst不能为空')
 | 
					            throw new Error('inst不能为空');
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        let dbInst = dbInstCache.get(inst.id);
 | 
					        let dbInst = dbInstCache.get(inst.id);
 | 
				
			||||||
        if (dbInst) {
 | 
					        if (dbInst) {
 | 
				
			||||||
@@ -249,16 +278,6 @@ export class DbInst {
 | 
				
			|||||||
        dbInstCache.clear();
 | 
					        dbInstCache.clear();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
    * 获取count sql
 | 
					 | 
				
			||||||
    * @param table 表名
 | 
					 | 
				
			||||||
    * @param condition 条件
 | 
					 | 
				
			||||||
    * @returns count sql
 | 
					 | 
				
			||||||
    */
 | 
					 | 
				
			||||||
    static getDefaultCountSql = (table: string, condition?: string) => {
 | 
					 | 
				
			||||||
        return `SELECT COUNT(*) count FROM ${table} ${condition ? 'WHERE ' + condition : ''}`;
 | 
					 | 
				
			||||||
    };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * 根据返回值包装值,若值为字符串类型则添加''
 | 
					     * 根据返回值包装值,若值为字符串类型则添加''
 | 
				
			||||||
     * @param val 值
 | 
					     * @param val 值
 | 
				
			||||||
@@ -282,7 +301,7 @@ export class DbInst {
 | 
				
			|||||||
            return value;
 | 
					            return value;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        return `'${value}'`;
 | 
					        return `'${value}'`;
 | 
				
			||||||
    };
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * 判断字段类型是否为数字类型
 | 
					     * 判断字段类型是否为数字类型
 | 
				
			||||||
@@ -291,7 +310,7 @@ export class DbInst {
 | 
				
			|||||||
     */
 | 
					     */
 | 
				
			||||||
    static isNumber(columnType: string) {
 | 
					    static isNumber(columnType: string) {
 | 
				
			||||||
        return columnType.match(/int|double|float|nubmer|decimal|byte|bit/gi);
 | 
					        return columnType.match(/int|double|float|nubmer|decimal|byte|bit/gi);
 | 
				
			||||||
    };
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     *
 | 
					     *
 | 
				
			||||||
@@ -300,81 +319,37 @@ export class DbInst {
 | 
				
			|||||||
     * @param flag 标志
 | 
					     * @param flag 标志
 | 
				
			||||||
     * @returns 列宽度
 | 
					     * @returns 列宽度
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    static flexColumnWidth = (str: any, tableData: any, flag = 'equal') => {
 | 
					    static flexColumnWidth = (prop: any, tableData: any) => {
 | 
				
			||||||
        // str为该列的字段名(传字符串);tableData为该表格的数据源(传变量);
 | 
					        if (!prop || !prop.length || prop.length === 0 || prop === undefined) {
 | 
				
			||||||
        // flag为可选值,可不传该参数,传参时可选'max'或'equal',默认为'max'
 | 
					 | 
				
			||||||
        // flag为'max'则设置列宽适配该列中最长的内容,flag为'equal'则设置列宽适配该列中第一行内容的长度。
 | 
					 | 
				
			||||||
        str = str + '';
 | 
					 | 
				
			||||||
        let columnContent = '';
 | 
					 | 
				
			||||||
        if (!tableData || !tableData.length || tableData.length === 0 || tableData === undefined) {
 | 
					 | 
				
			||||||
            return;
 | 
					            return;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        if (!str || !str.length || str.length === 0 || str === undefined) {
 | 
					 | 
				
			||||||
            return;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        if (flag === 'equal') {
 | 
					 | 
				
			||||||
            // 获取该列中第一个不为空的数据(内容)
 | 
					 | 
				
			||||||
            for (let i = 0; i < tableData.length; i++) {
 | 
					 | 
				
			||||||
                // 转为字符串后比较
 | 
					 | 
				
			||||||
                if ((tableData[i][str] + '').length > 0) {
 | 
					 | 
				
			||||||
                    columnContent = tableData[i][str] + '';
 | 
					 | 
				
			||||||
                    break;
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        } else {
 | 
					 | 
				
			||||||
            // 获取该列中最长的数据(内容)
 | 
					 | 
				
			||||||
            let index = 0;
 | 
					 | 
				
			||||||
            for (let i = 0; i < tableData.length; i++) {
 | 
					 | 
				
			||||||
                if (tableData[i][str] === null) {
 | 
					 | 
				
			||||||
                    return;
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
                const now_temp = tableData[i][str] + '';
 | 
					 | 
				
			||||||
                const max_temp = tableData[index][str] + '';
 | 
					 | 
				
			||||||
                if (now_temp.length > max_temp.length) {
 | 
					 | 
				
			||||||
                    index = i;
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            columnContent = tableData[index][str] + '';
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        const contentWidth: number = DbInst.getContentWidth(columnContent);
 | 
					 | 
				
			||||||
        // 获取列名称的长度 加上排序图标长度
 | 
					 | 
				
			||||||
        const columnWidth: number = DbInst.getContentWidth(str) + 43;
 | 
					 | 
				
			||||||
        const flexWidth: number = contentWidth > columnWidth ? contentWidth : columnWidth;
 | 
					 | 
				
			||||||
        return flexWidth + 'px';
 | 
					 | 
				
			||||||
    };
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					        // 获取列名称的长度 加上排序图标长度
 | 
				
			||||||
     * 获取内容所需要占用的宽度
 | 
					        const columnWidth: number = getTextWidth(prop) + 40;
 | 
				
			||||||
    */
 | 
					        // prop为该列的字段名(传字符串);tableData为该表格的数据源(传变量);
 | 
				
			||||||
    static getContentWidth = (content: any): number => {
 | 
					        if (!tableData || !tableData.length || tableData.length === 0 || tableData === undefined) {
 | 
				
			||||||
        // 以下分配的单位长度可根据实际需求进行调整
 | 
					            return columnWidth;
 | 
				
			||||||
        let flexWidth = 0;
 | 
					 | 
				
			||||||
        for (const char of content) {
 | 
					 | 
				
			||||||
            if (flexWidth > 500) {
 | 
					 | 
				
			||||||
                break;
 | 
					 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
            if ((char >= '0' && char <= '9') || (char >= 'a' && char <= 'z')) {
 | 
					
 | 
				
			||||||
                // 如果是小写字母、数字字符,分配8个单位宽度
 | 
					        // 获取该列中最长的数据(内容)
 | 
				
			||||||
                flexWidth += 8.5;
 | 
					        let maxWidthText = '';
 | 
				
			||||||
 | 
					        let maxWidthValue;
 | 
				
			||||||
 | 
					        // 获取该列中最长的数据(内容)
 | 
				
			||||||
 | 
					        for (let i = 0; i < tableData.length; i++) {
 | 
				
			||||||
 | 
					            let nowValue = tableData[i][prop];
 | 
				
			||||||
 | 
					            if (!nowValue) {
 | 
				
			||||||
                continue;
 | 
					                continue;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            if (char >= 'A' && char <= 'Z') {
 | 
					            // 转为字符串比较长度
 | 
				
			||||||
                flexWidth += 9;
 | 
					            let nowText = nowValue + '';
 | 
				
			||||||
                continue;
 | 
					            if (nowText.length > maxWidthText.length) {
 | 
				
			||||||
            }
 | 
					                maxWidthText = nowText;
 | 
				
			||||||
            if (char >= '\u4e00' && char <= '\u9fa5') {
 | 
					                maxWidthValue = nowValue;
 | 
				
			||||||
                // 如果是中文字符,为字符分配16个单位宽度
 | 
					 | 
				
			||||||
                flexWidth += 16;
 | 
					 | 
				
			||||||
            } else {
 | 
					 | 
				
			||||||
                // 其他种类字符,为字符分配9个单位宽度
 | 
					 | 
				
			||||||
                flexWidth += 8;
 | 
					 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        if (flexWidth > 500) {
 | 
					        const contentWidth: number = getTextWidth(maxWidthText) + 15;
 | 
				
			||||||
            // 设置最大宽度
 | 
					        const flexWidth: number = contentWidth > columnWidth ? contentWidth : columnWidth;
 | 
				
			||||||
            flexWidth = 500;
 | 
					        return flexWidth > 500 ? 500 : flexWidth;
 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        return flexWidth;
 | 
					 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -382,10 +357,10 @@ export class DbInst {
 | 
				
			|||||||
 * 数据库实例信息
 | 
					 * 数据库实例信息
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
class Db {
 | 
					class Db {
 | 
				
			||||||
    name: string  // 库名
 | 
					    name: string; // 库名
 | 
				
			||||||
    tables: []   // 数据库实例表信息
 | 
					    tables: []; // 数据库实例表信息
 | 
				
			||||||
    columnsMap: Map<string, any> = new Map  // table -> columns
 | 
					    columnsMap: Map<string, any> = new Map(); // table -> columns
 | 
				
			||||||
    tableHints: any = null // 提示词
 | 
					    tableHints: any = null; // 提示词
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * 获取指定表列信息(前提需要dbInst.loadColumns)
 | 
					     * 获取指定表列信息(前提需要dbInst.loadColumns)
 | 
				
			||||||
@@ -426,32 +401,32 @@ export class TabInfo {
 | 
				
			|||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * tab唯一key。与label、name都一致
 | 
					     * tab唯一key。与label、name都一致
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    key: string
 | 
					    key: string;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * 菜单树节点key
 | 
					     * 菜单树节点key
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    treeNodeKey: string
 | 
					    treeNodeKey: string;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * 数据库实例id
 | 
					     * 数据库实例id
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    dbId: number
 | 
					    dbId: number;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * 库名
 | 
					     * 库名
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    db: string = ''
 | 
					    db: string = '';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * tab 类型
 | 
					     * tab 类型
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    type: TabType
 | 
					    type: TabType;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * tab需要的其他信息
 | 
					     * tab需要的其他信息
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    params: any
 | 
					    params: any;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    getNowDbInst() {
 | 
					    getNowDbInst() {
 | 
				
			||||||
        return DbInst.getInst(this.dbId);
 | 
					        return DbInst.getInst(this.dbId);
 | 
				
			||||||
@@ -465,26 +440,26 @@ export class TabInfo {
 | 
				
			|||||||
/** 修改表字段所需数据 */
 | 
					/** 修改表字段所需数据 */
 | 
				
			||||||
export type UpdateFieldsMeta = {
 | 
					export type UpdateFieldsMeta = {
 | 
				
			||||||
    // 主键值
 | 
					    // 主键值
 | 
				
			||||||
    primaryKey: string
 | 
					    primaryKey: string;
 | 
				
			||||||
    // 主键名
 | 
					    // 主键名
 | 
				
			||||||
    primaryKeyName: string
 | 
					    primaryKeyName: string;
 | 
				
			||||||
    // 主键类型
 | 
					    // 主键类型
 | 
				
			||||||
    primaryKeyType: string
 | 
					    primaryKeyType: string;
 | 
				
			||||||
    // 新值
 | 
					    // 新值
 | 
				
			||||||
    fields: FieldsMeta[]
 | 
					    fields: FieldsMeta[];
 | 
				
			||||||
}
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export type FieldsMeta = {
 | 
					export type FieldsMeta = {
 | 
				
			||||||
    // 字段所在div
 | 
					    // 字段所在div
 | 
				
			||||||
    div: HTMLElement
 | 
					    div: HTMLElement;
 | 
				
			||||||
    // 字段名
 | 
					    // 字段名
 | 
				
			||||||
    fieldName: string
 | 
					    fieldName: string;
 | 
				
			||||||
    // 字段所在的表格行数据
 | 
					    // 字段所在的表格行数据
 | 
				
			||||||
    row: any
 | 
					    row: any;
 | 
				
			||||||
    // 字段类型
 | 
					    // 字段类型
 | 
				
			||||||
    fieldType: string
 | 
					    fieldType: string;
 | 
				
			||||||
    // 原值
 | 
					    // 原值
 | 
				
			||||||
    oldValue: string
 | 
					    oldValue: string;
 | 
				
			||||||
    // 新值
 | 
					    // 新值
 | 
				
			||||||
    newValue: string
 | 
					    newValue: string;
 | 
				
			||||||
}
 | 
					};
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,12 +1,10 @@
 | 
				
			|||||||
import { Enum } from '@/common/Enum'
 | 
					import { EnumValue } from '@/common/Enum';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					// 数据库sql执行类型
 | 
				
			||||||
 * 枚举类
 | 
					export const DbSqlExecTypeEnum = {
 | 
				
			||||||
 */
 | 
					    Update: EnumValue.of(1, 'UPDATE').setTagColor('#E4F5EB'),
 | 
				
			||||||
export default {
 | 
					    Delete: EnumValue.of(2, 'DELETE').setTagColor('#F9E2AE'),
 | 
				
			||||||
    // 数据库sql执行类型
 | 
					    Insert: EnumValue.of(3, 'INSERT').setTagColor('#A8DEE0'),
 | 
				
			||||||
    DbSqlExecTypeEnum: new Enum().add('UPDATE', 'UPDATE', 1)
 | 
					    Query: EnumValue.of(4, 'QUERY').setTagColor('#A8DEE0'),
 | 
				
			||||||
        .add('DELETE', 'DELETE', 2)
 | 
					    Other: EnumValue.of(-1, 'OTHER').setTagColor('#F9E2AE'),
 | 
				
			||||||
        .add('INSERT', 'INSERT', 3)
 | 
					};
 | 
				
			||||||
        .add('QUERY', 'QUERY', 4),
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 
 | 
				
			|||||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user