mirror of
				https://gitee.com/dromara/mayfly-go
				synced 2025-11-04 08:20:25 +08:00 
			
		
		
		
	Compare commits
	
		
			65 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					e2b524dadb | ||
| 
						 | 
					8998a21626 | ||
| 
						 | 
					abc015aec0 | ||
| 
						 | 
					4ef8d27b1e | ||
| 
						 | 
					40b6e603fc | ||
| 
						 | 
					21498584b1 | ||
| 
						 | 
					408bac09a1 | ||
| 
						 | 
					582d879a77 | ||
| 
						 | 
					38ff5152e0 | ||
| 
						 | 
					d1d372e1bf | ||
| 
						 | 
					5e4793433b | ||
| 
						 | 
					54ad19f97e | ||
| 
						 | 
					fc166650b3 | ||
| 
						 | 
					2acc295259 | ||
| 
						 | 
					4b3ed1310d | ||
| 
						 | 
					b2cfd1517c | ||
| 
						 | 
					b13d27ccd6 | ||
| 
						 | 
					68e0088016 | ||
| 
						 | 
					bd1e83989d | ||
| 
						 | 
					263dfa6be7 | ||
| 
						 | 
					eb55f93864 | ||
| 
						 | 
					8589105e44 | ||
| 
						 | 
					986b187f0a | ||
| 
						 | 
					008d34c453 | ||
| 
						 | 
					49d3f988c9 | ||
| 
						 | 
					76475e807e | ||
| 
						 | 
					f93231da61 | ||
| 
						 | 
					bf75483a3c | ||
| 
						 | 
					b56b0187cf | ||
| 
						 | 
					7e7f02b502 | ||
| 
						 | 
					878985f7c5 | ||
| 
						 | 
					2133d9b737 | ||
| 
						 | 
					d711a36749 | ||
| 
						 | 
					9dbf104ef1 | ||
| 
						 | 
					20eb06fb28 | ||
| 
						 | 
					9c20bdef39 | ||
| 
						 | 
					3fdd98a390 | ||
| 
						 | 
					d4f456c0cf | ||
| 
						 | 
					f2b6e15cf4 | ||
| 
						 | 
					6be0ea6aed | ||
| 
						 | 
					eee08be2cc | ||
| 
						 | 
					252fc553f2 | ||
| 
						 | 
					ac2ceed3f9 | ||
| 
						 | 
					3f828cc5b0 | ||
| 
						 | 
					fc1b9ef35d | ||
| 
						 | 
					d0b71a1c40 | ||
| 
						 | 
					a743a6a05a | ||
| 
						 | 
					0e6b9713ce | ||
| 
						 | 
					b9afbc764d | ||
| 
						 | 
					923e183a67 | ||
| 
						 | 
					7e9a381641 | ||
| 
						 | 
					bed95254d0 | ||
| 
						 | 
					e4d13f3377 | ||
| 
						 | 
					d530365ef9 | ||
| 
						 | 
					070d4ea104 | ||
| 
						 | 
					3fc86f0fae | ||
| 
						 | 
					3b77ab2727 | ||
| 
						 | 
					76cb991282 | ||
| 
						 | 
					9efd20f1b9 | ||
| 
						 | 
					de5b9e46d3 | ||
| 
						 | 
					f27d3d200f | ||
| 
						 | 
					f4a64b96a9 | ||
| 
						 | 
					9a59749763 | ||
| 
						 | 
					b017b902f8 | ||
| 
						 | 
					7c53353c60 | 
@@ -5,12 +5,12 @@ WORKDIR /mayfly
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
COPY mayfly_go_web .
 | 
					COPY mayfly_go_web .
 | 
				
			||||||
 | 
					
 | 
				
			||||||
RUN yarn config set registry 'https://registry.npm.taobao.org' && \
 | 
					RUN yarn config set registry 'https://registry.npmmirror.com' && \
 | 
				
			||||||
    yarn install && \
 | 
					    yarn install && \
 | 
				
			||||||
    yarn build
 | 
					    yarn build
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# 构建后端资源
 | 
					# 构建后端资源
 | 
				
			||||||
FROM golang:1.21.5 as be-builder
 | 
					FROM golang:1.22 as be-builder
 | 
				
			||||||
 | 
					
 | 
				
			||||||
ENV GOPROXY https://goproxy.cn
 | 
					ENV GOPROXY https://goproxy.cn
 | 
				
			||||||
WORKDIR /mayfly
 | 
					WORKDIR /mayfly
 | 
				
			||||||
@@ -24,7 +24,7 @@ COPY --from=fe-builder /mayfly/dist /mayfly/static/static
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
# Build
 | 
					# Build
 | 
				
			||||||
RUN GO111MODULE=on CGO_ENABLED=0 GOOS=linux \
 | 
					RUN GO111MODULE=on CGO_ENABLED=0 GOOS=linux \
 | 
				
			||||||
    go build -a \
 | 
					    go build -a -ldflags=-w \
 | 
				
			||||||
    -o mayfly-go main.go
 | 
					    -o mayfly-go main.go
 | 
				
			||||||
 | 
					
 | 
				
			||||||
FROM debian:bookworm-slim
 | 
					FROM debian:bookworm-slim
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -13,7 +13,7 @@
 | 
				
			|||||||
    <img src="https://img.shields.io/docker/pulls/mayflygo/mayfly-go.svg?label=docker%20pulls&color=fac858" alt="docker pulls"/>
 | 
					    <img src="https://img.shields.io/docker/pulls/mayflygo/mayfly-go.svg?label=docker%20pulls&color=fac858" alt="docker pulls"/>
 | 
				
			||||||
  </a>
 | 
					  </a>
 | 
				
			||||||
  <a href="https://github.com/golang/go" target="_blank">
 | 
					  <a href="https://github.com/golang/go" target="_blank">
 | 
				
			||||||
    <img src="https://img.shields.io/badge/Golang-1.21%2B-yellow.svg" alt="golang"/>
 | 
					    <img src="https://img.shields.io/badge/Golang-1.22%2B-yellow.svg" alt="golang"/>
 | 
				
			||||||
  </a>
 | 
					  </a>
 | 
				
			||||||
  <a href="https://cn.vuejs.org" target="_blank">
 | 
					  <a href="https://cn.vuejs.org" target="_blank">
 | 
				
			||||||
    <img src="https://img.shields.io/badge/Vue-3.x-green.svg" alt="vue">
 | 
					    <img src="https://img.shields.io/badge/Vue-3.x-green.svg" alt="vue">
 | 
				
			||||||
@@ -22,7 +22,7 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
### 介绍
 | 
					### 介绍
 | 
				
			||||||
 | 
					
 | 
				
			||||||
web 版 **linux(终端[终端回放] 文件 脚本 进程 计划任务)、数据库(mysql postgres oracle 达梦 高斯)、redis(单机 哨兵 集群)、mongo 统一管理操作平台**
 | 
					web 版 **linux(终端[终端回放] 文件 脚本 进程 计划任务)、数据库(mysql postgres oracle sqlserver 达梦 高斯 sqlite)、redis(单机 哨兵 集群)、mongo 等集工单流程审批于一体的统一管理操作平台**
 | 
				
			||||||
 | 
					
 | 
				
			||||||
### 开发语言与主要框架
 | 
					### 开发语言与主要框架
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,21 +0,0 @@
 | 
				
			|||||||
MIT License
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
Copyright (c) 2021 lyt-Top
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
 | 
					 | 
				
			||||||
of this software and associated documentation files (the "Software"), to deal
 | 
					 | 
				
			||||||
in the Software without restriction, including without limitation the rights
 | 
					 | 
				
			||||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 | 
					 | 
				
			||||||
copies of the Software, and to permit persons to whom the Software is
 | 
					 | 
				
			||||||
furnished to do so, subject to the following conditions:
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
The above copyright notice and this permission notice shall be included in all
 | 
					 | 
				
			||||||
copies or substantial portions of the Software.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 | 
					 | 
				
			||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 | 
					 | 
				
			||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 | 
					 | 
				
			||||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 | 
					 | 
				
			||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 | 
					 | 
				
			||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 | 
					 | 
				
			||||||
SOFTWARE.
 | 
					 | 
				
			||||||
@@ -10,31 +10,32 @@
 | 
				
			|||||||
  },
 | 
					  },
 | 
				
			||||||
  "dependencies": {
 | 
					  "dependencies": {
 | 
				
			||||||
    "@element-plus/icons-vue": "^2.3.1",
 | 
					    "@element-plus/icons-vue": "^2.3.1",
 | 
				
			||||||
    "@vueuse/core": "^10.7.2",
 | 
					    "@vueuse/core": "^10.9.0",
 | 
				
			||||||
    "asciinema-player": "^3.6.3",
 | 
					    "asciinema-player": "^3.7.0",
 | 
				
			||||||
    "axios": "^1.6.2",
 | 
					    "axios": "^1.6.2",
 | 
				
			||||||
    "clipboard": "^2.0.11",
 | 
					    "clipboard": "^2.0.11",
 | 
				
			||||||
    "countup.js": "^2.7.0",
 | 
					    "countup.js": "^2.8.0",
 | 
				
			||||||
    "cropperjs": "^1.5.11",
 | 
					    "cropperjs": "^1.6.1",
 | 
				
			||||||
    "echarts": "^5.4.3",
 | 
					    "echarts": "^5.5.0",
 | 
				
			||||||
    "element-plus": "^2.5.1",
 | 
					    "element-plus": "^2.6.2",
 | 
				
			||||||
    "js-base64": "^3.7.5",
 | 
					    "js-base64": "^3.7.7",
 | 
				
			||||||
    "jsencrypt": "^3.3.2",
 | 
					    "jsencrypt": "^3.3.2",
 | 
				
			||||||
    "lodash": "^4.17.21",
 | 
					    "lodash": "^4.17.21",
 | 
				
			||||||
    "mitt": "^3.0.1",
 | 
					    "mitt": "^3.0.1",
 | 
				
			||||||
    "monaco-editor": "^0.45.0",
 | 
					    "monaco-editor": "^0.47.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.7",
 | 
					    "pinia": "^2.1.7",
 | 
				
			||||||
    "qrcode.vue": "^3.4.1",
 | 
					    "qrcode.vue": "^3.4.1",
 | 
				
			||||||
    "screenfull": "^6.0.2",
 | 
					    "screenfull": "^6.0.2",
 | 
				
			||||||
    "sortablejs": "^1.15.0",
 | 
					    "sortablejs": "^1.15.2",
 | 
				
			||||||
    "splitpanes": "^3.1.5",
 | 
					    "splitpanes": "^3.1.5",
 | 
				
			||||||
    "sql-formatter": "^15.0.2",
 | 
					    "sql-formatter": "^15.0.2",
 | 
				
			||||||
 | 
					    "trzsz": "^1.1.5",
 | 
				
			||||||
    "uuid": "^9.0.1",
 | 
					    "uuid": "^9.0.1",
 | 
				
			||||||
    "vue": "^3.4.14",
 | 
					    "vue": "^3.4.21",
 | 
				
			||||||
    "vue-router": "^4.2.5",
 | 
					    "vue-router": "^4.3.0",
 | 
				
			||||||
    "xterm": "^5.3.0",
 | 
					    "xterm": "^5.3.0",
 | 
				
			||||||
    "xterm-addon-fit": "^0.8.0",
 | 
					    "xterm-addon-fit": "^0.8.0",
 | 
				
			||||||
    "xterm-addon-search": "^0.13.0",
 | 
					    "xterm-addon-search": "^0.13.0",
 | 
				
			||||||
@@ -44,19 +45,20 @@
 | 
				
			|||||||
    "@types/lodash": "^4.14.178",
 | 
					    "@types/lodash": "^4.14.178",
 | 
				
			||||||
    "@types/node": "^18.14.0",
 | 
					    "@types/node": "^18.14.0",
 | 
				
			||||||
    "@types/nprogress": "^0.2.0",
 | 
					    "@types/nprogress": "^0.2.0",
 | 
				
			||||||
    "@types/sortablejs": "^1.15.3",
 | 
					    "@types/sortablejs": "^1.15.8",
 | 
				
			||||||
    "@typescript-eslint/eslint-plugin": "^6.7.4",
 | 
					    "@typescript-eslint/eslint-plugin": "^6.7.4",
 | 
				
			||||||
    "@typescript-eslint/parser": "^6.7.4",
 | 
					    "@typescript-eslint/parser": "^6.7.4",
 | 
				
			||||||
    "@vitejs/plugin-vue": "^5.0.3",
 | 
					    "@vitejs/plugin-vue": "^5.0.4",
 | 
				
			||||||
    "@vue/compiler-sfc": "^3.4.14",
 | 
					    "@vue/compiler-sfc": "^3.4.21",
 | 
				
			||||||
 | 
					    "code-inspector-plugin": "^0.4.5",
 | 
				
			||||||
    "dotenv": "^16.3.1",
 | 
					    "dotenv": "^16.3.1",
 | 
				
			||||||
    "eslint": "^8.35.0",
 | 
					    "eslint": "^8.35.0",
 | 
				
			||||||
    "eslint-plugin-vue": "^9.19.2",
 | 
					    "eslint-plugin-vue": "^9.21.1",
 | 
				
			||||||
    "prettier": "^3.1.0",
 | 
					    "prettier": "^3.2.5",
 | 
				
			||||||
    "sass": "^1.69.0",
 | 
					    "sass": "^1.69.0",
 | 
				
			||||||
    "typescript": "^5.3.2",
 | 
					    "typescript": "^5.3.2",
 | 
				
			||||||
    "vite": "^5.0.11",
 | 
					    "vite": "^5.2.8",
 | 
				
			||||||
    "vue-eslint-parser": "^9.4.0"
 | 
					    "vue-eslint-parser": "^9.4.2"
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  "browserslist": [
 | 
					  "browserslist": [
 | 
				
			||||||
    "> 1%",
 | 
					    "> 1%",
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -4,7 +4,7 @@
 | 
				
			|||||||
            :zIndex="10000000"
 | 
					            :zIndex="10000000"
 | 
				
			||||||
            :width="210"
 | 
					            :width="210"
 | 
				
			||||||
            v-if="themeConfig.isWatermark"
 | 
					            v-if="themeConfig.isWatermark"
 | 
				
			||||||
            :font="{ color: 'rgba(180, 180, 180, 0.5)' }"
 | 
					            :font="{ color: 'rgba(180, 180, 180, 0.3)' }"
 | 
				
			||||||
            :content="themeConfig.watermarkText"
 | 
					            :content="themeConfig.watermarkText"
 | 
				
			||||||
            class="h100"
 | 
					            class="h100"
 | 
				
			||||||
        >
 | 
					        >
 | 
				
			||||||
 
 | 
				
			|||||||
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							@@ -55,11 +55,11 @@
 | 
				
			|||||||
      "unicode_decimal": 58905
 | 
					      "unicode_decimal": 58905
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
      "icon_id": "11617944",
 | 
					      "icon_id": "25271976",
 | 
				
			||||||
      "name": "oracle",
 | 
					      "name": "oracle",
 | 
				
			||||||
      "font_class": "oracle",
 | 
					      "font_class": "oracle",
 | 
				
			||||||
      "unicode": "e6ea",
 | 
					      "unicode": "e507",
 | 
				
			||||||
      "unicode_decimal": 59114
 | 
					      "unicode_decimal": 58631
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
      "icon_id": "8105644",
 | 
					      "icon_id": "8105644",
 | 
				
			||||||
@@ -67,6 +67,41 @@
 | 
				
			|||||||
      "font_class": "mariadb",
 | 
					      "font_class": "mariadb",
 | 
				
			||||||
      "unicode": "e513",
 | 
					      "unicode": "e513",
 | 
				
			||||||
      "unicode_decimal": 58643
 | 
					      "unicode_decimal": 58643
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					      "icon_id": "13601813",
 | 
				
			||||||
 | 
					      "name": "sqlite",
 | 
				
			||||||
 | 
					      "font_class": "sqlite",
 | 
				
			||||||
 | 
					      "unicode": "e546",
 | 
				
			||||||
 | 
					      "unicode_decimal": 58694
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					      "icon_id": "29340317",
 | 
				
			||||||
 | 
					      "name": "temp-mssql",
 | 
				
			||||||
 | 
					      "font_class": "MSSQLNATIVE",
 | 
				
			||||||
 | 
					      "unicode": "e600",
 | 
				
			||||||
 | 
					      "unicode_decimal": 58880
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					      "icon_id": "7699332",
 | 
				
			||||||
 | 
					      "name": "gaussdb",
 | 
				
			||||||
 | 
					      "font_class": "gauss",
 | 
				
			||||||
 | 
					      "unicode": "e683",
 | 
				
			||||||
 | 
					      "unicode_decimal": 59011
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					      "icon_id": "34836637",
 | 
				
			||||||
 | 
					      "name": "kingbase",
 | 
				
			||||||
 | 
					      "font_class": "kingbase",
 | 
				
			||||||
 | 
					      "unicode": "e882",
 | 
				
			||||||
 | 
					      "unicode_decimal": 59522
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					      "icon_id": "33047500",
 | 
				
			||||||
 | 
					      "name": "vastbase",
 | 
				
			||||||
 | 
					      "font_class": "vastbase",
 | 
				
			||||||
 | 
					      "unicode": "e62b",
 | 
				
			||||||
 | 
					      "unicode_decimal": 58923
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  ]
 | 
					  ]
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -22,6 +22,8 @@ export class EnumValue {
 | 
				
			|||||||
     */
 | 
					     */
 | 
				
			||||||
    tag: EnumValueTag;
 | 
					    tag: EnumValueTag;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    extra: any;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    constructor(value: any, label: string) {
 | 
					    constructor(value: any, label: string) {
 | 
				
			||||||
        this.value = value;
 | 
					        this.value = value;
 | 
				
			||||||
        this.label = label;
 | 
					        this.label = label;
 | 
				
			||||||
@@ -53,6 +55,11 @@ export class EnumValue {
 | 
				
			|||||||
        return this;
 | 
					        return this;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    setExtra(extra: any): EnumValue {
 | 
				
			||||||
 | 
					        this.extra = extra;
 | 
				
			||||||
 | 
					        return this;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public static of(value: any, label: string): EnumValue {
 | 
					    public static of(value: any, label: string): EnumValue {
 | 
				
			||||||
        return new EnumValue(value, label);
 | 
					        return new EnumValue(value, label);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@@ -60,11 +67,12 @@ export class EnumValue {
 | 
				
			|||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * 根据枚举值获取指定枚举值对象
 | 
					     * 根据枚举值获取指定枚举值对象
 | 
				
			||||||
     *
 | 
					     *
 | 
				
			||||||
     * @param enumValues 所有枚举值
 | 
					     * @param enums 枚举对象
 | 
				
			||||||
     * @param value 需要匹配的枚举值
 | 
					     * @param value 需要匹配的枚举值
 | 
				
			||||||
     * @returns 枚举值对象
 | 
					     * @returns 枚举值对象
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    static getEnumByValue(enumValues: EnumValue[], value: any): EnumValue | null {
 | 
					    static getEnumByValue(enums: any, value: any): EnumValue | null {
 | 
				
			||||||
 | 
					        const enumValues = Object.values(enums) as any;
 | 
				
			||||||
        for (let enumValue of enumValues) {
 | 
					        for (let enumValue of enumValues) {
 | 
				
			||||||
            if (enumValue.value == value) {
 | 
					            if (enumValue.value == value) {
 | 
				
			||||||
                return enumValue;
 | 
					                return enumValue;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -2,8 +2,13 @@ import EnumValue from './Enum';
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
// 标签关联的资源类型
 | 
					// 标签关联的资源类型
 | 
				
			||||||
export const TagResourceTypeEnum = {
 | 
					export const TagResourceTypeEnum = {
 | 
				
			||||||
    Machine: EnumValue.of(1, '机器'),
 | 
					    AuthCert: EnumValue.of(-2, '公共凭证').setExtra({ icon: 'Ticket' }),
 | 
				
			||||||
    Db: EnumValue.of(2, '数据库'),
 | 
					    Tag: EnumValue.of(-1, '标签').setExtra({ icon: 'CollectionTag' }),
 | 
				
			||||||
    Redis: EnumValue.of(3, 'redis'),
 | 
					
 | 
				
			||||||
    Mongo: EnumValue.of(4, 'mongo'),
 | 
					    Machine: EnumValue.of(1, '机器').setExtra({ icon: 'Monitor' }).tagTypeSuccess(),
 | 
				
			||||||
 | 
					    Db: EnumValue.of(2, '数据库').setExtra({ icon: 'Coin' }).tagTypeWarning(),
 | 
				
			||||||
 | 
					    Redis: EnumValue.of(3, 'redis').setExtra({ icon: 'iconfont icon-redis' }).tagTypeInfo(),
 | 
				
			||||||
 | 
					    Mongo: EnumValue.of(4, 'mongo').setExtra({ icon: 'iconfont icon-mongo' }).tagTypeDanger(),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    MachineAuthCert: EnumValue.of(11, '机器-授权凭证').setExtra({ icon: 'Ticket' }),
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -15,7 +15,7 @@ const config = {
 | 
				
			|||||||
    baseWsUrl: `${(window as any).globalConfig.BaseWsUrl || `${location.protocol == 'https:' ? 'wss:' : 'ws:'}//${getBaseApiUrl()}`}/api`,
 | 
					    baseWsUrl: `${(window as any).globalConfig.BaseWsUrl || `${location.protocol == 'https:' ? 'wss:' : 'ws:'}//${getBaseApiUrl()}`}/api`,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // 系统版本
 | 
					    // 系统版本
 | 
				
			||||||
    version: 'v1.7.0',
 | 
					    version: 'v1.8.0',
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default config;
 | 
					export default config;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -2,3 +2,8 @@ export const AccountUsernamePattern = {
 | 
				
			|||||||
    pattern: /^[a-zA-Z0-9_]{5,20}$/g,
 | 
					    pattern: /^[a-zA-Z0-9_]{5,20}$/g,
 | 
				
			||||||
    message: '只允许输入5-20位大小写字母、数字、下划线',
 | 
					    message: '只允许输入5-20位大小写字母、数字、下划线',
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const ResourceCodePattern = {
 | 
				
			||||||
 | 
					    pattern: /^[a-zA-Z0-9_.:]{1,32}$/g,
 | 
				
			||||||
 | 
					    message: '只允许输入1-32位大小写字母、数字、_.:',
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -7,24 +7,22 @@ export function exportCsv(filename: string, columns: string[], datas: []) {
 | 
				
			|||||||
        for (let column of columns) {
 | 
					        for (let column of columns) {
 | 
				
			||||||
            let val: any = data[column];
 | 
					            let val: any = data[column];
 | 
				
			||||||
            if (val == null || val == undefined) {
 | 
					            if (val == null || val == undefined) {
 | 
				
			||||||
                dataValueArr.push('');
 | 
					                val = '';
 | 
				
			||||||
                continue;
 | 
					            } else if (val && typeof val == 'string') {
 | 
				
			||||||
            }
 | 
					                // 替换换行符
 | 
				
			||||||
 | 
					                val = val.replace(/[\r\n]/g, '\\n');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            if (typeof val == 'string' && val) {
 | 
					 | 
				
			||||||
                // csv格式如果有逗号,整体用双引号括起来;如果里面还有双引号就替换成两个双引号,这样导出来的格式就不会有问题了
 | 
					                // csv格式如果有逗号,整体用双引号括起来;如果里面还有双引号就替换成两个双引号,这样导出来的格式就不会有问题了
 | 
				
			||||||
                if (val.indexOf(',') != -1) {
 | 
					                if (val.indexOf(',') != -1) {
 | 
				
			||||||
                    // 如果还有双引号,先将双引号转义,避免两边加了双引号后转义错误
 | 
					                    // 如果还有双引号,先将双引号转义,避免两边加了双引号后转义错误
 | 
				
			||||||
                    if (val.indexOf('"') != -1) {
 | 
					                    if (val.indexOf('"') != -1) {
 | 
				
			||||||
                        val = val.replace(/\"/g, '""');
 | 
					                        val = val.replace(/"/g, '""');
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
                    // 再将逗号转义
 | 
					                    // 再将逗号转义
 | 
				
			||||||
                    val = `"${val}"`;
 | 
					                    val = `"${val}"`;
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
                dataValueArr.push(val + '\t');
 | 
					 | 
				
			||||||
            } else {
 | 
					 | 
				
			||||||
                dataValueArr.push(val + '\t');
 | 
					 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					            dataValueArr.push(String(val));
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        cvsData.push(dataValueArr);
 | 
					        cvsData.push(dataValueArr);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -46,60 +46,6 @@ export function convertToBytes(sizeStr: string) {
 | 
				
			|||||||
    return bytes;
 | 
					    return bytes;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * 格式化json字符串
 | 
					 | 
				
			||||||
 * @param txt  json字符串
 | 
					 | 
				
			||||||
 * @param compress 是否压缩
 | 
					 | 
				
			||||||
 * @returns 格式化后的字符串
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
export function formatJsonString(txt: string, compress: boolean) {
 | 
					 | 
				
			||||||
    var indentChar = '    ';
 | 
					 | 
				
			||||||
    if (/^\s*$/.test(txt)) {
 | 
					 | 
				
			||||||
        console.log('数据为空,无法格式化! ');
 | 
					 | 
				
			||||||
        return txt;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    try {
 | 
					 | 
				
			||||||
        var data = JSON.parse(txt);
 | 
					 | 
				
			||||||
    } catch (e: any) {
 | 
					 | 
				
			||||||
        console.log('数据源语法错误,格式化失败! 错误信息: ' + e.description, 'err');
 | 
					 | 
				
			||||||
        return txt;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    var draw: any = [],
 | 
					 | 
				
			||||||
        line = compress ? '' : '\n',
 | 
					 | 
				
			||||||
        // eslint-disable-next-line no-unused-vars
 | 
					 | 
				
			||||||
        nodeCount: number = 0,
 | 
					 | 
				
			||||||
        // eslint-disable-next-line no-unused-vars
 | 
					 | 
				
			||||||
        maxDepth: number = 0;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    var notify = function (name: any, value: any, isLast: any, indent: any, formObj: any) {
 | 
					 | 
				
			||||||
        nodeCount++; /*节点计数*/
 | 
					 | 
				
			||||||
        for (var i = 0, tab = ''; i < indent; i++) tab += indentChar; /* 缩进HTML */
 | 
					 | 
				
			||||||
        tab = compress ? '' : tab; /*压缩模式忽略缩进*/
 | 
					 | 
				
			||||||
        maxDepth = ++indent; /*缩进递增并记录*/
 | 
					 | 
				
			||||||
        if (value && value.constructor == Array) {
 | 
					 | 
				
			||||||
            /*处理数组*/
 | 
					 | 
				
			||||||
            draw.push(tab + (formObj ? '"' + name + '": ' : '') + '[' + line); /*缩进'[' 然后换行*/
 | 
					 | 
				
			||||||
            for (var i = 0; i < value.length; i++) notify(i, value[i], i == value.length - 1, indent, false);
 | 
					 | 
				
			||||||
            draw.push(tab + ']' + (isLast ? line : ',' + line)); /*缩进']'换行,若非尾元素则添加逗号*/
 | 
					 | 
				
			||||||
        } else if (value && typeof value == 'object') {
 | 
					 | 
				
			||||||
            /*处理对象*/
 | 
					 | 
				
			||||||
            draw.push(tab + (formObj ? '"' + name + '": ' : '') + '{' + line); /*缩进'{' 然后换行*/
 | 
					 | 
				
			||||||
            var len = 0,
 | 
					 | 
				
			||||||
                i = 0;
 | 
					 | 
				
			||||||
            for (var key in value) len++;
 | 
					 | 
				
			||||||
            for (var key in value) notify(key, value[key], ++i == len, indent, true);
 | 
					 | 
				
			||||||
            draw.push(tab + '}' + (isLast ? line : ',' + line)); /*缩进'}'换行,若非尾元素则添加逗号*/
 | 
					 | 
				
			||||||
        } else {
 | 
					 | 
				
			||||||
            if (typeof value == 'string') value = '"' + value + '"';
 | 
					 | 
				
			||||||
            draw.push(tab + (formObj ? '"' + name + '": ' : '') + value + (isLast ? '' : ',') + line);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    };
 | 
					 | 
				
			||||||
    var isLast = true,
 | 
					 | 
				
			||||||
        indent = 0;
 | 
					 | 
				
			||||||
    notify('', data, isLast, indent, false);
 | 
					 | 
				
			||||||
    return draw.join('');
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/*
 | 
					/*
 | 
				
			||||||
 * 年(Y) 可用1-4个占位符
 | 
					 * 年(Y) 可用1-4个占位符
 | 
				
			||||||
 * 月(m)、日(d)、小时(H)、分(M)、秒(S) 可用1-2个占位符
 | 
					 * 月(m)、日(d)、小时(H)、分(M)、秒(S) 可用1-2个占位符
 | 
				
			||||||
@@ -204,6 +150,45 @@ export function formatPast(param: any, format: string = 'YYYY-mm-dd') {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * 格式化指定时间数为人性化可阅读的内容(默认time为秒单位)
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @param time 时间数
 | 
				
			||||||
 | 
					 * @param unit time对应的单位
 | 
				
			||||||
 | 
					 * @returns
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					export function formatTime(time: number, unit: string = 's') {
 | 
				
			||||||
 | 
					    const units = {
 | 
				
			||||||
 | 
					        y: 31536000,
 | 
				
			||||||
 | 
					        M: 2592000,
 | 
				
			||||||
 | 
					        d: 86400,
 | 
				
			||||||
 | 
					        h: 3600,
 | 
				
			||||||
 | 
					        m: 60,
 | 
				
			||||||
 | 
					        s: 1,
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (!units[unit]) {
 | 
				
			||||||
 | 
					        return 'Invalid unit';
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let seconds = time * units[unit];
 | 
				
			||||||
 | 
					    let result = '';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const timeUnits = Object.entries(units).map(([unit, duration]) => {
 | 
				
			||||||
 | 
					        const value = Math.floor(seconds / duration);
 | 
				
			||||||
 | 
					        seconds %= duration;
 | 
				
			||||||
 | 
					        return { value, unit };
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    timeUnits.forEach(({ value, unit }) => {
 | 
				
			||||||
 | 
					        if (value > 0) {
 | 
				
			||||||
 | 
					            result += `${value}${unit} `;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return result;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * formatAxis(new Date())   // 上午好
 | 
					 * formatAxis(new Date())   // 上午好
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										33
									
								
								mayfly_go_web/src/common/utils/object.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								mayfly_go_web/src/common/utils/object.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,33 @@
 | 
				
			|||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * 根据对象访问路径,获取对应的值
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @param obj 对象,如 {user: {name: 'xxx'}, orderNo: 1212211, products: [{id: 12}]}
 | 
				
			||||||
 | 
					 * @param path 访问路径,如 orderNo 或者 user.name 或者product[0].id
 | 
				
			||||||
 | 
					 * @returns 路径对应的值
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					export function getValueByPath(obj: any, path: string) {
 | 
				
			||||||
 | 
					    const keys = path.split('.');
 | 
				
			||||||
 | 
					    let result = obj;
 | 
				
			||||||
 | 
					    for (let key of keys) {
 | 
				
			||||||
 | 
					        if (!result || typeof result !== 'object') {
 | 
				
			||||||
 | 
					            return undefined;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (key.includes('[') && key.includes(']')) {
 | 
				
			||||||
 | 
					            // 处理包含数组索引的情况
 | 
				
			||||||
 | 
					            const arrayKey = key.substring(0, key.indexOf('['));
 | 
				
			||||||
 | 
					            const matchIndex = key.match(/\[(.*?)\]/);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (!matchIndex) {
 | 
				
			||||||
 | 
					                return undefined;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            const index = parseInt(matchIndex[1]);
 | 
				
			||||||
 | 
					            result = Array.isArray(result[arrayKey]) ? result[arrayKey][index] : undefined;
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            result = result[key];
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return result;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -1,5 +1,5 @@
 | 
				
			|||||||
<template>
 | 
					<template>
 | 
				
			||||||
    <el-input v-model="cron" placeholder="可点击左边按钮进行可视化配置">
 | 
					    <el-input v-model="cron" placeholder="可点击左边按钮配置">
 | 
				
			||||||
        <template #prepend>
 | 
					        <template #prepend>
 | 
				
			||||||
            <el-button @click="showCron = true" icon="Pointer"></el-button>
 | 
					            <el-button @click="showCron = true" icon="Pointer"></el-button>
 | 
				
			||||||
        </template>
 | 
					        </template>
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										33
									
								
								mayfly_go_web/src/components/drawer-header/DrawerHeader.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								mayfly_go_web/src/components/drawer-header/DrawerHeader.vue
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,33 @@
 | 
				
			|||||||
 | 
					<template>
 | 
				
			||||||
 | 
					    <el-page-header @back="props.back">
 | 
				
			||||||
 | 
					        <template #content>
 | 
				
			||||||
 | 
					            <span>{{ header }}</span>
 | 
				
			||||||
 | 
					            <span v-if="resource && !hideResource">
 | 
				
			||||||
 | 
					                -
 | 
				
			||||||
 | 
					                <el-tooltip v-if="resource.length > 25" :content="resource" placement="bottom">
 | 
				
			||||||
 | 
					                    <el-tag effect="dark" type="success">{{ resource.substring(0, 23) + '...' }}</el-tag>
 | 
				
			||||||
 | 
					                </el-tooltip>
 | 
				
			||||||
 | 
					                <el-tag v-else effect="dark" type="success">{{ resource }}</el-tag>
 | 
				
			||||||
 | 
					            </span>
 | 
				
			||||||
 | 
					            <el-divider v-if="slots.buttons" direction="vertical" />
 | 
				
			||||||
 | 
					            <slot v-if="slots.buttons" name="buttons"></slot>
 | 
				
			||||||
 | 
					        </template>
 | 
				
			||||||
 | 
					        <template #extra>
 | 
				
			||||||
 | 
					            <slot v-if="slots.extra" name="extra"></slot>
 | 
				
			||||||
 | 
					        </template>
 | 
				
			||||||
 | 
					    </el-page-header>
 | 
				
			||||||
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<script lang="ts" setup>
 | 
				
			||||||
 | 
					import { useSlots } from 'vue';
 | 
				
			||||||
 | 
					const slots = useSlots();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					defineOptions({ name: 'DrawerHeader' });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const props = defineProps({
 | 
				
			||||||
 | 
					    header: String,
 | 
				
			||||||
 | 
					    back: Function,
 | 
				
			||||||
 | 
					    resource: String,
 | 
				
			||||||
 | 
					    hideResource: Boolean,
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					</script>
 | 
				
			||||||
@@ -40,7 +40,7 @@ onMounted(() => {
 | 
				
			|||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const convert = (value: any) => {
 | 
					const convert = (value: any) => {
 | 
				
			||||||
    const enumValue = EnumValue.getEnumByValue(Object.values(props.enums as any) as any, value) as any;
 | 
					    const enumValue = EnumValue.getEnumByValue(props.enums, value) as any;
 | 
				
			||||||
    if (!enumValue) {
 | 
					    if (!enumValue) {
 | 
				
			||||||
        state.enumLabel = '-';
 | 
					        state.enumLabel = '-';
 | 
				
			||||||
        state.type = 'danger';
 | 
					        state.type = 'danger';
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -119,8 +119,8 @@ const open = (optionProps: MonacoEditorDialogProps) => {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    setTimeout(() => {
 | 
					    setTimeout(() => {
 | 
				
			||||||
        editorRef.value?.format();
 | 
					 | 
				
			||||||
        editorRef.value?.focus();
 | 
					        editorRef.value?.focus();
 | 
				
			||||||
 | 
					        editorRef.value?.format();
 | 
				
			||||||
    }, 300);
 | 
					    }, 300);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    state.dialogVisible = true;
 | 
					    state.dialogVisible = true;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -74,7 +74,7 @@
 | 
				
			|||||||
                                        trigger="click"
 | 
					                                        trigger="click"
 | 
				
			||||||
                                    >
 | 
					                                    >
 | 
				
			||||||
                                        <div v-for="(item, index) in tableColumns" :key="index">
 | 
					                                        <div v-for="(item, index) in tableColumns" :key="index">
 | 
				
			||||||
                                            <el-checkbox v-model="item.show" :label="item.label" :true-label="true" :false-label="false" />
 | 
					                                            <el-checkbox v-model="item.show" :label="item.label" :true-value="true" :false-value="false" />
 | 
				
			||||||
                                        </div>
 | 
					                                        </div>
 | 
				
			||||||
                                        <template #reference>
 | 
					                                        <template #reference>
 | 
				
			||||||
                                            <el-button icon="Operation" circle :size="props.size"></el-button>
 | 
					                                            <el-button icon="Operation" circle :size="props.size"></el-button>
 | 
				
			||||||
@@ -115,18 +115,18 @@
 | 
				
			|||||||
                        >
 | 
					                        >
 | 
				
			||||||
                            <!-- 插槽:预留功能 -->
 | 
					                            <!-- 插槽:预留功能 -->
 | 
				
			||||||
                            <template #default="scope" v-if="item.slot">
 | 
					                            <template #default="scope" v-if="item.slot">
 | 
				
			||||||
                                <slot :name="item.prop" :data="scope.row"></slot>
 | 
					                                <slot :name="item.slotName ? item.slotName : item.prop" :data="scope.row"></slot>
 | 
				
			||||||
                            </template>
 | 
					                            </template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                            <!-- 枚举类型使用tab展示 -->
 | 
					                            <!-- 枚举类型使用tab展示 -->
 | 
				
			||||||
                            <template #default="scope" v-else-if="item.type == 'tag'">
 | 
					                            <template #default="scope" v-else-if="item.type == 'tag'">
 | 
				
			||||||
                                <enum-tag :size="props.size" :enums="item.typeParam" :value="scope.row[item.prop]"></enum-tag>
 | 
					                                <enum-tag :size="props.size" :enums="item.typeParam" :value="item.getValueByData(scope.row)"></enum-tag>
 | 
				
			||||||
                            </template>
 | 
					                            </template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                            <template #default="scope" v-else>
 | 
					                            <template #default="scope" v-else>
 | 
				
			||||||
                                <!-- 配置了美化文本按钮以及文本内容大于指定长度,则显示美化按钮 -->
 | 
					                                <!-- 配置了美化文本按钮以及文本内容大于指定长度,则显示美化按钮 -->
 | 
				
			||||||
                                <el-popover
 | 
					                                <el-popover
 | 
				
			||||||
                                    v-if="item.isBeautify && scope.row[item.prop]?.length > 35"
 | 
					                                    v-if="item.isBeautify && item.getValueByData(scope.row)?.length > 35"
 | 
				
			||||||
                                    effect="light"
 | 
					                                    effect="light"
 | 
				
			||||||
                                    trigger="click"
 | 
					                                    trigger="click"
 | 
				
			||||||
                                    placement="top"
 | 
					                                    placement="top"
 | 
				
			||||||
@@ -137,7 +137,7 @@
 | 
				
			|||||||
                                    </template>
 | 
					                                    </template>
 | 
				
			||||||
                                    <template #reference>
 | 
					                                    <template #reference>
 | 
				
			||||||
                                        <el-link
 | 
					                                        <el-link
 | 
				
			||||||
                                            @click="formatText(scope.row[item.prop])"
 | 
					                                            @click="formatText(item.getValueByData(scope.row))"
 | 
				
			||||||
                                            :underline="false"
 | 
					                                            :underline="false"
 | 
				
			||||||
                                            type="success"
 | 
					                                            type="success"
 | 
				
			||||||
                                            icon="MagicStick"
 | 
					                                            icon="MagicStick"
 | 
				
			||||||
@@ -185,11 +185,11 @@ import SvgIcon from '@/components/svgIcon/index.vue';
 | 
				
			|||||||
import { usePageTable } from '@/hooks/usePageTable';
 | 
					import { usePageTable } from '@/hooks/usePageTable';
 | 
				
			||||||
import { ElTable } from 'element-plus';
 | 
					import { ElTable } from 'element-plus';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const emit = defineEmits(['update:queryForm', 'update:selectionData', 'pageChange']);
 | 
					const emit = defineEmits(['update:selectionData', 'pageChange']);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export interface PageTableProps {
 | 
					export interface PageTableProps {
 | 
				
			||||||
    size?: string;
 | 
					    size?: string;
 | 
				
			||||||
    pageApi: Api; // 请求表格数据的 api
 | 
					    pageApi?: Api; // 请求表格数据的 api
 | 
				
			||||||
    columns: TableColumn[]; // 列配置项  ==> 必传
 | 
					    columns: TableColumn[]; // 列配置项  ==> 必传
 | 
				
			||||||
    showSelection?: boolean;
 | 
					    showSelection?: boolean;
 | 
				
			||||||
    selectable?: (row: any) => boolean; // 是否可选
 | 
					    selectable?: (row: any) => boolean; // 是否可选
 | 
				
			||||||
@@ -257,7 +257,7 @@ const changeSimpleFormItem = (searchItem: SearchItem) => {
 | 
				
			|||||||
    nowSearchItem.value = searchItem;
 | 
					    nowSearchItem.value = searchItem;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const { tableData, total, loading, search, reset, getTableData, handlePageNumChange, handlePageSizeChange } = usePageTable(
 | 
					let { tableData, total, loading, search, reset, getTableData, handlePageNumChange, handlePageSizeChange } = usePageTable(
 | 
				
			||||||
    props.pageable,
 | 
					    props.pageable,
 | 
				
			||||||
    props.pageApi,
 | 
					    props.pageApi,
 | 
				
			||||||
    queryForm,
 | 
					    queryForm,
 | 
				
			||||||
@@ -288,6 +288,13 @@ watch(isShowSearch, () => {
 | 
				
			|||||||
    calcuTableHeight();
 | 
					    calcuTableHeight();
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					watch(
 | 
				
			||||||
 | 
					    () => props.data,
 | 
				
			||||||
 | 
					    (newValue: any) => {
 | 
				
			||||||
 | 
					        tableData = newValue;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
onMounted(async () => {
 | 
					onMounted(async () => {
 | 
				
			||||||
    calcuTableHeight();
 | 
					    calcuTableHeight();
 | 
				
			||||||
    useEventListener(window, 'resize', calcuTableHeight);
 | 
					    useEventListener(window, 'resize', calcuTableHeight);
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,5 +1,6 @@
 | 
				
			|||||||
import EnumValue from '@/common/Enum';
 | 
					import EnumValue from '@/common/Enum';
 | 
				
			||||||
import { dateFormat } from '@/common/utils/date';
 | 
					import { dateFormat } from '@/common/utils/date';
 | 
				
			||||||
 | 
					import { getValueByPath } from '@/common/utils/object';
 | 
				
			||||||
import { getTextWidth } from '@/common/utils/string';
 | 
					import { getTextWidth } from '@/common/utils/string';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export class TableColumn {
 | 
					export class TableColumn {
 | 
				
			||||||
@@ -29,10 +30,15 @@ export class TableColumn {
 | 
				
			|||||||
    minWidth: number | string;
 | 
					    minWidth: number | string;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * 是否插槽,是的话插槽名则为prop属性名
 | 
					     * 是否为插槽,若slotName为空则插槽名为prop属性名
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    slot: boolean = false;
 | 
					    slot: boolean = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * 插槽名,
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    slotName: string = '';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    showOverflowTooltip: boolean = true;
 | 
					    showOverflowTooltip: boolean = true;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    sortable: boolean = false;
 | 
					    sortable: boolean = false;
 | 
				
			||||||
@@ -87,7 +93,7 @@ export class TableColumn {
 | 
				
			|||||||
        if (this.formatFunc) {
 | 
					        if (this.formatFunc) {
 | 
				
			||||||
            return this.formatFunc(rowData, this.prop);
 | 
					            return this.formatFunc(rowData, this.prop);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        return rowData[this.prop];
 | 
					        return getValueByPath(rowData, this.prop);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    static new(prop: string, label: string): TableColumn {
 | 
					    static new(prop: string, label: string): TableColumn {
 | 
				
			||||||
@@ -144,8 +150,9 @@ export class TableColumn {
 | 
				
			|||||||
     * 标识该列为插槽
 | 
					     * 标识该列为插槽
 | 
				
			||||||
     * @returns this
 | 
					     * @returns this
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    isSlot(): TableColumn {
 | 
					    isSlot(slotName: string = ''): TableColumn {
 | 
				
			||||||
        this.slot = true;
 | 
					        this.slot = true;
 | 
				
			||||||
 | 
					        this.slotName = slotName;
 | 
				
			||||||
        return this;
 | 
					        return this;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -165,7 +172,7 @@ export class TableColumn {
 | 
				
			|||||||
     */
 | 
					     */
 | 
				
			||||||
    isTime(): TableColumn {
 | 
					    isTime(): TableColumn {
 | 
				
			||||||
        this.setFormatFunc((data: any, prop: string) => {
 | 
					        this.setFormatFunc((data: any, prop: string) => {
 | 
				
			||||||
            return dateFormat(data[prop]);
 | 
					            return dateFormat(getValueByPath(data, prop));
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
        return this;
 | 
					        return this;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@@ -176,7 +183,7 @@ export class TableColumn {
 | 
				
			|||||||
     */
 | 
					     */
 | 
				
			||||||
    isEnum(enums: any): TableColumn {
 | 
					    isEnum(enums: any): TableColumn {
 | 
				
			||||||
        this.setFormatFunc((data: any, prop: string) => {
 | 
					        this.setFormatFunc((data: any, prop: string) => {
 | 
				
			||||||
            return EnumValue.getLabelByValue(enums, data[prop]);
 | 
					            return EnumValue.getLabelByValue(enums, getValueByPath(data, prop));
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
        return this;
 | 
					        return this;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@@ -218,7 +225,7 @@ export class TableColumn {
 | 
				
			|||||||
        // 获取该列中最长的数据(内容)
 | 
					        // 获取该列中最长的数据(内容)
 | 
				
			||||||
        for (let i = 0; i < tableData.length; i++) {
 | 
					        for (let i = 0; i < tableData.length; i++) {
 | 
				
			||||||
            let nowData = tableData[i];
 | 
					            let nowData = tableData[i];
 | 
				
			||||||
            let nowValue = nowData[prop];
 | 
					            let nowValue = getValueByPath(nowData, prop);
 | 
				
			||||||
            if (!nowValue) {
 | 
					            if (!nowValue) {
 | 
				
			||||||
                continue;
 | 
					                continue;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										478
									
								
								mayfly_go_web/src/components/terminal-rdp/MachineRdp.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										478
									
								
								mayfly_go_web/src/components/terminal-rdp/MachineRdp.vue
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,478 @@
 | 
				
			|||||||
 | 
					<template>
 | 
				
			||||||
 | 
					    <div ref="viewportRef" class="viewport" :style="{ width: state.size.width + 'px', height: state.size.height + 'px' }">
 | 
				
			||||||
 | 
					        <div ref="displayRef" class="display" tabindex="0" />
 | 
				
			||||||
 | 
					        <div class="btn-box">
 | 
				
			||||||
 | 
					            <SvgIcon name="DocumentCopy" @click="openPaste" :size="20" class="pointer-icon mr10" title="剪贴板" />
 | 
				
			||||||
 | 
					            <SvgIcon name="FolderOpened" @click="openFilesystem" :size="20" class="pointer-icon mr10" title="文件管理" />
 | 
				
			||||||
 | 
					            <SvgIcon name="FullScreen" @click="state.fullscreen ? closeFullScreen() : openFullScreen()" :size="20" class="pointer-icon mr10" title="全屏" />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            <el-dropdown>
 | 
				
			||||||
 | 
					                <SvgIcon name="Monitor" :size="20" class="pointer-icon mr10" title="发送快捷键" style="color: #fff" />
 | 
				
			||||||
 | 
					                <template #dropdown>
 | 
				
			||||||
 | 
					                    <el-dropdown-menu>
 | 
				
			||||||
 | 
					                        <el-dropdown-item @click="openSendKeyboard(['65507', '65513', '65535'])"> Ctrl + Alt + Delete </el-dropdown-item>
 | 
				
			||||||
 | 
					                        <el-dropdown-item @click="openSendKeyboard(['65507', '65513', '65288'])"> Ctrl + Alt + Backspace </el-dropdown-item>
 | 
				
			||||||
 | 
					                        <el-dropdown-item @click="openSendKeyboard(['65515', '100'])"> Windows + D </el-dropdown-item>
 | 
				
			||||||
 | 
					                        <el-dropdown-item @click="openSendKeyboard(['65515', '101'])"> Windows + E </el-dropdown-item>
 | 
				
			||||||
 | 
					                        <el-dropdown-item @click="openSendKeyboard(['65515', '114'])"> Windows + R </el-dropdown-item>
 | 
				
			||||||
 | 
					                        <el-dropdown-item @click="openSendKeyboard(['65515'])"> Windows </el-dropdown-item>
 | 
				
			||||||
 | 
					                    </el-dropdown-menu>
 | 
				
			||||||
 | 
					                </template>
 | 
				
			||||||
 | 
					            </el-dropdown>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            <SvgIcon name="Refresh" @click="connect(0, 0)" :size="20" class="pointer-icon mr10" title="重新连接" />
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					        <clipboard-dialog ref="clipboardRef" v-model:visible="state.clipboardDialog.visible" @close="closePaste" @submit="onsubmitClipboard" />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        <el-dialog destroy-on-close :title="state.filesystemDialog.title" v-model="state.filesystemDialog.visible" :close-on-click-modal="false" width="70%">
 | 
				
			||||||
 | 
					            <machine-file
 | 
				
			||||||
 | 
					                :machine-id="state.filesystemDialog.machineId"
 | 
				
			||||||
 | 
					                :auth-cert-name="state.filesystemDialog.authCertName"
 | 
				
			||||||
 | 
					                :protocol="state.filesystemDialog.protocol"
 | 
				
			||||||
 | 
					                :file-id="state.filesystemDialog.fileId"
 | 
				
			||||||
 | 
					                :path="state.filesystemDialog.path"
 | 
				
			||||||
 | 
					            />
 | 
				
			||||||
 | 
					        </el-dialog>
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<script lang="ts" setup>
 | 
				
			||||||
 | 
					import Guacamole from './guac/guacamole-common';
 | 
				
			||||||
 | 
					import { getMachineRdpSocketUrl } from '@/views/ops/machine/api';
 | 
				
			||||||
 | 
					import clipboard from './guac/clipboard';
 | 
				
			||||||
 | 
					import { reactive, ref } from 'vue';
 | 
				
			||||||
 | 
					import { TerminalStatus } from '@/components/terminal/common';
 | 
				
			||||||
 | 
					import ClipboardDialog from '@/components/terminal-rdp/guac/ClipboardDialog.vue';
 | 
				
			||||||
 | 
					import { TerminalExpose } from '@/components/terminal-rdp/index';
 | 
				
			||||||
 | 
					import SvgIcon from '@/components/svgIcon/index.vue';
 | 
				
			||||||
 | 
					import MachineFile from '@/views/ops/machine/file/MachineFile.vue';
 | 
				
			||||||
 | 
					import { exitFullscreen, launchIntoFullscreen, unWatchFullscreenChange, watchFullscreenChange } from '@/components/terminal-rdp/guac/screen';
 | 
				
			||||||
 | 
					import { useEventListener } from '@vueuse/core';
 | 
				
			||||||
 | 
					import { debounce } from 'lodash';
 | 
				
			||||||
 | 
					import { ClientState, TunnelState } from '@/components/terminal-rdp/guac/states';
 | 
				
			||||||
 | 
					import { ElMessage } from 'element-plus';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const viewportRef = ref({} as any);
 | 
				
			||||||
 | 
					const displayRef = ref({} as any);
 | 
				
			||||||
 | 
					const clipboardRef = ref({} as any);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const props = defineProps({
 | 
				
			||||||
 | 
					    machineId: {
 | 
				
			||||||
 | 
					        type: Number,
 | 
				
			||||||
 | 
					        required: true,
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    authCert: {
 | 
				
			||||||
 | 
					        type: String,
 | 
				
			||||||
 | 
					        required: true,
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    clipboardList: {
 | 
				
			||||||
 | 
					        type: Array,
 | 
				
			||||||
 | 
					        default: () => [],
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const emit = defineEmits(['statusChange']);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const state = reactive({
 | 
				
			||||||
 | 
					    client: null as any,
 | 
				
			||||||
 | 
					    display: null as any,
 | 
				
			||||||
 | 
					    displayElm: {} as any,
 | 
				
			||||||
 | 
					    clipboard: {} as any,
 | 
				
			||||||
 | 
					    keyboard: {} as any,
 | 
				
			||||||
 | 
					    mouse: null as any,
 | 
				
			||||||
 | 
					    touchpad: null as any,
 | 
				
			||||||
 | 
					    errorMessage: '',
 | 
				
			||||||
 | 
					    arguments: {},
 | 
				
			||||||
 | 
					    status: TerminalStatus.NoConnected,
 | 
				
			||||||
 | 
					    size: {
 | 
				
			||||||
 | 
					        height: 710,
 | 
				
			||||||
 | 
					        width: 1024,
 | 
				
			||||||
 | 
					        force: false,
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    enableClipboard: true,
 | 
				
			||||||
 | 
					    clipboardDialog: {
 | 
				
			||||||
 | 
					        visible: false,
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    filesystemDialog: {
 | 
				
			||||||
 | 
					        visible: false,
 | 
				
			||||||
 | 
					        authCertName: '',
 | 
				
			||||||
 | 
					        machineId: 0,
 | 
				
			||||||
 | 
					        protocol: 1,
 | 
				
			||||||
 | 
					        title: '',
 | 
				
			||||||
 | 
					        fileId: 0,
 | 
				
			||||||
 | 
					        path: '',
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    fullscreen: false,
 | 
				
			||||||
 | 
					    beforeFullSize: {
 | 
				
			||||||
 | 
					        height: 710,
 | 
				
			||||||
 | 
					        width: 1024,
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const installKeyboard = () => {
 | 
				
			||||||
 | 
					    state.keyboard = new Guacamole.Keyboard(state.displayElm);
 | 
				
			||||||
 | 
					    uninstallKeyboard();
 | 
				
			||||||
 | 
					    state.keyboard.onkeydown = (keysym: any) => {
 | 
				
			||||||
 | 
					        state.client.sendKeyEvent(1, keysym);
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					    state.keyboard.onkeyup = (keysym: any) => {
 | 
				
			||||||
 | 
					        state.client.sendKeyEvent(0, keysym);
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					const uninstallKeyboard = () => {
 | 
				
			||||||
 | 
					    state.keyboard!.onkeydown = state.keyboard!.onkeyup = () => {};
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const installMouse = () => {
 | 
				
			||||||
 | 
					    state.mouse = new Guacamole.Mouse(state.displayElm);
 | 
				
			||||||
 | 
					    // Hide software cursor when mouse leaves display
 | 
				
			||||||
 | 
					    state.mouse.onmouseout = () => {
 | 
				
			||||||
 | 
					        if (!state.display) return;
 | 
				
			||||||
 | 
					        state.display.showCursor(false);
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					    state.mouse.onmousedown = state.mouse.onmouseup = state.mouse.onmousemove = handleMouseState;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const installTouchpad = () => {
 | 
				
			||||||
 | 
					    state.touchpad = new Guacamole.Mouse.Touchpad(state.displayElm);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    state.touchpad.onmousedown =
 | 
				
			||||||
 | 
					        state.touchpad.onmouseup =
 | 
				
			||||||
 | 
					        state.touchpad.onmousemove =
 | 
				
			||||||
 | 
					            (st: any) => {
 | 
				
			||||||
 | 
					                // 记录按下时,光标所在位置
 | 
				
			||||||
 | 
					                console.log(st);
 | 
				
			||||||
 | 
					                handleMouseState(st, true);
 | 
				
			||||||
 | 
					            };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // 记录单指按压时候手在屏幕的位置
 | 
				
			||||||
 | 
					    state.displayElm.ontouchend = (event: TouchEvent) => {
 | 
				
			||||||
 | 
					        console.log('end', event);
 | 
				
			||||||
 | 
					        state.displayElm.ontouchend = () => {};
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const setClipboard = (data: string) => {
 | 
				
			||||||
 | 
					    clipboardRef.value.setValue(data);
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const installClipboard = () => {
 | 
				
			||||||
 | 
					    state.enableClipboard = clipboard.install(state.client) as any;
 | 
				
			||||||
 | 
					    clipboard.installWatcher(props.clipboardList, setClipboard);
 | 
				
			||||||
 | 
					    state.client.onclipboard = clipboard.onClipboard;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const installResize = () => {
 | 
				
			||||||
 | 
					    // 在resize事件结束后300毫秒执行
 | 
				
			||||||
 | 
					    useEventListener('resize', debounce(resize, 300));
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const installDisplay = () => {
 | 
				
			||||||
 | 
					    let { width, height, force } = state.size;
 | 
				
			||||||
 | 
					    state.display = state.client.getDisplay();
 | 
				
			||||||
 | 
					    const displayElm = displayRef.value;
 | 
				
			||||||
 | 
					    displayElm.appendChild(state.display.getElement());
 | 
				
			||||||
 | 
					    displayElm.addEventListener('contextmenu', (e: any) => {
 | 
				
			||||||
 | 
					        e.stopPropagation();
 | 
				
			||||||
 | 
					        if (e.preventDefault) {
 | 
				
			||||||
 | 
					            e.preventDefault();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        e.returnValue = false;
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    state.client.connect('width=' + width + '&height=' + height + '&force=' + force);
 | 
				
			||||||
 | 
					    window.onunload = () => state.client.disconnect();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // allows focusing on the display div so that keyboard doesn't always go to session
 | 
				
			||||||
 | 
					    displayElm.onclick = () => {
 | 
				
			||||||
 | 
					        displayElm.focus();
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					    displayElm.onfocus = () => {
 | 
				
			||||||
 | 
					        displayElm.className = 'focus';
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					    displayElm.onblur = () => {
 | 
				
			||||||
 | 
					        displayElm.className = '';
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    state.displayElm = displayElm;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const installClient = () => {
 | 
				
			||||||
 | 
					    let tunnel = new Guacamole.WebSocketTunnel(getMachineRdpSocketUrl(props.authCert)) as any;
 | 
				
			||||||
 | 
					    if (state.client) {
 | 
				
			||||||
 | 
					        state.display?.scale(0);
 | 
				
			||||||
 | 
					        uninstallKeyboard();
 | 
				
			||||||
 | 
					        state.client.disconnect();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    state.client = new Guacamole.Client(tunnel);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    tunnel.onerror = (status: any) => {
 | 
				
			||||||
 | 
					        // eslint-disable-next-line no-console
 | 
				
			||||||
 | 
					        console.error(`Tunnel failed ${JSON.stringify(status)}`);
 | 
				
			||||||
 | 
					        // state.connectionState = states.TUNNEL_ERROR;
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    tunnel.onstatechange = (st: any) => {
 | 
				
			||||||
 | 
					        console.log('statechange', st);
 | 
				
			||||||
 | 
					        state.status = st;
 | 
				
			||||||
 | 
					        switch (st) {
 | 
				
			||||||
 | 
					            case TunnelState.CONNECTING: // 'CONNECTING'
 | 
				
			||||||
 | 
					                break;
 | 
				
			||||||
 | 
					            case TunnelState.OPEN: // 'OPEN'
 | 
				
			||||||
 | 
					                state.status = TerminalStatus.Connected;
 | 
				
			||||||
 | 
					                emit('statusChange', TerminalStatus.Connected);
 | 
				
			||||||
 | 
					                break;
 | 
				
			||||||
 | 
					            case TunnelState.CLOSED: // 'CLOSED'
 | 
				
			||||||
 | 
					                state.status = TerminalStatus.Disconnected;
 | 
				
			||||||
 | 
					                emit('statusChange', TerminalStatus.Disconnected);
 | 
				
			||||||
 | 
					                break;
 | 
				
			||||||
 | 
					            case TunnelState.UNSTABLE: // 'UNSTABLE'
 | 
				
			||||||
 | 
					                state.status = TerminalStatus.Error;
 | 
				
			||||||
 | 
					                emit('statusChange', TerminalStatus.Error);
 | 
				
			||||||
 | 
					                break;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    state.client.onstatechange = (clientState: any) => {
 | 
				
			||||||
 | 
					        console.log('clientState', clientState);
 | 
				
			||||||
 | 
					        switch (clientState) {
 | 
				
			||||||
 | 
					            case ClientState.IDLE:
 | 
				
			||||||
 | 
					                console.log('连接空闲');
 | 
				
			||||||
 | 
					                break;
 | 
				
			||||||
 | 
					            case ClientState.CONNECTING:
 | 
				
			||||||
 | 
					                console.log('连接中...');
 | 
				
			||||||
 | 
					                break;
 | 
				
			||||||
 | 
					            case ClientState.WAITING:
 | 
				
			||||||
 | 
					                console.log('等待服务器响应...');
 | 
				
			||||||
 | 
					                break;
 | 
				
			||||||
 | 
					            case ClientState.CONNECTED:
 | 
				
			||||||
 | 
					                console.log('连接成功...');
 | 
				
			||||||
 | 
					                break;
 | 
				
			||||||
 | 
					            // eslint-disable-next-line no-fallthrough
 | 
				
			||||||
 | 
					            case ClientState.DISCONNECTING:
 | 
				
			||||||
 | 
					                console.log('断开连接中...');
 | 
				
			||||||
 | 
					                break;
 | 
				
			||||||
 | 
					            case ClientState.DISCONNECTED:
 | 
				
			||||||
 | 
					                console.log('已断开连接...');
 | 
				
			||||||
 | 
					                break;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    state.client.onerror = (error: any) => {
 | 
				
			||||||
 | 
					        state.client.disconnect();
 | 
				
			||||||
 | 
					        console.error(`Client error ${JSON.stringify(error)}`);
 | 
				
			||||||
 | 
					        state.errorMessage = error.message;
 | 
				
			||||||
 | 
					        // state.connectionState = states.CLIENT_ERROR;
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    state.client.onsync = () => {};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    state.client.onargv = (stream: any, mimetype: any, name: any) => {
 | 
				
			||||||
 | 
					        if (mimetype !== 'text/plain') return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const reader = new Guacamole.StringReader(stream);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Assemble received data into a single string
 | 
				
			||||||
 | 
					        let value = '';
 | 
				
			||||||
 | 
					        reader.ontext = (text: any) => {
 | 
				
			||||||
 | 
					            value += text;
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Test mutability once stream is finished, storing the current value for the argument only if it is mutable
 | 
				
			||||||
 | 
					        reader.onend = () => {
 | 
				
			||||||
 | 
					            const stream = state.client.createArgumentValueStream('text/plain', name);
 | 
				
			||||||
 | 
					            stream.onack = (status: any) => {
 | 
				
			||||||
 | 
					                if (status.isError()) {
 | 
				
			||||||
 | 
					                    // ignore reject
 | 
				
			||||||
 | 
					                    return;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                state.arguments[name] = value;
 | 
				
			||||||
 | 
					            };
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const resize = () => {
 | 
				
			||||||
 | 
					    const elm = viewportRef.value;
 | 
				
			||||||
 | 
					    if (!elm || !elm.offsetWidth) {
 | 
				
			||||||
 | 
					        // resize is being called on the hidden window
 | 
				
			||||||
 | 
					        return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let box = elm.parentElement;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    state.size.width = box.clientWidth;
 | 
				
			||||||
 | 
					    state.size.height = box.clientHeight;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const width = parseInt(String(box.clientWidth));
 | 
				
			||||||
 | 
					    const height = parseInt(String(box.clientHeight));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (state.display.getWidth() !== width || state.display.getHeight() !== height) {
 | 
				
			||||||
 | 
					        if (state.status !== TerminalStatus.Connected) {
 | 
				
			||||||
 | 
					            connect(width, height);
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            state.client.sendSize(width, height);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    // setting timeout so display has time to get the correct size
 | 
				
			||||||
 | 
					    // setTimeout(() => {
 | 
				
			||||||
 | 
					    //     const scale = Math.min(box.clientWidth / Math.max(state.display.getWidth(), 1), box.clientHeight / Math.max(state.display.getHeight(), 1));
 | 
				
			||||||
 | 
					    //     state.display.scale(scale);
 | 
				
			||||||
 | 
					    //     console.log(state.size, scale);
 | 
				
			||||||
 | 
					    // }, 100);
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const handleMouseState = (mouseState: any, showCursor = false) => {
 | 
				
			||||||
 | 
					    state.client.getDisplay().showCursor(showCursor);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const scaledMouseState = Object.assign({}, mouseState, {
 | 
				
			||||||
 | 
					        x: mouseState.x / state.display.getScale(),
 | 
				
			||||||
 | 
					        y: mouseState.y / state.display.getScale(),
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    state.client.sendMouseState(scaledMouseState);
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const connect = (width: number, height: number, force = false) => {
 | 
				
			||||||
 | 
					    if (!width && !height) {
 | 
				
			||||||
 | 
					        if (state.size && state.size.width && state.size.height) {
 | 
				
			||||||
 | 
					            width = state.size.width;
 | 
				
			||||||
 | 
					            height = state.size.height;
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            // 获取当前viewportRef宽高
 | 
				
			||||||
 | 
					            width = viewportRef.value.clientWidth;
 | 
				
			||||||
 | 
					            height = viewportRef.value.clientHeight;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    state.size = { width, height, force };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    installClient();
 | 
				
			||||||
 | 
					    installDisplay();
 | 
				
			||||||
 | 
					    installKeyboard();
 | 
				
			||||||
 | 
					    installMouse();
 | 
				
			||||||
 | 
					    installTouchpad();
 | 
				
			||||||
 | 
					    installClipboard();
 | 
				
			||||||
 | 
					    installResize();
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const disconnect = () => {
 | 
				
			||||||
 | 
					    uninstallKeyboard();
 | 
				
			||||||
 | 
					    state.client?.disconnect();
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const blur = () => {
 | 
				
			||||||
 | 
					    uninstallKeyboard();
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const focus = () => {};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const openPaste = async () => {
 | 
				
			||||||
 | 
					    state.clipboardDialog.visible = true;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const closePaste = async () => {
 | 
				
			||||||
 | 
					    installKeyboard();
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const onsubmitClipboard = (val: string) => {
 | 
				
			||||||
 | 
					    state.clipboardDialog.visible = false;
 | 
				
			||||||
 | 
					    installKeyboard();
 | 
				
			||||||
 | 
					    clipboard.sendRemoteClipboard(state.client, val);
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const openFilesystem = async () => {
 | 
				
			||||||
 | 
					    state.filesystemDialog.protocol = 2;
 | 
				
			||||||
 | 
					    state.filesystemDialog.machineId = props.machineId;
 | 
				
			||||||
 | 
					    state.filesystemDialog.authCertName = props.authCert;
 | 
				
			||||||
 | 
					    state.filesystemDialog.fileId = props.machineId;
 | 
				
			||||||
 | 
					    state.filesystemDialog.path = '/';
 | 
				
			||||||
 | 
					    state.filesystemDialog.title = `远程桌面文件管理`;
 | 
				
			||||||
 | 
					    state.filesystemDialog.visible = true;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const openFullScreen = function () {
 | 
				
			||||||
 | 
					    launchIntoFullscreen(viewportRef.value);
 | 
				
			||||||
 | 
					    state.fullscreen = true;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // 记录原始尺寸
 | 
				
			||||||
 | 
					    state.beforeFullSize = {
 | 
				
			||||||
 | 
					        width: state.size.width,
 | 
				
			||||||
 | 
					        height: state.size.height,
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // 使用新的宽高重新连接
 | 
				
			||||||
 | 
					    setTimeout(() => {
 | 
				
			||||||
 | 
					        connect(viewportRef.value.clientWidth, viewportRef.value.clientHeight, false);
 | 
				
			||||||
 | 
					    }, 500);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    watchFullscreenChange(watchFullscreen);
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function watchFullscreen(event: Event, isFull: boolean) {
 | 
				
			||||||
 | 
					    if (!isFull) {
 | 
				
			||||||
 | 
					        closeFullScreen();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const closeFullScreen = function () {
 | 
				
			||||||
 | 
					    exitFullscreen();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    state.fullscreen = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // 使用新的宽高重新连接
 | 
				
			||||||
 | 
					    setTimeout(() => {
 | 
				
			||||||
 | 
					        connect(state.beforeFullSize.width, state.beforeFullSize.height, false);
 | 
				
			||||||
 | 
					    }, 500);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // 取消注册esc事件,退出全屏
 | 
				
			||||||
 | 
					    unWatchFullscreenChange(watchFullscreen);
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const openSendKeyboard = (keys: string[]) => {
 | 
				
			||||||
 | 
					    if (!state.client) {
 | 
				
			||||||
 | 
					        return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    for (let i = 0; i < keys.length; i++) {
 | 
				
			||||||
 | 
					        state.client.sendKeyEvent(1, keys[i]);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    for (let j = 0; j < keys.length; j++) {
 | 
				
			||||||
 | 
					        state.client.sendKeyEvent(0, keys[j]);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    ElMessage.success('发送组合键成功');
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const exposes = {
 | 
				
			||||||
 | 
					    connect,
 | 
				
			||||||
 | 
					    disconnect,
 | 
				
			||||||
 | 
					    init: connect,
 | 
				
			||||||
 | 
					    close: disconnect,
 | 
				
			||||||
 | 
					    fitTerminal: resize,
 | 
				
			||||||
 | 
					    focus,
 | 
				
			||||||
 | 
					    blur,
 | 
				
			||||||
 | 
					    setRemoteClipboard: onsubmitClipboard,
 | 
				
			||||||
 | 
					} as TerminalExpose;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					defineExpose(exposes);
 | 
				
			||||||
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<style lang="scss">
 | 
				
			||||||
 | 
					.viewport {
 | 
				
			||||||
 | 
					    position: relative;
 | 
				
			||||||
 | 
					    width: 1024px;
 | 
				
			||||||
 | 
					    min-height: 710px;
 | 
				
			||||||
 | 
					    z-index: 1;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					.display {
 | 
				
			||||||
 | 
					    overflow: hidden;
 | 
				
			||||||
 | 
					    width: 100%;
 | 
				
			||||||
 | 
					    height: 100%;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					.btn-box {
 | 
				
			||||||
 | 
					    position: absolute;
 | 
				
			||||||
 | 
					    top: 20px;
 | 
				
			||||||
 | 
					    right: 30px;
 | 
				
			||||||
 | 
					    padding: 5px 0 5px 10px;
 | 
				
			||||||
 | 
					    background: #dddddd4a;
 | 
				
			||||||
 | 
					    color: #fff;
 | 
				
			||||||
 | 
					    border-radius: 3px;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					</style>
 | 
				
			||||||
							
								
								
									
										130
									
								
								mayfly_go_web/src/components/terminal-rdp/MachineRdpDialog.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										130
									
								
								mayfly_go_web/src/components/terminal-rdp/MachineRdpDialog.vue
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,130 @@
 | 
				
			|||||||
 | 
					<template>
 | 
				
			||||||
 | 
					    <div class="rdpDialog" ref="dialogRef">
 | 
				
			||||||
 | 
					        <el-dialog
 | 
				
			||||||
 | 
					            v-model="dialogVisible"
 | 
				
			||||||
 | 
					            :before-close="handleClose"
 | 
				
			||||||
 | 
					            :close-on-click-modal="false"
 | 
				
			||||||
 | 
					            :destroy-on-close="true"
 | 
				
			||||||
 | 
					            :close-on-press-escape="false"
 | 
				
			||||||
 | 
					            :show-close="false"
 | 
				
			||||||
 | 
					            width="1024"
 | 
				
			||||||
 | 
					            @open="connect()"
 | 
				
			||||||
 | 
					        >
 | 
				
			||||||
 | 
					            <template #header>
 | 
				
			||||||
 | 
					                <div class="terminal-title-wrapper">
 | 
				
			||||||
 | 
					                    <!-- 左侧 -->
 | 
				
			||||||
 | 
					                    <div class="title-left-fixed">
 | 
				
			||||||
 | 
					                        <!-- title信息 -->
 | 
				
			||||||
 | 
					                        <div>
 | 
				
			||||||
 | 
					                            {{ title }}
 | 
				
			||||||
 | 
					                        </div>
 | 
				
			||||||
 | 
					                    </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    <!-- 右侧 -->
 | 
				
			||||||
 | 
					                    <div class="title-right-fixed">
 | 
				
			||||||
 | 
					                        <el-popconfirm @confirm="connect(true)" title="确认重新连接?">
 | 
				
			||||||
 | 
					                            <template #reference>
 | 
				
			||||||
 | 
					                                <div class="mr10 pointer">
 | 
				
			||||||
 | 
					                                    <el-tag v-if="state.status == TerminalStatus.Connected" type="success" effect="light" round> 已连接 </el-tag>
 | 
				
			||||||
 | 
					                                    <el-tag v-else type="danger" effect="light" round> 未连接,点击重连 </el-tag>
 | 
				
			||||||
 | 
					                                </div>
 | 
				
			||||||
 | 
					                            </template>
 | 
				
			||||||
 | 
					                        </el-popconfirm>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                        <el-popconfirm @confirm="handleClose" title="确认关闭?">
 | 
				
			||||||
 | 
					                            <template #reference>
 | 
				
			||||||
 | 
					                                <SvgIcon name="Close" class="pointer-icon" title="关闭" :size="20" />
 | 
				
			||||||
 | 
					                            </template>
 | 
				
			||||||
 | 
					                        </el-popconfirm>
 | 
				
			||||||
 | 
					                    </div>
 | 
				
			||||||
 | 
					                </div>
 | 
				
			||||||
 | 
					            </template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            <machine-rdp ref="rdpRef" :machine-id="machineId" @status-change="handleStatusChange" />
 | 
				
			||||||
 | 
					        </el-dialog>
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<script lang="ts" setup>
 | 
				
			||||||
 | 
					import { reactive, ref, toRefs, watch } from 'vue';
 | 
				
			||||||
 | 
					import MachineRdp from '@/components/terminal-rdp/MachineRdp.vue';
 | 
				
			||||||
 | 
					import { TerminalStatus } from '@/components/terminal/common';
 | 
				
			||||||
 | 
					import SvgIcon from '@/components/svgIcon/index.vue';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const rdpRef = ref({} as any);
 | 
				
			||||||
 | 
					const dialogRef = ref({} as any);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const props = defineProps({
 | 
				
			||||||
 | 
					    visible: { type: Boolean },
 | 
				
			||||||
 | 
					    machineId: { type: Number },
 | 
				
			||||||
 | 
					    title: { type: String },
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const emit = defineEmits(['update:visible', 'cancel', 'update:machineId']);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const state = reactive({
 | 
				
			||||||
 | 
					    dialogVisible: false,
 | 
				
			||||||
 | 
					    title: '',
 | 
				
			||||||
 | 
					    status: TerminalStatus.NoConnected,
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const { dialogVisible } = toRefs(state);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					watch(props, async (newValue: any) => {
 | 
				
			||||||
 | 
					    const visible = newValue.visible;
 | 
				
			||||||
 | 
					    state.dialogVisible = visible;
 | 
				
			||||||
 | 
					    if (visible) {
 | 
				
			||||||
 | 
					        state.title = newValue.title;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const connect = (force = false) => {
 | 
				
			||||||
 | 
					    rdpRef.value?.disconnect();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let width = 1024;
 | 
				
			||||||
 | 
					    let height = 710;
 | 
				
			||||||
 | 
					    rdpRef.value?.connect(width, height, force);
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const handleStatusChange = (status: TerminalStatus) => {
 | 
				
			||||||
 | 
					    state.status = status;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * 关闭取消按钮触发的事件
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					const handleClose = () => {
 | 
				
			||||||
 | 
					    emit('update:visible', false);
 | 
				
			||||||
 | 
					    emit('update:machineId', null);
 | 
				
			||||||
 | 
					    emit('cancel');
 | 
				
			||||||
 | 
					    rdpRef.value?.disconnect();
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					</script>
 | 
				
			||||||
 | 
					<style lang="scss">
 | 
				
			||||||
 | 
					.rdpDialog {
 | 
				
			||||||
 | 
					    .el-dialog {
 | 
				
			||||||
 | 
					        padding: 0;
 | 
				
			||||||
 | 
					        .el-dialog__header {
 | 
				
			||||||
 | 
					            padding: 10px;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    .el-overlay .el-overlay-dialog .el-dialog .el-dialog__body {
 | 
				
			||||||
 | 
					        max-height: 100% !important;
 | 
				
			||||||
 | 
					        padding: 0 !important;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    .terminal-title-wrapper {
 | 
				
			||||||
 | 
					        display: flex;
 | 
				
			||||||
 | 
					        justify-content: space-between;
 | 
				
			||||||
 | 
					        font-size: 16px;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        .title-right-fixed {
 | 
				
			||||||
 | 
					            display: flex;
 | 
				
			||||||
 | 
					            align-items: center;
 | 
				
			||||||
 | 
					            font-size: 20px;
 | 
				
			||||||
 | 
					            text-align: end;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					</style>
 | 
				
			||||||
@@ -0,0 +1,66 @@
 | 
				
			|||||||
 | 
					<template>
 | 
				
			||||||
 | 
					    <div class="clipboard-dialog">
 | 
				
			||||||
 | 
					        <el-dialog
 | 
				
			||||||
 | 
					            v-model="dialogVisible"
 | 
				
			||||||
 | 
					            title="请输入需要粘贴的文本"
 | 
				
			||||||
 | 
					            :before-close="onclose"
 | 
				
			||||||
 | 
					            :close-on-click-modal="false"
 | 
				
			||||||
 | 
					            :close-on-press-escape="false"
 | 
				
			||||||
 | 
					            width="600"
 | 
				
			||||||
 | 
					        >
 | 
				
			||||||
 | 
					            <el-input v-model="state.modelValue" type="textarea" :rows="20" />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            <template #footer>
 | 
				
			||||||
 | 
					                <el-button type="primary" @click="onsubmit">确 定</el-button>
 | 
				
			||||||
 | 
					            </template>
 | 
				
			||||||
 | 
					        </el-dialog>
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<script setup lang="ts">
 | 
				
			||||||
 | 
					import { reactive, toRefs, watch } from 'vue';
 | 
				
			||||||
 | 
					import { ElMessage } from 'element-plus';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const props = defineProps({
 | 
				
			||||||
 | 
					    visible: { type: Boolean },
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const emits = defineEmits(['submit', 'close', 'update:visible']);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const state = reactive({
 | 
				
			||||||
 | 
					    dialogVisible: false,
 | 
				
			||||||
 | 
					    modelValue: '',
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const { dialogVisible } = toRefs(state);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					watch(props, async (newValue: any) => {
 | 
				
			||||||
 | 
					    state.dialogVisible = newValue.visible;
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const onclose = () => {
 | 
				
			||||||
 | 
					    emits('update:visible', false);
 | 
				
			||||||
 | 
					    emits('close');
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const onsubmit = () => {
 | 
				
			||||||
 | 
					    state.dialogVisible = false;
 | 
				
			||||||
 | 
					    if (state.modelValue) {
 | 
				
			||||||
 | 
					        ElMessage.success('发送剪贴板数据成功');
 | 
				
			||||||
 | 
					        emits('submit', state.modelValue);
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					        ElMessage.warning('请输入需要粘贴的文本');
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const setValue = (val: string) => {
 | 
				
			||||||
 | 
					    state.modelValue = val;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					defineExpose({ setValue });
 | 
				
			||||||
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<style lang="scss">
 | 
				
			||||||
 | 
					.clipboard-dialog {
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					</style>
 | 
				
			||||||
							
								
								
									
										147
									
								
								mayfly_go_web/src/components/terminal-rdp/guac/clipboard.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										147
									
								
								mayfly_go_web/src/components/terminal-rdp/guac/clipboard.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,147 @@
 | 
				
			|||||||
 | 
					import Guacamole from './guacamole-common';
 | 
				
			||||||
 | 
					import { ElMessage } from 'element-plus';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const clipboard = {};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					clipboard.install = (client) => {
 | 
				
			||||||
 | 
					    if (!navigator.clipboard) {
 | 
				
			||||||
 | 
					        return false;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    clipboard.getLocalClipboard().then((data) => (clipboard.cache = data));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    window.addEventListener('load', clipboard.update(client), true);
 | 
				
			||||||
 | 
					    window.addEventListener('copy', clipboard.update(client));
 | 
				
			||||||
 | 
					    window.addEventListener('cut', clipboard.update(client));
 | 
				
			||||||
 | 
					    window.addEventListener(
 | 
				
			||||||
 | 
					        'focus',
 | 
				
			||||||
 | 
					        (e) => {
 | 
				
			||||||
 | 
					            if (e.target === window) {
 | 
				
			||||||
 | 
					                clipboard.update(client)();
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        true
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return true;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					clipboard.update = (client) => {
 | 
				
			||||||
 | 
					    return () => {
 | 
				
			||||||
 | 
					        clipboard.getLocalClipboard().then((data) => {
 | 
				
			||||||
 | 
					            clipboard.cache = data;
 | 
				
			||||||
 | 
					            clipboard.setRemoteClipboard(client);
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					clipboard.sendRemoteClipboard = (client, text) => {
 | 
				
			||||||
 | 
					    clipboard.cache = {
 | 
				
			||||||
 | 
					        type: 'text/plain',
 | 
				
			||||||
 | 
					        data: text,
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    clipboard.setRemoteClipboard(client);
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					clipboard.setRemoteClipboard = (client) => {
 | 
				
			||||||
 | 
					    if (!clipboard.cache) {
 | 
				
			||||||
 | 
					        return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let writer;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const stream = client.createClipboardStream(clipboard.cache.type);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (typeof clipboard.cache.data === 'string') {
 | 
				
			||||||
 | 
					        writer = new Guacamole.StringWriter(stream);
 | 
				
			||||||
 | 
					        writer.sendText(clipboard.cache.data);
 | 
				
			||||||
 | 
					        writer.sendEnd();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        clipboard.appendClipboardList('up', clipboard.cache.data);
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					        writer = new Guacamole.BlobWriter(stream);
 | 
				
			||||||
 | 
					        writer.oncomplete = function clipboardSent() {
 | 
				
			||||||
 | 
					            writer.sendEnd();
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					        writer.sendBlob(clipboard.cache.data);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					clipboard.getLocalClipboard = async () => {
 | 
				
			||||||
 | 
					    // 获取本地剪贴板数据
 | 
				
			||||||
 | 
					    if (navigator.clipboard && navigator.clipboard.readText) {
 | 
				
			||||||
 | 
					        const text = await navigator.clipboard.readText();
 | 
				
			||||||
 | 
					        return {
 | 
				
			||||||
 | 
					            type: 'text/plain',
 | 
				
			||||||
 | 
					            data: text,
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					        ElMessage.warning('只有https才可以访问剪贴板');
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					clipboard.setLocalClipboard = async (data) => {
 | 
				
			||||||
 | 
					    if (data.type === 'text/plain') {
 | 
				
			||||||
 | 
					        if (navigator.clipboard && navigator.clipboard.writeText) {
 | 
				
			||||||
 | 
					            await navigator.clipboard.writeText(data.data);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// 获取到远程服务器剪贴板变动
 | 
				
			||||||
 | 
					clipboard.onClipboard = (stream, mimetype) => {
 | 
				
			||||||
 | 
					    let reader;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (/^text\//.exec(mimetype)) {
 | 
				
			||||||
 | 
					        reader = new Guacamole.StringReader(stream);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Assemble received data into a single string
 | 
				
			||||||
 | 
					        let data = '';
 | 
				
			||||||
 | 
					        reader.ontext = (text) => {
 | 
				
			||||||
 | 
					            data += text;
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Set clipboard contents once stream is finished
 | 
				
			||||||
 | 
					        reader.onend = () => {
 | 
				
			||||||
 | 
					            clipboard.setLocalClipboard({
 | 
				
			||||||
 | 
					                type: mimetype,
 | 
				
			||||||
 | 
					                data: data,
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            clipboard.setClipboardFn && typeof clipboard.setClipboardFn === 'function' && clipboard.setClipboardFn(data);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            clipboard.appendClipboardList('down', data);
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					        reader = new Guacamole.BlobReader(stream, mimetype);
 | 
				
			||||||
 | 
					        reader.onend = () => {
 | 
				
			||||||
 | 
					            clipboard.setLocalClipboard({
 | 
				
			||||||
 | 
					                type: mimetype,
 | 
				
			||||||
 | 
					                data: reader.getBlob(),
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/***
 | 
				
			||||||
 | 
					 * 注册剪贴板监听器,如果有本地或远程剪贴板变动,则会更新剪贴板列表
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					clipboard.installWatcher = (clipboardList, setClipboardFn) => {
 | 
				
			||||||
 | 
					    clipboard.clipboardList = clipboardList;
 | 
				
			||||||
 | 
					    clipboard.setClipboardFn = setClipboardFn;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					clipboard.appendClipboardList = (src, data) => {
 | 
				
			||||||
 | 
					    clipboard.clipboardList = clipboard.clipboardList || [];
 | 
				
			||||||
 | 
					    // 循环判断是否重复
 | 
				
			||||||
 | 
					    for (let i = 0; i < clipboard.clipboardList.length; i++) {
 | 
				
			||||||
 | 
					        if (clipboard.clipboardList[i].data === data) {
 | 
				
			||||||
 | 
					            return;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    clipboard.clipboardList.push({ type: 'text/plain', data, src });
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default clipboard;
 | 
				
			||||||
							
								
								
									
										14441
									
								
								mayfly_go_web/src/components/terminal-rdp/guac/guacamole-common.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14441
									
								
								mayfly_go_web/src/components/terminal-rdp/guac/guacamole-common.js
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										38
									
								
								mayfly_go_web/src/components/terminal-rdp/guac/screen.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								mayfly_go_web/src/components/terminal-rdp/guac/screen.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,38 @@
 | 
				
			|||||||
 | 
					export function launchIntoFullscreen(element) {
 | 
				
			||||||
 | 
					    if (element.requestFullscreen) {
 | 
				
			||||||
 | 
					        element.requestFullscreen();
 | 
				
			||||||
 | 
					    } else if (element.mozRequestFullScreen) {
 | 
				
			||||||
 | 
					        element.mozRequestFullScreen();
 | 
				
			||||||
 | 
					    } else if (element.webkitRequestFullscreen) {
 | 
				
			||||||
 | 
					        element.webkitRequestFullscreen();
 | 
				
			||||||
 | 
					    } else if (element.msRequestFullscreen) {
 | 
				
			||||||
 | 
					        element.msRequestFullscreen();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					export function exitFullscreen() {
 | 
				
			||||||
 | 
					    if (document.exitFullscreen) {
 | 
				
			||||||
 | 
					        document.exitFullscreen();
 | 
				
			||||||
 | 
					    } else if (document.mozCancelFullScreen) {
 | 
				
			||||||
 | 
					        document.mozCancelFullScreen();
 | 
				
			||||||
 | 
					    } else if (document.webkitExitFullscreen) {
 | 
				
			||||||
 | 
					        document.webkitExitFullscreen();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function watchFullscreenChange(callback) {
 | 
				
			||||||
 | 
					    function onFullscreenChange(e) {
 | 
				
			||||||
 | 
					        let isFull = (document.fullscreenElement || document.mozFullScreenElement || document.webkitFullscreenElement || document.msFullscreenElement) != null;
 | 
				
			||||||
 | 
					        callback(e, isFull);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    document.addEventListener('fullscreenchange', onFullscreenChange);
 | 
				
			||||||
 | 
					    document.addEventListener('mozfullscreenchange', onFullscreenChange);
 | 
				
			||||||
 | 
					    document.addEventListener('webkitfullscreenchange', onFullscreenChange);
 | 
				
			||||||
 | 
					    document.addEventListener('msfullscreenchange', onFullscreenChange);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function unWatchFullscreenChange(callback) {
 | 
				
			||||||
 | 
					    document.removeEventListener('fullscreenchange', callback);
 | 
				
			||||||
 | 
					    document.removeEventListener('mozfullscreenchange', callback);
 | 
				
			||||||
 | 
					    document.removeEventListener('webkitfullscreenchange', callback);
 | 
				
			||||||
 | 
					    document.removeEventListener('msfullscreenchange', callback);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										78
									
								
								mayfly_go_web/src/components/terminal-rdp/guac/states.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										78
									
								
								mayfly_go_web/src/components/terminal-rdp/guac/states.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,78 @@
 | 
				
			|||||||
 | 
					export const ClientState = {
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * The client is idle, with no active connection.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @type number
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    IDLE: 0,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * The client is in the process of establishing a connection.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @type {!number}
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    CONNECTING: 1,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * The client is waiting on further information or a remote server to
 | 
				
			||||||
 | 
					     * establish the connection.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @type {!number}
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    WAITING: 2,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * The client is actively connected to a remote server.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @type {!number}
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    CONNECTED: 3,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * The client is in the process of disconnecting from the remote server.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @type {!number}
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    DISCONNECTING: 4,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * The client has completed the connection and is no longer connected.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @type {!number}
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    DISCONNECTED: 5,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const TunnelState = {
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * A connection is in pending. It is not yet known whether connection was
 | 
				
			||||||
 | 
					     * successful.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @type {!number}
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    CONNECTING: 0,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Connection was successful, and data is being received.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @type {!number}
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    OPEN: 1,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * The connection is closed. Connection may not have been successful, the
 | 
				
			||||||
 | 
					     * tunnel may have been explicitly closed by either side, or an error may
 | 
				
			||||||
 | 
					     * have occurred.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @type {!number}
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    CLOSED: 2,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * The connection is open, but communication through the tunnel appears to
 | 
				
			||||||
 | 
					     * be disrupted, and the connection may close as a result.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @type {!number}
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    UNSTABLE: 3,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
							
								
								
									
										11
									
								
								mayfly_go_web/src/components/terminal-rdp/index.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								mayfly_go_web/src/components/terminal-rdp/index.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,11 @@
 | 
				
			|||||||
 | 
					export interface TerminalExpose {
 | 
				
			||||||
 | 
					    /** 连接 */
 | 
				
			||||||
 | 
					    init(width: number, height: number, force: boolean): void;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /** 短开连接 */
 | 
				
			||||||
 | 
					    close(): void;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    blur(): void;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    focus(): void;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -1,5 +1,5 @@
 | 
				
			|||||||
<template>
 | 
					<template>
 | 
				
			||||||
    <div id="terminal-body" :style="{ height, background: themeConfig.terminalBackground }">
 | 
					    <div id="terminal-body" :style="{ height }">
 | 
				
			||||||
        <div ref="terminalRef" class="terminal" />
 | 
					        <div ref="terminalRef" class="terminal" />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        <TerminalSearch ref="terminalSearchRef" :search-addon="state.addon.search" @close="focus" />
 | 
					        <TerminalSearch ref="terminalSearchRef" :search-addon="state.addon.search" @close="focus" />
 | 
				
			||||||
@@ -8,7 +8,7 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
<script lang="ts" setup>
 | 
					<script lang="ts" setup>
 | 
				
			||||||
import 'xterm/css/xterm.css';
 | 
					import 'xterm/css/xterm.css';
 | 
				
			||||||
import { Terminal } from 'xterm';
 | 
					import { Terminal, ITheme } from 'xterm';
 | 
				
			||||||
import { FitAddon } from 'xterm-addon-fit';
 | 
					import { FitAddon } from 'xterm-addon-fit';
 | 
				
			||||||
import { SearchAddon } from 'xterm-addon-search';
 | 
					import { SearchAddon } from 'xterm-addon-search';
 | 
				
			||||||
import { WebLinksAddon } from 'xterm-addon-web-links';
 | 
					import { WebLinksAddon } from 'xterm-addon-web-links';
 | 
				
			||||||
@@ -20,8 +20,15 @@ import TerminalSearch from './TerminalSearch.vue';
 | 
				
			|||||||
import { debounce } from 'lodash';
 | 
					import { debounce } from 'lodash';
 | 
				
			||||||
import { TerminalStatus } from './common';
 | 
					import { TerminalStatus } from './common';
 | 
				
			||||||
import { useEventListener } from '@vueuse/core';
 | 
					import { useEventListener } from '@vueuse/core';
 | 
				
			||||||
 | 
					import themes from './themes';
 | 
				
			||||||
 | 
					import { TrzszFilter } from 'trzsz';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const props = defineProps({
 | 
					const props = defineProps({
 | 
				
			||||||
 | 
					    // mounted时,是否执行init方法
 | 
				
			||||||
 | 
					    mountInit: {
 | 
				
			||||||
 | 
					        type: Boolean,
 | 
				
			||||||
 | 
					        default: true,
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * 初始化执行命令
 | 
					     * 初始化执行命令
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
@@ -64,9 +71,9 @@ const state = reactive({
 | 
				
			|||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
onMounted(() => {
 | 
					onMounted(() => {
 | 
				
			||||||
    nextTick(() => {
 | 
					    if (props.mountInit) {
 | 
				
			||||||
        init();
 | 
					        init();
 | 
				
			||||||
    });
 | 
					    }
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
watch(
 | 
					watch(
 | 
				
			||||||
@@ -76,6 +83,14 @@ watch(
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
);
 | 
					);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// 监听 themeConfig terminalTheme配置的变化
 | 
				
			||||||
 | 
					watch(
 | 
				
			||||||
 | 
					    () => themeConfig.value.terminalTheme,
 | 
				
			||||||
 | 
					    () => {
 | 
				
			||||||
 | 
					        term.options.theme = getTerminalTheme();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
onBeforeUnmount(() => {
 | 
					onBeforeUnmount(() => {
 | 
				
			||||||
    close();
 | 
					    close();
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
@@ -85,6 +100,12 @@ function init() {
 | 
				
			|||||||
        console.log('重新连接...');
 | 
					        console.log('重新连接...');
 | 
				
			||||||
        close();
 | 
					        close();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					    nextTick(() => {
 | 
				
			||||||
 | 
					        initTerm();
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function initTerm() {
 | 
				
			||||||
    term = new Terminal({
 | 
					    term = new Terminal({
 | 
				
			||||||
        fontSize: themeConfig.value.terminalFontSize || 15,
 | 
					        fontSize: themeConfig.value.terminalFontSize || 15,
 | 
				
			||||||
        fontWeight: themeConfig.value.terminalFontWeight || 'normal',
 | 
					        fontWeight: themeConfig.value.terminalFontWeight || 'normal',
 | 
				
			||||||
@@ -92,13 +113,10 @@ function init() {
 | 
				
			|||||||
        cursorBlink: true,
 | 
					        cursorBlink: true,
 | 
				
			||||||
        disableStdin: false,
 | 
					        disableStdin: false,
 | 
				
			||||||
        allowProposedApi: true,
 | 
					        allowProposedApi: true,
 | 
				
			||||||
        theme: {
 | 
					        fastScrollModifier: 'ctrl',
 | 
				
			||||||
            foreground: themeConfig.value.terminalForeground || '#7e9192', //字体
 | 
					        theme: getTerminalTheme(),
 | 
				
			||||||
            background: themeConfig.value.terminalBackground || '#002833', //背景色
 | 
					 | 
				
			||||||
            cursor: themeConfig.value.terminalCursor || '#268F81', //设置光标
 | 
					 | 
				
			||||||
            // cursorAccent: "red",  // 光标停止颜色
 | 
					 | 
				
			||||||
        } as any,
 | 
					 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    term.open(terminalRef.value);
 | 
					    term.open(terminalRef.value);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // 注册自适应组件
 | 
					    // 注册自适应组件
 | 
				
			||||||
@@ -106,31 +124,12 @@ function init() {
 | 
				
			|||||||
    state.addon.fit = fitAddon;
 | 
					    state.addon.fit = fitAddon;
 | 
				
			||||||
    term.loadAddon(fitAddon);
 | 
					    term.loadAddon(fitAddon);
 | 
				
			||||||
    fitTerminal();
 | 
					    fitTerminal();
 | 
				
			||||||
 | 
					    // 注册窗口大小监听器
 | 
				
			||||||
 | 
					    useEventListener('resize', debounce(fitTerminal, 400));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // 注册搜索组件
 | 
					 | 
				
			||||||
    const searchAddon = new SearchAddon();
 | 
					 | 
				
			||||||
    state.addon.search = searchAddon;
 | 
					 | 
				
			||||||
    term.loadAddon(searchAddon);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // 注册 url link组件
 | 
					 | 
				
			||||||
    const weblinks = new WebLinksAddon();
 | 
					 | 
				
			||||||
    state.addon.weblinks = weblinks;
 | 
					 | 
				
			||||||
    term.loadAddon(weblinks);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // 初始化websocket
 | 
					 | 
				
			||||||
    initSocket();
 | 
					    initSocket();
 | 
				
			||||||
}
 | 
					    // 注册其他插件
 | 
				
			||||||
 | 
					    loadAddon();
 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * 连接成功
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
const onConnected = () => {
 | 
					 | 
				
			||||||
    // 注册心跳
 | 
					 | 
				
			||||||
    pingInterval = setInterval(sendPing, 15000);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // 注册 terminal 事件
 | 
					 | 
				
			||||||
    term.onResize((event) => sendResize(event.cols, event.rows));
 | 
					 | 
				
			||||||
    term.onData((event) => sendCmd(event));
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // 注册自定义快捷键
 | 
					    // 注册自定义快捷键
 | 
				
			||||||
    term.attachCustomKeyEventHandler((event: KeyboardEvent) => {
 | 
					    term.attachCustomKeyEventHandler((event: KeyboardEvent) => {
 | 
				
			||||||
@@ -142,50 +141,25 @@ const onConnected = () => {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        return true;
 | 
					        return true;
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
    state.status = TerminalStatus.Connected;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // 注册窗口大小监听器
 | 
					 | 
				
			||||||
    useEventListener('resize', debounce(fitTerminal, 400));
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    focus();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // 如果有初始要执行的命令,则发送执行命令
 | 
					 | 
				
			||||||
    if (props.cmd) {
 | 
					 | 
				
			||||||
        sendCmd(props.cmd + ' \r');
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// 自适应终端
 | 
					 | 
				
			||||||
const fitTerminal = () => {
 | 
					 | 
				
			||||||
    const dimensions = state.addon.fit && state.addon.fit.proposeDimensions();
 | 
					 | 
				
			||||||
    if (!dimensions) {
 | 
					 | 
				
			||||||
        return;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    if (dimensions?.cols && dimensions?.rows) {
 | 
					 | 
				
			||||||
        term.resize(dimensions.cols, dimensions.rows);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const focus = () => {
 | 
					 | 
				
			||||||
    setTimeout(() => term.focus(), 400);
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const clear = () => {
 | 
					 | 
				
			||||||
    term.clear();
 | 
					 | 
				
			||||||
    term.clearSelection();
 | 
					 | 
				
			||||||
    term.focus();
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
function initSocket() {
 | 
					function initSocket() {
 | 
				
			||||||
    if (props.socketUrl) {
 | 
					    if (!props.socketUrl) {
 | 
				
			||||||
        let socketUrl = `${props.socketUrl}&rows=${term?.rows}&cols=${term?.cols}`;
 | 
					        return;
 | 
				
			||||||
        socket = new WebSocket(socketUrl);
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					    socket = new WebSocket(`${props.socketUrl}&rows=${term?.rows}&cols=${term?.cols}`);
 | 
				
			||||||
    // 监听socket连接
 | 
					    // 监听socket连接
 | 
				
			||||||
    socket.onopen = () => {
 | 
					    socket.onopen = () => {
 | 
				
			||||||
        onConnected();
 | 
					        // 注册心跳
 | 
				
			||||||
 | 
					        pingInterval = setInterval(sendPing, 15000);
 | 
				
			||||||
 | 
					        state.status = TerminalStatus.Connected;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        focus();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // 如果有初始要执行的命令,则发送执行命令
 | 
				
			||||||
 | 
					        if (props.cmd) {
 | 
				
			||||||
 | 
					            sendCmd(props.cmd + ' \r');
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // 监听socket错误信息
 | 
					    // 监听socket错误信息
 | 
				
			||||||
@@ -197,20 +171,96 @@ function initSocket() {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    socket.onclose = (e: CloseEvent) => {
 | 
					    socket.onclose = (e: CloseEvent) => {
 | 
				
			||||||
        console.log('terminal socket close...', e.reason);
 | 
					        console.log('terminal socket close...', e.reason);
 | 
				
			||||||
        // 清除 ping
 | 
					 | 
				
			||||||
        pingInterval && clearInterval(pingInterval);
 | 
					 | 
				
			||||||
        state.status = TerminalStatus.Disconnected;
 | 
					        state.status = TerminalStatus.Disconnected;
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
 | 
					 | 
				
			||||||
    // 监听socket消息
 | 
					 | 
				
			||||||
    socket.onmessage = getMessage;
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function getMessage(msg: any) {
 | 
					function loadAddon() {
 | 
				
			||||||
    // msg.data是真正后端返回的数据
 | 
					    // 注册搜索组件
 | 
				
			||||||
    term.write(msg.data);
 | 
					    const searchAddon = new SearchAddon();
 | 
				
			||||||
 | 
					    state.addon.search = searchAddon;
 | 
				
			||||||
 | 
					    term.loadAddon(searchAddon);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // 注册 url link组件
 | 
				
			||||||
 | 
					    const weblinks = new WebLinksAddon();
 | 
				
			||||||
 | 
					    state.addon.weblinks = weblinks;
 | 
				
			||||||
 | 
					    term.loadAddon(weblinks);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // 注册 trzsz
 | 
				
			||||||
 | 
					    // initialize trzsz filter
 | 
				
			||||||
 | 
					    const trzsz = new TrzszFilter({
 | 
				
			||||||
 | 
					        // write the server output to the terminal
 | 
				
			||||||
 | 
					        writeToTerminal: (data: any) => term.write(typeof data === 'string' ? data : new Uint8Array(data)),
 | 
				
			||||||
 | 
					        // send the user input to the server
 | 
				
			||||||
 | 
					        sendToServer: sendCmd,
 | 
				
			||||||
 | 
					        // the terminal columns
 | 
				
			||||||
 | 
					        terminalColumns: term.cols,
 | 
				
			||||||
 | 
					        // there is a windows shell
 | 
				
			||||||
 | 
					        isWindowsShell: false,
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // let trzsz process the server output
 | 
				
			||||||
 | 
					    socket?.addEventListener('message', (e) => trzsz.processServerOutput(e.data));
 | 
				
			||||||
 | 
					    // let trzsz process the user input
 | 
				
			||||||
 | 
					    term.onData((data) => trzsz.processTerminalInput(data));
 | 
				
			||||||
 | 
					    term.onBinary((data) => trzsz.processBinaryInput(data));
 | 
				
			||||||
 | 
					    term.onResize((size) => {
 | 
				
			||||||
 | 
					        sendResize(size.cols, size.rows);
 | 
				
			||||||
 | 
					        // tell trzsz the terminal columns has been changed
 | 
				
			||||||
 | 
					        trzsz.setTerminalColumns(size.cols);
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    window.addEventListener('resize', () => state.addon.fit.fit());
 | 
				
			||||||
 | 
					    // enable drag files or directories to upload
 | 
				
			||||||
 | 
					    terminalRef.value.addEventListener('dragover', (event: Event) => event.preventDefault());
 | 
				
			||||||
 | 
					    terminalRef.value.addEventListener('drop', (event: any) => {
 | 
				
			||||||
 | 
					        event.preventDefault();
 | 
				
			||||||
 | 
					        trzsz
 | 
				
			||||||
 | 
					            .uploadFiles(event.dataTransfer.items)
 | 
				
			||||||
 | 
					            .then(() => console.log('upload success'))
 | 
				
			||||||
 | 
					            .catch((err: any) => console.log(err));
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// 写入内容至终端
 | 
				
			||||||
 | 
					const write2Term = (data: any) => {
 | 
				
			||||||
 | 
					    term.write(data);
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const writeln2Term = (data: any) => {
 | 
				
			||||||
 | 
					    term.writeln(data);
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const getTerminalTheme = () => {
 | 
				
			||||||
 | 
					    const terminalTheme = themeConfig.value.terminalTheme;
 | 
				
			||||||
 | 
					    // 如果不是自定义主题,则返回内置主题
 | 
				
			||||||
 | 
					    if (terminalTheme != 'custom') {
 | 
				
			||||||
 | 
					        return themes[terminalTheme];
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // 自定义主题
 | 
				
			||||||
 | 
					    return {
 | 
				
			||||||
 | 
					        foreground: themeConfig.value.terminalForeground || '#7e9192', //字体
 | 
				
			||||||
 | 
					        background: themeConfig.value.terminalBackground || '#002833', //背景色
 | 
				
			||||||
 | 
					        cursor: themeConfig.value.terminalCursor || '#268F81', //设置光标
 | 
				
			||||||
 | 
					        // cursorAccent: "red",  // 光标停止颜色
 | 
				
			||||||
 | 
					    } as ITheme;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// 自适应终端
 | 
				
			||||||
 | 
					const fitTerminal = () => {
 | 
				
			||||||
 | 
					    state.addon.fit.fit();
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const focus = () => {
 | 
				
			||||||
 | 
					    setTimeout(() => term.focus(), 300);
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const clear = () => {
 | 
				
			||||||
 | 
					    term.clear();
 | 
				
			||||||
 | 
					    term.clearSelection();
 | 
				
			||||||
 | 
					    term.focus();
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
enum MsgType {
 | 
					enum MsgType {
 | 
				
			||||||
    Resize = 1,
 | 
					    Resize = 1,
 | 
				
			||||||
    Data = 2,
 | 
					    Data = 2,
 | 
				
			||||||
@@ -218,29 +268,19 @@ enum MsgType {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const send = (msg: any) => {
 | 
					const send = (msg: any) => {
 | 
				
			||||||
    state.status == TerminalStatus.Connected && socket.send(JSON.stringify(msg));
 | 
					    state.status == TerminalStatus.Connected && socket?.send(msg);
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const sendResize = (cols: number, rows: number) => {
 | 
					const sendResize = (cols: number, rows: number) => {
 | 
				
			||||||
    send({
 | 
					    send(`${MsgType.Resize}|${rows}|${cols}`);
 | 
				
			||||||
        type: MsgType.Resize,
 | 
					 | 
				
			||||||
        Cols: cols,
 | 
					 | 
				
			||||||
        Rows: rows,
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const sendPing = () => {
 | 
					const sendPing = () => {
 | 
				
			||||||
    send({
 | 
					    send(`${MsgType.Ping}|ping`);
 | 
				
			||||||
        type: MsgType.Ping,
 | 
					 | 
				
			||||||
        msg: 'ping',
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function sendCmd(key: any) {
 | 
					function sendCmd(key: any) {
 | 
				
			||||||
    send({
 | 
					    send(`${MsgType.Data}|${key}`);
 | 
				
			||||||
        type: MsgType.Data,
 | 
					 | 
				
			||||||
        msg: key,
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function closeSocket() {
 | 
					function closeSocket() {
 | 
				
			||||||
@@ -265,20 +305,19 @@ const getStatus = (): TerminalStatus => {
 | 
				
			|||||||
    return state.status;
 | 
					    return state.status;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
defineExpose({ init, fitTerminal, focus, clear, close, getStatus });
 | 
					defineExpose({ init, fitTerminal, focus, clear, close, getStatus, sendResize, write2Term, writeln2Term });
 | 
				
			||||||
</script>
 | 
					</script>
 | 
				
			||||||
<style lang="scss">
 | 
					<style lang="scss">
 | 
				
			||||||
#terminal-body {
 | 
					#terminal-body {
 | 
				
			||||||
    background: #212529;
 | 
					 | 
				
			||||||
    width: 100%;
 | 
					    width: 100%;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    .terminal {
 | 
					    .terminal {
 | 
				
			||||||
        width: 100%;
 | 
					        width: 100%;
 | 
				
			||||||
        height: 100%;
 | 
					        height: 100%;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        .xterm .xterm-viewport {
 | 
					        // .xterm .xterm-viewport {
 | 
				
			||||||
            overflow-y: hidden;
 | 
					        //     overflow-y: hidden;
 | 
				
			||||||
        }
 | 
					        // }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
</style>
 | 
					</style>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -2,7 +2,7 @@
 | 
				
			|||||||
    <div>
 | 
					    <div>
 | 
				
			||||||
        <div class="terminal-dialog-container" v-for="openTerminal of terminals" :key="openTerminal.terminalId">
 | 
					        <div class="terminal-dialog-container" v-for="openTerminal of terminals" :key="openTerminal.terminalId">
 | 
				
			||||||
            <el-dialog
 | 
					            <el-dialog
 | 
				
			||||||
                title="终端"
 | 
					                title="SSH终端"
 | 
				
			||||||
                v-model="openTerminal.visible"
 | 
					                v-model="openTerminal.visible"
 | 
				
			||||||
                top="32px"
 | 
					                top="32px"
 | 
				
			||||||
                class="terminal-dialog"
 | 
					                class="terminal-dialog"
 | 
				
			||||||
@@ -58,7 +58,7 @@
 | 
				
			|||||||
                        </div>
 | 
					                        </div>
 | 
				
			||||||
                    </div>
 | 
					                    </div>
 | 
				
			||||||
                </template>
 | 
					                </template>
 | 
				
			||||||
                <div class="terminal-wrapper" :style="{ height: `calc(100vh - ${openTerminal.fullscreen ? '49px' : '200px'})` }">
 | 
					                <div :style="{ height: `calc(100vh - ${openTerminal.fullscreen ? '49px' : '200px'})` }">
 | 
				
			||||||
                    <TerminalBody
 | 
					                    <TerminalBody
 | 
				
			||||||
                        @status-change="terminalStatusChange(openTerminal.terminalId, $event)"
 | 
					                        @status-change="terminalStatusChange(openTerminal.terminalId, $event)"
 | 
				
			||||||
                        :ref="(el) => setTerminalRef(el, openTerminal.terminalId)"
 | 
					                        :ref="(el) => setTerminalRef(el, openTerminal.terminalId)"
 | 
				
			||||||
@@ -92,7 +92,7 @@
 | 
				
			|||||||
</template>
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<script lang="ts" setup>
 | 
					<script lang="ts" setup>
 | 
				
			||||||
import { toRefs, reactive } from 'vue';
 | 
					import { reactive, toRefs } from 'vue';
 | 
				
			||||||
import TerminalBody from '@/components/terminal/TerminalBody.vue';
 | 
					import TerminalBody from '@/components/terminal/TerminalBody.vue';
 | 
				
			||||||
import SvgIcon from '@/components/svgIcon/index.vue';
 | 
					import SvgIcon from '@/components/svgIcon/index.vue';
 | 
				
			||||||
import { TerminalStatus } from './common';
 | 
					import { TerminalStatus } from './common';
 | 
				
			||||||
@@ -259,6 +259,10 @@ defineExpose({
 | 
				
			|||||||
        padding: 10px;
 | 
					        padding: 10px;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    .el-dialog {
 | 
				
			||||||
 | 
					        padding: 1px 1px;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // 取消body最大高度,否则全屏有问题
 | 
					    // 取消body最大高度,否则全屏有问题
 | 
				
			||||||
    .el-dialog__body {
 | 
					    .el-dialog__body {
 | 
				
			||||||
        max-height: 100% !important;
 | 
					        max-height: 100% !important;
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										113
									
								
								mayfly_go_web/src/components/terminal/TerminalLog.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										113
									
								
								mayfly_go_web/src/components/terminal/TerminalLog.vue
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,113 @@
 | 
				
			|||||||
 | 
					<template>
 | 
				
			||||||
 | 
					    <div>
 | 
				
			||||||
 | 
					        <el-drawer v-model="visible" :before-close="cancel" size="50%">
 | 
				
			||||||
 | 
					            <template #header>
 | 
				
			||||||
 | 
					                <DrawerHeader :header="props.title" :back="cancel">
 | 
				
			||||||
 | 
					                    <template #extra>
 | 
				
			||||||
 | 
					                        <EnumTag :enums="LogTypeEnum" :value="log?.type" class="mr20" />
 | 
				
			||||||
 | 
					                    </template>
 | 
				
			||||||
 | 
					                </DrawerHeader>
 | 
				
			||||||
 | 
					            </template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            <el-descriptions class="mb10" :column="1" border v-if="extra">
 | 
				
			||||||
 | 
					                <el-descriptions-item v-for="(value, key) in extra" :key="key" :span="1" :label="key">{{ value }}</el-descriptions-item>
 | 
				
			||||||
 | 
					            </el-descriptions>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            <TerminalBody class="mb10" ref="terminalRef" height="calc(100vh - 220px)" />
 | 
				
			||||||
 | 
					        </el-drawer>
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<script lang="ts" setup>
 | 
				
			||||||
 | 
					import { computed, ref, watch } from 'vue';
 | 
				
			||||||
 | 
					import DrawerHeader from '@/components/drawer-header/DrawerHeader.vue';
 | 
				
			||||||
 | 
					import TerminalBody from './TerminalBody.vue';
 | 
				
			||||||
 | 
					import { logApi } from '../../views/system/api';
 | 
				
			||||||
 | 
					import { LogTypeEnum } from '@/views/system/enums';
 | 
				
			||||||
 | 
					import { useIntervalFn } from '@vueuse/core';
 | 
				
			||||||
 | 
					import EnumTag from '@/components/enumtag/EnumTag.vue';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const props = defineProps({
 | 
				
			||||||
 | 
					    title: {
 | 
				
			||||||
 | 
					        type: String,
 | 
				
			||||||
 | 
					        default: '日志',
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const visible = defineModel<boolean>('visible', { default: false });
 | 
				
			||||||
 | 
					const logId = defineModel<number>('logId', { default: 0 });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const terminalRef: any = ref(null);
 | 
				
			||||||
 | 
					const nowLine = ref(0);
 | 
				
			||||||
 | 
					const log = ref({}) as any;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const extra = computed(() => {
 | 
				
			||||||
 | 
					    if (log.value?.extra) {
 | 
				
			||||||
 | 
					        return JSON.parse(log.value.extra);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    return null;
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// 定时获取最新日志
 | 
				
			||||||
 | 
					const { pause, resume } = useIntervalFn(() => {
 | 
				
			||||||
 | 
					    writeLog();
 | 
				
			||||||
 | 
					}, 500);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					watch(
 | 
				
			||||||
 | 
					    () => logId.value,
 | 
				
			||||||
 | 
					    (logId: number) => {
 | 
				
			||||||
 | 
					        terminalRef.value?.clear();
 | 
				
			||||||
 | 
					        if (!logId) {
 | 
				
			||||||
 | 
					            return;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        writeLog();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const cancel = () => {
 | 
				
			||||||
 | 
					    visible.value = false;
 | 
				
			||||||
 | 
					    logId.value = 0;
 | 
				
			||||||
 | 
					    nowLine.value = 0;
 | 
				
			||||||
 | 
					    pause();
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const writeLog = async () => {
 | 
				
			||||||
 | 
					    const log = await getLog();
 | 
				
			||||||
 | 
					    if (!log) {
 | 
				
			||||||
 | 
					        return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    writeLog2Term(log);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // 如果不是还在执行中的日志,则暂停轮询
 | 
				
			||||||
 | 
					    if (log.type != LogTypeEnum.Running.value) {
 | 
				
			||||||
 | 
					        pause();
 | 
				
			||||||
 | 
					        return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    resume();
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const writeLog2Term = (log: any) => {
 | 
				
			||||||
 | 
					    if (!log) {
 | 
				
			||||||
 | 
					        return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    const lines = log.resp.split('\n');
 | 
				
			||||||
 | 
					    for (let line of lines.slice(nowLine.value)) {
 | 
				
			||||||
 | 
					        nowLine.value += 1;
 | 
				
			||||||
 | 
					        terminalRef.value?.writeln2Term(line);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    terminalRef.value?.focus();
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const getLog = async () => {
 | 
				
			||||||
 | 
					    if (!logId.value) {
 | 
				
			||||||
 | 
					        return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    const logRes = await logApi.detail.request({
 | 
				
			||||||
 | 
					        id: logId.value,
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    log.value = logRes;
 | 
				
			||||||
 | 
					    return logRes;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<style lang="scss"></style>
 | 
				
			||||||
							
								
								
									
										92
									
								
								mayfly_go_web/src/components/terminal/themes.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										92
									
								
								mayfly_go_web/src/components/terminal/themes.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,92 @@
 | 
				
			|||||||
 | 
					export default {
 | 
				
			||||||
 | 
					    dark: {
 | 
				
			||||||
 | 
					        foreground: '#c7c7c7',
 | 
				
			||||||
 | 
					        background: '#000000',
 | 
				
			||||||
 | 
					        cursor: '#c7c7c7',
 | 
				
			||||||
 | 
					        selectionBackground: '#686868',
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        black: '#000000',
 | 
				
			||||||
 | 
					        brightBlack: '#676767',
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        red: '#c91b00',
 | 
				
			||||||
 | 
					        brightRed: '#ff6d67',
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        green: '#00c200',
 | 
				
			||||||
 | 
					        brightGreen: '#5ff967',
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        yellow: '#c7c400',
 | 
				
			||||||
 | 
					        brightYellow: '#fefb67',
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        blue: '#0225c7',
 | 
				
			||||||
 | 
					        brightBlue: '#6871ff',
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        magenta: '#c930c7',
 | 
				
			||||||
 | 
					        brightMagenta: '#ff76ff',
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        cyan: '#00c5c7',
 | 
				
			||||||
 | 
					        brightCyan: '#5ffdff',
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        white: '#c7c7c7',
 | 
				
			||||||
 | 
					        brightWhite: '#fffefe',
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    light: {
 | 
				
			||||||
 | 
					        foreground: '#000000',
 | 
				
			||||||
 | 
					        background: '#fffefe',
 | 
				
			||||||
 | 
					        cursor: '#000000',
 | 
				
			||||||
 | 
					        selectionBackground: '#c7c7c7',
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        black: '#000000',
 | 
				
			||||||
 | 
					        brightBlack: '#676767',
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        red: '#c91b00',
 | 
				
			||||||
 | 
					        brightRed: '#ff6d67',
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        green: '#00c200',
 | 
				
			||||||
 | 
					        brightGreen: '#5ff967',
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        yellow: '#c7c400',
 | 
				
			||||||
 | 
					        brightYellow: '#fefb67',
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        blue: '#0225c7',
 | 
				
			||||||
 | 
					        brightBlue: '#6871ff',
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        magenta: '#c930c7',
 | 
				
			||||||
 | 
					        brightMagenta: '#ff76ff',
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        cyan: '#00c5c7',
 | 
				
			||||||
 | 
					        brightCyan: '#5ffdff',
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        white: '#c7c7c7',
 | 
				
			||||||
 | 
					        brightWhite: '#fffefe',
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    solarizedLight: {
 | 
				
			||||||
 | 
					        foreground: '#657b83',
 | 
				
			||||||
 | 
					        background: '#fdf6e3',
 | 
				
			||||||
 | 
					        cursor: '#657b83',
 | 
				
			||||||
 | 
					        selectionBackground: '#c7c7c7',
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        black: '#073642',
 | 
				
			||||||
 | 
					        brightBlack: '#002b36',
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        red: '#dc322f',
 | 
				
			||||||
 | 
					        brightRed: '#cb4b16',
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        green: '#859900',
 | 
				
			||||||
 | 
					        brightGreen: '#586e75',
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        yellow: '#b58900',
 | 
				
			||||||
 | 
					        brightYellow: '#657b83',
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        blue: '#268bd2',
 | 
				
			||||||
 | 
					        brightBlue: '#839496',
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        magenta: '#d33682',
 | 
				
			||||||
 | 
					        brightMagenta: '#6c71c4',
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        cyan: '#2aa198',
 | 
				
			||||||
 | 
					        brightCyan: '#93a1a1',
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        white: '#eee8d5',
 | 
				
			||||||
 | 
					        brightWhite: '#fdf6e3',
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
@@ -1,5 +1,5 @@
 | 
				
			|||||||
import Api from '@/common/Api';
 | 
					import Api from '@/common/Api';
 | 
				
			||||||
import { isReactive, reactive, toRefs, toValue } from 'vue';
 | 
					import { reactive, toRefs, toValue } from 'vue';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * @description table 页面操作方法封装
 | 
					 * @description table 页面操作方法封装
 | 
				
			||||||
@@ -41,12 +41,6 @@ export const usePageTable = (
 | 
				
			|||||||
            let sp = toValue(state.searchParams);
 | 
					            let sp = toValue(state.searchParams);
 | 
				
			||||||
            if (beforeQueryFn) {
 | 
					            if (beforeQueryFn) {
 | 
				
			||||||
                sp = beforeQueryFn(sp);
 | 
					                sp = beforeQueryFn(sp);
 | 
				
			||||||
 | 
					 | 
				
			||||||
                if (isReactive(state.searchParams)) {
 | 
					 | 
				
			||||||
                    state.searchParams.value = sp;
 | 
					 | 
				
			||||||
                } else {
 | 
					 | 
				
			||||||
                    state.searchParams = sp;
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            let res = await api.request(sp);
 | 
					            let res = await api.request(sp);
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -16,7 +16,7 @@ const useCustomFetch = createFetch({
 | 
				
			|||||||
    combination: 'chain',
 | 
					    combination: 'chain',
 | 
				
			||||||
    options: {
 | 
					    options: {
 | 
				
			||||||
        immediate: false,
 | 
					        immediate: false,
 | 
				
			||||||
        timeout: 60000,
 | 
					        timeout: 600000,
 | 
				
			||||||
        // beforeFetch in pre-configured instance will only run when the newly spawned instance do not pass beforeFetch
 | 
					        // beforeFetch in pre-configured instance will only run when the newly spawned instance do not pass beforeFetch
 | 
				
			||||||
        async beforeFetch({ options }) {
 | 
					        async beforeFetch({ options }) {
 | 
				
			||||||
            const token = getToken();
 | 
					            const token = getToken();
 | 
				
			||||||
@@ -48,9 +48,6 @@ export function useApiFetch<T>(api: Api, params: any = null, reqOptions: Request
 | 
				
			|||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            let paramsValue = unref(params);
 | 
					            let paramsValue = unref(params);
 | 
				
			||||||
            if (api.beforeHandler) {
 | 
					 | 
				
			||||||
                paramsValue = api.beforeHandler(paramsValue);
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
            let apiUrl = url;
 | 
					            let apiUrl = url;
 | 
				
			||||||
            // 简单判断该url是否是restful风格
 | 
					            // 简单判断该url是否是restful风格
 | 
				
			||||||
@@ -58,6 +55,10 @@ export function useApiFetch<T>(api: Api, params: any = null, reqOptions: Request
 | 
				
			|||||||
                apiUrl = templateResolve(apiUrl, paramsValue);
 | 
					                apiUrl = templateResolve(apiUrl, paramsValue);
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (api.beforeHandler) {
 | 
				
			||||||
 | 
					                paramsValue = api.beforeHandler(paramsValue);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            if (paramsValue) {
 | 
					            if (paramsValue) {
 | 
				
			||||||
                const method = options.method?.toLowerCase();
 | 
					                const method = options.method?.toLowerCase();
 | 
				
			||||||
                // post和put使用json格式传参
 | 
					                // post和put使用json格式传参
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,18 +1,15 @@
 | 
				
			|||||||
<template>
 | 
					<template>
 | 
				
			||||||
    <div class="layout-navbars-breadcrumb" v-show="themeConfig.isBreadcrumb">
 | 
					    <div class="layout-navbars-breadcrumb" v-show="themeConfig.isBreadcrumb">
 | 
				
			||||||
        <SvgIcon class="layout-navbars-breadcrumb-icon" :name="themeConfig.isCollapse ? 'expand' : 'fold'"
 | 
					        <SvgIcon class="layout-navbars-breadcrumb-icon" :name="themeConfig.isCollapse ? 'expand' : 'fold'" @click="onThemeConfigChange" />
 | 
				
			||||||
            @click="onThemeConfigChange" />
 | 
					 | 
				
			||||||
        <el-breadcrumb class="layout-navbars-breadcrumb-hide">
 | 
					        <el-breadcrumb class="layout-navbars-breadcrumb-hide">
 | 
				
			||||||
            <transition-group name="breadcrumb" mode="out-in">
 | 
					            <transition-group name="breadcrumb" mode="out-in">
 | 
				
			||||||
                <el-breadcrumb-item v-for="(v, k) in state.breadcrumbList" :key="v.meta.title">
 | 
					                <el-breadcrumb-item v-for="(v, k) in state.breadcrumbList" :key="v.meta.title">
 | 
				
			||||||
                    <span v-if="k === state.breadcrumbList.length - 1" class="layout-navbars-breadcrumb-span">
 | 
					                    <span v-if="k === state.breadcrumbList.length - 1" class="layout-navbars-breadcrumb-span">
 | 
				
			||||||
                        <SvgIcon :name="v.meta.icon" class="layout-navbars-breadcrumb-iconfont"
 | 
					                        <SvgIcon :name="v.meta.icon" class="layout-navbars-breadcrumb-iconfont" v-if="themeConfig.isBreadcrumbIcon" />
 | 
				
			||||||
                            v-if="themeConfig.isBreadcrumbIcon" />
 | 
					 | 
				
			||||||
                        {{ v.meta.title }}
 | 
					                        {{ v.meta.title }}
 | 
				
			||||||
                    </span>
 | 
					                    </span>
 | 
				
			||||||
                    <a v-else @click.prevent="onBreadcrumbClick(v)">
 | 
					                    <a v-else @click.prevent="onBreadcrumbClick(v)">
 | 
				
			||||||
                        <SvgIcon :name="v.meta.icon" class="layout-navbars-breadcrumb-iconfont"
 | 
					                        <SvgIcon :name="v.meta.icon" class="layout-navbars-breadcrumb-iconfont" v-if="themeConfig.isBreadcrumbIcon" />
 | 
				
			||||||
                            v-if="themeConfig.isBreadcrumbIcon" />
 | 
					 | 
				
			||||||
                        {{ v.meta.title }}
 | 
					                        {{ v.meta.title }}
 | 
				
			||||||
                    </a>
 | 
					                    </a>
 | 
				
			||||||
                </el-breadcrumb-item>
 | 
					                </el-breadcrumb-item>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,17 +1,22 @@
 | 
				
			|||||||
<template>
 | 
					<template>
 | 
				
			||||||
    <div class="layout-search-dialog">
 | 
					    <div class="layout-search-dialog">
 | 
				
			||||||
        <el-dialog v-model="state.isShowSearch" width="300px" destroy-on-close :modal="false" fullscreen :show-close="false">
 | 
					        <el-dialog v-model="state.isShowSearch" width="300px" destroy-on-close :modal="false" fullscreen :show-close="false">
 | 
				
			||||||
            <el-autocomplete v-model="state.menuQuery" :fetch-suggestions="menuSearch" placeholder="菜单搜索"
 | 
					            <el-autocomplete
 | 
				
			||||||
                prefix-icon="el-icon-search" ref="layoutMenuAutocompleteRef" @select="onHandleSelect" @blur="onSearchBlur">
 | 
					                v-model="state.menuQuery"
 | 
				
			||||||
 | 
					                :fetch-suggestions="menuSearch"
 | 
				
			||||||
 | 
					                placeholder="菜单搜索"
 | 
				
			||||||
 | 
					                prefix-icon="el-icon-search"
 | 
				
			||||||
 | 
					                ref="layoutMenuAutocompleteRef"
 | 
				
			||||||
 | 
					                @select="onHandleSelect"
 | 
				
			||||||
 | 
					                @blur="onSearchBlur"
 | 
				
			||||||
 | 
					            >
 | 
				
			||||||
                <template #prefix>
 | 
					                <template #prefix>
 | 
				
			||||||
                    <el-icon class="el-input__icon">
 | 
					                    <el-icon class="el-input__icon">
 | 
				
			||||||
                        <search />
 | 
					                        <search />
 | 
				
			||||||
                    </el-icon>
 | 
					                    </el-icon>
 | 
				
			||||||
                </template>
 | 
					                </template>
 | 
				
			||||||
                <template #default="{ item }">
 | 
					                <template #default="{ item }">
 | 
				
			||||||
                    <div>
 | 
					                    <div><SvgIcon :name="item.meta.icon" class="mr5" />{{ item.meta.title }}</div>
 | 
				
			||||||
                        <SvgIcon :name="item.meta.icon" class="mr5" />{{ item.meta.title }}
 | 
					 | 
				
			||||||
                    </div>
 | 
					 | 
				
			||||||
                </template>
 | 
					                </template>
 | 
				
			||||||
            </el-autocomplete>
 | 
					            </el-autocomplete>
 | 
				
			||||||
        </el-dialog>
 | 
					        </el-dialog>
 | 
				
			||||||
@@ -23,7 +28,7 @@ import { reactive, ref, nextTick } from 'vue';
 | 
				
			|||||||
import { useRouter } from 'vue-router';
 | 
					import { useRouter } from 'vue-router';
 | 
				
			||||||
import { useRoutesList } from '@/store/routesList';
 | 
					import { useRoutesList } from '@/store/routesList';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const layoutMenuAutocompleteRef: any = ref(null);;
 | 
					const layoutMenuAutocompleteRef: any = ref(null);
 | 
				
			||||||
const router = useRouter();
 | 
					const router = useRouter();
 | 
				
			||||||
const state: any = reactive({
 | 
					const state: any = reactive({
 | 
				
			||||||
    isShowSearch: false,
 | 
					    isShowSearch: false,
 | 
				
			||||||
@@ -54,8 +59,7 @@ const menuSearch = (queryString: any, cb: any) => {
 | 
				
			|||||||
const createFilter = (queryString: any) => {
 | 
					const createFilter = (queryString: any) => {
 | 
				
			||||||
    return (restaurant: any) => {
 | 
					    return (restaurant: any) => {
 | 
				
			||||||
        return (
 | 
					        return (
 | 
				
			||||||
            restaurant.path.toLowerCase().indexOf(queryString.toLowerCase()) > -1 ||
 | 
					            restaurant.path.toLowerCase().indexOf(queryString.toLowerCase()) > -1 || restaurant.meta.title.toLowerCase().indexOf(queryString.toLowerCase()) > -1
 | 
				
			||||||
            restaurant.meta.title.toLowerCase().indexOf(queryString.toLowerCase()) > -1
 | 
					 | 
				
			||||||
        );
 | 
					        );
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
@@ -97,7 +101,7 @@ const onSearchBlur = () => {
 | 
				
			|||||||
    closeSearch();
 | 
					    closeSearch();
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
defineExpose({openSearch})
 | 
					defineExpose({ openSearch });
 | 
				
			||||||
</script>
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<style scoped lang="scss">
 | 
					<style scoped lang="scss">
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -5,26 +5,39 @@
 | 
				
			|||||||
                <!-- ssh终端主题 -->
 | 
					                <!-- ssh终端主题 -->
 | 
				
			||||||
                <el-divider content-position="left">终端主题</el-divider>
 | 
					                <el-divider content-position="left">终端主题</el-divider>
 | 
				
			||||||
                <div class="layout-breadcrumb-seting-bar-flex">
 | 
					                <div class="layout-breadcrumb-seting-bar-flex">
 | 
				
			||||||
                    <div class="layout-breadcrumb-seting-bar-flex-label">字体颜色</div>
 | 
					                    <div class="layout-breadcrumb-seting-bar-flex-label">主题</div>
 | 
				
			||||||
                    <div class="layout-breadcrumb-seting-bar-flex-value">
 | 
					                    <div class="layout-breadcrumb-seting-bar-flex-value">
 | 
				
			||||||
                        <el-color-picker v-model="themeConfig.terminalForeground" size="small" @change="onColorPickerChange('terminalForeground')">
 | 
					                        <el-select @change="setLocalThemeConfig" v-model="themeConfig.terminalTheme" size="small" style="width: 140px">
 | 
				
			||||||
                        </el-color-picker>
 | 
					                            <el-option v-for="(_, k) in themes" :key="k" :label="k" :value="k"> </el-option>
 | 
				
			||||||
 | 
					                            <el-option label="自定义" value="custom"> </el-option>
 | 
				
			||||||
 | 
					                        </el-select>
 | 
				
			||||||
                    </div>
 | 
					                    </div>
 | 
				
			||||||
                </div>
 | 
					                </div>
 | 
				
			||||||
                <div class="layout-breadcrumb-seting-bar-flex">
 | 
					                <template v-if="themeConfig.terminalTheme == 'custom'">
 | 
				
			||||||
                    <div class="layout-breadcrumb-seting-bar-flex-label">背景颜色</div>
 | 
					                    <div class="layout-breadcrumb-seting-bar-flex mt10">
 | 
				
			||||||
                    <div class="layout-breadcrumb-seting-bar-flex-value">
 | 
					                        <div class="layout-breadcrumb-seting-bar-flex-label">字体颜色</div>
 | 
				
			||||||
                        <el-color-picker v-model="themeConfig.terminalBackground" size="small" @change="onColorPickerChange('terminalBackground')">
 | 
					                        <div class="layout-breadcrumb-seting-bar-flex-value">
 | 
				
			||||||
                        </el-color-picker>
 | 
					                            <el-color-picker v-model="themeConfig.terminalForeground" size="small" @change="onColorPickerChange('terminalForeground')">
 | 
				
			||||||
 | 
					                            </el-color-picker>
 | 
				
			||||||
 | 
					                        </div>
 | 
				
			||||||
                    </div>
 | 
					                    </div>
 | 
				
			||||||
                </div>
 | 
					                    <div class="layout-breadcrumb-seting-bar-flex">
 | 
				
			||||||
                <div class="layout-breadcrumb-seting-bar-flex">
 | 
					                        <div class="layout-breadcrumb-seting-bar-flex-label">背景颜色</div>
 | 
				
			||||||
                    <div class="layout-breadcrumb-seting-bar-flex-label">cursor颜色</div>
 | 
					                        <div class="layout-breadcrumb-seting-bar-flex-value">
 | 
				
			||||||
                    <div class="layout-breadcrumb-seting-bar-flex-value">
 | 
					                            <el-color-picker v-model="themeConfig.terminalBackground" size="small" @change="onColorPickerChange('terminalBackground')">
 | 
				
			||||||
                        <el-color-picker v-model="themeConfig.terminalCursor" size="small" @change="onColorPickerChange('terminalCursor')"> </el-color-picker>
 | 
					                            </el-color-picker>
 | 
				
			||||||
 | 
					                        </div>
 | 
				
			||||||
                    </div>
 | 
					                    </div>
 | 
				
			||||||
                </div>
 | 
					                    <div class="layout-breadcrumb-seting-bar-flex">
 | 
				
			||||||
                <div class="layout-breadcrumb-seting-bar-flex mt15">
 | 
					                        <div class="layout-breadcrumb-seting-bar-flex-label">cursor颜色</div>
 | 
				
			||||||
 | 
					                        <div class="layout-breadcrumb-seting-bar-flex-value">
 | 
				
			||||||
 | 
					                            <el-color-picker v-model="themeConfig.terminalCursor" size="small" @change="onColorPickerChange('terminalCursor')">
 | 
				
			||||||
 | 
					                            </el-color-picker>
 | 
				
			||||||
 | 
					                        </div>
 | 
				
			||||||
 | 
					                    </div>
 | 
				
			||||||
 | 
					                </template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                <div class="layout-breadcrumb-seting-bar-flex mt10">
 | 
				
			||||||
                    <div class="layout-breadcrumb-seting-bar-flex-label">字体大小</div>
 | 
					                    <div class="layout-breadcrumb-seting-bar-flex-label">字体大小</div>
 | 
				
			||||||
                    <div class="layout-breadcrumb-seting-bar-flex-value">
 | 
					                    <div class="layout-breadcrumb-seting-bar-flex-value">
 | 
				
			||||||
                        <el-input-number
 | 
					                        <el-input-number
 | 
				
			||||||
@@ -39,7 +52,7 @@
 | 
				
			|||||||
                        </el-input-number>
 | 
					                        </el-input-number>
 | 
				
			||||||
                    </div>
 | 
					                    </div>
 | 
				
			||||||
                </div>
 | 
					                </div>
 | 
				
			||||||
                <div class="layout-breadcrumb-seting-bar-flex mt15">
 | 
					                <div class="layout-breadcrumb-seting-bar-flex mt10">
 | 
				
			||||||
                    <div class="layout-breadcrumb-seting-bar-flex-label">字体粗细</div>
 | 
					                    <div class="layout-breadcrumb-seting-bar-flex-label">字体粗细</div>
 | 
				
			||||||
                    <div class="layout-breadcrumb-seting-bar-flex-value">
 | 
					                    <div class="layout-breadcrumb-seting-bar-flex-value">
 | 
				
			||||||
                        <el-select @change="setLocalThemeConfig" v-model="themeConfig.terminalFontWeight" size="small" style="width: 90px">
 | 
					                        <el-select @change="setLocalThemeConfig" v-model="themeConfig.terminalFontWeight" size="small" style="width: 90px">
 | 
				
			||||||
@@ -418,6 +431,7 @@ import { useThemeConfig } from '@/store/themeConfig';
 | 
				
			|||||||
import { getLightColor } from '@/common/utils/theme';
 | 
					import { getLightColor } from '@/common/utils/theme';
 | 
				
			||||||
import { setLocal, getLocal, removeLocal } from '@/common/utils/storage';
 | 
					import { setLocal, getLocal, removeLocal } from '@/common/utils/storage';
 | 
				
			||||||
import mittBus from '@/common/utils/mitt';
 | 
					import mittBus from '@/common/utils/mitt';
 | 
				
			||||||
 | 
					import themes from '@/components/terminal/themes';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const copyConfigBtnRef = ref();
 | 
					const copyConfigBtnRef = ref();
 | 
				
			||||||
const { themeConfig } = storeToRefs(useThemeConfig());
 | 
					const { themeConfig } = storeToRefs(useThemeConfig());
 | 
				
			||||||
@@ -615,6 +629,9 @@ const setLocalThemeConfigStyle = () => {
 | 
				
			|||||||
};
 | 
					};
 | 
				
			||||||
// 一键复制配置
 | 
					// 一键复制配置
 | 
				
			||||||
const onCopyConfigClick = (target: any) => {
 | 
					const onCopyConfigClick = (target: any) => {
 | 
				
			||||||
 | 
					    if (!target) {
 | 
				
			||||||
 | 
					        return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
    let copyThemeConfig = getLocal('themeConfig');
 | 
					    let copyThemeConfig = getLocal('themeConfig');
 | 
				
			||||||
    copyThemeConfig.isDrawer = false;
 | 
					    copyThemeConfig.isDrawer = false;
 | 
				
			||||||
    const clipboard = new ClipboardJS(target, {
 | 
					    const clipboard = new ClipboardJS(target, {
 | 
				
			||||||
@@ -690,6 +707,25 @@ defineExpose({ openDrawer });
 | 
				
			|||||||
</script>
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<style scoped lang="scss">
 | 
					<style scoped lang="scss">
 | 
				
			||||||
 | 
					::v-deep(.el-drawer) {
 | 
				
			||||||
 | 
					    --el-drawer-padding-primary: unset !important;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    .el-drawer__header {
 | 
				
			||||||
 | 
					        padding: 0 15px !important;
 | 
				
			||||||
 | 
					        height: 50px;
 | 
				
			||||||
 | 
					        display: flex;
 | 
				
			||||||
 | 
					        align-items: center;
 | 
				
			||||||
 | 
					        margin-bottom: 0 !important;
 | 
				
			||||||
 | 
					        border-bottom: 1px solid var(--el-border-color);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    .el-drawer__body {
 | 
				
			||||||
 | 
					        width: 100%;
 | 
				
			||||||
 | 
					        height: 100%;
 | 
				
			||||||
 | 
					        overflow: auto;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.layout-breadcrumb-seting-bar {
 | 
					.layout-breadcrumb-seting-bar {
 | 
				
			||||||
    height: calc(100vh - 50px);
 | 
					    height: calc(100vh - 50px);
 | 
				
			||||||
    padding: 0 15px;
 | 
					    padding: 0 15px;
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										20
									
								
								mayfly_go_web/src/layout/navBars/breadcrumb/switchDark.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								mayfly_go_web/src/layout/navBars/breadcrumb/switchDark.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,20 @@
 | 
				
			|||||||
 | 
					import { saveThemeConfig } from '@/common/utils/storage';
 | 
				
			||||||
 | 
					import { isDark } from './user.vue';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const switchDark = () => {
 | 
				
			||||||
 | 
					    themeConfig.value.isDark = isDark.value;
 | 
				
			||||||
 | 
					    if (isDark.value) {
 | 
				
			||||||
 | 
					        themeConfig.value.editorTheme = 'vs-dark';
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					        themeConfig.value.editorTheme = 'vs';
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    // 如果终端主题不是自定义主题,则切换主题
 | 
				
			||||||
 | 
					    if (themeConfig.value.terminalTheme != 'custom') {
 | 
				
			||||||
 | 
					        if (isDark.value) {
 | 
				
			||||||
 | 
					            themeConfig.value.terminalTheme = 'dark';
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            themeConfig.value.terminalTheme = 'solarizedLight';
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    saveThemeConfig(themeConfig.value);
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
@@ -174,12 +174,7 @@ watch(preDark, (newValue) => {
 | 
				
			|||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const switchDark = () => {
 | 
					const switchDark = () => {
 | 
				
			||||||
    themeConfig.value.isDark = isDark.value;
 | 
					    themeConfigStore.switchDark(isDark.value);
 | 
				
			||||||
    if (isDark.value) {
 | 
					 | 
				
			||||||
        themeConfig.value.editorTheme = 'vs-dark';
 | 
					 | 
				
			||||||
    } else {
 | 
					 | 
				
			||||||
        themeConfig.value.editorTheme = 'vs';
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    saveThemeConfig(themeConfig.value);
 | 
					    saveThemeConfig(themeConfig.value);
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -284,7 +284,9 @@ const onTagsClick = (v: any, k: number) => {
 | 
				
			|||||||
    state.tagsRefsIndex = k;
 | 
					    state.tagsRefsIndex = k;
 | 
				
			||||||
    try {
 | 
					    try {
 | 
				
			||||||
        router.push(v);
 | 
					        router.push(v);
 | 
				
			||||||
    } catch (e) {}
 | 
					    } catch (e) {
 | 
				
			||||||
 | 
					        // skip
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
// 更新滚动条显示
 | 
					// 更新滚动条显示
 | 
				
			||||||
const updateScrollbar = () => {
 | 
					const updateScrollbar = () => {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -89,13 +89,21 @@ type RouterConvCallbackFunc = (router: any) => void;
 | 
				
			|||||||
 * @param meta.link ==> 外链地址
 | 
					 * @param meta.link ==> 外链地址
 | 
				
			||||||
 * */
 | 
					 * */
 | 
				
			||||||
export function backEndRouterConverter(routes: any, callbackFunc: RouterConvCallbackFunc = null as any, parentPath: string = '/') {
 | 
					export function backEndRouterConverter(routes: any, callbackFunc: RouterConvCallbackFunc = null as any, parentPath: string = '/') {
 | 
				
			||||||
    if (!routes) return [];
 | 
					    if (!routes) {
 | 
				
			||||||
    return routes.map((item: any) => {
 | 
					        return [];
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const routeItems = [];
 | 
				
			||||||
 | 
					    for (let item of routes) {
 | 
				
			||||||
        if (!item.meta) {
 | 
					        if (!item.meta) {
 | 
				
			||||||
            return item;
 | 
					            return item;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        // 将json字符串的meta转为对象
 | 
					        // 将json字符串的meta转为对象
 | 
				
			||||||
        item.meta = JSON.parse(item.meta);
 | 
					        item.meta = JSON.parse(item.meta);
 | 
				
			||||||
 | 
					        if (item.meta.isHide) {
 | 
				
			||||||
 | 
					            continue;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // 将meta.comoponet 解析为route.component
 | 
					        // 将meta.comoponet 解析为route.component
 | 
				
			||||||
        if (item.meta.component) {
 | 
					        if (item.meta.component) {
 | 
				
			||||||
            item.component = dynamicImport(dynamicViewsModules, item.meta.component);
 | 
					            item.component = dynamicImport(dynamicViewsModules, item.meta.component);
 | 
				
			||||||
@@ -126,8 +134,10 @@ export function backEndRouterConverter(routes: any, callbackFunc: RouterConvCall
 | 
				
			|||||||
        // 存在回调,则执行回调
 | 
					        // 存在回调,则执行回调
 | 
				
			||||||
        callbackFunc && callbackFunc(item);
 | 
					        callbackFunc && callbackFunc(item);
 | 
				
			||||||
        item.children && backEndRouterConverter(item.children, callbackFunc, item.path);
 | 
					        item.children && backEndRouterConverter(item.children, callbackFunc, item.path);
 | 
				
			||||||
        return item;
 | 
					        routeItems.push(item);
 | 
				
			||||||
    });
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return routeItems;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -92,7 +92,7 @@ router.beforeEach(async (to, from, next) => {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // 终端不需要连接系统websocket消息
 | 
					    // 终端不需要连接系统websocket消息
 | 
				
			||||||
    if (to.path != '/machine/terminal') {
 | 
					    if (to.path != '/machine/terminal' && to.path != '/machine/terminal-rdp') {
 | 
				
			||||||
        syssocket.init();
 | 
					        syssocket.init();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -54,6 +54,17 @@ export const staticRoutes: Array<RouteRecordRaw> = [
 | 
				
			|||||||
            titleRename: true,
 | 
					            titleRename: true,
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        path: '/machine/terminal-rdp',
 | 
				
			||||||
 | 
					        name: 'machineTerminalRdp',
 | 
				
			||||||
 | 
					        component: () => import('@/views/ops/machine/RdpTerminalPage.vue'),
 | 
				
			||||||
 | 
					        meta: {
 | 
				
			||||||
 | 
					            // 将路径 'xxx?name=名字' 里的name字段值替换到title里
 | 
				
			||||||
 | 
					            title: '终端 | {name}',
 | 
				
			||||||
 | 
					            // 是否根据query对标题名进行参数替换,即最终显示为‘终端_机器名’
 | 
				
			||||||
 | 
					            titleRename: true,
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
];
 | 
					];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// 错误页面路由
 | 
					// 错误页面路由
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -114,6 +114,7 @@ export const useThemeConfig = defineStore('themeConfig', {
 | 
				
			|||||||
            // 默认布局,可选 1、默认 defaults 2、经典 classic 3、横向 transverse 4、分栏 columns
 | 
					            // 默认布局,可选 1、默认 defaults 2、经典 classic 3、横向 transverse 4、分栏 columns
 | 
				
			||||||
            layout: 'classic',
 | 
					            layout: 'classic',
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            terminalTheme: 'solarizedLight',
 | 
				
			||||||
            // ssh终端字体颜色
 | 
					            // ssh终端字体颜色
 | 
				
			||||||
            terminalForeground: '#C5C8C6',
 | 
					            terminalForeground: '#C5C8C6',
 | 
				
			||||||
            // ssh终端背景色
 | 
					            // ssh终端背景色
 | 
				
			||||||
@@ -192,5 +193,23 @@ export const useThemeConfig = defineStore('themeConfig', {
 | 
				
			|||||||
        setWatermarkNowTime() {
 | 
					        setWatermarkNowTime() {
 | 
				
			||||||
            this.themeConfig.watermarkText[1] = dateFormat2('yyyy-MM-dd HH:mm:ss', new Date());
 | 
					            this.themeConfig.watermarkText[1] = dateFormat2('yyyy-MM-dd HH:mm:ss', new Date());
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
 | 
					        // 切换暗黑模式
 | 
				
			||||||
 | 
					        switchDark(isDark: boolean) {
 | 
				
			||||||
 | 
					            this.themeConfig.isDark = isDark;
 | 
				
			||||||
 | 
					            // 切换编辑器主题
 | 
				
			||||||
 | 
					            if (isDark) {
 | 
				
			||||||
 | 
					                this.themeConfig.editorTheme = 'vs-dark';
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					                this.themeConfig.editorTheme = 'vs';
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            // 如果终端主题不是自定义主题,则切换主题
 | 
				
			||||||
 | 
					            if (this.themeConfig.terminalTheme != 'custom') {
 | 
				
			||||||
 | 
					                if (isDark) {
 | 
				
			||||||
 | 
					                    this.themeConfig.terminalTheme = 'dark';
 | 
				
			||||||
 | 
					                } else {
 | 
				
			||||||
 | 
					                    this.themeConfig.terminalTheme = 'solarizedLight';
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -35,7 +35,7 @@ body,
 | 
				
			|||||||
    width: 100%;
 | 
					    width: 100%;
 | 
				
			||||||
    height: 100%;
 | 
					    height: 100%;
 | 
				
			||||||
    font-family: 'Helvetica Neue', Helvetica, 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', '微软雅黑', Arial, sans-serif;
 | 
					    font-family: 'Helvetica Neue', Helvetica, 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', '微软雅黑', Arial, sans-serif;
 | 
				
			||||||
    font-weight: 450;
 | 
					    font-weight: 500;
 | 
				
			||||||
    -webkit-font-smoothing: antialiased;
 | 
					    -webkit-font-smoothing: antialiased;
 | 
				
			||||||
    -webkit-tap-highlight-color: transparent;
 | 
					    -webkit-tap-highlight-color: transparent;
 | 
				
			||||||
    background-color: var(--bg-main-color);
 | 
					    background-color: var(--bg-main-color);
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -7,335 +7,353 @@
 | 
				
			|||||||
------------------------------- */
 | 
					------------------------------- */
 | 
				
			||||||
// 菜单搜索
 | 
					// 菜单搜索
 | 
				
			||||||
.el-autocomplete-suggestion__wrap {
 | 
					.el-autocomplete-suggestion__wrap {
 | 
				
			||||||
	max-height: 280px !important;
 | 
					    max-height: 280px !important;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/* Form 表单
 | 
					/* Form 表单
 | 
				
			||||||
------------------------------- */
 | 
					------------------------------- */
 | 
				
			||||||
.el-form {
 | 
					.el-form {
 | 
				
			||||||
	// 用于修改弹窗时表单内容间隔太大问题,如系统设置的新增菜单弹窗里的表单内容
 | 
					
 | 
				
			||||||
	.el-form-item:last-of-type {
 | 
					    // 用于修改弹窗时表单内容间隔太大问题,如系统设置的新增菜单弹窗里的表单内容
 | 
				
			||||||
		margin-bottom: 0 !important;
 | 
					    .el-form-item:last-of-type {
 | 
				
			||||||
	}
 | 
					        margin-bottom: 0 !important;
 | 
				
			||||||
	// 修复行内表单最后一个 el-form-item 位置下移问题
 | 
					    }
 | 
				
			||||||
	&.el-form--inline {
 | 
					
 | 
				
			||||||
		.el-form-item--large.el-form-item:last-of-type {
 | 
					    // 修复行内表单最后一个 el-form-item 位置下移问题
 | 
				
			||||||
			margin-bottom: 22px !important;
 | 
					    &.el-form--inline {
 | 
				
			||||||
		}
 | 
					        .el-form-item--large.el-form-item:last-of-type {
 | 
				
			||||||
		.el-form-item--default.el-form-item:last-of-type,
 | 
					            margin-bottom: 22px !important;
 | 
				
			||||||
		.el-form-item--small.el-form-item:last-of-type {
 | 
					        }
 | 
				
			||||||
			margin-bottom: 18px !important;
 | 
					
 | 
				
			||||||
		}
 | 
					        .el-form-item--default.el-form-item:last-of-type,
 | 
				
			||||||
	}
 | 
					        .el-form-item--small.el-form-item:last-of-type {
 | 
				
			||||||
	// https://gitee.com/lyt-top/vue-next-admin/issues/I5K1PM
 | 
					            margin-bottom: 18px !important;
 | 
				
			||||||
	.el-form-item .el-form-item__label .el-icon {
 | 
					        }
 | 
				
			||||||
		margin-right: 0px;
 | 
					    }
 | 
				
			||||||
	}
 | 
					
 | 
				
			||||||
 | 
					    // https://gitee.com/lyt-top/vue-next-admin/issues/I5K1PM
 | 
				
			||||||
 | 
					    .el-form-item .el-form-item__label .el-icon {
 | 
				
			||||||
 | 
					        margin-right: 0px;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/* Alert 警告
 | 
					/* Alert 警告
 | 
				
			||||||
------------------------------- */
 | 
					------------------------------- */
 | 
				
			||||||
.el-alert {
 | 
					.el-alert {
 | 
				
			||||||
	border: 1px solid;
 | 
					    border: 1px solid;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.el-alert__title {
 | 
					.el-alert__title {
 | 
				
			||||||
	word-break: break-all;
 | 
					    word-break: break-all;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/* Message 消息提示
 | 
					/* Message 消息提示
 | 
				
			||||||
------------------------------- */
 | 
					------------------------------- */
 | 
				
			||||||
.el-message {
 | 
					.el-message {
 | 
				
			||||||
	min-width: unset !important;
 | 
					    min-width: unset !important;
 | 
				
			||||||
	padding: 15px !important;
 | 
					    padding: 15px !important;
 | 
				
			||||||
	box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.02);
 | 
					    box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.02);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/* NavMenu 导航菜单
 | 
					/* NavMenu 导航菜单
 | 
				
			||||||
------------------------------- */
 | 
					------------------------------- */
 | 
				
			||||||
// 鼠标 hover 时颜色
 | 
					// 鼠标 hover 时颜色
 | 
				
			||||||
.el-menu-hover-bg-color {
 | 
					.el-menu-hover-bg-color {
 | 
				
			||||||
	background-color: var(--bg-menuBarActiveColor) !important;
 | 
					    background-color: var(--bg-menuBarActiveColor) !important;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// 默认样式修改
 | 
					// 默认样式修改
 | 
				
			||||||
.el-menu {
 | 
					.el-menu {
 | 
				
			||||||
	border-right: none !important;
 | 
					    border-right: none !important;
 | 
				
			||||||
	width: 220px;
 | 
					    width: 220px;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.el-menu-item {
 | 
					.el-menu-item {
 | 
				
			||||||
	height: 56px !important;
 | 
					    height: 56px !important;
 | 
				
			||||||
	line-height: 56px !important;
 | 
					    line-height: 56px !important;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.el-menu-item,
 | 
					.el-menu-item,
 | 
				
			||||||
.el-sub-menu__title {
 | 
					.el-sub-menu__title {
 | 
				
			||||||
	color: var(--bg-menuBarColor);
 | 
					    color: var(--bg-menuBarColor);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// 修复点击左侧菜单折叠再展开时,宽度不跟随问题
 | 
					// 修复点击左侧菜单折叠再展开时,宽度不跟随问题
 | 
				
			||||||
.el-menu--collapse {
 | 
					.el-menu--collapse {
 | 
				
			||||||
	width: 64px !important;
 | 
					    width: 64px !important;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// 外部链接时
 | 
					// 外部链接时
 | 
				
			||||||
.el-menu-item a,
 | 
					.el-menu-item a,
 | 
				
			||||||
.el-menu-item a:hover,
 | 
					.el-menu-item a:hover,
 | 
				
			||||||
.el-menu-item i,
 | 
					.el-menu-item i,
 | 
				
			||||||
.el-sub-menu__title i {
 | 
					.el-sub-menu__title i {
 | 
				
			||||||
	color: inherit;
 | 
					    color: inherit;
 | 
				
			||||||
	text-decoration: none;
 | 
					    text-decoration: none;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// 第三方图标字体间距/大小设置
 | 
					// 第三方图标字体间距/大小设置
 | 
				
			||||||
.el-menu-item .iconfont,
 | 
					.el-menu-item .iconfont,
 | 
				
			||||||
.el-sub-menu .iconfont,
 | 
					.el-sub-menu .iconfont,
 | 
				
			||||||
.el-menu-item .fa,
 | 
					.el-menu-item .fa,
 | 
				
			||||||
.el-sub-menu .fa {
 | 
					.el-sub-menu .fa {
 | 
				
			||||||
	@include generalIcon;
 | 
					    @include generalIcon;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// 水平菜单、横向菜单高亮 背景色,鼠标 hover 时,有子级菜单的背景色
 | 
					// 水平菜单、横向菜单高亮 背景色,鼠标 hover 时,有子级菜单的背景色
 | 
				
			||||||
.el-menu-item.is-active,
 | 
					.el-menu-item.is-active,
 | 
				
			||||||
.el-sub-menu.is-active .el-sub-menu__title,
 | 
					.el-sub-menu.is-active .el-sub-menu__title,
 | 
				
			||||||
.el-sub-menu:not(.is-opened):hover .el-sub-menu__title {
 | 
					.el-sub-menu:not(.is-opened):hover .el-sub-menu__title {
 | 
				
			||||||
	@extend .el-menu-hover-bg-color;
 | 
					    @extend .el-menu-hover-bg-color;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.el-menu-item:hover {
 | 
					.el-menu-item:hover {
 | 
				
			||||||
	@extend .el-menu-hover-bg-color;
 | 
					    @extend .el-menu-hover-bg-color;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.el-sub-menu.is-active.is-opened .el-sub-menu__title {
 | 
					.el-sub-menu.is-active.is-opened .el-sub-menu__title {
 | 
				
			||||||
	background-color: unset !important;
 | 
					    background-color: unset !important;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// 子级菜单背景颜色
 | 
					// 子级菜单背景颜色
 | 
				
			||||||
// .el-menu--inline {
 | 
					// .el-menu--inline {
 | 
				
			||||||
// 	background: var(--next-bg-menuBar-light-1);
 | 
					// 	background: var(--next-bg-menuBar-light-1);
 | 
				
			||||||
// }
 | 
					// }
 | 
				
			||||||
// 水平菜单、横向菜单折叠 a 标签
 | 
					// 水平菜单、横向菜单折叠 a 标签
 | 
				
			||||||
.el-popper.is-dark a {
 | 
					.el-popper.is-dark a {
 | 
				
			||||||
	color: var(--el-color-white) !important;
 | 
					    color: var(--el-color-white) !important;
 | 
				
			||||||
	text-decoration: none;
 | 
					    text-decoration: none;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// 水平菜单、横向菜单折叠背景色
 | 
					// 水平菜单、横向菜单折叠背景色
 | 
				
			||||||
.el-popper.is-pure.is-light {
 | 
					.el-popper.is-pure.is-light {
 | 
				
			||||||
	// 水平菜单
 | 
					
 | 
				
			||||||
	.el-menu--vertical {
 | 
					    // 水平菜单
 | 
				
			||||||
		background: var(--bg-menuBar);
 | 
					    .el-menu--vertical {
 | 
				
			||||||
		.el-sub-menu.is-active .el-sub-menu__title {
 | 
					        background: var(--bg-menuBar);
 | 
				
			||||||
			color: var(--el-menu-active-color);
 | 
					
 | 
				
			||||||
		}
 | 
					        .el-sub-menu.is-active .el-sub-menu__title {
 | 
				
			||||||
		.el-popper.is-pure.is-light {
 | 
					            color: var(--el-menu-active-color);
 | 
				
			||||||
			.el-menu--vertical {
 | 
					        }
 | 
				
			||||||
				.el-sub-menu .el-sub-menu__title {
 | 
					
 | 
				
			||||||
					background-color: unset !important;
 | 
					        .el-popper.is-pure.is-light {
 | 
				
			||||||
					color: var(--bg-menuBarColor);
 | 
					            .el-menu--vertical {
 | 
				
			||||||
				}
 | 
					                .el-sub-menu .el-sub-menu__title {
 | 
				
			||||||
				.el-sub-menu.is-active .el-sub-menu__title {
 | 
					                    background-color: unset !important;
 | 
				
			||||||
					color: var(--el-menu-active-color);
 | 
					                    color: var(--bg-menuBarColor);
 | 
				
			||||||
				}
 | 
					                }
 | 
				
			||||||
			}
 | 
					
 | 
				
			||||||
		}
 | 
					                .el-sub-menu.is-active .el-sub-menu__title {
 | 
				
			||||||
	}
 | 
					                    color: var(--el-menu-active-color);
 | 
				
			||||||
	// 横向菜单
 | 
					                }
 | 
				
			||||||
	.el-menu--horizontal {
 | 
					            }
 | 
				
			||||||
		background: var(--bg-topBar);
 | 
					        }
 | 
				
			||||||
		.el-menu-item,
 | 
					    }
 | 
				
			||||||
		.el-sub-menu {
 | 
					
 | 
				
			||||||
			height: 48px !important;
 | 
					    // 横向菜单
 | 
				
			||||||
			line-height: 48px !important;
 | 
					    .el-menu--horizontal {
 | 
				
			||||||
			color: var(--bg-topBarColor);
 | 
					        background: var(--bg-topBar);
 | 
				
			||||||
			.el-sub-menu__title {
 | 
					
 | 
				
			||||||
				height: 48px !important;
 | 
					        .el-menu-item,
 | 
				
			||||||
				line-height: 48px !important;
 | 
					        .el-sub-menu {
 | 
				
			||||||
				color: var(--bg-topBarColor);
 | 
					            height: 48px !important;
 | 
				
			||||||
			}
 | 
					            line-height: 48px !important;
 | 
				
			||||||
			.el-popper.is-pure.is-light {
 | 
					            color: var(--bg-topBarColor);
 | 
				
			||||||
				.el-menu--horizontal {
 | 
					
 | 
				
			||||||
					.el-sub-menu .el-sub-menu__title {
 | 
					            .el-sub-menu__title {
 | 
				
			||||||
						background-color: unset !important;
 | 
					                height: 48px !important;
 | 
				
			||||||
						color: var(--bg-topBarColor);
 | 
					                line-height: 48px !important;
 | 
				
			||||||
					}
 | 
					                color: var(--bg-topBarColor);
 | 
				
			||||||
					.el-sub-menu.is-active .el-sub-menu__title {
 | 
					            }
 | 
				
			||||||
						color: var(--el-menu-active-color);
 | 
					
 | 
				
			||||||
					}
 | 
					            .el-popper.is-pure.is-light {
 | 
				
			||||||
				}
 | 
					                .el-menu--horizontal {
 | 
				
			||||||
			}
 | 
					                    .el-sub-menu .el-sub-menu__title {
 | 
				
			||||||
		}
 | 
					                        background-color: unset !important;
 | 
				
			||||||
		.el-menu-item.is-active,
 | 
					                        color: var(--bg-topBarColor);
 | 
				
			||||||
		.el-sub-menu.is-active .el-sub-menu__title {
 | 
					                    }
 | 
				
			||||||
			color: var(--el-menu-active-color);
 | 
					
 | 
				
			||||||
		}
 | 
					                    .el-sub-menu.is-active .el-sub-menu__title {
 | 
				
			||||||
	}
 | 
					                        color: var(--el-menu-active-color);
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        .el-menu-item.is-active,
 | 
				
			||||||
 | 
					        .el-sub-menu.is-active .el-sub-menu__title {
 | 
				
			||||||
 | 
					            color: var(--el-menu-active-color);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// 横向菜单(经典、横向)布局
 | 
					// 横向菜单(经典、横向)布局
 | 
				
			||||||
.el-menu.el-menu--horizontal {
 | 
					.el-menu.el-menu--horizontal {
 | 
				
			||||||
	border-bottom: none !important;
 | 
					    border-bottom: none !important;
 | 
				
			||||||
	width: 100% !important;
 | 
					    width: 100% !important;
 | 
				
			||||||
	.el-menu-item,
 | 
					
 | 
				
			||||||
	.el-sub-menu__title {
 | 
					    .el-menu-item,
 | 
				
			||||||
		height: 48px !important;
 | 
					    .el-sub-menu__title {
 | 
				
			||||||
		color: var(--bg-topBarColor);
 | 
					        height: 48px !important;
 | 
				
			||||||
	}
 | 
					        color: var(--bg-topBarColor);
 | 
				
			||||||
	.el-menu-item:not(.is-active):hover,
 | 
					    }
 | 
				
			||||||
	.el-sub-menu:not(.is-active):hover .el-sub-menu__title {
 | 
					
 | 
				
			||||||
		color: var(--bg-topBarColor);
 | 
					    .el-menu-item:not(.is-active):hover,
 | 
				
			||||||
	}
 | 
					    .el-sub-menu:not(.is-active):hover .el-sub-menu__title {
 | 
				
			||||||
 | 
					        color: var(--bg-topBarColor);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// 菜单收起时,图标不居中问题
 | 
					// 菜单收起时,图标不居中问题
 | 
				
			||||||
.el-menu--collapse {
 | 
					.el-menu--collapse {
 | 
				
			||||||
	.el-menu-item .iconfont,
 | 
					
 | 
				
			||||||
	.el-sub-menu .iconfont,
 | 
					    .el-menu-item .iconfont,
 | 
				
			||||||
	.el-menu-item .fa,
 | 
					    .el-sub-menu .iconfont,
 | 
				
			||||||
	.el-sub-menu .fa {
 | 
					    .el-menu-item .fa,
 | 
				
			||||||
		margin-right: 0 !important;
 | 
					    .el-sub-menu .fa {
 | 
				
			||||||
	}
 | 
					        margin-right: 0 !important;
 | 
				
			||||||
	.el-sub-menu__title {
 | 
					    }
 | 
				
			||||||
		padding-right: 0 !important;
 | 
					
 | 
				
			||||||
	}
 | 
					    .el-sub-menu__title {
 | 
				
			||||||
 | 
					        padding-right: 0 !important;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/* Tabs 标签页
 | 
					/* Tabs 标签页
 | 
				
			||||||
------------------------------- */
 | 
					------------------------------- */
 | 
				
			||||||
.el-tabs__nav-wrap::after {
 | 
					.el-tabs__nav-wrap::after {
 | 
				
			||||||
	height: 1px !important;
 | 
					    height: 1px !important;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/* Dropdown 下拉菜单
 | 
					/* Dropdown 下拉菜单
 | 
				
			||||||
------------------------------- */
 | 
					------------------------------- */
 | 
				
			||||||
.el-dropdown-menu {
 | 
					.el-dropdown-menu {
 | 
				
			||||||
	list-style: none !important; /*修复 Dropdown 下拉菜单样式问题 2022.03.04*/
 | 
					    list-style: none !important;
 | 
				
			||||||
}
 | 
					    /*修复 Dropdown 下拉菜单样式问题 2022.03.04*/
 | 
				
			||||||
.el-dropdown-menu .el-dropdown-menu__item {
 | 
					 | 
				
			||||||
	white-space: nowrap;
 | 
					 | 
				
			||||||
	&:not(.is-disabled):hover {
 | 
					 | 
				
			||||||
		background-color: var(--el-dropdown-menuItem-hover-fill);
 | 
					 | 
				
			||||||
		color: var(--el-dropdown-menuItem-hover-color);
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/* Steps 步骤条
 | 
					.el-dropdown-menu .el-dropdown-menu__item {
 | 
				
			||||||
------------------------------- */
 | 
					    white-space: nowrap;
 | 
				
			||||||
.el-step__icon-inner {
 | 
					
 | 
				
			||||||
	font-size: 30px !important;
 | 
					    &:not(.is-disabled):hover {
 | 
				
			||||||
	font-weight: 400 !important;
 | 
					        background-color: var(--el-dropdown-menuItem-hover-fill);
 | 
				
			||||||
}
 | 
					        color: var(--el-dropdown-menuItem-hover-color);
 | 
				
			||||||
.el-step__title {
 | 
					    }
 | 
				
			||||||
	font-size: 14px;
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/* Dialog 对话框
 | 
					/* Dialog 对话框
 | 
				
			||||||
------------------------------- */
 | 
					------------------------------- */
 | 
				
			||||||
.el-overlay {
 | 
					.el-overlay {
 | 
				
			||||||
	overflow: hidden;
 | 
					    overflow: hidden;
 | 
				
			||||||
	.el-overlay-dialog {
 | 
					
 | 
				
			||||||
		display: flex;
 | 
					    .el-overlay-dialog {
 | 
				
			||||||
		align-items: center;
 | 
					        display: flex;
 | 
				
			||||||
		justify-content: center;
 | 
					        align-items: center;
 | 
				
			||||||
		position: unset !important;
 | 
					        justify-content: center;
 | 
				
			||||||
		width: 100%;
 | 
					        position: unset !important;
 | 
				
			||||||
		height: 100%;
 | 
					        width: 100%;
 | 
				
			||||||
		.el-dialog {
 | 
					        height: 100%;
 | 
				
			||||||
			margin: 0 auto !important;
 | 
					
 | 
				
			||||||
			position: absolute;
 | 
					        .el-dialog {
 | 
				
			||||||
			.el-dialog__body {
 | 
					            margin: 0 auto !important;
 | 
				
			||||||
				padding: 20px !important;
 | 
					            position: absolute;
 | 
				
			||||||
			}
 | 
					
 | 
				
			||||||
		}
 | 
					            .el-dialog__body {
 | 
				
			||||||
	}
 | 
					                padding: 20px !important;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.el-dialog__body {
 | 
					.el-dialog__body {
 | 
				
			||||||
	max-height: calc(90vh - 111px) !important;
 | 
					    max-height: calc(90vh - 111px) !important;
 | 
				
			||||||
	overflow-y: auto;
 | 
					    overflow-y: auto;
 | 
				
			||||||
	overflow-x: hidden;
 | 
					    overflow-x: hidden;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/* Card 卡片
 | 
					/* Card 卡片
 | 
				
			||||||
------------------------------- */
 | 
					------------------------------- */
 | 
				
			||||||
.el-card__header {
 | 
					.el-card__header {
 | 
				
			||||||
	padding: 15px 20px;
 | 
					    padding: 15px 20px;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/* Table 表格 element plus 2.2.0 版本
 | 
					/* Table 表格 element plus 2.2.0 版本
 | 
				
			||||||
------------------------------- */
 | 
					------------------------------- */
 | 
				
			||||||
.el-table {
 | 
					.el-table {
 | 
				
			||||||
	.el-button.is-text {
 | 
					    .el-button.is-text {
 | 
				
			||||||
		padding: 0;
 | 
					        padding: 0;
 | 
				
			||||||
	}
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/* scrollbar
 | 
					/* scrollbar
 | 
				
			||||||
------------------------------- */
 | 
					------------------------------- */
 | 
				
			||||||
.el-scrollbar__bar {
 | 
					.el-scrollbar__bar {
 | 
				
			||||||
	z-index: 4;
 | 
					    z-index: 4;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/*防止页面切换时,滚动条高度不变的问题(滚动条高度非滚动条滚动高度)*/
 | 
					/*防止页面切换时,滚动条高度不变的问题(滚动条高度非滚动条滚动高度)*/
 | 
				
			||||||
.el-scrollbar__wrap {
 | 
					.el-scrollbar__wrap {
 | 
				
			||||||
	max-height: 100%;
 | 
					    max-height: 100%;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.el-select-dropdown .el-scrollbar__wrap {
 | 
					.el-select-dropdown .el-scrollbar__wrap {
 | 
				
			||||||
	overflow-x: scroll !important;
 | 
					    overflow-x: scroll !important;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/*修复Select 选择器高度问题*/
 | 
					/*修复Select 选择器高度问题*/
 | 
				
			||||||
.el-select-dropdown__wrap {
 | 
					.el-select-dropdown__wrap {
 | 
				
			||||||
	max-height: 274px !important;
 | 
					    max-height: 274px !important;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/*修复Cascader 级联选择器高度问题*/
 | 
					/*修复Cascader 级联选择器高度问题*/
 | 
				
			||||||
.el-cascader-menu__wrap.el-scrollbar__wrap {
 | 
					.el-cascader-menu__wrap.el-scrollbar__wrap {
 | 
				
			||||||
	height: 204px !important;
 | 
					    height: 204px !important;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/*用于界面高度自适应(main.vue),区分 scrollbar__view,防止其它使用 scrollbar 的地方出现滚动条消失*/
 | 
					/*用于界面高度自适应(main.vue),区分 scrollbar__view,防止其它使用 scrollbar 的地方出现滚动条消失*/
 | 
				
			||||||
.layout-container-view .el-scrollbar__view {
 | 
					.layout-container-view .el-scrollbar__view {
 | 
				
			||||||
	height: 100%;
 | 
					    height: 100%;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/*防止分栏布局二级菜单很多时,滚动条消失问题*/
 | 
					/*防止分栏布局二级菜单很多时,滚动条消失问题*/
 | 
				
			||||||
.layout-columns-warp .layout-aside .el-scrollbar__view {
 | 
					.layout-columns-warp .layout-aside .el-scrollbar__view {
 | 
				
			||||||
	height: unset !important;
 | 
					    height: unset !important;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/* Pagination 分页
 | 
					/* Pagination 分页
 | 
				
			||||||
------------------------------- */
 | 
					------------------------------- */
 | 
				
			||||||
.el-pagination__editor {
 | 
					.el-pagination__editor {
 | 
				
			||||||
	margin-right: 8px;
 | 
					    margin-right: 8px;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/*深色模式时分页高亮问题*/
 | 
					/*深色模式时分页高亮问题*/
 | 
				
			||||||
.el-pagination.is-background .btn-next.is-active,
 | 
					.el-pagination.is-background .btn-next.is-active,
 | 
				
			||||||
.el-pagination.is-background .btn-prev.is-active,
 | 
					.el-pagination.is-background .btn-prev.is-active,
 | 
				
			||||||
.el-pagination.is-background .el-pager li.is-active {
 | 
					.el-pagination.is-background .el-pager li.is-active {
 | 
				
			||||||
	background-color: var(--el-color-primary) !important;
 | 
					    background-color: var(--el-color-primary) !important;
 | 
				
			||||||
	color: var(--el-color-white) !important;
 | 
					    color: var(--el-color-white) !important;
 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/* Drawer 抽屉
 | 
					 | 
				
			||||||
------------------------------- */
 | 
					 | 
				
			||||||
.el-drawer {
 | 
					 | 
				
			||||||
	--el-drawer-padding-primary: unset !important;
 | 
					 | 
				
			||||||
	.el-drawer__header {
 | 
					 | 
				
			||||||
		padding: 0 15px !important;
 | 
					 | 
				
			||||||
		height: 50px;
 | 
					 | 
				
			||||||
		display: flex;
 | 
					 | 
				
			||||||
		align-items: center;
 | 
					 | 
				
			||||||
		margin-bottom: 0 !important;
 | 
					 | 
				
			||||||
		border-bottom: 1px solid var(--el-border-color);
 | 
					 | 
				
			||||||
		color: var(--el-text-color-primary);
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	.el-drawer__body {
 | 
					 | 
				
			||||||
		width: 100%;
 | 
					 | 
				
			||||||
		height: 100%;
 | 
					 | 
				
			||||||
		overflow: auto;
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/* Breadcrumb 面包屑
 | 
					/* Breadcrumb 面包屑
 | 
				
			||||||
------------------------------- */
 | 
					------------------------------- */
 | 
				
			||||||
.el-breadcrumb__inner a:hover,
 | 
					.el-breadcrumb__inner a:hover,
 | 
				
			||||||
.el-breadcrumb__inner.is-link:hover {
 | 
					.el-breadcrumb__inner.is-link:hover {
 | 
				
			||||||
	color: var(--el-color-primary);
 | 
					    color: var(--el-color-primary);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.el-breadcrumb__inner a,
 | 
					.el-breadcrumb__inner a,
 | 
				
			||||||
.el-breadcrumb__inner.is-link {
 | 
					.el-breadcrumb__inner.is-link {
 | 
				
			||||||
	color: var(--bg-topBarColor);
 | 
					    color: var(--bg-topBarColor);
 | 
				
			||||||
	font-weight: normal;
 | 
					    font-weight: normal;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// el-tooltip使用自定义主题时的样式
 | 
					// el-tooltip使用自定义主题时的样式
 | 
				
			||||||
.el-popper.is-customized {
 | 
					.el-popper.is-customized {
 | 
				
			||||||
    /* Set padding to ensure the height is 32px */
 | 
					    /* Set padding to ensure the height is 32px */
 | 
				
			||||||
  //   padding: 6px 12px;
 | 
					    //   padding: 6px 12px;
 | 
				
			||||||
    background: linear-gradient(90deg, rgb(159, 229, 151), rgb(204, 229, 129));
 | 
					    background: linear-gradient(90deg, rgb(159, 229, 151), rgb(204, 229, 129));
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.el-popper.is-customized .el-popper__arrow::before {
 | 
					.el-popper.is-customized .el-popper__arrow::before {
 | 
				
			||||||
    background: linear-gradient(45deg, #b2e68d, #bce689);
 | 
					    background: linear-gradient(45deg, #b2e68d, #bce689);
 | 
				
			||||||
    right: 0;
 | 
					    right: 0;
 | 
				
			||||||
@@ -343,7 +361,9 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.el-dialog {
 | 
					.el-dialog {
 | 
				
			||||||
    border-radius: 6px; /* 设置圆角 */
 | 
					    border-radius: 6px;
 | 
				
			||||||
    box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1); /* 添加轻微阴影效果 */
 | 
					    /* 设置圆角 */
 | 
				
			||||||
 | 
					    box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
 | 
				
			||||||
 | 
					    /* 添加轻微阴影效果 */
 | 
				
			||||||
    border: 1px solid var(--el-border-color-lighter);
 | 
					    border: 1px solid var(--el-border-color-lighter);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
							
								
								
									
										1
									
								
								mayfly_go_web/src/types/pinia.d.ts
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								mayfly_go_web/src/types/pinia.d.ts
									
									
									
									
										vendored
									
									
								
							@@ -52,6 +52,7 @@ declare interface ThemeConfigState {
 | 
				
			|||||||
        logoIcon: string;
 | 
					        logoIcon: string;
 | 
				
			||||||
        globalI18n: string;
 | 
					        globalI18n: string;
 | 
				
			||||||
        globalComponentSize: string;
 | 
					        globalComponentSize: string;
 | 
				
			||||||
 | 
					        terminalTheme: string;
 | 
				
			||||||
        terminalForeground: string;
 | 
					        terminalForeground: string;
 | 
				
			||||||
        terminalBackground: string;
 | 
					        terminalBackground: string;
 | 
				
			||||||
        terminalCursor: string;
 | 
					        terminalCursor: string;
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										2
									
								
								mayfly_go_web/src/types/shim.d.ts
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								mayfly_go_web/src/types/shim.d.ts
									
									
									
									
										vendored
									
									
								
							@@ -1,6 +1,6 @@
 | 
				
			|||||||
// 申明外部 npm 插件模块
 | 
					// 申明外部 npm 插件模块
 | 
				
			||||||
declare module 'sql-formatter';
 | 
					 | 
				
			||||||
declare module 'jsoneditor';
 | 
					declare module 'jsoneditor';
 | 
				
			||||||
declare module 'asciinema-player';
 | 
					declare module 'asciinema-player';
 | 
				
			||||||
declare module 'vue-grid-layout';
 | 
					declare module 'vue-grid-layout';
 | 
				
			||||||
declare module 'splitpanes';
 | 
					declare module 'splitpanes';
 | 
				
			||||||
 | 
					declare module 'uuid';
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -98,4 +98,3 @@ export default {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
</style>
 | 
					</style>
 | 
				
			||||||
@/router/staticRouter
 | 
					 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										211
									
								
								mayfly_go_web/src/views/flow/ProcdefEdit.vue
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										211
									
								
								mayfly_go_web/src/views/flow/ProcdefEdit.vue
									
									
									
									
									
										Executable file
									
								
							@@ -0,0 +1,211 @@
 | 
				
			|||||||
 | 
					<template>
 | 
				
			||||||
 | 
					    <div>
 | 
				
			||||||
 | 
					        <el-drawer @open="initSort" :title="title" v-model="visible" :before-close="cancel" :destroy-on-close="true" :close-on-click-modal="false">
 | 
				
			||||||
 | 
					            <template #header>
 | 
				
			||||||
 | 
					                <DrawerHeader :header="title" :back="cancel" />
 | 
				
			||||||
 | 
					            </template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            <el-form :model="form" ref="formRef" :rules="rules" label-width="auto">
 | 
				
			||||||
 | 
					                <el-form-item prop="name" label="名称">
 | 
				
			||||||
 | 
					                    <el-input v-model.trim="form.name" placeholder="请输入流程名称" auto-complete="off" clearable></el-input>
 | 
				
			||||||
 | 
					                </el-form-item>
 | 
				
			||||||
 | 
					                <el-form-item prop="defKey" label="key">
 | 
				
			||||||
 | 
					                    <el-input :disabled="form.id" v-model.trim="form.defKey" placeholder="请输入流程key" auto-complete="off" clearable></el-input>
 | 
				
			||||||
 | 
					                </el-form-item>
 | 
				
			||||||
 | 
					                <el-form-item prop="status" label="状态">
 | 
				
			||||||
 | 
					                    <el-select v-model="form.status" placeholder="请选择状态">
 | 
				
			||||||
 | 
					                        <el-option v-for="item in ProcdefStatus" :key="item.value" :label="item.label" :value="item.value"> </el-option>
 | 
				
			||||||
 | 
					                    </el-select>
 | 
				
			||||||
 | 
					                </el-form-item>
 | 
				
			||||||
 | 
					                <el-form-item prop="remark" label="备注">
 | 
				
			||||||
 | 
					                    <el-input v-model.trim="form.remark" placeholder="备注" auto-complete="off" clearable></el-input>
 | 
				
			||||||
 | 
					                </el-form-item>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                <el-divider content-position="left">审批节点</el-divider>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                <el-table ref="taskTableRef" :data="tasks" row-key="taskKey" stripe style="width: 100%">
 | 
				
			||||||
 | 
					                    <el-table-column prop="name" label="名称" min-width="100px">
 | 
				
			||||||
 | 
					                        <template #header>
 | 
				
			||||||
 | 
					                            <el-button class="ml0" type="primary" circle size="small" icon="Plus" @click="addTask()"> </el-button>
 | 
				
			||||||
 | 
					                            <span class="ml10">节点名称</span>
 | 
				
			||||||
 | 
					                            <el-tooltip content="点击指定节点可进行拖拽排序" placement="top">
 | 
				
			||||||
 | 
					                                <el-icon class="ml5">
 | 
				
			||||||
 | 
					                                    <question-filled />
 | 
				
			||||||
 | 
					                                </el-icon>
 | 
				
			||||||
 | 
					                            </el-tooltip>
 | 
				
			||||||
 | 
					                        </template>
 | 
				
			||||||
 | 
					                        <template #default="scope">
 | 
				
			||||||
 | 
					                            <el-input v-model="scope.row.name"> </el-input>
 | 
				
			||||||
 | 
					                        </template>
 | 
				
			||||||
 | 
					                    </el-table-column>
 | 
				
			||||||
 | 
					                    <el-table-column prop="userId" label="审核人员" min-width="150px" show-overflow-tooltip>
 | 
				
			||||||
 | 
					                        <template #default="scope">
 | 
				
			||||||
 | 
					                            <AccountSelectFormItem v-model="scope.row.userId" label="" />
 | 
				
			||||||
 | 
					                        </template>
 | 
				
			||||||
 | 
					                    </el-table-column>
 | 
				
			||||||
 | 
					                    <el-table-column label="操作" width="60px">
 | 
				
			||||||
 | 
					                        <template #default="scope">
 | 
				
			||||||
 | 
					                            <el-link @click="deleteTask(scope.$index)" class="ml5" type="danger" icon="delete" plain></el-link>
 | 
				
			||||||
 | 
					                        </template>
 | 
				
			||||||
 | 
					                    </el-table-column>
 | 
				
			||||||
 | 
					                </el-table>
 | 
				
			||||||
 | 
					            </el-form>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            <template #footer>
 | 
				
			||||||
 | 
					                <div>
 | 
				
			||||||
 | 
					                    <el-button @click="cancel()">取 消</el-button>
 | 
				
			||||||
 | 
					                    <el-button type="primary" :loading="saveBtnLoading" @click="btnOk">确 定</el-button>
 | 
				
			||||||
 | 
					                </div>
 | 
				
			||||||
 | 
					            </template>
 | 
				
			||||||
 | 
					        </el-drawer>
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<script lang="ts" setup>
 | 
				
			||||||
 | 
					import { toRefs, reactive, watch, ref, nextTick } from 'vue';
 | 
				
			||||||
 | 
					import { procdefApi } from './api';
 | 
				
			||||||
 | 
					import { ElMessage } from 'element-plus';
 | 
				
			||||||
 | 
					import DrawerHeader from '@/components/drawer-header/DrawerHeader.vue';
 | 
				
			||||||
 | 
					import AccountSelectFormItem from '@/views/system/account/components/AccountSelectFormItem.vue';
 | 
				
			||||||
 | 
					import Sortable from 'sortablejs';
 | 
				
			||||||
 | 
					import { randomUuid } from '../../common/utils/string';
 | 
				
			||||||
 | 
					import { ProcdefStatus } from './enums';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const props = defineProps({
 | 
				
			||||||
 | 
					    data: {
 | 
				
			||||||
 | 
					        type: [Boolean, Object],
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    title: {
 | 
				
			||||||
 | 
					        type: String,
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const visible = defineModel<boolean>('visible', { default: false });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					//定义事件
 | 
				
			||||||
 | 
					const emit = defineEmits(['cancel', 'val-change']);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const formRef: any = ref(null);
 | 
				
			||||||
 | 
					const taskTableRef: any = ref(null);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const rules = {
 | 
				
			||||||
 | 
					    name: [
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            required: true,
 | 
				
			||||||
 | 
					            message: '请输入流程名称',
 | 
				
			||||||
 | 
					            trigger: ['change', 'blur'],
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					    ],
 | 
				
			||||||
 | 
					    defKey: [
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            required: true,
 | 
				
			||||||
 | 
					            message: '请输入流程key',
 | 
				
			||||||
 | 
					            trigger: ['change', 'blur'],
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					    ],
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const state = reactive({
 | 
				
			||||||
 | 
					    tasks: [] as any,
 | 
				
			||||||
 | 
					    form: {
 | 
				
			||||||
 | 
					        id: null,
 | 
				
			||||||
 | 
					        name: null,
 | 
				
			||||||
 | 
					        defKey: null,
 | 
				
			||||||
 | 
					        status: null,
 | 
				
			||||||
 | 
					        remark: null,
 | 
				
			||||||
 | 
					        // 流程的审批节点任务
 | 
				
			||||||
 | 
					        tasks: '',
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    sortable: '' as any,
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const { form, tasks } = toRefs(state);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const { isFetching: saveBtnLoading, execute: saveFlowDefExec } = procdefApi.save.useApi(form);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					watch(props, (newValue: any) => {
 | 
				
			||||||
 | 
					    if (newValue.data) {
 | 
				
			||||||
 | 
					        state.form = { ...newValue.data };
 | 
				
			||||||
 | 
					        const tasks = JSON.parse(state.form.tasks);
 | 
				
			||||||
 | 
					        tasks.forEach((t: any) => {
 | 
				
			||||||
 | 
					            t.userId = Number.parseInt(t.userId);
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					        state.tasks = tasks;
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					        state.form = { status: ProcdefStatus.Enable.value } as any;
 | 
				
			||||||
 | 
					        state.tasks = [];
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const initSort = () => {
 | 
				
			||||||
 | 
					    nextTick(() => {
 | 
				
			||||||
 | 
					        const table = taskTableRef.value.$el.querySelector('table > tbody') as any;
 | 
				
			||||||
 | 
					        state.sortable = Sortable.create(table, {
 | 
				
			||||||
 | 
					            animation: 200,
 | 
				
			||||||
 | 
					            //拖拽结束事件
 | 
				
			||||||
 | 
					            onEnd: (evt) => {
 | 
				
			||||||
 | 
					                const curRow = state.tasks.splice(evt.oldIndex, 1)[0];
 | 
				
			||||||
 | 
					                state.tasks.splice(evt.newIndex, 0, curRow);
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const addTask = () => {
 | 
				
			||||||
 | 
					    state.tasks.push({ taskKey: randomUuid() });
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const deleteTask = (idx: any) => {
 | 
				
			||||||
 | 
					    state.tasks.splice(idx, 1);
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const btnOk = async () => {
 | 
				
			||||||
 | 
					    formRef.value.validate(async (valid: boolean) => {
 | 
				
			||||||
 | 
					        if (!valid) {
 | 
				
			||||||
 | 
					            ElMessage.error('表单填写有误');
 | 
				
			||||||
 | 
					            return false;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        const checkRes = checkTasks();
 | 
				
			||||||
 | 
					        if (checkRes.err) {
 | 
				
			||||||
 | 
					            ElMessage.error(checkRes.err);
 | 
				
			||||||
 | 
					            return false;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        state.form.tasks = JSON.stringify(checkRes.tasks);
 | 
				
			||||||
 | 
					        await saveFlowDefExec();
 | 
				
			||||||
 | 
					        ElMessage.success('操作成功');
 | 
				
			||||||
 | 
					        emit('val-change', state.form);
 | 
				
			||||||
 | 
					        //重置表单域
 | 
				
			||||||
 | 
					        formRef.value.resetFields();
 | 
				
			||||||
 | 
					        state.form = {} as any;
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const checkTasks = () => {
 | 
				
			||||||
 | 
					    if (state.tasks?.length == 0) {
 | 
				
			||||||
 | 
					        return { err: '请完善审批节点任务' };
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const tasks = [];
 | 
				
			||||||
 | 
					    for (let i = 0; i < state.tasks.length; i++) {
 | 
				
			||||||
 | 
					        const task = { ...state.tasks[i] };
 | 
				
			||||||
 | 
					        if (!task.name || !task.userId) {
 | 
				
			||||||
 | 
					            return { err: `请完善第${i + 1}个审批节点任务信息` };
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        // 转为字符串(方便后续万一需要调整啥的)
 | 
				
			||||||
 | 
					        task.userId = `${task.userId}`;
 | 
				
			||||||
 | 
					        if (!task.taskKey) {
 | 
				
			||||||
 | 
					            task.taskKey = randomUuid();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        tasks.push(task);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return { tasks: tasks };
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const cancel = () => {
 | 
				
			||||||
 | 
					    visible.value = false;
 | 
				
			||||||
 | 
					    emit('cancel');
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					</script>
 | 
				
			||||||
 | 
					<style lang="scss"></style>
 | 
				
			||||||
							
								
								
									
										141
									
								
								mayfly_go_web/src/views/flow/ProcdefList.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										141
									
								
								mayfly_go_web/src/views/flow/ProcdefList.vue
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,141 @@
 | 
				
			|||||||
 | 
					<template>
 | 
				
			||||||
 | 
					    <div>
 | 
				
			||||||
 | 
					        <page-table
 | 
				
			||||||
 | 
					            ref="pageTableRef"
 | 
				
			||||||
 | 
					            :page-api="procdefApi.list"
 | 
				
			||||||
 | 
					            :search-items="searchItems"
 | 
				
			||||||
 | 
					            v-model:query-form="query"
 | 
				
			||||||
 | 
					            :show-selection="true"
 | 
				
			||||||
 | 
					            v-model:selection-data="selectionData"
 | 
				
			||||||
 | 
					            :columns="columns"
 | 
				
			||||||
 | 
					        >
 | 
				
			||||||
 | 
					            <template #tableHeader>
 | 
				
			||||||
 | 
					                <el-button v-auth="perms.save" type="primary" icon="plus" @click="editFlowDef(false)">添加</el-button>
 | 
				
			||||||
 | 
					                <el-button v-auth="perms.del" :disabled="state.selectionData.length < 1" @click="deleteProcdef()" type="danger" icon="delete">删除</el-button>
 | 
				
			||||||
 | 
					            </template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            <template #tasks="{ data }">
 | 
				
			||||||
 | 
					                <el-link @click="showProcdefTasks(data)" icon="view" type="primary" :underline="false"> </el-link>
 | 
				
			||||||
 | 
					            </template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            <template #action="{ data }">
 | 
				
			||||||
 | 
					                <el-button link v-if="actionBtns[perms.save]" @click="editFlowDef(data)" type="primary">编辑</el-button>
 | 
				
			||||||
 | 
					            </template>
 | 
				
			||||||
 | 
					        </page-table>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        <el-dialog v-model="flowTasksDialog.visible" :title="flowTasksDialog.title">
 | 
				
			||||||
 | 
					            <procdef-tasks :tasks="flowTasksDialog.tasks" />
 | 
				
			||||||
 | 
					        </el-dialog>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        <procdef-edit v-model:visible="flowDefEditor.visible" :title="flowDefEditor.title" v-model:data="flowDefEditor.data" @val-change="valChange()" />
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<script lang="ts" setup>
 | 
				
			||||||
 | 
					import { ref, toRefs, reactive, onMounted, Ref } from 'vue';
 | 
				
			||||||
 | 
					import { procdefApi } from './api';
 | 
				
			||||||
 | 
					import { ElMessage, ElMessageBox } from 'element-plus';
 | 
				
			||||||
 | 
					import PageTable from '@/components/pagetable/PageTable.vue';
 | 
				
			||||||
 | 
					import { TableColumn } from '@/components/pagetable';
 | 
				
			||||||
 | 
					import { hasPerms } from '@/components/auth/auth';
 | 
				
			||||||
 | 
					import { SearchItem } from '@/components/SearchForm';
 | 
				
			||||||
 | 
					import ProcdefEdit from './ProcdefEdit.vue';
 | 
				
			||||||
 | 
					import ProcdefTasks from './components/ProcdefTasks.vue';
 | 
				
			||||||
 | 
					import { ProcdefStatus } from './enums';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const perms = {
 | 
				
			||||||
 | 
					    save: 'flow:procdef:save',
 | 
				
			||||||
 | 
					    del: 'flow:procdef:del',
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const searchItems = [SearchItem.input('name', '名称'), SearchItem.input('defKey', 'key')];
 | 
				
			||||||
 | 
					const columns = [
 | 
				
			||||||
 | 
					    TableColumn.new('name', '名称'),
 | 
				
			||||||
 | 
					    TableColumn.new('defKey', 'key'),
 | 
				
			||||||
 | 
					    TableColumn.new('status', '状态').typeTag(ProcdefStatus),
 | 
				
			||||||
 | 
					    TableColumn.new('remark', '备注'),
 | 
				
			||||||
 | 
					    TableColumn.new('tasks', '审批节点').isSlot().alignCenter().setMinWidth(60),
 | 
				
			||||||
 | 
					    TableColumn.new('creator', '创建账号'),
 | 
				
			||||||
 | 
					    TableColumn.new('createTime', '创建时间').isTime(),
 | 
				
			||||||
 | 
					];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// 该用户拥有的的操作列按钮权限
 | 
				
			||||||
 | 
					const actionBtns = hasPerms([perms.save, perms.del]);
 | 
				
			||||||
 | 
					const actionColumn = TableColumn.new('action', '操作').isSlot().fixedRight().setMinWidth(160).noShowOverflowTooltip().alignCenter();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const pageTableRef: Ref<any> = ref(null);
 | 
				
			||||||
 | 
					const state = reactive({
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * 选中的数据
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    selectionData: [],
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * 查询条件
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    query: {
 | 
				
			||||||
 | 
					        name: '',
 | 
				
			||||||
 | 
					        pageNum: 1,
 | 
				
			||||||
 | 
					        pageSize: 0,
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    flowDefEditor: {
 | 
				
			||||||
 | 
					        title: '新建流程定义',
 | 
				
			||||||
 | 
					        visible: false,
 | 
				
			||||||
 | 
					        data: null as any,
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    flowTasksDialog: {
 | 
				
			||||||
 | 
					        title: '',
 | 
				
			||||||
 | 
					        visible: false,
 | 
				
			||||||
 | 
					        tasks: '',
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const { selectionData, query, flowDefEditor, flowTasksDialog } = toRefs(state);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					onMounted(() => {
 | 
				
			||||||
 | 
					    if (Object.keys(actionBtns).length > 0) {
 | 
				
			||||||
 | 
					        columns.push(actionColumn);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const search = async () => {
 | 
				
			||||||
 | 
					    pageTableRef.value.search();
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const showProcdefTasks = (procdef: any) => {
 | 
				
			||||||
 | 
					    state.flowTasksDialog.tasks = procdef.tasks;
 | 
				
			||||||
 | 
					    state.flowTasksDialog.title = procdef.name + '-审批节点';
 | 
				
			||||||
 | 
					    state.flowTasksDialog.visible = true;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const editFlowDef = (data: any) => {
 | 
				
			||||||
 | 
					    if (!data) {
 | 
				
			||||||
 | 
					        state.flowDefEditor.data = null;
 | 
				
			||||||
 | 
					        state.flowDefEditor.title = '新建流程定义';
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					        state.flowDefEditor.data = data;
 | 
				
			||||||
 | 
					        state.flowDefEditor.title = '编辑流程定义';
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    state.flowDefEditor.visible = true;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const valChange = () => {
 | 
				
			||||||
 | 
					    state.flowDefEditor.visible = false;
 | 
				
			||||||
 | 
					    search();
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const deleteProcdef = async () => {
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					        await ElMessageBox.confirm(`确定删除【${state.selectionData.map((x: any) => x.name).join(', ')}】的流程定义?`, '提示', {
 | 
				
			||||||
 | 
					            confirmButtonText: '确定',
 | 
				
			||||||
 | 
					            cancelButtonText: '取消',
 | 
				
			||||||
 | 
					            type: 'warning',
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					        await procdefApi.del.request({ id: state.selectionData.map((x: any) => x.id).join(',') });
 | 
				
			||||||
 | 
					        ElMessage.success('删除成功');
 | 
				
			||||||
 | 
					        search();
 | 
				
			||||||
 | 
					    } catch (err) {
 | 
				
			||||||
 | 
					        //
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					</script>
 | 
				
			||||||
 | 
					<style lang="scss"></style>
 | 
				
			||||||
							
								
								
									
										171
									
								
								mayfly_go_web/src/views/flow/ProcinstDetail.vue
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										171
									
								
								mayfly_go_web/src/views/flow/ProcinstDetail.vue
									
									
									
									
									
										Executable file
									
								
							@@ -0,0 +1,171 @@
 | 
				
			|||||||
 | 
					<template>
 | 
				
			||||||
 | 
					    <div>
 | 
				
			||||||
 | 
					        <el-drawer :title="props.title" v-model="visible" :before-close="cancel" size="40%" :close-on-click-modal="!props.instTaskId">
 | 
				
			||||||
 | 
					            <template #header>
 | 
				
			||||||
 | 
					                <DrawerHeader :header="title" :back="cancel" />
 | 
				
			||||||
 | 
					            </template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            <div>
 | 
				
			||||||
 | 
					                <el-divider content-position="left">流程信息</el-divider>
 | 
				
			||||||
 | 
					                <el-descriptions :column="2" border>
 | 
				
			||||||
 | 
					                    <el-descriptions-item label="流程名">{{ procinst.procdefName }}</el-descriptions-item>
 | 
				
			||||||
 | 
					                    <el-descriptions-item label="业务">
 | 
				
			||||||
 | 
					                        <enum-tag :enums="FlowBizType" :value="procinst.bizType"></enum-tag>
 | 
				
			||||||
 | 
					                    </el-descriptions-item>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    <el-descriptions-item label="发起人">
 | 
				
			||||||
 | 
					                        <AccountInfo :account-id="procinst.creatorId" :username="procinst.creator" />
 | 
				
			||||||
 | 
					                        <!-- {{ procinst.creator }} -->
 | 
				
			||||||
 | 
					                    </el-descriptions-item>
 | 
				
			||||||
 | 
					                    <el-descriptions-item label="发起时间">{{ dateFormat(procinst.createTime) }}</el-descriptions-item>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    <div v-if="procinst.duration">
 | 
				
			||||||
 | 
					                        <el-descriptions-item label="持续时间">{{ formatTime(procinst.duration) }}</el-descriptions-item>
 | 
				
			||||||
 | 
					                        <el-descriptions-item label="结束时间">{{ dateFormat(procinst.endTime) }}</el-descriptions-item>
 | 
				
			||||||
 | 
					                    </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    <el-descriptions-item label="流程状态">
 | 
				
			||||||
 | 
					                        <enum-tag :enums="ProcinstStatus" :value="procinst.status"></enum-tag>
 | 
				
			||||||
 | 
					                    </el-descriptions-item>
 | 
				
			||||||
 | 
					                    <el-descriptions-item label="业务状态">
 | 
				
			||||||
 | 
					                        <enum-tag :enums="ProcinstBizStatus" :value="procinst.bizStatus"></enum-tag>
 | 
				
			||||||
 | 
					                    </el-descriptions-item>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    <el-descriptions-item label="备注">
 | 
				
			||||||
 | 
					                        {{ procinst.remark }}
 | 
				
			||||||
 | 
					                    </el-descriptions-item>
 | 
				
			||||||
 | 
					                </el-descriptions>
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            <div>
 | 
				
			||||||
 | 
					                <el-divider content-position="left">审批节点</el-divider>
 | 
				
			||||||
 | 
					                <procdef-tasks :tasks="procinst?.procdef?.tasks" :procinst-tasks="procinst.procinstTasks" />
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            <div>
 | 
				
			||||||
 | 
					                <el-divider content-position="left">业务信息</el-divider>
 | 
				
			||||||
 | 
					                <component
 | 
				
			||||||
 | 
					                    v-if="procinst.bizType"
 | 
				
			||||||
 | 
					                    ref="keyValueRef"
 | 
				
			||||||
 | 
					                    :is="bizComponents[procinst.bizType]"
 | 
				
			||||||
 | 
					                    :biz-key="procinst.bizKey"
 | 
				
			||||||
 | 
					                    :biz-form="procinst.bizForm"
 | 
				
			||||||
 | 
					                >
 | 
				
			||||||
 | 
					                </component>
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            <div v-if="props.instTaskId">
 | 
				
			||||||
 | 
					                <el-divider content-position="left">审批表单</el-divider>
 | 
				
			||||||
 | 
					                <el-form :model="form" label-width="auto">
 | 
				
			||||||
 | 
					                    <el-form-item prop="status" label="结果" required>
 | 
				
			||||||
 | 
					                        <el-select v-model="form.status" placeholder="请选择审批结果">
 | 
				
			||||||
 | 
					                            <el-option :label="ProcinstTaskStatus.Pass.label" :value="ProcinstTaskStatus.Pass.value"> </el-option>
 | 
				
			||||||
 | 
					                            <!-- <el-option :label="ProcinstTaskStatus.Back.label" :value="ProcinstTaskStatus.Back.value"> </el-option> -->
 | 
				
			||||||
 | 
					                            <el-option :label="ProcinstTaskStatus.Reject.label" :value="ProcinstTaskStatus.Reject.value"> </el-option>
 | 
				
			||||||
 | 
					                        </el-select>
 | 
				
			||||||
 | 
					                    </el-form-item>
 | 
				
			||||||
 | 
					                    <el-form-item prop="remark" label="备注">
 | 
				
			||||||
 | 
					                        <el-input v-model.trim="form.remark" placeholder="备注" type="textarea" clearable></el-input>
 | 
				
			||||||
 | 
					                    </el-form-item>
 | 
				
			||||||
 | 
					                </el-form>
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            <template #footer v-if="props.instTaskId">
 | 
				
			||||||
 | 
					                <div>
 | 
				
			||||||
 | 
					                    <el-button @click="cancel()">取 消</el-button>
 | 
				
			||||||
 | 
					                    <el-button type="primary" :loading="saveBtnLoading" @click="btnOk">确 定</el-button>
 | 
				
			||||||
 | 
					                </div>
 | 
				
			||||||
 | 
					            </template>
 | 
				
			||||||
 | 
					        </el-drawer>
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<script lang="ts" setup>
 | 
				
			||||||
 | 
					import { toRefs, reactive, watch, defineAsyncComponent, shallowReactive } from 'vue';
 | 
				
			||||||
 | 
					import { procinstApi } from './api';
 | 
				
			||||||
 | 
					import { ElMessage } from 'element-plus';
 | 
				
			||||||
 | 
					import DrawerHeader from '@/components/drawer-header/DrawerHeader.vue';
 | 
				
			||||||
 | 
					import { FlowBizType, ProcinstBizStatus, ProcinstTaskStatus, ProcinstStatus } from './enums';
 | 
				
			||||||
 | 
					import { dateFormat } from '@/common/utils/date';
 | 
				
			||||||
 | 
					import ProcdefTasks from './components/ProcdefTasks.vue';
 | 
				
			||||||
 | 
					import { formatTime } from '@/common/utils/format';
 | 
				
			||||||
 | 
					import EnumTag from '@/components/enumtag/EnumTag.vue';
 | 
				
			||||||
 | 
					import AccountInfo from '@/views/system/account/components/AccountInfo.vue';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const DbSqlExecBiz = defineAsyncComponent(() => import('./flowbiz/DbSqlExecBiz.vue'));
 | 
				
			||||||
 | 
					const RedisRunWriteCmdBiz = defineAsyncComponent(() => import('./flowbiz/RedisRunWriteCmdBiz.vue'));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const props = defineProps({
 | 
				
			||||||
 | 
					    procinstId: {
 | 
				
			||||||
 | 
					        type: Number,
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    // 流程实例任务id(存在则展示审批相关信息)
 | 
				
			||||||
 | 
					    instTaskId: {
 | 
				
			||||||
 | 
					        type: Number,
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    title: {
 | 
				
			||||||
 | 
					        type: String,
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const visible = defineModel<boolean>('visible', { default: false });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					//定义事件
 | 
				
			||||||
 | 
					const emit = defineEmits(['cancel', 'val-change']);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// 业务组件
 | 
				
			||||||
 | 
					const bizComponents = shallowReactive({
 | 
				
			||||||
 | 
					    db_sql_exec_flow: DbSqlExecBiz,
 | 
				
			||||||
 | 
					    redis_run_write_cmd_flow: RedisRunWriteCmdBiz,
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const state = reactive({
 | 
				
			||||||
 | 
					    procinst: {} as any,
 | 
				
			||||||
 | 
					    tasks: [] as any,
 | 
				
			||||||
 | 
					    form: {
 | 
				
			||||||
 | 
					        status: ProcinstTaskStatus.Pass.value,
 | 
				
			||||||
 | 
					        remark: '',
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    saveBtnLoading: false,
 | 
				
			||||||
 | 
					    sortable: '' as any,
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const { procinst, form, saveBtnLoading } = toRefs(state);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					watch(
 | 
				
			||||||
 | 
					    () => props.procinstId,
 | 
				
			||||||
 | 
					    async (newValue: any) => {
 | 
				
			||||||
 | 
					        if (newValue) {
 | 
				
			||||||
 | 
					            state.procinst = await procinstApi.detail.request({ id: newValue });
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            state.procinst = {};
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const btnOk = async () => {
 | 
				
			||||||
 | 
					    const status = state.form.status;
 | 
				
			||||||
 | 
					    let api = procinstApi.completeTask;
 | 
				
			||||||
 | 
					    if (status === ProcinstTaskStatus.Back.value) {
 | 
				
			||||||
 | 
					        api = procinstApi.backTask;
 | 
				
			||||||
 | 
					    } else if (status === ProcinstTaskStatus.Reject.value) {
 | 
				
			||||||
 | 
					        api = procinstApi.rejectTask;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					        state.saveBtnLoading = true;
 | 
				
			||||||
 | 
					        await api.request({ id: props.instTaskId, remark: state.form.remark });
 | 
				
			||||||
 | 
					        ElMessage.success('操作成功');
 | 
				
			||||||
 | 
					        cancel();
 | 
				
			||||||
 | 
					        emit('val-change');
 | 
				
			||||||
 | 
					    } finally {
 | 
				
			||||||
 | 
					        state.saveBtnLoading = false;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const cancel = () => {
 | 
				
			||||||
 | 
					    visible.value = false;
 | 
				
			||||||
 | 
					    emit('cancel');
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					</script>
 | 
				
			||||||
 | 
					<style lang="scss"></style>
 | 
				
			||||||
							
								
								
									
										126
									
								
								mayfly_go_web/src/views/flow/ProcinstList.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										126
									
								
								mayfly_go_web/src/views/flow/ProcinstList.vue
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,126 @@
 | 
				
			|||||||
 | 
					<template>
 | 
				
			||||||
 | 
					    <div>
 | 
				
			||||||
 | 
					        <page-table
 | 
				
			||||||
 | 
					            ref="pageTableRef"
 | 
				
			||||||
 | 
					            :page-api="procinstApi.list"
 | 
				
			||||||
 | 
					            :search-items="searchItems"
 | 
				
			||||||
 | 
					            v-model:query-form="query"
 | 
				
			||||||
 | 
					            v-model:selection-data="selectionData"
 | 
				
			||||||
 | 
					            :columns="columns"
 | 
				
			||||||
 | 
					        >
 | 
				
			||||||
 | 
					            <template #tableHeader>
 | 
				
			||||||
 | 
					                <!-- <el-button v-auth="perms.addAccount" type="primary" icon="plus" @click="editFlowDef(false)">添加</el-button> -->
 | 
				
			||||||
 | 
					            </template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            <template #action="{ data }">
 | 
				
			||||||
 | 
					                <el-button link @click="showProcinst(data)" type="primary">查看</el-button>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                <el-popconfirm
 | 
				
			||||||
 | 
					                    v-if="data.status == ProcinstStatus.Active.value || data.status == ProcinstStatus.Suspended.value"
 | 
				
			||||||
 | 
					                    title="确认取消该流程?"
 | 
				
			||||||
 | 
					                    width="160"
 | 
				
			||||||
 | 
					                    @confirm="procinstCancel(data)"
 | 
				
			||||||
 | 
					                >
 | 
				
			||||||
 | 
					                    <template #reference>
 | 
				
			||||||
 | 
					                        <el-button link type="warning">取消</el-button>
 | 
				
			||||||
 | 
					                    </template>
 | 
				
			||||||
 | 
					                </el-popconfirm>
 | 
				
			||||||
 | 
					            </template>
 | 
				
			||||||
 | 
					        </page-table>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        <ProcinstDetail
 | 
				
			||||||
 | 
					            v-model:visible="procinstDetail.visible"
 | 
				
			||||||
 | 
					            :title="procinstDetail.title"
 | 
				
			||||||
 | 
					            :procinst-id="procinstDetail.procinstId"
 | 
				
			||||||
 | 
					            :inst-task-id="procinstDetail.instTaskId"
 | 
				
			||||||
 | 
					            @val-change="valChange()"
 | 
				
			||||||
 | 
					            @cancel="procinstDetail.procinstId = 0"
 | 
				
			||||||
 | 
					        />
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<script lang="ts" setup>
 | 
				
			||||||
 | 
					import { ref, toRefs, reactive, Ref } from 'vue';
 | 
				
			||||||
 | 
					import { procinstApi } from './api';
 | 
				
			||||||
 | 
					import PageTable from '@/components/pagetable/PageTable.vue';
 | 
				
			||||||
 | 
					import { TableColumn } from '@/components/pagetable';
 | 
				
			||||||
 | 
					import { SearchItem } from '@/components/SearchForm';
 | 
				
			||||||
 | 
					import ProcinstDetail from './ProcinstDetail.vue';
 | 
				
			||||||
 | 
					import { FlowBizType, ProcinstBizStatus, ProcinstStatus } from './enums';
 | 
				
			||||||
 | 
					import { ElMessage } from 'element-plus';
 | 
				
			||||||
 | 
					import { formatTime } from '@/common/utils/format';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const searchItems = [
 | 
				
			||||||
 | 
					    SearchItem.select('status', '流程状态').withEnum(ProcinstStatus),
 | 
				
			||||||
 | 
					    SearchItem.select('bizType', '业务类型').withEnum(FlowBizType),
 | 
				
			||||||
 | 
					    SearchItem.input('bizKey', '业务key'),
 | 
				
			||||||
 | 
					];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const columns = [
 | 
				
			||||||
 | 
					    TableColumn.new('bizType', '业务').typeTag(FlowBizType),
 | 
				
			||||||
 | 
					    TableColumn.new('remark', '备注'),
 | 
				
			||||||
 | 
					    TableColumn.new('creator', '发起人'),
 | 
				
			||||||
 | 
					    TableColumn.new('bizKey', '业务key'),
 | 
				
			||||||
 | 
					    TableColumn.new('procdefName', '流程名'),
 | 
				
			||||||
 | 
					    TableColumn.new('status', '流程状态').typeTag(ProcinstStatus),
 | 
				
			||||||
 | 
					    TableColumn.new('bizStatus', '业务状态').typeTag(ProcinstBizStatus),
 | 
				
			||||||
 | 
					    TableColumn.new('createTime', '发起时间').isTime(),
 | 
				
			||||||
 | 
					    TableColumn.new('endTime', '结束时间').isTime(),
 | 
				
			||||||
 | 
					    TableColumn.new('duration', '持续时间').setFormatFunc((data: any, prop: string) => {
 | 
				
			||||||
 | 
					        const duration = data[prop];
 | 
				
			||||||
 | 
					        if (!duration) {
 | 
				
			||||||
 | 
					            return '';
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        return formatTime(duration);
 | 
				
			||||||
 | 
					    }),
 | 
				
			||||||
 | 
					    TableColumn.new('bizHandleRes', '业务处理结果'),
 | 
				
			||||||
 | 
					    TableColumn.new('action', '操作').isSlot().fixedRight().setMinWidth(160).noShowOverflowTooltip().alignCenter(),
 | 
				
			||||||
 | 
					];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const pageTableRef: Ref<any> = ref(null);
 | 
				
			||||||
 | 
					const state = reactive({
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * 选中的数据
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    selectionData: [],
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * 查询条件
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    query: {
 | 
				
			||||||
 | 
					        status: null,
 | 
				
			||||||
 | 
					        bizType: '',
 | 
				
			||||||
 | 
					        pageNum: 1,
 | 
				
			||||||
 | 
					        pageSize: 0,
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    procinstDetail: {
 | 
				
			||||||
 | 
					        title: '查看流程',
 | 
				
			||||||
 | 
					        visible: false,
 | 
				
			||||||
 | 
					        procinstId: 0,
 | 
				
			||||||
 | 
					        instTaskId: 0,
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const { selectionData, query, procinstDetail } = toRefs(state);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const search = async () => {
 | 
				
			||||||
 | 
					    pageTableRef.value.search();
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const procinstCancel = async (data: any) => {
 | 
				
			||||||
 | 
					    await procinstApi.cancel.request({ id: data.id });
 | 
				
			||||||
 | 
					    ElMessage.success('操作成功');
 | 
				
			||||||
 | 
					    search();
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const showProcinst = (data: any) => {
 | 
				
			||||||
 | 
					    state.procinstDetail.procinstId = data.id;
 | 
				
			||||||
 | 
					    state.procinstDetail.title = '流程查看';
 | 
				
			||||||
 | 
					    state.procinstDetail.visible = true;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const valChange = () => {
 | 
				
			||||||
 | 
					    state.procinstDetail.visible = false;
 | 
				
			||||||
 | 
					    search();
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					</script>
 | 
				
			||||||
 | 
					<style lang="scss"></style>
 | 
				
			||||||
							
								
								
									
										111
									
								
								mayfly_go_web/src/views/flow/ProcinstTaskList.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										111
									
								
								mayfly_go_web/src/views/flow/ProcinstTaskList.vue
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,111 @@
 | 
				
			|||||||
 | 
					<template>
 | 
				
			||||||
 | 
					    <div>
 | 
				
			||||||
 | 
					        <page-table
 | 
				
			||||||
 | 
					            ref="pageTableRef"
 | 
				
			||||||
 | 
					            :page-api="procinstApi.tasks"
 | 
				
			||||||
 | 
					            :search-items="searchItems"
 | 
				
			||||||
 | 
					            v-model:query-form="query"
 | 
				
			||||||
 | 
					            v-model:selection-data="selectionData"
 | 
				
			||||||
 | 
					            :columns="columns"
 | 
				
			||||||
 | 
					        >
 | 
				
			||||||
 | 
					            <template #tableHeader>
 | 
				
			||||||
 | 
					                <!-- <el-button v-auth="perms.addAccount" type="primary" icon="plus" @click="editFlowDef(false)">添加</el-button> -->
 | 
				
			||||||
 | 
					            </template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            <template #action="{ data }">
 | 
				
			||||||
 | 
					                <el-button link @click="showProcinst(data, false)" type="primary">查看</el-button>
 | 
				
			||||||
 | 
					                <el-button v-if="data.status == ProcinstTaskStatus.Process.value" link @click="showProcinst(data, true)" type="primary">审核</el-button>
 | 
				
			||||||
 | 
					            </template>
 | 
				
			||||||
 | 
					        </page-table>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        <ProcinstDetail
 | 
				
			||||||
 | 
					            v-model:visible="procinstDetail.visible"
 | 
				
			||||||
 | 
					            :title="procinstDetail.title"
 | 
				
			||||||
 | 
					            :procinst-id="procinstDetail.procinstId"
 | 
				
			||||||
 | 
					            :inst-task-id="procinstDetail.instTaskId"
 | 
				
			||||||
 | 
					            @val-change="valChange()"
 | 
				
			||||||
 | 
					            @cancel="procinstDetail.procinstId = 0"
 | 
				
			||||||
 | 
					        />
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<script lang="ts" setup>
 | 
				
			||||||
 | 
					import { ref, toRefs, reactive, Ref } from 'vue';
 | 
				
			||||||
 | 
					import { procinstApi } from './api';
 | 
				
			||||||
 | 
					import PageTable from '@/components/pagetable/PageTable.vue';
 | 
				
			||||||
 | 
					import { TableColumn } from '@/components/pagetable';
 | 
				
			||||||
 | 
					import { SearchItem } from '@/components/SearchForm';
 | 
				
			||||||
 | 
					import ProcinstDetail from './ProcinstDetail.vue';
 | 
				
			||||||
 | 
					import { FlowBizType, ProcinstStatus, ProcinstTaskStatus } from './enums';
 | 
				
			||||||
 | 
					import { formatTime } from '@/common/utils/format';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const searchItems = [SearchItem.select('status', '任务状态').withEnum(ProcinstTaskStatus), SearchItem.select('bizType', '业务类型').withEnum(FlowBizType)];
 | 
				
			||||||
 | 
					const columns = [
 | 
				
			||||||
 | 
					    TableColumn.new('procinst.bizType', '业务').typeTag(FlowBizType),
 | 
				
			||||||
 | 
					    TableColumn.new('procinst.remark', '备注'),
 | 
				
			||||||
 | 
					    TableColumn.new('procinst.creator', '发起人'),
 | 
				
			||||||
 | 
					    TableColumn.new('procinst.status', '流程状态').typeTag(ProcinstStatus),
 | 
				
			||||||
 | 
					    TableColumn.new('status', '任务状态').typeTag(ProcinstTaskStatus),
 | 
				
			||||||
 | 
					    TableColumn.new('procinst.bizKey', '业务key'),
 | 
				
			||||||
 | 
					    TableColumn.new('procinst.procdefName', '流程名'),
 | 
				
			||||||
 | 
					    TableColumn.new('taskName', '当前节点'),
 | 
				
			||||||
 | 
					    TableColumn.new('procinst.createTime', '发起时间').isTime(),
 | 
				
			||||||
 | 
					    TableColumn.new('createTime', '开始时间').isTime(),
 | 
				
			||||||
 | 
					    TableColumn.new('endTime', '结束时间').isTime(),
 | 
				
			||||||
 | 
					    TableColumn.new('duration', '持续时间').setFormatFunc((data: any, prop: string) => {
 | 
				
			||||||
 | 
					        const duration = data[prop];
 | 
				
			||||||
 | 
					        if (!duration) {
 | 
				
			||||||
 | 
					            return '';
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        return formatTime(duration);
 | 
				
			||||||
 | 
					    }),
 | 
				
			||||||
 | 
					    TableColumn.new('action', '操作').isSlot().fixedRight().setMinWidth(160).noShowOverflowTooltip().alignCenter(),
 | 
				
			||||||
 | 
					];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const pageTableRef: Ref<any> = ref(null);
 | 
				
			||||||
 | 
					const state = reactive({
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * 选中的数据
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    selectionData: [],
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * 查询条件
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    query: {
 | 
				
			||||||
 | 
					        status: ProcinstTaskStatus.Process.value,
 | 
				
			||||||
 | 
					        bizType: '',
 | 
				
			||||||
 | 
					        pageNum: 1,
 | 
				
			||||||
 | 
					        pageSize: 0,
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    procinstDetail: {
 | 
				
			||||||
 | 
					        title: '查看流程',
 | 
				
			||||||
 | 
					        visible: false,
 | 
				
			||||||
 | 
					        procinstId: 0,
 | 
				
			||||||
 | 
					        instTaskId: 0,
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const { selectionData, query, procinstDetail } = toRefs(state);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const search = async () => {
 | 
				
			||||||
 | 
					    pageTableRef.value.search();
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const showProcinst = (data: any, audit: boolean) => {
 | 
				
			||||||
 | 
					    state.procinstDetail.procinstId = data.procinstId;
 | 
				
			||||||
 | 
					    if (!audit) {
 | 
				
			||||||
 | 
					        state.procinstDetail.instTaskId = 0;
 | 
				
			||||||
 | 
					        state.procinstDetail.title = '流程查看';
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					        state.procinstDetail.instTaskId = data.id;
 | 
				
			||||||
 | 
					        state.procinstDetail.title = '流程审批';
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    state.procinstDetail.visible = true;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const valChange = () => {
 | 
				
			||||||
 | 
					    state.procinstDetail.visible = false;
 | 
				
			||||||
 | 
					    search();
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					</script>
 | 
				
			||||||
 | 
					<style lang="scss"></style>
 | 
				
			||||||
							
								
								
									
										20
									
								
								mayfly_go_web/src/views/flow/api.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								mayfly_go_web/src/views/flow/api.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,20 @@
 | 
				
			|||||||
 | 
					import Api from '@/common/Api';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const procdefApi = {
 | 
				
			||||||
 | 
					    list: Api.newGet('/flow/procdefs'),
 | 
				
			||||||
 | 
					    getByKey: Api.newGet('/flow/procdefs/{key}'),
 | 
				
			||||||
 | 
					    save: Api.newPost('/flow/procdefs'),
 | 
				
			||||||
 | 
					    del: Api.newDelete('/flow/procdefs/{id}'),
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const procinstApi = {
 | 
				
			||||||
 | 
					    list: Api.newGet('/flow/procinsts'),
 | 
				
			||||||
 | 
					    detail: Api.newGet('/flow/procinsts/{id}'),
 | 
				
			||||||
 | 
					    cancel: Api.newPost('/flow/procinsts/{id}/cancel'),
 | 
				
			||||||
 | 
					    tasks: Api.newGet('/flow/procinsts/tasks'),
 | 
				
			||||||
 | 
					    completeTask: Api.newPost('/flow/procinsts/tasks/complete'),
 | 
				
			||||||
 | 
					    backTask: Api.newPost('/flow/procinsts/tasks/back'),
 | 
				
			||||||
 | 
					    rejectTask: Api.newPost('/flow/procinsts/tasks/reject'),
 | 
				
			||||||
 | 
					    save: Api.newPost('/flow/procdefs'),
 | 
				
			||||||
 | 
					    del: Api.newDelete('/flow/procdefs/{id}'),
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
@@ -0,0 +1,33 @@
 | 
				
			|||||||
 | 
					<template>
 | 
				
			||||||
 | 
					    <el-form-item :label="props.label">
 | 
				
			||||||
 | 
					        <el-select style="width: 100%" v-model="procdefKey" filterable placeholder="绑定流程则开启对应审批流程" v-bind="$attrs" clearable>
 | 
				
			||||||
 | 
					            <el-option v-for="item in procdefs" :key="item.defKey" :label="`${item.defKey} [${item.name}]`" :value="item.defKey"> </el-option>
 | 
				
			||||||
 | 
					        </el-select>
 | 
				
			||||||
 | 
					    </el-form-item>
 | 
				
			||||||
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<script lang="ts" setup>
 | 
				
			||||||
 | 
					import { onMounted, ref } from 'vue';
 | 
				
			||||||
 | 
					import { procdefApi } from '../api';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const props = defineProps({
 | 
				
			||||||
 | 
					    label: {
 | 
				
			||||||
 | 
					        type: String,
 | 
				
			||||||
 | 
					        default: '工单流程',
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					onMounted(() => {
 | 
				
			||||||
 | 
					    getProcdefs();
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const procdefKey = defineModel('modelValue');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const procdefs: any = ref([]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const getProcdefs = () => {
 | 
				
			||||||
 | 
					    procdefApi.list.request({ pageSize: 200 }).then((res) => {
 | 
				
			||||||
 | 
					        procdefs.value = res.list;
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					</script>
 | 
				
			||||||
							
								
								
									
										136
									
								
								mayfly_go_web/src/views/flow/components/ProcdefTasks.vue
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										136
									
								
								mayfly_go_web/src/views/flow/components/ProcdefTasks.vue
									
									
									
									
									
										Executable file
									
								
							@@ -0,0 +1,136 @@
 | 
				
			|||||||
 | 
					<template>
 | 
				
			||||||
 | 
					    <el-steps align-center :active="stepActive">
 | 
				
			||||||
 | 
					        <el-step v-for="task in tasksArr" :status="getStepStatus(task)" :title="task.name" :key="task.taskKey">
 | 
				
			||||||
 | 
					            <template #description>
 | 
				
			||||||
 | 
					                <div>{{ `${task.accountUsername}(${task.accountName})` }}</div>
 | 
				
			||||||
 | 
					                <div v-if="task.completeTime">{{ `${dateFormat(task.completeTime)}` }}</div>
 | 
				
			||||||
 | 
					                <div v-if="task.remark">{{ task.remark }}</div>
 | 
				
			||||||
 | 
					            </template>
 | 
				
			||||||
 | 
					        </el-step>
 | 
				
			||||||
 | 
					    </el-steps>
 | 
				
			||||||
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<script lang="ts" setup>
 | 
				
			||||||
 | 
					import { toRefs, reactive, watch, onMounted } from 'vue';
 | 
				
			||||||
 | 
					import { accountApi } from '../../system/api';
 | 
				
			||||||
 | 
					import { ProcinstTaskStatus } from '../enums';
 | 
				
			||||||
 | 
					import { dateFormat } from '@/common/utils/date';
 | 
				
			||||||
 | 
					import { procdefApi } from '../api';
 | 
				
			||||||
 | 
					import { ElSteps, ElStep } from 'element-plus';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const props = defineProps({
 | 
				
			||||||
 | 
					    // 流程定义任务
 | 
				
			||||||
 | 
					    tasks: {
 | 
				
			||||||
 | 
					        type: [String, Object],
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    procdefKey: {
 | 
				
			||||||
 | 
					        type: String,
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    // 流程实例任务列表
 | 
				
			||||||
 | 
					    procinstTasks: {
 | 
				
			||||||
 | 
					        type: [Array],
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const state = reactive({
 | 
				
			||||||
 | 
					    tasksArr: [] as any,
 | 
				
			||||||
 | 
					    stepActive: 0,
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const { tasksArr, stepActive } = toRefs(state);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					watch(
 | 
				
			||||||
 | 
					    () => props.tasks,
 | 
				
			||||||
 | 
					    (newValue: any) => {
 | 
				
			||||||
 | 
					        parseTasks(newValue);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					watch(
 | 
				
			||||||
 | 
					    () => props.procinstTasks,
 | 
				
			||||||
 | 
					    () => {
 | 
				
			||||||
 | 
					        parseTasks(props.tasks);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					watch(
 | 
				
			||||||
 | 
					    () => props.procdefKey,
 | 
				
			||||||
 | 
					    async (newValue: any) => {
 | 
				
			||||||
 | 
					        if (newValue) {
 | 
				
			||||||
 | 
					            parseTasksByKey(newValue);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					onMounted(() => {
 | 
				
			||||||
 | 
					    if (props.procdefKey) {
 | 
				
			||||||
 | 
					        parseTasksByKey(props.procdefKey);
 | 
				
			||||||
 | 
					        return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    parseTasks(props.tasks);
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const parseTasksByKey = async (key: string) => {
 | 
				
			||||||
 | 
					    const procdef = await procdefApi.getByKey.request({ key });
 | 
				
			||||||
 | 
					    parseTasks(procdef.tasks);
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const parseTasks = async (tasksStr: any) => {
 | 
				
			||||||
 | 
					    if (!tasksStr) return;
 | 
				
			||||||
 | 
					    const tasks = JSON.parse(tasksStr);
 | 
				
			||||||
 | 
					    const userIds = tasks.map((x: any) => x.userId);
 | 
				
			||||||
 | 
					    const usersRes = await accountApi.querySimple.request({ ids: [...new Set(userIds)].join(','), pageSize: 50 });
 | 
				
			||||||
 | 
					    const users = usersRes.list;
 | 
				
			||||||
 | 
					    // 将数组转换为 Map 结构,以 id 为 key
 | 
				
			||||||
 | 
					    const userMap = users.reduce((acc: any, obj: any) => {
 | 
				
			||||||
 | 
					        acc.set(obj.id, obj);
 | 
				
			||||||
 | 
					        return acc;
 | 
				
			||||||
 | 
					    }, new Map());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // 流程实例任务(用于显示完成时间,完成到哪一步等)
 | 
				
			||||||
 | 
					    let instTasksMap: any;
 | 
				
			||||||
 | 
					    if (props.procinstTasks) {
 | 
				
			||||||
 | 
					        state.stepActive = props.procinstTasks.length - 1;
 | 
				
			||||||
 | 
					        instTasksMap = props.procinstTasks.reduce((acc: any, obj: any) => {
 | 
				
			||||||
 | 
					            acc.set(obj.taskKey, obj);
 | 
				
			||||||
 | 
					            return acc;
 | 
				
			||||||
 | 
					        }, new Map());
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    for (let task of tasks) {
 | 
				
			||||||
 | 
					        const user = userMap.get(Number.parseInt(task.userId));
 | 
				
			||||||
 | 
					        task.accountUsername = user.username;
 | 
				
			||||||
 | 
					        task.accountName = user.name;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // 存在实例任务,则赋值实例任务对应的完成时间和备注
 | 
				
			||||||
 | 
					        const instTask = instTasksMap?.get(task.taskKey);
 | 
				
			||||||
 | 
					        if (instTask) {
 | 
				
			||||||
 | 
					            task.status = instTask.status;
 | 
				
			||||||
 | 
					            task.completeTime = instTask.endTime;
 | 
				
			||||||
 | 
					            task.remark = instTask.remark;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    state.tasksArr = tasks;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const getStepStatus = (task: any): any => {
 | 
				
			||||||
 | 
					    const taskStatus = task.status;
 | 
				
			||||||
 | 
					    if (!taskStatus) {
 | 
				
			||||||
 | 
					        return 'wait';
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (taskStatus == ProcinstTaskStatus.Pass.value) {
 | 
				
			||||||
 | 
					        return 'success';
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    if (taskStatus == ProcinstTaskStatus.Process.value) {
 | 
				
			||||||
 | 
					        return 'proccess';
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    if (taskStatus == ProcinstTaskStatus.Back.value || taskStatus == ProcinstTaskStatus.Reject.value) {
 | 
				
			||||||
 | 
					        return 'error';
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return 'wait';
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					</script>
 | 
				
			||||||
 | 
					<style lang="scss"></style>
 | 
				
			||||||
							
								
								
									
										34
									
								
								mayfly_go_web/src/views/flow/enums.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								mayfly_go_web/src/views/flow/enums.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,34 @@
 | 
				
			|||||||
 | 
					import { EnumValue } from '@/common/Enum';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const ProcdefStatus = {
 | 
				
			||||||
 | 
					    Enable: EnumValue.of(1, '启用').setTagType('success'),
 | 
				
			||||||
 | 
					    Disable: EnumValue.of(-1, '禁用').setTagType('warning'),
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const ProcinstStatus = {
 | 
				
			||||||
 | 
					    Active: EnumValue.of(1, '执行中').setTagType('primary'),
 | 
				
			||||||
 | 
					    Completed: EnumValue.of(2, '完成').setTagType('success'),
 | 
				
			||||||
 | 
					    Suspended: EnumValue.of(-1, '挂起').setTagType('warning'),
 | 
				
			||||||
 | 
					    Terminated: EnumValue.of(-2, '终止').setTagType('danger'),
 | 
				
			||||||
 | 
					    Cancelled: EnumValue.of(-3, '取消').setTagType('warning'),
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const ProcinstBizStatus = {
 | 
				
			||||||
 | 
					    Wait: EnumValue.of(1, '待处理').setTagType('primary'),
 | 
				
			||||||
 | 
					    Success: EnumValue.of(2, '处理成功').setTagType('success'),
 | 
				
			||||||
 | 
					    Fail: EnumValue.of(-2, '处理失败').setTagType('danger'),
 | 
				
			||||||
 | 
					    No: EnumValue.of(-1, '不处理').setTagType('warning'),
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const ProcinstTaskStatus = {
 | 
				
			||||||
 | 
					    Process: EnumValue.of(1, '待处理').setTagType('primary'),
 | 
				
			||||||
 | 
					    Pass: EnumValue.of(2, '通过').setTagType('success'),
 | 
				
			||||||
 | 
					    Reject: EnumValue.of(-1, '拒绝').setTagType('danger'),
 | 
				
			||||||
 | 
					    Back: EnumValue.of(-2, '驳回').setTagType('warning'),
 | 
				
			||||||
 | 
					    Canceled: EnumValue.of(-3, '取消').setTagType('warning'),
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const FlowBizType = {
 | 
				
			||||||
 | 
					    DbSqlExec: EnumValue.of('db_sql_exec_flow', 'DBMS-执行SQL'),
 | 
				
			||||||
 | 
					    RedisRunWriteCmd: EnumValue.of('redis_run_write_cmd_flow', 'Redis-执行write命令'),
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
							
								
								
									
										79
									
								
								mayfly_go_web/src/views/flow/flowbiz/DbSqlExecBiz.vue
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										79
									
								
								mayfly_go_web/src/views/flow/flowbiz/DbSqlExecBiz.vue
									
									
									
									
									
										Executable file
									
								
							@@ -0,0 +1,79 @@
 | 
				
			|||||||
 | 
					<template>
 | 
				
			||||||
 | 
					    <div>
 | 
				
			||||||
 | 
					        <el-descriptions :column="3" border>
 | 
				
			||||||
 | 
					            <el-descriptions-item :span="2" label="名称">{{ db?.name }}</el-descriptions-item>
 | 
				
			||||||
 | 
					            <el-descriptions-item :span="1" label="id">{{ db?.id }}</el-descriptions-item>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            <el-descriptions-item :span="3" label="关联标签"><ResourceTags :tags="db.tags" /></el-descriptions-item>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            <el-descriptions-item :span="1" label="主机">{{ `${db?.host}:${db?.port}` }}</el-descriptions-item>
 | 
				
			||||||
 | 
					            <el-descriptions-item :span="1" label="类型">
 | 
				
			||||||
 | 
					                <SvgIcon :name="getDbDialect(db?.type).getInfo().icon" :size="20" />{{ db?.type }}
 | 
				
			||||||
 | 
					            </el-descriptions-item>
 | 
				
			||||||
 | 
					            <el-descriptions-item :span="1" label="用户名">{{ db?.username }}</el-descriptions-item>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            <el-descriptions-item label="数据库">{{ sqlExec.db }}</el-descriptions-item>
 | 
				
			||||||
 | 
					            <el-descriptions-item label="表">
 | 
				
			||||||
 | 
					                {{ sqlExec.table }}
 | 
				
			||||||
 | 
					            </el-descriptions-item>
 | 
				
			||||||
 | 
					            <el-descriptions-item label="类型">
 | 
				
			||||||
 | 
					                <el-tag size="small">{{ EnumValue.getLabelByValue(DbSqlExecTypeEnum, sqlExec.type) }}</el-tag>
 | 
				
			||||||
 | 
					            </el-descriptions-item>
 | 
				
			||||||
 | 
					            <el-descriptions-item label="执行SQL">
 | 
				
			||||||
 | 
					                <monaco-editor height="300px" language="sql" v-model="sqlExec.sql" :options="{ readOnly: true }" />
 | 
				
			||||||
 | 
					            </el-descriptions-item>
 | 
				
			||||||
 | 
					        </el-descriptions>
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<script lang="ts" setup>
 | 
				
			||||||
 | 
					import { toRefs, reactive, watch, onMounted } from 'vue';
 | 
				
			||||||
 | 
					import EnumValue from '@/common/Enum';
 | 
				
			||||||
 | 
					import { dbApi } from '@/views/ops/db/api';
 | 
				
			||||||
 | 
					import { DbSqlExecTypeEnum } from '@/views/ops/db/enums';
 | 
				
			||||||
 | 
					import MonacoEditor from '@/components/monaco/MonacoEditor.vue';
 | 
				
			||||||
 | 
					import { getDbDialect } from '@/views/ops/db/dialect';
 | 
				
			||||||
 | 
					import ResourceTags from '@/views/ops/component/ResourceTags.vue';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const props = defineProps({
 | 
				
			||||||
 | 
					    // 业务key
 | 
				
			||||||
 | 
					    bizKey: {
 | 
				
			||||||
 | 
					        type: [String],
 | 
				
			||||||
 | 
					        default: '',
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const state = reactive({
 | 
				
			||||||
 | 
					    sqlExec: {
 | 
				
			||||||
 | 
					        sql: '',
 | 
				
			||||||
 | 
					    } as any,
 | 
				
			||||||
 | 
					    db: {} as any,
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const { sqlExec, db } = toRefs(state);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					onMounted(() => {
 | 
				
			||||||
 | 
					    getDbSqlExec(props.bizKey);
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					watch(
 | 
				
			||||||
 | 
					    () => props.bizKey,
 | 
				
			||||||
 | 
					    (newValue: any) => {
 | 
				
			||||||
 | 
					        getDbSqlExec(newValue);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const getDbSqlExec = async (bizKey: string) => {
 | 
				
			||||||
 | 
					    if (!bizKey) {
 | 
				
			||||||
 | 
					        return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    const res = await dbApi.getSqlExecs.request({ flowBizKey: bizKey });
 | 
				
			||||||
 | 
					    if (!res.list) {
 | 
				
			||||||
 | 
					        return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    state.sqlExec = res.list?.[0];
 | 
				
			||||||
 | 
					    const dbRes = await dbApi.dbs.request({ id: state.sqlExec.dbId });
 | 
				
			||||||
 | 
					    state.db = dbRes.list?.[0];
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					</script>
 | 
				
			||||||
 | 
					<style lang="scss"></style>
 | 
				
			||||||
							
								
								
									
										80
									
								
								mayfly_go_web/src/views/flow/flowbiz/RedisRunWriteCmdBiz.vue
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										80
									
								
								mayfly_go_web/src/views/flow/flowbiz/RedisRunWriteCmdBiz.vue
									
									
									
									
									
										Executable file
									
								
							@@ -0,0 +1,80 @@
 | 
				
			|||||||
 | 
					<template>
 | 
				
			||||||
 | 
					    <div>
 | 
				
			||||||
 | 
					        <el-descriptions :column="3" border>
 | 
				
			||||||
 | 
					            <el-descriptions-item :span="1" label="名称">{{ redis?.name }}</el-descriptions-item>
 | 
				
			||||||
 | 
					            <el-descriptions-item :span="1" label="id">{{ redis?.id }}</el-descriptions-item>
 | 
				
			||||||
 | 
					            <el-descriptions-item :span="1" label="用户名">{{ redis?.username }}</el-descriptions-item>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            <el-descriptions-item :span="3" label="关联标签"><ResourceTags :tags="redis.tags" /></el-descriptions-item>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            <el-descriptions-item :span="1" label="主机">{{ `${redis?.host}` }}</el-descriptions-item>
 | 
				
			||||||
 | 
					            <el-descriptions-item :span="1" label="库">{{ state.db }}</el-descriptions-item>
 | 
				
			||||||
 | 
					            <el-descriptions-item :span="1" label="mode">
 | 
				
			||||||
 | 
					                {{ redis.mode }}
 | 
				
			||||||
 | 
					            </el-descriptions-item>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            <el-descriptions-item :span="3" label="执行Cmd">
 | 
				
			||||||
 | 
					                <el-input type="textarea" disabled v-model="cmd" rows="5" />
 | 
				
			||||||
 | 
					            </el-descriptions-item>
 | 
				
			||||||
 | 
					        </el-descriptions>
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<script lang="ts" setup>
 | 
				
			||||||
 | 
					import { toRefs, reactive, watch, onMounted } from 'vue';
 | 
				
			||||||
 | 
					import ResourceTags from '@/views/ops/component/ResourceTags.vue';
 | 
				
			||||||
 | 
					import { redisApi } from '@/views/ops/redis/api';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const props = defineProps({
 | 
				
			||||||
 | 
					    // 业务表单
 | 
				
			||||||
 | 
					    bizForm: {
 | 
				
			||||||
 | 
					        type: [String],
 | 
				
			||||||
 | 
					        default: '',
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const state = reactive({
 | 
				
			||||||
 | 
					    cmd: '',
 | 
				
			||||||
 | 
					    db: 0,
 | 
				
			||||||
 | 
					    redis: {} as any,
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const { cmd, redis } = toRefs(state);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					onMounted(() => {
 | 
				
			||||||
 | 
					    parseRunCmdForm(props.bizForm);
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					watch(
 | 
				
			||||||
 | 
					    () => props.bizForm,
 | 
				
			||||||
 | 
					    (newValue: any) => {
 | 
				
			||||||
 | 
					        parseRunCmdForm(newValue);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const parseRunCmdForm = async (bizForm: string) => {
 | 
				
			||||||
 | 
					    if (!bizForm) {
 | 
				
			||||||
 | 
					        return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    const form = JSON.parse(bizForm);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const cmds = form.cmd.map((item: any, index: number) => {
 | 
				
			||||||
 | 
					        if (index === 0) {
 | 
				
			||||||
 | 
					            return item; // 第一个元素直接返回原值
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        if (typeof item === 'string') {
 | 
				
			||||||
 | 
					            return `'${item}'`; // 字符串加单引号
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        return item; // 其他类型直接返回
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    state.cmd = cmds.join('  ');
 | 
				
			||||||
 | 
					    state.db = form.db;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const res = await redisApi.redisList.request({ id: form.id });
 | 
				
			||||||
 | 
					    if (!res.list) {
 | 
				
			||||||
 | 
					        return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    state.redis = res.list?.[0];
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					</script>
 | 
				
			||||||
 | 
					<style lang="scss"></style>
 | 
				
			||||||
@@ -73,12 +73,28 @@ const currentTime = computed(() => {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
// 初始化数字滚动
 | 
					// 初始化数字滚动
 | 
				
			||||||
const initNumCountUp = async () => {
 | 
					const initNumCountUp = async () => {
 | 
				
			||||||
    const res: any = await indexApi.getIndexCount.request();
 | 
					    indexApi.machineDashbord.request().then((res: any) => {
 | 
				
			||||||
    nextTick(() => {
 | 
					        nextTick(() => {
 | 
				
			||||||
        new CountUp('mongoNum', res.mongoNum).start();
 | 
					            new CountUp('machineNum', res.machineNum).start();
 | 
				
			||||||
        new CountUp('machineNum', res.machineNum).start();
 | 
					        });
 | 
				
			||||||
        new CountUp('dbNum', res.dbNum).start();
 | 
					    });
 | 
				
			||||||
        new CountUp('redisNum', res.redisNum).start();
 | 
					
 | 
				
			||||||
 | 
					    indexApi.dbDashbord.request().then((res: any) => {
 | 
				
			||||||
 | 
					        nextTick(() => {
 | 
				
			||||||
 | 
					            new CountUp('dbNum', res.dbNum).start();
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    indexApi.redisDashbord.request().then((res: any) => {
 | 
				
			||||||
 | 
					        nextTick(() => {
 | 
				
			||||||
 | 
					            new CountUp('redisNum', res.redisNum).start();
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    indexApi.mongoDashbord.request().then((res: any) => {
 | 
				
			||||||
 | 
					        nextTick(() => {
 | 
				
			||||||
 | 
					            new CountUp('mongoNum', res.mongoNum).start();
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -93,7 +109,7 @@ const toPage = (item: any) => {
 | 
				
			|||||||
            break;
 | 
					            break;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        case 'machineNum': {
 | 
					        case 'machineNum': {
 | 
				
			||||||
            router.push('/machine/machines');
 | 
					            router.push('/machine/machines-op');
 | 
				
			||||||
            break;
 | 
					            break;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        case 'dbNum': {
 | 
					        case 'dbNum': {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,6 +1,8 @@
 | 
				
			|||||||
import Api from '@/common/Api';
 | 
					import Api from '@/common/Api';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const indexApi = {
 | 
					export const indexApi = {
 | 
				
			||||||
    getIndexCount: Api.newGet("/common/index/count"),
 | 
					    machineDashbord: Api.newGet('/machines/dashbord'),
 | 
				
			||||||
}
 | 
					    dbDashbord: Api.newGet('/dbs/dashbord'),
 | 
				
			||||||
 | 
					    redisDashbord: Api.newGet('/redis/dashbord'),
 | 
				
			||||||
 | 
					    mongoDashbord: Api.newGet('/mongos/dashbord'),
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										28
									
								
								mayfly_go_web/src/views/ops/component/ResourceAuthCert.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								mayfly_go_web/src/views/ops/component/ResourceAuthCert.vue
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,28 @@
 | 
				
			|||||||
 | 
					<template>
 | 
				
			||||||
 | 
					    <div v-if="props.authCerts">
 | 
				
			||||||
 | 
					        <el-select default-first-option value-key="name" style="width: 100%" v-model="selectAuthCert" size="small">
 | 
				
			||||||
 | 
					            <el-option v-for="item in props.authCerts" :key="item.name" :label="item.username" :value="item">
 | 
				
			||||||
 | 
					                {{ item.username }}
 | 
				
			||||||
 | 
					                <el-divider direction="vertical" border-style="dashed" />
 | 
				
			||||||
 | 
					                <EnumTag :value="item.type" :enums="AuthCertTypeEnum" />
 | 
				
			||||||
 | 
					                <el-divider direction="vertical" border-style="dashed" />
 | 
				
			||||||
 | 
					                <EnumTag :value="item.ciphertextType" :enums="AuthCertCiphertextTypeEnum" />
 | 
				
			||||||
 | 
					            </el-option>
 | 
				
			||||||
 | 
					        </el-select>
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<script lang="ts" setup>
 | 
				
			||||||
 | 
					import EnumTag from '@/components/enumtag/EnumTag.vue';
 | 
				
			||||||
 | 
					import { AuthCertTypeEnum, AuthCertCiphertextTypeEnum } from '../tag/enums';
 | 
				
			||||||
 | 
					const props = defineProps({
 | 
				
			||||||
 | 
					    authCerts: {
 | 
				
			||||||
 | 
					        type: [Array<any>],
 | 
				
			||||||
 | 
					        required: true,
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const selectAuthCert = defineModel('selectAuthCert');
 | 
				
			||||||
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<style lang="scss"></style>
 | 
				
			||||||
							
								
								
									
										262
									
								
								mayfly_go_web/src/views/ops/component/ResourceAuthCertEdit.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										262
									
								
								mayfly_go_web/src/views/ops/component/ResourceAuthCertEdit.vue
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,262 @@
 | 
				
			|||||||
 | 
					<template>
 | 
				
			||||||
 | 
					    <div class="auth-cert-edit">
 | 
				
			||||||
 | 
					        <el-dialog :title="props.title" v-model="dialogVisible" :show-close="false" width="500px" :destroy-on-close="true" :close-on-click-modal="false">
 | 
				
			||||||
 | 
					            <el-form ref="acForm" :model="state.form" label-width="auto" :rules="rules">
 | 
				
			||||||
 | 
					                <el-form-item prop="type" label="凭证类型" required>
 | 
				
			||||||
 | 
					                    <el-select @change="changeType" v-model="form.type" placeholder="请选择凭证类型">
 | 
				
			||||||
 | 
					                        <el-option
 | 
				
			||||||
 | 
					                            v-for="item in AuthCertTypeEnum"
 | 
				
			||||||
 | 
					                            :key="item.value"
 | 
				
			||||||
 | 
					                            :label="item.label"
 | 
				
			||||||
 | 
					                            :value="item.value"
 | 
				
			||||||
 | 
					                            v-show="!props.disableType?.includes(item.value)"
 | 
				
			||||||
 | 
					                        >
 | 
				
			||||||
 | 
					                        </el-option>
 | 
				
			||||||
 | 
					                    </el-select>
 | 
				
			||||||
 | 
					                </el-form-item>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                <el-form-item prop="ciphertextType" label="密文类型" required>
 | 
				
			||||||
 | 
					                    <el-select v-model="form.ciphertextType" placeholder="请选择密文类型" @change="changeCiphertextType">
 | 
				
			||||||
 | 
					                        <el-option
 | 
				
			||||||
 | 
					                            v-for="item in AuthCertCiphertextTypeEnum"
 | 
				
			||||||
 | 
					                            :key="item.value"
 | 
				
			||||||
 | 
					                            :label="item.label"
 | 
				
			||||||
 | 
					                            :value="item.value"
 | 
				
			||||||
 | 
					                            v-show="!props.disableCiphertextType?.includes(item.value)"
 | 
				
			||||||
 | 
					                            :disabled="item.value == AuthCertCiphertextTypeEnum.Public.value && form.type == AuthCertTypeEnum.Public.value"
 | 
				
			||||||
 | 
					                        >
 | 
				
			||||||
 | 
					                        </el-option>
 | 
				
			||||||
 | 
					                    </el-select>
 | 
				
			||||||
 | 
					                </el-form-item>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                <template v-if="showResourceEdit">
 | 
				
			||||||
 | 
					                    <el-form-item prop="type" label="资源类型" required>
 | 
				
			||||||
 | 
					                        <el-select :disabled="form.id" v-model="form.resourceType" placeholder="请选择资源类型">
 | 
				
			||||||
 | 
					                            <el-option
 | 
				
			||||||
 | 
					                                :key="TagResourceTypeEnum.Machine.value"
 | 
				
			||||||
 | 
					                                :label="TagResourceTypeEnum.Machine.label"
 | 
				
			||||||
 | 
					                                :value="TagResourceTypeEnum.Machine.value"
 | 
				
			||||||
 | 
					                            />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                            <el-option :key="TagResourceTypeEnum.Db.value" :label="TagResourceTypeEnum.Db.label" :value="TagResourceTypeEnum.Db.value" />
 | 
				
			||||||
 | 
					                        </el-select>
 | 
				
			||||||
 | 
					                    </el-form-item>
 | 
				
			||||||
 | 
					                    <el-form-item prop="resourceCode" label="资源编号" required>
 | 
				
			||||||
 | 
					                        <el-input :disabled="form.id" v-model="form.resourceCode" placeholder="请输入资源编号"></el-input>
 | 
				
			||||||
 | 
					                    </el-form-item>
 | 
				
			||||||
 | 
					                </template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                <el-form-item prop="name" label="名称" required>
 | 
				
			||||||
 | 
					                    <el-input :disabled="form.id" v-model="form.name" placeholder="请输入凭证名 (全局唯一)"></el-input>
 | 
				
			||||||
 | 
					                </el-form-item>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                <template v-if="form.ciphertextType != AuthCertCiphertextTypeEnum.Public.value">
 | 
				
			||||||
 | 
					                    <el-form-item prop="username" label="用户名">
 | 
				
			||||||
 | 
					                        <el-input v-model="form.username"></el-input>
 | 
				
			||||||
 | 
					                    </el-form-item>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    <el-form-item v-if="form.ciphertextType == AuthCertCiphertextTypeEnum.Password.value" prop="ciphertext" label="密码">
 | 
				
			||||||
 | 
					                        <el-input type="password" show-password clearable v-model.trim="form.ciphertext" placeholder="请输入密码" autocomplete="new-password">
 | 
				
			||||||
 | 
					                            <template #suffix>
 | 
				
			||||||
 | 
					                                <SvgIcon v-if="form.id" v-auth="'authcert:showciphertext'" @click="getCiphertext" name="search" />
 | 
				
			||||||
 | 
					                            </template>
 | 
				
			||||||
 | 
					                        </el-input>
 | 
				
			||||||
 | 
					                    </el-form-item>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    <el-form-item v-if="form.ciphertextType == AuthCertCiphertextTypeEnum.PrivateKey.value" prop="ciphertext" label="秘钥">
 | 
				
			||||||
 | 
					                        <div class="w100" style="position: relative">
 | 
				
			||||||
 | 
					                            <SvgIcon
 | 
				
			||||||
 | 
					                                v-if="form.id"
 | 
				
			||||||
 | 
					                                v-auth="'authcert:showciphertext'"
 | 
				
			||||||
 | 
					                                @click="getCiphertext"
 | 
				
			||||||
 | 
					                                name="search"
 | 
				
			||||||
 | 
					                                style="position: absolute; top: 5px; right: 5px; cursor: pointer; z-index: 1"
 | 
				
			||||||
 | 
					                            />
 | 
				
			||||||
 | 
					                            <el-input type="textarea" :rows="5" v-model="form.ciphertext" placeholder="请将私钥文件内容拷贝至此"> </el-input>
 | 
				
			||||||
 | 
					                        </div>
 | 
				
			||||||
 | 
					                    </el-form-item>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    <el-form-item v-if="form.ciphertextType == AuthCertCiphertextTypeEnum.PrivateKey.value" prop="passphrase" label="秘钥密码">
 | 
				
			||||||
 | 
					                        <el-input type="password" show-password v-model="form.extra.passphrase"> </el-input>
 | 
				
			||||||
 | 
					                    </el-form-item>
 | 
				
			||||||
 | 
					                </template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                <template v-else>
 | 
				
			||||||
 | 
					                    <el-form-item label="公共凭证">
 | 
				
			||||||
 | 
					                        <el-select default-first-option filterable v-model="form.ciphertext" @change="changePublicAuthCert">
 | 
				
			||||||
 | 
					                            <el-option v-for="item in state.publicAuthCerts" :key="item.name" :label="item.name" :value="item.name">
 | 
				
			||||||
 | 
					                                {{ item.name }}
 | 
				
			||||||
 | 
					                                <el-divider direction="vertical" border-style="dashed" />
 | 
				
			||||||
 | 
					                                {{ item.username }}
 | 
				
			||||||
 | 
					                                <el-divider direction="vertical" border-style="dashed" />
 | 
				
			||||||
 | 
					                                <EnumTag :value="item.ciphertextType" :enums="AuthCertCiphertextTypeEnum" />
 | 
				
			||||||
 | 
					                                <el-divider direction="vertical" border-style="dashed" />
 | 
				
			||||||
 | 
					                                {{ item.remark }}
 | 
				
			||||||
 | 
					                            </el-option>
 | 
				
			||||||
 | 
					                        </el-select>
 | 
				
			||||||
 | 
					                    </el-form-item>
 | 
				
			||||||
 | 
					                </template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                <el-form-item label="备注">
 | 
				
			||||||
 | 
					                    <el-input v-model="form.remark" type="textarea" :rows="2"></el-input>
 | 
				
			||||||
 | 
					                </el-form-item>
 | 
				
			||||||
 | 
					            </el-form>
 | 
				
			||||||
 | 
					            <template #footer>
 | 
				
			||||||
 | 
					                <div class="dialog-footer">
 | 
				
			||||||
 | 
					                    <el-button @click="cancelEdit">取 消</el-button>
 | 
				
			||||||
 | 
					                    <el-button type="primary" :loading="btnLoading" @click="btnOk">确 定</el-button>
 | 
				
			||||||
 | 
					                </div>
 | 
				
			||||||
 | 
					            </template>
 | 
				
			||||||
 | 
					        </el-dialog>
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<script lang="ts" setup>
 | 
				
			||||||
 | 
					import { reactive, ref, toRefs, onMounted, watch, computed } from 'vue';
 | 
				
			||||||
 | 
					import { AuthCertTypeEnum, AuthCertCiphertextTypeEnum } from '../tag/enums';
 | 
				
			||||||
 | 
					import EnumTag from '@/components/enumtag/EnumTag.vue';
 | 
				
			||||||
 | 
					import { resourceAuthCertApi } from '../tag/api';
 | 
				
			||||||
 | 
					import { ResourceCodePattern } from '@/common/pattern';
 | 
				
			||||||
 | 
					import { TagResourceTypeEnum } from '@/common/commonEnum';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const props = defineProps({
 | 
				
			||||||
 | 
					    title: {
 | 
				
			||||||
 | 
					        type: String,
 | 
				
			||||||
 | 
					        default: '凭证保存',
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    authCert: {
 | 
				
			||||||
 | 
					        type: Object,
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    disableCiphertextType: {
 | 
				
			||||||
 | 
					        type: Array,
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    disableType: {
 | 
				
			||||||
 | 
					        type: Array,
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    // 是否为资源编辑该授权凭证,即机器编辑等页面等
 | 
				
			||||||
 | 
					    resourceEdit: {
 | 
				
			||||||
 | 
					        type: Boolean,
 | 
				
			||||||
 | 
					        default: true,
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const DefaultForm = {
 | 
				
			||||||
 | 
					    id: null,
 | 
				
			||||||
 | 
					    name: '',
 | 
				
			||||||
 | 
					    username: '',
 | 
				
			||||||
 | 
					    ciphertextType: AuthCertCiphertextTypeEnum.Password.value,
 | 
				
			||||||
 | 
					    type: AuthCertTypeEnum.Private.value,
 | 
				
			||||||
 | 
					    resourceType: TagResourceTypeEnum.AuthCert.value,
 | 
				
			||||||
 | 
					    resourceCode: '',
 | 
				
			||||||
 | 
					    ciphertext: '',
 | 
				
			||||||
 | 
					    extra: {} as any,
 | 
				
			||||||
 | 
					    remark: '',
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const rules = {
 | 
				
			||||||
 | 
					    name: [
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            required: true,
 | 
				
			||||||
 | 
					            message: '请输入凭证名',
 | 
				
			||||||
 | 
					            trigger: ['change', 'blur'],
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            pattern: ResourceCodePattern.pattern,
 | 
				
			||||||
 | 
					            message: ResourceCodePattern.message,
 | 
				
			||||||
 | 
					            trigger: ['blur'],
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					    ],
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const emit = defineEmits(['confirm', 'cancel']);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const dialogVisible = defineModel<boolean>('visible', { default: false });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const acForm: any = ref(null);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const state = reactive({
 | 
				
			||||||
 | 
					    form: { ...DefaultForm },
 | 
				
			||||||
 | 
					    btnLoading: false,
 | 
				
			||||||
 | 
					    publicAuthCerts: [] as any,
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const showResourceEdit = computed(() => {
 | 
				
			||||||
 | 
					    return state.form.type != AuthCertTypeEnum.Public.value && !props.resourceEdit;
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					onMounted(() => {
 | 
				
			||||||
 | 
					    setForm(props.authCert);
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					watch(
 | 
				
			||||||
 | 
					    () => props.authCert,
 | 
				
			||||||
 | 
					    (val: any) => {
 | 
				
			||||||
 | 
					        setForm(val);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const setForm = (val: any) => {
 | 
				
			||||||
 | 
					    val = { ...val };
 | 
				
			||||||
 | 
					    if (!val.extra) {
 | 
				
			||||||
 | 
					        val.extra = {};
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    state.form = val;
 | 
				
			||||||
 | 
					    if (state.form.ciphertextType == AuthCertCiphertextTypeEnum.Public.value) {
 | 
				
			||||||
 | 
					        getPublicAuthCerts();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const { form, btnLoading } = toRefs(state);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const changeType = (val: any) => {
 | 
				
			||||||
 | 
					    // 如果选择了公共凭证,则需要保证密文类型不能为公共凭证
 | 
				
			||||||
 | 
					    if (val == AuthCertTypeEnum.Public.value && state.form.ciphertextType == AuthCertCiphertextTypeEnum.Public.value) {
 | 
				
			||||||
 | 
					        state.form.ciphertextType = AuthCertCiphertextTypeEnum.Password.value;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const changeCiphertextType = (val: any) => {
 | 
				
			||||||
 | 
					    if (val == AuthCertCiphertextTypeEnum.Public.value) {
 | 
				
			||||||
 | 
					        state.form.type = AuthCertTypeEnum.Private.value;
 | 
				
			||||||
 | 
					        getPublicAuthCerts();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const changePublicAuthCert = (val: string) => {
 | 
				
			||||||
 | 
					    // 使用公共授权凭证名称赋值username
 | 
				
			||||||
 | 
					    state.form.username = val;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const getPublicAuthCerts = async () => {
 | 
				
			||||||
 | 
					    const res = await resourceAuthCertApi.listByQuery.request({
 | 
				
			||||||
 | 
					        type: AuthCertTypeEnum.Public.value,
 | 
				
			||||||
 | 
					        pageNum: 1,
 | 
				
			||||||
 | 
					        pageSize: 100,
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    state.publicAuthCerts = res.list;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const getCiphertext = async () => {
 | 
				
			||||||
 | 
					    const res = await resourceAuthCertApi.detail.request({ name: state.form.name });
 | 
				
			||||||
 | 
					    state.form.ciphertext = res.ciphertext;
 | 
				
			||||||
 | 
					    state.form.extra.passphrase = res.extra?.passphrase;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const cancelEdit = () => {
 | 
				
			||||||
 | 
					    dialogVisible.value = false;
 | 
				
			||||||
 | 
					    emit('cancel');
 | 
				
			||||||
 | 
					    setTimeout(() => {
 | 
				
			||||||
 | 
					        acForm.value?.resetFields();
 | 
				
			||||||
 | 
					        state.form = { ...DefaultForm };
 | 
				
			||||||
 | 
					    }, 300);
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const btnOk = async () => {
 | 
				
			||||||
 | 
					    acForm.value.validate(async (valid: boolean) => {
 | 
				
			||||||
 | 
					        if (valid) {
 | 
				
			||||||
 | 
					            emit('confirm', state.form);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					</script>
 | 
				
			||||||
 | 
					<style lang="scss"></style>
 | 
				
			||||||
@@ -0,0 +1,150 @@
 | 
				
			|||||||
 | 
					<template>
 | 
				
			||||||
 | 
					    <div class="auth-cert-manage">
 | 
				
			||||||
 | 
					        <el-table :data="authCerts" max-height="180" stripe size="small">
 | 
				
			||||||
 | 
					            <el-table-column min-wdith="120px">
 | 
				
			||||||
 | 
					                <template #header>
 | 
				
			||||||
 | 
					                    <el-button v-auth="'authcert:save'" class="ml0" type="primary" circle size="small" icon="Plus" @click="edit(null)"> </el-button>
 | 
				
			||||||
 | 
					                </template>
 | 
				
			||||||
 | 
					                <template #default="scope">
 | 
				
			||||||
 | 
					                    <el-button v-auth="'authcert:save'" @click="edit(scope.row, scope.$index)" type="primary" icon="edit" link></el-button>
 | 
				
			||||||
 | 
					                    <el-button class="ml1" v-auth="'authcert:del'" type="danger" @click="deleteRow(scope.$index)" icon="delete" link></el-button>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    <el-button
 | 
				
			||||||
 | 
					                        title="测试连接"
 | 
				
			||||||
 | 
					                        :loading="props.testConnBtnLoading && scope.$index == state.idx"
 | 
				
			||||||
 | 
					                        :disabled="props.testConnBtnLoading"
 | 
				
			||||||
 | 
					                        class="ml1"
 | 
				
			||||||
 | 
					                        type="success"
 | 
				
			||||||
 | 
					                        @click="testConn(scope.row, scope.$index)"
 | 
				
			||||||
 | 
					                        icon="Link"
 | 
				
			||||||
 | 
					                        link
 | 
				
			||||||
 | 
					                    ></el-button>
 | 
				
			||||||
 | 
					                </template>
 | 
				
			||||||
 | 
					            </el-table-column>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            <el-table-column prop="name" label="名称" show-overflow-tooltip min-width="100px"> </el-table-column>
 | 
				
			||||||
 | 
					            <el-table-column prop="username" label="用户名" min-width="120px" show-overflow-tooltip> </el-table-column>
 | 
				
			||||||
 | 
					            <el-table-column prop="ciphertextType" label="密文类型" width="100px">
 | 
				
			||||||
 | 
					                <template #default="scope">
 | 
				
			||||||
 | 
					                    <EnumTag :value="scope.row.ciphertextType" :enums="AuthCertCiphertextTypeEnum" />
 | 
				
			||||||
 | 
					                </template>
 | 
				
			||||||
 | 
					            </el-table-column>
 | 
				
			||||||
 | 
					            <el-table-column prop="type" label="凭证类型" width="100px">
 | 
				
			||||||
 | 
					                <template #default="scope">
 | 
				
			||||||
 | 
					                    <EnumTag :value="scope.row.type" :enums="AuthCertTypeEnum" />
 | 
				
			||||||
 | 
					                </template>
 | 
				
			||||||
 | 
					            </el-table-column>
 | 
				
			||||||
 | 
					            <el-table-column prop="remark" label="备注" show-overflow-tooltip width="120px"> </el-table-column>
 | 
				
			||||||
 | 
					        </el-table>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        <ResourceAuthCertEdit
 | 
				
			||||||
 | 
					            v-model:visible="state.dvisible"
 | 
				
			||||||
 | 
					            :auth-cert="state.form"
 | 
				
			||||||
 | 
					            @confirm="btnOk"
 | 
				
			||||||
 | 
					            @cancel="cancelEdit"
 | 
				
			||||||
 | 
					            :disable-type="[AuthCertTypeEnum.Public.value]"
 | 
				
			||||||
 | 
					            :disable-ciphertext-type="props.disableCiphertextType"
 | 
				
			||||||
 | 
					        />
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<script lang="ts" setup>
 | 
				
			||||||
 | 
					import { onMounted, reactive } from 'vue';
 | 
				
			||||||
 | 
					import { AuthCertTypeEnum, AuthCertCiphertextTypeEnum } from '../tag/enums';
 | 
				
			||||||
 | 
					import { resourceAuthCertApi } from '../tag/api';
 | 
				
			||||||
 | 
					import EnumTag from '@/components/enumtag/EnumTag.vue';
 | 
				
			||||||
 | 
					import ResourceAuthCertEdit from './ResourceAuthCertEdit.vue';
 | 
				
			||||||
 | 
					import { ElMessage } from 'element-plus';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const props = defineProps({
 | 
				
			||||||
 | 
					    resourceType: { type: Number },
 | 
				
			||||||
 | 
					    resourceCode: { type: String },
 | 
				
			||||||
 | 
					    disableCiphertextType: {
 | 
				
			||||||
 | 
					        type: Array,
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    testConnBtnLoading: { type: Boolean },
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const authCerts = defineModel<any>('modelValue', { required: true, default: [] });
 | 
				
			||||||
 | 
					const emit = defineEmits(['testConn']);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const state = reactive({
 | 
				
			||||||
 | 
					    dvisible: false,
 | 
				
			||||||
 | 
					    params: [] as any,
 | 
				
			||||||
 | 
					    form: {},
 | 
				
			||||||
 | 
					    idx: -1,
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					onMounted(() => {
 | 
				
			||||||
 | 
					    getAuthCerts();
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const getAuthCerts = async () => {
 | 
				
			||||||
 | 
					    if (!props.resourceCode || !props.resourceType) {
 | 
				
			||||||
 | 
					        return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    const res = await resourceAuthCertApi.listByQuery.request({
 | 
				
			||||||
 | 
					        resourceCode: props.resourceCode,
 | 
				
			||||||
 | 
					        resourceType: props.resourceType,
 | 
				
			||||||
 | 
					        pageNum: 1,
 | 
				
			||||||
 | 
					        pageSize: 100,
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    authCerts.value = res.list?.reverse() || [];
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const testConn = async (row: any, idx: number) => {
 | 
				
			||||||
 | 
					    state.idx = idx;
 | 
				
			||||||
 | 
					    emit('testConn', row);
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const edit = (form: any, idx = -1) => {
 | 
				
			||||||
 | 
					    state.idx = idx;
 | 
				
			||||||
 | 
					    if (form) {
 | 
				
			||||||
 | 
					        state.form = form;
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					        state.form = { ciphertextType: AuthCertCiphertextTypeEnum.Password.value, type: AuthCertTypeEnum.Private.value, extra: {} };
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    state.dvisible = true;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const deleteRow = (idx: any) => {
 | 
				
			||||||
 | 
					    authCerts.value.splice(idx, 1);
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const cancelEdit = () => {
 | 
				
			||||||
 | 
					    state.dvisible = false;
 | 
				
			||||||
 | 
					    setTimeout(() => {
 | 
				
			||||||
 | 
					        state.form = {};
 | 
				
			||||||
 | 
					    }, 300);
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const btnOk = async (authCert: any) => {
 | 
				
			||||||
 | 
					    const isEdit = authCert.id;
 | 
				
			||||||
 | 
					    if (!isEdit) {
 | 
				
			||||||
 | 
					        const res = await resourceAuthCertApi.listByQuery.request({
 | 
				
			||||||
 | 
					            name: authCert.name,
 | 
				
			||||||
 | 
					            pageNum: 1,
 | 
				
			||||||
 | 
					            pageSize: 100,
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					        if (res.total) {
 | 
				
			||||||
 | 
					            ElMessage.error('该授权凭证名称已存在');
 | 
				
			||||||
 | 
					            return;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (isEdit || state.idx >= 0) {
 | 
				
			||||||
 | 
					        authCerts.value[state.idx] = authCert;
 | 
				
			||||||
 | 
					        cancelEdit();
 | 
				
			||||||
 | 
					        return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (authCerts.value?.filter((x: any) => x.username == authCert.username || x.name == authCert.name).length > 0) {
 | 
				
			||||||
 | 
					        ElMessage.error('该名称或用户名已存在于该账号列表中');
 | 
				
			||||||
 | 
					        return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    authCerts.value.push(authCert);
 | 
				
			||||||
 | 
					    cancelEdit();
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					</script>
 | 
				
			||||||
 | 
					<style lang="scss"></style>
 | 
				
			||||||
@@ -1,45 +0,0 @@
 | 
				
			|||||||
<template>
 | 
					 | 
				
			||||||
    <div style="display: inline-flex; justify-content: center; align-items: center; cursor: pointer; vertical-align: middle">
 | 
					 | 
				
			||||||
        <el-popover :show-after="500" @show="getTags" placement="top-start" width="230" trigger="hover">
 | 
					 | 
				
			||||||
            <template #reference>
 | 
					 | 
				
			||||||
                <div>
 | 
					 | 
				
			||||||
                    <!-- <el-button type="primary" link size="small">标签</el-button> -->
 | 
					 | 
				
			||||||
                    <SvgIcon name="view" :size="16" color="var(--el-color-primary)" />
 | 
					 | 
				
			||||||
                </div>
 | 
					 | 
				
			||||||
            </template>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            <el-tag effect="plain" v-for="tag in tags" :key="tag" class="ml5" type="success" size="small">{{ tag.tagPath }}</el-tag>
 | 
					 | 
				
			||||||
        </el-popover>
 | 
					 | 
				
			||||||
    </div>
 | 
					 | 
				
			||||||
</template>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
<script lang="ts" setup>
 | 
					 | 
				
			||||||
import { reactive, toRefs } from 'vue';
 | 
					 | 
				
			||||||
import { tagApi } from '../tag/api';
 | 
					 | 
				
			||||||
import SvgIcon from '@/components/svgIcon/index.vue';
 | 
					 | 
				
			||||||
const props = defineProps({
 | 
					 | 
				
			||||||
    resourceCode: {
 | 
					 | 
				
			||||||
        type: [String],
 | 
					 | 
				
			||||||
        required: true,
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    resourceType: {
 | 
					 | 
				
			||||||
        type: [Number],
 | 
					 | 
				
			||||||
        required: true,
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
});
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const state = reactive({
 | 
					 | 
				
			||||||
    tags: [] as any,
 | 
					 | 
				
			||||||
});
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const { tags } = toRefs(state);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const getTags = async () => {
 | 
					 | 
				
			||||||
    state.tags = await tagApi.getTagResources.request({
 | 
					 | 
				
			||||||
        resourceCode: props.resourceCode,
 | 
					 | 
				
			||||||
        resourceType: props.resourceType,
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
</script>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
<style lang="scss"></style>
 | 
					 | 
				
			||||||
							
								
								
									
										33
									
								
								mayfly_go_web/src/views/ops/component/ResourceTags.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								mayfly_go_web/src/views/ops/component/ResourceTags.vue
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,33 @@
 | 
				
			|||||||
 | 
					<template>
 | 
				
			||||||
 | 
					    <div v-if="props.tags">
 | 
				
			||||||
 | 
					        <el-row v-for="(tag, idx) in props.tags?.slice(0, 1)" :key="idx">
 | 
				
			||||||
 | 
					            <TagInfo :tag-path="tag.tagPath" />
 | 
				
			||||||
 | 
					            <span class="ml3">{{ tag.tagPath }}</span>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            <!-- 展示剩余的标签信息 -->
 | 
				
			||||||
 | 
					            <el-popover :show-after="300" v-if="props.tags.length > 1 && idx == 0" placement="top-start" width="230" trigger="hover">
 | 
				
			||||||
 | 
					                <template #reference>
 | 
				
			||||||
 | 
					                    <SvgIcon class="mt5 ml5" color="var(--el-color-primary)" name="MoreFilled" />
 | 
				
			||||||
 | 
					                </template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                <el-row v-for="i in props.tags.slice(1)" :key="i">
 | 
				
			||||||
 | 
					                    <TagInfo :tag-path="i.tagPath" />
 | 
				
			||||||
 | 
					                    <span class="ml3">{{ i.tagPath }}</span>
 | 
				
			||||||
 | 
					                </el-row>
 | 
				
			||||||
 | 
					            </el-popover>
 | 
				
			||||||
 | 
					        </el-row>
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<script lang="ts" setup>
 | 
				
			||||||
 | 
					import SvgIcon from '@/components/svgIcon/index.vue';
 | 
				
			||||||
 | 
					import TagInfo from './TagInfo.vue';
 | 
				
			||||||
 | 
					const props = defineProps({
 | 
				
			||||||
 | 
					    tags: {
 | 
				
			||||||
 | 
					        type: [Array<any>],
 | 
				
			||||||
 | 
					        required: true,
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<style lang="scss"></style>
 | 
				
			||||||
@@ -46,7 +46,7 @@ onMounted(async () => {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
const getSshTunnelMachines = async () => {
 | 
					const getSshTunnelMachines = async () => {
 | 
				
			||||||
    if (state.sshTunnelMachineList.length == 0) {
 | 
					    if (state.sshTunnelMachineList.length == 0) {
 | 
				
			||||||
        const res = await machineApi.list.request({ pageNum: 1, pageSize: 100 });
 | 
					        const res = await machineApi.list.request({ pageNum: 1, pageSize: 100, ssh: 1 });
 | 
				
			||||||
        state.sshTunnelMachineList = res.list;
 | 
					        state.sshTunnelMachineList = res.list;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,7 +1,7 @@
 | 
				
			|||||||
<template>
 | 
					<template>
 | 
				
			||||||
    <div class="tag-tree card pd5">
 | 
					    <div class="card pd5">
 | 
				
			||||||
        <el-scrollbar>
 | 
					        <el-input v-model="filterText" placeholder="输入关键字->搜索已展开节点信息" clearable size="small" class="mb5 w100" />
 | 
				
			||||||
            <el-input v-model="filterText" placeholder="输入关键字->搜索已展开节点信息" clearable size="small" class="mb5 w100" />
 | 
					        <el-scrollbar class="tag-tree">
 | 
				
			||||||
            <el-tree
 | 
					            <el-tree
 | 
				
			||||||
                ref="treeRef"
 | 
					                ref="treeRef"
 | 
				
			||||||
                :highlight-current="true"
 | 
					                :highlight-current="true"
 | 
				
			||||||
@@ -10,14 +10,14 @@
 | 
				
			|||||||
                :props="treeProps"
 | 
					                :props="treeProps"
 | 
				
			||||||
                lazy
 | 
					                lazy
 | 
				
			||||||
                node-key="key"
 | 
					                node-key="key"
 | 
				
			||||||
                :expand-on-click-node="true"
 | 
					                :expand-on-click-node="false"
 | 
				
			||||||
                :filter-node-method="filterNode"
 | 
					                :filter-node-method="filterNode"
 | 
				
			||||||
                @node-click="treeNodeClick"
 | 
					                @node-click="treeNodeClick"
 | 
				
			||||||
                @node-expand="treeNodeClick"
 | 
					                @node-expand="treeNodeClick"
 | 
				
			||||||
                @node-contextmenu="nodeContextmenu"
 | 
					                @node-contextmenu="nodeContextmenu"
 | 
				
			||||||
            >
 | 
					            >
 | 
				
			||||||
                <template #default="{ node, data }">
 | 
					                <template #default="{ node, data }">
 | 
				
			||||||
                    <span>
 | 
					                    <span @dblclick="treeNodeDblclick(data)" :class="data.type.nodeDblclickFunc ? 'none-select' : ''">
 | 
				
			||||||
                        <span v-if="data.type.value == TagTreeNode.TagPath">
 | 
					                        <span v-if="data.type.value == TagTreeNode.TagPath">
 | 
				
			||||||
                            <tag-info :tag-path="data.label" />
 | 
					                            <tag-info :tag-path="data.label" />
 | 
				
			||||||
                        </span>
 | 
					                        </span>
 | 
				
			||||||
@@ -25,7 +25,13 @@
 | 
				
			|||||||
                        <slot v-else :node="node" :data="data" name="prefix"></slot>
 | 
					                        <slot v-else :node="node" :data="data" name="prefix"></slot>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                        <span class="ml3" :title="data.labelRemark">
 | 
					                        <span class="ml3" :title="data.labelRemark">
 | 
				
			||||||
                            <slot name="label" :data="data"> {{ data.label }}</slot>
 | 
					                            <slot name="label" :data="data" v-if="!data.disabled"> {{ data.label }}</slot>
 | 
				
			||||||
 | 
					                            <!-- 禁用状态 -->
 | 
				
			||||||
 | 
					                            <slot name="disabledLabel" :data="data" v-else>
 | 
				
			||||||
 | 
					                                <el-link type="danger" disabled :underline="false">
 | 
				
			||||||
 | 
					                                    {{ `${data.label}` }}
 | 
				
			||||||
 | 
					                                </el-link>
 | 
				
			||||||
 | 
					                            </slot>
 | 
				
			||||||
                        </span>
 | 
					                        </span>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                        <slot :node="node" :data="data" name="suffix"></slot>
 | 
					                        <slot :node="node" :data="data" name="suffix"></slot>
 | 
				
			||||||
@@ -134,16 +140,30 @@ const loadNode = async (node: any, resolve: any) => {
 | 
				
			|||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const treeNodeClick = (data: any) => {
 | 
					const treeNodeClick = (data: any) => {
 | 
				
			||||||
    emit('nodeClick', data);
 | 
					    if (!data.disabled && !data.type.nodeDblclickFunc && data.type.nodeClickFunc) {
 | 
				
			||||||
    if (data.type.nodeClickFunc) {
 | 
					        emit('nodeClick', data);
 | 
				
			||||||
        data.type.nodeClickFunc(data);
 | 
					        data.type.nodeClickFunc(data);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    // 关闭可能存在的右击菜单
 | 
					    // 关闭可能存在的右击菜单
 | 
				
			||||||
    contextmenuRef.value.closeContextmenu();
 | 
					    contextmenuRef.value.closeContextmenu();
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// 树节点双击事件
 | 
				
			||||||
 | 
					const treeNodeDblclick = (data: any) => {
 | 
				
			||||||
 | 
					    // emit('nodeDblick', data);
 | 
				
			||||||
 | 
					    if (!data.disabled && data.type.nodeDblclickFunc) {
 | 
				
			||||||
 | 
					        data.type.nodeDblclickFunc(data);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    // 关闭可能存在的右击菜单
 | 
				
			||||||
 | 
					    contextmenuRef.value.closeContextmenu();
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// 树节点右击事件
 | 
					// 树节点右击事件
 | 
				
			||||||
const nodeContextmenu = (event: any, data: any) => {
 | 
					const nodeContextmenu = (event: any, data: any) => {
 | 
				
			||||||
 | 
					    if (data.disabled) {
 | 
				
			||||||
 | 
					        return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // 加载当前节点是否需要显示右击菜单
 | 
					    // 加载当前节点是否需要显示右击菜单
 | 
				
			||||||
    let items = data.type.contextMenuItems;
 | 
					    let items = data.type.contextMenuItems;
 | 
				
			||||||
    if (!items || items.length == 0) {
 | 
					    if (!items || items.length == 0) {
 | 
				
			||||||
@@ -186,7 +206,7 @@ defineExpose({
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
<style lang="scss" scoped>
 | 
					<style lang="scss" scoped>
 | 
				
			||||||
.tag-tree {
 | 
					.tag-tree {
 | 
				
			||||||
    height: calc(100vh - 108px);
 | 
					    height: calc(100vh - 148px);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    .el-tree {
 | 
					    .el-tree {
 | 
				
			||||||
        display: inline-block;
 | 
					        display: inline-block;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -14,6 +14,9 @@
 | 
				
			|||||||
        v-model="modelValue"
 | 
					        v-model="modelValue"
 | 
				
			||||||
        @change="changeNode"
 | 
					        @change="changeNode"
 | 
				
			||||||
    >
 | 
					    >
 | 
				
			||||||
 | 
					        <template #prefix="{ node, data }">
 | 
				
			||||||
 | 
					            <slot name="iconPrefix" :node="node" :data="data" />
 | 
				
			||||||
 | 
					        </template>
 | 
				
			||||||
        <template #default="{ node, data }">
 | 
					        <template #default="{ node, data }">
 | 
				
			||||||
            <span>
 | 
					            <span>
 | 
				
			||||||
                <span v-if="data.type.value == TagTreeNode.TagPath">
 | 
					                <span v-if="data.type.value == TagTreeNode.TagPath">
 | 
				
			||||||
@@ -33,7 +36,7 @@
 | 
				
			|||||||
</template>
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<script lang="ts" setup>
 | 
					<script lang="ts" setup>
 | 
				
			||||||
import { onMounted, reactive, ref, watch, toRefs } from 'vue';
 | 
					import { onMounted, reactive, ref, toRefs, watch } from 'vue';
 | 
				
			||||||
import { NodeType, TagTreeNode } from './tag';
 | 
					import { NodeType, TagTreeNode } from './tag';
 | 
				
			||||||
import TagInfo from './TagInfo.vue';
 | 
					import TagInfo from './TagInfo.vue';
 | 
				
			||||||
import { tagApi } from '../tag/api';
 | 
					import { tagApi } from '../tag/api';
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -2,13 +2,13 @@
 | 
				
			|||||||
    <div>
 | 
					    <div>
 | 
				
			||||||
        <el-tree-select
 | 
					        <el-tree-select
 | 
				
			||||||
            v-bind="$attrs"
 | 
					            v-bind="$attrs"
 | 
				
			||||||
            v-model="selectTags"
 | 
					            v-model="state.selectTags"
 | 
				
			||||||
            @change="changeTag"
 | 
					            @change="changeTag"
 | 
				
			||||||
            style="width: 100%"
 | 
					            style="width: 100%"
 | 
				
			||||||
            :data="tags"
 | 
					            :data="tags"
 | 
				
			||||||
            placeholder="请选择关联标签"
 | 
					            placeholder="请选择关联标签"
 | 
				
			||||||
            :render-after-expand="true"
 | 
					            :render-after-expand="true"
 | 
				
			||||||
            :default-expanded-keys="[selectTags]"
 | 
					            :default-expanded-keys="[state.selectTags]"
 | 
				
			||||||
            show-checkbox
 | 
					            show-checkbox
 | 
				
			||||||
            node-key="id"
 | 
					            node-key="id"
 | 
				
			||||||
            :props="{
 | 
					            :props="{
 | 
				
			||||||
@@ -40,35 +40,25 @@ import { tagApi } from '../tag/api';
 | 
				
			|||||||
const emit = defineEmits(['update:modelValue', 'changeTag', 'input']);
 | 
					const emit = defineEmits(['update:modelValue', 'changeTag', 'input']);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const props = defineProps({
 | 
					const props = defineProps({
 | 
				
			||||||
    resourceCode: {
 | 
					    selectTags: {
 | 
				
			||||||
        type: [String],
 | 
					        type: [Array<any>],
 | 
				
			||||||
        required: true,
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    resourceType: {
 | 
					 | 
				
			||||||
        type: [Number],
 | 
					 | 
				
			||||||
        required: true,
 | 
					 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const state = reactive({
 | 
					const state = reactive({
 | 
				
			||||||
    tags: [],
 | 
					    tags: [],
 | 
				
			||||||
    // 单选则为id,多选为id数组
 | 
					    // 单选则为id,多选为id数组
 | 
				
			||||||
    selectTags: [],
 | 
					    selectTags: [] as any,
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const { tags, selectTags } = toRefs(state);
 | 
					const { tags } = toRefs(state);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
onMounted(async () => {
 | 
					onMounted(async () => {
 | 
				
			||||||
    if (props.resourceCode) {
 | 
					    if (props.selectTags) {
 | 
				
			||||||
        const resourceTags = await tagApi.getTagResources.request({
 | 
					        state.selectTags = props.selectTags;
 | 
				
			||||||
            resourceCode: props.resourceCode,
 | 
					 | 
				
			||||||
            resourceType: props.resourceType,
 | 
					 | 
				
			||||||
        });
 | 
					 | 
				
			||||||
        state.selectTags = resourceTags.map((x: any) => x.tagId);
 | 
					 | 
				
			||||||
        changeTag();
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    state.tags = await tagApi.getTagTrees.request(null);
 | 
					    state.tags = await tagApi.getTagTrees.request({ type: -1 });
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const changeTag = () => {
 | 
					const changeTag = () => {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -28,6 +28,11 @@ export class TagTreeNode {
 | 
				
			|||||||
     */
 | 
					     */
 | 
				
			||||||
    isLeaf: boolean = false;
 | 
					    isLeaf: boolean = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * 是否禁用状态
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    disabled: boolean = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * 额外需要传递的参数
 | 
					     * 额外需要传递的参数
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
@@ -53,6 +58,11 @@ export class TagTreeNode {
 | 
				
			|||||||
        return this;
 | 
					        return this;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    withDisabled(disabled: boolean) {
 | 
				
			||||||
 | 
					        this.disabled = disabled;
 | 
				
			||||||
 | 
					        return this;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    withParams(params: any) {
 | 
					    withParams(params: any) {
 | 
				
			||||||
        this.params = params;
 | 
					        this.params = params;
 | 
				
			||||||
        return this;
 | 
					        return this;
 | 
				
			||||||
@@ -91,8 +101,14 @@ export class NodeType {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    loadNodesFunc: (parentNode: TagTreeNode) => Promise<TagTreeNode[]>;
 | 
					    loadNodesFunc: (parentNode: TagTreeNode) => Promise<TagTreeNode[]>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * 节点点击事件
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
    nodeClickFunc: (node: TagTreeNode) => void;
 | 
					    nodeClickFunc: (node: TagTreeNode) => void;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // 节点双击事件
 | 
				
			||||||
 | 
					    nodeDblclickFunc: (node: TagTreeNode) => void;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    constructor(value: number) {
 | 
					    constructor(value: number) {
 | 
				
			||||||
        this.value = value;
 | 
					        this.value = value;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@@ -117,6 +133,16 @@ export class NodeType {
 | 
				
			|||||||
        return this;
 | 
					        return this;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * 赋值节点双击事件回调函数
 | 
				
			||||||
 | 
					     * @param func 节点双击事件回调函数
 | 
				
			||||||
 | 
					     * @returns this
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    withNodeDblclickFunc(func: (node: TagTreeNode) => void) {
 | 
				
			||||||
 | 
					        this.nodeDblclickFunc = func;
 | 
				
			||||||
 | 
					        return this;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * 赋值右击菜单按钮选项
 | 
					     * 赋值右击菜单按钮选项
 | 
				
			||||||
     * @param contextMenuItems 右击菜单按钮选项
 | 
					     * @param contextMenuItems 右击菜单按钮选项
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -23,13 +23,16 @@
 | 
				
			|||||||
                </el-form-item>
 | 
					                </el-form-item>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                <el-form-item prop="name" label="任务名称">
 | 
					                <el-form-item prop="name" label="任务名称">
 | 
				
			||||||
                    <el-input v-model.number="state.form.name" type="text" placeholder="任务名称"></el-input>
 | 
					                    <el-input v-model="state.form.name" type="text" placeholder="任务名称"></el-input>
 | 
				
			||||||
                </el-form-item>
 | 
					                </el-form-item>
 | 
				
			||||||
                <el-form-item prop="startTime" label="开始时间">
 | 
					                <el-form-item prop="startTime" label="开始时间">
 | 
				
			||||||
                    <el-date-picker v-model="state.form.startTime" type="datetime" placeholder="开始时间" />
 | 
					                    <el-date-picker v-model="state.form.startTime" type="datetime" placeholder="开始时间" />
 | 
				
			||||||
                </el-form-item>
 | 
					                </el-form-item>
 | 
				
			||||||
                <el-form-item prop="intervalDay" label="备份周期">
 | 
					                <el-form-item prop="intervalDay" label="备份周期(天)">
 | 
				
			||||||
                    <el-input v-model.number="state.form.intervalDay" type="number" placeholder="备份周期(单位:天)"></el-input>
 | 
					                    <el-input v-model.number="state.form.intervalDay" type="number" placeholder="单位:天"></el-input>
 | 
				
			||||||
 | 
					                </el-form-item>
 | 
				
			||||||
 | 
					                <el-form-item prop="maxSaveDays" label="备份历史保留天数">
 | 
				
			||||||
 | 
					                    <el-input v-model.number="state.form.maxSaveDays" type="number" placeholder="0: 永久保留"></el-input>
 | 
				
			||||||
                </el-form-item>
 | 
					                </el-form-item>
 | 
				
			||||||
            </el-form>
 | 
					            </el-form>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -92,6 +95,14 @@ const rules = {
 | 
				
			|||||||
            trigger: ['change', 'blur'],
 | 
					            trigger: ['change', 'blur'],
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
    ],
 | 
					    ],
 | 
				
			||||||
 | 
					    maxSaveDays: [
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            required: true,
 | 
				
			||||||
 | 
					            pattern: /^[0-9]\d*$/,
 | 
				
			||||||
 | 
					            message: '请输入非负整数',
 | 
				
			||||||
 | 
					            trigger: ['change', 'blur'],
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					    ],
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const backupForm: any = ref(null);
 | 
					const backupForm: any = ref(null);
 | 
				
			||||||
@@ -101,10 +112,11 @@ const state = reactive({
 | 
				
			|||||||
        id: 0,
 | 
					        id: 0,
 | 
				
			||||||
        dbId: 0,
 | 
					        dbId: 0,
 | 
				
			||||||
        dbNames: '',
 | 
					        dbNames: '',
 | 
				
			||||||
        name: null as any,
 | 
					        name: '',
 | 
				
			||||||
        intervalDay: null,
 | 
					        intervalDay: 1,
 | 
				
			||||||
        startTime: null as any,
 | 
					        startTime: null as any,
 | 
				
			||||||
        repeated: null as any,
 | 
					        repeated: true,
 | 
				
			||||||
 | 
					        maxSaveDays: 0,
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    btnLoading: false,
 | 
					    btnLoading: false,
 | 
				
			||||||
    dbNamesSelected: [] as any,
 | 
					    dbNamesSelected: [] as any,
 | 
				
			||||||
@@ -137,12 +149,14 @@ const init = (data: any) => {
 | 
				
			|||||||
        state.form.name = data.name;
 | 
					        state.form.name = data.name;
 | 
				
			||||||
        state.form.intervalDay = data.intervalDay;
 | 
					        state.form.intervalDay = data.intervalDay;
 | 
				
			||||||
        state.form.startTime = data.startTime;
 | 
					        state.form.startTime = data.startTime;
 | 
				
			||||||
 | 
					        state.form.maxSaveDays = data.maxSaveDays;
 | 
				
			||||||
    } else {
 | 
					    } else {
 | 
				
			||||||
        state.editOrCreate = false;
 | 
					        state.editOrCreate = false;
 | 
				
			||||||
        state.form.name = '';
 | 
					        state.form.name = '';
 | 
				
			||||||
        state.form.intervalDay = null;
 | 
					        state.form.intervalDay = 1;
 | 
				
			||||||
        const now = new Date();
 | 
					        const now = new Date();
 | 
				
			||||||
        state.form.startTime = new Date(now.getFullYear(), now.getMonth(), now.getDate() + 1);
 | 
					        state.form.startTime = new Date(now.getFullYear(), now.getMonth(), now.getDate() + 1);
 | 
				
			||||||
 | 
					        state.form.maxSaveDays = 0;
 | 
				
			||||||
        getDbNamesWithoutBackup();
 | 
					        getDbNamesWithoutBackup();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										155
									
								
								mayfly_go_web/src/views/ops/db/DbBackupHistoryList.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										155
									
								
								mayfly_go_web/src/views/ops/db/DbBackupHistoryList.vue
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,155 @@
 | 
				
			|||||||
 | 
					<template>
 | 
				
			||||||
 | 
					    <div class="db-backup-history">
 | 
				
			||||||
 | 
					        <page-table
 | 
				
			||||||
 | 
					            height="100%"
 | 
				
			||||||
 | 
					            ref="pageTableRef"
 | 
				
			||||||
 | 
					            :page-api="dbApi.getDbBackupHistories"
 | 
				
			||||||
 | 
					            :show-selection="true"
 | 
				
			||||||
 | 
					            v-model:selection-data="state.selectedData"
 | 
				
			||||||
 | 
					            :searchItems="searchItems"
 | 
				
			||||||
 | 
					            :before-query-fn="beforeQueryFn"
 | 
				
			||||||
 | 
					            v-model:query-form="query"
 | 
				
			||||||
 | 
					            :columns="columns"
 | 
				
			||||||
 | 
					        >
 | 
				
			||||||
 | 
					            <template #dbSelect>
 | 
				
			||||||
 | 
					                <el-select v-model="query.dbName" placeholder="请选择数据库" style="width: 200px" filterable clearable>
 | 
				
			||||||
 | 
					                    <el-option v-for="item in props.dbNames" :key="item" :label="`${item}`" :value="item"> </el-option>
 | 
				
			||||||
 | 
					                </el-select>
 | 
				
			||||||
 | 
					            </template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            <template #tableHeader>
 | 
				
			||||||
 | 
					                <el-button type="primary" icon="back" @click="restoreDbBackupHistory(null)">立即恢复</el-button>
 | 
				
			||||||
 | 
					                <el-button type="danger" icon="delete" @click="deleteDbBackupHistory(null)">删除</el-button>
 | 
				
			||||||
 | 
					            </template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            <template #action="{ data }">
 | 
				
			||||||
 | 
					                <div>
 | 
				
			||||||
 | 
					                    <el-button @click="restoreDbBackupHistory(data)" type="primary" link>立即恢复</el-button>
 | 
				
			||||||
 | 
					                    <el-button @click="deleteDbBackupHistory(data)" type="danger" link>删除</el-button>
 | 
				
			||||||
 | 
					                </div>
 | 
				
			||||||
 | 
					            </template>
 | 
				
			||||||
 | 
					        </page-table>
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<script lang="ts" setup>
 | 
				
			||||||
 | 
					import { toRefs, reactive, Ref, ref } from 'vue';
 | 
				
			||||||
 | 
					import { dbApi } from './api';
 | 
				
			||||||
 | 
					import PageTable from '@/components/pagetable/PageTable.vue';
 | 
				
			||||||
 | 
					import { TableColumn } from '@/components/pagetable';
 | 
				
			||||||
 | 
					import { SearchItem } from '@/components/SearchForm';
 | 
				
			||||||
 | 
					import { ElMessage, ElMessageBox } from 'element-plus';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const pageTableRef: Ref<any> = ref(null);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const props = defineProps({
 | 
				
			||||||
 | 
					    dbId: {
 | 
				
			||||||
 | 
					        type: [Number],
 | 
				
			||||||
 | 
					        required: true,
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    dbNames: {
 | 
				
			||||||
 | 
					        type: [Array<String>],
 | 
				
			||||||
 | 
					        required: true,
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const searchItems = [SearchItem.slot('dbName', '数据库名称', 'dbSelect')];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const columns = [
 | 
				
			||||||
 | 
					    TableColumn.new('dbName', '数据库名称'),
 | 
				
			||||||
 | 
					    TableColumn.new('name', '备份名称'),
 | 
				
			||||||
 | 
					    TableColumn.new('createTime', '创建时间').isTime(),
 | 
				
			||||||
 | 
					    TableColumn.new('lastResult', '恢复结果'),
 | 
				
			||||||
 | 
					    TableColumn.new('lastTime', '恢复时间').isTime(),
 | 
				
			||||||
 | 
					    TableColumn.new('action', '操作').isSlot().setMinWidth(160).fixedRight(),
 | 
				
			||||||
 | 
					];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const emptyQuery = {
 | 
				
			||||||
 | 
					    dbId: 0,
 | 
				
			||||||
 | 
					    dbName: '',
 | 
				
			||||||
 | 
					    pageNum: 1,
 | 
				
			||||||
 | 
					    pageSize: 10,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const state = reactive({
 | 
				
			||||||
 | 
					    data: [],
 | 
				
			||||||
 | 
					    total: 0,
 | 
				
			||||||
 | 
					    query: emptyQuery,
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * 选中的数据
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    selectedData: [],
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const { query } = toRefs(state);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const beforeQueryFn = (query: any) => {
 | 
				
			||||||
 | 
					    query.dbId = props.dbId;
 | 
				
			||||||
 | 
					    return query;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const search = async () => {
 | 
				
			||||||
 | 
					    await pageTableRef.value.search();
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const deleteDbBackupHistory = async (data: any) => {
 | 
				
			||||||
 | 
					    let backupHistoryId: string;
 | 
				
			||||||
 | 
					    if (data) {
 | 
				
			||||||
 | 
					        backupHistoryId = data.id;
 | 
				
			||||||
 | 
					    } else if (state.selectedData.length > 0) {
 | 
				
			||||||
 | 
					        backupHistoryId = state.selectedData.map((x: any) => x.id).join(' ');
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					        ElMessage.error('请选择需要删除的数据库备份历史');
 | 
				
			||||||
 | 
					        return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    await ElMessageBox.confirm(`确定删除 “数据库备份历史” 吗?`, '提示', {
 | 
				
			||||||
 | 
					        confirmButtonText: '确定',
 | 
				
			||||||
 | 
					        cancelButtonText: '取消',
 | 
				
			||||||
 | 
					        type: 'warning',
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    await dbApi.deleteDbBackupHistory.request({ dbId: props.dbId, backupHistoryId: backupHistoryId });
 | 
				
			||||||
 | 
					    await search();
 | 
				
			||||||
 | 
					    ElMessage.success('删除成功');
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const restoreDbBackupHistory = async (data: any) => {
 | 
				
			||||||
 | 
					    let backupHistoryId: string;
 | 
				
			||||||
 | 
					    if (data) {
 | 
				
			||||||
 | 
					        backupHistoryId = data.id;
 | 
				
			||||||
 | 
					    } else if (state.selectedData.length > 0) {
 | 
				
			||||||
 | 
					        const pluralDbNames: string[] = [];
 | 
				
			||||||
 | 
					        const dbNames: Map<string, boolean> = new Map();
 | 
				
			||||||
 | 
					        state.selectedData.forEach((item: any) => {
 | 
				
			||||||
 | 
					            if (!dbNames.has(item.dbName)) {
 | 
				
			||||||
 | 
					                dbNames.set(item.dbName, false);
 | 
				
			||||||
 | 
					                return;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            if (!dbNames.get(item.dbName)) {
 | 
				
			||||||
 | 
					                dbNames.set(item.dbName, true);
 | 
				
			||||||
 | 
					                pluralDbNames.push(item.dbName);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					        if (pluralDbNames.length > 0) {
 | 
				
			||||||
 | 
					            ElMessage.error('多次选择相同数据库:' + pluralDbNames.join(', '));
 | 
				
			||||||
 | 
					            return;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        backupHistoryId = state.selectedData.map((x: any) => x.id).join(' ');
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					        ElMessage.error('请选择需要恢复的数据库备份历史');
 | 
				
			||||||
 | 
					        return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    await ElMessageBox.confirm(`确定从 “数据库备份历史” 中恢复数据库吗?`, '提示', {
 | 
				
			||||||
 | 
					        confirmButtonText: '确定',
 | 
				
			||||||
 | 
					        cancelButtonText: '取消',
 | 
				
			||||||
 | 
					        type: 'warning',
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    await dbApi.restoreDbBackupHistory.request({
 | 
				
			||||||
 | 
					        dbId: props.dbId,
 | 
				
			||||||
 | 
					        backupHistoryId: backupHistoryId,
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    await search();
 | 
				
			||||||
 | 
					    ElMessage.success('成功创建数据库恢复任务');
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					</script>
 | 
				
			||||||
 | 
					<style lang="scss"></style>
 | 
				
			||||||
@@ -21,6 +21,7 @@
 | 
				
			|||||||
                <el-button type="primary" icon="plus" @click="createDbBackup()">添加</el-button>
 | 
					                <el-button type="primary" icon="plus" @click="createDbBackup()">添加</el-button>
 | 
				
			||||||
                <el-button type="primary" icon="video-play" @click="enableDbBackup(null)">启用</el-button>
 | 
					                <el-button type="primary" icon="video-play" @click="enableDbBackup(null)">启用</el-button>
 | 
				
			||||||
                <el-button type="primary" icon="video-pause" @click="disableDbBackup(null)">禁用</el-button>
 | 
					                <el-button type="primary" icon="video-pause" @click="disableDbBackup(null)">禁用</el-button>
 | 
				
			||||||
 | 
					                <el-button type="danger" icon="delete" @click="deleteDbBackup(null)">删除</el-button>
 | 
				
			||||||
            </template>
 | 
					            </template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            <template #action="{ data }">
 | 
					            <template #action="{ data }">
 | 
				
			||||||
@@ -29,6 +30,7 @@
 | 
				
			|||||||
                    <el-button v-if="!data.enabled" @click="enableDbBackup(data)" type="primary" link>启用</el-button>
 | 
					                    <el-button v-if="!data.enabled" @click="enableDbBackup(data)" type="primary" link>启用</el-button>
 | 
				
			||||||
                    <el-button v-if="data.enabled" @click="disableDbBackup(data)" type="primary" link>禁用</el-button>
 | 
					                    <el-button v-if="data.enabled" @click="disableDbBackup(data)" type="primary" link>禁用</el-button>
 | 
				
			||||||
                    <el-button v-if="data.enabled" @click="startDbBackup(data)" type="primary" link>立即备份</el-button>
 | 
					                    <el-button v-if="data.enabled" @click="startDbBackup(data)" type="primary" link>立即备份</el-button>
 | 
				
			||||||
 | 
					                    <el-button @click="deleteDbBackup(data)" type="danger" link>删除</el-button>
 | 
				
			||||||
                </div>
 | 
					                </div>
 | 
				
			||||||
            </template>
 | 
					            </template>
 | 
				
			||||||
        </page-table>
 | 
					        </page-table>
 | 
				
			||||||
@@ -49,7 +51,7 @@ import { dbApi } from './api';
 | 
				
			|||||||
import PageTable from '@/components/pagetable/PageTable.vue';
 | 
					import PageTable from '@/components/pagetable/PageTable.vue';
 | 
				
			||||||
import { TableColumn } from '@/components/pagetable';
 | 
					import { TableColumn } from '@/components/pagetable';
 | 
				
			||||||
import { SearchItem } from '@/components/SearchForm';
 | 
					import { SearchItem } from '@/components/SearchForm';
 | 
				
			||||||
import { ElMessage } from 'element-plus';
 | 
					import { ElMessage, ElMessageBox } from 'element-plus';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const DbBackupEdit = defineAsyncComponent(() => import('./DbBackupEdit.vue'));
 | 
					const DbBackupEdit = defineAsyncComponent(() => import('./DbBackupEdit.vue'));
 | 
				
			||||||
const pageTableRef: Ref<any> = ref(null);
 | 
					const pageTableRef: Ref<any> = ref(null);
 | 
				
			||||||
@@ -72,10 +74,10 @@ const columns = [
 | 
				
			|||||||
    TableColumn.new('name', '任务名称'),
 | 
					    TableColumn.new('name', '任务名称'),
 | 
				
			||||||
    TableColumn.new('startTime', '启动时间').isTime(),
 | 
					    TableColumn.new('startTime', '启动时间').isTime(),
 | 
				
			||||||
    TableColumn.new('intervalDay', '备份周期'),
 | 
					    TableColumn.new('intervalDay', '备份周期'),
 | 
				
			||||||
    TableColumn.new('enabled', '是否启用'),
 | 
					    TableColumn.new('enabledDesc', '是否启用'),
 | 
				
			||||||
    TableColumn.new('lastResult', '执行结果'),
 | 
					    TableColumn.new('lastResult', '执行结果'),
 | 
				
			||||||
    TableColumn.new('lastTime', '执行时间').isTime(),
 | 
					    TableColumn.new('lastTime', '执行时间').isTime(),
 | 
				
			||||||
    TableColumn.new('action', '操作').isSlot().setMinWidth(180).fixedRight(),
 | 
					    TableColumn.new('action', '操作').isSlot().setMinWidth(220).fixedRight(),
 | 
				
			||||||
];
 | 
					];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const emptyQuery = {
 | 
					const emptyQuery = {
 | 
				
			||||||
@@ -168,5 +170,25 @@ const startDbBackup = async (data: any) => {
 | 
				
			|||||||
    await search();
 | 
					    await search();
 | 
				
			||||||
    ElMessage.success('备份任务启动成功');
 | 
					    ElMessage.success('备份任务启动成功');
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const deleteDbBackup = async (data: any) => {
 | 
				
			||||||
 | 
					    let backupId: string;
 | 
				
			||||||
 | 
					    if (data) {
 | 
				
			||||||
 | 
					        backupId = data.id;
 | 
				
			||||||
 | 
					    } else if (state.selectedData.length > 0) {
 | 
				
			||||||
 | 
					        backupId = state.selectedData.map((x: any) => x.id).join(' ');
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					        ElMessage.error('请选择需要删除的数据库备份任务');
 | 
				
			||||||
 | 
					        return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    await ElMessageBox.confirm(`确定删除 “数据库备份任务” 吗?`, '提示', {
 | 
				
			||||||
 | 
					        confirmButtonText: '确定',
 | 
				
			||||||
 | 
					        cancelButtonText: '取消',
 | 
				
			||||||
 | 
					        type: 'warning',
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    await dbApi.deleteDbBackup.request({ dbId: props.dbId, backupId: backupId });
 | 
				
			||||||
 | 
					    await search();
 | 
				
			||||||
 | 
					    ElMessage.success('删除成功');
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
</script>
 | 
					</script>
 | 
				
			||||||
<style lang="scss"></style>
 | 
					<style lang="scss"></style>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -19,8 +19,7 @@
 | 
				
			|||||||
                            }
 | 
					                            }
 | 
				
			||||||
                        "
 | 
					                        "
 | 
				
			||||||
                        multiple
 | 
					                        multiple
 | 
				
			||||||
                        :resource-code="form.code"
 | 
					                        :select-tags="form.tagId"
 | 
				
			||||||
                        :resource-type="TagResourceTypeEnum.Db.value"
 | 
					 | 
				
			||||||
                        style="width: 100%"
 | 
					                        style="width: 100%"
 | 
				
			||||||
                    />
 | 
					                    />
 | 
				
			||||||
                </el-form-item>
 | 
					                </el-form-item>
 | 
				
			||||||
@@ -31,13 +30,14 @@
 | 
				
			|||||||
                        remote
 | 
					                        remote
 | 
				
			||||||
                        :remote-method="getInstances"
 | 
					                        :remote-method="getInstances"
 | 
				
			||||||
                        @change="changeInstance"
 | 
					                        @change="changeInstance"
 | 
				
			||||||
                        v-model="form.instanceId"
 | 
					                        v-model="state.selectInstalce"
 | 
				
			||||||
 | 
					                        value-key="id"
 | 
				
			||||||
                        placeholder="请输入实例名称搜索并选择实例"
 | 
					                        placeholder="请输入实例名称搜索并选择实例"
 | 
				
			||||||
                        filterable
 | 
					                        filterable
 | 
				
			||||||
                        clearable
 | 
					                        clearable
 | 
				
			||||||
                        class="w100"
 | 
					                        class="w100"
 | 
				
			||||||
                    >
 | 
					                    >
 | 
				
			||||||
                        <el-option v-for="item in state.instances" :key="item.id" :label="`${item.name}`" :value="item.id">
 | 
					                        <el-option v-for="item in state.instances" :key="item.id" :label="`${item.name}`" :value="item">
 | 
				
			||||||
                            {{ item.name }}
 | 
					                            {{ item.name }}
 | 
				
			||||||
                            <el-divider direction="vertical" border-style="dashed" />
 | 
					                            <el-divider direction="vertical" border-style="dashed" />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -48,6 +48,26 @@
 | 
				
			|||||||
                    </el-select>
 | 
					                    </el-select>
 | 
				
			||||||
                </el-form-item>
 | 
					                </el-form-item>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                <el-form-item prop="authCertName" label="授权凭证" required>
 | 
				
			||||||
 | 
					                    <el-select @focus="getAuthCerts" @change="changeAuthCert" v-model="form.authCertName" placeholder="请选择授权凭证" filterable>
 | 
				
			||||||
 | 
					                        <el-option v-for="item in state.authCerts" :key="item.id" :label="`${item.name}`" :value="item.name">
 | 
				
			||||||
 | 
					                            {{ item.name }}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                            <el-divider direction="vertical" border-style="dashed" />
 | 
				
			||||||
 | 
					                            {{ item.username }}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                            <el-divider direction="vertical" border-style="dashed" />
 | 
				
			||||||
 | 
					                            <EnumTag :value="item.ciphertextType" :enums="AuthCertCiphertextTypeEnum" />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                            <el-divider direction="vertical" border-style="dashed" />
 | 
				
			||||||
 | 
					                            {{ item.remark }}
 | 
				
			||||||
 | 
					                        </el-option>
 | 
				
			||||||
 | 
					                    </el-select>
 | 
				
			||||||
 | 
					                </el-form-item>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                <el-form-item prop="code" label="编号" required>
 | 
				
			||||||
 | 
					                    <el-input :disabled="form.id" v-model.trim="form.code" placeholder="请输入编号 (数字字母下划线), 不可修改" auto-complete="off"></el-input>
 | 
				
			||||||
 | 
					                </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>
 | 
				
			||||||
@@ -75,6 +95,8 @@
 | 
				
			|||||||
                <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>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                <procdef-select-form-item v-model="form.flowProcdefKey" />
 | 
				
			||||||
            </el-form>
 | 
					            </el-form>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            <template #footer>
 | 
					            <template #footer>
 | 
				
			||||||
@@ -92,8 +114,14 @@ 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 TagTreeSelect from '../component/TagTreeSelect.vue';
 | 
					import TagTreeSelect from '../component/TagTreeSelect.vue';
 | 
				
			||||||
import { TagResourceTypeEnum } from '@/common/commonEnum';
 | 
					 | 
				
			||||||
import type { CheckboxValueType } from 'element-plus';
 | 
					import type { CheckboxValueType } from 'element-plus';
 | 
				
			||||||
 | 
					import ProcdefSelectFormItem from '@/views/flow/components/ProcdefSelectFormItem.vue';
 | 
				
			||||||
 | 
					import { DbType } from '@/views/ops/db/dialect';
 | 
				
			||||||
 | 
					import { ResourceCodePattern } from '@/common/pattern';
 | 
				
			||||||
 | 
					import { resourceAuthCertApi } from '../tag/api';
 | 
				
			||||||
 | 
					import { TagResourceTypeEnum } from '@/common/commonEnum';
 | 
				
			||||||
 | 
					import EnumTag from '@/components/enumtag/EnumTag.vue';
 | 
				
			||||||
 | 
					import { AuthCertCiphertextTypeEnum } from '../tag/enums';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const props = defineProps({
 | 
					const props = defineProps({
 | 
				
			||||||
    visible: {
 | 
					    visible: {
 | 
				
			||||||
@@ -126,7 +154,18 @@ const rules = {
 | 
				
			|||||||
            trigger: ['change', 'blur'],
 | 
					            trigger: ['change', 'blur'],
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
    ],
 | 
					    ],
 | 
				
			||||||
 | 
					    code: [
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            required: true,
 | 
				
			||||||
 | 
					            message: '请输入编码',
 | 
				
			||||||
 | 
					            trigger: ['change', 'blur'],
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            pattern: ResourceCodePattern.pattern,
 | 
				
			||||||
 | 
					            message: ResourceCodePattern.message,
 | 
				
			||||||
 | 
					            trigger: ['blur'],
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					    ],
 | 
				
			||||||
    name: [
 | 
					    name: [
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            required: true,
 | 
					            required: true,
 | 
				
			||||||
@@ -155,6 +194,8 @@ const state = reactive({
 | 
				
			|||||||
    dbNamesSelected: [] as any,
 | 
					    dbNamesSelected: [] as any,
 | 
				
			||||||
    dbNamesFiltered: [] as any,
 | 
					    dbNamesFiltered: [] as any,
 | 
				
			||||||
    filterString: '',
 | 
					    filterString: '',
 | 
				
			||||||
 | 
					    selectInstalce: {} as any,
 | 
				
			||||||
 | 
					    authCerts: [] as any,
 | 
				
			||||||
    form: {
 | 
					    form: {
 | 
				
			||||||
        id: null,
 | 
					        id: null,
 | 
				
			||||||
        tagId: [],
 | 
					        tagId: [],
 | 
				
			||||||
@@ -163,6 +204,8 @@ const state = reactive({
 | 
				
			|||||||
        database: '',
 | 
					        database: '',
 | 
				
			||||||
        remark: '',
 | 
					        remark: '',
 | 
				
			||||||
        instanceId: null as any,
 | 
					        instanceId: null as any,
 | 
				
			||||||
 | 
					        authCertName: '',
 | 
				
			||||||
 | 
					        flowProcdefKey: '',
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    instances: [] as any,
 | 
					    instances: [] as any,
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
@@ -178,7 +221,7 @@ watch(props, async (newValue: any) => {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
    if (newValue.db) {
 | 
					    if (newValue.db) {
 | 
				
			||||||
        state.form = { ...newValue.db };
 | 
					        state.form = { ...newValue.db };
 | 
				
			||||||
 | 
					        state.form.tagId = newValue.db.tags.map((t: any) => t.tagId);
 | 
				
			||||||
        // 将数据库名使用空格切割,获取所有数据库列表
 | 
					        // 将数据库名使用空格切割,获取所有数据库列表
 | 
				
			||||||
        state.dbNamesSelected = newValue.db.database.split(' ');
 | 
					        state.dbNamesSelected = newValue.db.database.split(' ');
 | 
				
			||||||
    } else {
 | 
					    } else {
 | 
				
			||||||
@@ -187,14 +230,34 @@ watch(props, async (newValue: any) => {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const changeInstance = () => {
 | 
					const changeInstance = async () => {
 | 
				
			||||||
    state.dbNamesSelected = [];
 | 
					    state.dbNamesSelected = [];
 | 
				
			||||||
    getAllDatabase();
 | 
					    state.form.instanceId = state.selectInstalce.id;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const getAllDatabase = async () => {
 | 
					const getAuthCerts = async () => {
 | 
				
			||||||
 | 
					    const res = await resourceAuthCertApi.listByQuery.request({
 | 
				
			||||||
 | 
					        resourceCode: state.selectInstalce.code,
 | 
				
			||||||
 | 
					        resourceType: TagResourceTypeEnum.Db.value,
 | 
				
			||||||
 | 
					        pageSize: 100,
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    state.authCerts = res.list || [];
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const changeAuthCert = (val: string) => {
 | 
				
			||||||
 | 
					    getAllDatabase(val);
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const getAllDatabase = async (authCertName: string) => {
 | 
				
			||||||
    if (state.form.instanceId > 0) {
 | 
					    if (state.form.instanceId > 0) {
 | 
				
			||||||
        state.allDatabases = await dbApi.getAllDatabase.request({ instanceId: state.form.instanceId });
 | 
					        let dbs = await dbApi.getAllDatabase.request({ instanceId: state.form.instanceId, authCertName });
 | 
				
			||||||
 | 
					        state.allDatabases = dbs;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // 如果是oracle,且没查出数据库列表,则取实例sid
 | 
				
			||||||
 | 
					        let instance = state.instances.find((item: any) => item.id === state.form.instanceId);
 | 
				
			||||||
 | 
					        if (instance && instance.type === DbType.oracle && dbs.length === 0) {
 | 
				
			||||||
 | 
					            state.allDatabases = [instance.sid];
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -213,8 +276,9 @@ const open = async () => {
 | 
				
			|||||||
    if (state.form.instanceId) {
 | 
					    if (state.form.instanceId) {
 | 
				
			||||||
        // 根据id获取,因为需要回显实例名称
 | 
					        // 根据id获取,因为需要回显实例名称
 | 
				
			||||||
        await getInstances('', state.form.instanceId);
 | 
					        await getInstances('', state.form.instanceId);
 | 
				
			||||||
 | 
					        state.selectInstalce = state.instances[0];
 | 
				
			||||||
 | 
					        await getAllDatabase(state.form.authCertName);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    await getAllDatabase();
 | 
					 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const btnOk = async () => {
 | 
					const btnOk = async () => {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -9,6 +9,7 @@
 | 
				
			|||||||
            :show-selection="true"
 | 
					            :show-selection="true"
 | 
				
			||||||
            v-model:selection-data="state.selectionData"
 | 
					            v-model:selection-data="state.selectionData"
 | 
				
			||||||
            :columns="columns"
 | 
					            :columns="columns"
 | 
				
			||||||
 | 
					            lazy
 | 
				
			||||||
        >
 | 
					        >
 | 
				
			||||||
            <template #instanceSelect>
 | 
					            <template #instanceSelect>
 | 
				
			||||||
                <el-select remote :remote-method="getInstances" v-model="query.instanceId" placeholder="输入并选择实例" filterable clearable>
 | 
					                <el-select remote :remote-method="getInstances" v-model="query.instanceId" placeholder="输入并选择实例" filterable clearable>
 | 
				
			||||||
@@ -39,7 +40,7 @@
 | 
				
			|||||||
            </template>
 | 
					            </template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            <template #tagPath="{ data }">
 | 
					            <template #tagPath="{ data }">
 | 
				
			||||||
                <resource-tag :resource-code="data.code" :resource-type="TagResourceTypeEnum.Db.value" />
 | 
					                <ResourceTags :tags="data.tags" />
 | 
				
			||||||
            </template>
 | 
					            </template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            <template #action="{ data }">
 | 
					            <template #action="{ data }">
 | 
				
			||||||
@@ -61,30 +62,43 @@
 | 
				
			|||||||
                    <template #dropdown>
 | 
					                    <template #dropdown>
 | 
				
			||||||
                        <el-dropdown-menu>
 | 
					                        <el-dropdown-menu>
 | 
				
			||||||
                            <el-dropdown-item :command="{ type: 'detail', data }"> 详情 </el-dropdown-item>
 | 
					                            <el-dropdown-item :command="{ type: 'detail', data }"> 详情 </el-dropdown-item>
 | 
				
			||||||
                            <el-dropdown-item :command="{ type: 'dumpDb', data }" v-if="supportAction('dumpDb', data.type)"> 导出 </el-dropdown-item>
 | 
					                            <el-dropdown-item :command="{ type: 'dumpDb', data }"> 导出 </el-dropdown-item>
 | 
				
			||||||
                            <el-dropdown-item :command="{ type: 'dbBackup', data }" v-if="supportAction('dbBackup', data.type)"> 备份 </el-dropdown-item>
 | 
					                            <el-dropdown-item :command="{ type: 'backupDb', data }" v-if="actionBtns[perms.backupDb] && supportAction('backupDb', data.type)">
 | 
				
			||||||
                            <el-dropdown-item :command="{ type: 'dbRestore', data }" v-if="supportAction('dbRestore', data.type)"> 恢复 </el-dropdown-item>
 | 
					                                备份任务
 | 
				
			||||||
 | 
					                            </el-dropdown-item>
 | 
				
			||||||
 | 
					                            <el-dropdown-item
 | 
				
			||||||
 | 
					                                :command="{ type: 'backupHistory', data }"
 | 
				
			||||||
 | 
					                                v-if="actionBtns[perms.backupDb] && supportAction('backupDb', data.type)"
 | 
				
			||||||
 | 
					                            >
 | 
				
			||||||
 | 
					                                备份历史
 | 
				
			||||||
 | 
					                            </el-dropdown-item>
 | 
				
			||||||
 | 
					                            <el-dropdown-item
 | 
				
			||||||
 | 
					                                :command="{ type: 'restoreDb', data }"
 | 
				
			||||||
 | 
					                                v-if="actionBtns[perms.restoreDb] && supportAction('restoreDb', data.type)"
 | 
				
			||||||
 | 
					                            >
 | 
				
			||||||
 | 
					                                恢复任务
 | 
				
			||||||
 | 
					                            </el-dropdown-item>
 | 
				
			||||||
                        </el-dropdown-menu>
 | 
					                        </el-dropdown-menu>
 | 
				
			||||||
                    </template>
 | 
					                    </template>
 | 
				
			||||||
                </el-dropdown>
 | 
					                </el-dropdown>
 | 
				
			||||||
            </template>
 | 
					            </template>
 | 
				
			||||||
        </page-table>
 | 
					        </page-table>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        <el-dialog width="720px" :title="`${db} 数据库导出`" v-model="exportDialog.visible">
 | 
					        <el-dialog width="750px" :title="`${db} 数据库导出`" v-model="exportDialog.visible">
 | 
				
			||||||
            <el-row justify="space-between">
 | 
					            <el-row justify="space-between">
 | 
				
			||||||
                <el-col :span="9">
 | 
					                <el-col :span="9">
 | 
				
			||||||
                    <el-form-item label="导出内容: ">
 | 
					                    <el-form-item label="导出内容: ">
 | 
				
			||||||
                        <el-checkbox-group v-model="exportDialog.contents" :min="1">
 | 
					                        <el-checkbox-group v-model="exportDialog.contents" :min="1">
 | 
				
			||||||
                            <el-checkbox label="结构" />
 | 
					                            <el-checkbox label="结构" value="结构" />
 | 
				
			||||||
                            <el-checkbox label="数据" />
 | 
					                            <el-checkbox label="数据" value="数据" />
 | 
				
			||||||
                        </el-checkbox-group>
 | 
					                        </el-checkbox-group>
 | 
				
			||||||
                    </el-form-item>
 | 
					                    </el-form-item>
 | 
				
			||||||
                </el-col>
 | 
					                </el-col>
 | 
				
			||||||
                <el-col :span="9">
 | 
					                <el-col :span="9">
 | 
				
			||||||
                    <el-form-item label="扩展名: ">
 | 
					                    <el-form-item label="扩展名: ">
 | 
				
			||||||
                        <el-radio-group v-model="exportDialog.extName">
 | 
					                        <el-radio-group v-model="exportDialog.extName">
 | 
				
			||||||
                            <el-radio label="sql" />
 | 
					                            <el-radio label="sql" value="sql" />
 | 
				
			||||||
                            <el-radio label="gzip" />
 | 
					                            <el-radio label="gzip" value="gzip" />
 | 
				
			||||||
                        </el-radio-group>
 | 
					                        </el-radio-group>
 | 
				
			||||||
                    </el-form-item>
 | 
					                    </el-form-item>
 | 
				
			||||||
                </el-col>
 | 
					                </el-col>
 | 
				
			||||||
@@ -131,6 +145,16 @@
 | 
				
			|||||||
            <db-backup-list :dbId="dbBackupDialog.dbId" :dbNames="dbBackupDialog.dbs" />
 | 
					            <db-backup-list :dbId="dbBackupDialog.dbId" :dbNames="dbBackupDialog.dbs" />
 | 
				
			||||||
        </el-dialog>
 | 
					        </el-dialog>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        <el-dialog
 | 
				
			||||||
 | 
					            width="80%"
 | 
				
			||||||
 | 
					            :title="`${dbBackupHistoryDialog.title} - 数据库备份历史`"
 | 
				
			||||||
 | 
					            :close-on-click-modal="false"
 | 
				
			||||||
 | 
					            :destroy-on-close="true"
 | 
				
			||||||
 | 
					            v-model="dbBackupHistoryDialog.visible"
 | 
				
			||||||
 | 
					        >
 | 
				
			||||||
 | 
					            <db-backup-history-list :dbId="dbBackupHistoryDialog.dbId" :dbNames="dbBackupHistoryDialog.dbs" />
 | 
				
			||||||
 | 
					        </el-dialog>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        <el-dialog
 | 
					        <el-dialog
 | 
				
			||||||
            width="80%"
 | 
					            width="80%"
 | 
				
			||||||
            :title="`${dbRestoreDialog.title} - 数据库恢复`"
 | 
					            :title="`${dbRestoreDialog.title} - 数据库恢复`"
 | 
				
			||||||
@@ -141,27 +165,36 @@
 | 
				
			|||||||
            <db-restore-list :dbId="dbRestoreDialog.dbId" :dbNames="dbRestoreDialog.dbs" />
 | 
					            <db-restore-list :dbId="dbRestoreDialog.dbId" :dbNames="dbRestoreDialog.dbs" />
 | 
				
			||||||
        </el-dialog>
 | 
					        </el-dialog>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        <el-dialog v-model="infoDialog.visible" :before-close="onBeforeCloseInfoDialog" :close-on-click-modal="false">
 | 
					        <el-dialog v-if="infoDialog.visible" v-model="infoDialog.visible" :before-close="onBeforeCloseInfoDialog">
 | 
				
			||||||
            <el-descriptions title="详情" :column="3" border>
 | 
					            <el-descriptions title="详情" :column="3" border>
 | 
				
			||||||
                <!-- <el-descriptions-item :span="3" label="标签路径">{{ infoDialog.data?.tagPath }}</el-descriptions-item> -->
 | 
					 | 
				
			||||||
                <el-descriptions-item :span="2" 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="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="关联标签"><ResourceTags :tags="infoDialog.data.tags" /></el-descriptions-item>
 | 
				
			||||||
                <el-descriptions-item :span="3" label="数据库实例名称">{{ infoDialog.instance?.name }}</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.instance?.host }}</el-descriptions-item>
 | 
				
			||||||
                <el-descriptions-item :span="1" label="端口">{{ infoDialog.instance?.port }}</el-descriptions-item>
 | 
					                <el-descriptions-item :span="1" label="端口">{{ infoDialog.instance?.port }}</el-descriptions-item>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                <el-descriptions-item :span="2" label="用户名">{{ infoDialog.instance?.username }}</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="1" label="类型">
 | 
				
			||||||
 | 
					                    <SvgIcon :name="getDbDialect(infoDialog.instance?.type).getInfo().icon" :size="20" />{{ infoDialog.instance?.type }}
 | 
				
			||||||
 | 
					                </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="3" label="工单流程key">{{ infoDialog.data?.flowProcdefKey }}</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="search" :title="dbEditDialog.title" v-model:visible="dbEditDialog.visible" v-model:db="dbEditDialog.data"></db-edit>
 | 
					        <db-edit @val-change="search()" :title="dbEditDialog.title" v-model:visible="dbEditDialog.visible" v-model:db="dbEditDialog.data"></db-edit>
 | 
				
			||||||
    </div>
 | 
					    </div>
 | 
				
			||||||
</template>
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -173,7 +206,6 @@ import config from '@/common/config';
 | 
				
			|||||||
import { joinClientParams } from '@/common/request';
 | 
					import { joinClientParams } from '@/common/request';
 | 
				
			||||||
import { isTrue } from '@/common/assert';
 | 
					import { isTrue } from '@/common/assert';
 | 
				
			||||||
import { dateFormat } from '@/common/utils/date';
 | 
					import { dateFormat } from '@/common/utils/date';
 | 
				
			||||||
import ResourceTag from '../component/ResourceTag.vue';
 | 
					 | 
				
			||||||
import PageTable from '@/components/pagetable/PageTable.vue';
 | 
					import PageTable from '@/components/pagetable/PageTable.vue';
 | 
				
			||||||
import { TableColumn } from '@/components/pagetable';
 | 
					import { TableColumn } from '@/components/pagetable';
 | 
				
			||||||
import { hasPerms } from '@/components/auth/auth';
 | 
					import { hasPerms } from '@/components/auth/auth';
 | 
				
			||||||
@@ -185,30 +217,49 @@ import { getDbDialect } from './dialect/index';
 | 
				
			|||||||
import { getTagPathSearchItem } from '../component/tag';
 | 
					import { getTagPathSearchItem } from '../component/tag';
 | 
				
			||||||
import { SearchItem } from '@/components/SearchForm';
 | 
					import { SearchItem } from '@/components/SearchForm';
 | 
				
			||||||
import DbBackupList from './DbBackupList.vue';
 | 
					import DbBackupList from './DbBackupList.vue';
 | 
				
			||||||
 | 
					import DbBackupHistoryList from './DbBackupHistoryList.vue';
 | 
				
			||||||
import DbRestoreList from './DbRestoreList.vue';
 | 
					import DbRestoreList from './DbRestoreList.vue';
 | 
				
			||||||
 | 
					import ResourceTags from '../component/ResourceTags.vue';
 | 
				
			||||||
 | 
					import { sleep } from '@/common/utils/loading';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const DbEdit = defineAsyncComponent(() => import('./DbEdit.vue'));
 | 
					const DbEdit = defineAsyncComponent(() => import('./DbEdit.vue'));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const props = defineProps({
 | 
				
			||||||
 | 
					    lazy: {
 | 
				
			||||||
 | 
					        type: [Boolean],
 | 
				
			||||||
 | 
					        default: false,
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const perms = {
 | 
					const perms = {
 | 
				
			||||||
    base: 'db',
 | 
					    base: 'db',
 | 
				
			||||||
    saveDb: 'db:save',
 | 
					    saveDb: 'db:save',
 | 
				
			||||||
    delDb: 'db:del',
 | 
					    delDb: 'db:del',
 | 
				
			||||||
 | 
					    backupDb: 'db:backup',
 | 
				
			||||||
 | 
					    restoreDb: 'db:restore',
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const searchItems = [getTagPathSearchItem(TagResourceTypeEnum.Db.value), SearchItem.slot('instanceId', '实例', 'instanceSelect')];
 | 
					const searchItems = [
 | 
				
			||||||
 | 
					    getTagPathSearchItem(TagResourceTypeEnum.Db.value),
 | 
				
			||||||
 | 
					    SearchItem.slot('instanceId', '实例', 'instanceSelect'),
 | 
				
			||||||
 | 
					    SearchItem.input('code', '编号'),
 | 
				
			||||||
 | 
					];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const columns = ref([
 | 
					const columns = ref([
 | 
				
			||||||
 | 
					    TableColumn.new('tags[0].tagPath', '关联标签').isSlot('tagPath').setAddWidth(20),
 | 
				
			||||||
 | 
					    TableColumn.new('code', '编号'),
 | 
				
			||||||
    TableColumn.new('name', '名称'),
 | 
					    TableColumn.new('name', '名称'),
 | 
				
			||||||
    TableColumn.new('type', '类型').isSlot().setAddWidth(-15).alignCenter(),
 | 
					    TableColumn.new('type', '类型').isSlot().setAddWidth(-15).alignCenter(),
 | 
				
			||||||
    TableColumn.new('instanceName', '实例名'),
 | 
					    TableColumn.new('instanceName', '实例名'),
 | 
				
			||||||
    TableColumn.new('host', 'ip:port').isSlot().setAddWidth(40),
 | 
					    TableColumn.new('host', 'ip:port').isSlot().setAddWidth(40),
 | 
				
			||||||
    TableColumn.new('username', 'username'),
 | 
					    TableColumn.new('username', 'username'),
 | 
				
			||||||
    TableColumn.new('tagPath', '关联标签').isSlot().setAddWidth(10).alignCenter(),
 | 
					    TableColumn.new('flowProcdefKey', '关联流程'),
 | 
				
			||||||
    TableColumn.new('remark', '备注'),
 | 
					    TableColumn.new('remark', '备注'),
 | 
				
			||||||
]);
 | 
					]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// 该用户拥有的的操作列按钮权限
 | 
					// 该用户拥有的的操作列按钮权限
 | 
				
			||||||
const actionBtns = hasPerms([perms.base, perms.saveDb]);
 | 
					// const actionBtns = hasPerms([perms.base, perms.saveDb]);
 | 
				
			||||||
 | 
					const actionBtns = hasPerms(Object.values(perms));
 | 
				
			||||||
const actionColumn = TableColumn.new('action', '操作').isSlot().setMinWidth(220).fixedRight().alignCenter();
 | 
					const actionColumn = TableColumn.new('action', '操作').isSlot().setMinWidth(220).fixedRight().alignCenter();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const route = useRoute();
 | 
					const route = useRoute();
 | 
				
			||||||
@@ -253,6 +304,13 @@ const state = reactive({
 | 
				
			|||||||
        dbs: [],
 | 
					        dbs: [],
 | 
				
			||||||
        dbId: 0,
 | 
					        dbId: 0,
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					    // 数据库备份历史弹框
 | 
				
			||||||
 | 
					    dbBackupHistoryDialog: {
 | 
				
			||||||
 | 
					        title: '',
 | 
				
			||||||
 | 
					        visible: false,
 | 
				
			||||||
 | 
					        dbs: [],
 | 
				
			||||||
 | 
					        dbId: 0,
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
    // 数据库恢复弹框
 | 
					    // 数据库恢复弹框
 | 
				
			||||||
    dbRestoreDialog: {
 | 
					    dbRestoreDialog: {
 | 
				
			||||||
        title: '',
 | 
					        title: '',
 | 
				
			||||||
@@ -285,12 +343,16 @@ const state = reactive({
 | 
				
			|||||||
    },
 | 
					    },
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const { db, selectionData, query, infoDialog, sqlExecLogDialog, exportDialog, dbEditDialog, dbBackupDialog, dbRestoreDialog } = toRefs(state);
 | 
					const { db, selectionData, query, infoDialog, sqlExecLogDialog, exportDialog, dbEditDialog, dbBackupDialog, dbBackupHistoryDialog, dbRestoreDialog } =
 | 
				
			||||||
 | 
					    toRefs(state);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
onMounted(async () => {
 | 
					onMounted(async () => {
 | 
				
			||||||
    if (Object.keys(actionBtns).length > 0) {
 | 
					    if (Object.keys(actionBtns).length > 0) {
 | 
				
			||||||
        columns.value.push(actionColumn);
 | 
					        columns.value.push(actionColumn);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					    if (!props.lazy) {
 | 
				
			||||||
 | 
					        search();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const checkRouteTagPath = (query: any) => {
 | 
					const checkRouteTagPath = (query: any) => {
 | 
				
			||||||
@@ -300,7 +362,10 @@ const checkRouteTagPath = (query: any) => {
 | 
				
			|||||||
    return query;
 | 
					    return query;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const search = async () => {
 | 
					const search = async (tagPath: string = '') => {
 | 
				
			||||||
 | 
					    if (tagPath) {
 | 
				
			||||||
 | 
					        state.query.tagPath = tagPath;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
    pageTableRef.value.search();
 | 
					    pageTableRef.value.search();
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -345,11 +410,15 @@ const handleMoreActionCommand = (commond: any) => {
 | 
				
			|||||||
            onDumpDbs(data);
 | 
					            onDumpDbs(data);
 | 
				
			||||||
            return;
 | 
					            return;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        case 'dbBackup': {
 | 
					        case 'backupDb': {
 | 
				
			||||||
            onShowDbBackupDialog(data);
 | 
					            onShowDbBackupDialog(data);
 | 
				
			||||||
            return;
 | 
					            return;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        case 'dbRestore': {
 | 
					        case 'backupHistory': {
 | 
				
			||||||
 | 
					            onShowDbBackupHistoryDialog(data);
 | 
				
			||||||
 | 
					            return;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        case 'restoreDb': {
 | 
				
			||||||
            onShowDbRestoreDialog(data);
 | 
					            onShowDbRestoreDialog(data);
 | 
				
			||||||
            return;
 | 
					            return;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
@@ -402,6 +471,13 @@ const onShowDbBackupDialog = async (row: any) => {
 | 
				
			|||||||
    state.dbBackupDialog.visible = true;
 | 
					    state.dbBackupDialog.visible = true;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const onShowDbBackupHistoryDialog = async (row: any) => {
 | 
				
			||||||
 | 
					    state.dbBackupHistoryDialog.title = `${row.name}`;
 | 
				
			||||||
 | 
					    state.dbBackupHistoryDialog.dbId = row.id;
 | 
				
			||||||
 | 
					    state.dbBackupHistoryDialog.dbs = row.database.split(' ');
 | 
				
			||||||
 | 
					    state.dbBackupHistoryDialog.visible = true;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const onShowDbRestoreDialog = async (row: any) => {
 | 
					const onShowDbRestoreDialog = async (row: any) => {
 | 
				
			||||||
    state.dbRestoreDialog.title = `${row.name}`;
 | 
					    state.dbRestoreDialog.title = `${row.name}`;
 | 
				
			||||||
    state.dbRestoreDialog.dbId = row.id;
 | 
					    state.dbRestoreDialog.dbId = row.id;
 | 
				
			||||||
@@ -429,9 +505,8 @@ const onDumpDbs = async (row: any) => {
 | 
				
			|||||||
/**
 | 
					/**
 | 
				
			||||||
 * 数据库信息导出
 | 
					 * 数据库信息导出
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
const dumpDbs = () => {
 | 
					const dumpDbs = async () => {
 | 
				
			||||||
    isTrue(state.exportDialog.value.length > 0, '请添加要导出的数据库');
 | 
					    isTrue(state.exportDialog.value.length > 0, '请添加要导出的数据库');
 | 
				
			||||||
    const a = document.createElement('a');
 | 
					 | 
				
			||||||
    let type = 0;
 | 
					    let type = 0;
 | 
				
			||||||
    for (let c of state.exportDialog.contents) {
 | 
					    for (let c of state.exportDialog.contents) {
 | 
				
			||||||
        if (c == '结构') {
 | 
					        if (c == '结构') {
 | 
				
			||||||
@@ -440,13 +515,15 @@ const dumpDbs = () => {
 | 
				
			|||||||
            type += 2;
 | 
					            type += 2;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    a.setAttribute(
 | 
					    for (let db of state.exportDialog.value) {
 | 
				
			||||||
        'href',
 | 
					        const a = document.createElement('a');
 | 
				
			||||||
        `${config.baseApiUrl}/dbs/${state.exportDialog.dbId}/dump?db=${state.exportDialog.value.join(',')}&type=${type}&extName=${
 | 
					        a.setAttribute(
 | 
				
			||||||
            state.exportDialog.extName
 | 
					            'href',
 | 
				
			||||||
        }&${joinClientParams()}`
 | 
					            `${config.baseApiUrl}/dbs/${state.exportDialog.dbId}/dump?db=${db}&type=${type}&extName=${state.exportDialog.extName}&${joinClientParams()}`
 | 
				
			||||||
    );
 | 
					        );
 | 
				
			||||||
    a.click();
 | 
					        a.click();
 | 
				
			||||||
 | 
					        await sleep(500);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
    state.exportDialog.visible = false;
 | 
					    state.exportDialog.visible = false;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -455,10 +532,12 @@ const supportAction = (action: string, dbType: string): boolean => {
 | 
				
			|||||||
    switch (dbType) {
 | 
					    switch (dbType) {
 | 
				
			||||||
        case DbType.mysql:
 | 
					        case DbType.mysql:
 | 
				
			||||||
        case DbType.mariadb:
 | 
					        case DbType.mariadb:
 | 
				
			||||||
            actions = ['dumpDb', 'dbBackup', 'dbRestore'];
 | 
					            actions = ['dumpDb', 'backupDb', 'restoreDb'];
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    return actions.includes(action);
 | 
					    return actions.includes(action);
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					defineExpose({ search });
 | 
				
			||||||
</script>
 | 
					</script>
 | 
				
			||||||
<style lang="scss">
 | 
					<style lang="scss">
 | 
				
			||||||
.db-list {
 | 
					.db-list {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -35,7 +35,13 @@
 | 
				
			|||||||
                        clearable
 | 
					                        clearable
 | 
				
			||||||
                        class="w100"
 | 
					                        class="w100"
 | 
				
			||||||
                    >
 | 
					                    >
 | 
				
			||||||
                        <el-option v-for="item in state.histories" :key="item.id" :label="item.name" :value="item"> </el-option>
 | 
					                        <el-option
 | 
				
			||||||
 | 
					                            v-for="item in state.histories"
 | 
				
			||||||
 | 
					                            :key="item.id"
 | 
				
			||||||
 | 
					                            :label="item.name + (item.binlogFileName ? ' ' : ' 不') + '支持指定时间点恢复'"
 | 
				
			||||||
 | 
					                            :value="item"
 | 
				
			||||||
 | 
					                        >
 | 
				
			||||||
 | 
					                        </el-option>
 | 
				
			||||||
                    </el-select>
 | 
					                    </el-select>
 | 
				
			||||||
                </el-form-item>
 | 
					                </el-form-item>
 | 
				
			||||||
                <el-form-item prop="startTime" label="开始时间">
 | 
					                <el-form-item prop="startTime" label="开始时间">
 | 
				
			||||||
@@ -56,7 +62,7 @@
 | 
				
			|||||||
<script lang="ts" setup>
 | 
					<script lang="ts" setup>
 | 
				
			||||||
import { onMounted, reactive, ref, watch } from 'vue';
 | 
					import { onMounted, reactive, ref, watch } from 'vue';
 | 
				
			||||||
import { dbApi } from './api';
 | 
					import { dbApi } from './api';
 | 
				
			||||||
import { ElMessage } from 'element-plus';
 | 
					import { ElMessage, ElMessageBox } from 'element-plus';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const props = defineProps({
 | 
					const props = defineProps({
 | 
				
			||||||
    data: {
 | 
					    data: {
 | 
				
			||||||
@@ -83,20 +89,30 @@ const visible = defineModel<boolean>('visible', {
 | 
				
			|||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const validatePointInTime = (rule: any, value: any, callback: any) => {
 | 
					const validatePointInTime = (rule: any, value: any, callback: any) => {
 | 
				
			||||||
    if (!state.histories || state.histories.length == 0) {
 | 
					 | 
				
			||||||
        callback(new Error('数据库没有备份记录'));
 | 
					 | 
				
			||||||
        return;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    const history = state.histories[state.histories.length - 1];
 | 
					 | 
				
			||||||
    if (value < new Date(history.createTime)) {
 | 
					 | 
				
			||||||
        callback(new Error('在此之前数据库没有备份记录'));
 | 
					 | 
				
			||||||
        return;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    if (value > new Date()) {
 | 
					    if (value > new Date()) {
 | 
				
			||||||
        callback(new Error('恢复时间点晚于当前时间'));
 | 
					        callback(new Error('恢复时间点晚于当前时间'));
 | 
				
			||||||
        return;
 | 
					        return;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    callback();
 | 
					    if (!state.histories || state.histories.length == 0) {
 | 
				
			||||||
 | 
					        callback(new Error('数据库没有备份记录'));
 | 
				
			||||||
 | 
					        return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    let last = null;
 | 
				
			||||||
 | 
					    for (const history of state.histories) {
 | 
				
			||||||
 | 
					        if (!history.binlogFileName || history.binlogFileName.length === 0) {
 | 
				
			||||||
 | 
					            break;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        if (new Date(history.createTime) < value) {
 | 
				
			||||||
 | 
					            callback();
 | 
				
			||||||
 | 
					            return;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        last = history;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    if (!last) {
 | 
				
			||||||
 | 
					        callback(new Error('现有数据库备份不支持指定时间恢复'));
 | 
				
			||||||
 | 
					        return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    callback(last.name + ' 之前的数据库备份不支持指定时间恢复');
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const rules = {
 | 
					const rules = {
 | 
				
			||||||
@@ -110,7 +126,6 @@ const rules = {
 | 
				
			|||||||
    pointInTime: [
 | 
					    pointInTime: [
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            required: true,
 | 
					            required: true,
 | 
				
			||||||
            // message: '请选择恢复时间点',
 | 
					 | 
				
			||||||
            validator: validatePointInTime,
 | 
					            validator: validatePointInTime,
 | 
				
			||||||
            trigger: ['change', 'blur'],
 | 
					            trigger: ['change', 'blur'],
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
@@ -146,7 +161,7 @@ const state = reactive({
 | 
				
			|||||||
        id: 0,
 | 
					        id: 0,
 | 
				
			||||||
        dbId: 0,
 | 
					        dbId: 0,
 | 
				
			||||||
        dbName: null as any,
 | 
					        dbName: null as any,
 | 
				
			||||||
        intervalDay: 1,
 | 
					        intervalDay: 0,
 | 
				
			||||||
        startTime: null as any,
 | 
					        startTime: null as any,
 | 
				
			||||||
        repeated: null as any,
 | 
					        repeated: null as any,
 | 
				
			||||||
        dbBackupId: null as any,
 | 
					        dbBackupId: null as any,
 | 
				
			||||||
@@ -218,7 +233,8 @@ const init = async (data: any) => {
 | 
				
			|||||||
    } else {
 | 
					    } else {
 | 
				
			||||||
        state.form.dbName = '';
 | 
					        state.form.dbName = '';
 | 
				
			||||||
        state.editOrCreate = false;
 | 
					        state.editOrCreate = false;
 | 
				
			||||||
        state.form.intervalDay = 1;
 | 
					        state.form.intervalDay = 0;
 | 
				
			||||||
 | 
					        state.form.repeated = false;
 | 
				
			||||||
        state.form.pointInTime = new Date();
 | 
					        state.form.pointInTime = new Date();
 | 
				
			||||||
        state.form.startTime = new Date();
 | 
					        state.form.startTime = new Date();
 | 
				
			||||||
        state.histories = [];
 | 
					        state.histories = [];
 | 
				
			||||||
@@ -237,6 +253,12 @@ const getDbNamesWithoutRestore = async () => {
 | 
				
			|||||||
const btnOk = async () => {
 | 
					const btnOk = async () => {
 | 
				
			||||||
    restoreForm.value.validate(async (valid: any) => {
 | 
					    restoreForm.value.validate(async (valid: any) => {
 | 
				
			||||||
        if (valid) {
 | 
					        if (valid) {
 | 
				
			||||||
 | 
					            await ElMessageBox.confirm(`确定恢复数据库吗?`, '提示', {
 | 
				
			||||||
 | 
					                confirmButtonText: '确定',
 | 
				
			||||||
 | 
					                cancelButtonText: '取消',
 | 
				
			||||||
 | 
					                type: 'warning',
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            if (state.restoreMode == 'point-in-time') {
 | 
					            if (state.restoreMode == 'point-in-time') {
 | 
				
			||||||
                state.form.dbBackupId = 0;
 | 
					                state.form.dbBackupId = 0;
 | 
				
			||||||
                state.form.dbBackupHistoryId = 0;
 | 
					                state.form.dbBackupHistoryId = 0;
 | 
				
			||||||
@@ -245,13 +267,14 @@ const btnOk = async () => {
 | 
				
			|||||||
                state.form.pointInTime = null;
 | 
					                state.form.pointInTime = null;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            state.form.repeated = false;
 | 
					            state.form.repeated = false;
 | 
				
			||||||
 | 
					            state.form.intervalDay = 0;
 | 
				
			||||||
            const reqForm = { ...state.form };
 | 
					            const reqForm = { ...state.form };
 | 
				
			||||||
            let api = dbApi.createDbRestore;
 | 
					            let api = dbApi.createDbRestore;
 | 
				
			||||||
            if (props.data) {
 | 
					            if (props.data) {
 | 
				
			||||||
                api = dbApi.saveDbRestore;
 | 
					                api = dbApi.saveDbRestore;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            api.request(reqForm).then(() => {
 | 
					            api.request(reqForm).then(() => {
 | 
				
			||||||
                ElMessage.success('保存成功');
 | 
					                ElMessage.success('成功创建数据库恢复任务');
 | 
				
			||||||
                emit('val-change', state.form);
 | 
					                emit('val-change', state.form);
 | 
				
			||||||
                state.btnLoading = true;
 | 
					                state.btnLoading = true;
 | 
				
			||||||
                setTimeout(() => {
 | 
					                setTimeout(() => {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -21,12 +21,14 @@
 | 
				
			|||||||
                <el-button type="primary" icon="plus" @click="createDbRestore()">添加</el-button>
 | 
					                <el-button type="primary" icon="plus" @click="createDbRestore()">添加</el-button>
 | 
				
			||||||
                <el-button type="primary" icon="video-play" @click="enableDbRestore(null)">启用</el-button>
 | 
					                <el-button type="primary" icon="video-play" @click="enableDbRestore(null)">启用</el-button>
 | 
				
			||||||
                <el-button type="primary" icon="video-pause" @click="disableDbRestore(null)">禁用</el-button>
 | 
					                <el-button type="primary" icon="video-pause" @click="disableDbRestore(null)">禁用</el-button>
 | 
				
			||||||
 | 
					                <el-button type="danger" icon="delete" @click="deleteDbRestore(null)">删除</el-button>
 | 
				
			||||||
            </template>
 | 
					            </template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            <template #action="{ data }">
 | 
					            <template #action="{ data }">
 | 
				
			||||||
                <el-button @click="showDbRestore(data)" type="primary" link>详情</el-button>
 | 
					                <el-button @click="showDbRestore(data)" type="primary" link>详情</el-button>
 | 
				
			||||||
                <el-button @click="enableDbRestore(data)" v-if="!data.enabled" type="primary" link>启用</el-button>
 | 
					                <el-button @click="enableDbRestore(data)" v-if="!data.enabled" type="primary" link>启用</el-button>
 | 
				
			||||||
                <el-button @click="disableDbRestore(data)" v-if="data.enabled" type="primary" link>禁用</el-button>
 | 
					                <el-button @click="disableDbRestore(data)" v-if="data.enabled" type="primary" link>禁用</el-button>
 | 
				
			||||||
 | 
					                <el-button @click="deleteDbRestore(data)" type="danger" link>删除</el-button>
 | 
				
			||||||
            </template>
 | 
					            </template>
 | 
				
			||||||
        </page-table>
 | 
					        </page-table>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -49,7 +51,7 @@
 | 
				
			|||||||
                    infoDialog.data.dbBackupHistoryName
 | 
					                    infoDialog.data.dbBackupHistoryName
 | 
				
			||||||
                }}</el-descriptions-item>
 | 
					                }}</el-descriptions-item>
 | 
				
			||||||
                <el-descriptions-item :span="1" label="开始时间">{{ dateFormat(infoDialog.data.startTime) }}</el-descriptions-item>
 | 
					                <el-descriptions-item :span="1" label="开始时间">{{ dateFormat(infoDialog.data.startTime) }}</el-descriptions-item>
 | 
				
			||||||
                <el-descriptions-item :span="1" label="是否启用">{{ infoDialog.data.enabled }}</el-descriptions-item>
 | 
					                <el-descriptions-item :span="1" label="是否启用">{{ infoDialog.data.enabledDesc }}</el-descriptions-item>
 | 
				
			||||||
                <el-descriptions-item :span="1" label="执行时间">{{ dateFormat(infoDialog.data.lastTime) }}</el-descriptions-item>
 | 
					                <el-descriptions-item :span="1" label="执行时间">{{ dateFormat(infoDialog.data.lastTime) }}</el-descriptions-item>
 | 
				
			||||||
                <el-descriptions-item :span="1" label="执行结果">{{ infoDialog.data.lastResult }}</el-descriptions-item>
 | 
					                <el-descriptions-item :span="1" label="执行结果">{{ infoDialog.data.lastResult }}</el-descriptions-item>
 | 
				
			||||||
            </el-descriptions>
 | 
					            </el-descriptions>
 | 
				
			||||||
@@ -63,7 +65,7 @@ import { dbApi } from './api';
 | 
				
			|||||||
import PageTable from '@/components/pagetable/PageTable.vue';
 | 
					import PageTable from '@/components/pagetable/PageTable.vue';
 | 
				
			||||||
import { TableColumn } from '@/components/pagetable';
 | 
					import { TableColumn } from '@/components/pagetable';
 | 
				
			||||||
import { SearchItem } from '@/components/SearchForm';
 | 
					import { SearchItem } from '@/components/SearchForm';
 | 
				
			||||||
import { ElMessage } from 'element-plus';
 | 
					import { ElMessage, ElMessageBox } from 'element-plus';
 | 
				
			||||||
import { dateFormat } from '@/common/utils/date';
 | 
					import { dateFormat } from '@/common/utils/date';
 | 
				
			||||||
const DbRestoreEdit = defineAsyncComponent(() => import('./DbRestoreEdit.vue'));
 | 
					const DbRestoreEdit = defineAsyncComponent(() => import('./DbRestoreEdit.vue'));
 | 
				
			||||||
const pageTableRef: Ref<any> = ref(null);
 | 
					const pageTableRef: Ref<any> = ref(null);
 | 
				
			||||||
@@ -85,7 +87,7 @@ const searchItems = [SearchItem.slot('dbName', '数据库名称', 'dbSelect')];
 | 
				
			|||||||
const columns = [
 | 
					const columns = [
 | 
				
			||||||
    TableColumn.new('dbName', '数据库名称'),
 | 
					    TableColumn.new('dbName', '数据库名称'),
 | 
				
			||||||
    TableColumn.new('startTime', '启动时间').isTime(),
 | 
					    TableColumn.new('startTime', '启动时间').isTime(),
 | 
				
			||||||
    TableColumn.new('enabled', '是否启用'),
 | 
					    TableColumn.new('enabledDesc', '是否启用'),
 | 
				
			||||||
    TableColumn.new('lastTime', '执行时间').isTime(),
 | 
					    TableColumn.new('lastTime', '执行时间').isTime(),
 | 
				
			||||||
    TableColumn.new('lastResult', '执行结果'),
 | 
					    TableColumn.new('lastResult', '执行结果'),
 | 
				
			||||||
    TableColumn.new('action', '操作').isSlot().setMinWidth(220).fixedRight().alignCenter(),
 | 
					    TableColumn.new('action', '操作').isSlot().setMinWidth(220).fixedRight().alignCenter(),
 | 
				
			||||||
@@ -135,19 +137,39 @@ const createDbRestore = async () => {
 | 
				
			|||||||
    state.dbRestoreEditDialog.visible = true;
 | 
					    state.dbRestoreEditDialog.visible = true;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const deleteDbRestore = async (data: any) => {
 | 
				
			||||||
 | 
					    let restoreId: string;
 | 
				
			||||||
 | 
					    if (data) {
 | 
				
			||||||
 | 
					        restoreId = data.id;
 | 
				
			||||||
 | 
					    } else if (state.selectedData.length > 0) {
 | 
				
			||||||
 | 
					        restoreId = state.selectedData.map((x: any) => x.id).join(' ');
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					        ElMessage.error('请选择需要删除的数据库恢复任务');
 | 
				
			||||||
 | 
					        return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    await ElMessageBox.confirm(`确定删除 “数据库恢复任务” 吗?`, '提示', {
 | 
				
			||||||
 | 
					        confirmButtonText: '确定',
 | 
				
			||||||
 | 
					        cancelButtonText: '取消',
 | 
				
			||||||
 | 
					        type: 'warning',
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    await dbApi.deleteDbRestore.request({ dbId: props.dbId, restoreId: restoreId });
 | 
				
			||||||
 | 
					    await search();
 | 
				
			||||||
 | 
					    ElMessage.success('删除成功');
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const showDbRestore = async (data: any) => {
 | 
					const showDbRestore = async (data: any) => {
 | 
				
			||||||
    state.infoDialog.data = data;
 | 
					    state.infoDialog.data = data;
 | 
				
			||||||
    state.infoDialog.visible = true;
 | 
					    state.infoDialog.visible = true;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const enableDbRestore = async (data: any) => {
 | 
					const enableDbRestore = async (data: any) => {
 | 
				
			||||||
    let restoreId: String;
 | 
					    let restoreId: string;
 | 
				
			||||||
    if (data) {
 | 
					    if (data) {
 | 
				
			||||||
        restoreId = data.id;
 | 
					        restoreId = data.id;
 | 
				
			||||||
    } else if (state.selectedData.length > 0) {
 | 
					    } else if (state.selectedData.length > 0) {
 | 
				
			||||||
        restoreId = state.selectedData.map((x: any) => x.id).join(' ');
 | 
					        restoreId = state.selectedData.map((x: any) => x.id).join(' ');
 | 
				
			||||||
    } else {
 | 
					    } else {
 | 
				
			||||||
        ElMessage.error('请选择需要启用的恢复任务');
 | 
					        ElMessage.error('请选择需要启用的数据库恢复任务');
 | 
				
			||||||
        return;
 | 
					        return;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    await dbApi.enableDbRestore.request({ dbId: props.dbId, restoreId: restoreId });
 | 
					    await dbApi.enableDbRestore.request({ dbId: props.dbId, restoreId: restoreId });
 | 
				
			||||||
@@ -156,13 +178,13 @@ const enableDbRestore = async (data: any) => {
 | 
				
			|||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const disableDbRestore = async (data: any) => {
 | 
					const disableDbRestore = async (data: any) => {
 | 
				
			||||||
    let restoreId: String;
 | 
					    let restoreId: string;
 | 
				
			||||||
    if (data) {
 | 
					    if (data) {
 | 
				
			||||||
        restoreId = data.id;
 | 
					        restoreId = data.id;
 | 
				
			||||||
    } else if (state.selectedData.length > 0) {
 | 
					    } else if (state.selectedData.length > 0) {
 | 
				
			||||||
        restoreId = state.selectedData.map((x: any) => x.id).join(' ');
 | 
					        restoreId = state.selectedData.map((x: any) => x.id).join(' ');
 | 
				
			||||||
    } else {
 | 
					    } else {
 | 
				
			||||||
        ElMessage.error('请选择需要禁用的恢复任务');
 | 
					        ElMessage.error('请选择需要禁用的数据库恢复任务');
 | 
				
			||||||
        return;
 | 
					        return;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    await dbApi.disableDbRestore.request({ dbId: props.dbId, restoreId: restoreId });
 | 
					    await dbApi.disableDbRestore.request({ dbId: props.dbId, restoreId: restoreId });
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -17,7 +17,10 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
            <template #action="{ data }">
 | 
					            <template #action="{ data }">
 | 
				
			||||||
                <el-link
 | 
					                <el-link
 | 
				
			||||||
                    v-if="data.type == DbSqlExecTypeEnum.Update.value || data.type == DbSqlExecTypeEnum.Delete.value"
 | 
					                    v-if="
 | 
				
			||||||
 | 
					                        data.status == DbSqlExecStatusEnum.Success.value &&
 | 
				
			||||||
 | 
					                        (data.type == DbSqlExecTypeEnum.Update.value || data.type == DbSqlExecTypeEnum.Delete.value)
 | 
				
			||||||
 | 
					                    "
 | 
				
			||||||
                    type="primary"
 | 
					                    type="primary"
 | 
				
			||||||
                    plain
 | 
					                    plain
 | 
				
			||||||
                    size="small"
 | 
					                    size="small"
 | 
				
			||||||
@@ -36,9 +39,9 @@
 | 
				
			|||||||
</template>
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<script lang="ts" setup>
 | 
					<script lang="ts" setup>
 | 
				
			||||||
import { toRefs, watch, reactive, onMounted, Ref, ref } from 'vue';
 | 
					import { onMounted, reactive, Ref, ref, toRefs, watch } from 'vue';
 | 
				
			||||||
import { dbApi } from './api';
 | 
					import { dbApi } from './api';
 | 
				
			||||||
import { DbSqlExecTypeEnum } from './enums';
 | 
					import { DbSqlExecTypeEnum, DbSqlExecStatusEnum } from './enums';
 | 
				
			||||||
import PageTable from '@/components/pagetable/PageTable.vue';
 | 
					import PageTable from '@/components/pagetable/PageTable.vue';
 | 
				
			||||||
import { TableColumn } from '@/components/pagetable';
 | 
					import { TableColumn } from '@/components/pagetable';
 | 
				
			||||||
import { SearchItem } from '@/components/SearchForm';
 | 
					import { SearchItem } from '@/components/SearchForm';
 | 
				
			||||||
@@ -66,9 +69,11 @@ const columns = ref([
 | 
				
			|||||||
    TableColumn.new('type', '类型').typeTag(DbSqlExecTypeEnum).setAddWidth(10),
 | 
					    TableColumn.new('type', '类型').typeTag(DbSqlExecTypeEnum).setAddWidth(10),
 | 
				
			||||||
    TableColumn.new('creator', '执行人'),
 | 
					    TableColumn.new('creator', '执行人'),
 | 
				
			||||||
    TableColumn.new('sql', 'SQL').canBeautify(),
 | 
					    TableColumn.new('sql', 'SQL').canBeautify(),
 | 
				
			||||||
    TableColumn.new('oldValue', '原值').canBeautify(),
 | 
					 | 
				
			||||||
    TableColumn.new('createTime', '执行时间').isTime(),
 | 
					 | 
				
			||||||
    TableColumn.new('remark', '备注'),
 | 
					    TableColumn.new('remark', '备注'),
 | 
				
			||||||
 | 
					    TableColumn.new('status', '执行状态').typeTag(DbSqlExecStatusEnum),
 | 
				
			||||||
 | 
					    TableColumn.new('res', '执行结果'),
 | 
				
			||||||
 | 
					    TableColumn.new('createTime', '执行时间').isTime(),
 | 
				
			||||||
 | 
					    TableColumn.new('oldValue', '原值').canBeautify(),
 | 
				
			||||||
    TableColumn.new('action', '操作').isSlot().setMinWidth(90).fixedRight().alignCenter(),
 | 
					    TableColumn.new('action', '操作').isSlot().setMinWidth(90).fixedRight().alignCenter(),
 | 
				
			||||||
]);
 | 
					]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -80,6 +85,7 @@ const state = reactive({
 | 
				
			|||||||
        dbId: 0,
 | 
					        dbId: 0,
 | 
				
			||||||
        db: '',
 | 
					        db: '',
 | 
				
			||||||
        table: '',
 | 
					        table: '',
 | 
				
			||||||
 | 
					        status: [DbSqlExecStatusEnum.Success.value, DbSqlExecStatusEnum.Fail.value].join(','),
 | 
				
			||||||
        type: null,
 | 
					        type: null,
 | 
				
			||||||
        pageNum: 1,
 | 
					        pageNum: 1,
 | 
				
			||||||
        pageSize: 10,
 | 
					        pageSize: 10,
 | 
				
			||||||
@@ -120,6 +126,12 @@ const onShowRollbackSql = async (sqlExecLog: any) => {
 | 
				
			|||||||
    const primaryKey = getPrimaryKey(columns);
 | 
					    const primaryKey = getPrimaryKey(columns);
 | 
				
			||||||
    const oldValue = JSON.parse(sqlExecLog.oldValue);
 | 
					    const oldValue = JSON.parse(sqlExecLog.oldValue);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let schema = '';
 | 
				
			||||||
 | 
					    let dbArr = sqlExecLog.db.split('/');
 | 
				
			||||||
 | 
					    if (dbArr.length == 2) {
 | 
				
			||||||
 | 
					        schema = dbArr[1] + '.';
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const rollbackSqls = [];
 | 
					    const rollbackSqls = [];
 | 
				
			||||||
    if (sqlExecLog.type == DbSqlExecTypeEnum.Update.value) {
 | 
					    if (sqlExecLog.type == DbSqlExecTypeEnum.Update.value) {
 | 
				
			||||||
        for (let ov of oldValue) {
 | 
					        for (let ov of oldValue) {
 | 
				
			||||||
@@ -130,7 +142,7 @@ const onShowRollbackSql = async (sqlExecLog: any) => {
 | 
				
			|||||||
                }
 | 
					                }
 | 
				
			||||||
                setItems.push(`${key} = ${wrapValue(ov[key])}`);
 | 
					                setItems.push(`${key} = ${wrapValue(ov[key])}`);
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            rollbackSqls.push(`UPDATE ${sqlExecLog.table} SET ${setItems.join(', ')} WHERE ${primaryKey} = ${wrapValue(ov[primaryKey])};`);
 | 
					            rollbackSqls.push(`UPDATE ${schema}${sqlExecLog.table} SET ${setItems.join(', ')} WHERE ${primaryKey} = ${wrapValue(ov[primaryKey])};`);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    } else if (sqlExecLog.type == DbSqlExecTypeEnum.Delete.value) {
 | 
					    } else if (sqlExecLog.type == DbSqlExecTypeEnum.Delete.value) {
 | 
				
			||||||
        const columnNames = columns.map((c: any) => c.columnName);
 | 
					        const columnNames = columns.map((c: any) => c.columnName);
 | 
				
			||||||
@@ -139,7 +151,7 @@ const onShowRollbackSql = async (sqlExecLog: any) => {
 | 
				
			|||||||
            for (let column of columnNames) {
 | 
					            for (let column of columnNames) {
 | 
				
			||||||
                values.push(wrapValue(ov[column]));
 | 
					                values.push(wrapValue(ov[column]));
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            rollbackSqls.push(`INSERT INTO ${sqlExecLog.table} (${columnNames.join(', ')}) VALUES (${values.join(', ')});`);
 | 
					            rollbackSqls.push(`INSERT INTO ${schema}${sqlExecLog.table} (${columnNames.join(', ')}) VALUES (${values.join(', ')});`);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -148,7 +160,7 @@ const onShowRollbackSql = async (sqlExecLog: any) => {
 | 
				
			|||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const getPrimaryKey = (columns: any) => {
 | 
					const getPrimaryKey = (columns: any) => {
 | 
				
			||||||
    const col = columns.find((c: any) => c.columnKey == 'PRI');
 | 
					    const col = columns.find((c: any) => c.isPrimaryKey);
 | 
				
			||||||
    if (col) {
 | 
					    if (col) {
 | 
				
			||||||
        return col.columnName;
 | 
					        return col.columnName;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										331
									
								
								mayfly_go_web/src/views/ops/db/DbTransferEdit.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										331
									
								
								mayfly_go_web/src/views/ops/db/DbTransferEdit.vue
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,331 @@
 | 
				
			|||||||
 | 
					<template>
 | 
				
			||||||
 | 
					    <div class="db-transfer-edit">
 | 
				
			||||||
 | 
					        <el-dialog
 | 
				
			||||||
 | 
					            :title="title"
 | 
				
			||||||
 | 
					            v-model="dialogVisible"
 | 
				
			||||||
 | 
					            :before-close="cancel"
 | 
				
			||||||
 | 
					            :close-on-click-modal="false"
 | 
				
			||||||
 | 
					            :close-on-press-escape="false"
 | 
				
			||||||
 | 
					            :destroy-on-close="true"
 | 
				
			||||||
 | 
					            width="850px"
 | 
				
			||||||
 | 
					        >
 | 
				
			||||||
 | 
					            <el-form :model="form" ref="dbForm" :rules="rules" label-width="auto">
 | 
				
			||||||
 | 
					                <el-tabs v-model="tabActiveName">
 | 
				
			||||||
 | 
					                    <el-tab-pane label="基本信息" :name="basicTab">
 | 
				
			||||||
 | 
					                        <el-form-item prop="srcDbId" label="源数据库" required>
 | 
				
			||||||
 | 
					                            <db-select-tree
 | 
				
			||||||
 | 
					                                placeholder="请选择源数据库"
 | 
				
			||||||
 | 
					                                v-model:db-id="form.srcDbId"
 | 
				
			||||||
 | 
					                                v-model:inst-name="form.srcInstName"
 | 
				
			||||||
 | 
					                                v-model:db-name="form.srcDbName"
 | 
				
			||||||
 | 
					                                v-model:tag-path="form.srcTagPath"
 | 
				
			||||||
 | 
					                                v-model:db-type="form.srcDbType"
 | 
				
			||||||
 | 
					                                @select-db="onSelectSrcDb"
 | 
				
			||||||
 | 
					                            />
 | 
				
			||||||
 | 
					                        </el-form-item>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                        <el-form-item prop="targetDbId" label="目标数据库" required>
 | 
				
			||||||
 | 
					                            <db-select-tree
 | 
				
			||||||
 | 
					                                placeholder="请选择目标数据库"
 | 
				
			||||||
 | 
					                                v-model:db-id="form.targetDbId"
 | 
				
			||||||
 | 
					                                v-model:inst-name="form.targetInstName"
 | 
				
			||||||
 | 
					                                v-model:db-name="form.targetDbName"
 | 
				
			||||||
 | 
					                                v-model:tag-path="form.targetTagPath"
 | 
				
			||||||
 | 
					                                v-model:db-type="form.targetDbType"
 | 
				
			||||||
 | 
					                                @select-db="onSelectTargetDb"
 | 
				
			||||||
 | 
					                            />
 | 
				
			||||||
 | 
					                        </el-form-item>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                        <el-form-item prop="strategy" label="迁移策略" required>
 | 
				
			||||||
 | 
					                            <el-select v-model="form.strategy" filterable placeholder="迁移策略">
 | 
				
			||||||
 | 
					                                <el-option label="全量" :value="1" />
 | 
				
			||||||
 | 
					                                <el-option label="增量(暂不可用)" disabled :value="2" />
 | 
				
			||||||
 | 
					                            </el-select>
 | 
				
			||||||
 | 
					                        </el-form-item>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                        <el-form-item prop="nameCase" label="转换表、字段名" required>
 | 
				
			||||||
 | 
					                            <el-select v-model="form.nameCase">
 | 
				
			||||||
 | 
					                                <el-option label="无" :value="1" />
 | 
				
			||||||
 | 
					                                <el-option label="大写" :value="2" />
 | 
				
			||||||
 | 
					                                <el-option label="小写" :value="3" />
 | 
				
			||||||
 | 
					                            </el-select>
 | 
				
			||||||
 | 
					                        </el-form-item>
 | 
				
			||||||
 | 
					                        <el-form-item prop="deleteTable" label="创建前删除表" required>
 | 
				
			||||||
 | 
					                            <el-select v-model="form.deleteTable">
 | 
				
			||||||
 | 
					                                <el-option label="是" :value="1" />
 | 
				
			||||||
 | 
					                                <el-option label="否" :value="2" />
 | 
				
			||||||
 | 
					                            </el-select>
 | 
				
			||||||
 | 
					                        </el-form-item>
 | 
				
			||||||
 | 
					                    </el-tab-pane>
 | 
				
			||||||
 | 
					                    <el-tab-pane label="数据库对象" :name="tableTab" :disabled="!baseFieldCompleted">
 | 
				
			||||||
 | 
					                        <el-form-item>
 | 
				
			||||||
 | 
					                            <el-input v-model="state.filterSrcTableText" style="width: 240px" placeholder="过滤表" />
 | 
				
			||||||
 | 
					                        </el-form-item>
 | 
				
			||||||
 | 
					                        <el-form-item>
 | 
				
			||||||
 | 
					                            <el-tree
 | 
				
			||||||
 | 
					                                ref="srcTreeRef"
 | 
				
			||||||
 | 
					                                style="width: 760px; max-height: 400px; overflow-y: auto"
 | 
				
			||||||
 | 
					                                default-expand-all
 | 
				
			||||||
 | 
					                                :expand-on-click-node="false"
 | 
				
			||||||
 | 
					                                :data="state.srcTableTree"
 | 
				
			||||||
 | 
					                                node-key="id"
 | 
				
			||||||
 | 
					                                show-checkbox
 | 
				
			||||||
 | 
					                                @check-change="handleSrcTableCheckChange"
 | 
				
			||||||
 | 
					                                :filter-node-method="filterSrcTableTreeNode"
 | 
				
			||||||
 | 
					                            />
 | 
				
			||||||
 | 
					                        </el-form-item>
 | 
				
			||||||
 | 
					                    </el-tab-pane>
 | 
				
			||||||
 | 
					                </el-tabs>
 | 
				
			||||||
 | 
					            </el-form>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            <template #footer>
 | 
				
			||||||
 | 
					                <div>
 | 
				
			||||||
 | 
					                    <el-button @click="cancel()">取 消</el-button>
 | 
				
			||||||
 | 
					                    <el-button type="primary" :loading="saveBtnLoading" @click="btnOk">确 定</el-button>
 | 
				
			||||||
 | 
					                </div>
 | 
				
			||||||
 | 
					            </template>
 | 
				
			||||||
 | 
					        </el-dialog>
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<script lang="ts" setup>
 | 
				
			||||||
 | 
					import { computed, nextTick, reactive, ref, toRefs, watch } from 'vue';
 | 
				
			||||||
 | 
					import { dbApi } from './api';
 | 
				
			||||||
 | 
					import { ElMessage } from 'element-plus';
 | 
				
			||||||
 | 
					import DbSelectTree from '@/views/ops/db/component/DbSelectTree.vue';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const props = defineProps({
 | 
				
			||||||
 | 
					    data: {
 | 
				
			||||||
 | 
					        type: [Boolean, Object],
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    title: {
 | 
				
			||||||
 | 
					        type: String,
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					//定义事件
 | 
				
			||||||
 | 
					const emit = defineEmits(['update:visible', 'cancel', 'val-change']);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const dialogVisible = defineModel<boolean>('visible', { default: false });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const rules = {};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const dbForm: any = ref(null);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const basicTab = 'basic';
 | 
				
			||||||
 | 
					const tableTab = 'table';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type FormData = {
 | 
				
			||||||
 | 
					    id?: number;
 | 
				
			||||||
 | 
					    srcDbId?: number;
 | 
				
			||||||
 | 
					    srcDbName?: string;
 | 
				
			||||||
 | 
					    srcDbType?: string;
 | 
				
			||||||
 | 
					    srcInstName?: string;
 | 
				
			||||||
 | 
					    srcTagPath?: string;
 | 
				
			||||||
 | 
					    srcTableNames?: string;
 | 
				
			||||||
 | 
					    targetDbId?: number;
 | 
				
			||||||
 | 
					    targetInstName?: string;
 | 
				
			||||||
 | 
					    targetDbName?: string;
 | 
				
			||||||
 | 
					    targetTagPath?: string;
 | 
				
			||||||
 | 
					    targetDbType?: string;
 | 
				
			||||||
 | 
					    strategy: 1 | 2;
 | 
				
			||||||
 | 
					    nameCase: 1 | 2 | 3;
 | 
				
			||||||
 | 
					    deleteTable?: 1 | 2;
 | 
				
			||||||
 | 
					    checkedKeys: string;
 | 
				
			||||||
 | 
					    runningState: 1 | 2;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const basicFormData = {
 | 
				
			||||||
 | 
					    strategy: 1,
 | 
				
			||||||
 | 
					    nameCase: 1,
 | 
				
			||||||
 | 
					    deleteTable: 1,
 | 
				
			||||||
 | 
					    checkedKeys: '',
 | 
				
			||||||
 | 
					    runningState: 1,
 | 
				
			||||||
 | 
					} as FormData;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const srcTableList = ref<{ tableName: string; tableComment: string }[]>([]);
 | 
				
			||||||
 | 
					const srcTableListDisabled = ref(false);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const defaultKeys = ['tab-check', 'all', 'table-list'];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const state = reactive({
 | 
				
			||||||
 | 
					    tabActiveName: 'basic',
 | 
				
			||||||
 | 
					    form: basicFormData,
 | 
				
			||||||
 | 
					    submitForm: {} as any,
 | 
				
			||||||
 | 
					    srcTableFields: [] as string[],
 | 
				
			||||||
 | 
					    targetColumnList: [] as any[],
 | 
				
			||||||
 | 
					    filterSrcTableText: '',
 | 
				
			||||||
 | 
					    srcTableTree: [
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            id: 'tab-check',
 | 
				
			||||||
 | 
					            label: '表',
 | 
				
			||||||
 | 
					            children: [
 | 
				
			||||||
 | 
					                { id: 'all', label: '全部表(*)' },
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    id: 'table-list',
 | 
				
			||||||
 | 
					                    label: '自定义',
 | 
				
			||||||
 | 
					                    disabled: srcTableListDisabled,
 | 
				
			||||||
 | 
					                    children: [] as any[],
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					            ],
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					    ],
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const { tabActiveName, form, submitForm } = toRefs(state);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const { isFetching: saveBtnLoading, execute: saveExec } = dbApi.saveDbTransferTask.useApi(submitForm);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// 基础字段信息是否填写完整
 | 
				
			||||||
 | 
					const baseFieldCompleted = computed(() => {
 | 
				
			||||||
 | 
					    return state.form.srcDbId && state.form.targetDbId && state.form.targetDbName;
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					watch(dialogVisible, async (newValue: boolean) => {
 | 
				
			||||||
 | 
					    if (!newValue) {
 | 
				
			||||||
 | 
					        return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    state.tabActiveName = 'basic';
 | 
				
			||||||
 | 
					    const propsData = props.data as any;
 | 
				
			||||||
 | 
					    if (!propsData?.id) {
 | 
				
			||||||
 | 
					        let d = {} as FormData;
 | 
				
			||||||
 | 
					        Object.assign(d, basicFormData);
 | 
				
			||||||
 | 
					        state.form = d;
 | 
				
			||||||
 | 
					        await nextTick(() => {
 | 
				
			||||||
 | 
					            srcTreeRef.value.setCheckedKeys([]);
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					        return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    state.form = props.data as FormData;
 | 
				
			||||||
 | 
					    let { srcDbId, targetDbId } = state.form;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    //  初始化src数据源
 | 
				
			||||||
 | 
					    if (srcDbId) {
 | 
				
			||||||
 | 
					        // 通过tagPath查询实例列表
 | 
				
			||||||
 | 
					        const dbInfoRes = await dbApi.dbs.request({ id: srcDbId });
 | 
				
			||||||
 | 
					        const db = dbInfoRes.list[0];
 | 
				
			||||||
 | 
					        // 初始化实例
 | 
				
			||||||
 | 
					        db.databases = db.database?.split(' ').sort() || [];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (srcDbId && state.form.srcDbName) {
 | 
				
			||||||
 | 
					            await loadDbTables(srcDbId, state.form.srcDbName);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    //  初始化target数据源
 | 
				
			||||||
 | 
					    if (targetDbId) {
 | 
				
			||||||
 | 
					        // 通过tagPath查询实例列表
 | 
				
			||||||
 | 
					        const dbInfoRes = await dbApi.dbs.request({ id: targetDbId });
 | 
				
			||||||
 | 
					        const db = dbInfoRes.list[0];
 | 
				
			||||||
 | 
					        // 初始化实例
 | 
				
			||||||
 | 
					        db.databases = db.database?.split(' ').sort() || [];
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // 初始化勾选迁移表
 | 
				
			||||||
 | 
					    srcTreeRef.value.setCheckedKeys(state.form.checkedKeys.split(','));
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					watch(
 | 
				
			||||||
 | 
					    () => state.filterSrcTableText,
 | 
				
			||||||
 | 
					    (val) => {
 | 
				
			||||||
 | 
					        srcTreeRef.value!.filter(val);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const onSelectSrcDb = async (params: any) => {
 | 
				
			||||||
 | 
					    //  初始化数据源
 | 
				
			||||||
 | 
					    params.databases = params.dbs; // 数据源里需要这个值
 | 
				
			||||||
 | 
					    await loadDbTables(params.id, params.db);
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const onSelectTargetDb = async (params: any) => {
 | 
				
			||||||
 | 
					    console.log(params);
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const loadDbTables = async (dbId: number, db: string) => {
 | 
				
			||||||
 | 
					    // 加载db下的表
 | 
				
			||||||
 | 
					    srcTableList.value = await dbApi.tableInfos.request({ id: dbId, db });
 | 
				
			||||||
 | 
					    handleLoadSrcTableTree();
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const handleSrcTableCheckChange = (data: { id: string; name: string }, checked: boolean) => {
 | 
				
			||||||
 | 
					    if (data.id === 'all') {
 | 
				
			||||||
 | 
					        srcTableListDisabled.value = checked;
 | 
				
			||||||
 | 
					        if (checked) {
 | 
				
			||||||
 | 
					            state.form.checkedKeys = 'all';
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            state.form.checkedKeys = '';
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    if (data.id && (data.id + '').startsWith('list-item')) {
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const filterSrcTableTreeNode = (value: string, data: any) => {
 | 
				
			||||||
 | 
					    if (!value) return true;
 | 
				
			||||||
 | 
					    return data.label.includes(value);
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const handleLoadSrcTableTree = () => {
 | 
				
			||||||
 | 
					    state.srcTableTree[0].children[1].children = srcTableList.value.map((item) => {
 | 
				
			||||||
 | 
					        return {
 | 
				
			||||||
 | 
					            id: item.tableName,
 | 
				
			||||||
 | 
					            label: item.tableName + (item.tableComment && '-' + item.tableComment),
 | 
				
			||||||
 | 
					            disabled: srcTableListDisabled,
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const getReqForm = async () => {
 | 
				
			||||||
 | 
					    return { ...state.form };
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const srcTreeRef = ref();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const getCheckedKeys = () => {
 | 
				
			||||||
 | 
					    let checks = srcTreeRef.value!.getCheckedKeys(false);
 | 
				
			||||||
 | 
					    if (checks.indexOf('all') >= 0) {
 | 
				
			||||||
 | 
					        return ['all'];
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    return checks.filter((item: any) => !defaultKeys.includes(item));
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const btnOk = async () => {
 | 
				
			||||||
 | 
					    dbForm.value.validate(async (valid: boolean) => {
 | 
				
			||||||
 | 
					        if (!valid) {
 | 
				
			||||||
 | 
					            ElMessage.error('请正确填写信息');
 | 
				
			||||||
 | 
					            return false;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        state.submitForm = await getReqForm();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        let checkedKeys = getCheckedKeys();
 | 
				
			||||||
 | 
					        if (checkedKeys.length > 0) {
 | 
				
			||||||
 | 
					            state.submitForm.checkedKeys = checkedKeys.join(',');
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (!state.submitForm.checkedKeys) {
 | 
				
			||||||
 | 
					            ElMessage.error('请选择需要迁移的表');
 | 
				
			||||||
 | 
					            return false;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        await saveExec();
 | 
				
			||||||
 | 
					        ElMessage.success('保存成功');
 | 
				
			||||||
 | 
					        emit('val-change', state.form);
 | 
				
			||||||
 | 
					        cancel();
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const cancel = () => {
 | 
				
			||||||
 | 
					    dialogVisible.value = false;
 | 
				
			||||||
 | 
					    emit('cancel');
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					</script>
 | 
				
			||||||
 | 
					<style lang="scss">
 | 
				
			||||||
 | 
					.db-transfer-edit {
 | 
				
			||||||
 | 
					    .el-select {
 | 
				
			||||||
 | 
					        width: 100%;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					</style>
 | 
				
			||||||
							
								
								
									
										196
									
								
								mayfly_go_web/src/views/ops/db/DbTransferList.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										196
									
								
								mayfly_go_web/src/views/ops/db/DbTransferList.vue
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,196 @@
 | 
				
			|||||||
 | 
					<template>
 | 
				
			||||||
 | 
					    <div class="db-list">
 | 
				
			||||||
 | 
					        <page-table
 | 
				
			||||||
 | 
					            ref="pageTableRef"
 | 
				
			||||||
 | 
					            :page-api="dbApi.dbTransferTasks"
 | 
				
			||||||
 | 
					            :searchItems="searchItems"
 | 
				
			||||||
 | 
					            v-model:query-form="query"
 | 
				
			||||||
 | 
					            :show-selection="true"
 | 
				
			||||||
 | 
					            v-model:selection-data="state.selectionData"
 | 
				
			||||||
 | 
					            :columns="columns"
 | 
				
			||||||
 | 
					        >
 | 
				
			||||||
 | 
					            <template #tableHeader>
 | 
				
			||||||
 | 
					                <el-button v-auth="perms.save" type="primary" icon="plus" @click="edit(false)">添加</el-button>
 | 
				
			||||||
 | 
					                <el-button v-auth="perms.del" :disabled="selectionData.length < 1" @click="del()" type="danger" icon="delete">删除</el-button>
 | 
				
			||||||
 | 
					            </template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            <template #srcDb="{ data }">
 | 
				
			||||||
 | 
					                <el-tooltip :content="`${data.srcTagPath} > ${data.srcInstName} > ${data.srcDbName}`">
 | 
				
			||||||
 | 
					                    <span>
 | 
				
			||||||
 | 
					                        <SvgIcon :name="getDbDialect(data.srcDbType).getInfo().icon" :size="18" />
 | 
				
			||||||
 | 
					                        {{ data.srcInstName }}
 | 
				
			||||||
 | 
					                    </span>
 | 
				
			||||||
 | 
					                </el-tooltip>
 | 
				
			||||||
 | 
					            </template>
 | 
				
			||||||
 | 
					            <template #targetDb="{ data }">
 | 
				
			||||||
 | 
					                <el-tooltip :content="`${data.targetTagPath} > ${data.targetInstName} > ${data.targetDbName}`">
 | 
				
			||||||
 | 
					                    <span>
 | 
				
			||||||
 | 
					                        <SvgIcon :name="getDbDialect(data.targetDbType).getInfo().icon" :size="18" />
 | 
				
			||||||
 | 
					                        {{ data.targetInstName }}
 | 
				
			||||||
 | 
					                    </span>
 | 
				
			||||||
 | 
					                </el-tooltip>
 | 
				
			||||||
 | 
					            </template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            <template #action="{ data }">
 | 
				
			||||||
 | 
					                <!-- 删除、启停用、编辑 -->
 | 
				
			||||||
 | 
					                <el-button v-if="actionBtns[perms.save]" @click="edit(data)" type="primary" link>编辑</el-button>
 | 
				
			||||||
 | 
					                <el-button v-if="actionBtns[perms.log]" type="primary" link @click="log(data)">日志</el-button>
 | 
				
			||||||
 | 
					                <el-button v-if="data.runningState === 1" @click="stop(data.id)" type="danger" link>停止</el-button>
 | 
				
			||||||
 | 
					                <el-button v-if="actionBtns[perms.run] && data.runningState !== 1" type="primary" link @click="reRun(data)">运行</el-button>
 | 
				
			||||||
 | 
					            </template>
 | 
				
			||||||
 | 
					        </page-table>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        <db-transfer-edit @val-change="search" :title="editDialog.title" v-model:visible="editDialog.visible" v-model:data="editDialog.data" />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        <TerminalLog v-model:log-id="logsDialog.logId" v-model:visible="logsDialog.visible" :title="logsDialog.title" />
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<script lang="ts" setup>
 | 
				
			||||||
 | 
					import { defineAsyncComponent, onMounted, reactive, ref, Ref, toRefs } from 'vue';
 | 
				
			||||||
 | 
					import { ElMessage, ElMessageBox } from 'element-plus';
 | 
				
			||||||
 | 
					import { dbApi } from './api';
 | 
				
			||||||
 | 
					import PageTable from '@/components/pagetable/PageTable.vue';
 | 
				
			||||||
 | 
					import { TableColumn } from '@/components/pagetable';
 | 
				
			||||||
 | 
					import { hasPerms } from '@/components/auth/auth';
 | 
				
			||||||
 | 
					import { SearchItem } from '@/components/SearchForm';
 | 
				
			||||||
 | 
					import { getDbDialect } from '@/views/ops/db/dialect';
 | 
				
			||||||
 | 
					import { DbTransferRunningStateEnum } from './enums';
 | 
				
			||||||
 | 
					import TerminalLog from '@/components/terminal/TerminalLog.vue';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const DbTransferEdit = defineAsyncComponent(() => import('./DbTransferEdit.vue'));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const perms = {
 | 
				
			||||||
 | 
					    save: 'db:transfer:save',
 | 
				
			||||||
 | 
					    del: 'db:transfer:del',
 | 
				
			||||||
 | 
					    status: 'db:transfer:status',
 | 
				
			||||||
 | 
					    log: 'db:transfer:log',
 | 
				
			||||||
 | 
					    run: 'db:transfer:run',
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const searchItems = [SearchItem.input('name', '名称')];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const columns = ref([
 | 
				
			||||||
 | 
					    TableColumn.new('srcDb', '源库').setMinWidth(200).isSlot(),
 | 
				
			||||||
 | 
					    TableColumn.new('targetDb', '目标库').setMinWidth(200).isSlot(),
 | 
				
			||||||
 | 
					    TableColumn.new('runningState', '执行状态').typeTag(DbTransferRunningStateEnum),
 | 
				
			||||||
 | 
					    TableColumn.new('creator', '创建人'),
 | 
				
			||||||
 | 
					    TableColumn.new('createTime', '创建时间').isTime(),
 | 
				
			||||||
 | 
					    TableColumn.new('modifier', '修改人'),
 | 
				
			||||||
 | 
					    TableColumn.new('updateTime', '修改时间').isTime(),
 | 
				
			||||||
 | 
					]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// 该用户拥有的的操作列按钮权限
 | 
				
			||||||
 | 
					const actionBtns = hasPerms([perms.save, perms.del, perms.status, perms.log, perms.run]);
 | 
				
			||||||
 | 
					const actionWidth = ((actionBtns[perms.save] ? 1 : 0) + (actionBtns[perms.log] ? 1 : 0) + (actionBtns[perms.run] ? 1 : 0)) * 55;
 | 
				
			||||||
 | 
					const actionColumn = TableColumn.new('action', '操作').isSlot().setMinWidth(actionWidth).fixedRight().alignCenter();
 | 
				
			||||||
 | 
					const pageTableRef: Ref<any> = ref(null);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const state = reactive({
 | 
				
			||||||
 | 
					    row: {},
 | 
				
			||||||
 | 
					    dbId: 0,
 | 
				
			||||||
 | 
					    db: '',
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * 选中的数据
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    selectionData: [],
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * 查询条件
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    query: {
 | 
				
			||||||
 | 
					        name: null,
 | 
				
			||||||
 | 
					        pageNum: 1,
 | 
				
			||||||
 | 
					        pageSize: 0,
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    editDialog: {
 | 
				
			||||||
 | 
					        visible: false,
 | 
				
			||||||
 | 
					        data: null as any,
 | 
				
			||||||
 | 
					        title: '新增数据数据迁移任务',
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    logsDialog: {
 | 
				
			||||||
 | 
					        logId: 0,
 | 
				
			||||||
 | 
					        title: '数据库迁移日志',
 | 
				
			||||||
 | 
					        visible: false,
 | 
				
			||||||
 | 
					        data: null as any,
 | 
				
			||||||
 | 
					        running: false,
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const { selectionData, query, editDialog, logsDialog } = toRefs(state);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					onMounted(async () => {
 | 
				
			||||||
 | 
					    if (Object.keys(actionBtns).length > 0) {
 | 
				
			||||||
 | 
					        columns.value.push(actionColumn);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const search = () => {
 | 
				
			||||||
 | 
					    pageTableRef.value.search();
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const edit = async (data: any) => {
 | 
				
			||||||
 | 
					    if (!data) {
 | 
				
			||||||
 | 
					        state.editDialog.data = null;
 | 
				
			||||||
 | 
					        state.editDialog.title = '新增数据库迁移任务';
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					        state.editDialog.data = data;
 | 
				
			||||||
 | 
					        state.editDialog.title = '修改数据库迁移任务';
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    state.editDialog.visible = true;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const stop = async (id: any) => {
 | 
				
			||||||
 | 
					    await ElMessageBox.confirm(`确定停止?`, '提示', {
 | 
				
			||||||
 | 
					        confirmButtonText: '确定',
 | 
				
			||||||
 | 
					        cancelButtonText: '取消',
 | 
				
			||||||
 | 
					        type: 'warning',
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    await dbApi.stopDbTransferTask.request({ taskId: id });
 | 
				
			||||||
 | 
					    ElMessage.success(`停止成功`);
 | 
				
			||||||
 | 
					    search();
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const log = (data: any) => {
 | 
				
			||||||
 | 
					    state.logsDialog.logId = data.logId;
 | 
				
			||||||
 | 
					    state.logsDialog.visible = true;
 | 
				
			||||||
 | 
					    state.logsDialog.title = '数据库迁移日志';
 | 
				
			||||||
 | 
					    state.logsDialog.running = data.state === 1;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const reRun = async (data: any) => {
 | 
				
			||||||
 | 
					    await ElMessageBox.confirm(`确定运行?`, '提示', {
 | 
				
			||||||
 | 
					        confirmButtonText: '确定',
 | 
				
			||||||
 | 
					        cancelButtonText: '取消',
 | 
				
			||||||
 | 
					        type: 'warning',
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					        let res = await dbApi.runDbTransferTask.request({ taskId: data.id });
 | 
				
			||||||
 | 
					        console.log(res);
 | 
				
			||||||
 | 
					        ElMessage.success('运行成功');
 | 
				
			||||||
 | 
					        // 拿到日志id之后,弹出日志弹窗
 | 
				
			||||||
 | 
					        log({ logId: res, state: 1 });
 | 
				
			||||||
 | 
					    } catch (e) {
 | 
				
			||||||
 | 
					        //
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    // 延迟2秒执行,后端异步执行
 | 
				
			||||||
 | 
					    setTimeout(() => {
 | 
				
			||||||
 | 
					        search();
 | 
				
			||||||
 | 
					    }, 2000);
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const del = async () => {
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					        await ElMessageBox.confirm(`确定删除任务?`, '提示', {
 | 
				
			||||||
 | 
					            confirmButtonText: '确定',
 | 
				
			||||||
 | 
					            cancelButtonText: '取消',
 | 
				
			||||||
 | 
					            type: 'warning',
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					        await dbApi.deleteDbTransferTask.request({ taskId: state.selectionData.map((x: any) => x.id).join(',') });
 | 
				
			||||||
 | 
					        ElMessage.success('删除成功');
 | 
				
			||||||
 | 
					        search();
 | 
				
			||||||
 | 
					    } catch (err) {
 | 
				
			||||||
 | 
					        //
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					</script>
 | 
				
			||||||
 | 
					<style lang="scss"></style>
 | 
				
			||||||
@@ -1,58 +1,111 @@
 | 
				
			|||||||
<template>
 | 
					<template>
 | 
				
			||||||
    <div>
 | 
					    <div>
 | 
				
			||||||
        <el-dialog :title="title" v-model="dialogVisible" :before-close="cancel" :close-on-click-modal="false" :destroy-on-close="true" width="38%">
 | 
					        <el-drawer :title="title" v-model="dialogVisible" :before-close="cancel" :destroy-on-close="true" :close-on-click-modal="false" size="40%">
 | 
				
			||||||
 | 
					            <template #header>
 | 
				
			||||||
 | 
					                <DrawerHeader :header="title" :back="cancel" />
 | 
				
			||||||
 | 
					            </template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            <el-form :model="form" ref="dbForm" :rules="rules" label-width="auto">
 | 
					            <el-form :model="form" ref="dbForm" :rules="rules" label-width="auto">
 | 
				
			||||||
                <el-tabs v-model="tabActiveName">
 | 
					                <el-divider content-position="left">基本</el-divider>
 | 
				
			||||||
                    <el-tab-pane label="基础信息" name="basic">
 | 
					                <el-form-item prop="code" label="编号" required>
 | 
				
			||||||
                        <el-form-item prop="name" label="别名" required>
 | 
					                    <el-input :disabled="form.id" v-model.trim="form.code" 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="name" label="名称" required>
 | 
				
			||||||
                        <el-form-item prop="type" label="类型" required>
 | 
					                    <el-input v-model.trim="form.name" placeholder="请输入数据库别名" auto-complete="off"></el-input>
 | 
				
			||||||
                            <el-select @change="changeDbType" style="width: 100%" v-model="form.type" placeholder="请选择数据库类型">
 | 
					                </el-form-item>
 | 
				
			||||||
                                <el-option v-for="dt in dbTypes" :key="dt.type" :value="dt.type" :label="dt.label">
 | 
					
 | 
				
			||||||
                                    <SvgIcon :name="getDbDialect(dt.type).getInfo().icon" :size="18" />
 | 
					                <el-form-item prop="type" label="类型" required>
 | 
				
			||||||
                                    {{ dt.label }}
 | 
					                    <el-select @change="changeDbType" style="width: 100%" v-model="form.type" placeholder="请选择数据库类型">
 | 
				
			||||||
                                </el-option>
 | 
					                        <el-option
 | 
				
			||||||
                            </el-select>
 | 
					                            v-for="(dbTypeAndDialect, key) in getDbDialectMap()"
 | 
				
			||||||
                        </el-form-item>
 | 
					                            :key="key"
 | 
				
			||||||
                        <el-form-item prop="host" label="host" required>
 | 
					                            :value="dbTypeAndDialect[0]"
 | 
				
			||||||
                            <el-col :span="18">
 | 
					                            :label="dbTypeAndDialect[1].getInfo().name"
 | 
				
			||||||
                                <el-input :disabled="form.id !== undefined" v-model.trim="form.host" placeholder="请输入主机ip" auto-complete="off"></el-input>
 | 
					                        >
 | 
				
			||||||
                            </el-col>
 | 
					                            <SvgIcon :name="dbTypeAndDialect[1].getInfo().icon" :size="20" />
 | 
				
			||||||
                            <el-col style="text-align: center" :span="1">:</el-col>
 | 
					                            {{ dbTypeAndDialect[1].getInfo().name }}
 | 
				
			||||||
                            <el-col :span="5">
 | 
					                        </el-option>
 | 
				
			||||||
                                <el-input type="number" v-model.number="form.port" placeholder="端口"></el-input>
 | 
					
 | 
				
			||||||
                            </el-col>
 | 
					                        <template #prefix>
 | 
				
			||||||
                        </el-form-item>
 | 
					                            <SvgIcon :name="getDbDialect(form.type).getInfo().icon" :size="20" />
 | 
				
			||||||
                        <el-form-item v-if="form.type === DbType.oracle" prop="sid" label="SID">
 | 
					                        </template>
 | 
				
			||||||
                            <el-input v-model.trim="form.sid" placeholder="请输入服务id"></el-input>
 | 
					                    </el-select>
 | 
				
			||||||
                        </el-form-item>
 | 
					                </el-form-item>
 | 
				
			||||||
                        <el-form-item prop="username" label="用户名" required>
 | 
					
 | 
				
			||||||
                            <el-input v-model.trim="form.username" placeholder="请输入用户名"></el-input>
 | 
					                <el-form-item v-if="form.type !== DbType.sqlite" prop="host" label="host" required>
 | 
				
			||||||
                        </el-form-item>
 | 
					                    <el-col :span="18">
 | 
				
			||||||
                        <el-form-item prop="password" label="密码">
 | 
					                        <el-input :disabled="form.id !== undefined" v-model.trim="form.host" placeholder="请输入主机ip" auto-complete="off"></el-input>
 | 
				
			||||||
                            <el-input type="password" show-password v-model.trim="form.password" placeholder="请输入密码" autocomplete="new-password">
 | 
					                    </el-col>
 | 
				
			||||||
                                <template v-if="form.id && form.id != 0" #suffix>
 | 
					                    <el-col style="text-align: center" :span="1">:</el-col>
 | 
				
			||||||
                                    <el-popover @hide="pwd = ''" placement="right" title="原密码" :width="200" trigger="click" :content="pwd">
 | 
					                    <el-col :span="5">
 | 
				
			||||||
                                        <template #reference>
 | 
					                        <el-input type="number" v-model.number="form.port" placeholder="端口"></el-input>
 | 
				
			||||||
                                            <el-link v-auth="'db:instance:save'" @click="getDbPwd" :underline="false" type="primary" class="mr5"
 | 
					                    </el-col>
 | 
				
			||||||
                                                >原密码
 | 
					                </el-form-item>
 | 
				
			||||||
                                            </el-link>
 | 
					
 | 
				
			||||||
                                        </template>
 | 
					                <el-form-item v-if="form.type === DbType.sqlite" prop="host" label="sqlite地址">
 | 
				
			||||||
                                    </el-popover>
 | 
					                    <el-input v-model.trim="form.host" placeholder="请输入sqlite文件在服务器的绝对地址"></el-input>
 | 
				
			||||||
 | 
					                </el-form-item>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                <el-form-item v-if="form.type === DbType.oracle" label="SID|服务名">
 | 
				
			||||||
 | 
					                    <el-col :span="5">
 | 
				
			||||||
 | 
					                        <el-select
 | 
				
			||||||
 | 
					                            @change="
 | 
				
			||||||
 | 
					                                () => {
 | 
				
			||||||
 | 
					                                    state.extra.serviceName = '';
 | 
				
			||||||
 | 
					                                    state.extra.sid = '';
 | 
				
			||||||
 | 
					                                }
 | 
				
			||||||
 | 
					                            "
 | 
				
			||||||
 | 
					                            v-model="state.extra.stype"
 | 
				
			||||||
 | 
					                            placeholder="请选择"
 | 
				
			||||||
 | 
					                        >
 | 
				
			||||||
 | 
					                            <el-option label="服务名" :value="1" />
 | 
				
			||||||
 | 
					                            <el-option label="SID" :value="2" />
 | 
				
			||||||
 | 
					                        </el-select>
 | 
				
			||||||
 | 
					                    </el-col>
 | 
				
			||||||
 | 
					                    <el-col style="text-align: center" :span="1">:</el-col>
 | 
				
			||||||
 | 
					                    <el-col :span="18">
 | 
				
			||||||
 | 
					                        <el-input v-if="state.extra.stype == 1" v-model="state.extra.serviceName" placeholder="请输入服务名"> </el-input>
 | 
				
			||||||
 | 
					                        <el-input v-else v-model="state.extra.sid" placeholder="请输入SID"> </el-input>
 | 
				
			||||||
 | 
					                    </el-col>
 | 
				
			||||||
 | 
					                </el-form-item>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                <el-form-item prop="remark" label="备注">
 | 
				
			||||||
 | 
					                    <el-input v-model="form.remark" auto-complete="off" type="textarea"></el-input>
 | 
				
			||||||
 | 
					                </el-form-item>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                <template v-if="form.type !== DbType.sqlite">
 | 
				
			||||||
 | 
					                    <el-divider content-position="left">账号</el-divider>
 | 
				
			||||||
 | 
					                    <div>
 | 
				
			||||||
 | 
					                        <ResourceAuthCertTableEdit
 | 
				
			||||||
 | 
					                            v-model="form.authCerts"
 | 
				
			||||||
 | 
					                            :resource-code="form.code"
 | 
				
			||||||
 | 
					                            :resource-type="TagResourceTypeEnum.Db.value"
 | 
				
			||||||
 | 
					                            :test-conn-btn-loading="testConnBtnLoading"
 | 
				
			||||||
 | 
					                            @test-conn="testConn"
 | 
				
			||||||
 | 
					                            :disable-ciphertext-type="[AuthCertCiphertextTypeEnum.PrivateKey.value]"
 | 
				
			||||||
 | 
					                        />
 | 
				
			||||||
 | 
					                    </div>
 | 
				
			||||||
 | 
					                </template>
 | 
				
			||||||
 | 
					                <!--
 | 
				
			||||||
 | 
					                <el-form-item v-if="form.type !== DbType.sqlite" prop="username" label="用户名" required>
 | 
				
			||||||
 | 
					                    <el-input v-model.trim="form.username" placeholder="请输入用户名"></el-input>
 | 
				
			||||||
 | 
					                </el-form-item>
 | 
				
			||||||
 | 
					                <el-form-item v-if="form.type !== DbType.sqlite" 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>
 | 
					                                </template>
 | 
				
			||||||
                            </el-input>
 | 
					                            </el-popover>
 | 
				
			||||||
                        </el-form-item>
 | 
					                        </template>
 | 
				
			||||||
 | 
					                    </el-input>
 | 
				
			||||||
 | 
					                </el-form-item> -->
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                        <el-form-item prop="remark" label="备注">
 | 
					                <el-divider content-position="left">其他</el-divider>
 | 
				
			||||||
                            <el-input v-model="form.remark" auto-complete="off" type="textarea"></el-input>
 | 
					                <el-form-item prop="params" label="连接参数">
 | 
				
			||||||
                        </el-form-item>
 | 
					                    <el-input v-model.trim="form.params" placeholder="其他连接参数,形如: key1=value1&key2=value2">
 | 
				
			||||||
                    </el-tab-pane>
 | 
					                        <!-- <template #suffix>
 | 
				
			||||||
 | 
					 | 
				
			||||||
                    <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
 | 
					                                    <el-link
 | 
				
			||||||
                                        target="_blank"
 | 
					                                        target="_blank"
 | 
				
			||||||
                                        href="https://github.com/go-sql-driver/mysql#parameters"
 | 
					                                        href="https://github.com/go-sql-driver/mysql#parameters"
 | 
				
			||||||
@@ -62,24 +115,21 @@
 | 
				
			|||||||
                                        >参数参考</el-link
 | 
					                                        >参数参考</el-link
 | 
				
			||||||
                                    >
 | 
					                                    >
 | 
				
			||||||
                                </template> -->
 | 
					                                </template> -->
 | 
				
			||||||
                            </el-input>
 | 
					                    </el-input>
 | 
				
			||||||
                        </el-form-item>
 | 
					                </el-form-item>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                        <el-form-item prop="sshTunnelMachineId" label="SSH隧道">
 | 
					                <el-form-item prop="sshTunnelMachineId" label="SSH隧道">
 | 
				
			||||||
                            <ssh-tunnel-select v-model="form.sshTunnelMachineId" />
 | 
					                    <ssh-tunnel-select v-model="form.sshTunnelMachineId" />
 | 
				
			||||||
                        </el-form-item>
 | 
					                </el-form-item>
 | 
				
			||||||
                    </el-tab-pane>
 | 
					 | 
				
			||||||
                </el-tabs>
 | 
					 | 
				
			||||||
            </el-form>
 | 
					            </el-form>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            <template #footer>
 | 
					            <template #footer>
 | 
				
			||||||
                <div class="dialog-footer">
 | 
					                <div class="dialog-footer">
 | 
				
			||||||
                    <el-button @click="testConn" :loading="testConnBtnLoading" type="success">测试连接</el-button>
 | 
					 | 
				
			||||||
                    <el-button @click="cancel()">取 消</el-button>
 | 
					                    <el-button @click="cancel()">取 消</el-button>
 | 
				
			||||||
                    <el-button type="primary" :loading="saveBtnLoading" @click="btnOk">确 定</el-button>
 | 
					                    <el-button type="primary" :loading="saveBtnLoading" @click="btnOk">确 定</el-button>
 | 
				
			||||||
                </div>
 | 
					                </div>
 | 
				
			||||||
            </template>
 | 
					            </template>
 | 
				
			||||||
        </el-dialog>
 | 
					        </el-drawer>
 | 
				
			||||||
    </div>
 | 
					    </div>
 | 
				
			||||||
</template>
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -87,11 +137,14 @@
 | 
				
			|||||||
import { reactive, ref, toRefs, watch } from 'vue';
 | 
					import { reactive, ref, toRefs, watch } 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 SshTunnelSelect from '../component/SshTunnelSelect.vue';
 | 
					import SshTunnelSelect from '../component/SshTunnelSelect.vue';
 | 
				
			||||||
import { DbType, getDbDialect } from './dialect';
 | 
					import { DbType, getDbDialect, getDbDialectMap } from './dialect';
 | 
				
			||||||
import SvgIcon from '@/components/svgIcon/index.vue';
 | 
					import SvgIcon from '@/components/svgIcon/index.vue';
 | 
				
			||||||
 | 
					import DrawerHeader from '@/components/drawer-header/DrawerHeader.vue';
 | 
				
			||||||
 | 
					import { ResourceCodePattern } from '@/common/pattern';
 | 
				
			||||||
 | 
					import { TagResourceTypeEnum } from '@/common/commonEnum';
 | 
				
			||||||
 | 
					import ResourceAuthCertTableEdit from '../component/ResourceAuthCertTableEdit.vue';
 | 
				
			||||||
 | 
					import { AuthCertCiphertextTypeEnum } from '../tag/enums';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const props = defineProps({
 | 
					const props = defineProps({
 | 
				
			||||||
    visible: {
 | 
					    visible: {
 | 
				
			||||||
@@ -109,6 +162,18 @@ const props = defineProps({
 | 
				
			|||||||
const emit = defineEmits(['update:visible', 'cancel', 'val-change']);
 | 
					const emit = defineEmits(['update:visible', 'cancel', 'val-change']);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const rules = {
 | 
					const rules = {
 | 
				
			||||||
 | 
					    code: [
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            required: true,
 | 
				
			||||||
 | 
					            message: '请输入编码',
 | 
				
			||||||
 | 
					            trigger: ['change', 'blur'],
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            pattern: ResourceCodePattern.pattern,
 | 
				
			||||||
 | 
					            message: ResourceCodePattern.message,
 | 
				
			||||||
 | 
					            trigger: ['blur'],
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					    ],
 | 
				
			||||||
    name: [
 | 
					    name: [
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            required: true,
 | 
					            required: true,
 | 
				
			||||||
@@ -130,13 +195,6 @@ const rules = {
 | 
				
			|||||||
            trigger: ['blur'],
 | 
					            trigger: ['blur'],
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
    ],
 | 
					    ],
 | 
				
			||||||
    username: [
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            required: true,
 | 
					 | 
				
			||||||
            message: '请输入用户名',
 | 
					 | 
				
			||||||
            trigger: ['change', 'blur'],
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
    ],
 | 
					 | 
				
			||||||
    sid: [
 | 
					    sid: [
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            required: true,
 | 
					            required: true,
 | 
				
			||||||
@@ -148,69 +206,45 @@ const rules = {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
const dbForm: any = ref(null);
 | 
					const dbForm: any = ref(null);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const dbTypes = [
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        type: 'mysql',
 | 
					 | 
				
			||||||
        label: 'mysql',
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        type: 'mariadb',
 | 
					 | 
				
			||||||
        label: 'mariadb',
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        type: 'postgres',
 | 
					 | 
				
			||||||
        label: 'postgres',
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        type: 'dm',
 | 
					 | 
				
			||||||
        label: '达梦',
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        type: 'oracle',
 | 
					 | 
				
			||||||
        label: 'oracle',
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
];
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const state = reactive({
 | 
					const state = reactive({
 | 
				
			||||||
    dialogVisible: false,
 | 
					    dialogVisible: false,
 | 
				
			||||||
    tabActiveName: 'basic',
 | 
					    extra: {} as any, // 连接需要的额外参数(json)
 | 
				
			||||||
    form: {
 | 
					    form: {
 | 
				
			||||||
        id: null,
 | 
					        id: null,
 | 
				
			||||||
        type: null,
 | 
					        type: '',
 | 
				
			||||||
 | 
					        code: '',
 | 
				
			||||||
        name: null,
 | 
					        name: null,
 | 
				
			||||||
        host: '',
 | 
					        host: '',
 | 
				
			||||||
        port: null,
 | 
					        port: null,
 | 
				
			||||||
        username: null,
 | 
					        authCerts: [],
 | 
				
			||||||
        sid: null, // oracle类项目需要服务id
 | 
					        extra: '', // 连接需要的额外参数(json字符串)
 | 
				
			||||||
        password: null,
 | 
					 | 
				
			||||||
        params: null,
 | 
					        params: null,
 | 
				
			||||||
        remark: '',
 | 
					        remark: '',
 | 
				
			||||||
        sshTunnelMachineId: null as any,
 | 
					        sshTunnelMachineId: null as any,
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    subimtForm: {},
 | 
					    submitForm: {} as any,
 | 
				
			||||||
    // 原密码
 | 
					 | 
				
			||||||
    pwd: '',
 | 
					 | 
				
			||||||
    // 原用户名
 | 
					 | 
				
			||||||
    oldUserName: null,
 | 
					 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const { dialogVisible, tabActiveName, form, subimtForm, pwd } = toRefs(state);
 | 
					const { dialogVisible, form, submitForm } = toRefs(state);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const { isFetching: saveBtnLoading, execute: saveInstanceExec } = dbApi.saveInstance.useApi(subimtForm);
 | 
					const { isFetching: saveBtnLoading, execute: saveInstanceExec } = dbApi.saveInstance.useApi(submitForm);
 | 
				
			||||||
const { isFetching: testConnBtnLoading, execute: testConnExec } = dbApi.testConn.useApi(subimtForm);
 | 
					const { isFetching: testConnBtnLoading, execute: testConnExec } = dbApi.testConn.useApi(submitForm);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
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.data) {
 | 
					    if (newValue.data) {
 | 
				
			||||||
        state.form = { ...newValue.data };
 | 
					        state.form = { ...newValue.data };
 | 
				
			||||||
        state.oldUserName = state.form.username;
 | 
					        try {
 | 
				
			||||||
 | 
					            state.extra = JSON.parse(state.form.extra);
 | 
				
			||||||
 | 
					        } catch (e) {
 | 
				
			||||||
 | 
					            state.extra = {};
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
    } else {
 | 
					    } else {
 | 
				
			||||||
        state.form = { port: null } as any;
 | 
					        state.form = { port: null, type: DbType.mysql } as any;
 | 
				
			||||||
        state.oldUserName = null;
 | 
					        state.form.authCerts = [];
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -218,48 +252,42 @@ const changeDbType = (val: string) => {
 | 
				
			|||||||
    if (!state.form.id) {
 | 
					    if (!state.form.id) {
 | 
				
			||||||
        state.form.port = getDbDialect(val).getInfo().defaultPort as any;
 | 
					        state.form.port = getDbDialect(val).getInfo().defaultPort as any;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
};
 | 
					    state.extra = {};
 | 
				
			||||||
 | 
					 | 
				
			||||||
const getDbPwd = async () => {
 | 
					 | 
				
			||||||
    state.pwd = await dbApi.getInstancePwd.request({ id: state.form.id });
 | 
					 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const getReqForm = async () => {
 | 
					const getReqForm = async () => {
 | 
				
			||||||
    const reqForm = { ...state.form };
 | 
					    const reqForm = { ...state.form };
 | 
				
			||||||
    reqForm.password = await RsaEncrypt(reqForm.password);
 | 
					 | 
				
			||||||
    if (!state.form.sshTunnelMachineId) {
 | 
					    if (!state.form.sshTunnelMachineId) {
 | 
				
			||||||
        reqForm.sshTunnelMachineId = -1;
 | 
					        reqForm.sshTunnelMachineId = -1;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					    if (Object.keys(state.extra).length > 0) {
 | 
				
			||||||
 | 
					        reqForm.extra = JSON.stringify(state.extra);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
    return reqForm;
 | 
					    return reqForm;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const testConn = async () => {
 | 
					const testConn = async (authCert: any) => {
 | 
				
			||||||
    dbForm.value.validate(async (valid: boolean) => {
 | 
					    dbForm.value.validate(async (valid: boolean) => {
 | 
				
			||||||
        if (!valid) {
 | 
					        if (!valid) {
 | 
				
			||||||
            ElMessage.error('请正确填写信息');
 | 
					            ElMessage.error('请正确填写信息');
 | 
				
			||||||
            return false;
 | 
					            return false;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        state.subimtForm = await getReqForm();
 | 
					        state.submitForm = await getReqForm();
 | 
				
			||||||
 | 
					        state.submitForm.authCerts = [authCert];
 | 
				
			||||||
        await testConnExec();
 | 
					        await testConnExec();
 | 
				
			||||||
        ElMessage.success('连接成功');
 | 
					        ElMessage.success('连接成功');
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const btnOk = async () => {
 | 
					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) => {
 | 
					    dbForm.value.validate(async (valid: boolean) => {
 | 
				
			||||||
        if (!valid) {
 | 
					        if (!valid) {
 | 
				
			||||||
            ElMessage.error('请正确填写信息');
 | 
					            ElMessage.error('请正确填写信息');
 | 
				
			||||||
            return false;
 | 
					            return false;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        state.subimtForm = await getReqForm();
 | 
					        state.submitForm = await getReqForm();
 | 
				
			||||||
        await saveInstanceExec();
 | 
					        await saveInstanceExec();
 | 
				
			||||||
        ElMessage.success('保存成功');
 | 
					        ElMessage.success('保存成功');
 | 
				
			||||||
        emit('val-change', state.form);
 | 
					        emit('val-change', state.form);
 | 
				
			||||||
@@ -270,6 +298,7 @@ const btnOk = async () => {
 | 
				
			|||||||
const cancel = () => {
 | 
					const cancel = () => {
 | 
				
			||||||
    emit('update:visible', false);
 | 
					    emit('update:visible', false);
 | 
				
			||||||
    emit('cancel');
 | 
					    emit('cancel');
 | 
				
			||||||
 | 
					    state.extra = {};
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
</script>
 | 
					</script>
 | 
				
			||||||
<style lang="scss"></style>
 | 
					<style lang="scss"></style>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -3,6 +3,7 @@
 | 
				
			|||||||
        <page-table
 | 
					        <page-table
 | 
				
			||||||
            ref="pageTableRef"
 | 
					            ref="pageTableRef"
 | 
				
			||||||
            :page-api="dbApi.instances"
 | 
					            :page-api="dbApi.instances"
 | 
				
			||||||
 | 
					            :data-handler-fn="handleData"
 | 
				
			||||||
            :searchItems="searchItems"
 | 
					            :searchItems="searchItems"
 | 
				
			||||||
            v-model:query-form="query"
 | 
					            v-model:query-form="query"
 | 
				
			||||||
            :show-selection="true"
 | 
					            :show-selection="true"
 | 
				
			||||||
@@ -16,8 +17,12 @@
 | 
				
			|||||||
                >
 | 
					                >
 | 
				
			||||||
            </template>
 | 
					            </template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            <template #authCert="{ data }">
 | 
				
			||||||
 | 
					                <ResourceAuthCert v-model:select-auth-cert="data.selectAuthCert" :auth-certs="data.authCerts" />
 | 
				
			||||||
 | 
					            </template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            <template #type="{ data }">
 | 
					            <template #type="{ data }">
 | 
				
			||||||
                <el-tooltip :content="data.type" placement="top">
 | 
					                <el-tooltip :content="getDbDialect(data.type).getInfo().name" placement="top">
 | 
				
			||||||
                    <SvgIcon :name="getDbDialect(data.type).getInfo().icon" :size="20" />
 | 
					                    <SvgIcon :name="getDbDialect(data.type).getInfo().icon" :size="20" />
 | 
				
			||||||
                </el-tooltip>
 | 
					                </el-tooltip>
 | 
				
			||||||
            </template>
 | 
					            </template>
 | 
				
			||||||
@@ -35,7 +40,6 @@
 | 
				
			|||||||
                <el-descriptions-item :span="2" label="主机">{{ infoDialog.data.host }}</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="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="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.params }}</el-descriptions-item>
 | 
				
			||||||
@@ -61,7 +65,7 @@
 | 
				
			|||||||
</template>
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<script lang="ts" setup>
 | 
					<script lang="ts" setup>
 | 
				
			||||||
import { ref, toRefs, reactive, onMounted, defineAsyncComponent, Ref } from 'vue';
 | 
					import { defineAsyncComponent, onMounted, reactive, ref, Ref, toRefs } from 'vue';
 | 
				
			||||||
import { ElMessage, ElMessageBox } from 'element-plus';
 | 
					import { ElMessage, ElMessageBox } from 'element-plus';
 | 
				
			||||||
import { dbApi } from './api';
 | 
					import { dbApi } from './api';
 | 
				
			||||||
import { dateFormat } from '@/common/utils/date';
 | 
					import { dateFormat } from '@/common/utils/date';
 | 
				
			||||||
@@ -71,6 +75,7 @@ import { hasPerms } from '@/components/auth/auth';
 | 
				
			|||||||
import SvgIcon from '@/components/svgIcon/index.vue';
 | 
					import SvgIcon from '@/components/svgIcon/index.vue';
 | 
				
			||||||
import { getDbDialect } from './dialect';
 | 
					import { getDbDialect } from './dialect';
 | 
				
			||||||
import { SearchItem } from '@/components/SearchForm';
 | 
					import { SearchItem } from '@/components/SearchForm';
 | 
				
			||||||
 | 
					import ResourceAuthCert from '../component/ResourceAuthCert.vue';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const InstanceEdit = defineAsyncComponent(() => import('./InstanceEdit.vue'));
 | 
					const InstanceEdit = defineAsyncComponent(() => import('./InstanceEdit.vue'));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -79,19 +84,20 @@ const perms = {
 | 
				
			|||||||
    delInstance: 'db:instance:del',
 | 
					    delInstance: 'db:instance:del',
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const searchItems = [SearchItem.input('name', '名称')];
 | 
					const searchItems = [SearchItem.input('code', '编号'), SearchItem.input('name', '名称')];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const columns = ref([
 | 
					const columns = ref([
 | 
				
			||||||
 | 
					    TableColumn.new('code', '编号'),
 | 
				
			||||||
    TableColumn.new('name', '名称'),
 | 
					    TableColumn.new('name', '名称'),
 | 
				
			||||||
    TableColumn.new('type', '类型').isSlot().setAddWidth(-15).alignCenter(),
 | 
					    TableColumn.new('type', '类型').isSlot().setAddWidth(-15).alignCenter(),
 | 
				
			||||||
    TableColumn.new('host', 'host:port').setFormatFunc((data: any) => `${data.host}:${data.port}`),
 | 
					    TableColumn.new('host', 'host:port').setFormatFunc((data: any) => `${data.host}:${data.port}`),
 | 
				
			||||||
    TableColumn.new('username', '用户名'),
 | 
					    TableColumn.new('authCerts[0].username', '授权凭证').isSlot('authCert').setAddWidth(10),
 | 
				
			||||||
    TableColumn.new('params', '连接参数'),
 | 
					    TableColumn.new('params', '连接参数'),
 | 
				
			||||||
    TableColumn.new('remark', '备注'),
 | 
					    TableColumn.new('remark', '备注'),
 | 
				
			||||||
]);
 | 
					]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// 该用户拥有的的操作列按钮权限
 | 
					// 该用户拥有的的操作列按钮权限
 | 
				
			||||||
const actionBtns = hasPerms([perms.saveInstance]);
 | 
					const actionBtns = hasPerms(Object.values(perms));
 | 
				
			||||||
const actionColumn = TableColumn.new('action', '操作').isSlot().setMinWidth(110).fixedRight().alignCenter();
 | 
					const actionColumn = TableColumn.new('action', '操作').isSlot().setMinWidth(110).fixedRight().alignCenter();
 | 
				
			||||||
const pageTableRef: Ref<any> = ref(null);
 | 
					const pageTableRef: Ref<any> = ref(null);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -134,6 +140,15 @@ const search = () => {
 | 
				
			|||||||
    pageTableRef.value.search();
 | 
					    pageTableRef.value.search();
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const handleData = (res: any) => {
 | 
				
			||||||
 | 
					    const dataList = res.list;
 | 
				
			||||||
 | 
					    // 赋值授权凭证
 | 
				
			||||||
 | 
					    for (let x of dataList) {
 | 
				
			||||||
 | 
					        x.selectAuthCert = x.authCerts[0];
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    return res;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const showInfo = (info: any) => {
 | 
					const showInfo = (info: any) => {
 | 
				
			||||||
    state.infoDialog.data = info;
 | 
					    state.infoDialog.data = info;
 | 
				
			||||||
    state.infoDialog.visible = true;
 | 
					    state.infoDialog.visible = true;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -71,7 +71,7 @@
 | 
				
			|||||||
                                <el-descriptions-item label-align="right">
 | 
					                                <el-descriptions-item label-align="right">
 | 
				
			||||||
                                    <template #label>
 | 
					                                    <template #label>
 | 
				
			||||||
                                        <div>
 | 
					                                        <div>
 | 
				
			||||||
                                            <SvgIcon :name="getDbDialect(nowDbInst.type).getInfo().icon" :size="18" />
 | 
					                                            <SvgIcon :name="nowDbInst.getDialect().getInfo().icon" :size="18" />
 | 
				
			||||||
                                            实例
 | 
					                                            实例
 | 
				
			||||||
                                        </div>
 | 
					                                        </div>
 | 
				
			||||||
                                    </template>
 | 
					                                    </template>
 | 
				
			||||||
@@ -143,6 +143,7 @@
 | 
				
			|||||||
                                    :db-id="dt.params.id"
 | 
					                                    :db-id="dt.params.id"
 | 
				
			||||||
                                    :db="dt.params.db"
 | 
					                                    :db="dt.params.db"
 | 
				
			||||||
                                    :db-type="dt.params.type"
 | 
					                                    :db-type="dt.params.type"
 | 
				
			||||||
 | 
					                                    :flow-procdef-key="dt.params.flowProcdefKey"
 | 
				
			||||||
                                    :height="state.tablesOpHeight"
 | 
					                                    :height="state.tablesOpHeight"
 | 
				
			||||||
                                />
 | 
					                                />
 | 
				
			||||||
                            </el-tab-pane>
 | 
					                            </el-tab-pane>
 | 
				
			||||||
@@ -151,12 +152,23 @@
 | 
				
			|||||||
                </div>
 | 
					                </div>
 | 
				
			||||||
            </Pane>
 | 
					            </Pane>
 | 
				
			||||||
        </Splitpanes>
 | 
					        </Splitpanes>
 | 
				
			||||||
 | 
					        <db-table-op
 | 
				
			||||||
 | 
					            :title="tableCreateDialog.title"
 | 
				
			||||||
 | 
					            :active-name="tableCreateDialog.activeName"
 | 
				
			||||||
 | 
					            :dbId="tableCreateDialog.dbId"
 | 
				
			||||||
 | 
					            :db="tableCreateDialog.db"
 | 
				
			||||||
 | 
					            :dbType="tableCreateDialog.dbType"
 | 
				
			||||||
 | 
					            :flow-procdef-key="tableCreateDialog.flowProcdefKey"
 | 
				
			||||||
 | 
					            :data="tableCreateDialog.data"
 | 
				
			||||||
 | 
					            v-model:visible="tableCreateDialog.visible"
 | 
				
			||||||
 | 
					            @submit-sql="onSubmitEditTableSql"
 | 
				
			||||||
 | 
					        />
 | 
				
			||||||
    </div>
 | 
					    </div>
 | 
				
			||||||
</template>
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<script lang="ts" setup>
 | 
					<script lang="ts" setup>
 | 
				
			||||||
import { defineAsyncComponent, onBeforeUnmount, onMounted, reactive, ref, toRefs } from 'vue';
 | 
					import { defineAsyncComponent, h, onBeforeUnmount, onMounted, reactive, ref, toRefs } from 'vue';
 | 
				
			||||||
import { ElMessage, ElMessageBox } from 'element-plus';
 | 
					import { ElCheckbox, ElMessage, ElMessageBox } from 'element-plus';
 | 
				
			||||||
import { formatByteSize } from '@/common/utils/format';
 | 
					import { formatByteSize } from '@/common/utils/format';
 | 
				
			||||||
import { DbInst, registerDbCompletionItemProvider, TabInfo, TabType } from './db';
 | 
					import { DbInst, registerDbCompletionItemProvider, TabInfo, TabType } from './db';
 | 
				
			||||||
import { NodeType, TagTreeNode } from '../component/tag';
 | 
					import { NodeType, TagTreeNode } from '../component/tag';
 | 
				
			||||||
@@ -165,12 +177,14 @@ import { dbApi } from './api';
 | 
				
			|||||||
import { dispposeCompletionItemProvider } from '@/components/monaco/completionItemProvider';
 | 
					import { dispposeCompletionItemProvider } from '@/components/monaco/completionItemProvider';
 | 
				
			||||||
import SvgIcon from '@/components/svgIcon/index.vue';
 | 
					import SvgIcon from '@/components/svgIcon/index.vue';
 | 
				
			||||||
import { ContextmenuItem } from '@/components/contextmenu';
 | 
					import { ContextmenuItem } from '@/components/contextmenu';
 | 
				
			||||||
import { DbType, getDbDialect } from './dialect/index';
 | 
					import { getDbDialect, schemaDbTypes } from './dialect/index';
 | 
				
			||||||
import { sleep } from '@/common/utils/loading';
 | 
					import { sleep } from '@/common/utils/loading';
 | 
				
			||||||
import { TagResourceTypeEnum } from '@/common/commonEnum';
 | 
					import { TagResourceTypeEnum } from '@/common/commonEnum';
 | 
				
			||||||
import { Pane, Splitpanes } from 'splitpanes';
 | 
					import { Pane, Splitpanes } from 'splitpanes';
 | 
				
			||||||
import { useEventListener } from '@vueuse/core';
 | 
					import { useEventListener } from '@vueuse/core';
 | 
				
			||||||
 | 
					import SqlExecBox from '@/views/ops/db/component/sqleditor/SqlExecBox';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const DbTableOp = defineAsyncComponent(() => import('./component/table/DbTableOp.vue'));
 | 
				
			||||||
const DbSqlEditor = defineAsyncComponent(() => import('./component/sqleditor/DbSqlEditor.vue'));
 | 
					const DbSqlEditor = defineAsyncComponent(() => import('./component/sqleditor/DbSqlEditor.vue'));
 | 
				
			||||||
const DbTableDataOp = defineAsyncComponent(() => import('./component/table/DbTableDataOp.vue'));
 | 
					const DbTableDataOp = defineAsyncComponent(() => import('./component/table/DbTableDataOp.vue'));
 | 
				
			||||||
const DbTablesOp = defineAsyncComponent(() => import('./component/table/DbTablesOp.vue'));
 | 
					const DbTablesOp = defineAsyncComponent(() => import('./component/table/DbTablesOp.vue'));
 | 
				
			||||||
@@ -214,25 +228,40 @@ const SqlIcon = {
 | 
				
			|||||||
const nodeClickChangeDb = (nodeData: TagTreeNode) => {
 | 
					const nodeClickChangeDb = (nodeData: TagTreeNode) => {
 | 
				
			||||||
    const params = nodeData.params;
 | 
					    const params = nodeData.params;
 | 
				
			||||||
    if (params.db) {
 | 
					    if (params.db) {
 | 
				
			||||||
        changeDb({ id: params.id, host: `${params.host}`, name: params.name, type: params.type, tagPath: params.tagPath, databases: params.dbs }, params.db);
 | 
					        changeDb(
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                id: params.id,
 | 
				
			||||||
 | 
					                host: `${params.host}`,
 | 
				
			||||||
 | 
					                name: params.name,
 | 
				
			||||||
 | 
					                type: params.type,
 | 
				
			||||||
 | 
					                tagPath: params.tagPath,
 | 
				
			||||||
 | 
					                databases: params.dbs,
 | 
				
			||||||
 | 
					                flowProcdefKey: params.flowProcdefKey,
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            params.db
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// tagpath 节点类型
 | 
					const ContextmenuItemRefresh = new ContextmenuItem('refresh', '刷新').withIcon('RefreshRight').withOnClick((data: any) => reloadNode(data.key));
 | 
				
			||||||
const NodeTypeTagPath = new NodeType(TagTreeNode.TagPath).withLoadNodesFunc(async (parentNode: TagTreeNode) => {
 | 
					 | 
				
			||||||
    const dbInfoRes = await dbApi.dbs.request({ tagPath: parentNode.key });
 | 
					 | 
				
			||||||
    const dbInfos = dbInfoRes.list;
 | 
					 | 
				
			||||||
    if (!dbInfos) {
 | 
					 | 
				
			||||||
        return [];
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // 防止过快加载会出现一闪而过,对眼睛不好
 | 
					// tagpath 节点类型
 | 
				
			||||||
    await sleep(100);
 | 
					const NodeTypeTagPath = new NodeType(TagTreeNode.TagPath)
 | 
				
			||||||
    return dbInfos?.map((x: any) => {
 | 
					    .withLoadNodesFunc(async (parentNode: TagTreeNode) => {
 | 
				
			||||||
        x.tagPath = parentNode.key;
 | 
					        const dbInfoRes = await dbApi.dbs.request({ tagPath: parentNode.key });
 | 
				
			||||||
        return new TagTreeNode(`${parentNode.key}.${x.id}`, x.name, NodeTypeDbInst).withParams(x);
 | 
					        const dbInfos = dbInfoRes.list;
 | 
				
			||||||
    });
 | 
					        if (!dbInfos) {
 | 
				
			||||||
});
 | 
					            return [];
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // 防止过快加载会出现一闪而过,对眼睛不好
 | 
				
			||||||
 | 
					        await sleep(100);
 | 
				
			||||||
 | 
					        return dbInfos?.map((x: any) => {
 | 
				
			||||||
 | 
					            x.tagPath = parentNode.key;
 | 
				
			||||||
 | 
					            return new TagTreeNode(`${parentNode.key}.${x.id}`, x.name, NodeTypeDbInst).withParams(x);
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					    .withContextMenuItems([ContextmenuItemRefresh]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// 数据库实例节点类型
 | 
					// 数据库实例节点类型
 | 
				
			||||||
const NodeTypeDbInst = new NodeType(SqlExecNodeType.DbInst).withLoadNodesFunc((parentNode: TagTreeNode) => {
 | 
					const NodeTypeDbInst = new NodeType(SqlExecNodeType.DbInst).withLoadNodesFunc((parentNode: TagTreeNode) => {
 | 
				
			||||||
@@ -248,6 +277,7 @@ const NodeTypeDbInst = new NodeType(SqlExecNodeType.DbInst).withLoadNodesFunc((p
 | 
				
			|||||||
                host: `${params.host}:${params.port}`,
 | 
					                host: `${params.host}:${params.port}`,
 | 
				
			||||||
                dbs: dbs,
 | 
					                dbs: dbs,
 | 
				
			||||||
                db: x,
 | 
					                db: x,
 | 
				
			||||||
 | 
					                flowProcdefKey: params.flowProcdefKey,
 | 
				
			||||||
            })
 | 
					            })
 | 
				
			||||||
            .withIcon(DbIcon);
 | 
					            .withIcon(DbIcon);
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
@@ -255,12 +285,12 @@ const NodeTypeDbInst = new NodeType(SqlExecNodeType.DbInst).withLoadNodesFunc((p
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
// 数据库节点
 | 
					// 数据库节点
 | 
				
			||||||
const NodeTypeDb = new NodeType(SqlExecNodeType.Db)
 | 
					const NodeTypeDb = new NodeType(SqlExecNodeType.Db)
 | 
				
			||||||
    .withContextMenuItems([new ContextmenuItem('reloadTables', '刷新').withIcon('RefreshRight').withOnClick((data: any) => reloadNode(data.key))])
 | 
					    .withContextMenuItems([ContextmenuItemRefresh])
 | 
				
			||||||
    .withLoadNodesFunc(async (parentNode: TagTreeNode) => {
 | 
					    .withLoadNodesFunc(async (parentNode: TagTreeNode) => {
 | 
				
			||||||
        const params = parentNode.params;
 | 
					        const params = parentNode.params;
 | 
				
			||||||
 | 
					        params.parentKey = parentNode.key;
 | 
				
			||||||
        // pg类数据库会多一层schema
 | 
					        // pg类数据库会多一层schema
 | 
				
			||||||
        if (params.type == DbType.postgresql || params.type === DbType.dm || params.type === DbType.oracle) {
 | 
					        if (schemaDbTypes.includes(params.type)) {
 | 
				
			||||||
            const params = parentNode.params;
 | 
					 | 
				
			||||||
            const { id, db } = params;
 | 
					            const { id, db } = params;
 | 
				
			||||||
            const schemaNames = await dbApi.pgSchemas.request({ id, db });
 | 
					            const schemaNames = await dbApi.pgSchemas.request({ id, db });
 | 
				
			||||||
            return schemaNames.map((sn: any) => {
 | 
					            return schemaNames.map((sn: any) => {
 | 
				
			||||||
@@ -269,33 +299,37 @@ const NodeTypeDb = new NodeType(SqlExecNodeType.Db)
 | 
				
			|||||||
                nParams.schema = sn;
 | 
					                nParams.schema = sn;
 | 
				
			||||||
                nParams.db = nParams.db + '/' + sn;
 | 
					                nParams.db = nParams.db + '/' + sn;
 | 
				
			||||||
                nParams.dbs = schemaNames;
 | 
					                nParams.dbs = schemaNames;
 | 
				
			||||||
                return new TagTreeNode(`${params.id}.${params.db}.schema.${sn}`, sn, NodeTypePostgresScheam).withParams(nParams).withIcon(SchemaIcon);
 | 
					                return new TagTreeNode(`${params.id}.${params.db}.schema.${sn}`, sn, NodeTypePostgresSchema).withParams(nParams).withIcon(SchemaIcon);
 | 
				
			||||||
            });
 | 
					            });
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        return [
 | 
					        return NodeTypeTables(params);
 | 
				
			||||||
            new TagTreeNode(`${params.id}.${params.db}.table-menu`, '表', NodeTypeTableMenu).withParams(params).withIcon(TableIcon),
 | 
					 | 
				
			||||||
            new TagTreeNode(getSqlMenuNodeKey(params.id, params.db), 'SQL', NodeTypeSqlMenu).withParams(params).withIcon(SqlIcon),
 | 
					 | 
				
			||||||
        ];
 | 
					 | 
				
			||||||
    })
 | 
					    })
 | 
				
			||||||
    .withNodeClickFunc(nodeClickChangeDb);
 | 
					    .withNodeClickFunc(nodeClickChangeDb);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const NodeTypeTables = (params: any) => {
 | 
				
			||||||
 | 
					    let tableKey = `${params.id}.${params.db}.table-menu`;
 | 
				
			||||||
 | 
					    let sqlKey = getSqlMenuNodeKey(params.id, params.db);
 | 
				
			||||||
 | 
					    return [
 | 
				
			||||||
 | 
					        new TagTreeNode(`${params.id}.${params.db}.table-menu`, '表', NodeTypeTableMenu).withParams({ ...params, key: tableKey }).withIcon(TableIcon),
 | 
				
			||||||
 | 
					        new TagTreeNode(sqlKey, 'SQL', NodeTypeSqlMenu).withParams({ ...params, key: sqlKey }).withIcon(SqlIcon),
 | 
				
			||||||
 | 
					    ];
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// postgres schema模式
 | 
					// postgres schema模式
 | 
				
			||||||
const NodeTypePostgresScheam = new NodeType(SqlExecNodeType.PgSchema)
 | 
					const NodeTypePostgresSchema = new NodeType(SqlExecNodeType.PgSchema)
 | 
				
			||||||
    .withContextMenuItems([new ContextmenuItem('reloadTables', '刷新').withIcon('RefreshRight').withOnClick((data: any) => reloadNode(data.key))])
 | 
					    .withContextMenuItems([ContextmenuItemRefresh])
 | 
				
			||||||
    .withLoadNodesFunc(async (parentNode: TagTreeNode) => {
 | 
					    .withLoadNodesFunc(async (parentNode: TagTreeNode) => {
 | 
				
			||||||
        const params = parentNode.params;
 | 
					        const params = parentNode.params;
 | 
				
			||||||
        return [
 | 
					        params.parentKey = parentNode.key;
 | 
				
			||||||
            new TagTreeNode(`${params.id}.${params.db}.table-menu`, '表', NodeTypeTableMenu).withParams(params).withIcon(TableIcon),
 | 
					        return NodeTypeTables(params);
 | 
				
			||||||
            new TagTreeNode(getSqlMenuNodeKey(params.id, params.db), 'SQL', NodeTypeSqlMenu).withParams(params).withIcon(SqlIcon),
 | 
					 | 
				
			||||||
        ];
 | 
					 | 
				
			||||||
    })
 | 
					    })
 | 
				
			||||||
    .withNodeClickFunc(nodeClickChangeDb);
 | 
					    .withNodeClickFunc(nodeClickChangeDb);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// 数据库表菜单节点
 | 
					// 数据库表菜单节点
 | 
				
			||||||
const NodeTypeTableMenu = new NodeType(SqlExecNodeType.TableMenu)
 | 
					const NodeTypeTableMenu = new NodeType(SqlExecNodeType.TableMenu)
 | 
				
			||||||
    .withContextMenuItems([
 | 
					    .withContextMenuItems([
 | 
				
			||||||
        new ContextmenuItem('reloadTables', '刷新').withIcon('RefreshRight').withOnClick((data: any) => reloadNode(data.key)),
 | 
					        ContextmenuItemRefresh,
 | 
				
			||||||
 | 
					        new ContextmenuItem('createTable', '创建表').withIcon('Plus').withOnClick((data: any) => onEditTable(data)),
 | 
				
			||||||
        new ContextmenuItem('tablesOp', '表操作').withIcon('Setting').withOnClick((data: any) => {
 | 
					        new ContextmenuItem('tablesOp', '表操作').withIcon('Setting').withOnClick((data: any) => {
 | 
				
			||||||
            const params = data.params;
 | 
					            const params = data.params;
 | 
				
			||||||
            addTablesOpTab({ id: params.id, db: params.db, type: params.type, nodeKey: data.key });
 | 
					            addTablesOpTab({ id: params.id, db: params.db, type: params.type, nodeKey: data.key });
 | 
				
			||||||
@@ -303,30 +337,40 @@ const NodeTypeTableMenu = new NodeType(SqlExecNodeType.TableMenu)
 | 
				
			|||||||
    ])
 | 
					    ])
 | 
				
			||||||
    .withLoadNodesFunc(async (parentNode: TagTreeNode) => {
 | 
					    .withLoadNodesFunc(async (parentNode: TagTreeNode) => {
 | 
				
			||||||
        const params = parentNode.params;
 | 
					        const params = parentNode.params;
 | 
				
			||||||
        let { id, db } = params;
 | 
					        let { id, db, type, flowProcdefKey, schema } = 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;
 | 
				
			||||||
        let dbTableSize = 0;
 | 
					        let dbTableSize = 0;
 | 
				
			||||||
        const tablesNode = tables.map((x: any) => {
 | 
					        const tablesNode = tables.map((x: any) => {
 | 
				
			||||||
            dbTableSize += x.dataLength + x.indexLength;
 | 
					            const tableSize = x.dataLength + x.indexLength;
 | 
				
			||||||
            return new TagTreeNode(`${id}.${db}.${x.tableName}`, x.tableName, NodeTypeTable)
 | 
					            dbTableSize += tableSize;
 | 
				
			||||||
 | 
					            const key = `${id}.${db}.${x.tableName}`;
 | 
				
			||||||
 | 
					            return new TagTreeNode(key, x.tableName, NodeTypeTable)
 | 
				
			||||||
                .withIsLeaf(true)
 | 
					                .withIsLeaf(true)
 | 
				
			||||||
                .withParams({
 | 
					                .withParams({
 | 
				
			||||||
                    id,
 | 
					                    id,
 | 
				
			||||||
                    db,
 | 
					                    db,
 | 
				
			||||||
 | 
					                    type,
 | 
				
			||||||
 | 
					                    schema,
 | 
				
			||||||
 | 
					                    flowProcdefKey: flowProcdefKey,
 | 
				
			||||||
 | 
					                    key: key,
 | 
				
			||||||
 | 
					                    parentKey: parentNode.key,
 | 
				
			||||||
                    tableName: x.tableName,
 | 
					                    tableName: x.tableName,
 | 
				
			||||||
                    tableComment: x.tableComment,
 | 
					                    tableComment: x.tableComment,
 | 
				
			||||||
                    size: formatByteSize(x.dataLength + x.indexLength, 1),
 | 
					                    size: tableSize == 0 ? '' : formatByteSize(tableSize, 1),
 | 
				
			||||||
                })
 | 
					                })
 | 
				
			||||||
                .withIcon(TableIcon)
 | 
					                .withIcon(TableIcon)
 | 
				
			||||||
                .withLabelRemark(`${x.tableName} ${x.tableComment ? '| ' + x.tableComment : ''}`);
 | 
					                .withLabelRemark(`${x.tableName} ${x.tableComment ? '| ' + x.tableComment : ''}`);
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
        // 设置父节点参数的表大小
 | 
					        // 设置父节点参数的表大小
 | 
				
			||||||
        parentNode.params.dbTableSize = formatByteSize(dbTableSize);
 | 
					        parentNode.params.dbTableSize = dbTableSize == 0 ? '' : formatByteSize(dbTableSize);
 | 
				
			||||||
        return tablesNode;
 | 
					        return tablesNode;
 | 
				
			||||||
    })
 | 
					    })
 | 
				
			||||||
    .withNodeClickFunc(nodeClickChangeDb);
 | 
					    .withNodeDblclickFunc((node: TagTreeNode) => {
 | 
				
			||||||
 | 
					        const params = node.params;
 | 
				
			||||||
 | 
					        addTablesOpTab({ id: params.id, db: params.db, type: params.type, nodeKey: node.key });
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// 数据库sql模板菜单节点
 | 
					// 数据库sql模板菜单节点
 | 
				
			||||||
const NodeTypeSqlMenu = new NodeType(SqlExecNodeType.SqlMenu)
 | 
					const NodeTypeSqlMenu = new NodeType(SqlExecNodeType.SqlMenu)
 | 
				
			||||||
@@ -340,22 +384,24 @@ const NodeTypeSqlMenu = new NodeType(SqlExecNodeType.SqlMenu)
 | 
				
			|||||||
        return sqls.map((x: any) => {
 | 
					        return sqls.map((x: any) => {
 | 
				
			||||||
            return new TagTreeNode(`${id}.${db}.${x.name}`, x.name, NodeTypeSql)
 | 
					            return new TagTreeNode(`${id}.${db}.${x.name}`, x.name, NodeTypeSql)
 | 
				
			||||||
                .withIsLeaf(true)
 | 
					                .withIsLeaf(true)
 | 
				
			||||||
                .withParams({
 | 
					                .withParams({ id, db, dbs, sqlName: x.name })
 | 
				
			||||||
                    id,
 | 
					 | 
				
			||||||
                    db,
 | 
					 | 
				
			||||||
                    dbs,
 | 
					 | 
				
			||||||
                    sqlName: x.name,
 | 
					 | 
				
			||||||
                })
 | 
					 | 
				
			||||||
                .withIcon(SqlIcon);
 | 
					                .withIcon(SqlIcon);
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
    })
 | 
					    })
 | 
				
			||||||
    .withNodeClickFunc(nodeClickChangeDb);
 | 
					    .withNodeClickFunc(nodeClickChangeDb);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// 表节点类型
 | 
					// 表节点类型
 | 
				
			||||||
const NodeTypeTable = new NodeType(SqlExecNodeType.Table).withNodeClickFunc((nodeData: TagTreeNode) => {
 | 
					const NodeTypeTable = new NodeType(SqlExecNodeType.Table)
 | 
				
			||||||
    const params = nodeData.params;
 | 
					    .withContextMenuItems([
 | 
				
			||||||
    loadTableData({ id: params.id, nodeKey: nodeData.key }, params.db, params.tableName);
 | 
					        new ContextmenuItem('copyTable', '复制表').withIcon('copyDocument').withOnClick((data: any) => onCopyTable(data)),
 | 
				
			||||||
});
 | 
					        new ContextmenuItem('renameTable', '重命名').withIcon('edit').withOnClick((data: any) => onRenameTable(data)),
 | 
				
			||||||
 | 
					        new ContextmenuItem('editTable', '编辑表').withIcon('edit').withOnClick((data: any) => onEditTable(data)),
 | 
				
			||||||
 | 
					        new ContextmenuItem('delTable', '删除表').withIcon('Delete').withOnClick((data: any) => onDeleteTable(data)),
 | 
				
			||||||
 | 
					    ])
 | 
				
			||||||
 | 
					    .withNodeClickFunc((nodeData: TagTreeNode) => {
 | 
				
			||||||
 | 
					        const params = nodeData.params;
 | 
				
			||||||
 | 
					        loadTableData({ id: params.id, nodeKey: nodeData.key }, params.db, params.tableName);
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// sql模板节点类型
 | 
					// sql模板节点类型
 | 
				
			||||||
const NodeTypeSql = new NodeType(SqlExecNodeType.Sql)
 | 
					const NodeTypeSql = new NodeType(SqlExecNodeType.Sql)
 | 
				
			||||||
@@ -385,9 +431,20 @@ const state = reactive({
 | 
				
			|||||||
        loading: true,
 | 
					        loading: true,
 | 
				
			||||||
        version: '',
 | 
					        version: '',
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					    tableCreateDialog: {
 | 
				
			||||||
 | 
					        visible: false,
 | 
				
			||||||
 | 
					        title: '',
 | 
				
			||||||
 | 
					        activeName: '',
 | 
				
			||||||
 | 
					        dbId: 0,
 | 
				
			||||||
 | 
					        db: '',
 | 
				
			||||||
 | 
					        dbType: '',
 | 
				
			||||||
 | 
					        flowProcdefKey: '',
 | 
				
			||||||
 | 
					        data: {},
 | 
				
			||||||
 | 
					        parentKey: '',
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const { nowDbInst } = toRefs(state);
 | 
					const { nowDbInst, tableCreateDialog } = toRefs(state);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const serverInfoReqParam = ref({
 | 
					const serverInfoReqParam = ref({
 | 
				
			||||||
    instanceId: 0,
 | 
					    instanceId: 0,
 | 
				
			||||||
@@ -408,7 +465,7 @@ onBeforeUnmount(() => {
 | 
				
			|||||||
 * 设置editor高度和数据表高度
 | 
					 * 设置editor高度和数据表高度
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
const setHeight = () => {
 | 
					const setHeight = () => {
 | 
				
			||||||
    state.dataTabsTableHeight = window.innerHeight - 270 + 'px';
 | 
					    state.dataTabsTableHeight = window.innerHeight - 253 + 'px';
 | 
				
			||||||
    state.tablesOpHeight = window.innerHeight - 225 + 'px';
 | 
					    state.tablesOpHeight = window.innerHeight - 225 + 'px';
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -603,6 +660,127 @@ const reloadNode = (nodeKey: string) => {
 | 
				
			|||||||
    tagTreeRef.value.reloadNode(nodeKey);
 | 
					    tagTreeRef.value.reloadNode(nodeKey);
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const onEditTable = async (data: any) => {
 | 
				
			||||||
 | 
					    let { db, id, tableName, tableComment, type, parentKey, key, flowProcdefKey } = data.params;
 | 
				
			||||||
 | 
					    // data.label就是表名
 | 
				
			||||||
 | 
					    if (tableName) {
 | 
				
			||||||
 | 
					        state.tableCreateDialog.title = '修改表';
 | 
				
			||||||
 | 
					        let indexs = await dbApi.tableIndex.request({ id, db, tableName });
 | 
				
			||||||
 | 
					        let columns = await dbApi.columnMetadata.request({ id, db, tableName });
 | 
				
			||||||
 | 
					        let row = { tableName, tableComment };
 | 
				
			||||||
 | 
					        state.tableCreateDialog.data = { edit: true, row, indexs, columns };
 | 
				
			||||||
 | 
					        state.tableCreateDialog.parentKey = parentKey;
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					        state.tableCreateDialog.title = '创建表';
 | 
				
			||||||
 | 
					        state.tableCreateDialog.data = { edit: false, row: {} };
 | 
				
			||||||
 | 
					        state.tableCreateDialog.parentKey = key;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    state.tableCreateDialog.activeName = '1';
 | 
				
			||||||
 | 
					    state.tableCreateDialog.dbId = id;
 | 
				
			||||||
 | 
					    state.tableCreateDialog.db = db;
 | 
				
			||||||
 | 
					    state.tableCreateDialog.dbType = type;
 | 
				
			||||||
 | 
					    state.tableCreateDialog.flowProcdefKey = flowProcdefKey;
 | 
				
			||||||
 | 
					    state.tableCreateDialog.visible = true;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const onDeleteTable = async (data: any) => {
 | 
				
			||||||
 | 
					    let { db, id, tableName, parentKey, flowProcdefKey, schema } = data.params;
 | 
				
			||||||
 | 
					    await ElMessageBox.confirm(`此操作是永久性且无法撤销,确定删除【${tableName}】? `, '提示', {
 | 
				
			||||||
 | 
					        confirmButtonText: '确定',
 | 
				
			||||||
 | 
					        cancelButtonText: '取消',
 | 
				
			||||||
 | 
					        type: 'warning',
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // 执行sql
 | 
				
			||||||
 | 
					    let dialect = getDbDialect(state.nowDbInst.type);
 | 
				
			||||||
 | 
					    let schemaStr = schema ? `${dialect.quoteIdentifier(schema)}.` : '';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    dbApi.sqlExec.request({ id, db, sql: `drop table ${schemaStr + dialect.quoteIdentifier(tableName)}` }).then(() => {
 | 
				
			||||||
 | 
					        if (flowProcdefKey) {
 | 
				
			||||||
 | 
					            ElMessage.success('工单提交成功');
 | 
				
			||||||
 | 
					            return;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        ElMessage.success('删除成功');
 | 
				
			||||||
 | 
					        setTimeout(() => {
 | 
				
			||||||
 | 
					            parentKey && reloadNode(parentKey);
 | 
				
			||||||
 | 
					        }, 1000);
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const onRenameTable = async (data: any) => {
 | 
				
			||||||
 | 
					    let { db, id, tableName, parentKey, flowProcdefKey } = data.params;
 | 
				
			||||||
 | 
					    let tableData = { db, oldTableName: tableName, tableName };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let value = ref(tableName);
 | 
				
			||||||
 | 
					    // 弹出确认框
 | 
				
			||||||
 | 
					    const promptValue = await ElMessageBox.prompt('', `重命名表【${db}.${tableName}】`, {
 | 
				
			||||||
 | 
					        inputValue: value.value,
 | 
				
			||||||
 | 
					        confirmButtonText: '确定',
 | 
				
			||||||
 | 
					        cancelButtonText: '取消',
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    tableData.tableName = promptValue.value;
 | 
				
			||||||
 | 
					    let sql = nowDbInst.value.getDialect().getModifyTableInfoSql(tableData);
 | 
				
			||||||
 | 
					    if (!sql) {
 | 
				
			||||||
 | 
					        ElMessage.warning('无更改');
 | 
				
			||||||
 | 
					        return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    SqlExecBox({
 | 
				
			||||||
 | 
					        sql: sql,
 | 
				
			||||||
 | 
					        dbId: id as any,
 | 
				
			||||||
 | 
					        db: db as any,
 | 
				
			||||||
 | 
					        dbType: nowDbInst.value.getDialect().getInfo().formatSqlDialect,
 | 
				
			||||||
 | 
					        flowProcdefKey: flowProcdefKey,
 | 
				
			||||||
 | 
					        runSuccessCallback: () => {
 | 
				
			||||||
 | 
					            setTimeout(() => {
 | 
				
			||||||
 | 
					                parentKey && reloadNode(parentKey);
 | 
				
			||||||
 | 
					            }, 1000);
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const onCopyTable = async (data: any) => {
 | 
				
			||||||
 | 
					    let { db, id, tableName, parentKey } = data.params;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let checked = ref(false);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // 弹出确认框,并选择是否复制数据
 | 
				
			||||||
 | 
					    await ElMessageBox({
 | 
				
			||||||
 | 
					        title: `复制表【${tableName}】`,
 | 
				
			||||||
 | 
					        type: 'warning',
 | 
				
			||||||
 | 
					        //  icon: markRaw(Delete),
 | 
				
			||||||
 | 
					        message: () =>
 | 
				
			||||||
 | 
					            h(ElCheckbox, {
 | 
				
			||||||
 | 
					                label: '是否复制数据?',
 | 
				
			||||||
 | 
					                modelValue: checked.value,
 | 
				
			||||||
 | 
					                'onUpdate:modelValue': (val: boolean | string | number) => {
 | 
				
			||||||
 | 
					                    if (typeof val === 'boolean') {
 | 
				
			||||||
 | 
					                        checked.value = val;
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					            }),
 | 
				
			||||||
 | 
					        callback: (action: string) => {
 | 
				
			||||||
 | 
					            if (action === 'confirm') {
 | 
				
			||||||
 | 
					                // 执行sql
 | 
				
			||||||
 | 
					                dbApi.copyTable.request({ id, db, tableName, copyData: checked.value }).then(() => {
 | 
				
			||||||
 | 
					                    ElMessage.success('复制成功');
 | 
				
			||||||
 | 
					                    setTimeout(() => {
 | 
				
			||||||
 | 
					                        parentKey && reloadNode(parentKey);
 | 
				
			||||||
 | 
					                    }, 1000);
 | 
				
			||||||
 | 
					                });
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const onSubmitEditTableSql = () => {
 | 
				
			||||||
 | 
					    state.tableCreateDialog.visible = false;
 | 
				
			||||||
 | 
					    state.tableCreateDialog.data = { edit: false, row: {} };
 | 
				
			||||||
 | 
					    reloadNode(state.tableCreateDialog.parentKey);
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * 获取当前操作的数据库信息
 | 
					 * 获取当前操作的数据库信息
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
@@ -614,6 +792,7 @@ const getNowDbInfo = () => {
 | 
				
			|||||||
        name: di.name,
 | 
					        name: di.name,
 | 
				
			||||||
        type: di.type,
 | 
					        type: di.type,
 | 
				
			||||||
        host: di.host,
 | 
					        host: di.host,
 | 
				
			||||||
 | 
					        flowProcdefKey: di.flowProcdefKey,
 | 
				
			||||||
        dbName: state.db,
 | 
					        dbName: state.db,
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -16,7 +16,7 @@
 | 
				
			|||||||
                            <el-row>
 | 
					                            <el-row>
 | 
				
			||||||
                                <el-col :span="11">
 | 
					                                <el-col :span="11">
 | 
				
			||||||
                                    <el-form-item prop="taskName" label="任务名" required>
 | 
					                                    <el-form-item prop="taskName" label="任务名" required>
 | 
				
			||||||
                                        <el-input v-model.trim="form.taskName" placeholder="请输入数据库别名" auto-complete="off" />
 | 
					                                        <el-input v-model.trim="form.taskName" placeholder="请输入同步任务名" auto-complete="off" />
 | 
				
			||||||
                                    </el-form-item>
 | 
					                                    </el-form-item>
 | 
				
			||||||
                                </el-col>
 | 
					                                </el-col>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -45,8 +45,10 @@
 | 
				
			|||||||
                            <db-select-tree
 | 
					                            <db-select-tree
 | 
				
			||||||
                                placeholder="请选择源数据库"
 | 
					                                placeholder="请选择源数据库"
 | 
				
			||||||
                                v-model:db-id="form.srcDbId"
 | 
					                                v-model:db-id="form.srcDbId"
 | 
				
			||||||
 | 
					                                v-model:inst-name="form.srcInstName"
 | 
				
			||||||
                                v-model:db-name="form.srcDbName"
 | 
					                                v-model:db-name="form.srcDbName"
 | 
				
			||||||
                                v-model:tag-path="form.srcTagPath"
 | 
					                                v-model:tag-path="form.srcTagPath"
 | 
				
			||||||
 | 
					                                v-model:db-type="form.srcDbType"
 | 
				
			||||||
                                @select-db="onSelectSrcDb"
 | 
					                                @select-db="onSelectSrcDb"
 | 
				
			||||||
                            />
 | 
					                            />
 | 
				
			||||||
                        </el-form-item>
 | 
					                        </el-form-item>
 | 
				
			||||||
@@ -55,8 +57,10 @@
 | 
				
			|||||||
                            <db-select-tree
 | 
					                            <db-select-tree
 | 
				
			||||||
                                placeholder="请选择目标数据库"
 | 
					                                placeholder="请选择目标数据库"
 | 
				
			||||||
                                v-model:db-id="form.targetDbId"
 | 
					                                v-model:db-id="form.targetDbId"
 | 
				
			||||||
 | 
					                                v-model:inst-name="form.targetInstName"
 | 
				
			||||||
                                v-model:db-name="form.targetDbName"
 | 
					                                v-model:db-name="form.targetDbName"
 | 
				
			||||||
                                v-model:tag-path="form.targetTagPath"
 | 
					                                v-model:tag-path="form.targetTagPath"
 | 
				
			||||||
 | 
					                                v-model:db-type="form.targetDbType"
 | 
				
			||||||
                                @select-db="onSelectTargetDb"
 | 
					                                @select-db="onSelectTargetDb"
 | 
				
			||||||
                            />
 | 
					                            />
 | 
				
			||||||
                        </el-form-item>
 | 
					                        </el-form-item>
 | 
				
			||||||
@@ -85,9 +89,11 @@
 | 
				
			|||||||
                                </el-col>
 | 
					                                </el-col>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                                <el-col :span="8">
 | 
					                                <el-col :span="8">
 | 
				
			||||||
                                    <el-form-item prop="updField" label="更新字段" required>
 | 
					                                    <el-tooltip content="查询数据源的时候会带上这个字段当前最大值,支持带别名,如:t.create_time" placement="top">
 | 
				
			||||||
                                        <el-input v-model.trim="form.updField" placeholder="查询数据源的时候会带上这个字段当前最大值" auto-complete="off" />
 | 
					                                        <el-form-item prop="updField" label="更新字段" required>
 | 
				
			||||||
                                    </el-form-item>
 | 
					                                            <el-input v-model.trim="form.updField" placeholder="查询数据源的时候会带上这个字段当前最大值" auto-complete="off" />
 | 
				
			||||||
 | 
					                                        </el-form-item>
 | 
				
			||||||
 | 
					                                    </el-tooltip>
 | 
				
			||||||
                                </el-col>
 | 
					                                </el-col>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                                <el-col :span="8">
 | 
					                                <el-col :span="8">
 | 
				
			||||||
@@ -109,7 +115,7 @@
 | 
				
			|||||||
                                            <el-option
 | 
					                                            <el-option
 | 
				
			||||||
                                                v-for="item in state.targetColumnList"
 | 
					                                                v-for="item in state.targetColumnList"
 | 
				
			||||||
                                                :key="item.columnName"
 | 
					                                                :key="item.columnName"
 | 
				
			||||||
                                                :label="item.columnName + ` ${item.columnType}` + (item.columnComment && ' - ' + item.columnComment)"
 | 
					                                                :label="item.columnName + ` ${item.showDataType}` + (item.columnComment && ' - ' + item.columnComment)"
 | 
				
			||||||
                                                :value="item.columnName"
 | 
					                                                :value="item.columnName"
 | 
				
			||||||
                                            />
 | 
					                                            />
 | 
				
			||||||
                                        </el-select>
 | 
					                                        </el-select>
 | 
				
			||||||
@@ -121,10 +127,17 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
                    <el-tab-pane label="sql预览" :name="sqlPreviewTab" :disabled="!baseFieldCompleted">
 | 
					                    <el-tab-pane label="sql预览" :name="sqlPreviewTab" :disabled="!baseFieldCompleted">
 | 
				
			||||||
                        <el-form-item prop="fieldMap" label="查询sql">
 | 
					                        <el-form-item prop="fieldMap" label="查询sql">
 | 
				
			||||||
                            <el-input type="textarea" v-model="state.previewDataSql" readonly :input-style="{ height: '190px' }" />
 | 
					                            <el-input type="textarea" v-model="state.previewDataSql" readonly :input-style="{ height: '170px' }" />
 | 
				
			||||||
                        </el-form-item>
 | 
					                        </el-form-item>
 | 
				
			||||||
                        <el-form-item prop="fieldMap" label="插入sql">
 | 
					                        <el-form-item prop="fieldMap" label="插入sql">
 | 
				
			||||||
                            <el-input type="textarea" v-model="state.previewInsertSql" readonly :input-style="{ height: '190px' }" />
 | 
					                            <el-input type="textarea" v-model="state.previewInsertSql" readonly :input-style="{ height: '170px' }" />
 | 
				
			||||||
 | 
					                        </el-form-item>
 | 
				
			||||||
 | 
					                        <el-form-item prop="isReplace" v-if="compatibleDuplicateStrategy(form.targetDbType!)" label="键冲突策略">
 | 
				
			||||||
 | 
					                            <el-select v-model="form.duplicateStrategy" @change="handleDuplicateStrategy" style="width: 100px">
 | 
				
			||||||
 | 
					                                <el-option label="无" :value="DuplicateStrategy.NONE" />
 | 
				
			||||||
 | 
					                                <el-option label="忽略" :value="DuplicateStrategy.IGNORE" />
 | 
				
			||||||
 | 
					                                <el-option label="替换" :value="DuplicateStrategy.REPLACE" />
 | 
				
			||||||
 | 
					                            </el-select>
 | 
				
			||||||
                        </el-form-item>
 | 
					                        </el-form-item>
 | 
				
			||||||
                    </el-tab-pane>
 | 
					                    </el-tab-pane>
 | 
				
			||||||
                </el-tabs>
 | 
					                </el-tabs>
 | 
				
			||||||
@@ -181,7 +194,7 @@ import { ElMessage } from 'element-plus';
 | 
				
			|||||||
import DbSelectTree from '@/views/ops/db/component/DbSelectTree.vue';
 | 
					import DbSelectTree from '@/views/ops/db/component/DbSelectTree.vue';
 | 
				
			||||||
import MonacoEditor from '@/components/monaco/MonacoEditor.vue';
 | 
					import MonacoEditor from '@/components/monaco/MonacoEditor.vue';
 | 
				
			||||||
import { DbInst, registerDbCompletionItemProvider } from '@/views/ops/db/db';
 | 
					import { DbInst, registerDbCompletionItemProvider } from '@/views/ops/db/db';
 | 
				
			||||||
import { getDbDialect } from '@/views/ops/db/dialect';
 | 
					import { compatibleDuplicateStrategy, DbType, DuplicateStrategy, getDbDialect } from '@/views/ops/db/dialect';
 | 
				
			||||||
import CrontabInput from '@/components/crontab/CrontabInput.vue';
 | 
					import CrontabInput from '@/components/crontab/CrontabInput.vue';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const props = defineProps({
 | 
					const props = defineProps({
 | 
				
			||||||
@@ -226,18 +239,23 @@ type FormData = {
 | 
				
			|||||||
    taskName?: string;
 | 
					    taskName?: string;
 | 
				
			||||||
    taskCron: string;
 | 
					    taskCron: string;
 | 
				
			||||||
    srcDbId?: number;
 | 
					    srcDbId?: number;
 | 
				
			||||||
 | 
					    srcInstName?: string;
 | 
				
			||||||
    srcDbName?: string;
 | 
					    srcDbName?: string;
 | 
				
			||||||
 | 
					    srcDbType?: string;
 | 
				
			||||||
    srcTagPath?: string;
 | 
					    srcTagPath?: string;
 | 
				
			||||||
    targetDbId?: number;
 | 
					    targetDbId?: number;
 | 
				
			||||||
 | 
					    targetInstName?: string;
 | 
				
			||||||
    targetDbName?: string;
 | 
					    targetDbName?: string;
 | 
				
			||||||
    targetTagPath?: string;
 | 
					    targetTagPath?: string;
 | 
				
			||||||
    targetTableName?: string;
 | 
					    targetTableName?: string;
 | 
				
			||||||
 | 
					    targetDbType?: string;
 | 
				
			||||||
    dataSql?: string;
 | 
					    dataSql?: string;
 | 
				
			||||||
    pageSize?: number;
 | 
					    pageSize?: number;
 | 
				
			||||||
    updField?: string;
 | 
					    updField?: string;
 | 
				
			||||||
    updFieldVal?: string;
 | 
					    updFieldVal?: string;
 | 
				
			||||||
    fieldMap?: { src: string; target: string }[];
 | 
					    fieldMap?: { src: string; target: string }[];
 | 
				
			||||||
    status?: 1 | 2;
 | 
					    status?: 1 | 2;
 | 
				
			||||||
 | 
					    duplicateStrategy?: -1 | 1 | 2;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const basicFormData = {
 | 
					const basicFormData = {
 | 
				
			||||||
@@ -245,10 +263,11 @@ const basicFormData = {
 | 
				
			|||||||
    targetDbId: -1,
 | 
					    targetDbId: -1,
 | 
				
			||||||
    dataSql: 'select * from',
 | 
					    dataSql: 'select * from',
 | 
				
			||||||
    pageSize: 1000,
 | 
					    pageSize: 1000,
 | 
				
			||||||
    updField: 'id',
 | 
					    updField: '',
 | 
				
			||||||
    updFieldVal: '0',
 | 
					    updFieldVal: '0',
 | 
				
			||||||
    fieldMap: [{ src: 'a', target: 'b' }],
 | 
					    fieldMap: [{ src: 'a', target: 'b' }],
 | 
				
			||||||
    status: 1,
 | 
					    status: 1,
 | 
				
			||||||
 | 
					    duplicateStrategy: -1,
 | 
				
			||||||
} as FormData;
 | 
					} as FormData;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const state = reactive({
 | 
					const state = reactive({
 | 
				
			||||||
@@ -263,6 +282,7 @@ const state = reactive({
 | 
				
			|||||||
    previewRes: {} as any,
 | 
					    previewRes: {} as any,
 | 
				
			||||||
    previewDataSql: '',
 | 
					    previewDataSql: '',
 | 
				
			||||||
    previewInsertSql: '',
 | 
					    previewInsertSql: '',
 | 
				
			||||||
 | 
					    previewFieldArr: [] as string[],
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const { tabActiveName, form, submitForm } = toRefs(state);
 | 
					const { tabActiveName, form, submitForm } = toRefs(state);
 | 
				
			||||||
@@ -281,12 +301,17 @@ watch(dialogVisible, async (newValue: boolean) => {
 | 
				
			|||||||
    state.tabActiveName = 'basic';
 | 
					    state.tabActiveName = 'basic';
 | 
				
			||||||
    const propsData = props.data as any;
 | 
					    const propsData = props.data as any;
 | 
				
			||||||
    if (!propsData?.id) {
 | 
					    if (!propsData?.id) {
 | 
				
			||||||
        state.form = basicFormData;
 | 
					        let d = {} as FormData;
 | 
				
			||||||
 | 
					        Object.assign(d, basicFormData);
 | 
				
			||||||
 | 
					        state.form = d;
 | 
				
			||||||
        return;
 | 
					        return;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    let data = await dbApi.getDatasyncTask.request({ taskId: propsData?.id });
 | 
					    let data = await dbApi.getDatasyncTask.request({ taskId: propsData?.id });
 | 
				
			||||||
    state.form = data;
 | 
					    state.form = data;
 | 
				
			||||||
 | 
					    if (!state.form.duplicateStrategy) {
 | 
				
			||||||
 | 
					        state.form.duplicateStrategy = -1;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
    try {
 | 
					    try {
 | 
				
			||||||
        state.form.fieldMap = JSON.parse(data.fieldMap);
 | 
					        state.form.fieldMap = JSON.parse(data.fieldMap);
 | 
				
			||||||
    } catch (e) {
 | 
					    } catch (e) {
 | 
				
			||||||
@@ -302,6 +327,8 @@ watch(dialogVisible, async (newValue: boolean) => {
 | 
				
			|||||||
        // 初始化实例
 | 
					        // 初始化实例
 | 
				
			||||||
        db.databases = db.database?.split(' ').sort() || [];
 | 
					        db.databases = db.database?.split(' ').sort() || [];
 | 
				
			||||||
        state.srcDbInst = DbInst.getOrNewInst(db);
 | 
					        state.srcDbInst = DbInst.getOrNewInst(db);
 | 
				
			||||||
 | 
					        state.form.srcDbType = state.srcDbInst.type;
 | 
				
			||||||
 | 
					        state.form.srcInstName = db.instanceName;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    //  初始化target数据源
 | 
					    //  初始化target数据源
 | 
				
			||||||
@@ -312,6 +339,8 @@ watch(dialogVisible, async (newValue: boolean) => {
 | 
				
			|||||||
        // 初始化实例
 | 
					        // 初始化实例
 | 
				
			||||||
        db.databases = db.database?.split(' ').sort() || [];
 | 
					        db.databases = db.database?.split(' ').sort() || [];
 | 
				
			||||||
        state.targetDbInst = DbInst.getOrNewInst(db);
 | 
					        state.targetDbInst = DbInst.getOrNewInst(db);
 | 
				
			||||||
 | 
					        state.form.targetDbType = state.targetDbInst.type;
 | 
				
			||||||
 | 
					        state.form.targetInstName = db.instanceName;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (targetDbId && state.form.targetDbName) {
 | 
					    if (targetDbId && state.form.targetDbName) {
 | 
				
			||||||
@@ -331,13 +360,12 @@ watch(tabActiveName, async (newValue: string) => {
 | 
				
			|||||||
            await handleGetTargetFields();
 | 
					            await handleGetTargetFields();
 | 
				
			||||||
            break;
 | 
					            break;
 | 
				
			||||||
        case sqlPreviewTab:
 | 
					        case sqlPreviewTab:
 | 
				
			||||||
            let srcDbDialect = getDbDialect(state.srcDbInst.type);
 | 
					 | 
				
			||||||
            let targetDbDialect = getDbDialect(state.targetDbInst.type);
 | 
					            let targetDbDialect = getDbDialect(state.targetDbInst.type);
 | 
				
			||||||
 | 
					            let updField = state.form.updField!;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            let updField = srcDbDialect.quoteIdentifier(state.form.updField!);
 | 
					            // 判断sql是否以where .*结尾
 | 
				
			||||||
            state.previewDataSql = `SELECT * FROM (\n ${state.form.dataSql?.trim() || '请输入数据sql'} \n ) t \n where ${updField} > '${
 | 
					            let hasCondition = /where/i.test(state.form.dataSql!);
 | 
				
			||||||
                state.form.updFieldVal || ''
 | 
					            state.previewDataSql = `${state.form.dataSql?.trim() || '请输入数据sql'} \n ${hasCondition ? 'and' : 'where'} ${updField} > '${state.form.updFieldVal || ''}'`;
 | 
				
			||||||
            }'`;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
            // 检查字段映射中是否存在重复的目标字段
 | 
					            // 检查字段映射中是否存在重复的目标字段
 | 
				
			||||||
            let fields = new Set();
 | 
					            let fields = new Set();
 | 
				
			||||||
@@ -353,17 +381,19 @@ watch(tabActiveName, async (newValue: string) => {
 | 
				
			|||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            let fieldArr = state.form.fieldMap?.map((a: any) => targetDbDialect.quoteIdentifier(a.target)) || [];
 | 
					            let fieldArr = state.form.fieldMap?.map((a: any) => targetDbDialect.quoteIdentifier(a.target)) || [];
 | 
				
			||||||
            let placeholder = '?'.repeat(fieldArr.length).split('').join(',');
 | 
					            state.previewFieldArr = fieldArr;
 | 
				
			||||||
 | 
					            refreshPreviewInsertSql();
 | 
				
			||||||
            state.previewInsertSql = ` insert into ${targetDbDialect.quoteIdentifier(state.form.targetTableName!)}(${fieldArr.join(
 | 
					 | 
				
			||||||
                ','
 | 
					 | 
				
			||||||
            )}) values (${placeholder});`;
 | 
					 | 
				
			||||||
            break;
 | 
					            break;
 | 
				
			||||||
        default:
 | 
					        default:
 | 
				
			||||||
            break;
 | 
					            break;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const refreshPreviewInsertSql = () => {
 | 
				
			||||||
 | 
					    let targetDbDialect = getDbDialect(state.targetDbInst.type);
 | 
				
			||||||
 | 
					    state.previewInsertSql = targetDbDialect.getBatchInsertPreviewSql(state.form.targetTableName!, state.previewFieldArr, state.form.duplicateStrategy!);
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const onSelectSrcDb = async (params: any) => {
 | 
					const onSelectSrcDb = async (params: any) => {
 | 
				
			||||||
    //  初始化数据源
 | 
					    //  初始化数据源
 | 
				
			||||||
    params.databases = params.dbs; // 数据源里需要这个值
 | 
					    params.databases = params.dbs; // 数据源里需要这个值
 | 
				
			||||||
@@ -396,8 +426,8 @@ const handleGetSrcFields = async () => {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // 判断sql是否是查询语句
 | 
					    // 判断sql是否是查询语句
 | 
				
			||||||
    if (!/^select/i.test(state.form.dataSql!)) {
 | 
					    if (!/^select/i.test(state.form.dataSql.trim()!)) {
 | 
				
			||||||
        let msg = 'sql语句错误,请输入查询语句';
 | 
					        let msg = 'sql语句错误,请输入select语句';
 | 
				
			||||||
        ElMessage.warning(msg);
 | 
					        ElMessage.warning(msg);
 | 
				
			||||||
        return;
 | 
					        return;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@@ -410,10 +440,24 @@ const handleGetSrcFields = async () => {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // 执行sql
 | 
					    // 执行sql
 | 
				
			||||||
 | 
					    let sql: string;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (state.form.srcDbType === DbType.mssql) {
 | 
				
			||||||
 | 
					        // mssql的分页语法不一样
 | 
				
			||||||
 | 
					        let top1 = `select top 1`;
 | 
				
			||||||
 | 
					        sql = `${top1} * from (${state.form.dataSql}) a`;
 | 
				
			||||||
 | 
					    } else if (state.form.srcDbType === DbType.oracle) {
 | 
				
			||||||
 | 
					        // oracle的分页关键字不一样
 | 
				
			||||||
 | 
					        let hasCondition = /where/i.test(state.form.dataSql!);
 | 
				
			||||||
 | 
					        sql = `${state.form.dataSql} ${hasCondition ? 'and' : 'where'} rownum <= 1`;
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					        sql = `${state.form.dataSql} limit 1`;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const res = await dbApi.sqlExec.request({
 | 
					    const res = await dbApi.sqlExec.request({
 | 
				
			||||||
        id: state.form.srcDbId,
 | 
					        id: state.form.srcDbId,
 | 
				
			||||||
        db: state.form.srcDbName,
 | 
					        db: state.form.srcDbName,
 | 
				
			||||||
        sql: state.form.dataSql.trim() + ' limit 1',
 | 
					        sql,
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (!res.columns) {
 | 
					    if (!res.columns) {
 | 
				
			||||||
@@ -487,6 +531,11 @@ const btnOk = async () => {
 | 
				
			|||||||
const cancel = () => {
 | 
					const cancel = () => {
 | 
				
			||||||
    dialogVisible.value = false;
 | 
					    dialogVisible.value = false;
 | 
				
			||||||
    emit('cancel');
 | 
					    emit('cancel');
 | 
				
			||||||
 | 
					    state.form = basicFormData;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const handleDuplicateStrategy = () => {
 | 
				
			||||||
 | 
					    refreshPreviewInsertSql();
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
</script>
 | 
					</script>
 | 
				
			||||||
<style lang="scss">
 | 
					<style lang="scss">
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -71,11 +71,13 @@ const searchItems = [SearchItem.input('name', '名称')];
 | 
				
			|||||||
// 任务名、修改人、修改时间、最近一次任务执行状态、状态(停用启用)、操作
 | 
					// 任务名、修改人、修改时间、最近一次任务执行状态、状态(停用启用)、操作
 | 
				
			||||||
const columns = ref([
 | 
					const columns = ref([
 | 
				
			||||||
    TableColumn.new('taskName', '任务名'),
 | 
					    TableColumn.new('taskName', '任务名'),
 | 
				
			||||||
    TableColumn.new('runningState', '运行状态').alignCenter().typeTag(DbDataSyncRunningStateEnum),
 | 
					    TableColumn.new('runningState', '运行状态').typeTag(DbDataSyncRunningStateEnum),
 | 
				
			||||||
    TableColumn.new('recentState', '最近任务状态').alignCenter().typeTag(DbDataSyncRecentStateEnum),
 | 
					    TableColumn.new('recentState', '最近任务状态').typeTag(DbDataSyncRecentStateEnum),
 | 
				
			||||||
    TableColumn.new('status', '状态').alignCenter().isSlot(),
 | 
					    TableColumn.new('status', '状态').isSlot(),
 | 
				
			||||||
    TableColumn.new('modifier', '修改人').alignCenter(),
 | 
					    TableColumn.new('creator', '创建人'),
 | 
				
			||||||
    TableColumn.new('updateTime', '修改时间').alignCenter().isTime(),
 | 
					    TableColumn.new('createTime', '创建时间').isTime(),
 | 
				
			||||||
 | 
					    TableColumn.new('modifier', '修改人'),
 | 
				
			||||||
 | 
					    TableColumn.new('updateTime', '修改时间').isTime(),
 | 
				
			||||||
]);
 | 
					]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// 该用户拥有的的操作列按钮权限
 | 
					// 该用户拥有的的操作列按钮权限
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -11,6 +11,7 @@ export const dbApi = {
 | 
				
			|||||||
    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'),
 | 
				
			||||||
 | 
					    copyTable: Api.newPost('/dbs/{id}/copy-table'),
 | 
				
			||||||
    columnMetadata: Api.newGet('/dbs/{id}/c-metadata'),
 | 
					    columnMetadata: Api.newGet('/dbs/{id}/c-metadata'),
 | 
				
			||||||
    pgSchemas: Api.newGet('/dbs/{id}/pg/schemas'),
 | 
					    pgSchemas: Api.newGet('/dbs/{id}/pg/schemas'),
 | 
				
			||||||
    // 获取表即列提示
 | 
					    // 获取表即列提示
 | 
				
			||||||
@@ -34,7 +35,7 @@ export const dbApi = {
 | 
				
			|||||||
    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/sql-execs'),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    instances: Api.newGet('/instances'),
 | 
					    instances: Api.newGet('/instances'),
 | 
				
			||||||
    getInstance: Api.newGet('/instances/{instanceId}'),
 | 
					    getInstance: Api.newGet('/instances/{instanceId}'),
 | 
				
			||||||
@@ -42,22 +43,25 @@ export const dbApi = {
 | 
				
			|||||||
    getInstanceServerInfo: Api.newGet('/instances/{instanceId}/server-info'),
 | 
					    getInstanceServerInfo: Api.newGet('/instances/{instanceId}/server-info'),
 | 
				
			||||||
    testConn: Api.newPost('/instances/test-conn'),
 | 
					    testConn: Api.newPost('/instances/test-conn'),
 | 
				
			||||||
    saveInstance: Api.newPost('/instances'),
 | 
					    saveInstance: Api.newPost('/instances'),
 | 
				
			||||||
    getInstancePwd: Api.newGet('/instances/{id}/pwd'),
 | 
					 | 
				
			||||||
    deleteInstance: Api.newDelete('/instances/{id}'),
 | 
					    deleteInstance: Api.newDelete('/instances/{id}'),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // 获取数据库备份列表
 | 
					    // 获取数据库备份列表
 | 
				
			||||||
    getDbBackups: Api.newGet('/dbs/{dbId}/backups'),
 | 
					    getDbBackups: Api.newGet('/dbs/{dbId}/backups'),
 | 
				
			||||||
    createDbBackup: Api.newPost('/dbs/{dbId}/backups'),
 | 
					    createDbBackup: Api.newPost('/dbs/{dbId}/backups'),
 | 
				
			||||||
 | 
					    deleteDbBackup: Api.newDelete('/dbs/{dbId}/backups/{backupId}'),
 | 
				
			||||||
    getDbNamesWithoutBackup: Api.newGet('/dbs/{dbId}/db-names-without-backup'),
 | 
					    getDbNamesWithoutBackup: Api.newGet('/dbs/{dbId}/db-names-without-backup'),
 | 
				
			||||||
    enableDbBackup: Api.newPut('/dbs/{dbId}/backups/{backupId}/enable'),
 | 
					    enableDbBackup: Api.newPut('/dbs/{dbId}/backups/{backupId}/enable'),
 | 
				
			||||||
    disableDbBackup: Api.newPut('/dbs/{dbId}/backups/{backupId}/disable'),
 | 
					    disableDbBackup: Api.newPut('/dbs/{dbId}/backups/{backupId}/disable'),
 | 
				
			||||||
    startDbBackup: Api.newPut('/dbs/{dbId}/backups/{backupId}/start'),
 | 
					    startDbBackup: Api.newPut('/dbs/{dbId}/backups/{backupId}/start'),
 | 
				
			||||||
    saveDbBackup: Api.newPut('/dbs/{dbId}/backups/{id}'),
 | 
					    saveDbBackup: Api.newPut('/dbs/{dbId}/backups/{id}'),
 | 
				
			||||||
    getDbBackupHistories: Api.newGet('/dbs/{dbId}/backup-histories'),
 | 
					    getDbBackupHistories: Api.newGet('/dbs/{dbId}/backup-histories'),
 | 
				
			||||||
 | 
					    restoreDbBackupHistory: Api.newPost('/dbs/{dbId}/backup-histories/{backupHistoryId}/restore'),
 | 
				
			||||||
 | 
					    deleteDbBackupHistory: Api.newDelete('/dbs/{dbId}/backup-histories/{backupHistoryId}'),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // 获取数据库备份列表
 | 
					    // 获取数据库恢复列表
 | 
				
			||||||
    getDbRestores: Api.newGet('/dbs/{dbId}/restores'),
 | 
					    getDbRestores: Api.newGet('/dbs/{dbId}/restores'),
 | 
				
			||||||
    createDbRestore: Api.newPost('/dbs/{dbId}/restores'),
 | 
					    createDbRestore: Api.newPost('/dbs/{dbId}/restores'),
 | 
				
			||||||
 | 
					    deleteDbRestore: Api.newDelete('/dbs/{dbId}/restores/{restoreId}'),
 | 
				
			||||||
    getDbNamesWithoutRestore: Api.newGet('/dbs/{dbId}/db-names-without-restore'),
 | 
					    getDbNamesWithoutRestore: Api.newGet('/dbs/{dbId}/db-names-without-restore'),
 | 
				
			||||||
    enableDbRestore: Api.newPut('/dbs/{dbId}/restores/{restoreId}/enable'),
 | 
					    enableDbRestore: Api.newPut('/dbs/{dbId}/restores/{restoreId}/enable'),
 | 
				
			||||||
    disableDbRestore: Api.newPut('/dbs/{dbId}/restores/{restoreId}/disable'),
 | 
					    disableDbRestore: Api.newPut('/dbs/{dbId}/restores/{restoreId}/disable'),
 | 
				
			||||||
@@ -78,4 +82,17 @@ export const dbApi = {
 | 
				
			|||||||
    runDatasyncTask: Api.newPost('/datasync/tasks/{taskId}/run'),
 | 
					    runDatasyncTask: Api.newPost('/datasync/tasks/{taskId}/run'),
 | 
				
			||||||
    stopDatasyncTask: Api.newPost('/datasync/tasks/{taskId}/stop'),
 | 
					    stopDatasyncTask: Api.newPost('/datasync/tasks/{taskId}/stop'),
 | 
				
			||||||
    datasyncLogs: Api.newGet('/datasync/tasks/{taskId}/logs'),
 | 
					    datasyncLogs: Api.newGet('/datasync/tasks/{taskId}/logs'),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // 数据库迁移相关
 | 
				
			||||||
 | 
					    dbTransferTasks: Api.newGet('/dbTransfer'),
 | 
				
			||||||
 | 
					    saveDbTransferTask: Api.newPost('/dbTransfer/save'),
 | 
				
			||||||
 | 
					    deleteDbTransferTask: Api.newDelete('/dbTransfer/{taskId}/del'),
 | 
				
			||||||
 | 
					    runDbTransferTask: Api.newPost('/dbTransfer/{taskId}/run'),
 | 
				
			||||||
 | 
					    stopDbTransferTask: Api.newPost('/dbTransfer/{taskId}/stop'),
 | 
				
			||||||
 | 
					    dbTransferTaskLogs: Api.newGet('/dbTransfer/{taskId}/logs'),
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const dbSqlExecApi = {
 | 
				
			||||||
 | 
					    // 根据业务key获取sql执行信息
 | 
				
			||||||
 | 
					    getSqlExecByBizKey: Api.newGet('/dbs/sql-execs'),
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -6,6 +6,9 @@
 | 
				
			|||||||
        :resource-type="TagResourceTypeEnum.Db.value"
 | 
					        :resource-type="TagResourceTypeEnum.Db.value"
 | 
				
			||||||
        :tag-path-node-type="NodeTypeTagPath"
 | 
					        :tag-path-node-type="NodeTypeTagPath"
 | 
				
			||||||
    >
 | 
					    >
 | 
				
			||||||
 | 
					        <template #iconPrefix>
 | 
				
			||||||
 | 
					            <SvgIcon v-if="dbType && getDbDialect(dbType)" :name="getDbDialect(dbType).getInfo().icon" :size="18" />
 | 
				
			||||||
 | 
					        </template>
 | 
				
			||||||
        <template #prefix="{ data }">
 | 
					        <template #prefix="{ data }">
 | 
				
			||||||
            <SvgIcon v-if="data.type.value == SqlExecNodeType.DbInst" :name="getDbDialect(data.params.type).getInfo().icon" :size="18" />
 | 
					            <SvgIcon v-if="data.type.value == SqlExecNodeType.DbInst" :name="getDbDialect(data.params.type).getInfo().icon" :size="18" />
 | 
				
			||||||
            <SvgIcon v-if="data.icon" :name="data.icon.name" :color="data.icon.color" />
 | 
					            <SvgIcon v-if="data.icon" :name="data.icon.name" :color="data.icon.color" />
 | 
				
			||||||
@@ -19,7 +22,7 @@ import { NodeType, TagTreeNode } from '@/views/ops/component/tag';
 | 
				
			|||||||
import { dbApi } from '@/views/ops/db/api';
 | 
					import { dbApi } from '@/views/ops/db/api';
 | 
				
			||||||
import { sleep } from '@/common/utils/loading';
 | 
					import { sleep } from '@/common/utils/loading';
 | 
				
			||||||
import SvgIcon from '@/components/svgIcon/index.vue';
 | 
					import SvgIcon from '@/components/svgIcon/index.vue';
 | 
				
			||||||
import { DbType, getDbDialect } from '@/views/ops/db/dialect';
 | 
					import { getDbDialect, noSchemaTypes } from '@/views/ops/db/dialect';
 | 
				
			||||||
import TagTreeResourceSelect from '../../component/TagTreeResourceSelect.vue';
 | 
					import TagTreeResourceSelect from '../../component/TagTreeResourceSelect.vue';
 | 
				
			||||||
import { computed } from 'vue';
 | 
					import { computed } from 'vue';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -27,15 +30,21 @@ const props = defineProps({
 | 
				
			|||||||
    dbId: {
 | 
					    dbId: {
 | 
				
			||||||
        type: Number,
 | 
					        type: Number,
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					    instName: {
 | 
				
			||||||
 | 
					        type: String,
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
    dbName: {
 | 
					    dbName: {
 | 
				
			||||||
        type: String,
 | 
					        type: String,
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    tagPath: {
 | 
					    tagPath: {
 | 
				
			||||||
        type: String,
 | 
					        type: String,
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					    dbType: {
 | 
				
			||||||
 | 
					        type: String,
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const emits = defineEmits(['update:dbName', 'update:tagPath', 'update:dbId', 'selectDb']);
 | 
					const emits = defineEmits(['update:dbName', 'update:tagPath', 'update:instName', 'update:dbId', 'update:dbType', 'selectDb']);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * 树节点类型
 | 
					 * 树节点类型
 | 
				
			||||||
@@ -53,7 +62,7 @@ class SqlExecNodeType {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
const selectNode = computed({
 | 
					const selectNode = computed({
 | 
				
			||||||
    get: () => {
 | 
					    get: () => {
 | 
				
			||||||
        return props.dbName ? `${props.tagPath} - ${props.dbId} - ${props.dbName}` : '';
 | 
					        return props.dbName ? `${props.tagPath} > ${props.instName} > ${props.dbName}` : '';
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    set: () => {
 | 
					    set: () => {
 | 
				
			||||||
        //
 | 
					        //
 | 
				
			||||||
@@ -87,8 +96,8 @@ const NodeTypeTagPath = new NodeType(TagTreeNode.TagPath).withLoadNodesFunc(asyn
 | 
				
			|||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**  mysql类型的数据库,没有schema层 */
 | 
					/**  mysql类型的数据库,没有schema层 */
 | 
				
			||||||
const mysqlType = (type: string) => {
 | 
					const noSchemaType = (type: string) => {
 | 
				
			||||||
    return type === DbType.mysql;
 | 
					    return noSchemaTypes.includes(type);
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// 数据库实例节点类型
 | 
					// 数据库实例节点类型
 | 
				
			||||||
@@ -96,7 +105,7 @@ const NodeTypeDbInst = new NodeType(SqlExecNodeType.DbInst).withLoadNodesFunc((p
 | 
				
			|||||||
    const params = parentNode.params;
 | 
					    const params = parentNode.params;
 | 
				
			||||||
    const dbs = params.database.split(' ')?.sort();
 | 
					    const dbs = params.database.split(' ')?.sort();
 | 
				
			||||||
    let fn: NodeType;
 | 
					    let fn: NodeType;
 | 
				
			||||||
    if (mysqlType(params.type)) {
 | 
					    if (noSchemaType(params.type)) {
 | 
				
			||||||
        fn = MysqlNodeTypes;
 | 
					        fn = MysqlNodeTypes;
 | 
				
			||||||
    } else {
 | 
					    } else {
 | 
				
			||||||
        fn = PgNodeTypes;
 | 
					        fn = PgNodeTypes;
 | 
				
			||||||
@@ -114,7 +123,7 @@ const NodeTypeDbInst = new NodeType(SqlExecNodeType.DbInst).withLoadNodesFunc((p
 | 
				
			|||||||
                db: x,
 | 
					                db: x,
 | 
				
			||||||
            })
 | 
					            })
 | 
				
			||||||
            .withIcon(DbIcon);
 | 
					            .withIcon(DbIcon);
 | 
				
			||||||
        if (mysqlType(params.type)) {
 | 
					        if (noSchemaType(params.type)) {
 | 
				
			||||||
            tagTreeNode.isLeaf = true;
 | 
					            tagTreeNode.isLeaf = true;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        return tagTreeNode;
 | 
					        return tagTreeNode;
 | 
				
			||||||
@@ -148,8 +157,10 @@ const changeNode = (nodeData: TagTreeNode) => {
 | 
				
			|||||||
    const params = nodeData.params;
 | 
					    const params = nodeData.params;
 | 
				
			||||||
    // postgres
 | 
					    // postgres
 | 
				
			||||||
    emits('update:dbName', params.db);
 | 
					    emits('update:dbName', params.db);
 | 
				
			||||||
 | 
					    emits('update:instName', params.name);
 | 
				
			||||||
    emits('update:dbId', params.id);
 | 
					    emits('update:dbId', params.id);
 | 
				
			||||||
    emits('update:tagPath', params.tagPath);
 | 
					    emits('update:tagPath', params.tagPath);
 | 
				
			||||||
 | 
					    emits('update:dbType', params.type);
 | 
				
			||||||
    emits('selectDb', params);
 | 
					    emits('selectDb', params);
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
</script>
 | 
					</script>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -128,12 +128,12 @@
 | 
				
			|||||||
</template>
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<script lang="ts" setup>
 | 
					<script lang="ts" setup>
 | 
				
			||||||
import { h, nextTick, onMounted, reactive, toRefs, ref, unref } from 'vue';
 | 
					import { h, nextTick, onMounted, reactive, ref, toRefs, unref } from 'vue';
 | 
				
			||||||
import { getToken } from '@/common/utils/storage';
 | 
					import { getToken } from '@/common/utils/storage';
 | 
				
			||||||
import { notBlank } from '@/common/assert';
 | 
					import { 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, ElNotification } from 'element-plus';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import * as monaco from 'monaco-editor/esm/vs/editor/editor.api';
 | 
					import * as monaco from 'monaco-editor/esm/vs/editor/editor.api';
 | 
				
			||||||
import { editor } from 'monaco-editor';
 | 
					import { editor } from 'monaco-editor';
 | 
				
			||||||
@@ -146,11 +146,9 @@ import MonacoEditor from '@/components/monaco/MonacoEditor.vue';
 | 
				
			|||||||
import { joinClientParams } from '@/common/request';
 | 
					import { joinClientParams } from '@/common/request';
 | 
				
			||||||
import { buildProgressProps } from '@/components/progress-notify/progress-notify';
 | 
					import { buildProgressProps } from '@/components/progress-notify/progress-notify';
 | 
				
			||||||
import ProgressNotify from '@/components/progress-notify/progress-notify.vue';
 | 
					import ProgressNotify from '@/components/progress-notify/progress-notify.vue';
 | 
				
			||||||
import { ElNotification } from 'element-plus';
 | 
					 | 
				
			||||||
import syssocket from '@/common/syssocket';
 | 
					import syssocket from '@/common/syssocket';
 | 
				
			||||||
import SvgIcon from '@/components/svgIcon/index.vue';
 | 
					import SvgIcon from '@/components/svgIcon/index.vue';
 | 
				
			||||||
import { getDbDialect } from '../../dialect';
 | 
					import { Pane, Splitpanes } from 'splitpanes';
 | 
				
			||||||
import { Splitpanes, Pane } from 'splitpanes';
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
const emits = defineEmits(['saveSqlSuccess']);
 | 
					const emits = defineEmits(['saveSqlSuccess']);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -299,13 +297,16 @@ const onRunSql = async (newTab = false) => {
 | 
				
			|||||||
    sql = sql.replace(/(^\s*)/g, '');
 | 
					    sql = sql.replace(/(^\s*)/g, '');
 | 
				
			||||||
    let execRemark = '';
 | 
					    let execRemark = '';
 | 
				
			||||||
    let canRun = true;
 | 
					    let canRun = true;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // 简单截取前十个字符
 | 
				
			||||||
 | 
					    const sqlPrefix = sql.slice(0, 10).toLowerCase();
 | 
				
			||||||
    if (
 | 
					    if (
 | 
				
			||||||
        sql.startsWith('update') ||
 | 
					        sqlPrefix.startsWith('update') ||
 | 
				
			||||||
        sql.startsWith('UPDATE') ||
 | 
					        sqlPrefix.startsWith('insert') ||
 | 
				
			||||||
        sql.startsWith('INSERT') ||
 | 
					        sqlPrefix.startsWith('delete') ||
 | 
				
			||||||
        sql.startsWith('insert') ||
 | 
					        sqlPrefix.startsWith('alert') ||
 | 
				
			||||||
        sql.startsWith('DELETE') ||
 | 
					        sqlPrefix.startsWith('drop') ||
 | 
				
			||||||
        sql.startsWith('delete')
 | 
					        sqlPrefix.startsWith('create')
 | 
				
			||||||
    ) {
 | 
					    ) {
 | 
				
			||||||
        const res: any = await ElMessageBox.prompt('请输入备注', 'Tip', {
 | 
					        const res: any = await ElMessageBox.prompt('请输入备注', 'Tip', {
 | 
				
			||||||
            confirmButtonText: '确定',
 | 
					            confirmButtonText: '确定',
 | 
				
			||||||
@@ -322,6 +323,18 @@ const onRunSql = async (newTab = false) => {
 | 
				
			|||||||
        return;
 | 
					        return;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // 启用工单审批
 | 
				
			||||||
 | 
					    if (execRemark && getNowDbInst().flowProcdefKey) {
 | 
				
			||||||
 | 
					        try {
 | 
				
			||||||
 | 
					            await getNowDbInst().runSql(props.dbName, sql, execRemark);
 | 
				
			||||||
 | 
					            ElMessage.success('工单提交成功');
 | 
				
			||||||
 | 
					            return;
 | 
				
			||||||
 | 
					        } catch (e) {
 | 
				
			||||||
 | 
					            ElMessage.success('工单提交失败');
 | 
				
			||||||
 | 
					            return;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    let execRes: ExecResTab;
 | 
					    let execRes: ExecResTab;
 | 
				
			||||||
    let i = 0;
 | 
					    let i = 0;
 | 
				
			||||||
    let id;
 | 
					    let id;
 | 
				
			||||||
@@ -357,6 +370,7 @@ const onRunSql = async (newTab = false) => {
 | 
				
			|||||||
        const colAndData: any = data.value;
 | 
					        const colAndData: any = data.value;
 | 
				
			||||||
        if (!colAndData.res || colAndData.res.length === 0) {
 | 
					        if (!colAndData.res || colAndData.res.length === 0) {
 | 
				
			||||||
            ElMessage.warning('未查询到结果集');
 | 
					            ElMessage.warning('未查询到结果集');
 | 
				
			||||||
 | 
					            return;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // 要实时响应,故需要用索引改变数据才生效
 | 
					        // 要实时响应,故需要用索引改变数据才生效
 | 
				
			||||||
@@ -453,7 +467,7 @@ const formatSql = () => {
 | 
				
			|||||||
        return;
 | 
					        return;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const formatDialect = getDbDialect(getNowDbInst().type).getInfo().formatSqlDialect;
 | 
					    const formatDialect = getNowDbInst().getDialect().getInfo().formatSqlDialect;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    let sql = monacoEditor.getModel()?.getValueInRange(selection);
 | 
					    let sql = monacoEditor.getModel()?.getValueInRange(selection);
 | 
				
			||||||
    // 有选中sql则格式化并替换选中sql, 否则格式化编辑器所有内容
 | 
					    // 有选中sql则格式化并替换选中sql, 否则格式化编辑器所有内容
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,4 +1,4 @@
 | 
				
			|||||||
import { h, render, VNode } from 'vue';
 | 
					import { h, render } from 'vue';
 | 
				
			||||||
import SqlExecDialog from './SqlExecDialog.vue';
 | 
					import SqlExecDialog from './SqlExecDialog.vue';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export type SqlExecProps = {
 | 
					export type SqlExecProps = {
 | 
				
			||||||
@@ -6,31 +6,26 @@ export type SqlExecProps = {
 | 
				
			|||||||
    dbId: number;
 | 
					    dbId: number;
 | 
				
			||||||
    db: string;
 | 
					    db: string;
 | 
				
			||||||
    dbType?: string;
 | 
					    dbType?: string;
 | 
				
			||||||
 | 
					    flowProcdefKey?: string;
 | 
				
			||||||
    runSuccessCallback?: Function;
 | 
					    runSuccessCallback?: Function;
 | 
				
			||||||
    cancelCallback?: Function;
 | 
					    cancelCallback?: Function;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const boxId = 'sql-exec-dialog-id';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
let boxInstance: VNode;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const SqlExecBox = (props: SqlExecProps): void => {
 | 
					const SqlExecBox = (props: SqlExecProps): void => {
 | 
				
			||||||
    if (!boxInstance) {
 | 
					    const propsCancelFn = props.cancelCallback;
 | 
				
			||||||
        const container = document.createElement('div');
 | 
					    //  包装取消回调函数,新增销毁组件代码
 | 
				
			||||||
        container.id = boxId;
 | 
					    props.cancelCallback = () => {
 | 
				
			||||||
        // 创建 虚拟dom
 | 
					        propsCancelFn && propsCancelFn();
 | 
				
			||||||
        boxInstance = h(SqlExecDialog);
 | 
					        setTimeout(() => {
 | 
				
			||||||
        // 将虚拟dom渲染到 container dom 上
 | 
					            // 销毁组件
 | 
				
			||||||
        render(boxInstance, container);
 | 
					            render(null, document.body);
 | 
				
			||||||
        // 最后将 container 追加到 body 上
 | 
					        }, 500);
 | 
				
			||||||
        document.body.appendChild(container);
 | 
					    };
 | 
				
			||||||
    }
 | 
					    const vnode = h(SqlExecDialog, {
 | 
				
			||||||
 | 
					        ...props,
 | 
				
			||||||
    const boxVue = boxInstance.component;
 | 
					        visible: true,
 | 
				
			||||||
    if (boxVue) {
 | 
					    });
 | 
				
			||||||
        // 调用open方法显示弹框,注意不能使用boxVue.ctx来调用组件函数(build打包后ctx会获取不到)
 | 
					    render(vnode, document.body);
 | 
				
			||||||
        boxVue.exposed?.open(props);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default SqlExecBox;
 | 
					export default SqlExecBox;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,8 +1,20 @@
 | 
				
			|||||||
<template>
 | 
					<template>
 | 
				
			||||||
    <div>
 | 
					    <div>
 | 
				
			||||||
        <el-dialog title="待执行SQL" v-model="dialogVisible" :show-close="false" width="600px" @close="cancel">
 | 
					        <el-dialog title="待执行SQL" v-model="dialogVisible" :show-close="false" width="600px">
 | 
				
			||||||
            <monaco-editor height="300px" class="codesql" language="sql" v-model="sqlValue" />
 | 
					            <monaco-editor height="300px" class="codesql" language="sql" v-model="sqlValue" />
 | 
				
			||||||
            <el-input @keyup.enter="runSql" ref="remarkInputRef" v-model="remark" placeholder="请输入执行备注" class="mt5" />
 | 
					            <el-input
 | 
				
			||||||
 | 
					                @keyup.enter="runSql"
 | 
				
			||||||
 | 
					                ref="remarkInputRef"
 | 
				
			||||||
 | 
					                v-model="remark"
 | 
				
			||||||
 | 
					                :placeholder="props.flowProcdefKey ? '执行备注(必填)' : '执行备注(选填)'"
 | 
				
			||||||
 | 
					                class="mt5"
 | 
				
			||||||
 | 
					            />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            <div v-if="props.flowProcdefKey">
 | 
				
			||||||
 | 
					                <el-divider content-position="left">审批节点</el-divider>
 | 
				
			||||||
 | 
					                <procdef-tasks :procdef-key="props.flowProcdefKey" />
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            <template #footer>
 | 
					            <template #footer>
 | 
				
			||||||
                <span class="dialog-footer">
 | 
					                <span class="dialog-footer">
 | 
				
			||||||
                    <el-button @click="cancel">取 消</el-button>
 | 
					                    <el-button @click="cancel">取 消</el-button>
 | 
				
			||||||
@@ -14,52 +26,40 @@
 | 
				
			|||||||
</template>
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<script lang="ts" setup>
 | 
					<script lang="ts" setup>
 | 
				
			||||||
import { toRefs, ref, nextTick, reactive } from 'vue';
 | 
					import { toRefs, ref, reactive, onMounted } from 'vue';
 | 
				
			||||||
import { dbApi } from '@/views/ops/db/api';
 | 
					import { dbApi } from '@/views/ops/db/api';
 | 
				
			||||||
import { ElDialog, ElButton, ElInput, ElMessage, InputInstance } from 'element-plus';
 | 
					import { ElDialog, ElButton, ElInput, ElMessage, InputInstance, ElDivider } from 'element-plus';
 | 
				
			||||||
// import base style
 | 
					// import base style
 | 
				
			||||||
import MonacoEditor from '@/components/monaco/MonacoEditor.vue';
 | 
					import MonacoEditor from '@/components/monaco/MonacoEditor.vue';
 | 
				
			||||||
import { format as sqlFormatter } from 'sql-formatter';
 | 
					import { format as sqlFormatter } from 'sql-formatter';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import { SqlExecProps } from './SqlExecBox';
 | 
					import { SqlExecProps } from './SqlExecBox';
 | 
				
			||||||
 | 
					import ProcdefTasks from '@/views/flow/components/ProcdefTasks.vue';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const props = defineProps({
 | 
					const props = withDefaults(defineProps<SqlExecProps>(), {});
 | 
				
			||||||
    visible: {
 | 
					 | 
				
			||||||
        type: Boolean,
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    dbId: {
 | 
					 | 
				
			||||||
        type: [Number],
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    db: {
 | 
					 | 
				
			||||||
        type: String,
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    sql: {
 | 
					 | 
				
			||||||
        type: String,
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
});
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
const remarkInputRef = ref<InputInstance>();
 | 
					const remarkInputRef = ref<InputInstance>();
 | 
				
			||||||
const state = reactive({
 | 
					const state = reactive({
 | 
				
			||||||
    dialogVisible: false,
 | 
					    dialogVisible: false,
 | 
				
			||||||
    sqlValue: '',
 | 
					    sqlValue: '',
 | 
				
			||||||
    dbId: 0,
 | 
					 | 
				
			||||||
    db: '',
 | 
					 | 
				
			||||||
    remark: '',
 | 
					    remark: '',
 | 
				
			||||||
    btnLoading: false,
 | 
					    btnLoading: false,
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const { dialogVisible, sqlValue, remark, btnLoading } = toRefs(state);
 | 
					const { dialogVisible, sqlValue, remark, btnLoading } = toRefs(state);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
state.sqlValue = props.sql as any;
 | 
					 | 
				
			||||||
let runSuccessCallback: any;
 | 
					 | 
				
			||||||
let cancelCallback: any;
 | 
					 | 
				
			||||||
let runSuccess: boolean = false;
 | 
					let runSuccess: boolean = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					onMounted(() => {
 | 
				
			||||||
 | 
					    open();
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * 执行sql
 | 
					 * 执行sql
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
const runSql = async () => {
 | 
					const runSql = async () => {
 | 
				
			||||||
    if (!state.remark) {
 | 
					    // 存在流程审批,则备注为必填
 | 
				
			||||||
 | 
					    if (!state.remark && props.flowProcdefKey) {
 | 
				
			||||||
        ElMessage.error('请输入执行的备注信息');
 | 
					        ElMessage.error('请输入执行的备注信息');
 | 
				
			||||||
        return;
 | 
					        return;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@@ -67,12 +67,19 @@ const runSql = async () => {
 | 
				
			|||||||
    try {
 | 
					    try {
 | 
				
			||||||
        state.btnLoading = true;
 | 
					        state.btnLoading = true;
 | 
				
			||||||
        const res = await dbApi.sqlExec.request({
 | 
					        const res = await dbApi.sqlExec.request({
 | 
				
			||||||
            id: state.dbId,
 | 
					            id: props.dbId,
 | 
				
			||||||
            db: state.db,
 | 
					            db: props.db,
 | 
				
			||||||
            remark: state.remark,
 | 
					            remark: state.remark,
 | 
				
			||||||
            sql: state.sqlValue.trim(),
 | 
					            sql: state.sqlValue.trim(),
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // 存在流程审批
 | 
				
			||||||
 | 
					        if (props.flowProcdefKey) {
 | 
				
			||||||
 | 
					            runSuccess = false;
 | 
				
			||||||
 | 
					            ElMessage.success('工单提交成功');
 | 
				
			||||||
 | 
					            return;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        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}`);
 | 
				
			||||||
@@ -84,45 +91,33 @@ const runSql = async () => {
 | 
				
			|||||||
        ElMessage.success('执行成功');
 | 
					        ElMessage.success('执行成功');
 | 
				
			||||||
    } catch (e) {
 | 
					    } catch (e) {
 | 
				
			||||||
        runSuccess = false;
 | 
					        runSuccess = false;
 | 
				
			||||||
    }
 | 
					    } finally {
 | 
				
			||||||
    if (runSuccess) {
 | 
					        if (runSuccess) {
 | 
				
			||||||
        if (runSuccessCallback) {
 | 
					            if (props.runSuccessCallback) {
 | 
				
			||||||
            runSuccessCallback();
 | 
					                props.runSuccessCallback();
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					        state.btnLoading = false;
 | 
				
			||||||
        cancel();
 | 
					        cancel();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    state.btnLoading = false;
 | 
					 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const cancel = () => {
 | 
					const cancel = () => {
 | 
				
			||||||
    state.dialogVisible = false;
 | 
					    state.dialogVisible = false;
 | 
				
			||||||
    // 没有执行成功,并且取消回调函数存在,则执行
 | 
					    props.cancelCallback && props.cancelCallback();
 | 
				
			||||||
    if (!runSuccess && cancelCallback) {
 | 
					 | 
				
			||||||
        cancelCallback();
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    setTimeout(() => {
 | 
					    setTimeout(() => {
 | 
				
			||||||
        state.dbId = 0;
 | 
					 | 
				
			||||||
        state.sqlValue = '';
 | 
					        state.sqlValue = '';
 | 
				
			||||||
        state.remark = '';
 | 
					        state.remark = '';
 | 
				
			||||||
        runSuccessCallback = null;
 | 
					 | 
				
			||||||
        cancelCallback = null;
 | 
					 | 
				
			||||||
        runSuccess = false;
 | 
					        runSuccess = false;
 | 
				
			||||||
    }, 200);
 | 
					    }, 200);
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const open = (props: SqlExecProps) => {
 | 
					const open = () => {
 | 
				
			||||||
    runSuccessCallback = props.runSuccessCallback;
 | 
					    state.sqlValue = sqlFormatter(props.sql, { language: props.dbType || 'mysql' });
 | 
				
			||||||
    cancelCallback = props.cancelCallback;
 | 
					 | 
				
			||||||
    props.dbType = props.dbType || 'mysql';
 | 
					 | 
				
			||||||
    state.sqlValue = sqlFormatter(props.sql, { language: props.dbType });
 | 
					 | 
				
			||||||
    state.dbId = props.dbId;
 | 
					 | 
				
			||||||
    state.db = props.db;
 | 
					 | 
				
			||||||
    state.dialogVisible = true;
 | 
					    state.dialogVisible = true;
 | 
				
			||||||
    nextTick(() => {
 | 
					    setTimeout(() => {
 | 
				
			||||||
        setTimeout(() => {
 | 
					        remarkInputRef.value?.focus();
 | 
				
			||||||
            remarkInputRef.value?.focus();
 | 
					    }, 200);
 | 
				
			||||||
        });
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
defineExpose({ open });
 | 
					defineExpose({ open });
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -3,9 +3,9 @@
 | 
				
			|||||||
        <el-input
 | 
					        <el-input
 | 
				
			||||||
            v-if="dataType == DataType.String"
 | 
					            v-if="dataType == DataType.String"
 | 
				
			||||||
            :ref="(el: any) => focus && el?.focus()"
 | 
					            :ref="(el: any) => focus && el?.focus()"
 | 
				
			||||||
 | 
					            :disabled="disabled"
 | 
				
			||||||
            @blur="handleBlur"
 | 
					            @blur="handleBlur"
 | 
				
			||||||
            :class="`w100 mb4 ${showEditorIcon ? 'string-input-container-show-icon' : ''}`"
 | 
					            :class="`w100 mb4 ${showEditorIcon ? 'string-input-container-show-icon' : ''}`"
 | 
				
			||||||
            input-style="text-align: center; height: 26px;"
 | 
					 | 
				
			||||||
            size="small"
 | 
					            size="small"
 | 
				
			||||||
            v-model="itemValue"
 | 
					            v-model="itemValue"
 | 
				
			||||||
            :placeholder="placeholder"
 | 
					            :placeholder="placeholder"
 | 
				
			||||||
@@ -16,9 +16,9 @@
 | 
				
			|||||||
    <el-input
 | 
					    <el-input
 | 
				
			||||||
        v-else-if="dataType == DataType.Number"
 | 
					        v-else-if="dataType == DataType.Number"
 | 
				
			||||||
        :ref="(el: any) => focus && el?.focus()"
 | 
					        :ref="(el: any) => focus && el?.focus()"
 | 
				
			||||||
 | 
					        :disabled="disabled"
 | 
				
			||||||
        @blur="handleBlur"
 | 
					        @blur="handleBlur"
 | 
				
			||||||
        class="w100 mb4"
 | 
					        class="w100 mb4"
 | 
				
			||||||
        input-style="text-align: center; height: 26px;"
 | 
					 | 
				
			||||||
        size="small"
 | 
					        size="small"
 | 
				
			||||||
        v-model.number="itemValue"
 | 
					        v-model.number="itemValue"
 | 
				
			||||||
        :placeholder="placeholder"
 | 
					        :placeholder="placeholder"
 | 
				
			||||||
@@ -28,6 +28,7 @@
 | 
				
			|||||||
    <el-date-picker
 | 
					    <el-date-picker
 | 
				
			||||||
        v-else-if="dataType == DataType.Date"
 | 
					        v-else-if="dataType == DataType.Date"
 | 
				
			||||||
        :ref="(el: any) => focus && el?.focus()"
 | 
					        :ref="(el: any) => focus && el?.focus()"
 | 
				
			||||||
 | 
					        :disabled="disabled"
 | 
				
			||||||
        @change="emit('blur')"
 | 
					        @change="emit('blur')"
 | 
				
			||||||
        @blur="handleBlur"
 | 
					        @blur="handleBlur"
 | 
				
			||||||
        class="edit-time-picker mb4"
 | 
					        class="edit-time-picker mb4"
 | 
				
			||||||
@@ -43,6 +44,7 @@
 | 
				
			|||||||
    <el-date-picker
 | 
					    <el-date-picker
 | 
				
			||||||
        v-else-if="dataType == DataType.DateTime"
 | 
					        v-else-if="dataType == DataType.DateTime"
 | 
				
			||||||
        :ref="(el: any) => focus && el?.focus()"
 | 
					        :ref="(el: any) => focus && el?.focus()"
 | 
				
			||||||
 | 
					        :disabled="disabled"
 | 
				
			||||||
        @change="handleBlur"
 | 
					        @change="handleBlur"
 | 
				
			||||||
        @blur="handleBlur"
 | 
					        @blur="handleBlur"
 | 
				
			||||||
        class="edit-time-picker mb4"
 | 
					        class="edit-time-picker mb4"
 | 
				
			||||||
@@ -58,6 +60,7 @@
 | 
				
			|||||||
    <el-time-picker
 | 
					    <el-time-picker
 | 
				
			||||||
        v-else-if="dataType == DataType.Time"
 | 
					        v-else-if="dataType == DataType.Time"
 | 
				
			||||||
        :ref="(el: any) => focus && el?.focus()"
 | 
					        :ref="(el: any) => focus && el?.focus()"
 | 
				
			||||||
 | 
					        :disabled="disabled"
 | 
				
			||||||
        @change="handleBlur"
 | 
					        @change="handleBlur"
 | 
				
			||||||
        @blur="handleBlur"
 | 
					        @blur="handleBlur"
 | 
				
			||||||
        class="edit-time-picker mb4"
 | 
					        class="edit-time-picker mb4"
 | 
				
			||||||
@@ -71,7 +74,7 @@
 | 
				
			|||||||
</template>
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<script lang="ts" setup>
 | 
					<script lang="ts" setup>
 | 
				
			||||||
import { Ref, ref, computed } from 'vue';
 | 
					import { computed, ref, Ref } from 'vue';
 | 
				
			||||||
import { ElInput } from 'element-plus';
 | 
					import { ElInput } from 'element-plus';
 | 
				
			||||||
import { DataType } from '../../dialect/index';
 | 
					import { DataType } from '../../dialect/index';
 | 
				
			||||||
import SvgIcon from '@/components/svgIcon/index.vue';
 | 
					import SvgIcon from '@/components/svgIcon/index.vue';
 | 
				
			||||||
@@ -83,11 +86,13 @@ export interface ColumnFormItemProps {
 | 
				
			|||||||
    focus?: boolean; // 是否获取焦点
 | 
					    focus?: boolean; // 是否获取焦点
 | 
				
			||||||
    placeholder?: string;
 | 
					    placeholder?: string;
 | 
				
			||||||
    columnName?: string;
 | 
					    columnName?: string;
 | 
				
			||||||
 | 
					    disabled?: boolean;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const props = withDefaults(defineProps<ColumnFormItemProps>(), {
 | 
					const props = withDefaults(defineProps<ColumnFormItemProps>(), {
 | 
				
			||||||
    focus: false,
 | 
					    focus: false,
 | 
				
			||||||
    dataType: DataType.String,
 | 
					    dataType: DataType.String,
 | 
				
			||||||
 | 
					    disabled: false,
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const emit = defineEmits(['update:modelValue', 'blur']);
 | 
					const emit = defineEmits(['update:modelValue', 'blur']);
 | 
				
			||||||
@@ -178,9 +183,6 @@ const getEditorLangByValue = (value: any) => {
 | 
				
			|||||||
    .el-input__prefix {
 | 
					    .el-input__prefix {
 | 
				
			||||||
        display: none;
 | 
					        display: none;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    .el-input__inner {
 | 
					 | 
				
			||||||
        text-align: center;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.edit-time-picker-popper {
 | 
					.edit-time-picker-popper {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -46,14 +46,6 @@
 | 
				
			|||||||
                                            <b :title="column.remark" class="el-text" style="cursor: pointer">
 | 
					                                            <b :title="column.remark" class="el-text" style="cursor: pointer">
 | 
				
			||||||
                                                {{ column.title }}
 | 
					                                                {{ column.title }}
 | 
				
			||||||
                                            </b>
 | 
					                                            </b>
 | 
				
			||||||
 | 
					 | 
				
			||||||
                                            <span>
 | 
					 | 
				
			||||||
                                                <SvgIcon
 | 
					 | 
				
			||||||
                                                    color="var(--el-color-primary)"
 | 
					 | 
				
			||||||
                                                    v-if="column.title == nowSortColumn?.columnName"
 | 
					 | 
				
			||||||
                                                    :name="nowSortColumn?.order == 'asc' ? 'top' : 'bottom'"
 | 
					 | 
				
			||||||
                                                ></SvgIcon>
 | 
					 | 
				
			||||||
                                            </span>
 | 
					 | 
				
			||||||
                                        </div>
 | 
					                                        </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                                        <!-- 字段备注信息 -->
 | 
					                                        <!-- 字段备注信息 -->
 | 
				
			||||||
@@ -71,6 +63,13 @@
 | 
				
			|||||||
                                            {{ column.title }}
 | 
					                                            {{ column.title }}
 | 
				
			||||||
                                        </b>
 | 
					                                        </b>
 | 
				
			||||||
                                    </div>
 | 
					                                    </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                                    <!-- 字段列右部分内容 -->
 | 
				
			||||||
 | 
					                                    <div class="column-right">
 | 
				
			||||||
 | 
					                                        <span v-if="column.title == nowSortColumn?.columnName">
 | 
				
			||||||
 | 
					                                            <SvgIcon color="var(--el-color-primary)" :name="nowSortColumn?.order == 'asc' ? 'top' : 'bottom'"></SvgIcon>
 | 
				
			||||||
 | 
					                                        </span>
 | 
				
			||||||
 | 
					                                    </div>
 | 
				
			||||||
                                </div>
 | 
					                                </div>
 | 
				
			||||||
                            </div>
 | 
					                            </div>
 | 
				
			||||||
                        </div>
 | 
					                        </div>
 | 
				
			||||||
@@ -138,13 +137,25 @@
 | 
				
			|||||||
            <el-input v-model="state.genTxtDialog.txt" type="textarea" rows="20" />
 | 
					            <el-input v-model="state.genTxtDialog.txt" type="textarea" rows="20" />
 | 
				
			||||||
        </el-dialog>
 | 
					        </el-dialog>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        <DbTableDataForm
 | 
				
			||||||
 | 
					            v-if="state.tableDataFormDialog.visible"
 | 
				
			||||||
 | 
					            :db-inst="getNowDbInst()"
 | 
				
			||||||
 | 
					            :db-name="db"
 | 
				
			||||||
 | 
					            :columns="columns!"
 | 
				
			||||||
 | 
					            :title="state.tableDataFormDialog.title"
 | 
				
			||||||
 | 
					            :table-name="table"
 | 
				
			||||||
 | 
					            v-model:visible="state.tableDataFormDialog.visible"
 | 
				
			||||||
 | 
					            v-model="state.tableDataFormDialog.data"
 | 
				
			||||||
 | 
					            @submit-success="emits('changeUpdatedField')"
 | 
				
			||||||
 | 
					        />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        <contextmenu :dropdown="state.contextmenu.dropdown" :items="state.contextmenu.items" ref="contextmenuRef" />
 | 
					        <contextmenu :dropdown="state.contextmenu.dropdown" :items="state.contextmenu.items" ref="contextmenuRef" />
 | 
				
			||||||
    </div>
 | 
					    </div>
 | 
				
			||||||
</template>
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<script lang="ts" setup>
 | 
					<script lang="ts" setup>
 | 
				
			||||||
import { onBeforeUnmount, onMounted, reactive, ref, toRefs, watch } from 'vue';
 | 
					import { onBeforeUnmount, onMounted, reactive, ref, toRefs, watch } from 'vue';
 | 
				
			||||||
import { ElInput } from 'element-plus';
 | 
					import { ElInput, ElMessage } from 'element-plus';
 | 
				
			||||||
import { copyToClipboard } from '@/common/utils/string';
 | 
					import { copyToClipboard } from '@/common/utils/string';
 | 
				
			||||||
import { DbInst } from '@/views/ops/db/db';
 | 
					import { DbInst } from '@/views/ops/db/db';
 | 
				
			||||||
import { Contextmenu, ContextmenuItem } from '@/components/contextmenu';
 | 
					import { Contextmenu, ContextmenuItem } from '@/components/contextmenu';
 | 
				
			||||||
@@ -154,6 +165,7 @@ import { dateStrFormat } from '@/common/utils/date';
 | 
				
			|||||||
import { useIntervalFn, useStorage } from '@vueuse/core';
 | 
					import { useIntervalFn, useStorage } from '@vueuse/core';
 | 
				
			||||||
import { ColumnTypeSubscript, compatibleMysql, DataType, DbDialect, getDbDialect } from '../../dialect/index';
 | 
					import { ColumnTypeSubscript, compatibleMysql, DataType, DbDialect, getDbDialect } from '../../dialect/index';
 | 
				
			||||||
import ColumnFormItem from './ColumnFormItem.vue';
 | 
					import ColumnFormItem from './ColumnFormItem.vue';
 | 
				
			||||||
 | 
					import DbTableDataForm from './DbTableDataForm.vue';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const emits = defineEmits(['dataDelete', 'sortChange', 'deleteData', 'selectionChange', 'changeUpdatedField']);
 | 
					const emits = defineEmits(['dataDelete', 'sortChange', 'deleteData', 'selectionChange', 'changeUpdatedField']);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -247,6 +259,13 @@ const cmDataDel = new ContextmenuItem('deleteData', '删除')
 | 
				
			|||||||
        return state.table == '';
 | 
					        return state.table == '';
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const cmDataEdit = new ContextmenuItem('editData', '编辑行')
 | 
				
			||||||
 | 
					    .withIcon('edit')
 | 
				
			||||||
 | 
					    .withOnClick(() => onEditRowData())
 | 
				
			||||||
 | 
					    .withHideFunc(() => {
 | 
				
			||||||
 | 
					        return state.table == '';
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const cmDataGenInsertSql = new ContextmenuItem('genInsertSql', 'Insert SQL')
 | 
					const cmDataGenInsertSql = new ContextmenuItem('genInsertSql', 'Insert SQL')
 | 
				
			||||||
    .withIcon('tickets')
 | 
					    .withIcon('tickets')
 | 
				
			||||||
    .withOnClick(() => onGenerateInsertSql())
 | 
					    .withOnClick(() => onGenerateInsertSql())
 | 
				
			||||||
@@ -333,7 +352,11 @@ const state = reactive({
 | 
				
			|||||||
        },
 | 
					        },
 | 
				
			||||||
        items: [] as ContextmenuItem[],
 | 
					        items: [] as ContextmenuItem[],
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					    tableDataFormDialog: {
 | 
				
			||||||
 | 
					        data: {},
 | 
				
			||||||
 | 
					        title: '',
 | 
				
			||||||
 | 
					        visible: false,
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
    genTxtDialog: {
 | 
					    genTxtDialog: {
 | 
				
			||||||
        title: 'SQL',
 | 
					        title: 'SQL',
 | 
				
			||||||
        visible: false,
 | 
					        visible: false,
 | 
				
			||||||
@@ -438,13 +461,13 @@ const formatDataValues = (datas: any) => {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    for (let data of datas) {
 | 
					    for (let data of datas) {
 | 
				
			||||||
        for (let column of props.columns!) {
 | 
					        for (let column of props.columns!) {
 | 
				
			||||||
            data[column.columnName] = getFormatTimeValue(dbDialect.getDataType(column.columnType), data[column.columnName]);
 | 
					            data[column.columnName] = getFormatTimeValue(dbDialect.getDataType(column.dataType), data[column.columnName]);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const setTableData = (datas: any) => {
 | 
					const setTableData = (datas: any) => {
 | 
				
			||||||
    tableRef.value.scrollTo({ scrollLeft: 0, scrollTop: 0 });
 | 
					    tableRef.value?.scrollTo({ scrollLeft: 0, scrollTop: 0 });
 | 
				
			||||||
    selectionRowsMap.clear();
 | 
					    selectionRowsMap.clear();
 | 
				
			||||||
    cellUpdateMap.clear();
 | 
					    cellUpdateMap.clear();
 | 
				
			||||||
    formatDataValues(datas);
 | 
					    formatDataValues(datas);
 | 
				
			||||||
@@ -456,9 +479,9 @@ const setTableColumns = (columns: any) => {
 | 
				
			|||||||
    state.columns = columns.map((x: any) => {
 | 
					    state.columns = columns.map((x: any) => {
 | 
				
			||||||
        const columnName = x.columnName;
 | 
					        const columnName = x.columnName;
 | 
				
			||||||
        // 数据类型
 | 
					        // 数据类型
 | 
				
			||||||
        x.dataType = dbDialect.getDataType(x.columnType);
 | 
					        x.dataType = dbDialect.getDataType(x.dataType);
 | 
				
			||||||
        x.dataTypeSubscript = ColumnTypeSubscript[x.dataType];
 | 
					        x.dataTypeSubscript = ColumnTypeSubscript[x.dataType];
 | 
				
			||||||
        x.remark = `${x.columnType} ${x.columnComment ? ' |  ' + x.columnComment : ''}`;
 | 
					        x.remark = `${x.showDataType} ${x.columnComment ? ' |  ' + x.columnComment : ''}`;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return {
 | 
					        return {
 | 
				
			||||||
            ...x,
 | 
					            ...x,
 | 
				
			||||||
@@ -576,7 +599,7 @@ const dataContextmenuClick = (event: any, rowIndex: number, column: any, data: a
 | 
				
			|||||||
    const { clientX, clientY } = event;
 | 
					    const { clientX, clientY } = event;
 | 
				
			||||||
    state.contextmenu.dropdown.x = clientX;
 | 
					    state.contextmenu.dropdown.x = clientX;
 | 
				
			||||||
    state.contextmenu.dropdown.y = clientY;
 | 
					    state.contextmenu.dropdown.y = clientY;
 | 
				
			||||||
    state.contextmenu.items = [cmDataCopyCell, cmDataDel, cmDataGenInsertSql, cmDataGenJson, cmDataExportCsv, cmDataExportSql];
 | 
					    state.contextmenu.items = [cmDataCopyCell, cmDataDel, cmDataEdit, cmDataGenInsertSql, cmDataGenJson, cmDataExportCsv, cmDataExportSql];
 | 
				
			||||||
    contextmenuRef.value.openContextmenu({ column, rowData: data });
 | 
					    contextmenuRef.value.openContextmenu({ column, rowData: data });
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -601,6 +624,18 @@ const onDeleteData = async () => {
 | 
				
			|||||||
    });
 | 
					    });
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const onEditRowData = () => {
 | 
				
			||||||
 | 
					    const selectionDatas = Array.from(selectionRowsMap.values());
 | 
				
			||||||
 | 
					    if (selectionDatas.length > 1) {
 | 
				
			||||||
 | 
					        ElMessage.warning('只能编辑一行数据');
 | 
				
			||||||
 | 
					        return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    const data = selectionDatas[0];
 | 
				
			||||||
 | 
					    state.tableDataFormDialog.data = data;
 | 
				
			||||||
 | 
					    state.tableDataFormDialog.title = `编辑表'${props.table}'数据`;
 | 
				
			||||||
 | 
					    state.tableDataFormDialog.visible = true;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const onGenerateInsertSql = async () => {
 | 
					const onGenerateInsertSql = async () => {
 | 
				
			||||||
    const selectionDatas = Array.from(selectionRowsMap.values());
 | 
					    const selectionDatas = Array.from(selectionRowsMap.values());
 | 
				
			||||||
    state.genTxtDialog.txt = await getNowDbInst().genInsertSql(state.db, state.table, selectionDatas);
 | 
					    state.genTxtDialog.txt = await getNowDbInst().genInsertSql(state.db, state.table, selectionDatas);
 | 
				
			||||||
@@ -714,48 +749,28 @@ const submitUpdateFields = async () => {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    const db = state.db;
 | 
					    const db = state.db;
 | 
				
			||||||
    let res = '';
 | 
					    let res = '';
 | 
				
			||||||
    const dbDialect = getDbDialect(dbInst.type);
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    for (let updateRow of cellUpdateMap.values()) {
 | 
					    for (let updateRow of cellUpdateMap.values()) {
 | 
				
			||||||
        let sql = `UPDATE ${dbInst.wrapName(state.table)} SET `;
 | 
					        const rowData = { ...updateRow.rowData };
 | 
				
			||||||
        const rowData = updateRow.rowData;
 | 
					        let updateColumnValue = {};
 | 
				
			||||||
        // 主键列信息
 | 
					 | 
				
			||||||
        const primaryKey = await dbInst.loadTableColumn(db, state.table);
 | 
					 | 
				
			||||||
        let primaryKeyType = primaryKey.columnType;
 | 
					 | 
				
			||||||
        let primaryKeyName = primaryKey.columnName;
 | 
					 | 
				
			||||||
        let primaryKeyValue = rowData[primaryKeyName];
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        for (let k of updateRow.columnsMap.keys()) {
 | 
					        for (let k of updateRow.columnsMap.keys()) {
 | 
				
			||||||
            const v = updateRow.columnsMap.get(k);
 | 
					            const v = updateRow.columnsMap.get(k);
 | 
				
			||||||
            if (!v) {
 | 
					            if (!v) {
 | 
				
			||||||
                continue;
 | 
					                continue;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            // 更新字段列信息
 | 
					            updateColumnValue[k] = rowData[k];
 | 
				
			||||||
            const updateColumn = await dbInst.loadTableColumn(db, state.table, k);
 | 
					            // 将更新的字段对应的原始数据还原(主要应对可能更新修改了主键等)
 | 
				
			||||||
 | 
					            rowData[k] = v.oldValue;
 | 
				
			||||||
            sql += ` ${dbInst.wrapName(k)} = ${DbInst.wrapColumnValue(updateColumn.columnType, rowData[k], dbDialect)},`;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            // 如果修改的字段是主键
 | 
					 | 
				
			||||||
            if (k === primaryKeyName) {
 | 
					 | 
				
			||||||
                primaryKeyValue = v.oldValue;
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					        res += await dbInst.genUpdateSql(db, state.table, updateColumnValue, rowData);
 | 
				
			||||||
        sql = sql.substring(0, sql.length - 1);
 | 
					 | 
				
			||||||
        sql += ` WHERE ${dbInst.wrapName(primaryKeyName)} = ${DbInst.wrapColumnValue(primaryKeyType, primaryKeyValue)} ;`;
 | 
					 | 
				
			||||||
        res += sql;
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    dbInst.promptExeSql(
 | 
					    dbInst.promptExeSql(db, res, cancelUpdateFields, () => {
 | 
				
			||||||
        db,
 | 
					        triggerRefresh();
 | 
				
			||||||
        res,
 | 
					        cellUpdateMap.clear();
 | 
				
			||||||
        () => {},
 | 
					        changeUpdatedField();
 | 
				
			||||||
        () => {
 | 
					    });
 | 
				
			||||||
            triggerRefresh();
 | 
					 | 
				
			||||||
            cellUpdateMap.clear();
 | 
					 | 
				
			||||||
            changeUpdatedField();
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    );
 | 
					 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const cancelUpdateFields = () => {
 | 
					const cancelUpdateFields = () => {
 | 
				
			||||||
@@ -868,9 +883,15 @@ defineExpose({
 | 
				
			|||||||
        color: var(--el-color-info-light-3);
 | 
					        color: var(--el-color-info-light-3);
 | 
				
			||||||
        font-weight: bold;
 | 
					        font-weight: bold;
 | 
				
			||||||
        position: absolute;
 | 
					        position: absolute;
 | 
				
			||||||
        top: -7px;
 | 
					        top: -5px;
 | 
				
			||||||
 | 
					        padding: 2px;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    .column-right {
 | 
				
			||||||
 | 
					        position: absolute;
 | 
				
			||||||
 | 
					        top: 2px;
 | 
				
			||||||
 | 
					        right: 0;
 | 
				
			||||||
        padding: 2px;
 | 
					        padding: 2px;
 | 
				
			||||||
        height: 12px;
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
</style>
 | 
					</style>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -0,0 +1,122 @@
 | 
				
			|||||||
 | 
					<template>
 | 
				
			||||||
 | 
					    <el-dialog v-model="visible" :title="title" :destroy-on-close="true" width="600px">
 | 
				
			||||||
 | 
					        <el-form ref="dataForm" :model="modelValue" :show-message="false" label-width="auto" size="small">
 | 
				
			||||||
 | 
					            <el-form-item
 | 
				
			||||||
 | 
					                v-for="column in columns"
 | 
				
			||||||
 | 
					                :key="column.columnName"
 | 
				
			||||||
 | 
					                class="w100 mb5"
 | 
				
			||||||
 | 
					                :prop="column.columnName"
 | 
				
			||||||
 | 
					                :required="column.nullable != 'YES' && !column.isPrimaryKey && !column.isIdentity"
 | 
				
			||||||
 | 
					            >
 | 
				
			||||||
 | 
					                <template #label>
 | 
				
			||||||
 | 
					                    <span class="pointer" :title="`${column.showDataType} | ${column.columnComment}`">
 | 
				
			||||||
 | 
					                        {{ column.columnName }}
 | 
				
			||||||
 | 
					                    </span>
 | 
				
			||||||
 | 
					                </template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                <ColumnFormItem
 | 
				
			||||||
 | 
					                    v-model="modelValue[`${column.columnName}`]"
 | 
				
			||||||
 | 
					                    :data-type="dbInst.getDialect().getDataType(column.dataType)"
 | 
				
			||||||
 | 
					                    :placeholder="`${column.showDataType}  ${column.columnComment}`"
 | 
				
			||||||
 | 
					                    :column-name="column.columnName"
 | 
				
			||||||
 | 
					                    :disabled="column.isIdentity"
 | 
				
			||||||
 | 
					                />
 | 
				
			||||||
 | 
					            </el-form-item>
 | 
				
			||||||
 | 
					        </el-form>
 | 
				
			||||||
 | 
					        <template #footer>
 | 
				
			||||||
 | 
					            <span class="dialog-footer">
 | 
				
			||||||
 | 
					                <el-button @click="closeDialog">取消</el-button>
 | 
				
			||||||
 | 
					                <el-button type="primary" @click="confirm">确定</el-button>
 | 
				
			||||||
 | 
					            </span>
 | 
				
			||||||
 | 
					        </template>
 | 
				
			||||||
 | 
					    </el-dialog>
 | 
				
			||||||
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<script lang="ts" setup>
 | 
				
			||||||
 | 
					import { ref, watch, onMounted } from 'vue';
 | 
				
			||||||
 | 
					import ColumnFormItem from './ColumnFormItem.vue';
 | 
				
			||||||
 | 
					import { DbInst } from '../../db';
 | 
				
			||||||
 | 
					import { ElMessage } from 'element-plus';
 | 
				
			||||||
 | 
					import { getDbDialect } from '@/views/ops/db/dialect';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export interface ColumnFormItemProps {
 | 
				
			||||||
 | 
					    dbInst: DbInst;
 | 
				
			||||||
 | 
					    dbName: string;
 | 
				
			||||||
 | 
					    tableName: string;
 | 
				
			||||||
 | 
					    columns: any[];
 | 
				
			||||||
 | 
					    title?: string; // dialog title
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const props = withDefaults(defineProps<ColumnFormItemProps>(), {
 | 
				
			||||||
 | 
					    title: '',
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const modelValue = defineModel<any>('modelValue');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const visible = defineModel<boolean>('visible', {
 | 
				
			||||||
 | 
					    default: false,
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const emit = defineEmits(['submitSuccess']);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const dataForm: any = ref(null);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					let oldValue = null as any;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					onMounted(() => {
 | 
				
			||||||
 | 
					    setOldValue();
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					watch(visible, (newValue) => {
 | 
				
			||||||
 | 
					    if (newValue) {
 | 
				
			||||||
 | 
					        setOldValue();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const setOldValue = () => {
 | 
				
			||||||
 | 
					    // 空对象则为insert操作,否则为update
 | 
				
			||||||
 | 
					    if (Object.keys(modelValue.value).length > 0) {
 | 
				
			||||||
 | 
					        oldValue = Object.assign({}, modelValue.value);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const closeDialog = () => {
 | 
				
			||||||
 | 
					    visible.value = false;
 | 
				
			||||||
 | 
					    modelValue.value = {};
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const confirm = async () => {
 | 
				
			||||||
 | 
					    dataForm.value.validate(async (valid: boolean) => {
 | 
				
			||||||
 | 
					        if (!valid) {
 | 
				
			||||||
 | 
					            ElMessage.error('请正确填写数据信息');
 | 
				
			||||||
 | 
					            return false;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const dbInst = props.dbInst;
 | 
				
			||||||
 | 
					        const data = modelValue.value;
 | 
				
			||||||
 | 
					        const db = props.dbName;
 | 
				
			||||||
 | 
					        const tableName = props.tableName;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        let sql = '';
 | 
				
			||||||
 | 
					        if (oldValue) {
 | 
				
			||||||
 | 
					            const updateColumnValue = {};
 | 
				
			||||||
 | 
					            Object.keys(oldValue).forEach((key) => {
 | 
				
			||||||
 | 
					                // 如果新旧值不相等,则为需要更新的字段
 | 
				
			||||||
 | 
					                if (oldValue[key] !== modelValue.value[key]) {
 | 
				
			||||||
 | 
					                    updateColumnValue[key] = modelValue.value[key];
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					            sql = await dbInst.genUpdateSql(db, tableName, updateColumnValue, oldValue);
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            sql = await dbInst.genInsertSql(db, tableName, [data], true);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        dbInst.promptExeSql(db, sql, null, () => {
 | 
				
			||||||
 | 
					            closeDialog();
 | 
				
			||||||
 | 
					            emit('submitSuccess');
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<style lang="scss"></style>
 | 
				
			||||||
@@ -17,8 +17,8 @@
 | 
				
			|||||||
                            <el-checkbox
 | 
					                            <el-checkbox
 | 
				
			||||||
                                v-model="item.show"
 | 
					                                v-model="item.show"
 | 
				
			||||||
                                :label="`${!item.columnComment ? item.columnName : item.columnName + ' [' + item.columnComment + ']'}`"
 | 
					                                :label="`${!item.columnComment ? item.columnName : item.columnName + ' [' + item.columnComment + ']'}`"
 | 
				
			||||||
                                :true-label="true"
 | 
					                                :true-value="true"
 | 
				
			||||||
                                :false-label="false"
 | 
					                                :false-value="false"
 | 
				
			||||||
                                size="small"
 | 
					                                size="small"
 | 
				
			||||||
                            />
 | 
					                            />
 | 
				
			||||||
                        </div>
 | 
					                        </div>
 | 
				
			||||||
@@ -55,7 +55,7 @@
 | 
				
			|||||||
                        title="展示配置"
 | 
					                        title="展示配置"
 | 
				
			||||||
                        trigger="click"
 | 
					                        trigger="click"
 | 
				
			||||||
                    >
 | 
					                    >
 | 
				
			||||||
                        <el-checkbox v-model="dbConfig.showColumnComment" label="显示字段备注" :true-label="true" :false-label="false" size="small" />
 | 
					                        <el-checkbox v-model="dbConfig.showColumnComment" label="显示字段备注" :true-value="true" :false-value="false" size="small" />
 | 
				
			||||||
                        <template #reference>
 | 
					                        <template #reference>
 | 
				
			||||||
                            <el-link type="primary" icon="setting" :underline="false"></el-link>
 | 
					                            <el-link type="primary" icon="setting" :underline="false"></el-link>
 | 
				
			||||||
                        </template>
 | 
					                        </template>
 | 
				
			||||||
@@ -98,7 +98,7 @@
 | 
				
			|||||||
                        <el-divider direction="vertical" />
 | 
					                        <el-divider direction="vertical" />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                        <span style="color: var(--el-color-info-light-3)">
 | 
					                        <span style="color: var(--el-color-info-light-3)">
 | 
				
			||||||
                            {{ item.columnType }}
 | 
					                            {{ item.showDataType }}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                            <template v-if="item.columnComment">
 | 
					                            <template v-if="item.columnComment">
 | 
				
			||||||
                                <el-divider direction="vertical" />
 | 
					                                <el-divider direction="vertical" />
 | 
				
			||||||
@@ -158,24 +158,55 @@
 | 
				
			|||||||
            @data-delete="onRefresh"
 | 
					            @data-delete="onRefresh"
 | 
				
			||||||
        ></db-table-data>
 | 
					        ></db-table-data>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        <el-row type="flex" class="mt5" justify="center">
 | 
					        <el-row type="flex" class="mt5" :gutter="10" justify="space-between" style="user-select: none">
 | 
				
			||||||
            <el-pagination
 | 
					            <el-col :span="12">
 | 
				
			||||||
                small
 | 
					                <el-text
 | 
				
			||||||
                :total="count"
 | 
					                    id="copyValue"
 | 
				
			||||||
                @size-change="handleSizeChange"
 | 
					                    style="color: var(--el-color-info-light-3)"
 | 
				
			||||||
                @current-change="pageChange()"
 | 
					                    class="is-truncated font12 mt5"
 | 
				
			||||||
                layout="prev, pager, next, total, sizes, jumper"
 | 
					                    @click="copyToClipboard(sql)"
 | 
				
			||||||
                v-model:current-page="pageNum"
 | 
					                    :title="sql"
 | 
				
			||||||
                v-model:page-size="pageSize"
 | 
					                    >{{ sql }}</el-text
 | 
				
			||||||
                :page-sizes="pageSizes"
 | 
					                >
 | 
				
			||||||
            ></el-pagination>
 | 
					            </el-col>
 | 
				
			||||||
        </el-row>
 | 
					            <el-col :span="12">
 | 
				
			||||||
        <div style="font-size: 12px; padding: 0 10px; color: #606266">
 | 
					                <el-row :gutter="10" justify="left">
 | 
				
			||||||
            <span>{{ state.sql }}</span>
 | 
					                    <el-link class="op-page" :underline="false" @click="pageNum = 1" :disabled="pageNum == 1" icon="DArrowLeft" title="首页" />
 | 
				
			||||||
        </div>
 | 
					                    <el-link class="op-page" :underline="false" @click="pageNum = --pageNum || 1" :disabled="pageNum == 1" icon="Back" title="上一页" />
 | 
				
			||||||
 | 
					                    <div class="op-page">
 | 
				
			||||||
 | 
					                        <el-input-number
 | 
				
			||||||
 | 
					                            style="width: 50px"
 | 
				
			||||||
 | 
					                            :controls="false"
 | 
				
			||||||
 | 
					                            :min="1"
 | 
				
			||||||
 | 
					                            v-model="state.setPageNum"
 | 
				
			||||||
 | 
					                            size="small"
 | 
				
			||||||
 | 
					                            @blur="handleSetPageNum"
 | 
				
			||||||
 | 
					                            @keydown.enter="handleSetPageNum"
 | 
				
			||||||
 | 
					                        />
 | 
				
			||||||
 | 
					                    </div>
 | 
				
			||||||
 | 
					                    <el-link class="op-page" :underline="false" @click="++pageNum" :disabled="datas.length < pageSize" icon="Right" />
 | 
				
			||||||
 | 
					                    <el-link class="op-page" :underline="false" @click="handleEndPage" :disabled="datas.length < pageSize" icon="DArrowRight" />
 | 
				
			||||||
 | 
					                    <div style="width: 90px" class="op-page ml10">
 | 
				
			||||||
 | 
					                        <el-select size="small" :default-first-option="true" v-model="pageSize" @change="handleSizeChange">
 | 
				
			||||||
 | 
					                            <el-option
 | 
				
			||||||
 | 
					                                style="font-size: 12px; height: 24px; line-height: 24px"
 | 
				
			||||||
 | 
					                                v-for="(op, i) in pageSizes"
 | 
				
			||||||
 | 
					                                :key="i"
 | 
				
			||||||
 | 
					                                :label="op + '条/页'"
 | 
				
			||||||
 | 
					                                :value="op"
 | 
				
			||||||
 | 
					                            />
 | 
				
			||||||
 | 
					                        </el-select>
 | 
				
			||||||
 | 
					                    </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        <el-dialog v-model="conditionDialog.visible" :title="conditionDialog.title" width="420px">
 | 
					                    <el-button @click="handleCount" :loading="state.counting" class="ml10" text bg size="small">
 | 
				
			||||||
            <el-row>
 | 
					                        {{ state.showTotal ? `${state.total} 条` : 'count' }}
 | 
				
			||||||
 | 
					                    </el-button>
 | 
				
			||||||
 | 
					                </el-row>
 | 
				
			||||||
 | 
					            </el-col>
 | 
				
			||||||
 | 
					        </el-row>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        <el-dialog v-model="conditionDialog.visible" :title="conditionDialog.title" width="460px">
 | 
				
			||||||
 | 
					            <el-row gutter="5">
 | 
				
			||||||
                <el-col :span="5">
 | 
					                <el-col :span="5">
 | 
				
			||||||
                    <el-select v-model="conditionDialog.condition">
 | 
					                    <el-select v-model="conditionDialog.condition">
 | 
				
			||||||
                        <el-option label="=" value="="> </el-option>
 | 
					                        <el-option label="=" value="="> </el-option>
 | 
				
			||||||
@@ -203,31 +234,16 @@
 | 
				
			|||||||
            </template>
 | 
					            </template>
 | 
				
			||||||
        </el-dialog>
 | 
					        </el-dialog>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        <el-dialog v-model="addDataDialog.visible" :title="addDataDialog.title" :destroy-on-close="true" width="600px">
 | 
					        <DbTableDataForm
 | 
				
			||||||
            <el-form ref="dataForm" :model="addDataDialog.data" :show-message="false" label-width="auto" size="small">
 | 
					            :db-inst="getNowDbInst()"
 | 
				
			||||||
                <el-form-item
 | 
					            :db-name="dbName"
 | 
				
			||||||
                    v-for="column in columns"
 | 
					            :columns="columns"
 | 
				
			||||||
                    :key="column.columnName"
 | 
					            :title="addDataDialog.title"
 | 
				
			||||||
                    class="w100 mb5"
 | 
					            :table-name="tableName"
 | 
				
			||||||
                    :prop="column.columnName"
 | 
					            v-model:visible="addDataDialog.visible"
 | 
				
			||||||
                    :label="column.columnName"
 | 
					            v-model="addDataDialog.data"
 | 
				
			||||||
                    :required="column.nullable != 'YES' && column.columnKey != 'PRI'"
 | 
					            @submit-success="onRefresh"
 | 
				
			||||||
                >
 | 
					        />
 | 
				
			||||||
                    <ColumnFormItem
 | 
					 | 
				
			||||||
                        v-model="addDataDialog.data[`${column.columnName}`]"
 | 
					 | 
				
			||||||
                        :data-type="dbDialect.getDataType(column.columnType)"
 | 
					 | 
				
			||||||
                        :placeholder="`${column.columnType}  ${column.columnComment}`"
 | 
					 | 
				
			||||||
                        :column-name="column.columnName"
 | 
					 | 
				
			||||||
                    />
 | 
					 | 
				
			||||||
                </el-form-item>
 | 
					 | 
				
			||||||
            </el-form>
 | 
					 | 
				
			||||||
            <template #footer>
 | 
					 | 
				
			||||||
                <span class="dialog-footer">
 | 
					 | 
				
			||||||
                    <el-button @click="closeAddDataDialog">取消</el-button>
 | 
					 | 
				
			||||||
                    <el-button type="primary" @click="addRow">确定</el-button>
 | 
					 | 
				
			||||||
                </span>
 | 
					 | 
				
			||||||
            </template>
 | 
					 | 
				
			||||||
        </el-dialog>
 | 
					 | 
				
			||||||
    </div>
 | 
					    </div>
 | 
				
			||||||
</template>
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -237,10 +253,11 @@ import { ElMessage } from 'element-plus';
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
import { DbInst } from '@/views/ops/db/db';
 | 
					import { DbInst } from '@/views/ops/db/db';
 | 
				
			||||||
import DbTableData from './DbTableData.vue';
 | 
					import DbTableData from './DbTableData.vue';
 | 
				
			||||||
import { DbDialect, getDbDialect } from '@/views/ops/db/dialect';
 | 
					import { DbDialect } from '@/views/ops/db/dialect';
 | 
				
			||||||
import SvgIcon from '@/components/svgIcon/index.vue';
 | 
					import SvgIcon from '@/components/svgIcon/index.vue';
 | 
				
			||||||
import ColumnFormItem from './ColumnFormItem.vue';
 | 
					 | 
				
			||||||
import { useEventListener, useStorage } from '@vueuse/core';
 | 
					import { useEventListener, useStorage } from '@vueuse/core';
 | 
				
			||||||
 | 
					import { copyToClipboard } from '@/common/utils/string';
 | 
				
			||||||
 | 
					import DbTableDataForm from './DbTableDataForm.vue';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const props = defineProps({
 | 
					const props = defineProps({
 | 
				
			||||||
    dbId: {
 | 
					    dbId: {
 | 
				
			||||||
@@ -261,7 +278,6 @@ const props = defineProps({
 | 
				
			|||||||
    },
 | 
					    },
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const dataForm: any = ref(null);
 | 
					 | 
				
			||||||
const dbTableRef: Ref = ref(null);
 | 
					const dbTableRef: Ref = ref(null);
 | 
				
			||||||
const condInputRef: Ref = ref(null);
 | 
					const condInputRef: Ref = ref(null);
 | 
				
			||||||
const columnNameSearchInputRef: Ref = ref(null);
 | 
					const columnNameSearchInputRef: Ref = ref(null);
 | 
				
			||||||
@@ -289,7 +305,10 @@ const state = reactive({
 | 
				
			|||||||
        defaultPageSize * 40,
 | 
					        defaultPageSize * 40,
 | 
				
			||||||
        defaultPageSize * 80,
 | 
					        defaultPageSize * 80,
 | 
				
			||||||
    ],
 | 
					    ],
 | 
				
			||||||
    count: 0,
 | 
					    setPageNum: 0,
 | 
				
			||||||
 | 
					    total: 0,
 | 
				
			||||||
 | 
					    showTotal: false,
 | 
				
			||||||
 | 
					    counting: false,
 | 
				
			||||||
    selectionDatas: [] as any,
 | 
					    selectionDatas: [] as any,
 | 
				
			||||||
    condPopVisible: false,
 | 
					    condPopVisible: false,
 | 
				
			||||||
    columnNameSearch: '',
 | 
					    columnNameSearch: '',
 | 
				
			||||||
@@ -305,7 +324,6 @@ const state = reactive({
 | 
				
			|||||||
    addDataDialog: {
 | 
					    addDataDialog: {
 | 
				
			||||||
        data: {},
 | 
					        data: {},
 | 
				
			||||||
        title: '',
 | 
					        title: '',
 | 
				
			||||||
        placeholder: '',
 | 
					 | 
				
			||||||
        visible: false,
 | 
					        visible: false,
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    tableHeight: '600px',
 | 
					    tableHeight: '600px',
 | 
				
			||||||
@@ -313,7 +331,7 @@ const state = reactive({
 | 
				
			|||||||
    dbDialect: {} as DbDialect,
 | 
					    dbDialect: {} as DbDialect,
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const { datas, condition, loading, columns, pageNum, pageSize, pageSizes, count, hasUpdatedFileds, conditionDialog, addDataDialog, dbDialect } = toRefs(state);
 | 
					const { datas, condition, loading, columns, pageNum, pageSize, pageSizes, sql, hasUpdatedFileds, conditionDialog, addDataDialog } = toRefs(state);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
watch(
 | 
					watch(
 | 
				
			||||||
    () => props.tableHeight,
 | 
					    () => props.tableHeight,
 | 
				
			||||||
@@ -331,7 +349,7 @@ onMounted(async () => {
 | 
				
			|||||||
    state.tableHeight = props.tableHeight;
 | 
					    state.tableHeight = props.tableHeight;
 | 
				
			||||||
    await onRefresh();
 | 
					    await onRefresh();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    state.dbDialect = getDbDialect(getNowDbInst().type);
 | 
					    state.dbDialect = getNowDbInst().getDialect();
 | 
				
			||||||
    useEventListener('click', handlerWindowClick);
 | 
					    useEventListener('click', handlerWindowClick);
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -346,18 +364,19 @@ const onRefresh = async () => {
 | 
				
			|||||||
    await selectData();
 | 
					    await selectData();
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					watch(
 | 
				
			||||||
 * 数据tab修改页数
 | 
					    () => state.pageNum,
 | 
				
			||||||
 */
 | 
					    async () => {
 | 
				
			||||||
const pageChange = async () => {
 | 
					        await selectData();
 | 
				
			||||||
    await selectData();
 | 
					    }
 | 
				
			||||||
};
 | 
					);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * 单表数据信息查询数据
 | 
					 * 单表数据信息查询数据
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
const selectData = async () => {
 | 
					const selectData = async () => {
 | 
				
			||||||
    state.loading = true;
 | 
					    state.loading = true;
 | 
				
			||||||
 | 
					    state.setPageNum = state.pageNum;
 | 
				
			||||||
    const dbInst = getNowDbInst();
 | 
					    const dbInst = getNowDbInst();
 | 
				
			||||||
    const db = props.dbName;
 | 
					    const db = props.dbName;
 | 
				
			||||||
    const table = props.tableName;
 | 
					    const table = props.tableName;
 | 
				
			||||||
@@ -370,16 +389,10 @@ const selectData = async () => {
 | 
				
			|||||||
            state.columns = columns;
 | 
					            state.columns = columns;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        const countRes = await dbInst.runSql(db, dbInst.getDefaultCountSql(table, state.condition));
 | 
					        let sql = dbInst.getDefaultSelectSql(db, table, state.condition, state.orderBy, state.pageNum, state.pageSize);
 | 
				
			||||||
        state.count = countRes.res[0].count || countRes.res[0].COUNT || 0;
 | 
					 | 
				
			||||||
        let sql = dbInst.getDefaultSelectSql(table, state.condition, state.orderBy, state.pageNum, state.pageSize);
 | 
					 | 
				
			||||||
        state.sql = sql;
 | 
					        state.sql = sql;
 | 
				
			||||||
        if (state.count > 0) {
 | 
					        const colAndData: any = await dbInst.runSql(db, sql);
 | 
				
			||||||
            const colAndData: any = await dbInst.runSql(db, sql);
 | 
					        state.datas = colAndData.res;
 | 
				
			||||||
            state.datas = colAndData.res;
 | 
					 | 
				
			||||||
        } else {
 | 
					 | 
				
			||||||
            state.datas = [];
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    } finally {
 | 
					    } finally {
 | 
				
			||||||
        state.loading = false;
 | 
					        state.loading = false;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@@ -391,6 +404,33 @@ const handleSizeChange = async (size: any) => {
 | 
				
			|||||||
    await selectData();
 | 
					    await selectData();
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const handleEndPage = async () => {
 | 
				
			||||||
 | 
					    await handleCount();
 | 
				
			||||||
 | 
					    state.pageNum = Math.ceil(state.total / state.pageSize);
 | 
				
			||||||
 | 
					    await selectData();
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const handleSetPageNum = async () => {
 | 
				
			||||||
 | 
					    state.pageNum = state.setPageNum;
 | 
				
			||||||
 | 
					    await selectData();
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					const handleCount = async () => {
 | 
				
			||||||
 | 
					    state.counting = true;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					        const db = props.dbName;
 | 
				
			||||||
 | 
					        const table = props.tableName;
 | 
				
			||||||
 | 
					        const dbInst = getNowDbInst();
 | 
				
			||||||
 | 
					        const countRes = await dbInst.runSql(db, dbInst.getDefaultCountSql(table, state.condition));
 | 
				
			||||||
 | 
					        state.total = parseInt(countRes.res[0].count || countRes.res[0].COUNT || 0);
 | 
				
			||||||
 | 
					        state.showTotal = true;
 | 
				
			||||||
 | 
					    } catch (e) {
 | 
				
			||||||
 | 
					        /* empty */
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    state.counting = false;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// 完整的条件,每次选中后会重置条件框内容,故需要这个变量在获取建议时将文本框内容保存
 | 
					// 完整的条件,每次选中后会重置条件框内容,故需要这个变量在获取建议时将文本框内容保存
 | 
				
			||||||
let completeCond = '';
 | 
					let completeCond = '';
 | 
				
			||||||
// 是否存在列建议
 | 
					// 是否存在列建议
 | 
				
			||||||
@@ -423,7 +463,7 @@ const handlerColumnSelect = (column: any) => {
 | 
				
			|||||||
    // 默认拼接上 columnName =
 | 
					    // 默认拼接上 columnName =
 | 
				
			||||||
    let value = column.columnName + ' = ';
 | 
					    let value = column.columnName + ' = ';
 | 
				
			||||||
    // 不是数字类型默认拼接上''
 | 
					    // 不是数字类型默认拼接上''
 | 
				
			||||||
    if (!DbInst.isNumber(column.columnType)) {
 | 
					    if (!DbInst.isNumber(column.dataType)) {
 | 
				
			||||||
        value = `${value} ''`;
 | 
					        value = `${value} ''`;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -467,7 +507,7 @@ const filterCondColumns = computed(() => {
 | 
				
			|||||||
const onConditionRowClick = (event: any) => {
 | 
					const onConditionRowClick = (event: any) => {
 | 
				
			||||||
    const row = event[0];
 | 
					    const row = event[0];
 | 
				
			||||||
    state.conditionDialog.title = `请输入 [${row.columnName}] 的值`;
 | 
					    state.conditionDialog.title = `请输入 [${row.columnName}] 的值`;
 | 
				
			||||||
    state.conditionDialog.placeholder = `${row.columnType}  ${row.columnComment}`;
 | 
					    state.conditionDialog.placeholder = `${row.showDataType}  ${row.columnComment}`;
 | 
				
			||||||
    state.conditionDialog.columnRow = row;
 | 
					    state.conditionDialog.columnRow = row;
 | 
				
			||||||
    state.conditionDialog.visible = true;
 | 
					    state.conditionDialog.visible = true;
 | 
				
			||||||
    setTimeout(() => {
 | 
					    setTimeout(() => {
 | 
				
			||||||
@@ -484,7 +524,7 @@ const onConfirmCondition = () => {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
    const row = conditionDialog.columnRow as any;
 | 
					    const row = conditionDialog.columnRow as any;
 | 
				
			||||||
    condition += `${row.columnName} ${conditionDialog.condition} `;
 | 
					    condition += `${row.columnName} ${conditionDialog.condition} `;
 | 
				
			||||||
    state.condition = condition + DbInst.wrapColumnValue(row.columnType, conditionDialog.value);
 | 
					    state.condition = condition + state.dbDialect.wrapValue(row.dataType, conditionDialog.value!);
 | 
				
			||||||
    onCancelCondition();
 | 
					    onCancelCondition();
 | 
				
			||||||
    condInputRef.value.focus();
 | 
					    condInputRef.value.focus();
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
@@ -543,40 +583,10 @@ const onShowAddDataDialog = async () => {
 | 
				
			|||||||
    state.addDataDialog.title = `添加'${props.tableName}'表数据`;
 | 
					    state.addDataDialog.title = `添加'${props.tableName}'表数据`;
 | 
				
			||||||
    state.addDataDialog.visible = true;
 | 
					    state.addDataDialog.visible = true;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					 | 
				
			||||||
const closeAddDataDialog = () => {
 | 
					 | 
				
			||||||
    state.addDataDialog.visible = false;
 | 
					 | 
				
			||||||
    state.addDataDialog.data = {};
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// 添加新数据行
 | 
					 | 
				
			||||||
const addRow = async () => {
 | 
					 | 
				
			||||||
    dataForm.value.validate(async (valid: boolean) => {
 | 
					 | 
				
			||||||
        if (valid) {
 | 
					 | 
				
			||||||
            const dbInst = getNowDbInst();
 | 
					 | 
				
			||||||
            const data = state.addDataDialog.data;
 | 
					 | 
				
			||||||
            // key: 字段名,value: 字段名提示
 | 
					 | 
				
			||||||
            let obj: any = {};
 | 
					 | 
				
			||||||
            for (let item of state.columns) {
 | 
					 | 
				
			||||||
                const value = data[item.columnName];
 | 
					 | 
				
			||||||
                if (!value) {
 | 
					 | 
				
			||||||
                    continue;
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
                obj[`${dbInst.wrapName(item.columnName)}`] = DbInst.wrapValueByType(value);
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            let columnNames = Object.keys(obj).join(',');
 | 
					 | 
				
			||||||
            let values = Object.values(obj).join(',');
 | 
					 | 
				
			||||||
            let sql = `INSERT INTO ${dbInst.wrapName(props.tableName)} (${columnNames}) VALUES (${values});`;
 | 
					 | 
				
			||||||
            dbInst.promptExeSql(props.dbName, sql, null, () => {
 | 
					 | 
				
			||||||
                closeAddDataDialog();
 | 
					 | 
				
			||||||
                onRefresh();
 | 
					 | 
				
			||||||
            });
 | 
					 | 
				
			||||||
        } else {
 | 
					 | 
				
			||||||
            ElMessage.error('请正确填写数据信息');
 | 
					 | 
				
			||||||
            return false;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
</script>
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<style lang="scss"></style>
 | 
					<style lang="scss">
 | 
				
			||||||
 | 
					.op-page {
 | 
				
			||||||
 | 
					    margin-left: 5px;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					</style>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,6 +1,6 @@
 | 
				
			|||||||
<template>
 | 
					<template>
 | 
				
			||||||
    <div>
 | 
					    <div>
 | 
				
			||||||
        <el-dialog :title="title" v-model="dialogVisible" :before-close="cancel" width="90%" :close-on-press-escape="false" :close-on-click-modal="false">
 | 
					        <el-dialog :title="title" v-model="dialogVisible" :before-close="cancel" width="70%" :close-on-press-escape="false" :close-on-click-modal="false">
 | 
				
			||||||
            <el-form label-position="left" ref="formRef" :model="tableData" label-width="80px">
 | 
					            <el-form label-position="left" ref="formRef" :model="tableData" label-width="80px">
 | 
				
			||||||
                <el-row>
 | 
					                <el-row>
 | 
				
			||||||
                    <el-col :span="12">
 | 
					                    <el-col :span="12">
 | 
				
			||||||
@@ -26,11 +26,11 @@
 | 
				
			|||||||
                                :width="item.width"
 | 
					                                :width="item.width"
 | 
				
			||||||
                            >
 | 
					                            >
 | 
				
			||||||
                                <template #default="scope">
 | 
					                                <template #default="scope">
 | 
				
			||||||
                                    <el-input v-if="item.prop === 'name'" size="small" v-model="scope.row.name"> </el-input>
 | 
					                                    <el-input v-if="item.prop === 'name'" size="small" v-model="scope.row.name" />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                                    <el-select v-else-if="item.prop === 'type'" filterable size="small" v-model="scope.row.type">
 | 
					                                    <el-select v-else-if="item.prop === 'type'" filterable size="small" v-model="scope.row.type">
 | 
				
			||||||
                                        <el-option
 | 
					                                        <el-option
 | 
				
			||||||
                                            v-for="pgsqlType in state.columnTypeList"
 | 
					                                            v-for="pgsqlType in getDbDialect(dbType).getInfo().columnTypes"
 | 
				
			||||||
                                            :key="pgsqlType.dataType"
 | 
					                                            :key="pgsqlType.dataType"
 | 
				
			||||||
                                            :value="pgsqlType.udtName"
 | 
					                                            :value="pgsqlType.udtName"
 | 
				
			||||||
                                            :label="pgsqlType.dataType"
 | 
					                                            :label="pgsqlType.dataType"
 | 
				
			||||||
@@ -42,35 +42,30 @@
 | 
				
			|||||||
                                        </el-option>
 | 
					                                        </el-option>
 | 
				
			||||||
                                    </el-select>
 | 
					                                    </el-select>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                                    <el-input v-else-if="item.prop === 'value'" size="small" v-model="scope.row.value"> </el-input>
 | 
					                                    <el-input v-else-if="item.prop === 'value'" size="small" v-model="scope.row.value" />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                                    <el-input v-else-if="item.prop === 'length'" size="small" v-model="scope.row.length"> </el-input>
 | 
					                                    <el-input v-else-if="item.prop === 'length'" type="number" size="small" v-model.number="scope.row.length" />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                                    <el-input v-else-if="item.prop === 'numScale'" size="small" v-model="scope.row.numScale"> </el-input>
 | 
					                                    <el-input v-else-if="item.prop === 'numScale'" type="number" size="small" v-model.number="scope.row.numScale" />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                                    <el-checkbox v-else-if="item.prop === 'notNull'" size="small" v-model="scope.row.notNull"> </el-checkbox>
 | 
					                                    <el-checkbox v-else-if="item.prop === 'notNull'" size="small" v-model="scope.row.notNull" />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                                    <el-checkbox v-else-if="item.prop === 'pri'" size="small" v-model="scope.row.pri"> </el-checkbox>
 | 
					                                    <el-checkbox v-else-if="item.prop === 'pri'" size="small" v-model="scope.row.pri" />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                                    <el-checkbox
 | 
					                                    <el-checkbox
 | 
				
			||||||
                                        v-else-if="item.prop === 'auto_increment'"
 | 
					                                        v-else-if="item.prop === 'auto_increment'"
 | 
				
			||||||
                                        size="small"
 | 
					                                        size="small"
 | 
				
			||||||
                                        v-model="scope.row.auto_increment"
 | 
					                                        v-model="scope.row.auto_increment"
 | 
				
			||||||
                                        :disabled="dbType === DbType.postgresql"
 | 
					                                        :disabled="disableEditIncr()"
 | 
				
			||||||
                                    >
 | 
					                                    />
 | 
				
			||||||
                                    </el-checkbox>
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
                                    <el-input v-else-if="item.prop === 'remark'" size="small" v-model="scope.row.remark"> </el-input>
 | 
					                                    <el-input v-else-if="item.prop === 'remark'" size="small" v-model="scope.row.remark" />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                                    <el-link
 | 
					                                    <el-popconfirm v-else-if="item.prop === 'action'" title="确定删除?" @confirm="deleteRow(scope.$index)">
 | 
				
			||||||
                                        v-else-if="item.prop === 'action'"
 | 
					                                        <template #reference>
 | 
				
			||||||
                                        type="danger"
 | 
					                                            <el-link type="danger" plain size="small" :underline="false">删除</el-link>
 | 
				
			||||||
                                        plain
 | 
					                                        </template>
 | 
				
			||||||
                                        size="small"
 | 
					                                    </el-popconfirm>
 | 
				
			||||||
                                        :underline="false"
 | 
					 | 
				
			||||||
                                        @click.prevent="deleteRow(scope.$index)"
 | 
					 | 
				
			||||||
                                        >删除</el-link
 | 
					 | 
				
			||||||
                                    >
 | 
					 | 
				
			||||||
                                </template>
 | 
					                                </template>
 | 
				
			||||||
                            </el-table-column>
 | 
					                            </el-table-column>
 | 
				
			||||||
                        </el-table>
 | 
					                        </el-table>
 | 
				
			||||||
@@ -104,21 +99,15 @@
 | 
				
			|||||||
                                    <el-checkbox v-if="item.prop === 'unique'" size="small" v-model="scope.row.unique" @change="indexChanges(scope.row)">
 | 
					                                    <el-checkbox v-if="item.prop === 'unique'" size="small" v-model="scope.row.unique" @change="indexChanges(scope.row)">
 | 
				
			||||||
                                    </el-checkbox>
 | 
					                                    </el-checkbox>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                                    <el-select v-if="item.prop === 'indexType'" disabled size="small" v-model="scope.row.indexType">
 | 
					                                    <el-input v-if="item.prop === 'indexType'" disabled size="small" v-model="scope.row.indexType" />
 | 
				
			||||||
                                        <el-option v-for="typeValue in indexTypeList" :key="typeValue" :value="typeValue">{{ typeValue }}</el-option>
 | 
					 | 
				
			||||||
                                    </el-select>
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
                                    <el-input v-if="item.prop === 'indexComment'" size="small" v-model="scope.row.indexComment"> </el-input>
 | 
					                                    <el-input v-if="item.prop === 'indexComment'" size="small" v-model="scope.row.indexComment"> </el-input>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                                    <el-link
 | 
					                                    <el-popconfirm v-else-if="item.prop === 'action'" title="确定删除?" @confirm="deleteIndex(scope.$index)">
 | 
				
			||||||
                                        v-if="item.prop === 'action'"
 | 
					                                        <template #reference>
 | 
				
			||||||
                                        type="danger"
 | 
					                                            <el-link type="danger" plain size="small" :underline="false">删除</el-link>
 | 
				
			||||||
                                        plain
 | 
					                                        </template>
 | 
				
			||||||
                                        size="small"
 | 
					                                    </el-popconfirm>
 | 
				
			||||||
                                        :underline="false"
 | 
					 | 
				
			||||||
                                        @click.prevent="deleteIndex(scope.$index)"
 | 
					 | 
				
			||||||
                                        >删除</el-link
 | 
					 | 
				
			||||||
                                    >
 | 
					 | 
				
			||||||
                                </template>
 | 
					                                </template>
 | 
				
			||||||
                            </el-table-column>
 | 
					                            </el-table-column>
 | 
				
			||||||
                        </el-table>
 | 
					                        </el-table>
 | 
				
			||||||
@@ -130,6 +119,7 @@
 | 
				
			|||||||
                </el-tabs>
 | 
					                </el-tabs>
 | 
				
			||||||
            </el-form>
 | 
					            </el-form>
 | 
				
			||||||
            <template #footer>
 | 
					            <template #footer>
 | 
				
			||||||
 | 
					                <el-button @click="cancel()">取消</el-button>
 | 
				
			||||||
                <el-button :loading="btnloading" @click="submit()" type="primary">保存</el-button>
 | 
					                <el-button :loading="btnloading" @click="submit()" type="primary">保存</el-button>
 | 
				
			||||||
            </template>
 | 
					            </template>
 | 
				
			||||||
        </el-dialog>
 | 
					        </el-dialog>
 | 
				
			||||||
@@ -141,6 +131,7 @@ import { reactive, ref, toRefs, watch } from 'vue';
 | 
				
			|||||||
import { ElMessage } from 'element-plus';
 | 
					import { ElMessage } from 'element-plus';
 | 
				
			||||||
import SqlExecBox from '../sqleditor/SqlExecBox';
 | 
					import SqlExecBox from '../sqleditor/SqlExecBox';
 | 
				
			||||||
import { DbType, getDbDialect, IndexDefinition, RowDefinition } from '../../dialect/index';
 | 
					import { DbType, getDbDialect, IndexDefinition, RowDefinition } from '../../dialect/index';
 | 
				
			||||||
 | 
					import { DbInst } from '../../db';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const props = defineProps({
 | 
					const props = defineProps({
 | 
				
			||||||
    visible: {
 | 
					    visible: {
 | 
				
			||||||
@@ -161,12 +152,15 @@ const props = defineProps({
 | 
				
			|||||||
    dbType: {
 | 
					    dbType: {
 | 
				
			||||||
        type: String,
 | 
					        type: String,
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					    flowProcdefKey: {
 | 
				
			||||||
 | 
					        type: String,
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
//定义事件
 | 
					//定义事件
 | 
				
			||||||
const emit = defineEmits(['update:visible', 'cancel', 'val-change', 'submit-sql']);
 | 
					const emit = defineEmits(['update:visible', 'cancel', 'val-change', 'submit-sql']);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const dbDialect = getDbDialect(props.dbType);
 | 
					let dbDialect = getDbDialect(props.dbType);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type ColName = {
 | 
					type ColName = {
 | 
				
			||||||
    prop: string;
 | 
					    prop: string;
 | 
				
			||||||
@@ -179,30 +173,33 @@ const state = reactive({
 | 
				
			|||||||
    dialogVisible: false,
 | 
					    dialogVisible: false,
 | 
				
			||||||
    btnloading: false,
 | 
					    btnloading: false,
 | 
				
			||||||
    activeName: '1',
 | 
					    activeName: '1',
 | 
				
			||||||
    columnTypeList: dbDialect.getInfo().columnTypes,
 | 
					 | 
				
			||||||
    indexTypeList: ['BTREE', 'NORMAL'], // mysql索引类型详解 http://c.biancheng.net/view/7897.html
 | 
					 | 
				
			||||||
    tableData: {
 | 
					    tableData: {
 | 
				
			||||||
        fields: {
 | 
					        fields: {
 | 
				
			||||||
            colNames: [
 | 
					            colNames: [
 | 
				
			||||||
                {
 | 
					                {
 | 
				
			||||||
                    prop: 'name',
 | 
					                    prop: 'name',
 | 
				
			||||||
                    label: '字段名称',
 | 
					                    label: '字段名称',
 | 
				
			||||||
 | 
					                    width: 200,
 | 
				
			||||||
                },
 | 
					                },
 | 
				
			||||||
                {
 | 
					                {
 | 
				
			||||||
                    prop: 'type',
 | 
					                    prop: 'type',
 | 
				
			||||||
                    label: '字段类型',
 | 
					                    label: '字段类型',
 | 
				
			||||||
 | 
					                    width: 120,
 | 
				
			||||||
                },
 | 
					                },
 | 
				
			||||||
                {
 | 
					                {
 | 
				
			||||||
                    prop: 'length',
 | 
					                    prop: 'length',
 | 
				
			||||||
                    label: '长度',
 | 
					                    label: '长度',
 | 
				
			||||||
 | 
					                    width: 120,
 | 
				
			||||||
                },
 | 
					                },
 | 
				
			||||||
                {
 | 
					                {
 | 
				
			||||||
                    prop: 'numScale',
 | 
					                    prop: 'numScale',
 | 
				
			||||||
                    label: '小数点',
 | 
					                    label: '小数精度',
 | 
				
			||||||
 | 
					                    width: 120,
 | 
				
			||||||
                },
 | 
					                },
 | 
				
			||||||
                {
 | 
					                {
 | 
				
			||||||
                    prop: 'value',
 | 
					                    prop: 'value',
 | 
				
			||||||
                    label: '默认值',
 | 
					                    label: '默认值',
 | 
				
			||||||
 | 
					                    width: 120,
 | 
				
			||||||
                },
 | 
					                },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                {
 | 
					                {
 | 
				
			||||||
@@ -231,6 +228,7 @@ const state = reactive({
 | 
				
			|||||||
                },
 | 
					                },
 | 
				
			||||||
            ] as ColName[],
 | 
					            ] as ColName[],
 | 
				
			||||||
            res: [] as RowDefinition[],
 | 
					            res: [] as RowDefinition[],
 | 
				
			||||||
 | 
					            oldFields: [] as RowDefinition[],
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
        indexs: {
 | 
					        indexs: {
 | 
				
			||||||
            colNames: [
 | 
					            colNames: [
 | 
				
			||||||
@@ -261,19 +259,36 @@ const state = reactive({
 | 
				
			|||||||
            ],
 | 
					            ],
 | 
				
			||||||
            columns: [{ name: '', remark: '' }],
 | 
					            columns: [{ name: '', remark: '' }],
 | 
				
			||||||
            res: [] as IndexDefinition[],
 | 
					            res: [] as IndexDefinition[],
 | 
				
			||||||
 | 
					            oldIndexs: [] as IndexDefinition[],
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
        tableName: '',
 | 
					        tableName: '',
 | 
				
			||||||
        tableComment: '',
 | 
					        tableComment: '',
 | 
				
			||||||
 | 
					        oldTableName: '',
 | 
				
			||||||
 | 
					        oldTableComment: '',
 | 
				
			||||||
        height: 450,
 | 
					        height: 450,
 | 
				
			||||||
 | 
					        db: '',
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const { dialogVisible, btnloading, activeName, indexTypeList, tableData } = toRefs(state);
 | 
					const { dialogVisible, btnloading, activeName, tableData } = toRefs(state);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
watch(props, async (newValue) => {
 | 
					watch(props, async (newValue) => {
 | 
				
			||||||
    state.dialogVisible = newValue.visible;
 | 
					    state.dialogVisible = newValue.visible;
 | 
				
			||||||
 | 
					    dbDialect = getDbDialect(newValue.dbType);
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// 切换到索引tab时,刷新索引字段下拉选项
 | 
				
			||||||
 | 
					watch(
 | 
				
			||||||
 | 
					    () => state.activeName,
 | 
				
			||||||
 | 
					    (newValue) => {
 | 
				
			||||||
 | 
					        if (newValue === '2') {
 | 
				
			||||||
 | 
					            state.tableData.indexs.columns = state.tableData.fields.res.map((a) => {
 | 
				
			||||||
 | 
					                return { name: a.name, remark: a.remark };
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const cancel = () => {
 | 
					const cancel = () => {
 | 
				
			||||||
    emit('update:visible', false);
 | 
					    emit('update:visible', false);
 | 
				
			||||||
    reset();
 | 
					    reset();
 | 
				
			||||||
@@ -320,6 +335,7 @@ const submit = async () => {
 | 
				
			|||||||
        dbId: props.dbId as any,
 | 
					        dbId: props.dbId as any,
 | 
				
			||||||
        db: props.db as any,
 | 
					        db: props.db as any,
 | 
				
			||||||
        dbType: dbDialect.getInfo().formatSqlDialect,
 | 
					        dbType: dbDialect.getInfo().formatSqlDialect,
 | 
				
			||||||
 | 
					        flowProcdefKey: props.flowProcdefKey,
 | 
				
			||||||
        runSuccessCallback: () => {
 | 
					        runSuccessCallback: () => {
 | 
				
			||||||
            emit('submit-sql', { tableName: state.tableData.tableName });
 | 
					            emit('submit-sql', { tableName: state.tableData.tableName });
 | 
				
			||||||
            // cancel();
 | 
					            // cancel();
 | 
				
			||||||
@@ -333,22 +349,25 @@ const submit = async () => {
 | 
				
			|||||||
 * @param nowArr 修改后的对象数组
 | 
					 * @param nowArr 修改后的对象数组
 | 
				
			||||||
 * @param key 标志对象唯一属性
 | 
					 * @param key 标志对象唯一属性
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
const filterChangedData = (oldArr: object[], nowArr: object[], key: string): { del: any[]; add: any[]; upd: any[] } => {
 | 
					const filterChangedData = (oldArr: object[], nowArr: object[], key: string): { del: any[]; add: any[]; upd: any[]; changed: boolean } => {
 | 
				
			||||||
    let data = {
 | 
					    let data = {
 | 
				
			||||||
        del: [] as object[], // 删除的数据
 | 
					        del: [] as object[], // 删除的数据
 | 
				
			||||||
        add: [] as object[], // 新增的数据
 | 
					        add: [] as object[], // 新增的数据
 | 
				
			||||||
        upd: [] as object[], // 修改的数据
 | 
					        upd: [] as object[], // 修改的数据
 | 
				
			||||||
 | 
					        changed: false,
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // 旧数据为空
 | 
					    // 旧数据为空
 | 
				
			||||||
    if (oldArr && Array.isArray(oldArr) && oldArr.length === 0 && nowArr && Array.isArray(nowArr) && nowArr.length > 0) {
 | 
					    if (oldArr && Array.isArray(oldArr) && oldArr.length === 0 && nowArr && Array.isArray(nowArr) && nowArr.length > 0) {
 | 
				
			||||||
        data.add = nowArr;
 | 
					        data.add = nowArr;
 | 
				
			||||||
 | 
					        data.changed = true;
 | 
				
			||||||
        return data;
 | 
					        return data;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // 新数据为空
 | 
					    // 新数据为空
 | 
				
			||||||
    if (nowArr && Array.isArray(nowArr) && nowArr.length === 0 && oldArr && Array.isArray(oldArr) && oldArr.length > 0) {
 | 
					    if (nowArr && Array.isArray(nowArr) && nowArr.length === 0 && oldArr && Array.isArray(oldArr) && oldArr.length > 0) {
 | 
				
			||||||
        data.del = oldArr;
 | 
					        data.del = oldArr;
 | 
				
			||||||
 | 
					        data.changed = true;
 | 
				
			||||||
        return data;
 | 
					        return data;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -359,8 +378,12 @@ const filterChangedData = (oldArr: object[], nowArr: object[], key: string): { d
 | 
				
			|||||||
    nowArr.forEach((a) => {
 | 
					    nowArr.forEach((a) => {
 | 
				
			||||||
        let k = a[key];
 | 
					        let k = a[key];
 | 
				
			||||||
        newMap[k] = a;
 | 
					        newMap[k] = a;
 | 
				
			||||||
        if (!oldMap.hasOwnProperty(k)) {
 | 
					        // 取oldName,因为修改了name,但是oldName不会变
 | 
				
			||||||
 | 
					        let oldName = a['oldName'];
 | 
				
			||||||
 | 
					        oldName && (newMap[oldName] = a);
 | 
				
			||||||
 | 
					        if (!oldMap.hasOwnProperty(k) && (!oldName || (oldName && !oldMap.hasOwnProperty(oldName)))) {
 | 
				
			||||||
            // 新增
 | 
					            // 新增
 | 
				
			||||||
 | 
					            data.changed = true;
 | 
				
			||||||
            data.add.push(a);
 | 
					            data.add.push(a);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
@@ -370,13 +393,15 @@ const filterChangedData = (oldArr: object[], nowArr: object[], key: string): { d
 | 
				
			|||||||
        let newData = newMap[k];
 | 
					        let newData = newMap[k];
 | 
				
			||||||
        if (!newData) {
 | 
					        if (!newData) {
 | 
				
			||||||
            // 删除
 | 
					            // 删除
 | 
				
			||||||
 | 
					            data.changed = true;
 | 
				
			||||||
            data.del.push(a);
 | 
					            data.del.push(a);
 | 
				
			||||||
        } else {
 | 
					        } else {
 | 
				
			||||||
            // 判断每个字段是否相等,否则为修改
 | 
					            // 判断每个字段是否相等,否则为修改
 | 
				
			||||||
            for (let f in a) {
 | 
					            for (let f in a) {
 | 
				
			||||||
                let oldV = a[f];
 | 
					                let oldV = a[f];
 | 
				
			||||||
                let newV = newData[f];
 | 
					                let newV = newData[f];
 | 
				
			||||||
                if (oldV.toString() !== newV.toString()) {
 | 
					                if (oldV?.toString() !== newV?.toString()) {
 | 
				
			||||||
 | 
					                    data.changed = true;
 | 
				
			||||||
                    data.upd.push(newData);
 | 
					                    data.upd.push(newData);
 | 
				
			||||||
                    break;
 | 
					                    break;
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
@@ -390,22 +415,28 @@ const genSql = () => {
 | 
				
			|||||||
    let data = state.tableData;
 | 
					    let data = state.tableData;
 | 
				
			||||||
    // 创建表
 | 
					    // 创建表
 | 
				
			||||||
    if (!props.data?.edit) {
 | 
					    if (!props.data?.edit) {
 | 
				
			||||||
        if (state.activeName === '1') {
 | 
					        let createTable = dbDialect.getCreateTableSql(data);
 | 
				
			||||||
            return dbDialect.getCreateTableSql(data);
 | 
					        let createIndex = '';
 | 
				
			||||||
        } else if (state.activeName === '2' && data.indexs.res.length > 0) {
 | 
					        if (data.indexs.res.length > 0) {
 | 
				
			||||||
            return dbDialect.getCreateIndexSql(data);
 | 
					            createIndex = dbDialect.getCreateIndexSql(data);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					        return createTable + ';' + createIndex;
 | 
				
			||||||
    } else {
 | 
					    } else {
 | 
				
			||||||
        // 修改
 | 
					        // 修改列
 | 
				
			||||||
        if (state.activeName === '1') {
 | 
					        let changeColData = filterChangedData(state.tableData.fields.oldFields, state.tableData.fields.res, 'name');
 | 
				
			||||||
            // 修改列
 | 
					        let colSql = changeColData.changed ? dbDialect.getModifyColumnSql(data, data.tableName, changeColData) : '';
 | 
				
			||||||
            let changeData = filterChangedData(oldData.fields, state.tableData.fields.res, 'name');
 | 
					        // 修改索引
 | 
				
			||||||
            return dbDialect.getModifyColumnSql(data.tableName, changeData);
 | 
					        let changeIdxData = filterChangedData(state.tableData.indexs.oldIndexs, state.tableData.indexs.res, 'indexName');
 | 
				
			||||||
        } else if (state.activeName === '2') {
 | 
					        let idxSql = changeIdxData.changed ? dbDialect.getModifyIndexSql(data, data.tableName, changeIdxData) : '';
 | 
				
			||||||
            // 修改索引
 | 
					        // 修改表名,表注释
 | 
				
			||||||
            let changeData = filterChangedData(oldData.indexs, state.tableData.indexs.res, 'indexName');
 | 
					        let tableInfoSql = data.tableName !== data.oldTableName || data.tableComment !== data.oldTableComment ? dbDialect.getModifyTableInfoSql(data) : '';
 | 
				
			||||||
            return dbDialect.getModifyIndexSql(data.tableName, changeData);
 | 
					
 | 
				
			||||||
        }
 | 
					        let sqlArr = [];
 | 
				
			||||||
 | 
					        colSql && sqlArr.push(colSql);
 | 
				
			||||||
 | 
					        idxSql && sqlArr.push(idxSql);
 | 
				
			||||||
 | 
					        tableInfoSql && sqlArr.push(tableInfoSql);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return sqlArr.join(';');
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -414,28 +445,10 @@ const reset = () => {
 | 
				
			|||||||
    formRef.value.resetFields();
 | 
					    formRef.value.resetFields();
 | 
				
			||||||
    state.tableData.tableName = '';
 | 
					    state.tableData.tableName = '';
 | 
				
			||||||
    state.tableData.tableComment = '';
 | 
					    state.tableData.tableComment = '';
 | 
				
			||||||
    state.tableData.fields.res = [
 | 
					    state.tableData.fields.res = [];
 | 
				
			||||||
        {
 | 
					    state.tableData.fields.oldFields = [];
 | 
				
			||||||
            name: '',
 | 
					    state.tableData.indexs.res = [];
 | 
				
			||||||
            type: '',
 | 
					    state.tableData.indexs.oldIndexs = [];
 | 
				
			||||||
            value: '',
 | 
					 | 
				
			||||||
            length: '',
 | 
					 | 
				
			||||||
            numScale: '',
 | 
					 | 
				
			||||||
            notNull: false,
 | 
					 | 
				
			||||||
            pri: false,
 | 
					 | 
				
			||||||
            auto_increment: false,
 | 
					 | 
				
			||||||
            remark: '',
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
    ];
 | 
					 | 
				
			||||||
    state.tableData.indexs.res = [
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            indexName: '',
 | 
					 | 
				
			||||||
            columnNames: [],
 | 
					 | 
				
			||||||
            unique: false,
 | 
					 | 
				
			||||||
            indexType: 'BTREE',
 | 
					 | 
				
			||||||
            indexComment: '',
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
    ];
 | 
					 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const indexChanges = (row: any) => {
 | 
					const indexChanges = (row: any) => {
 | 
				
			||||||
@@ -456,7 +469,21 @@ const indexChanges = (row: any) => {
 | 
				
			|||||||
    row.indexComment = `${tableData.value.tableName}表(${name.replaceAll('_', ',')})${commentSuffix}`;
 | 
					    row.indexComment = `${tableData.value.tableName}表(${name.replaceAll('_', ',')})${commentSuffix}`;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const oldData = { indexs: [] as any[], fields: [] as RowDefinition[] };
 | 
					const disableEditIncr = () => {
 | 
				
			||||||
 | 
					    if (DbType.postgresql === props.dbType) {
 | 
				
			||||||
 | 
					        return true;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // 如果是mssql则不能修改自增
 | 
				
			||||||
 | 
					    if (props.data?.edit) {
 | 
				
			||||||
 | 
					        if (DbType.mssql === props.dbType) {
 | 
				
			||||||
 | 
					            return true;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return false;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
watch(
 | 
					watch(
 | 
				
			||||||
    () => props.data,
 | 
					    () => props.data,
 | 
				
			||||||
    (newValue: any) => {
 | 
					    (newValue: any) => {
 | 
				
			||||||
@@ -464,37 +491,47 @@ watch(
 | 
				
			|||||||
        // 回显表名表注释
 | 
					        // 回显表名表注释
 | 
				
			||||||
        state.tableData.tableName = row.tableName;
 | 
					        state.tableData.tableName = row.tableName;
 | 
				
			||||||
        state.tableData.tableComment = row.tableComment;
 | 
					        state.tableData.tableComment = row.tableComment;
 | 
				
			||||||
 | 
					        state.tableData.oldTableName = row.tableName;
 | 
				
			||||||
 | 
					        state.tableData.oldTableComment = row.tableComment;
 | 
				
			||||||
 | 
					        state.tableData.db = props.db!;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        state.tableData.fields.oldFields = [];
 | 
				
			||||||
 | 
					        state.tableData.fields.res = [];
 | 
				
			||||||
 | 
					        state.tableData.indexs.oldIndexs = [];
 | 
				
			||||||
 | 
					        state.tableData.indexs.res = [];
 | 
				
			||||||
 | 
					        // 索引列下拉选
 | 
				
			||||||
 | 
					        state.tableData.indexs.columns = [];
 | 
				
			||||||
 | 
					        DbInst.initColumns(columns);
 | 
				
			||||||
        // 回显列
 | 
					        // 回显列
 | 
				
			||||||
        if (columns && Array.isArray(columns) && columns.length > 0) {
 | 
					        if (columns && Array.isArray(columns) && columns.length > 0) {
 | 
				
			||||||
            oldData.fields = [];
 | 
					 | 
				
			||||||
            state.tableData.fields.res = [];
 | 
					 | 
				
			||||||
            // 索引列下拉选
 | 
					 | 
				
			||||||
            state.tableData.indexs.columns = [];
 | 
					 | 
				
			||||||
            columns.forEach((a) => {
 | 
					            columns.forEach((a) => {
 | 
				
			||||||
                let typeObj = a.columnType.replace(')', '').split('(');
 | 
					                let defaultValue = '';
 | 
				
			||||||
                let type = typeObj[0];
 | 
					                if (a.columnDefault) {
 | 
				
			||||||
                let length = (typeObj.length > 1 && typeObj[1]) || '';
 | 
					                    defaultValue = a.columnDefault.trim().replace(/^'|'$/g, '');
 | 
				
			||||||
 | 
					                    // 解决高斯的默认值问题
 | 
				
			||||||
 | 
					                    defaultValue = defaultValue.replace("'::character varying", '');
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
                let data = {
 | 
					                let data = {
 | 
				
			||||||
                    name: a.columnName,
 | 
					                    name: a.columnName,
 | 
				
			||||||
                    type,
 | 
					                    oldName: a.columnName,
 | 
				
			||||||
                    value: a.columnDefault || '',
 | 
					                    type: a.dataType,
 | 
				
			||||||
                    length,
 | 
					                    value: defaultValue,
 | 
				
			||||||
                    numScale: a.numScale,
 | 
					                    length: a.showLength,
 | 
				
			||||||
                    notNull: a.nullable !== 'YES',
 | 
					                    numScale: a.showScale,
 | 
				
			||||||
                    pri: a.columnKey === 'PRI',
 | 
					                    notNull: !a.nullable,
 | 
				
			||||||
                    auto_increment: a.columnKey === 'PRI' /*a.extra?.indexOf('auto_increment') > -1*/,
 | 
					                    pri: a.isPrimaryKey,
 | 
				
			||||||
 | 
					                    auto_increment: a.isIdentity /*a.extra?.indexOf('auto_increment') > -1*/,
 | 
				
			||||||
                    remark: a.columnComment,
 | 
					                    remark: a.columnComment,
 | 
				
			||||||
                };
 | 
					                };
 | 
				
			||||||
                state.tableData.fields.res.push(data);
 | 
					                state.tableData.fields.res.push(data);
 | 
				
			||||||
                oldData.fields.push(JSON.parse(JSON.stringify(data)));
 | 
					                state.tableData.fields.oldFields.push(JSON.parse(JSON.stringify(data)));
 | 
				
			||||||
                // 索引字段下拉选项
 | 
					                // 索引字段下拉选项
 | 
				
			||||||
                state.tableData.indexs.columns.push({ name: a.columnName, remark: a.columnComment });
 | 
					                state.tableData.indexs.columns.push({ name: a.columnName, remark: a.columnComment });
 | 
				
			||||||
            });
 | 
					            });
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // 回显索引
 | 
					        // 回显索引
 | 
				
			||||||
        if (indexs && Array.isArray(indexs) && indexs.length > 0) {
 | 
					        if (indexs && Array.isArray(indexs) && indexs.length > 0) {
 | 
				
			||||||
            oldData.indexs = [];
 | 
					 | 
				
			||||||
            state.tableData.indexs.res = [];
 | 
					 | 
				
			||||||
            // 索引过滤掉主键
 | 
					            // 索引过滤掉主键
 | 
				
			||||||
            indexs
 | 
					            indexs
 | 
				
			||||||
                .filter((a) => a.indexName !== 'PRIMARY')
 | 
					                .filter((a) => a.indexName !== 'PRIMARY')
 | 
				
			||||||
@@ -502,12 +539,12 @@ watch(
 | 
				
			|||||||
                    let data = {
 | 
					                    let data = {
 | 
				
			||||||
                        indexName: a.indexName,
 | 
					                        indexName: a.indexName,
 | 
				
			||||||
                        columnNames: a.columnName?.split(','),
 | 
					                        columnNames: a.columnName?.split(','),
 | 
				
			||||||
                        unique: a.nonUnique === 0 || false,
 | 
					                        unique: a.isUnique || false,
 | 
				
			||||||
                        indexType: a.indexType,
 | 
					                        indexType: a.indexType,
 | 
				
			||||||
                        indexComment: a.indexComment,
 | 
					                        indexComment: a.indexComment,
 | 
				
			||||||
                    };
 | 
					                    };
 | 
				
			||||||
                    state.tableData.indexs.res.push(data);
 | 
					                    state.tableData.indexs.res.push(data);
 | 
				
			||||||
                    oldData.indexs.push(JSON.parse(JSON.stringify(data)));
 | 
					                    state.tableData.indexs.oldIndexs.push(JSON.parse(JSON.stringify(data)));
 | 
				
			||||||
                });
 | 
					                });
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -7,9 +7,9 @@
 | 
				
			|||||||
                </template>
 | 
					                </template>
 | 
				
			||||||
                <el-form-item label="导出内容: ">
 | 
					                <el-form-item label="导出内容: ">
 | 
				
			||||||
                    <el-radio-group v-model="dumpInfo.type">
 | 
					                    <el-radio-group v-model="dumpInfo.type">
 | 
				
			||||||
                        <el-radio :label="1" size="small">结构</el-radio>
 | 
					                        <el-radio :value="1" size="small">结构</el-radio>
 | 
				
			||||||
                        <el-radio :label="2" size="small">数据</el-radio>
 | 
					                        <el-radio :value="2" size="small">数据</el-radio>
 | 
				
			||||||
                        <el-radio :label="3" size="small">结构+数据</el-radio>
 | 
					                        <el-radio :value="3" size="small">结构+数据</el-radio>
 | 
				
			||||||
                    </el-radio-group>
 | 
					                    </el-radio-group>
 | 
				
			||||||
                </el-form-item>
 | 
					                </el-form-item>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -68,9 +68,7 @@
 | 
				
			|||||||
                <template #default="scope">
 | 
					                <template #default="scope">
 | 
				
			||||||
                    <el-link @click.prevent="showColumns(scope.row)" type="primary">字段</el-link>
 | 
					                    <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" @click.prevent="showTableIndex(scope.row)" type="success">索引</el-link>
 | 
				
			||||||
                    <el-link class="ml5" v-if="tableCreateDialog.enableEditTypes.indexOf(dbType) > -1" @click.prevent="openEditTable(scope.row)" type="warning"
 | 
					                    <el-link class="ml5" v-if="editDbTypes.indexOf(dbType) > -1" @click.prevent="openEditTable(scope.row)" type="warning">编辑表</el-link>
 | 
				
			||||||
                        >编辑表</el-link
 | 
					 | 
				
			||||||
                    >
 | 
					 | 
				
			||||||
                    <el-link class="ml5" @click.prevent="showCreateDdl(scope.row)" type="info">DDL</el-link>
 | 
					                    <el-link class="ml5" @click.prevent="showCreateDdl(scope.row)" type="info">DDL</el-link>
 | 
				
			||||||
                </template>
 | 
					                </template>
 | 
				
			||||||
            </el-table-column>
 | 
					            </el-table-column>
 | 
				
			||||||
@@ -84,7 +82,7 @@
 | 
				
			|||||||
        <el-dialog width="40%" :title="`${chooseTableName} 字段信息`" v-model="columnDialog.visible">
 | 
					        <el-dialog width="40%" :title="`${chooseTableName} 字段信息`" v-model="columnDialog.visible">
 | 
				
			||||||
            <el-table border stripe :data="columnDialog.columns" size="small">
 | 
					            <el-table border stripe :data="columnDialog.columns" size="small">
 | 
				
			||||||
                <el-table-column prop="columnName" label="名称" show-overflow-tooltip> </el-table-column>
 | 
					                <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="120" prop="showDataType" label="类型" show-overflow-tooltip> </el-table-column>
 | 
				
			||||||
                <el-table-column width="80" prop="nullable" 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-column prop="columnComment" label="备注" show-overflow-tooltip> </el-table-column>
 | 
				
			||||||
            </el-table>
 | 
					            </el-table>
 | 
				
			||||||
@@ -100,8 +98,8 @@
 | 
				
			|||||||
            </el-table>
 | 
					            </el-table>
 | 
				
			||||||
        </el-dialog>
 | 
					        </el-dialog>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        <el-dialog width="55%" :title="`${chooseTableName} Create-DDL`" v-model="ddlDialog.visible">
 | 
					        <el-dialog width="55%" :title="`'${chooseTableName}' DDL`" v-model="ddlDialog.visible">
 | 
				
			||||||
            <el-input disabled type="textarea" :autosize="{ minRows: 15, maxRows: 30 }" v-model="ddlDialog.ddl" size="small"> </el-input>
 | 
					            <monaco-editor height="400px" language="sql" v-model="ddlDialog.ddl" :options="{ readOnly: true }" />
 | 
				
			||||||
        </el-dialog>
 | 
					        </el-dialog>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        <db-table-op
 | 
					        <db-table-op
 | 
				
			||||||
@@ -110,6 +108,7 @@
 | 
				
			|||||||
            :dbId="dbId"
 | 
					            :dbId="dbId"
 | 
				
			||||||
            :db="db"
 | 
					            :db="db"
 | 
				
			||||||
            :dbType="dbType"
 | 
					            :dbType="dbType"
 | 
				
			||||||
 | 
					            :flow-procdef-key="props.flowProcdefKey"
 | 
				
			||||||
            :data="tableCreateDialog.data"
 | 
					            :data="tableCreateDialog.data"
 | 
				
			||||||
            v-model:visible="tableCreateDialog.visible"
 | 
					            v-model:visible="tableCreateDialog.visible"
 | 
				
			||||||
            @submit-sql="onSubmitSql"
 | 
					            @submit-sql="onSubmitSql"
 | 
				
			||||||
@@ -127,7 +126,10 @@ import SqlExecBox from '../sqleditor/SqlExecBox';
 | 
				
			|||||||
import config from '@/common/config';
 | 
					import config from '@/common/config';
 | 
				
			||||||
import { joinClientParams } from '@/common/request';
 | 
					import { joinClientParams } from '@/common/request';
 | 
				
			||||||
import { isTrue } from '@/common/assert';
 | 
					import { isTrue } from '@/common/assert';
 | 
				
			||||||
import { compatibleMysql, DbType } from '../../dialect/index';
 | 
					import { compatibleMysql, editDbTypes, getDbDialect } from '../../dialect/index';
 | 
				
			||||||
 | 
					import { DbInst } from '../../db';
 | 
				
			||||||
 | 
					import MonacoEditor from '@/components/monaco/MonacoEditor.vue';
 | 
				
			||||||
 | 
					import { format as sqlFormatter } from 'sql-formatter';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const DbTableOp = defineAsyncComponent(() => import('./DbTableOp.vue'));
 | 
					const DbTableOp = defineAsyncComponent(() => import('./DbTableOp.vue'));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -148,6 +150,9 @@ const props = defineProps({
 | 
				
			|||||||
        type: [String],
 | 
					        type: [String],
 | 
				
			||||||
        required: true,
 | 
					        required: true,
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					    flowProcdefKey: {
 | 
				
			||||||
 | 
					        type: [String],
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const state = reactive({
 | 
					const state = reactive({
 | 
				
			||||||
@@ -181,7 +186,6 @@ const state = reactive({
 | 
				
			|||||||
        visible: false,
 | 
					        visible: false,
 | 
				
			||||||
        activeName: '1',
 | 
					        activeName: '1',
 | 
				
			||||||
        type: '',
 | 
					        type: '',
 | 
				
			||||||
        enableEditTypes: [DbType.mysql, DbType.mariadb, DbType.postgresql, DbType.dm, DbType.oracle], // 支持"编辑表"的数据库类型
 | 
					 | 
				
			||||||
        data: {
 | 
					        data: {
 | 
				
			||||||
            // 修改表时,传递修改数据
 | 
					            // 修改表时,传递修改数据
 | 
				
			||||||
            edit: false,
 | 
					            edit: false,
 | 
				
			||||||
@@ -274,11 +278,13 @@ const dump = (db: string) => {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
const showColumns = async (row: any) => {
 | 
					const showColumns = async (row: any) => {
 | 
				
			||||||
    state.chooseTableName = row.tableName;
 | 
					    state.chooseTableName = row.tableName;
 | 
				
			||||||
    state.columnDialog.columns = await dbApi.columnMetadata.request({
 | 
					    const columns = await dbApi.columnMetadata.request({
 | 
				
			||||||
        id: props.dbId,
 | 
					        id: props.dbId,
 | 
				
			||||||
        db: props.db,
 | 
					        db: props.db,
 | 
				
			||||||
        tableName: row.tableName,
 | 
					        tableName: row.tableName,
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					    DbInst.initColumns(columns);
 | 
				
			||||||
 | 
					    state.columnDialog.columns = columns;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    state.columnDialog.visible = true;
 | 
					    state.columnDialog.visible = true;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
@@ -301,7 +307,8 @@ const showCreateDdl = async (row: any) => {
 | 
				
			|||||||
        db: props.db,
 | 
					        db: props.db,
 | 
				
			||||||
        tableName: row.tableName,
 | 
					        tableName: row.tableName,
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
    state.ddlDialog.ddl = res;
 | 
					
 | 
				
			||||||
 | 
					    state.ddlDialog.ddl = sqlFormatter(res, { language: getDbDialect(props.dbType).getInfo().formatSqlDialect });
 | 
				
			||||||
    state.ddlDialog.visible = true;
 | 
					    state.ddlDialog.visible = true;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -320,6 +327,7 @@ const dropTable = async (row: any) => {
 | 
				
			|||||||
            sql: `DROP TABLE ${tableName}`,
 | 
					            sql: `DROP TABLE ${tableName}`,
 | 
				
			||||||
            dbId: props.dbId as any,
 | 
					            dbId: props.dbId as any,
 | 
				
			||||||
            db: props.db as any,
 | 
					            db: props.db as any,
 | 
				
			||||||
 | 
					            flowProcdefKey: props.flowProcdefKey,
 | 
				
			||||||
            runSuccessCallback: async () => {
 | 
					            runSuccessCallback: async () => {
 | 
				
			||||||
                await getTables();
 | 
					                await getTables();
 | 
				
			||||||
            },
 | 
					            },
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -7,6 +7,10 @@ import { editor, languages, Position } from 'monaco-editor';
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
import { registerCompletionItemProvider } from '@/components/monaco/completionItemProvider';
 | 
					import { registerCompletionItemProvider } from '@/components/monaco/completionItemProvider';
 | 
				
			||||||
import { DbDialect, EditorCompletionItem, getDbDialect } from './dialect';
 | 
					import { DbDialect, EditorCompletionItem, getDbDialect } from './dialect';
 | 
				
			||||||
 | 
					import { type RemovableRef, useLocalStorage } from '@vueuse/core';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const hintsStorage: RemovableRef<Map<string, any>> = useLocalStorage('db-table-hints', new Map());
 | 
				
			||||||
 | 
					const tableStorage: RemovableRef<Map<string, any>> = useLocalStorage('db-tables', new Map());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const dbInstCache: Map<number, DbInst> = new Map();
 | 
					const dbInstCache: Map<number, DbInst> = new Map();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -36,6 +40,11 @@ export class DbInst {
 | 
				
			|||||||
     */
 | 
					     */
 | 
				
			||||||
    type: string;
 | 
					    type: string;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * 流程定义key,若存在则需要审批执行
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    flowProcdefKey: string;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * dbName -> db
 | 
					     * dbName -> db
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
@@ -58,17 +67,23 @@ export class DbInst {
 | 
				
			|||||||
        if (!dbName) {
 | 
					        if (!dbName) {
 | 
				
			||||||
            throw new Error('dbName不能为空');
 | 
					            throw new Error('dbName不能为空');
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        let db = this.dbs.get(dbName);
 | 
					        let key = `${this.id}_${dbName}`;
 | 
				
			||||||
 | 
					        let db = this.dbs.get(key);
 | 
				
			||||||
        if (db) {
 | 
					        if (db) {
 | 
				
			||||||
            return db;
 | 
					            return db;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        console.info(`new db -> dbId: ${this.id}, dbName: ${dbName}`);
 | 
					        console.info(`new db -> dbId: ${this.id}, dbName: ${dbName}`);
 | 
				
			||||||
        db = new Db();
 | 
					        db = new Db();
 | 
				
			||||||
        db.name = dbName;
 | 
					        db.name = dbName;
 | 
				
			||||||
        this.dbs.set(dbName, db);
 | 
					        this.dbs.set(key, db);
 | 
				
			||||||
        return db;
 | 
					        return db;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // 获取数据库实例方言
 | 
				
			||||||
 | 
					    getDialect(): DbDialect {
 | 
				
			||||||
 | 
					        return getDbDialect(this.type);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * 加载数据库表信息
 | 
					     * 加载数据库表信息
 | 
				
			||||||
     * @param dbName 数据库名
 | 
					     * @param dbName 数据库名
 | 
				
			||||||
@@ -77,17 +92,22 @@ export class DbInst {
 | 
				
			|||||||
     */
 | 
					     */
 | 
				
			||||||
    async loadTables(dbName: string, reload?: boolean) {
 | 
					    async loadTables(dbName: string, reload?: boolean) {
 | 
				
			||||||
        const db = this.getDb(dbName);
 | 
					        const db = this.getDb(dbName);
 | 
				
			||||||
        // 优先从 table map中获取
 | 
					        let key = this.dbTablesKey(dbName);
 | 
				
			||||||
        let tables = db.tables;
 | 
					        let tables = tableStorage.value.get(key);
 | 
				
			||||||
 | 
					        // 优先从 table 缓存中获取
 | 
				
			||||||
        if (!reload && tables) {
 | 
					        if (!reload && tables) {
 | 
				
			||||||
 | 
					            db.tables = tables;
 | 
				
			||||||
            return tables;
 | 
					            return tables;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        // 重置列信息缓存与表提示信息
 | 
					        // 重置列信息缓存与表提示信息
 | 
				
			||||||
        db.columnsMap?.clear();
 | 
					        db.columnsMap?.clear();
 | 
				
			||||||
        db.tableHints = null;
 | 
					 | 
				
			||||||
        console.log(`load tables -> dbName: ${dbName}`);
 | 
					        console.log(`load tables -> dbName: ${dbName}`);
 | 
				
			||||||
        tables = await dbApi.tableInfos.request({ id: this.id, db: dbName });
 | 
					        tables = await dbApi.tableInfos.request({ id: this.id, db: dbName });
 | 
				
			||||||
 | 
					        tableStorage.value.set(key, tables);
 | 
				
			||||||
        db.tables = tables;
 | 
					        db.tables = tables;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // 异步加载表提示信息
 | 
				
			||||||
 | 
					        this.loadDbHints(dbName, true).then(() => {});
 | 
				
			||||||
        return tables;
 | 
					        return tables;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -146,7 +166,7 @@ export class DbInst {
 | 
				
			|||||||
        const db = this.getDb(dbName);
 | 
					        const db = this.getDb(dbName);
 | 
				
			||||||
        // 优先从 table map中获取
 | 
					        // 优先从 table map中获取
 | 
				
			||||||
        let columns = db.getColumns(table);
 | 
					        let columns = db.getColumns(table);
 | 
				
			||||||
        if (columns) {
 | 
					        if (columns && columns.length > 0) {
 | 
				
			||||||
            return columns;
 | 
					            return columns;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        console.log(`load columns -> dbName: ${dbName}, table: ${table}`);
 | 
					        console.log(`load columns -> dbName: ${dbName}, table: ${table}`);
 | 
				
			||||||
@@ -155,6 +175,9 @@ export class DbInst {
 | 
				
			|||||||
            db: dbName,
 | 
					            db: dbName,
 | 
				
			||||||
            tableName: table,
 | 
					            tableName: table,
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        DbInst.initColumns(columns);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        db.columnsMap.set(table, columns);
 | 
					        db.columnsMap.set(table, columns);
 | 
				
			||||||
        return columns;
 | 
					        return columns;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@@ -169,18 +192,30 @@ export class DbInst {
 | 
				
			|||||||
        return this.getDb(dbName).getColumn(table, columnName);
 | 
					        return this.getDb(dbName).getColumn(table, columnName);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    dbTableHintsKey(dbName: string) {
 | 
				
			||||||
 | 
					        return `db-table-hints_${this.id}_${dbName}`;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    dbTablesKey(dbName: string) {
 | 
				
			||||||
 | 
					        return `db-tables_${this.id}_${dbName}`;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * 获取库信息提示
 | 
					     * 获取库信息提示
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    async loadDbHints(dbName: string) {
 | 
					    async loadDbHints(dbName: string, reload?: boolean) {
 | 
				
			||||||
        const db = this.getDb(dbName);
 | 
					        const db = this.getDb(dbName);
 | 
				
			||||||
        if (db.tableHints) {
 | 
					        let key = this.dbTableHintsKey(dbName);
 | 
				
			||||||
            return db.tableHints;
 | 
					        let hints = hintsStorage.value.get(key);
 | 
				
			||||||
 | 
					        if (!reload && hints) {
 | 
				
			||||||
 | 
					            db.tableHints = hints;
 | 
				
			||||||
 | 
					            return hints;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        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 });
 | 
					        hints = await dbApi.hintTables.request({ id: this.id, db: db.name });
 | 
				
			||||||
        db.tableHints = hits;
 | 
					        db.tableHints = hints;
 | 
				
			||||||
        return hits;
 | 
					        hintsStorage.value.set(key, hints);
 | 
				
			||||||
 | 
					        return hints;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
@@ -225,8 +260,8 @@ export class DbInst {
 | 
				
			|||||||
    };
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // 获取指定表的默认查询sql
 | 
					    // 获取指定表的默认查询sql
 | 
				
			||||||
    getDefaultSelectSql(table: string, condition: string, orderBy: string, pageNum: number, limit: number = DbInst.DefaultLimit) {
 | 
					    getDefaultSelectSql(db: string, table: string, condition: string, orderBy: string, pageNum: number, limit: number = DbInst.DefaultLimit) {
 | 
				
			||||||
        return getDbDialect(this.type).getDefaultSelectSql(table, condition, orderBy, pageNum, limit);
 | 
					        return this.getDialect().getDefaultSelectSql(db, table, condition, orderBy, pageNum, limit);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
@@ -234,12 +269,22 @@ export class DbInst {
 | 
				
			|||||||
     * @param dbName 数据库名
 | 
					     * @param dbName 数据库名
 | 
				
			||||||
     * @param table 表名
 | 
					     * @param table 表名
 | 
				
			||||||
     * @param datas 要生成的数据
 | 
					     * @param datas 要生成的数据
 | 
				
			||||||
 | 
					     * @param dbDialect db方言
 | 
				
			||||||
 | 
					     * @param skipNull 是否跳过空字段
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    async genInsertSql(dbName: string, table: string, datas: any[]) {
 | 
					    async genInsertSql(dbName: string, table: string, datas: any[], skipNull = false) {
 | 
				
			||||||
        if (!datas) {
 | 
					        if (!datas) {
 | 
				
			||||||
            return '';
 | 
					            return '';
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					        let schema = '';
 | 
				
			||||||
 | 
					        let arr = dbName.split('/');
 | 
				
			||||||
 | 
					        if (arr.length == 1) {
 | 
				
			||||||
 | 
					            schema = this.wrapName(dbName) + '.';
 | 
				
			||||||
 | 
					        } else if (arr.length == 2) {
 | 
				
			||||||
 | 
					            schema = this.wrapName(arr[1]) + '.';
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        let dbDialect = this.getDialect();
 | 
				
			||||||
        const columns = await this.loadColumns(dbName, table);
 | 
					        const columns = await this.loadColumns(dbName, table);
 | 
				
			||||||
        const sqls = [];
 | 
					        const sqls = [];
 | 
				
			||||||
        for (let data of datas) {
 | 
					        for (let data of datas) {
 | 
				
			||||||
@@ -247,23 +292,59 @@ 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;
 | 
				
			||||||
 | 
					                if (skipNull && data[colName] == null) {
 | 
				
			||||||
 | 
					                    continue;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
                colNames.push(this.wrapName(colName));
 | 
					                colNames.push(this.wrapName(colName));
 | 
				
			||||||
                values.push(DbInst.wrapValueByType(data[colName]));
 | 
					                values.push(dbDialect.wrapValue(column.dataType, data[colName]));
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            sqls.push(`INSERT INTO ${this.wrapName(table)} (${colNames.join(', ')}) VALUES(${values.join(', ')})`);
 | 
					            sqls.push(`INSERT INTO ${schema}${this.wrapName(table)} (${colNames.join(', ')}) VALUES(${values.join(', ')})`);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        return sqls.join(';\n') + ';';
 | 
					        return sqls.join(';\n') + ';';
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * 生成根据主键更新语句
 | 
				
			||||||
 | 
					     * @param dbName 数据库名
 | 
				
			||||||
 | 
					     * @param table 表名
 | 
				
			||||||
 | 
					     * @param columnValue 要更新的列以及对应的值 field->columnName; value->columnValue
 | 
				
			||||||
 | 
					     * @param rowData 表的一行完整数据(需要获取主键信息)
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    async genUpdateSql(dbName: string, table: string, columnValue: {}, rowData: {}) {
 | 
				
			||||||
 | 
					        let schema = '';
 | 
				
			||||||
 | 
					        let dbArr = dbName.split('/');
 | 
				
			||||||
 | 
					        if (dbArr.length == 2) {
 | 
				
			||||||
 | 
					            schema = this.wrapName(dbArr[1]) + '.';
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        let sql = `UPDATE ${schema}${this.wrapName(table)} SET `;
 | 
				
			||||||
 | 
					        // 主键列信息
 | 
				
			||||||
 | 
					        const primaryKey = await this.loadTableColumn(dbName, table);
 | 
				
			||||||
 | 
					        let primaryKeyType = primaryKey.dataType;
 | 
				
			||||||
 | 
					        let primaryKeyName = primaryKey.columnName;
 | 
				
			||||||
 | 
					        let primaryKeyValue = rowData[primaryKeyName];
 | 
				
			||||||
 | 
					        const dialect = this.getDialect();
 | 
				
			||||||
 | 
					        for (let k of Object.keys(columnValue)) {
 | 
				
			||||||
 | 
					            const v = columnValue[k];
 | 
				
			||||||
 | 
					            // 更新字段列信息
 | 
				
			||||||
 | 
					            const updateColumn = await this.loadTableColumn(dbName, table, k);
 | 
				
			||||||
 | 
					            sql += ` ${this.wrapName(k)} = ${dialect.wrapValue(updateColumn.dataType, v)},`;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        sql = sql.substring(0, sql.length - 1);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return sql + ` WHERE ${this.wrapName(primaryKeyName)} = ${this.getDialect().wrapValue(primaryKeyType, primaryKeyValue)} ;`;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * 生成根据主键删除的sql语句
 | 
					     * 生成根据主键删除的sql语句
 | 
				
			||||||
 | 
					     * @param db 数据库名
 | 
				
			||||||
     * @param table 表名
 | 
					     * @param table 表名
 | 
				
			||||||
     * @param datas 要删除的记录
 | 
					     * @param datas 要删除的记录
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    async genDeleteByPrimaryKeysSql(db: string, table: string, datas: any[]) {
 | 
					    async genDeleteByPrimaryKeysSql(db: string, table: string, datas: any[]) {
 | 
				
			||||||
        const primaryKey = await this.loadTableColumn(db, table);
 | 
					        const primaryKey = await this.loadTableColumn(db, 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) => `${this.getDialect().wrapValue(primaryKey.dataType, d[primaryKeyColumnName])}`).join(',');
 | 
				
			||||||
        return `DELETE FROM ${this.wrapName(table)} WHERE ${this.wrapName(primaryKeyColumnName)} IN (${ids})`;
 | 
					        return `DELETE FROM ${this.wrapName(table)} WHERE ${this.wrapName(primaryKeyColumnName)} IN (${ids})`;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -275,19 +356,20 @@ export class DbInst {
 | 
				
			|||||||
            sql,
 | 
					            sql,
 | 
				
			||||||
            dbId: this.id,
 | 
					            dbId: this.id,
 | 
				
			||||||
            db,
 | 
					            db,
 | 
				
			||||||
 | 
					            dbType: this.getDialect().getInfo().formatSqlDialect,
 | 
				
			||||||
            runSuccessCallback: successFunc,
 | 
					            runSuccessCallback: successFunc,
 | 
				
			||||||
            cancelCallback: cancelFunc,
 | 
					            cancelCallback: cancelFunc,
 | 
				
			||||||
 | 
					            flowProcdefKey: this.flowProcdefKey,
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * 包裹数据库表名、字段名等,避免使用关键字为字段名或表名时报错
 | 
					     * 包裹数据库表名、字段名等,避免使用关键字为字段名或表名时报错
 | 
				
			||||||
     * @param table
 | 
					     * @param name 表名、字段名、schema名
 | 
				
			||||||
     * @param condition
 | 
					     * @returns 包裹后的字符串
 | 
				
			||||||
     * @returns
 | 
					 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    wrapName = (name: string) => {
 | 
					    wrapName = (name: string) => {
 | 
				
			||||||
        return getDbDialect(this.type).quoteIdentifier(name);
 | 
					        return this.getDialect().quoteIdentifier(name);
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
@@ -311,6 +393,7 @@ export class DbInst {
 | 
				
			|||||||
        dbInst.name = inst.name;
 | 
					        dbInst.name = inst.name;
 | 
				
			||||||
        dbInst.type = inst.type;
 | 
					        dbInst.type = inst.type;
 | 
				
			||||||
        dbInst.databases = inst.databases;
 | 
					        dbInst.databases = inst.databases;
 | 
				
			||||||
 | 
					        dbInst.flowProcdefKey = inst.flowProcdefKey;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        dbInstCache.set(dbInst.id, dbInst);
 | 
					        dbInstCache.set(dbInst.id, dbInst);
 | 
				
			||||||
        return dbInst;
 | 
					        return dbInst;
 | 
				
			||||||
@@ -340,41 +423,13 @@ export class DbInst {
 | 
				
			|||||||
        dbInstCache.clear();
 | 
					        dbInstCache.clear();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * 根据返回值包装值,若值为字符串类型则添加''
 | 
					 | 
				
			||||||
     * @param val 值
 | 
					 | 
				
			||||||
     * @returns 包装后的值
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    static wrapValueByType = (val: any) => {
 | 
					 | 
				
			||||||
        if (val == null) {
 | 
					 | 
				
			||||||
            return 'NULL';
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        if (typeof val == 'number') {
 | 
					 | 
				
			||||||
            return val;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        return `'${val}'`;
 | 
					 | 
				
			||||||
    };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * 根据字段类型包装字段值,如为字符串等则添加‘’,数字类型则直接返回即可
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    static wrapColumnValue(columnType: string, value: any, dbDialect?: DbDialect) {
 | 
					 | 
				
			||||||
        if (this.isNumber(columnType)) {
 | 
					 | 
				
			||||||
            return value;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        if (!dbDialect) {
 | 
					 | 
				
			||||||
            return `${value}`;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        return dbDialect.wrapStrValue(columnType, value);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * 判断字段类型是否为数字类型
 | 
					     * 判断字段类型是否为数字类型
 | 
				
			||||||
     * @param columnType 字段类型
 | 
					     * @param columnType 字段类型
 | 
				
			||||||
     * @returns
 | 
					     * @returns
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    static isNumber(columnType: string) {
 | 
					    static isNumber(columnType: string) {
 | 
				
			||||||
        return columnType.match(/int|double|float|number|decimal|byte|bit/gi);
 | 
					        return columnType && columnType.match(/int|double|float|number|decimal|byte|bit/gi);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
@@ -414,6 +469,35 @@ export class DbInst {
 | 
				
			|||||||
        const flexWidth: number = contentWidth > columnWidth ? contentWidth : columnWidth;
 | 
					        const flexWidth: number = contentWidth > columnWidth ? contentWidth : columnWidth;
 | 
				
			||||||
        return flexWidth > 500 ? 500 : flexWidth;
 | 
					        return flexWidth > 500 ? 500 : flexWidth;
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // 初始化所有列信息,完善需要显示的列类型,包含长度等,如varchar(20)
 | 
				
			||||||
 | 
					    static initColumns(columns: any[]) {
 | 
				
			||||||
 | 
					        if (!columns) {
 | 
				
			||||||
 | 
					            return;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        for (let col of columns) {
 | 
				
			||||||
 | 
					            if (col.charMaxLength > 0) {
 | 
				
			||||||
 | 
					                col.showDataType = `${col.dataType}(${col.charMaxLength})`;
 | 
				
			||||||
 | 
					                col.showLength = col.charMaxLength;
 | 
				
			||||||
 | 
					                col.showScale = null;
 | 
				
			||||||
 | 
					                continue;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            if (col.numPrecision > 0) {
 | 
				
			||||||
 | 
					                if (col.numScale > 0) {
 | 
				
			||||||
 | 
					                    col.showDataType = `${col.dataType}(${col.numPrecision},${col.numScale})`;
 | 
				
			||||||
 | 
					                    col.showScale = col.numScale;
 | 
				
			||||||
 | 
					                } else {
 | 
				
			||||||
 | 
					                    col.showDataType = `${col.dataType}(${col.numPrecision})`;
 | 
				
			||||||
 | 
					                    col.showScale = null;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                col.showLength = col.numPrecision;
 | 
				
			||||||
 | 
					                continue;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            col.showDataType = col.dataType;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
@@ -441,7 +525,7 @@ class Db {
 | 
				
			|||||||
    getColumn(table: string, columnName: string = '') {
 | 
					    getColumn(table: string, columnName: string = '') {
 | 
				
			||||||
        const cols = this.getColumns(table);
 | 
					        const cols = this.getColumns(table);
 | 
				
			||||||
        if (!columnName) {
 | 
					        if (!columnName) {
 | 
				
			||||||
            const col = cols.find((c: any) => c.columnKey == 'PRI');
 | 
					            const col = cols.find((c: any) => c.isPrimaryKey);
 | 
				
			||||||
            return col || cols[0];
 | 
					            return col || cols[0];
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        return cols.find((c: any) => c.columnName == columnName);
 | 
					        return cols.find((c: any) => c.columnName == columnName);
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -4,8 +4,10 @@ import {
 | 
				
			|||||||
    DataType,
 | 
					    DataType,
 | 
				
			||||||
    DbDialect,
 | 
					    DbDialect,
 | 
				
			||||||
    DialectInfo,
 | 
					    DialectInfo,
 | 
				
			||||||
 | 
					    DuplicateStrategy,
 | 
				
			||||||
    EditorCompletion,
 | 
					    EditorCompletion,
 | 
				
			||||||
    EditorCompletionItem,
 | 
					    EditorCompletionItem,
 | 
				
			||||||
 | 
					    QuoteEscape,
 | 
				
			||||||
    IndexDefinition,
 | 
					    IndexDefinition,
 | 
				
			||||||
    RowDefinition,
 | 
					    RowDefinition,
 | 
				
			||||||
    sqlColumnType,
 | 
					    sqlColumnType,
 | 
				
			||||||
@@ -34,7 +36,6 @@ const DM_TYPE_LIST: sqlColumnType[] = [
 | 
				
			|||||||
    // 位串数据类型 BIT 用于存储整数数据 1、0 或 NULL,只有 0 才转换为假,其他非空、非 0 值都会自动转换为真
 | 
					    // 位串数据类型 BIT 用于存储整数数据 1、0 或 NULL,只有 0 才转换为假,其他非空、非 0 值都会自动转换为真
 | 
				
			||||||
    { udtName: 'BIT', dataType: 'BIT', desc: '用于存储整数数据 1、0 或 NULL', space: '1', range: '1' },
 | 
					    { udtName: 'BIT', dataType: 'BIT', desc: '用于存储整数数据 1、0 或 NULL', space: '1', range: '1' },
 | 
				
			||||||
    // 一般日期时间数据类型 DATE TIME TIMESTAMP 默认精度 6
 | 
					    // 一般日期时间数据类型 DATE TIME TIMESTAMP 默认精度 6
 | 
				
			||||||
    // 多媒体数据类型 TEXT/LONG/LONGVARCHAR 类型:变长字符串类型  IMAGE/LONGVARBINARY 类型  BLOB CLOB BFILE  100G-1
 | 
					 | 
				
			||||||
    { udtName: 'DATE', dataType: 'DATE', desc: '年、月、日', space: '', range: '' },
 | 
					    { udtName: 'DATE', dataType: 'DATE', desc: '年、月、日', space: '', range: '' },
 | 
				
			||||||
    { udtName: 'TIME', dataType: 'TIME', desc: '时、分、秒', space: '', range: '' },
 | 
					    { udtName: 'TIME', dataType: 'TIME', desc: '时、分、秒', space: '', range: '' },
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
@@ -44,6 +45,7 @@ const DM_TYPE_LIST: sqlColumnType[] = [
 | 
				
			|||||||
        space: '',
 | 
					        space: '',
 | 
				
			||||||
        range: '-4712-01-01 00:00:00.000000000 ~ 9999-12-31 23:59:59.999999999',
 | 
					        range: '-4712-01-01 00:00:00.000000000 ~ 9999-12-31 23:59:59.999999999',
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					    // 多媒体数据类型 TEXT/LONG/LONGVARCHAR 类型:变长字符串类型  IMAGE/LONGVARBINARY 类型  BLOB CLOB BFILE  100G-1
 | 
				
			||||||
    { udtName: 'TEXT', dataType: 'TEXT', desc: '变长字符串', space: '', range: '100G-1' },
 | 
					    { udtName: 'TEXT', dataType: 'TEXT', desc: '变长字符串', space: '', range: '100G-1' },
 | 
				
			||||||
    { udtName: 'LONG', dataType: 'LONG', desc: '同TEXT', space: '', range: '100G-1' },
 | 
					    { udtName: 'LONG', dataType: 'LONG', desc: '同TEXT', space: '', range: '100G-1' },
 | 
				
			||||||
    { udtName: 'LONGVARCHAR', dataType: 'LONGVARCHAR', desc: '同TEXT', space: '', range: '100G-1' },
 | 
					    { udtName: 'LONGVARCHAR', dataType: 'LONGVARCHAR', desc: '同TEXT', space: '', range: '100G-1' },
 | 
				
			||||||
@@ -54,6 +56,7 @@ const DM_TYPE_LIST: sqlColumnType[] = [
 | 
				
			|||||||
    { udtName: 'BFILE', dataType: 'BFILE', desc: '二进制文件', space: '', range: '100G-1' },
 | 
					    { udtName: 'BFILE', dataType: 'BFILE', desc: '二进制文件', space: '', range: '100G-1' },
 | 
				
			||||||
];
 | 
					];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// 参考官方文档:https://eco.dameng.com/document/dm/zh-cn/pm/function.html
 | 
				
			||||||
const replaceFunctions: EditorCompletionItem[] = [
 | 
					const replaceFunctions: EditorCompletionItem[] = [
 | 
				
			||||||
    //  数值函数
 | 
					    //  数值函数
 | 
				
			||||||
    { label: 'ABS', insertText: 'ABS(n)', description: '求数值 n 的绝对值' },
 | 
					    { label: 'ABS', insertText: 'ABS(n)', description: '求数值 n 的绝对值' },
 | 
				
			||||||
@@ -365,21 +368,22 @@ class DMDialect implements DbDialect {
 | 
				
			|||||||
        };
 | 
					        };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        dmDialectInfo = {
 | 
					        dmDialectInfo = {
 | 
				
			||||||
 | 
					            name: 'DM',
 | 
				
			||||||
            icon: 'iconfont icon-db-dm',
 | 
					            icon: 'iconfont icon-db-dm',
 | 
				
			||||||
            defaultPort: 5236,
 | 
					            defaultPort: 5236,
 | 
				
			||||||
            formatSqlDialect: 'postgresql',
 | 
					            formatSqlDialect: 'plsql',
 | 
				
			||||||
            columnTypes: DM_TYPE_LIST.sort((a, b) => a.udtName.localeCompare(b.udtName)),
 | 
					            columnTypes: DM_TYPE_LIST.sort((a, b) => a.udtName.localeCompare(b.udtName)),
 | 
				
			||||||
            editorCompletions,
 | 
					            editorCompletions,
 | 
				
			||||||
        };
 | 
					        };
 | 
				
			||||||
        return dmDialectInfo;
 | 
					        return dmDialectInfo;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    getDefaultSelectSql(table: string, condition: string, orderBy: string, pageNum: number, limit: number) {
 | 
					    getDefaultSelectSql(db: string, table: string, condition: string, orderBy: string, pageNum: number, limit: number) {
 | 
				
			||||||
        return `SELECT * FROM "${table}" ${condition ? 'WHERE ' + condition : ''} ${orderBy ? orderBy : ''} ${this.getPageSql(pageNum, limit)};`;
 | 
					        return `SELECT * FROM "${table}" ${condition ? 'WHERE ' + condition : ''} ${orderBy ? orderBy : ''} ${this.getPageSql(pageNum, limit)};`;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    getPageSql(pageNum: number, limit: number) {
 | 
					    getPageSql(pageNum: number, limit: number) {
 | 
				
			||||||
        return ` OFFSET ${(pageNum - 1) * limit} LIMIT ${limit};`;
 | 
					        return ` OFFSET ${(pageNum - 1) * limit} LIMIT ${limit}`;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    getDefaultRows(): RowDefinition[] {
 | 
					    getDefaultRows(): RowDefinition[] {
 | 
				
			||||||
@@ -500,7 +504,9 @@ class DMDialect implements DbDialect {
 | 
				
			|||||||
        // 默认值
 | 
					        // 默认值
 | 
				
			||||||
        let defVal = this.getDefaultValueSql(cl);
 | 
					        let defVal = this.getDefaultValueSql(cl);
 | 
				
			||||||
        let incr = cl.auto_increment ? 'IDENTITY' : '';
 | 
					        let incr = cl.auto_increment ? 'IDENTITY' : '';
 | 
				
			||||||
        return ` "${cl.name}" ${cl.type}${length} ${incr} ${cl.notNull ? 'NOT NULL' : ''} ${defVal} `;
 | 
					        // 如果有原名以原名为准
 | 
				
			||||||
 | 
					        let name = cl.oldName && cl.name !== cl.oldName ? cl.oldName : cl.name;
 | 
				
			||||||
 | 
					        return ` ${this.quoteIdentifier(name)} ${cl.type}${length} ${incr} ${cl.notNull ? 'NOT NULL' : ''} ${defVal} `;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    getCreateTableSql(data: any): string {
 | 
					    getCreateTableSql(data: any): string {
 | 
				
			||||||
@@ -518,7 +524,7 @@ class DMDialect implements DbDialect {
 | 
				
			|||||||
            }
 | 
					            }
 | 
				
			||||||
            // 列注释
 | 
					            // 列注释
 | 
				
			||||||
            if (item.remark) {
 | 
					            if (item.remark) {
 | 
				
			||||||
                columCommentSql += ` comment on column "${data.tableName}"."${item.name}" is '${item.remark}'; `;
 | 
					                columCommentSql += ` comment on column "${data.tableName}"."${item.name}" is '${QuoteEscape(item.remark)}'; `;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
        // 建表
 | 
					        // 建表
 | 
				
			||||||
@@ -529,7 +535,7 @@ class DMDialect implements DbDialect {
 | 
				
			|||||||
                     );`;
 | 
					                     );`;
 | 
				
			||||||
        // 表注释
 | 
					        // 表注释
 | 
				
			||||||
        if (data.tableComment) {
 | 
					        if (data.tableComment) {
 | 
				
			||||||
            tableCommentSql = ` comment on table "${data.tableName}" is '${data.tableComment}'; `;
 | 
					            tableCommentSql = ` comment on table "${data.tableName}" is '${QuoteEscape(data.tableComment)}'; `;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return createSql + tableCommentSql + columCommentSql;
 | 
					        return createSql + tableCommentSql + columCommentSql;
 | 
				
			||||||
@@ -546,35 +552,78 @@ class DMDialect implements DbDialect {
 | 
				
			|||||||
        return sql.join(';');
 | 
					        return sql.join(';');
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    getModifyColumnSql(tableName: string, changeData: { del: RowDefinition[]; add: RowDefinition[]; upd: RowDefinition[] }): string {
 | 
					    getModifyColumnSql(tableData: any, tableName: string, changeData: { del: RowDefinition[]; add: RowDefinition[]; upd: RowDefinition[] }): string {
 | 
				
			||||||
        let sql: string[] = [];
 | 
					        let schemaArr = tableData.db.split('/');
 | 
				
			||||||
 | 
					        let schema = schemaArr.length > 1 ? schemaArr[schemaArr.length - 1] : schemaArr[0];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        let dbTable = `${this.quoteIdentifier(schema)}.${this.quoteIdentifier(tableName)}`;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        let modifySql = '';
 | 
				
			||||||
 | 
					        let dropSql = '';
 | 
				
			||||||
 | 
					        let renameSql = '';
 | 
				
			||||||
 | 
					        let commentSql = '';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // 主键字段
 | 
				
			||||||
 | 
					        let priArr = new Set();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (changeData.add.length > 0) {
 | 
					        if (changeData.add.length > 0) {
 | 
				
			||||||
            changeData.add.forEach((a) => {
 | 
					            changeData.add.forEach((a) => {
 | 
				
			||||||
                sql.push(`ALTER TABLE "${tableName}" add COLUMN ${this.genColumnBasicSql(a)}`);
 | 
					                modifySql += `ALTER TABLE ${dbTable} add COLUMN ${this.genColumnBasicSql(a)};`;
 | 
				
			||||||
                if (a.remark) {
 | 
					                if (a.remark) {
 | 
				
			||||||
                    sql.push(`comment on COLUMN "${tableName}"."${a.name}" is '${a.remark}'`);
 | 
					                    commentSql += `COMMENT ON COLUMN ${dbTable}.${this.quoteIdentifier(a.name)} IS '${QuoteEscape(a.remark)}';`;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                if (a.pri) {
 | 
				
			||||||
 | 
					                    priArr.add(`"${a.name}"`);
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            });
 | 
					            });
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (changeData.upd.length > 0) {
 | 
					        if (changeData.upd.length > 0) {
 | 
				
			||||||
            changeData.upd.forEach((a) => {
 | 
					            changeData.upd.forEach((a) => {
 | 
				
			||||||
                sql.push(`ALTER TABLE "${tableName}" MODIFY ${this.genColumnBasicSql(a)}`);
 | 
					                let cmtSql = `COMMENT ON COLUMN ${dbTable}.${this.quoteIdentifier(a.name)} IS '${QuoteEscape(a.remark)}';`;
 | 
				
			||||||
                if (a.remark) {
 | 
					                if (a.remark && a.oldName === a.name) {
 | 
				
			||||||
                    sql.push(`comment on COLUMN "${tableName}"."${a.name}" is '${a.remark}'`);
 | 
					                    commentSql += cmtSql;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                // 修改了字段名
 | 
				
			||||||
 | 
					                if (a.oldName !== a.name) {
 | 
				
			||||||
 | 
					                    renameSql += `ALTER TABLE ${dbTable} RENAME COLUMN ${this.quoteIdentifier(a.oldName!)} TO ${this.quoteIdentifier(a.name)};`;
 | 
				
			||||||
 | 
					                    if (a.remark) {
 | 
				
			||||||
 | 
					                        commentSql += cmtSql;
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                modifySql += `ALTER TABLE ${dbTable} MODIFY ${this.genColumnBasicSql(a)};`;
 | 
				
			||||||
 | 
					                if (a.pri) {
 | 
				
			||||||
 | 
					                    priArr.add(`${this.quoteIdentifier(a.name)}`);
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            });
 | 
					            });
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (changeData.del.length > 0) {
 | 
					        if (changeData.del.length > 0) {
 | 
				
			||||||
            changeData.del.forEach((a) => {
 | 
					            changeData.del.forEach((a) => {
 | 
				
			||||||
                sql.push(`ALTER TABLE "${tableName}" DROP COLUMN ${a.name}`);
 | 
					                dropSql += `ALTER TABLE ${dbTable} DROP COLUMN ${a.name};`;
 | 
				
			||||||
            });
 | 
					            });
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        return sql.join(';');
 | 
					
 | 
				
			||||||
 | 
					        // 编辑主键
 | 
				
			||||||
 | 
					        let dropPkSql = '';
 | 
				
			||||||
 | 
					        if (priArr.size > 0) {
 | 
				
			||||||
 | 
					            let resPri = tableData.fields.res.filter((a: RowDefinition) => a.pri);
 | 
				
			||||||
 | 
					            if (resPri) {
 | 
				
			||||||
 | 
					                priArr.add(`${this.quoteIdentifier(resPri.name)}`);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            // 如果有编辑主键字段,则删除主键,再添加主键
 | 
				
			||||||
 | 
					            // 解析表字段中是否含有主键,有的话就删除主键
 | 
				
			||||||
 | 
					            if (tableData.fields.oldFields.find((a: RowDefinition) => a.pri)) {
 | 
				
			||||||
 | 
					                dropPkSql = `ALTER TABLE ${dbTable} DROP PRIMARY KEY;`;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        let addPkSql = priArr.size > 0 ? `ALTER TABLE ${dbTable} ADD PRIMARY KEY (${Array.from(priArr).join(',')});` : '';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return dropPkSql + modifySql + dropSql + renameSql + addPkSql + commentSql;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    getModifyIndexSql(tableName: string, changeData: { del: any[]; add: any[]; upd: any[] }): string {
 | 
					    getModifyIndexSql(tableData: any, tableName: string, changeData: { del: any[]; add: any[]; upd: any[] }): string {
 | 
				
			||||||
        // 不能直接修改索引名或字段、需要先删后加
 | 
					        // 不能直接修改索引名或字段、需要先删后加
 | 
				
			||||||
        let dropIndexNames: string[] = [];
 | 
					        let dropIndexNames: string[] = [];
 | 
				
			||||||
        let addIndexs: any[] = [];
 | 
					        let addIndexs: any[] = [];
 | 
				
			||||||
@@ -615,6 +664,22 @@ class DMDialect implements DbDialect {
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
        return '';
 | 
					        return '';
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					    getModifyTableInfoSql(tableData: any): string {
 | 
				
			||||||
 | 
					        let schemaArr = tableData.db.split('/');
 | 
				
			||||||
 | 
					        let schema = schemaArr.length > 1 ? schemaArr[schemaArr.length - 1] : schemaArr[0];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        let sql = '';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (tableData.oldTableName !== tableData.tableName) {
 | 
				
			||||||
 | 
					            let baseTable = `${this.quoteIdentifier(schema)}.${this.quoteIdentifier(tableData.oldTableName)}`;
 | 
				
			||||||
 | 
					            sql += `ALTER TABLE ${baseTable} RENAME TO ${this.quoteIdentifier(tableData.tableName)};`;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        if (tableData.oldTableComment !== tableData.tableComment) {
 | 
				
			||||||
 | 
					            let baseTable = `${this.quoteIdentifier(schema)}.${this.quoteIdentifier(tableData.tableName)}`;
 | 
				
			||||||
 | 
					            sql += `COMMENT ON TABLE ${baseTable} IS '${QuoteEscape(tableData.tableComment)}';`;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        return sql;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    getDataType(columnType: string): DataType {
 | 
					    getDataType(columnType: string): DataType {
 | 
				
			||||||
        if (DbInst.isNumber(columnType)) {
 | 
					        if (DbInst.isNumber(columnType)) {
 | 
				
			||||||
@@ -635,8 +700,54 @@ class DMDialect implements DbDialect {
 | 
				
			|||||||
        return DataType.String;
 | 
					        return DataType.String;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // eslint-disable-next-line @typescript-eslint/no-unused-vars,no-unused-vars
 | 
					    wrapValue(columnType: string, value: any): any {
 | 
				
			||||||
    wrapStrValue(columnType: string, value: string): string {
 | 
					        if (value == null) {
 | 
				
			||||||
 | 
					            return 'NULL';
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        if (DbInst.isNumber(columnType)) {
 | 
				
			||||||
 | 
					            return value;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
        return `'${value}'`;
 | 
					        return `'${value}'`;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    getBatchInsertPreviewSql(tableName: string, fieldArr: string[], duplicateStrategy: DuplicateStrategy): string {
 | 
				
			||||||
 | 
					        // 替换
 | 
				
			||||||
 | 
					        //  MERGE INTO t_person T1
 | 
				
			||||||
 | 
					        //   USING (
 | 
				
			||||||
 | 
					        //   <foreach collection="list" item="item" index="index" separator="UNION ALL">
 | 
				
			||||||
 | 
					        //   SELECT
 | 
				
			||||||
 | 
					        //   #{item.id} id,
 | 
				
			||||||
 | 
					        //   #{item.mc} mc,
 | 
				
			||||||
 | 
					        //   #{item.sex} sex,
 | 
				
			||||||
 | 
					        //   #{item.age} age
 | 
				
			||||||
 | 
					        //   FROM dual
 | 
				
			||||||
 | 
					        //   </foreach>
 | 
				
			||||||
 | 
					        // ) T2 ON (T1.id = T2.id )
 | 
				
			||||||
 | 
					        //   WHEN NOT MATCHED THEN INSERT(id, mc, sex,
 | 
				
			||||||
 | 
					        //   age) VALUES
 | 
				
			||||||
 | 
					        //   (T2.id, T2.mc, T2.sex, T2.age)
 | 
				
			||||||
 | 
					        //   WHEN MATCHED THEN UPDATE
 | 
				
			||||||
 | 
					        //   SET T1.mc = T2.mc,T1.sex = T2.sex,T1.age = T2.age
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (duplicateStrategy == DuplicateStrategy.REPLACE || duplicateStrategy == DuplicateStrategy.IGNORE) {
 | 
				
			||||||
 | 
					            // 字段数组生成占位符sql
 | 
				
			||||||
 | 
					            let phs = [];
 | 
				
			||||||
 | 
					            let values = [];
 | 
				
			||||||
 | 
					            for (let i = 0; i < fieldArr.length; i++) {
 | 
				
			||||||
 | 
					                phs.push(`? ${fieldArr[i]}`);
 | 
				
			||||||
 | 
					                values.push(`T2.${fieldArr[i]}`);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            let placeholder = phs.join(',');
 | 
				
			||||||
 | 
					            let sql = `MERGE INTO ${tableName} T1 USING 
 | 
				
			||||||
 | 
					        (
 | 
				
			||||||
 | 
					         SELECT ${placeholder} FROM dual
 | 
				
			||||||
 | 
					        ) T2 ON (T1.id = T2.id) 
 | 
				
			||||||
 | 
					        WHEN NOT MATCHED THEN INSERT(${fieldArr.join(',')}) VALUES (${values.join(',')})
 | 
				
			||||||
 | 
					        WHEN MATCHED THEN UPDATE SET ${fieldArr.map((a) => `T1.${a} = T2.${a}`).join(',')}`;
 | 
				
			||||||
 | 
					            return sql;
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            let placeholder = '?'.repeat(fieldArr.length).split('').join(',');
 | 
				
			||||||
 | 
					            return `INSERT INTO ${tableName} (${fieldArr.join(',')}) VALUES (${placeholder}), (${placeholder});`;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user