mirror of
				https://gitee.com/dromara/mayfly-go
				synced 2025-11-04 08:20:25 +08:00 
			
		
		
		
	Compare commits
	
		
			27 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					2118acf244 | ||
| 
						 | 
					44a1bd626e | ||
| 
						 | 
					ea3c70a8a8 | ||
| 
						 | 
					6343173cf8 | ||
| 
						 | 
					6837a9c867 | ||
| 
						 | 
					a726927a28 | ||
| 
						 | 
					e135e4ce64 | ||
| 
						 | 
					43edef412c | ||
| 
						 | 
					2deb3109c2 | ||
| 
						 | 
					a80221a950 | ||
| 
						 | 
					10630847df | ||
| 
						 | 
					f43851698e | ||
| 
						 | 
					73884bb693 | ||
| 
						 | 
					1b5bb1de8b | ||
| 
						 | 
					4814793546 | ||
| 
						 | 
					d85bbff270 | ||
| 
						 | 
					bb1522f4dc | ||
| 
						 | 
					a7632fbf58 | ||
| 
						 | 
					c4cb4234fd | ||
| 
						 | 
					89e12678eb | ||
| 
						 | 
					137ebb8e9e | ||
| 
						 | 
					05625bd8c1 | ||
| 
						 | 
					4afeac5fdd | ||
| 
						 | 
					1d0e91f1af | ||
| 
						 | 
					cf5111a325 | ||
| 
						 | 
					78957a8ebd | ||
| 
						 | 
					29fd5a25d2 | 
@@ -10,7 +10,7 @@ RUN yarn config set registry 'https://registry.npmmirror.com' && \
 | 
			
		||||
    yarn build
 | 
			
		||||
 | 
			
		||||
# 构建后端资源
 | 
			
		||||
FROM golang:1.22 as be-builder
 | 
			
		||||
FROM golang:1.23 as be-builder
 | 
			
		||||
 | 
			
		||||
ENV GOPROXY https://goproxy.cn
 | 
			
		||||
WORKDIR /mayfly
 | 
			
		||||
 
 | 
			
		||||
@@ -22,7 +22,7 @@
 | 
			
		||||
 | 
			
		||||
### 介绍
 | 
			
		||||
 | 
			
		||||
web 版 **linux(终端[终端回放、命令过滤] 文件 脚本 进程 计划任务)、数据库(mysql postgres oracle sqlserver 达梦 高斯 sqlite)数据同步 数据迁移、redis(单机 哨兵 集群)、mongo 等集工单流程审批于一体的统一管理操作平台**
 | 
			
		||||
web 版 **linux(终端[终端回放、命令过滤] 文件 脚本 进程 计划任务)、数据库(mysql postgres oracle sqlserver 达梦 高斯 sqlite)数据操作 数据同步 数据迁移、redis(单机 哨兵 集群)、mongo 等集工单流程审批于一体的统一管理操作平台**
 | 
			
		||||
 | 
			
		||||
### 开发语言与主要框架
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -14,7 +14,7 @@ services:
 | 
			
		||||
    restart: always
 | 
			
		||||
 | 
			
		||||
  server:
 | 
			
		||||
    image: mayfly-go:v1.3.1
 | 
			
		||||
    image: ccr.ccs.tencentyun.com/mayfly/mayfly-go:v1.8.5
 | 
			
		||||
    build:
 | 
			
		||||
      context: .
 | 
			
		||||
      dockerfile: Dockerfile
 | 
			
		||||
 
 | 
			
		||||
@@ -6,3 +6,5 @@ VITE_OPEN = false
 | 
			
		||||
 | 
			
		||||
# public path 配置线上环境路径(打包)
 | 
			
		||||
VITE_PUBLIC_PATH = ''
 | 
			
		||||
 | 
			
		||||
VITE_EDITOR=idea
 | 
			
		||||
@@ -1,67 +1,72 @@
 | 
			
		||||
{
 | 
			
		||||
  "name": "mayfly",
 | 
			
		||||
  "version": "1.0.0",
 | 
			
		||||
  "scripts": {
 | 
			
		||||
    "dev": "vite",
 | 
			
		||||
    "build": "vite build",
 | 
			
		||||
    "preview": "vite preview",
 | 
			
		||||
    "build-preview": "npm run build && npm run preview",
 | 
			
		||||
    "lint-fix": "eslint --fix --ext .js --ext .jsx --ext .vue src/"
 | 
			
		||||
  },
 | 
			
		||||
  "dependencies": {
 | 
			
		||||
    "@element-plus/icons-vue": "^2.3.1",
 | 
			
		||||
    "@vueuse/core": "^10.9.0",
 | 
			
		||||
    "asciinema-player": "^3.7.1",
 | 
			
		||||
    "axios": "^1.6.2",
 | 
			
		||||
    "clipboard": "^2.0.11",
 | 
			
		||||
    "cropperjs": "^1.6.1",
 | 
			
		||||
    "echarts": "^5.5.0",
 | 
			
		||||
    "element-plus": "^2.7.2",
 | 
			
		||||
    "js-base64": "^3.7.7",
 | 
			
		||||
    "jsencrypt": "^3.3.2",
 | 
			
		||||
    "lodash": "^4.17.21",
 | 
			
		||||
    "mitt": "^3.0.1",
 | 
			
		||||
    "monaco-editor": "^0.48.0",
 | 
			
		||||
    "monaco-sql-languages": "^0.11.0",
 | 
			
		||||
    "monaco-themes": "^0.4.4",
 | 
			
		||||
    "nprogress": "^0.2.0",
 | 
			
		||||
    "pinia": "^2.1.7",
 | 
			
		||||
    "qrcode.vue": "^3.4.1",
 | 
			
		||||
    "screenfull": "^6.0.2",
 | 
			
		||||
    "sortablejs": "^1.15.2",
 | 
			
		||||
    "splitpanes": "^3.1.5",
 | 
			
		||||
    "sql-formatter": "^15.0.2",
 | 
			
		||||
    "trzsz": "^1.1.5",
 | 
			
		||||
    "uuid": "^9.0.1",
 | 
			
		||||
    "vue": "^3.4.25",
 | 
			
		||||
    "vue-router": "^4.3.2",
 | 
			
		||||
    "xterm": "^5.3.0",
 | 
			
		||||
    "xterm-addon-fit": "^0.8.0",
 | 
			
		||||
    "xterm-addon-search": "^0.13.0",
 | 
			
		||||
    "xterm-addon-web-links": "^0.9.0"
 | 
			
		||||
  },
 | 
			
		||||
  "devDependencies": {
 | 
			
		||||
    "@types/lodash": "^4.14.178",
 | 
			
		||||
    "@types/node": "^18.14.0",
 | 
			
		||||
    "@types/nprogress": "^0.2.0",
 | 
			
		||||
    "@types/sortablejs": "^1.15.8",
 | 
			
		||||
    "@typescript-eslint/eslint-plugin": "^6.7.4",
 | 
			
		||||
    "@typescript-eslint/parser": "^6.7.4",
 | 
			
		||||
    "@vitejs/plugin-vue": "^5.0.4",
 | 
			
		||||
    "@vue/compiler-sfc": "^3.4.25",
 | 
			
		||||
    "code-inspector-plugin": "^0.4.5",
 | 
			
		||||
    "dotenv": "^16.3.1",
 | 
			
		||||
    "eslint": "^8.35.0",
 | 
			
		||||
    "eslint-plugin-vue": "^9.25.0",
 | 
			
		||||
    "prettier": "^3.2.5",
 | 
			
		||||
    "sass": "^1.75.0",
 | 
			
		||||
    "typescript": "^5.4.5",
 | 
			
		||||
    "vite": "^5.2.10",
 | 
			
		||||
    "vue-eslint-parser": "^9.4.2"
 | 
			
		||||
  },
 | 
			
		||||
  "browserslist": [
 | 
			
		||||
    "> 1%",
 | 
			
		||||
    "last 2 versions",
 | 
			
		||||
    "not dead"
 | 
			
		||||
  ]
 | 
			
		||||
}
 | 
			
		||||
    "name": "mayfly",
 | 
			
		||||
    "version": "1.0.0",
 | 
			
		||||
    "type": "module",
 | 
			
		||||
    "scripts": {
 | 
			
		||||
      "dev": "vite",
 | 
			
		||||
      "build": "vite build",
 | 
			
		||||
      "preview": "vite preview",
 | 
			
		||||
      "build-preview": "npm run build && npm run preview",
 | 
			
		||||
      "lint-fix": "eslint --fix --ext .js --ext .jsx --ext .vue src/"
 | 
			
		||||
    },
 | 
			
		||||
    "dependencies": {
 | 
			
		||||
      "@element-plus/icons-vue": "^2.3.1",
 | 
			
		||||
      "@vueuse/core": "^11.1.0",
 | 
			
		||||
      "asciinema-player": "^3.8.1",
 | 
			
		||||
      "axios": "^1.6.2",
 | 
			
		||||
      "clipboard": "^2.0.11",
 | 
			
		||||
      "cropperjs": "^1.6.1",
 | 
			
		||||
      "crypto-js": "^4.2.0",
 | 
			
		||||
      "dayjs": "^1.11.13",
 | 
			
		||||
      "echarts": "^5.5.1",
 | 
			
		||||
      "element-plus": "^2.8.6",
 | 
			
		||||
      "js-base64": "^3.7.7",
 | 
			
		||||
      "jsencrypt": "^3.3.2",
 | 
			
		||||
      "lodash": "^4.17.21",
 | 
			
		||||
      "mitt": "^3.0.1",
 | 
			
		||||
      "monaco-editor": "^0.52.0",
 | 
			
		||||
      "monaco-sql-languages": "^0.12.2",
 | 
			
		||||
      "monaco-themes": "^0.4.4",
 | 
			
		||||
      "nprogress": "^0.2.0",
 | 
			
		||||
      "pinia": "^2.2.4",
 | 
			
		||||
      "qrcode.vue": "^3.5.0",
 | 
			
		||||
      "screenfull": "^6.0.2",
 | 
			
		||||
      "sortablejs": "^1.15.3",
 | 
			
		||||
      "splitpanes": "^3.1.5",
 | 
			
		||||
      "sql-formatter": "^15.4.5",
 | 
			
		||||
      "trzsz": "^1.1.5",
 | 
			
		||||
      "uuid": "^9.0.1",
 | 
			
		||||
      "vue": "^3.5.12",
 | 
			
		||||
      "vue-router": "^4.4.5",
 | 
			
		||||
      "xterm": "^5.3.0",
 | 
			
		||||
      "xterm-addon-fit": "^0.8.0",
 | 
			
		||||
      "xterm-addon-search": "^0.13.0",
 | 
			
		||||
      "xterm-addon-web-links": "^0.9.0"
 | 
			
		||||
    },
 | 
			
		||||
    "devDependencies": {
 | 
			
		||||
      "@types/crypto-js": "^4.2.2",
 | 
			
		||||
      "@types/lodash": "^4.14.178",
 | 
			
		||||
      "@types/node": "^18.14.0",
 | 
			
		||||
      "@types/nprogress": "^0.2.0",
 | 
			
		||||
      "@types/sortablejs": "^1.15.8",
 | 
			
		||||
      "@typescript-eslint/eslint-plugin": "^6.7.4",
 | 
			
		||||
      "@typescript-eslint/parser": "^6.7.4",
 | 
			
		||||
      "@vitejs/plugin-vue": "^5.1.4",
 | 
			
		||||
      "@vue/compiler-sfc": "^3.5.12",
 | 
			
		||||
      "code-inspector-plugin": "^0.4.5",
 | 
			
		||||
      "dotenv": "^16.3.1",
 | 
			
		||||
      "eslint": "^8.35.0",
 | 
			
		||||
      "eslint-plugin-vue": "^9.28.0",
 | 
			
		||||
      "prettier": "^3.2.5",
 | 
			
		||||
      "sass": "^1.80.3",
 | 
			
		||||
      "typescript": "^5.6.3",
 | 
			
		||||
      "vite": "^5.4.10",
 | 
			
		||||
      "vue-eslint-parser": "^9.4.3"
 | 
			
		||||
    },
 | 
			
		||||
    "browserslist": [
 | 
			
		||||
      "> 1%",
 | 
			
		||||
      "last 2 versions",
 | 
			
		||||
      "not dead"
 | 
			
		||||
    ]
 | 
			
		||||
  }
 | 
			
		||||
  
 | 
			
		||||
@@ -69,7 +69,7 @@ class Api {
 | 
			
		||||
     */
 | 
			
		||||
    async xhrReq(param: any = null, options: any = {}): Promise<any> {
 | 
			
		||||
        if (this.beforeHandler) {
 | 
			
		||||
            this.beforeHandler(param);
 | 
			
		||||
            await this.beforeHandler(param);
 | 
			
		||||
        }
 | 
			
		||||
        return request.xhrReq(this.method, this.url, param, options);
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -15,7 +15,7 @@ const config = {
 | 
			
		||||
    baseWsUrl: `${(window as any).globalConfig.BaseWsUrl || `${location.protocol == 'https:' ? 'wss:' : 'ws:'}//${getBaseApiUrl()}`}/api`,
 | 
			
		||||
 | 
			
		||||
    // 系统版本
 | 
			
		||||
    version: 'v1.8.2',
 | 
			
		||||
    version: 'v1.9.0',
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export default config;
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										38
									
								
								mayfly_go_web/src/common/crypto.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								mayfly_go_web/src/common/crypto.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,38 @@
 | 
			
		||||
import CryptoJS from 'crypto-js';
 | 
			
		||||
import { getToken } from '@/common/utils/storage';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * AES 加密数据
 | 
			
		||||
 * @param word
 | 
			
		||||
 * @param key
 | 
			
		||||
 */
 | 
			
		||||
export function AesEncrypt(word: string, key?: string) {
 | 
			
		||||
    if (!key) {
 | 
			
		||||
        key = getToken().substring(0, 24);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const sKey = CryptoJS.enc.Utf8.parse(key);
 | 
			
		||||
    const encrypted = CryptoJS.AES.encrypt(word, sKey, {
 | 
			
		||||
        iv: sKey,
 | 
			
		||||
        mode: CryptoJS.mode.CBC,
 | 
			
		||||
        padding: CryptoJS.pad.Pkcs7,
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    return encrypted.ciphertext.toString(CryptoJS.enc.Base64);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function AesDecrypt(word: string, key?: string): string {
 | 
			
		||||
    if (!key) {
 | 
			
		||||
        key = getToken().substring(0, 24);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const sKey = CryptoJS.enc.Utf8.parse(key);
 | 
			
		||||
    // key 和 iv 使用同一个值
 | 
			
		||||
    const decrypted = CryptoJS.AES.decrypt(word, sKey, {
 | 
			
		||||
        iv: sKey,
 | 
			
		||||
        mode: CryptoJS.mode.CBC, // CBC算法
 | 
			
		||||
        padding: CryptoJS.pad.Pkcs7, //使用pkcs7 进行padding 后端需要注意
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    return decrypted.toString(CryptoJS.enc.Base64);
 | 
			
		||||
}
 | 
			
		||||
@@ -2,6 +2,7 @@ import request from './request';
 | 
			
		||||
 | 
			
		||||
export default {
 | 
			
		||||
    login: (param: any) => request.post('/auth/accounts/login', param),
 | 
			
		||||
    refreshToken: (param: any) => request.get('/auth/accounts/refreshToken', param),
 | 
			
		||||
    otpVerify: (param: any) => request.post('/auth/accounts/otp-verify', param),
 | 
			
		||||
    getPublicKey: () => request.get('/common/public-key'),
 | 
			
		||||
    getConfigValue: (params: any) => request.get('/sys/configs/value', params),
 | 
			
		||||
@@ -13,4 +14,5 @@ export default {
 | 
			
		||||
    oauth2Callback: (params: any) => request.get('/auth/oauth2/callback', params),
 | 
			
		||||
    getLdapEnabled: () => request.get('/auth/ldap/enabled'),
 | 
			
		||||
    ldapLogin: (param: any) => request.post('/auth/ldap/login', param),
 | 
			
		||||
    getFileDetail: (keys: string[]) => request.get(`/sys/files/detail/${keys.join(',')}`),
 | 
			
		||||
};
 | 
			
		||||
 
 | 
			
		||||
@@ -38,6 +38,7 @@ export enum ResultEnum {
 | 
			
		||||
    PARAM_ERROR = 405,
 | 
			
		||||
    SERVER_ERROR = 500,
 | 
			
		||||
    NO_PERMISSION = 501,
 | 
			
		||||
    ACCESS_TOKEN_INVALID = 502, // accessToken失效
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export const baseUrl: string = config.baseApiUrl;
 | 
			
		||||
@@ -208,6 +209,36 @@ export function joinClientParams(): string {
 | 
			
		||||
    return `token=${getToken()}&clientId=${getClientId()}`;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 获取文件url地址
 | 
			
		||||
 * @param key 文件key
 | 
			
		||||
 * @returns 文件url
 | 
			
		||||
 */
 | 
			
		||||
export function getFileUrl(key: string) {
 | 
			
		||||
    return `${baseUrl}/sys/files/${key}`;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 获取系统文件上传url
 | 
			
		||||
 * @param key 文件key
 | 
			
		||||
 * @returns 文件上传url
 | 
			
		||||
 */
 | 
			
		||||
export function getUploadFileUrl(key: string = '') {
 | 
			
		||||
    return `${baseUrl}/sys/files/upload?token=${getToken()}&fileKey=${key}`;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 下载文件
 | 
			
		||||
 * @param key 文件key
 | 
			
		||||
 */
 | 
			
		||||
export function downloadFile(key: string) {
 | 
			
		||||
    const a = document.createElement('a');
 | 
			
		||||
    a.setAttribute('href', `${getFileUrl(key)}`);
 | 
			
		||||
    a.setAttribute('target', '_blank');
 | 
			
		||||
    a.click();
 | 
			
		||||
    a.remove();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function parseResult(result: Result) {
 | 
			
		||||
    if (result.code === ResultEnum.SUCCESS) {
 | 
			
		||||
        return result.data;
 | 
			
		||||
 
 | 
			
		||||
@@ -1,9 +1,9 @@
 | 
			
		||||
import Config from './config';
 | 
			
		||||
import { ElNotification } from 'element-plus';
 | 
			
		||||
import {ElNotification} from 'element-plus';
 | 
			
		||||
import SocketBuilder from './SocketBuilder';
 | 
			
		||||
import { getToken } from '@/common/utils/storage';
 | 
			
		||||
import {getToken} from '@/common/utils/storage';
 | 
			
		||||
 | 
			
		||||
import { joinClientParams } from './request';
 | 
			
		||||
import {joinClientParams} from './request';
 | 
			
		||||
 | 
			
		||||
class SysSocket {
 | 
			
		||||
    /**
 | 
			
		||||
@@ -19,10 +19,11 @@ class SysSocket {
 | 
			
		||||
    /**
 | 
			
		||||
     * 消息类型
 | 
			
		||||
     */
 | 
			
		||||
    messageTypes = {
 | 
			
		||||
    messageTypes: any = {
 | 
			
		||||
        0: 'error',
 | 
			
		||||
        1: 'success',
 | 
			
		||||
        2: 'info',
 | 
			
		||||
        22: 'info',
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
@@ -57,12 +58,20 @@ class SysSocket {
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                const type = this.getMsgType(message.type);
 | 
			
		||||
                let msg = message.msg
 | 
			
		||||
                let duration = 0
 | 
			
		||||
                if (message.type == 22) {
 | 
			
		||||
                    let obj = JSON.parse(msg);
 | 
			
		||||
                    msg = `文件:${obj['title']} 执行成功: ${obj['executedStatements']} 条`
 | 
			
		||||
                    duration = 2000
 | 
			
		||||
                }
 | 
			
		||||
                ElNotification({
 | 
			
		||||
                    duration: 0,
 | 
			
		||||
                    duration: duration,
 | 
			
		||||
                    title: message.title,
 | 
			
		||||
                    message: message.msg,
 | 
			
		||||
                    message: msg,
 | 
			
		||||
                    type: type,
 | 
			
		||||
                });
 | 
			
		||||
                console.log(message)
 | 
			
		||||
            })
 | 
			
		||||
            .open((event: any) => console.log(event))
 | 
			
		||||
            .close(() => {
 | 
			
		||||
 
 | 
			
		||||
@@ -1,31 +0,0 @@
 | 
			
		||||
export function dateFormat2(fmt: string, date: Date) {
 | 
			
		||||
    let ret;
 | 
			
		||||
    const opt = {
 | 
			
		||||
        'y+': date.getFullYear().toString(), // 年
 | 
			
		||||
        'M+': (date.getMonth() + 1).toString(), // 月
 | 
			
		||||
        'd+': date.getDate().toString(), // 日
 | 
			
		||||
        'H+': date.getHours().toString(), // 时
 | 
			
		||||
        'm+': date.getMinutes().toString(), // 分
 | 
			
		||||
        's+': date.getSeconds().toString(), // 秒
 | 
			
		||||
        'S+': date.getMilliseconds() ? date.getMilliseconds().toString() : '', // 毫秒
 | 
			
		||||
        // 有其他格式化字符需求可以继续添加,必须转化成字符串
 | 
			
		||||
    };
 | 
			
		||||
    for (const k in opt) {
 | 
			
		||||
        ret = new RegExp('(' + k + ')').exec(fmt);
 | 
			
		||||
        if (ret) {
 | 
			
		||||
            fmt = fmt.replace(ret[1], ret[1].length == 1 ? opt[k] : opt[k].padStart(ret[1].length, '0'));
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    return fmt;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function dateStrFormat(fmt: string, dateStr: string) {
 | 
			
		||||
    return dateFormat2(fmt, new Date(dateStr));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function dateFormat(dateStr: string) {
 | 
			
		||||
    if (!dateStr) {
 | 
			
		||||
        return '';
 | 
			
		||||
    }
 | 
			
		||||
    return dateFormat2('yyyy-MM-dd HH:mm:ss', new Date(dateStr));
 | 
			
		||||
}
 | 
			
		||||
@@ -1,3 +1,18 @@
 | 
			
		||||
import dayjs from 'dayjs';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 格式化日期
 | 
			
		||||
 * @param date 日期 字符串 Date 时间戳等
 | 
			
		||||
 * @param format 格式化格式  默认 YYYY-MM-DD HH:mm:ss
 | 
			
		||||
 * @returns 格式化后内容
 | 
			
		||||
 */
 | 
			
		||||
export function formatDate(date: any, format: string = 'YYYY-MM-DD HH:mm:ss') {
 | 
			
		||||
    if (!date) {
 | 
			
		||||
        return '';
 | 
			
		||||
    }
 | 
			
		||||
    return dayjs(date).format(format);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 格式化字节单位
 | 
			
		||||
 * @param size byte size
 | 
			
		||||
@@ -46,110 +61,6 @@ export function convertToBytes(sizeStr: string) {
 | 
			
		||||
    return bytes;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
 * 年(Y) 可用1-4个占位符
 | 
			
		||||
 * 月(m)、日(d)、小时(H)、分(M)、秒(S) 可用1-2个占位符
 | 
			
		||||
 * 星期(W) 可用1-3个占位符
 | 
			
		||||
 * 季度(q为阿拉伯数字,Q为中文数字)可用1或4个占位符
 | 
			
		||||
 *
 | 
			
		||||
 * let date = new Date()
 | 
			
		||||
 * formatDate(date, "YYYY-mm-dd HH:MM:SS")           // 2020-02-09 14:04:23
 | 
			
		||||
 * formatDate(date, "YYYY-mm-dd HH:MM:SS Q")         // 2020-02-09 14:09:03 一
 | 
			
		||||
 * formatDate(date, "YYYY-mm-dd HH:MM:SS WWW")       // 2020-02-09 14:45:12 星期日
 | 
			
		||||
 * formatDate(date, "YYYY-mm-dd HH:MM:SS QQQQ")      // 2020-02-09 14:09:36 第一季度
 | 
			
		||||
 * formatDate(date, "YYYY-mm-dd HH:MM:SS WWW QQQQ")  // 2020-02-09 14:46:12 星期日 第一季度
 | 
			
		||||
 */
 | 
			
		||||
export function formatDate(date: Date, format: string) {
 | 
			
		||||
    let we = date.getDay(); // 星期
 | 
			
		||||
    let qut = Math.floor((date.getMonth() + 3) / 3).toString(); // 季度
 | 
			
		||||
    const opt: any = {
 | 
			
		||||
        'Y+': date.getFullYear().toString(), // 年
 | 
			
		||||
        'm+': (date.getMonth() + 1).toString(), // 月(月份从0开始,要+1)
 | 
			
		||||
        'd+': date.getDate().toString(), // 日
 | 
			
		||||
        'H+': date.getHours().toString(), // 时
 | 
			
		||||
        'M+': date.getMinutes().toString(), // 分
 | 
			
		||||
        'S+': date.getSeconds().toString(), // 秒
 | 
			
		||||
        'q+': qut, // 季度
 | 
			
		||||
    };
 | 
			
		||||
    // 中文数字 (星期)
 | 
			
		||||
    const week: any = {
 | 
			
		||||
        '0': '日',
 | 
			
		||||
        '1': '一',
 | 
			
		||||
        '2': '二',
 | 
			
		||||
        '3': '三',
 | 
			
		||||
        '4': '四',
 | 
			
		||||
        '5': '五',
 | 
			
		||||
        '6': '六',
 | 
			
		||||
    };
 | 
			
		||||
    // 中文数字(季度)
 | 
			
		||||
    const quarter: any = {
 | 
			
		||||
        '1': '一',
 | 
			
		||||
        '2': '二',
 | 
			
		||||
        '3': '三',
 | 
			
		||||
        '4': '四',
 | 
			
		||||
    };
 | 
			
		||||
    if (/(W+)/.test(format)) format = format.replace(RegExp.$1, RegExp.$1.length > 1 ? (RegExp.$1.length > 2 ? '星期' + week[we] : '周' + week[we]) : week[we]);
 | 
			
		||||
    if (/(Q+)/.test(format)) format = format.replace(RegExp.$1, RegExp.$1.length == 4 ? '第' + quarter[qut] + '季度' : quarter[qut]);
 | 
			
		||||
    for (let k in opt) {
 | 
			
		||||
        let r = new RegExp('(' + k + ')').exec(format);
 | 
			
		||||
        // 若输入的长度不为1,则前面补零
 | 
			
		||||
        if (r) format = format.replace(r[1], RegExp.$1.length == 1 ? opt[k] : opt[k].padStart(RegExp.$1.length, '0'));
 | 
			
		||||
    }
 | 
			
		||||
    return format;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 10秒:  10 * 1000
 | 
			
		||||
 * 1分:   60 * 1000
 | 
			
		||||
 * 1小时: 60 * 60 * 1000
 | 
			
		||||
 * 24小时:60 * 60 * 24 * 1000
 | 
			
		||||
 * 3天:   60 * 60* 24 * 1000 * 3
 | 
			
		||||
 *
 | 
			
		||||
 * let data = new Date()
 | 
			
		||||
 * formatPast(data)                                           // 刚刚
 | 
			
		||||
 * formatPast(data - 11 * 1000)                               // 11秒前
 | 
			
		||||
 * formatPast(data - 2 * 60 * 1000)                           // 2分钟前
 | 
			
		||||
 * formatPast(data - 60 * 60 * 2 * 1000)                      // 2小时前
 | 
			
		||||
 * formatPast(data - 60 * 60 * 2 * 1000)                      // 2小时前
 | 
			
		||||
 * formatPast(data - 60 * 60 * 71 * 1000)                     // 2天前
 | 
			
		||||
 * formatPast("2020-06-01")                                   // 2020-06-01
 | 
			
		||||
 * formatPast("2020-06-01", "YYYY-mm-dd HH:MM:SS WWW QQQQ")   // 2020-06-01 08:00:00 星期一 第二季度
 | 
			
		||||
 */
 | 
			
		||||
export function formatPast(param: any, format: string = 'YYYY-mm-dd') {
 | 
			
		||||
    // 传入格式处理、存储转换值
 | 
			
		||||
    let t: any, s: any;
 | 
			
		||||
    // 获取js 时间戳
 | 
			
		||||
    let time: any = new Date().getTime();
 | 
			
		||||
    // 是否是对象
 | 
			
		||||
    typeof param === 'string' || 'object' ? (t = new Date(param).getTime()) : (t = param);
 | 
			
		||||
    // 当前时间戳 - 传入时间戳
 | 
			
		||||
    time = Number.parseInt(`${time - t}`);
 | 
			
		||||
    if (time < 10000) {
 | 
			
		||||
        // 10秒内
 | 
			
		||||
        return '刚刚';
 | 
			
		||||
    } else if (time < 60000 && time >= 10000) {
 | 
			
		||||
        // 超过10秒少于1分钟内
 | 
			
		||||
        s = Math.floor(time / 1000);
 | 
			
		||||
        return `${s}秒前`;
 | 
			
		||||
    } else if (time < 3600000 && time >= 60000) {
 | 
			
		||||
        // 超过1分钟少于1小时
 | 
			
		||||
        s = Math.floor(time / 60000);
 | 
			
		||||
        return `${s}分钟前`;
 | 
			
		||||
    } else if (time < 86400000 && time >= 3600000) {
 | 
			
		||||
        // 超过1小时少于24小时
 | 
			
		||||
        s = Math.floor(time / 3600000);
 | 
			
		||||
        return `${s}小时前`;
 | 
			
		||||
    } else if (time < 259200000 && time >= 86400000) {
 | 
			
		||||
        // 超过1天少于3天内
 | 
			
		||||
        s = Math.floor(time / 86400000);
 | 
			
		||||
        return `${s}天前`;
 | 
			
		||||
    } else {
 | 
			
		||||
        // 超过3天
 | 
			
		||||
        let date = typeof param === 'string' || 'object' ? new Date(param) : param;
 | 
			
		||||
        return formatDate(date, format);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 格式化指定时间数为人性化可阅读的内容(默认time为秒单位)
 | 
			
		||||
 *
 | 
			
		||||
 
 | 
			
		||||
@@ -9,7 +9,19 @@ export function getValueByPath(obj: any, path: string) {
 | 
			
		||||
    const keys = path.split('.');
 | 
			
		||||
    let result = obj;
 | 
			
		||||
    for (let key of keys) {
 | 
			
		||||
        if (!result || typeof result !== 'object') {
 | 
			
		||||
        if (!result) {
 | 
			
		||||
            return undefined;
 | 
			
		||||
        }
 | 
			
		||||
        // 如果是字符串,则尝试使用json解析
 | 
			
		||||
        if (typeof result == 'string') {
 | 
			
		||||
            try {
 | 
			
		||||
                result = JSON.parse(result);
 | 
			
		||||
            } catch (e) {
 | 
			
		||||
                console.error(e);
 | 
			
		||||
                return undefined;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        if (typeof result !== 'object') {
 | 
			
		||||
            return undefined;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
@@ -23,7 +35,18 @@ export function getValueByPath(obj: any, path: string) {
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            const index = parseInt(matchIndex[1]);
 | 
			
		||||
            result = Array.isArray(result[arrayKey]) ? result[arrayKey][index] : undefined;
 | 
			
		||||
 | 
			
		||||
            let arrValue = result[arrayKey];
 | 
			
		||||
            if (typeof arrValue == 'string') {
 | 
			
		||||
                try {
 | 
			
		||||
                    arrValue = JSON.parse(arrValue);
 | 
			
		||||
                } catch (e) {
 | 
			
		||||
                    result = undefined;
 | 
			
		||||
                    break;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            result = Array.isArray(arrValue) ? arrValue[index] : undefined;
 | 
			
		||||
        } else {
 | 
			
		||||
            result = result[key];
 | 
			
		||||
        }
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,7 @@
 | 
			
		||||
import { randomUuid } from './string';
 | 
			
		||||
 | 
			
		||||
const TokenKey = 'm-token';
 | 
			
		||||
const RefreshTokenKey = 'm-refresh-token';
 | 
			
		||||
const UserKey = 'm-user';
 | 
			
		||||
const TagViewsKey = 'm-tagViews';
 | 
			
		||||
const ClientIdKey = 'm-clientId';
 | 
			
		||||
@@ -15,6 +16,14 @@ export function saveToken(token: string) {
 | 
			
		||||
    setLocal(TokenKey, token);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function getRefreshToken(): string {
 | 
			
		||||
    return getLocal(RefreshTokenKey);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function saveRefreshToken(refreshToken: string) {
 | 
			
		||||
    return setLocal(RefreshTokenKey, refreshToken);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 获取登录用户基础信息
 | 
			
		||||
export function getUser() {
 | 
			
		||||
    return getLocal(UserKey);
 | 
			
		||||
@@ -39,6 +48,7 @@ export function getThemeConfig() {
 | 
			
		||||
export function clearUser() {
 | 
			
		||||
    removeLocal(TokenKey);
 | 
			
		||||
    removeLocal(UserKey);
 | 
			
		||||
    removeLocal(RefreshTokenKey);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function getTagViews() {
 | 
			
		||||
 
 | 
			
		||||
@@ -97,43 +97,6 @@ export function getTextWidth(str: string) {
 | 
			
		||||
    return width;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 获取内容所需要占用的宽度
 | 
			
		||||
 */
 | 
			
		||||
export function getContentWidth(content: any): number {
 | 
			
		||||
    if (!content) {
 | 
			
		||||
        return 50;
 | 
			
		||||
    }
 | 
			
		||||
    // 以下分配的单位长度可根据实际需求进行调整
 | 
			
		||||
    let flexWidth = 0;
 | 
			
		||||
    for (const char of content) {
 | 
			
		||||
        if (flexWidth > 500) {
 | 
			
		||||
            break;
 | 
			
		||||
        }
 | 
			
		||||
        if ((char >= '0' && char <= '9') || (char >= 'a' && char <= 'z')) {
 | 
			
		||||
            // 小写字母、数字字符
 | 
			
		||||
            flexWidth += 9.3;
 | 
			
		||||
            continue;
 | 
			
		||||
        }
 | 
			
		||||
        if (char >= 'A' && char <= 'Z') {
 | 
			
		||||
            flexWidth += 9;
 | 
			
		||||
            continue;
 | 
			
		||||
        }
 | 
			
		||||
        if (char >= '\u4e00' && char <= '\u9fa5') {
 | 
			
		||||
            // 如果是中文字符,为字符分配16个单位宽度
 | 
			
		||||
            flexWidth += 20;
 | 
			
		||||
        } else {
 | 
			
		||||
            // 其他种类字符
 | 
			
		||||
            flexWidth += 8;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    // if (flexWidth > 450) {
 | 
			
		||||
    //     // 设置最大宽度
 | 
			
		||||
    //     flexWidth = 450;
 | 
			
		||||
    // }
 | 
			
		||||
    return flexWidth;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 *
 | 
			
		||||
 * @returns uuid
 | 
			
		||||
@@ -179,3 +142,38 @@ export async function copyToClipboard(txt: string, selector: string = '#copyValu
 | 
			
		||||
        clipboard.destroy();
 | 
			
		||||
    });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function fuzzyMatchField(keyword: string, fields: any[], ...valueExtractFuncs: Function[]) {
 | 
			
		||||
    keyword = keyword?.toLowerCase();
 | 
			
		||||
    return fields.filter((field) => {
 | 
			
		||||
        for (let valueExtractFunc of valueExtractFuncs) {
 | 
			
		||||
            const value = valueExtractFunc(field)?.toLowerCase();
 | 
			
		||||
            if (isPrefixSubsequence(keyword, value)) {
 | 
			
		||||
                return true;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        return false;
 | 
			
		||||
    });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 匹配是否为前缀子序列 targetTemplate=username prefix=uname -> true,prefix=uname2 -> false
 | 
			
		||||
 * @param prefix 字符串前缀(不连续也可以,但不改变字符的相对顺序)
 | 
			
		||||
 * @param targetTemplate 目标模板
 | 
			
		||||
 * @returns 是否匹配
 | 
			
		||||
 */
 | 
			
		||||
export function isPrefixSubsequence(prefix: string, targetTemplate: string) {
 | 
			
		||||
    let i = 0; // 指向prefix的索引
 | 
			
		||||
    let j = 0; // 指向targetTemplate的索引
 | 
			
		||||
 | 
			
		||||
    while (i < prefix.length && j < targetTemplate.length) {
 | 
			
		||||
        if (prefix[i] === targetTemplate[j]) {
 | 
			
		||||
            // 字符匹配,两个指针都向前移动
 | 
			
		||||
            i++;
 | 
			
		||||
        }
 | 
			
		||||
        j++; // 目标字符串指针始终向前移动
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // 如果prefix的所有字符都被找到,返回true
 | 
			
		||||
    return i === prefix.length;
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -4,6 +4,7 @@ export interface ViteEnv {
 | 
			
		||||
    VITE_PORT: number;
 | 
			
		||||
    VITE_OPEN: boolean;
 | 
			
		||||
    VITE_PUBLIC_PATH: string;
 | 
			
		||||
    VITE_EDITOR: string;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function loadEnv(): ViteEnv {
 | 
			
		||||
 
 | 
			
		||||
@@ -83,7 +83,7 @@ const breakPoint = computed<BreakPoint>(() => gridRef.value?.breakPoint);
 | 
			
		||||
// 判断是否显示 展开/合并 按钮
 | 
			
		||||
const showCollapse = computed(() => {
 | 
			
		||||
    let show = false;
 | 
			
		||||
    props.items.reduce((prev, current) => {
 | 
			
		||||
    props.items.reduce((prev, current: any) => {
 | 
			
		||||
        prev += (current![breakPoint.value]?.span ?? current?.span ?? 1) + (current![breakPoint.value]?.offset ?? current?.offset ?? 0);
 | 
			
		||||
        if (typeof props.searchCol !== 'number') {
 | 
			
		||||
            if (prev >= props.searchCol[breakPoint.value]) show = true;
 | 
			
		||||
 
 | 
			
		||||
@@ -14,11 +14,11 @@ export function hasPerm(code: string) {
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 判断用户是否拥有权限对象里对应的code
 | 
			
		||||
 * @param perms { save: "xxx:save"}
 | 
			
		||||
 * @returns {"xxx:save": true}  key->permission code
 | 
			
		||||
 * @param permCodes
 | 
			
		||||
 */
 | 
			
		||||
export function hasPerms(permCodes: any[]) {
 | 
			
		||||
    const res = {};
 | 
			
		||||
    const res = {} as { [key: string]: boolean };
 | 
			
		||||
    for (let permCode of permCodes) {
 | 
			
		||||
        if (hasPerm(permCode)) {
 | 
			
		||||
            res[permCode] = true;
 | 
			
		||||
 
 | 
			
		||||
@@ -35,38 +35,42 @@
 | 
			
		||||
                <p class="title">时间表达式</p>
 | 
			
		||||
                <table>
 | 
			
		||||
                    <thead>
 | 
			
		||||
                        <th v-for="item of tabTitles" width="40" :key="item">{{ item }}</th>
 | 
			
		||||
                        <th>crontab完整表达式</th>
 | 
			
		||||
                        <tr>
 | 
			
		||||
                            <th v-for="item of tabTitles" width="40" :key="item">{{ item }}</th>
 | 
			
		||||
                            <th>crontab完整表达式</th>
 | 
			
		||||
                        </tr>
 | 
			
		||||
                    </thead>
 | 
			
		||||
                    <tbody>
 | 
			
		||||
                        <td>
 | 
			
		||||
                            <span>{{ crontabValueObj.second }}</span>
 | 
			
		||||
                        </td>
 | 
			
		||||
                        <td>
 | 
			
		||||
                            <span>{{ crontabValueObj.min }}</span>
 | 
			
		||||
                        </td>
 | 
			
		||||
                        <td>
 | 
			
		||||
                            <span>{{ crontabValueObj.hour }}</span>
 | 
			
		||||
                        </td>
 | 
			
		||||
                        <td>
 | 
			
		||||
                            <span>{{ crontabValueObj.day }}</span>
 | 
			
		||||
                        </td>
 | 
			
		||||
                        <td>
 | 
			
		||||
                            <span>{{ crontabValueObj.mouth }}</span>
 | 
			
		||||
                        </td>
 | 
			
		||||
                        <td>
 | 
			
		||||
                            <span>{{ crontabValueObj.week }}</span>
 | 
			
		||||
                        </td>
 | 
			
		||||
                        <td>
 | 
			
		||||
                            <span>{{ crontabValueObj.year }}</span>
 | 
			
		||||
                        </td>
 | 
			
		||||
                        <td>
 | 
			
		||||
                            <span>{{ contabValueString }}</span>
 | 
			
		||||
                        </td>
 | 
			
		||||
                        <tr>
 | 
			
		||||
                            <td>
 | 
			
		||||
                                <span>{{ crontabValueObj.second }}</span>
 | 
			
		||||
                            </td>
 | 
			
		||||
                            <td>
 | 
			
		||||
                                <span>{{ crontabValueObj.min }}</span>
 | 
			
		||||
                            </td>
 | 
			
		||||
                            <td>
 | 
			
		||||
                                <span>{{ crontabValueObj.hour }}</span>
 | 
			
		||||
                            </td>
 | 
			
		||||
                            <td>
 | 
			
		||||
                                <span>{{ crontabValueObj.day }}</span>
 | 
			
		||||
                            </td>
 | 
			
		||||
                            <td>
 | 
			
		||||
                                <span>{{ crontabValueObj.mouth }}</span>
 | 
			
		||||
                            </td>
 | 
			
		||||
                            <td>
 | 
			
		||||
                                <span>{{ crontabValueObj.week }}</span>
 | 
			
		||||
                            </td>
 | 
			
		||||
                            <td>
 | 
			
		||||
                                <span>{{ crontabValueObj.year }}</span>
 | 
			
		||||
                            </td>
 | 
			
		||||
                            <td>
 | 
			
		||||
                                <span>{{ crontabValueString }}</span>
 | 
			
		||||
                            </td>
 | 
			
		||||
                        </tr>
 | 
			
		||||
                    </tbody>
 | 
			
		||||
                </table>
 | 
			
		||||
            </div>
 | 
			
		||||
            <CrontabResult :ex="contabValueString"></CrontabResult>
 | 
			
		||||
            <CrontabResult :ex="crontabValueString"></CrontabResult>
 | 
			
		||||
 | 
			
		||||
            <div class="pop_btn">
 | 
			
		||||
                <el-button size="small" @click="hidePopup">取消</el-button>
 | 
			
		||||
@@ -202,7 +206,7 @@ function hidePopup() {
 | 
			
		||||
 | 
			
		||||
// 填充表达式
 | 
			
		||||
const submitFill = () => {
 | 
			
		||||
    emit('fill', contabValueString.value);
 | 
			
		||||
    emit('fill', crontabValueString.value);
 | 
			
		||||
    hidePopup();
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@@ -220,7 +224,7 @@ const clearCron = () => {
 | 
			
		||||
    changeTab(state.activeName);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const contabValueString = computed(() => {
 | 
			
		||||
const crontabValueString = computed(() => {
 | 
			
		||||
    let obj = state.crontabValueObj;
 | 
			
		||||
    let str = obj.second + ' ' + obj.min + ' ' + obj.hour + ' ' + obj.day + ' ' + obj.mouth + ' ' + obj.week + (obj.year == '' ? '' : ' ' + obj.year);
 | 
			
		||||
    return str;
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										17
									
								
								mayfly_go_web/src/components/enumselect/EnumSelect.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								mayfly_go_web/src/components/enumselect/EnumSelect.vue
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,17 @@
 | 
			
		||||
<template>
 | 
			
		||||
    <el-select v-bind="$attrs" v-model="modelValue">
 | 
			
		||||
        <el-option v-for="item in props.enums" :key="item.value" :label="item.label" :value="item.value"> </el-option>
 | 
			
		||||
    </el-select>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script lang="ts" setup>
 | 
			
		||||
const props = defineProps({
 | 
			
		||||
    enums: {
 | 
			
		||||
        type: Object, // 需要为EnumValue类型
 | 
			
		||||
        required: true,
 | 
			
		||||
    },
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const modelValue: any = defineModel('modelValue');
 | 
			
		||||
</script>
 | 
			
		||||
<style scoped lang="scss"></style>
 | 
			
		||||
@@ -1,5 +1,5 @@
 | 
			
		||||
<template>
 | 
			
		||||
    <el-tag v-bind="$attrs" :type="type" :color="color" effect="plain">{{ enumLabel }}</el-tag>
 | 
			
		||||
    <el-tag :disable-transitions="true" v-bind="$attrs" :type="type" :color="color" effect="plain">{{ enumLabel }}</el-tag>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script lang="ts" setup>
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										60
									
								
								mayfly_go_web/src/components/file/FileInfo.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										60
									
								
								mayfly_go_web/src/components/file/FileInfo.vue
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,60 @@
 | 
			
		||||
<template>
 | 
			
		||||
    <el-tooltip :content="formatByteSize(fileDetail?.size)" placement="left">
 | 
			
		||||
        <el-link v-if="props.canDownload" target="_blank" rel="noopener noreferrer" icon="Download" type="primary" :href="getFileUrl(props.fileKey)"></el-link>
 | 
			
		||||
    </el-tooltip>
 | 
			
		||||
 | 
			
		||||
    {{ fileDetail?.filename }}
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script lang="ts" setup>
 | 
			
		||||
import { onMounted, ref, watch } from 'vue';
 | 
			
		||||
import openApi from '@/common/openApi';
 | 
			
		||||
import { getFileUrl } from '@/common/request';
 | 
			
		||||
import { formatByteSize } from '@/common/utils/format';
 | 
			
		||||
const props = defineProps({
 | 
			
		||||
    fileKey: {
 | 
			
		||||
        type: String,
 | 
			
		||||
        required: true,
 | 
			
		||||
    },
 | 
			
		||||
    files: {
 | 
			
		||||
        type: [Array],
 | 
			
		||||
    },
 | 
			
		||||
    canDownload: {
 | 
			
		||||
        type: Boolean,
 | 
			
		||||
        default: true,
 | 
			
		||||
    },
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
onMounted(async () => {
 | 
			
		||||
    setFileInfo();
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
watch(
 | 
			
		||||
    () => props.fileKey,
 | 
			
		||||
    async (val) => {
 | 
			
		||||
        if (val) {
 | 
			
		||||
            setFileInfo();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
const fileDetail: any = ref({});
 | 
			
		||||
 | 
			
		||||
const setFileInfo = async () => {
 | 
			
		||||
    if (!props.fileKey) {
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
    if (props.files && props.files.length > 0) {
 | 
			
		||||
        const file: any = props.files.find((file: any) => {
 | 
			
		||||
            return file.fileKey === props.fileKey;
 | 
			
		||||
        });
 | 
			
		||||
        fileDetail.value = file;
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const files = await openApi.getFileDetail([props.fileKey]);
 | 
			
		||||
    fileDetail.value = files?.[0];
 | 
			
		||||
};
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style lang="scss"></style>
 | 
			
		||||
@@ -252,7 +252,9 @@ const changeLanguage = (value: any) => {
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const setEditorValue = (value: any) => {
 | 
			
		||||
    monacoEditorIns.getModel()?.setValue(value);
 | 
			
		||||
    if (value) {
 | 
			
		||||
        monacoEditorIns.getModel()?.setValue(value);
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 
 | 
			
		||||
@@ -156,8 +156,8 @@
 | 
			
		||||
            <el-row v-if="props.pageable" class="mt20" type="flex" justify="end">
 | 
			
		||||
                <el-pagination
 | 
			
		||||
                    :small="props.size == 'small'"
 | 
			
		||||
                    @current-change="handlePageNumChange"
 | 
			
		||||
                    @size-change="handlePageSizeChange"
 | 
			
		||||
                    @current-change="pageNumChange"
 | 
			
		||||
                    @size-change="pageSizeChange"
 | 
			
		||||
                    style="text-align: right"
 | 
			
		||||
                    layout="prev, pager, next, total, sizes"
 | 
			
		||||
                    :total="total"
 | 
			
		||||
@@ -185,7 +185,7 @@ import SvgIcon from '@/components/svgIcon/index.vue';
 | 
			
		||||
import { usePageTable } from '@/hooks/usePageTable';
 | 
			
		||||
import { ElTable } from 'element-plus';
 | 
			
		||||
 | 
			
		||||
const emit = defineEmits(['update:selectionData', 'pageChange']);
 | 
			
		||||
const emit = defineEmits(['update:selectionData', 'pageSizeChange', 'pageNumChange']);
 | 
			
		||||
 | 
			
		||||
export interface PageTableProps {
 | 
			
		||||
    size?: string;
 | 
			
		||||
@@ -257,6 +257,15 @@ const changeSimpleFormItem = (searchItem: SearchItem) => {
 | 
			
		||||
    nowSearchItem.value = searchItem;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const pageSizeChange = (val: number) => {
 | 
			
		||||
    emit('pageSizeChange', val);
 | 
			
		||||
    handlePageSizeChange(val);
 | 
			
		||||
};
 | 
			
		||||
const pageNumChange = (val: number) => {
 | 
			
		||||
    emit('pageNumChange', val);
 | 
			
		||||
    handlePageNumChange(val);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
let { tableData, total, loading, search, reset, getTableData, handlePageNumChange, handlePageSizeChange } = usePageTable(
 | 
			
		||||
    props.pageable,
 | 
			
		||||
    props.pageApi,
 | 
			
		||||
@@ -353,6 +362,7 @@ defineExpose({
 | 
			
		||||
    tableRef: tableRef,
 | 
			
		||||
    search: getTableData,
 | 
			
		||||
    getData,
 | 
			
		||||
    total,
 | 
			
		||||
});
 | 
			
		||||
</script>
 | 
			
		||||
<style scoped lang="scss">
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,5 @@
 | 
			
		||||
import EnumValue from '@/common/Enum';
 | 
			
		||||
import { dateFormat } from '@/common/utils/date';
 | 
			
		||||
import { formatDate } from '@/common/utils/format';
 | 
			
		||||
import { getValueByPath } from '@/common/utils/object';
 | 
			
		||||
import { getTextWidth } from '@/common/utils/string';
 | 
			
		||||
 | 
			
		||||
@@ -172,7 +172,7 @@ export class TableColumn {
 | 
			
		||||
     */
 | 
			
		||||
    isTime(): TableColumn {
 | 
			
		||||
        this.setFormatFunc((data: any, prop: string) => {
 | 
			
		||||
            return dateFormat(getValueByPath(data, prop));
 | 
			
		||||
            return formatDate(getValueByPath(data, prop));
 | 
			
		||||
        });
 | 
			
		||||
        return this;
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -24,8 +24,33 @@
 | 
			
		||||
                <SvgIcon name="Refresh" @click="connect(0, 0)" :size="20" class="pointer-icon mr10" title="重新连接" />
 | 
			
		||||
            </div>
 | 
			
		||||
            <clipboard-dialog ref="clipboardRef" v-model:visible="state.clipboardDialog.visible" @close="closePaste" @submit="onsubmitClipboard" />
 | 
			
		||||
 | 
			
		||||
            <el-dialog
 | 
			
		||||
                v-if="!state.fullscreen"
 | 
			
		||||
                destroy-on-close
 | 
			
		||||
                :title="state.filesystemDialog.title"
 | 
			
		||||
                v-model="state.filesystemDialog.visible"
 | 
			
		||||
                :close-on-click-modal="false"
 | 
			
		||||
                width="70%"
 | 
			
		||||
            >
 | 
			
		||||
                <machine-file
 | 
			
		||||
                    :machine-id="state.filesystemDialog.machineId"
 | 
			
		||||
                    :auth-cert-name="state.filesystemDialog.authCertName"
 | 
			
		||||
                    :protocol="state.filesystemDialog.protocol"
 | 
			
		||||
                    :file-id="state.filesystemDialog.fileId"
 | 
			
		||||
                    :path="state.filesystemDialog.path"
 | 
			
		||||
                />
 | 
			
		||||
            </el-dialog>
 | 
			
		||||
        </div>
 | 
			
		||||
        <el-dialog destroy-on-close :title="state.filesystemDialog.title" v-model="state.filesystemDialog.visible" :close-on-click-modal="false" width="70%">
 | 
			
		||||
 | 
			
		||||
        <el-dialog
 | 
			
		||||
            v-if="!state.fullscreen"
 | 
			
		||||
            destroy-on-close
 | 
			
		||||
            :title="state.filesystemDialog.title"
 | 
			
		||||
            v-model="state.filesystemDialog.visible"
 | 
			
		||||
            :close-on-click-modal="false"
 | 
			
		||||
            width="70%"
 | 
			
		||||
        >
 | 
			
		||||
            <machine-file
 | 
			
		||||
                :machine-id="state.filesystemDialog.machineId"
 | 
			
		||||
                :auth-cert-name="state.filesystemDialog.authCertName"
 | 
			
		||||
@@ -84,7 +109,7 @@ const state = reactive({
 | 
			
		||||
    mouse: null as any,
 | 
			
		||||
    touchpad: null as any,
 | 
			
		||||
    errorMessage: '',
 | 
			
		||||
    arguments: {},
 | 
			
		||||
    arguments: {} as any,
 | 
			
		||||
    status: TerminalStatus.NoConnected,
 | 
			
		||||
    size: {
 | 
			
		||||
        height: 710,
 | 
			
		||||
 
 | 
			
		||||
@@ -67,7 +67,7 @@ const state = reactive({
 | 
			
		||||
        search: null as any,
 | 
			
		||||
        weblinks: null as any,
 | 
			
		||||
    },
 | 
			
		||||
    status: TerminalStatus.NoConnected,
 | 
			
		||||
    status: -11,
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
onMounted(() => {
 | 
			
		||||
@@ -96,6 +96,7 @@ onBeforeUnmount(() => {
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
function init() {
 | 
			
		||||
    state.status = TerminalStatus.NoConnected;
 | 
			
		||||
    if (term) {
 | 
			
		||||
        console.log('重新连接...');
 | 
			
		||||
        close();
 | 
			
		||||
@@ -105,7 +106,7 @@ function init() {
 | 
			
		||||
    });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function initTerm() {
 | 
			
		||||
async function initTerm() {
 | 
			
		||||
    term = new Terminal({
 | 
			
		||||
        fontSize: themeConfig.value.terminalFontSize || 15,
 | 
			
		||||
        fontWeight: themeConfig.value.terminalFontWeight || 'normal',
 | 
			
		||||
@@ -155,6 +156,7 @@ function initSocket() {
 | 
			
		||||
        state.status = TerminalStatus.Connected;
 | 
			
		||||
 | 
			
		||||
        focus();
 | 
			
		||||
        fitTerminal();
 | 
			
		||||
 | 
			
		||||
        // 如果有初始要执行的命令,则发送执行命令
 | 
			
		||||
        if (props.cmd) {
 | 
			
		||||
@@ -209,7 +211,6 @@ function loadAddon() {
 | 
			
		||||
        // 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) => {
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,15 @@
 | 
			
		||||
import EnumValue from '@/common/Enum';
 | 
			
		||||
 | 
			
		||||
export enum TerminalStatus {
 | 
			
		||||
    Error = -1,
 | 
			
		||||
    NoConnected = 0,
 | 
			
		||||
    Connected = 1,
 | 
			
		||||
    Disconnected = 2,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export const TerminalStatusEnum = {
 | 
			
		||||
    Error: EnumValue.of(TerminalStatus.Error, '连接出错').setExtra({ iconColor: 'var(--el-color-error)' }),
 | 
			
		||||
    NoConnected: EnumValue.of(TerminalStatus.NoConnected, '未连接').setExtra({ iconColor: 'var(--el-color-primary)' }),
 | 
			
		||||
    Connected: EnumValue.of(TerminalStatus.Connected, '连接成功').setExtra({ iconColor: 'var(--el-color-success)' }),
 | 
			
		||||
    Disconnected: EnumValue.of(TerminalStatus.Disconnected, '连接失败').setExtra({ iconColor: 'var(--el-color-error)' }),
 | 
			
		||||
};
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,5 @@
 | 
			
		||||
import router from '@/router';
 | 
			
		||||
import { getClientId, getToken } from '@/common/utils/storage';
 | 
			
		||||
import { clearUser, getClientId, getRefreshToken, getToken, saveRefreshToken, saveToken } from '@/common/utils/storage';
 | 
			
		||||
import { templateResolve } from '@/common/utils/string';
 | 
			
		||||
import { ElMessage } from 'element-plus';
 | 
			
		||||
import { createFetch } from '@vueuse/core';
 | 
			
		||||
@@ -8,6 +8,7 @@ import { Result, ResultEnum } from '@/common/request';
 | 
			
		||||
import config from '@/common/config';
 | 
			
		||||
import { unref } from 'vue';
 | 
			
		||||
import { URL_401 } from '@/router/staticRouter';
 | 
			
		||||
import openApi from '@/common/openApi';
 | 
			
		||||
 | 
			
		||||
const baseUrl: string = config.baseApiUrl;
 | 
			
		||||
 | 
			
		||||
@@ -41,7 +42,7 @@ const useCustomFetch = createFetch({
 | 
			
		||||
 | 
			
		||||
export function useApiFetch<T>(api: Api, params: any = null, reqOptions: RequestInit = {}) {
 | 
			
		||||
    const uaf = useCustomFetch<T>(api.url, {
 | 
			
		||||
        beforeFetch({ url, options }) {
 | 
			
		||||
        async beforeFetch({ url, options }) {
 | 
			
		||||
            options.method = api.method;
 | 
			
		||||
            if (!params) {
 | 
			
		||||
                return;
 | 
			
		||||
@@ -56,7 +57,7 @@ export function useApiFetch<T>(api: Api, params: any = null, reqOptions: Request
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (api.beforeHandler) {
 | 
			
		||||
                paramsValue = api.beforeHandler(paramsValue);
 | 
			
		||||
                paramsValue = await api.beforeHandler(paramsValue);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (paramsValue) {
 | 
			
		||||
@@ -88,61 +89,104 @@ export function useApiFetch<T>(api: Api, params: any = null, reqOptions: Request
 | 
			
		||||
 | 
			
		||||
    return {
 | 
			
		||||
        execute: async function () {
 | 
			
		||||
            try {
 | 
			
		||||
                await uaf.execute(true);
 | 
			
		||||
            } catch (e: any) {
 | 
			
		||||
                const rejectPromise = Promise.reject(e);
 | 
			
		||||
 | 
			
		||||
                if (e?.name == 'AbortError') {
 | 
			
		||||
                    console.log('请求已取消');
 | 
			
		||||
                    return rejectPromise;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                const respStatus = uaf.response.value?.status;
 | 
			
		||||
                if (respStatus == 404) {
 | 
			
		||||
                    ElMessage.error('请求接口不存在');
 | 
			
		||||
                    return rejectPromise;
 | 
			
		||||
                }
 | 
			
		||||
                if (respStatus == 500) {
 | 
			
		||||
                    ElMessage.error('服务器响应异常');
 | 
			
		||||
                    return rejectPromise;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                console.error(e);
 | 
			
		||||
                ElMessage.error('网络请求错误');
 | 
			
		||||
                return rejectPromise;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            const result: Result = uaf.data.value as any;
 | 
			
		||||
            if (!result) {
 | 
			
		||||
                ElMessage.error('网络请求失败');
 | 
			
		||||
                return Promise.reject(result);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // 如果返回为成功结果,则将结果的data赋值给响应式data
 | 
			
		||||
            if (result.code === ResultEnum.SUCCESS) {
 | 
			
		||||
                uaf.data.value = result.data;
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // 如果提示没有权限,则跳转至无权限页面
 | 
			
		||||
            if (result.code === ResultEnum.NO_PERMISSION) {
 | 
			
		||||
                router.push({
 | 
			
		||||
                    path: URL_401,
 | 
			
		||||
                });
 | 
			
		||||
                return Promise.reject(result);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // 如果返回的code不为成功,则会返回对应的错误msg,则直接统一通知即可。忽略登录超时或没有权限的提示(直接跳转至401页面)
 | 
			
		||||
            if (result.msg && result?.code != ResultEnum.NO_PERMISSION) {
 | 
			
		||||
                ElMessage.error(result.msg);
 | 
			
		||||
                uaf.error.value = new Error(result.msg);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return Promise.reject(result);
 | 
			
		||||
            return execUaf(uaf);
 | 
			
		||||
        },
 | 
			
		||||
        isFetching: uaf.isFetching,
 | 
			
		||||
        data: uaf.data,
 | 
			
		||||
        abort: uaf.abort,
 | 
			
		||||
    };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
let refreshingToken = false;
 | 
			
		||||
let queue: any[] = [];
 | 
			
		||||
 | 
			
		||||
async function execUaf(uaf: any) {
 | 
			
		||||
    try {
 | 
			
		||||
        await uaf.execute(true);
 | 
			
		||||
    } catch (e: any) {
 | 
			
		||||
        const rejectPromise = Promise.reject(e);
 | 
			
		||||
 | 
			
		||||
        if (e?.name == 'AbortError') {
 | 
			
		||||
            console.log('请求已取消');
 | 
			
		||||
            return rejectPromise;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        const respStatus = uaf.response.value?.status;
 | 
			
		||||
        if (respStatus == 404) {
 | 
			
		||||
            ElMessage.error('请求接口不存在');
 | 
			
		||||
            return rejectPromise;
 | 
			
		||||
        }
 | 
			
		||||
        if (respStatus == 500) {
 | 
			
		||||
            ElMessage.error('服务器响应异常');
 | 
			
		||||
            return rejectPromise;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        console.error(e);
 | 
			
		||||
        ElMessage.error('网络请求错误');
 | 
			
		||||
        return rejectPromise;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const result: Result = uaf.data.value as any;
 | 
			
		||||
    if (!result) {
 | 
			
		||||
        ElMessage.error('网络请求失败');
 | 
			
		||||
        return Promise.reject(result);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const resultCode = result.code;
 | 
			
		||||
 | 
			
		||||
    // 如果返回为成功结果,则将结果的data赋值给响应式data
 | 
			
		||||
    if (resultCode === ResultEnum.SUCCESS) {
 | 
			
		||||
        uaf.data.value = result.data;
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // 如果是accessToken失效,则使用refreshToken刷新token
 | 
			
		||||
    if (resultCode == ResultEnum.ACCESS_TOKEN_INVALID) {
 | 
			
		||||
        if (refreshingToken) {
 | 
			
		||||
            // 请求加入队列等待, 防止并发多次请求refreshToken
 | 
			
		||||
            return new Promise((resolve) => {
 | 
			
		||||
                queue.push(() => {
 | 
			
		||||
                    resolve(execUaf(uaf));
 | 
			
		||||
                });
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        try {
 | 
			
		||||
            refreshingToken = true;
 | 
			
		||||
            const res = await openApi.refreshToken({ refresh_token: getRefreshToken() });
 | 
			
		||||
            saveToken(res.token);
 | 
			
		||||
            saveRefreshToken(res.refresh_token);
 | 
			
		||||
            // 重新缓存后端用户权限code
 | 
			
		||||
            await openApi.getPermissions();
 | 
			
		||||
 | 
			
		||||
            // 执行accessToken失效的请求
 | 
			
		||||
            queue.forEach((resolve: any) => {
 | 
			
		||||
                resolve();
 | 
			
		||||
            });
 | 
			
		||||
        } catch (e: any) {
 | 
			
		||||
            clearUser();
 | 
			
		||||
        } finally {
 | 
			
		||||
            refreshingToken = false;
 | 
			
		||||
            queue = [];
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        await execUaf(uaf);
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // 如果提示没有权限,则跳转至无权限页面
 | 
			
		||||
    if (resultCode === ResultEnum.NO_PERMISSION) {
 | 
			
		||||
        router.push({
 | 
			
		||||
            path: URL_401,
 | 
			
		||||
        });
 | 
			
		||||
        return Promise.reject(result);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // 如果返回的code不为成功,则会返回对应的错误msg,则直接统一通知即可。忽略登录超时或没有权限的提示(直接跳转至401页面)
 | 
			
		||||
    if (result.msg && resultCode != ResultEnum.NO_PERMISSION) {
 | 
			
		||||
        ElMessage.error(result.msg);
 | 
			
		||||
        uaf.error.value = new Error(result.msg);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return Promise.reject(result);
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,5 @@
 | 
			
		||||
import { defineStore } from 'pinia';
 | 
			
		||||
import { dateFormat2 } from '@/common/utils/date';
 | 
			
		||||
import { formatDate } from '@/common/utils/format';
 | 
			
		||||
import { useUserInfo } from '@/store/userInfo';
 | 
			
		||||
import { getSysStyleConfig } from '@/common/sysconfig';
 | 
			
		||||
import { getLocal, getThemeConfig } from '@/common/utils/storage';
 | 
			
		||||
@@ -191,7 +191,7 @@ export const useThemeConfig = defineStore('themeConfig', {
 | 
			
		||||
        },
 | 
			
		||||
        // 设置水印时间为当前时间
 | 
			
		||||
        setWatermarkNowTime() {
 | 
			
		||||
            this.themeConfig.watermarkText[1] = dateFormat2('yyyy-MM-dd HH:mm:ss', new Date());
 | 
			
		||||
            this.themeConfig.watermarkText[1] = formatDate(new Date());
 | 
			
		||||
        },
 | 
			
		||||
        // 切换暗黑模式
 | 
			
		||||
        switchDark(isDark: boolean) {
 | 
			
		||||
 
 | 
			
		||||
@@ -1 +1 @@
 | 
			
		||||
@import 'common/transition.scss';
 | 
			
		||||
@use 'common/transition.scss';
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
@import 'mixins/index.scss';
 | 
			
		||||
@use 'mixins/index' as mixins;
 | 
			
		||||
 | 
			
		||||
/* Button 按钮
 | 
			
		||||
------------------------------- */
 | 
			
		||||
@@ -97,7 +97,7 @@
 | 
			
		||||
.el-sub-menu .iconfont,
 | 
			
		||||
.el-menu-item .fa,
 | 
			
		||||
.el-sub-menu .fa {
 | 
			
		||||
    @include generalIcon;
 | 
			
		||||
    @include mixins.generalIcon;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 水平菜单、横向菜单高亮 背景色,鼠标 hover 时,有子级菜单的背景色
 | 
			
		||||
 
 | 
			
		||||
@@ -1,8 +1,8 @@
 | 
			
		||||
@import './app.scss';
 | 
			
		||||
@import './base.scss';
 | 
			
		||||
@import './other.scss';
 | 
			
		||||
@import './element.scss';
 | 
			
		||||
@import './media/media.scss';
 | 
			
		||||
@import './waves.scss';
 | 
			
		||||
@import './dark.scss';
 | 
			
		||||
@import './iconSelector.scss';
 | 
			
		||||
@use './app.scss';
 | 
			
		||||
@use './base.scss';
 | 
			
		||||
@use './other.scss';
 | 
			
		||||
@use './element.scss';
 | 
			
		||||
@use './media/media.scss';
 | 
			
		||||
@use './waves.scss';
 | 
			
		||||
@use './dark.scss';
 | 
			
		||||
@use './iconSelector.scss';
 | 
			
		||||
@@ -1,94 +1,109 @@
 | 
			
		||||
@import './index.scss';
 | 
			
		||||
@use './index.scss' as index;
 | 
			
		||||
 | 
			
		||||
/* 页面宽度小于768px
 | 
			
		||||
------------------------------- */
 | 
			
		||||
@media screen and (max-width: $sm) {
 | 
			
		||||
	.big-data-down-left {
 | 
			
		||||
		width: 100% !important;
 | 
			
		||||
		flex-direction: unset !important;
 | 
			
		||||
		flex-wrap: wrap;
 | 
			
		||||
		.flex-warp-item {
 | 
			
		||||
			min-height: 196.24px;
 | 
			
		||||
			padding: 0 7.5px 15px 15px !important;
 | 
			
		||||
			.flex-warp-item-box {
 | 
			
		||||
				border: none !important;
 | 
			
		||||
				border-bottom: 1px solid #ebeef5 !important;
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	.big-data-down-center {
 | 
			
		||||
		width: 100% !important;
 | 
			
		||||
		.big-data-down-center-one,
 | 
			
		||||
		.big-data-down-center-two {
 | 
			
		||||
			min-height: 196.24px;
 | 
			
		||||
			padding-left: 15px !important;
 | 
			
		||||
			.big-data-down-center-one-content {
 | 
			
		||||
				border: none !important;
 | 
			
		||||
				border-bottom: 1px solid #ebeef5 !important;
 | 
			
		||||
			}
 | 
			
		||||
			.flex-warp-item-box {
 | 
			
		||||
				@extend .big-data-down-center-one-content;
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	.big-data-down-right {
 | 
			
		||||
		.flex-warp-item {
 | 
			
		||||
			.flex-warp-item-box {
 | 
			
		||||
				border: none !important;
 | 
			
		||||
				border-bottom: 1px solid #ebeef5 !important;
 | 
			
		||||
			}
 | 
			
		||||
			&:nth-of-type(2) {
 | 
			
		||||
				padding-left: 15px !important;
 | 
			
		||||
			}
 | 
			
		||||
			&:last-of-type {
 | 
			
		||||
				.flex-warp-item-box {
 | 
			
		||||
					border: none !important;
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
@media screen and (max-width: index.$sm) {
 | 
			
		||||
    .big-data-down-left {
 | 
			
		||||
        width: 100% !important;
 | 
			
		||||
        flex-direction: unset !important;
 | 
			
		||||
        flex-wrap: wrap;
 | 
			
		||||
 | 
			
		||||
        .flex-warp-item {
 | 
			
		||||
            min-height: 196.24px;
 | 
			
		||||
            padding: 0 7.5px 15px 15px !important;
 | 
			
		||||
 | 
			
		||||
            .flex-warp-item-box {
 | 
			
		||||
                border: none !important;
 | 
			
		||||
                border-bottom: 1px solid #ebeef5 !important;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .big-data-down-center {
 | 
			
		||||
        width: 100% !important;
 | 
			
		||||
 | 
			
		||||
        .big-data-down-center-one,
 | 
			
		||||
        .big-data-down-center-two {
 | 
			
		||||
            min-height: 196.24px;
 | 
			
		||||
            padding-left: 15px !important;
 | 
			
		||||
 | 
			
		||||
            .big-data-down-center-one-content {
 | 
			
		||||
                border: none !important;
 | 
			
		||||
                border-bottom: 1px solid #ebeef5 !important;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            .flex-warp-item-box {
 | 
			
		||||
                @extend .big-data-down-center-one-content;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .big-data-down-right {
 | 
			
		||||
        .flex-warp-item {
 | 
			
		||||
            .flex-warp-item-box {
 | 
			
		||||
                border: none !important;
 | 
			
		||||
                border-bottom: 1px solid #ebeef5 !important;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            &:nth-of-type(2) {
 | 
			
		||||
                padding-left: 15px !important;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            &:last-of-type {
 | 
			
		||||
                .flex-warp-item-box {
 | 
			
		||||
                    border: none !important;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* 页面宽度大于768px小于1200px
 | 
			
		||||
------------------------------- */
 | 
			
		||||
@media screen and (min-width: $sm) and (max-width: $lg) {
 | 
			
		||||
	.chart-warp-bottom {
 | 
			
		||||
		.big-data-down-left {
 | 
			
		||||
			width: 50% !important;
 | 
			
		||||
		}
 | 
			
		||||
		.big-data-down-center {
 | 
			
		||||
			width: 50% !important;
 | 
			
		||||
		}
 | 
			
		||||
		.big-data-down-right {
 | 
			
		||||
			.flex-warp-item {
 | 
			
		||||
				width: 50% !important;
 | 
			
		||||
				&:nth-of-type(2) {
 | 
			
		||||
					padding-left: 7.5px !important;
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
@media screen and (min-width: index.$sm) and (max-width: index.$lg) {
 | 
			
		||||
    .chart-warp-bottom {
 | 
			
		||||
        .big-data-down-left {
 | 
			
		||||
            width: 50% !important;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        .big-data-down-center {
 | 
			
		||||
            width: 50% !important;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        .big-data-down-right {
 | 
			
		||||
            .flex-warp-item {
 | 
			
		||||
                width: 50% !important;
 | 
			
		||||
 | 
			
		||||
                &:nth-of-type(2) {
 | 
			
		||||
                    padding-left: 7.5px !important;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* 页面宽度小于1200px
 | 
			
		||||
------------------------------- */
 | 
			
		||||
@media screen and (max-width: $lg) {
 | 
			
		||||
	.chart-warp-top {
 | 
			
		||||
		.up-left {
 | 
			
		||||
			display: none;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	.chart-warp-bottom {
 | 
			
		||||
		overflow-y: auto !important;
 | 
			
		||||
		flex-wrap: wrap;
 | 
			
		||||
		.big-data-down-right {
 | 
			
		||||
			width: 100% !important;
 | 
			
		||||
			flex-direction: unset !important;
 | 
			
		||||
			flex-wrap: wrap;
 | 
			
		||||
			.flex-warp-item {
 | 
			
		||||
				min-height: 196.24px;
 | 
			
		||||
				padding: 0 7.5px 15px 15px !important;
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
@media screen and (max-width: index.$lg) {
 | 
			
		||||
    .chart-warp-top {
 | 
			
		||||
        .up-left {
 | 
			
		||||
            display: none;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .chart-warp-bottom {
 | 
			
		||||
        overflow-y: auto !important;
 | 
			
		||||
        flex-wrap: wrap;
 | 
			
		||||
 | 
			
		||||
        .big-data-down-right {
 | 
			
		||||
            width: 100% !important;
 | 
			
		||||
            flex-direction: unset !important;
 | 
			
		||||
            flex-wrap: wrap;
 | 
			
		||||
 | 
			
		||||
            .flex-warp-item {
 | 
			
		||||
                min-height: 196.24px;
 | 
			
		||||
                padding: 0 7.5px 15px 15px !important;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,10 +1,10 @@
 | 
			
		||||
@import './index.scss';
 | 
			
		||||
@use './index.scss' as index;
 | 
			
		||||
 | 
			
		||||
/* 页面宽度小于576px
 | 
			
		||||
------------------------------- */
 | 
			
		||||
@media screen and (max-width: $xs) {
 | 
			
		||||
	.el-cascader__dropdown.el-popper {
 | 
			
		||||
		overflow: auto;
 | 
			
		||||
		max-width: 100%;
 | 
			
		||||
	}
 | 
			
		||||
@media screen and (max-width: index.$xs) {
 | 
			
		||||
    .el-cascader__dropdown.el-popper {
 | 
			
		||||
        overflow: auto;
 | 
			
		||||
        max-width: 100%;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,12 +1,13 @@
 | 
			
		||||
@import './index.scss';
 | 
			
		||||
@use './index.scss';
 | 
			
		||||
 | 
			
		||||
/* 页面宽度小于800px
 | 
			
		||||
------------------------------- */
 | 
			
		||||
@media screen and (max-width: 800px) {
 | 
			
		||||
	.el-dialog {
 | 
			
		||||
		width: 90% !important;
 | 
			
		||||
	}
 | 
			
		||||
	.el-dialog.is-fullscreen {
 | 
			
		||||
		width: 100% !important;
 | 
			
		||||
	}
 | 
			
		||||
    .el-dialog {
 | 
			
		||||
        width: 90% !important;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .el-dialog.is-fullscreen {
 | 
			
		||||
        width: 100% !important;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,35 +1,38 @@
 | 
			
		||||
@import './index.scss';
 | 
			
		||||
@use './index.scss' as index;
 | 
			
		||||
 | 
			
		||||
/* 页面宽度小于768px
 | 
			
		||||
------------------------------- */
 | 
			
		||||
@media screen and (max-width: $sm) {
 | 
			
		||||
	.error {
 | 
			
		||||
		.error-flex {
 | 
			
		||||
			flex-direction: column-reverse !important;
 | 
			
		||||
			height: auto !important;
 | 
			
		||||
			width: 100% !important;
 | 
			
		||||
		}
 | 
			
		||||
		.right,
 | 
			
		||||
		.left {
 | 
			
		||||
			flex: unset !important;
 | 
			
		||||
			display: flex !important;
 | 
			
		||||
		}
 | 
			
		||||
		.left-item {
 | 
			
		||||
			margin: auto !important;
 | 
			
		||||
		}
 | 
			
		||||
		.right img {
 | 
			
		||||
			max-width: 450px !important;
 | 
			
		||||
			@extend .left-item;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
@media screen and (max-width: index.$sm) {
 | 
			
		||||
    .error {
 | 
			
		||||
        .error-flex {
 | 
			
		||||
            flex-direction: column-reverse !important;
 | 
			
		||||
            height: auto !important;
 | 
			
		||||
            width: 100% !important;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        .right,
 | 
			
		||||
        .left {
 | 
			
		||||
            flex: unset !important;
 | 
			
		||||
            display: flex !important;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        .left-item {
 | 
			
		||||
            margin: auto !important;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        .right img {
 | 
			
		||||
            max-width: 450px !important;
 | 
			
		||||
            @extend .left-item;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* 页面宽度大于768px小于992px
 | 
			
		||||
------------------------------- */
 | 
			
		||||
@media screen and (min-width: $sm) and (max-width: $md) {
 | 
			
		||||
	.error {
 | 
			
		||||
		.error-flex {
 | 
			
		||||
			padding-left: 30px !important;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
@media screen and (min-width: index.$sm) and (max-width: index.$md) {
 | 
			
		||||
    .error {
 | 
			
		||||
        .error-flex {
 | 
			
		||||
            padding-left: 30px !important;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,13 +1,14 @@
 | 
			
		||||
@import './index.scss';
 | 
			
		||||
@use './index.scss' as index;
 | 
			
		||||
 | 
			
		||||
/* 页面宽度小于576px
 | 
			
		||||
------------------------------- */
 | 
			
		||||
@media screen and (max-width: $xs) {
 | 
			
		||||
	.el-form-item__label {
 | 
			
		||||
		width: 100% !important;
 | 
			
		||||
		text-align: left !important;
 | 
			
		||||
	}
 | 
			
		||||
	.el-form-item__content {
 | 
			
		||||
		margin-left: 0 !important;
 | 
			
		||||
	}
 | 
			
		||||
@media screen and (max-width: index.$xs) {
 | 
			
		||||
    .el-form-item__label {
 | 
			
		||||
        width: 100% !important;
 | 
			
		||||
        text-align: left !important;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .el-form-item__content {
 | 
			
		||||
        margin-left: 0 !important;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,10 +1,11 @@
 | 
			
		||||
@import './index.scss';
 | 
			
		||||
@use './index.scss' as index;
 | 
			
		||||
 | 
			
		||||
/* 页面宽度小于768px
 | 
			
		||||
------------------------------- */
 | 
			
		||||
@media screen and (max-width: $sm) {
 | 
			
		||||
	.home-warning-media,
 | 
			
		||||
	.home-dynamic-media {
 | 
			
		||||
		margin-top: 15px;
 | 
			
		||||
	}
 | 
			
		||||
@media screen and (max-width: index.$sm) {
 | 
			
		||||
 | 
			
		||||
    .home-warning-media,
 | 
			
		||||
    .home-dynamic-media {
 | 
			
		||||
        margin-top: 15px;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,55 +1,61 @@
 | 
			
		||||
@import './index.scss';
 | 
			
		||||
@use './index.scss' as index;
 | 
			
		||||
 | 
			
		||||
/* 页面宽度小于576px
 | 
			
		||||
------------------------------- */
 | 
			
		||||
@media screen and (max-width: $xs) {
 | 
			
		||||
	// MessageBox 弹框
 | 
			
		||||
	.el-message-box {
 | 
			
		||||
		width: 80% !important;
 | 
			
		||||
	}
 | 
			
		||||
@media screen and (max-width: index.$xs) {
 | 
			
		||||
 | 
			
		||||
    // MessageBox 弹框
 | 
			
		||||
    .el-message-box {
 | 
			
		||||
        width: 80% !important;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* 页面宽度小于768px
 | 
			
		||||
------------------------------- */
 | 
			
		||||
@media screen and (max-width: $sm) {
 | 
			
		||||
	// Breadcrumb 面包屑
 | 
			
		||||
	.layout-navbars-breadcrumb-hide {
 | 
			
		||||
		display: none;
 | 
			
		||||
	}
 | 
			
		||||
	// 外链视图
 | 
			
		||||
	.layout-view-link {
 | 
			
		||||
		a {
 | 
			
		||||
			max-width: 80%;
 | 
			
		||||
			text-align: center;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	// 菜单搜索
 | 
			
		||||
	.layout-search-dialog {
 | 
			
		||||
		.el-autocomplete {
 | 
			
		||||
			width: 80% !important;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
@media screen and (max-width: index.$sm) {
 | 
			
		||||
 | 
			
		||||
    // Breadcrumb 面包屑
 | 
			
		||||
    .layout-navbars-breadcrumb-hide {
 | 
			
		||||
        display: none;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // 外链视图
 | 
			
		||||
    .layout-view-link {
 | 
			
		||||
        a {
 | 
			
		||||
            max-width: 80%;
 | 
			
		||||
            text-align: center;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // 菜单搜索
 | 
			
		||||
    .layout-search-dialog {
 | 
			
		||||
        .el-autocomplete {
 | 
			
		||||
            width: 80% !important;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* 页面宽度小于1000px
 | 
			
		||||
------------------------------- */
 | 
			
		||||
@media screen and (max-width: 1000px) {
 | 
			
		||||
	// 布局配置
 | 
			
		||||
	.layout-drawer-content-flex {
 | 
			
		||||
		position: relative;
 | 
			
		||||
		&::after {
 | 
			
		||||
			content: '手机版不支持切换布局';
 | 
			
		||||
			position: absolute;
 | 
			
		||||
			top: 0;
 | 
			
		||||
			right: 0;
 | 
			
		||||
			bottom: 0;
 | 
			
		||||
			left: 0;
 | 
			
		||||
			z-index: 1;
 | 
			
		||||
			text-align: center;
 | 
			
		||||
			height: 140px;
 | 
			
		||||
			line-height: 140px;
 | 
			
		||||
			background: rgba(255, 255, 255, 0.9);
 | 
			
		||||
			color: #666666;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
    // 布局配置
 | 
			
		||||
    .layout-drawer-content-flex {
 | 
			
		||||
        position: relative;
 | 
			
		||||
 | 
			
		||||
        &::after {
 | 
			
		||||
            content: '手机版不支持切换布局';
 | 
			
		||||
            position: absolute;
 | 
			
		||||
            top: 0;
 | 
			
		||||
            right: 0;
 | 
			
		||||
            bottom: 0;
 | 
			
		||||
            left: 0;
 | 
			
		||||
            z-index: 1;
 | 
			
		||||
            text-align: center;
 | 
			
		||||
            height: 140px;
 | 
			
		||||
            line-height: 140px;
 | 
			
		||||
            background: rgba(255, 255, 255, 0.9);
 | 
			
		||||
            color: #666666;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,21 +1,23 @@
 | 
			
		||||
@import './index.scss';
 | 
			
		||||
@use './index.scss' as index;
 | 
			
		||||
 | 
			
		||||
/* 页面宽度小于576px
 | 
			
		||||
------------------------------- */
 | 
			
		||||
@media screen and (max-width: $xs) {
 | 
			
		||||
	.login-container {
 | 
			
		||||
		.login-content {
 | 
			
		||||
			width: 90% !important;
 | 
			
		||||
			padding: 20px 0 !important;
 | 
			
		||||
		}
 | 
			
		||||
		.login-content-form-btn {
 | 
			
		||||
			width: 100% !important;
 | 
			
		||||
			padding: 12px 0 !important;
 | 
			
		||||
		}
 | 
			
		||||
		.login-copyright {
 | 
			
		||||
			.login-copyright-msg {
 | 
			
		||||
				white-space: unset !important;
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
@media screen and (max-width: index.$xs) {
 | 
			
		||||
    .login-container {
 | 
			
		||||
        .login-content {
 | 
			
		||||
            width: 90% !important;
 | 
			
		||||
            padding: 20px 0 !important;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        .login-content-form-btn {
 | 
			
		||||
            width: 100% !important;
 | 
			
		||||
            padding: 12px 0 !important;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        .login-copyright {
 | 
			
		||||
            .login-copyright-msg {
 | 
			
		||||
                white-space: unset !important;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,12 +1,12 @@
 | 
			
		||||
@import './login.scss';
 | 
			
		||||
@import './error.scss';
 | 
			
		||||
@import './layout.scss';
 | 
			
		||||
@import './personal.scss';
 | 
			
		||||
@import './tagsView.scss';
 | 
			
		||||
@import './home.scss';
 | 
			
		||||
@import './chart.scss';
 | 
			
		||||
@import './form.scss';
 | 
			
		||||
@import './scrollbar.scss';
 | 
			
		||||
@import './pagination.scss';
 | 
			
		||||
@import './dialog.scss';
 | 
			
		||||
@import './cityLinkage.scss';
 | 
			
		||||
@use './login.scss';
 | 
			
		||||
@use './error.scss';
 | 
			
		||||
@use './layout.scss';
 | 
			
		||||
@use './personal.scss';
 | 
			
		||||
@use './tagsView.scss';
 | 
			
		||||
@use './home.scss';
 | 
			
		||||
@use './chart.scss';
 | 
			
		||||
@use './form.scss';
 | 
			
		||||
@use './scrollbar.scss';
 | 
			
		||||
@use './pagination.scss';
 | 
			
		||||
@use './dialog.scss';
 | 
			
		||||
@use './cityLinkage.scss';
 | 
			
		||||
@@ -1,15 +1,16 @@
 | 
			
		||||
@import './index.scss';
 | 
			
		||||
@use './index.scss' as index;
 | 
			
		||||
 | 
			
		||||
/* 页面宽度小于576px
 | 
			
		||||
------------------------------- */
 | 
			
		||||
@media screen and (max-width: $xs) {
 | 
			
		||||
	.el-pager,
 | 
			
		||||
	.el-pagination__jump {
 | 
			
		||||
		display: none !important;
 | 
			
		||||
	}
 | 
			
		||||
@media screen and (max-width: index.$xs) {
 | 
			
		||||
 | 
			
		||||
    .el-pager,
 | 
			
		||||
    .el-pagination__jump {
 | 
			
		||||
        display: none !important;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 默认居中对齐
 | 
			
		||||
.el-pagination {
 | 
			
		||||
	text-align: center !important;
 | 
			
		||||
    text-align: center !important;
 | 
			
		||||
}
 | 
			
		||||
@@ -1,16 +1,18 @@
 | 
			
		||||
@import './index.scss';
 | 
			
		||||
@use './index.scss' as index;
 | 
			
		||||
 | 
			
		||||
/* 页面宽度小于768px
 | 
			
		||||
------------------------------- */
 | 
			
		||||
@media screen and (max-width: $sm) {
 | 
			
		||||
	.personal-info {
 | 
			
		||||
		padding-left: 0 !important;
 | 
			
		||||
		margin-top: 15px;
 | 
			
		||||
	}
 | 
			
		||||
	.personal-recommend-col {
 | 
			
		||||
		margin-bottom: 15px;
 | 
			
		||||
		&:last-of-type {
 | 
			
		||||
			margin-bottom: 0;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
@media screen and (max-width: index.$sm) {
 | 
			
		||||
    .personal-info {
 | 
			
		||||
        padding-left: 0 !important;
 | 
			
		||||
        margin-top: 15px;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .personal-recommend-col {
 | 
			
		||||
        margin-bottom: 15px;
 | 
			
		||||
 | 
			
		||||
        &:last-of-type {
 | 
			
		||||
            margin-bottom: 0;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,56 +1,66 @@
 | 
			
		||||
@import './index.scss';
 | 
			
		||||
@use './index.scss' as index;
 | 
			
		||||
 | 
			
		||||
/* 页面宽度小于768px
 | 
			
		||||
------------------------------- */
 | 
			
		||||
@media screen and (max-width: $sm) {
 | 
			
		||||
	// 滚动条的宽度
 | 
			
		||||
	::-webkit-scrollbar {
 | 
			
		||||
		width: 3px !important;
 | 
			
		||||
		height: 3px !important;
 | 
			
		||||
	}
 | 
			
		||||
	::-webkit-scrollbar-track-piece {
 | 
			
		||||
		background-color: var(--bg-main-color);
 | 
			
		||||
	}
 | 
			
		||||
	// 滚动条的设置
 | 
			
		||||
	::-webkit-scrollbar-thumb {
 | 
			
		||||
		background-color: rgba(144, 147, 153, 0.3);
 | 
			
		||||
		background-clip: padding-box;
 | 
			
		||||
		min-height: 28px;
 | 
			
		||||
		border-radius: 5px;
 | 
			
		||||
		transition: 0.3s background-color;
 | 
			
		||||
	}
 | 
			
		||||
	::-webkit-scrollbar-thumb:hover {
 | 
			
		||||
		background-color: rgba(144, 147, 153, 0.5);
 | 
			
		||||
	}
 | 
			
		||||
	// element plus scrollbar
 | 
			
		||||
	.el-scrollbar__bar.is-vertical {
 | 
			
		||||
		width: 2px !important;
 | 
			
		||||
	}
 | 
			
		||||
	.el-scrollbar__bar.is-horizontal {
 | 
			
		||||
		height: 2px !important;
 | 
			
		||||
	}
 | 
			
		||||
@media screen and (max-width: index.$sm) {
 | 
			
		||||
 | 
			
		||||
    // 滚动条的宽度
 | 
			
		||||
    ::-webkit-scrollbar {
 | 
			
		||||
        width: 3px !important;
 | 
			
		||||
        height: 3px !important;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    ::-webkit-scrollbar-track-piece {
 | 
			
		||||
        background-color: var(--bg-main-color);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // 滚动条的设置
 | 
			
		||||
    ::-webkit-scrollbar-thumb {
 | 
			
		||||
        background-color: rgba(144, 147, 153, 0.3);
 | 
			
		||||
        background-clip: padding-box;
 | 
			
		||||
        min-height: 28px;
 | 
			
		||||
        border-radius: 5px;
 | 
			
		||||
        transition: 0.3s background-color;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    ::-webkit-scrollbar-thumb:hover {
 | 
			
		||||
        background-color: rgba(144, 147, 153, 0.5);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // element plus scrollbar
 | 
			
		||||
    .el-scrollbar__bar.is-vertical {
 | 
			
		||||
        width: 2px !important;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .el-scrollbar__bar.is-horizontal {
 | 
			
		||||
        height: 2px !important;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* 页面宽度大于768px
 | 
			
		||||
------------------------------- */
 | 
			
		||||
@media screen and (min-width: 769px) {
 | 
			
		||||
	// 滚动条的宽度
 | 
			
		||||
	::-webkit-scrollbar {
 | 
			
		||||
		width: 7px;
 | 
			
		||||
		height: 7px;
 | 
			
		||||
	}
 | 
			
		||||
	::-webkit-scrollbar-track-piece {
 | 
			
		||||
		background-color: var(--bg-main-color);
 | 
			
		||||
	}
 | 
			
		||||
	// 滚动条的设置
 | 
			
		||||
	::-webkit-scrollbar-thumb {
 | 
			
		||||
		background-color: rgba(144, 147, 153, 0.3);
 | 
			
		||||
		background-clip: padding-box;
 | 
			
		||||
		min-height: 28px;
 | 
			
		||||
		border-radius: 5px;
 | 
			
		||||
		transition: 0.3s background-color;
 | 
			
		||||
	}
 | 
			
		||||
	::-webkit-scrollbar-thumb:hover {
 | 
			
		||||
		background-color: rgba(144, 147, 153, 0.5);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
    // 滚动条的宽度
 | 
			
		||||
    ::-webkit-scrollbar {
 | 
			
		||||
        width: 7px;
 | 
			
		||||
        height: 7px;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    ::-webkit-scrollbar-track-piece {
 | 
			
		||||
        background-color: var(--bg-main-color);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // 滚动条的设置
 | 
			
		||||
    ::-webkit-scrollbar-thumb {
 | 
			
		||||
        background-color: rgba(144, 147, 153, 0.3);
 | 
			
		||||
        background-clip: padding-box;
 | 
			
		||||
        min-height: 28px;
 | 
			
		||||
        border-radius: 5px;
 | 
			
		||||
        transition: 0.3s background-color;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    ::-webkit-scrollbar-thumb:hover {
 | 
			
		||||
        background-color: rgba(144, 147, 153, 0.5);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,11 +1,11 @@
 | 
			
		||||
@import './index.scss';
 | 
			
		||||
@use './index.scss' as index;
 | 
			
		||||
 | 
			
		||||
/* 页面宽度小于768px
 | 
			
		||||
------------------------------- */
 | 
			
		||||
@media screen and (max-width: $sm) {
 | 
			
		||||
	.tags-view-form {
 | 
			
		||||
		.tags-view-form-col {
 | 
			
		||||
			margin-bottom: 20px;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
@media screen and (max-width: index.$sm) {
 | 
			
		||||
    .tags-view-form {
 | 
			
		||||
        .tags-view-form-col {
 | 
			
		||||
            margin-bottom: 20px;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,32 +1,32 @@
 | 
			
		||||
/* 第三方图标字体间距/大小设置
 | 
			
		||||
------------------------------- */
 | 
			
		||||
@mixin generalIcon {
 | 
			
		||||
	font-size: 14px !important;
 | 
			
		||||
	display: inline-block;
 | 
			
		||||
	vertical-align: middle;
 | 
			
		||||
	margin-right: 5px;
 | 
			
		||||
	width: 24px;
 | 
			
		||||
	text-align: center;
 | 
			
		||||
	justify-content: center;
 | 
			
		||||
    font-size: 14px !important;
 | 
			
		||||
    display: inline-block;
 | 
			
		||||
    vertical-align: middle;
 | 
			
		||||
    margin-right: 5px;
 | 
			
		||||
    width: 24px;
 | 
			
		||||
    text-align: center;
 | 
			
		||||
    justify-content: center;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* 文本不换行
 | 
			
		||||
------------------------------- */
 | 
			
		||||
@mixin text-no-wrap() {
 | 
			
		||||
	text-overflow: ellipsis;
 | 
			
		||||
	overflow: hidden;
 | 
			
		||||
	white-space: nowrap;
 | 
			
		||||
    text-overflow: ellipsis;
 | 
			
		||||
    overflow: hidden;
 | 
			
		||||
    white-space: nowrap;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* 多行文本溢出
 | 
			
		||||
  ------------------------------- */
 | 
			
		||||
@mixin text-ellipsis($line: 2) {
 | 
			
		||||
	overflow: hidden;
 | 
			
		||||
	word-break: break-all;
 | 
			
		||||
	text-overflow: ellipsis;
 | 
			
		||||
	display: -webkit-box;
 | 
			
		||||
	-webkit-line-clamp: $line;
 | 
			
		||||
	-webkit-box-orient: vertical;
 | 
			
		||||
    overflow: hidden;
 | 
			
		||||
    word-break: break-all;
 | 
			
		||||
    text-overflow: ellipsis;
 | 
			
		||||
    display: -webkit-box;
 | 
			
		||||
    -webkit-line-clamp: $line;
 | 
			
		||||
    -webkit-box-orient: vertical;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* 滚动条(页面未使用) div 中使用:
 | 
			
		||||
@@ -35,22 +35,26 @@
 | 
			
		||||
//   @include scrollBar;
 | 
			
		||||
// }
 | 
			
		||||
@mixin scrollBar {
 | 
			
		||||
	// 滚动条凹槽的颜色,还可以设置边框属性
 | 
			
		||||
	&::-webkit-scrollbar-track-piece {
 | 
			
		||||
		background-color: #f8f8f8;
 | 
			
		||||
	}
 | 
			
		||||
	// 滚动条的宽度
 | 
			
		||||
	&::-webkit-scrollbar {
 | 
			
		||||
		width: 9px;
 | 
			
		||||
		height: 9px;
 | 
			
		||||
	}
 | 
			
		||||
	// 滚动条的设置
 | 
			
		||||
	&::-webkit-scrollbar-thumb {
 | 
			
		||||
		background-color: #dddddd;
 | 
			
		||||
		background-clip: padding-box;
 | 
			
		||||
		min-height: 28px;
 | 
			
		||||
	}
 | 
			
		||||
	&::-webkit-scrollbar-thumb:hover {
 | 
			
		||||
		background-color: #bbb;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
    // 滚动条凹槽的颜色,还可以设置边框属性
 | 
			
		||||
    &::-webkit-scrollbar-track-piece {
 | 
			
		||||
        background-color: #f8f8f8;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // 滚动条的宽度
 | 
			
		||||
    &::-webkit-scrollbar {
 | 
			
		||||
        width: 9px;
 | 
			
		||||
        height: 9px;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // 滚动条的设置
 | 
			
		||||
    &::-webkit-scrollbar-thumb {
 | 
			
		||||
        background-color: #dddddd;
 | 
			
		||||
        background-clip: padding-box;
 | 
			
		||||
        min-height: 28px;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    &::-webkit-scrollbar-thumb:hover {
 | 
			
		||||
        background-color: #bbb;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,28 +1,31 @@
 | 
			
		||||
/* wangeditor富文本编辑器
 | 
			
		||||
------------------------------- */
 | 
			
		||||
.w-e-toolbar {
 | 
			
		||||
	border: 1px solid #ebeef5 !important;
 | 
			
		||||
	border-bottom: 1px solid #ebeef5 !important;
 | 
			
		||||
	border-top-left-radius: 3px;
 | 
			
		||||
	border-top-right-radius: 3px;
 | 
			
		||||
	z-index: 2 !important;
 | 
			
		||||
    border: 1px solid #ebeef5 !important;
 | 
			
		||||
    border-bottom: 1px solid #ebeef5 !important;
 | 
			
		||||
    border-top-left-radius: 3px;
 | 
			
		||||
    border-top-right-radius: 3px;
 | 
			
		||||
    z-index: 2 !important;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.w-e-text-container {
 | 
			
		||||
	border: 1px solid #ebeef5 !important;
 | 
			
		||||
	border-top: none !important;
 | 
			
		||||
	border-bottom-left-radius: 3px;
 | 
			
		||||
	border-bottom-right-radius: 3px;
 | 
			
		||||
	z-index: 1 !important;
 | 
			
		||||
    border: 1px solid #ebeef5 !important;
 | 
			
		||||
    border-top: none !important;
 | 
			
		||||
    border-bottom-left-radius: 3px;
 | 
			
		||||
    border-bottom-right-radius: 3px;
 | 
			
		||||
    z-index: 1 !important;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* web端自定义截屏
 | 
			
		||||
------------------------------- */
 | 
			
		||||
#screenShotContainer {
 | 
			
		||||
	z-index: 9998 !important;
 | 
			
		||||
    z-index: 9998 !important;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#toolPanel {
 | 
			
		||||
	height: 42px !important;
 | 
			
		||||
    height: 42px !important;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#optionPanel {
 | 
			
		||||
	height: 37px !important;
 | 
			
		||||
    height: 37px !important;
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										149
									
								
								mayfly_go_web/src/views/flow/ProcInstEdit.vue
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										149
									
								
								mayfly_go_web/src/views/flow/ProcInstEdit.vue
									
									
									
									
									
										Executable file
									
								
							@@ -0,0 +1,149 @@
 | 
			
		||||
<template>
 | 
			
		||||
    <div>
 | 
			
		||||
        <el-drawer :title="props.title" v-model="visible" :before-close="cancel" :destroy-on-close="true" :close-on-click-modal="false" size="50%">
 | 
			
		||||
            <template #header>
 | 
			
		||||
                <DrawerHeader :header="title" :back="cancel" />
 | 
			
		||||
            </template>
 | 
			
		||||
 | 
			
		||||
            <el-form :model="form" ref="formRef" :rules="rules" label-width="auto">
 | 
			
		||||
                <el-form-item prop="bizType" label="业务类型">
 | 
			
		||||
                    <EnumSelect v-model="form.bizType" :enums="FlowBizType" placeholder="请选择业务类型" />
 | 
			
		||||
                </el-form-item>
 | 
			
		||||
 | 
			
		||||
                <el-form-item prop="remark" label="备注">
 | 
			
		||||
                    <el-input v-model.trim="form.remark" type="textarea" placeholder="备注" auto-complete="off" clearable></el-input>
 | 
			
		||||
                </el-form-item>
 | 
			
		||||
 | 
			
		||||
                <el-divider content-position="left">业务信息</el-divider>
 | 
			
		||||
                <component
 | 
			
		||||
                    ref="bizFormRef"
 | 
			
		||||
                    v-if="form.bizType"
 | 
			
		||||
                    :is="bizComponents[form.bizType]"
 | 
			
		||||
                    v-model:bizForm="form.bizForm"
 | 
			
		||||
                    @changeResourceCode="changeResourceCode"
 | 
			
		||||
                >
 | 
			
		||||
                </component>
 | 
			
		||||
            </el-form>
 | 
			
		||||
 | 
			
		||||
            <span v-if="flowProcdef || !state.form.procdefId">
 | 
			
		||||
                <el-divider content-position="left">审批节点</el-divider>
 | 
			
		||||
 | 
			
		||||
                <ProcdefTasks v-if="flowProcdef" :procdef="flowProcdef" />
 | 
			
		||||
 | 
			
		||||
                <el-result v-if="!state.form.procdefId" icon="error" title="不存在审批节点" sub-title="该资源无需审批操作"> </el-result>
 | 
			
		||||
            </span>
 | 
			
		||||
 | 
			
		||||
            <template #footer>
 | 
			
		||||
                <div>
 | 
			
		||||
                    <el-button @click="cancel()">取 消</el-button>
 | 
			
		||||
                    <el-button type="primary" :loading="saveBtnLoading" @click="btnOk" :disabled="!state.form.procdefId">确 定</el-button>
 | 
			
		||||
                </div>
 | 
			
		||||
            </template>
 | 
			
		||||
        </el-drawer>
 | 
			
		||||
    </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script lang="ts" setup>
 | 
			
		||||
import { toRefs, reactive, defineAsyncComponent, shallowReactive, useTemplateRef } from 'vue';
 | 
			
		||||
import { procdefApi, procinstApi } from './api';
 | 
			
		||||
import { ElMessage } from 'element-plus';
 | 
			
		||||
import DrawerHeader from '@/components/drawer-header/DrawerHeader.vue';
 | 
			
		||||
import { FlowBizType } from './enums';
 | 
			
		||||
import EnumSelect from '@/components/enumselect/EnumSelect.vue';
 | 
			
		||||
import ProcdefTasks from './components/ProcdefTasks.vue';
 | 
			
		||||
import RedisRunCmdFlowBizForm from './flowbiz/redis/RedisRunCmdFlowBizForm.vue';
 | 
			
		||||
 | 
			
		||||
const DbSqlExecFlowBizForm = defineAsyncComponent(() => import('./flowbiz/dbms/DbSqlExecFlowBizForm.vue'));
 | 
			
		||||
 | 
			
		||||
const props = defineProps({
 | 
			
		||||
    title: {
 | 
			
		||||
        type: String,
 | 
			
		||||
    },
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const visible = defineModel<boolean>('visible', { default: false });
 | 
			
		||||
 | 
			
		||||
//定义事件
 | 
			
		||||
const emit = defineEmits(['cancel', 'val-change']);
 | 
			
		||||
 | 
			
		||||
const formRef: any = useTemplateRef('formRef');
 | 
			
		||||
const bizFormRef: any = useTemplateRef('bizFormRef');
 | 
			
		||||
 | 
			
		||||
// 业务组件
 | 
			
		||||
const bizComponents: any = shallowReactive({
 | 
			
		||||
    db_sql_exec_flow: DbSqlExecFlowBizForm,
 | 
			
		||||
    redis_run_cmd_flow: RedisRunCmdFlowBizForm,
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const rules = {
 | 
			
		||||
    bizType: [
 | 
			
		||||
        {
 | 
			
		||||
            required: true,
 | 
			
		||||
            message: '请选择流程业务类型',
 | 
			
		||||
            trigger: ['change', 'blur'],
 | 
			
		||||
        },
 | 
			
		||||
    ],
 | 
			
		||||
    remark: [
 | 
			
		||||
        {
 | 
			
		||||
            required: true,
 | 
			
		||||
            message: '请输入申请备注',
 | 
			
		||||
            trigger: ['change', 'blur'],
 | 
			
		||||
        },
 | 
			
		||||
    ],
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const defaultForm = {
 | 
			
		||||
    bizType: FlowBizType.DbSqlExec.value,
 | 
			
		||||
    procdefId: -1,
 | 
			
		||||
    status: null,
 | 
			
		||||
    remark: '',
 | 
			
		||||
    bizForm: {},
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const state = reactive({
 | 
			
		||||
    tasks: [] as any,
 | 
			
		||||
    form: { ...defaultForm },
 | 
			
		||||
    flowProcdef: null as any,
 | 
			
		||||
    sortable: '' as any,
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const { form, flowProcdef } = toRefs(state);
 | 
			
		||||
 | 
			
		||||
const { isFetching: saveBtnLoading, execute: procinstStart } = procinstApi.start.useApi(form);
 | 
			
		||||
 | 
			
		||||
const changeResourceCode = async (resourceType: any, code: string) => {
 | 
			
		||||
    state.flowProcdef = await procdefApi.getByResource.request({ resourceType, resourceCode: code });
 | 
			
		||||
    if (!state.flowProcdef) {
 | 
			
		||||
        state.form.procdefId = 0;
 | 
			
		||||
    } else {
 | 
			
		||||
        state.form.procdefId = state.flowProcdef.id;
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const btnOk = async () => {
 | 
			
		||||
    try {
 | 
			
		||||
        await formRef.value.validate();
 | 
			
		||||
        await bizFormRef.value.validateBizForm();
 | 
			
		||||
    } catch (e: any) {
 | 
			
		||||
        ElMessage.error('请正确填写信息');
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    await procinstStart();
 | 
			
		||||
    ElMessage.success('流程发起成功');
 | 
			
		||||
    emit('val-change', state.form);
 | 
			
		||||
    //重置表单域
 | 
			
		||||
    cancel();
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const cancel = () => {
 | 
			
		||||
    visible.value = false;
 | 
			
		||||
    emit('cancel');
 | 
			
		||||
    state.flowProcdef = null;
 | 
			
		||||
    formRef.value.resetFields();
 | 
			
		||||
    bizFormRef.value.resetBizForm();
 | 
			
		||||
 | 
			
		||||
    state.form = { ...defaultForm };
 | 
			
		||||
};
 | 
			
		||||
</script>
 | 
			
		||||
<style lang="scss"></style>
 | 
			
		||||
@@ -1,6 +1,6 @@
 | 
			
		||||
<template>
 | 
			
		||||
    <div>
 | 
			
		||||
        <el-drawer @open="initSort" :title="title" v-model="visible" :before-close="cancel" :destroy-on-close="true" :close-on-click-modal="false">
 | 
			
		||||
        <el-drawer @open="initSort" :title="title" v-model="visible" :before-close="cancel" :destroy-on-close="true" :close-on-click-modal="false" size="40%">
 | 
			
		||||
            <template #header>
 | 
			
		||||
                <DrawerHeader :header="title" :back="cancel" />
 | 
			
		||||
            </template>
 | 
			
		||||
@@ -17,10 +17,33 @@
 | 
			
		||||
                        <el-option v-for="item in ProcdefStatus" :key="item.value" :label="item.label" :value="item.value"> </el-option>
 | 
			
		||||
                    </el-select>
 | 
			
		||||
                </el-form-item>
 | 
			
		||||
                <el-form-item prop="condition" label="触发条件">
 | 
			
		||||
                    <template #label>
 | 
			
		||||
                        触发条件
 | 
			
		||||
                        <el-tooltip content="go template语法。若输出结果为1,则表示触发该审批流程" placement="top">
 | 
			
		||||
                            <el-icon>
 | 
			
		||||
                                <question-filled />
 | 
			
		||||
                            </el-icon>
 | 
			
		||||
                        </el-tooltip>
 | 
			
		||||
                    </template>
 | 
			
		||||
 | 
			
		||||
                    <el-input
 | 
			
		||||
                        v-model="form.condition"
 | 
			
		||||
                        :rows="10"
 | 
			
		||||
                        type="textarea"
 | 
			
		||||
                        placeholder="触发条件, 返回值=1, 则表示触发该审批流程"
 | 
			
		||||
                        auto-complete="off"
 | 
			
		||||
                        clearable
 | 
			
		||||
                    ></el-input>
 | 
			
		||||
                </el-form-item>
 | 
			
		||||
                <el-form-item prop="remark" label="备注">
 | 
			
		||||
                    <el-input v-model.trim="form.remark" placeholder="备注" auto-complete="off" clearable></el-input>
 | 
			
		||||
                </el-form-item>
 | 
			
		||||
 | 
			
		||||
                <el-form-item ref="tagSelectRef" prop="codePaths" label="关联资源">
 | 
			
		||||
                    <tag-tree-check height="300px" v-model="form.codePaths" :tag-type="[TagResourceTypeEnum.DbName.value, TagResourceTypeEnum.Redis.value]" />
 | 
			
		||||
                </el-form-item>
 | 
			
		||||
 | 
			
		||||
                <el-divider content-position="left">审批节点</el-divider>
 | 
			
		||||
 | 
			
		||||
                <el-table ref="taskTableRef" :data="tasks" row-key="taskKey" stripe style="width: 100%">
 | 
			
		||||
@@ -70,6 +93,8 @@ import AccountSelectFormItem from '@/views/system/account/components/AccountSele
 | 
			
		||||
import Sortable from 'sortablejs';
 | 
			
		||||
import { randomUuid } from '../../common/utils/string';
 | 
			
		||||
import { ProcdefStatus } from './enums';
 | 
			
		||||
import TagTreeCheck from '../ops/component/TagTreeCheck.vue';
 | 
			
		||||
import { TagResourceTypeEnum } from '@/common/commonEnum';
 | 
			
		||||
 | 
			
		||||
const props = defineProps({
 | 
			
		||||
    data: {
 | 
			
		||||
@@ -112,9 +137,11 @@ const state = reactive({
 | 
			
		||||
        name: null,
 | 
			
		||||
        defKey: null,
 | 
			
		||||
        status: null,
 | 
			
		||||
        condition: '',
 | 
			
		||||
        remark: null,
 | 
			
		||||
        // 流程的审批节点任务
 | 
			
		||||
        tasks: '',
 | 
			
		||||
        codePaths: [],
 | 
			
		||||
    },
 | 
			
		||||
    sortable: '' as any,
 | 
			
		||||
});
 | 
			
		||||
@@ -126,6 +153,7 @@ const { isFetching: saveBtnLoading, execute: saveFlowDefExec } = procdefApi.save
 | 
			
		||||
watch(props, (newValue: any) => {
 | 
			
		||||
    if (newValue.data) {
 | 
			
		||||
        state.form = { ...newValue.data };
 | 
			
		||||
        state.form.codePaths = newValue.data.tags?.map((tag: any) => tag.codePath);
 | 
			
		||||
        const tasks = JSON.parse(state.form.tasks);
 | 
			
		||||
        tasks.forEach((t: any) => {
 | 
			
		||||
            t.userId = Number.parseInt(t.userId);
 | 
			
		||||
@@ -133,6 +161,23 @@ watch(props, (newValue: any) => {
 | 
			
		||||
        state.tasks = tasks;
 | 
			
		||||
    } else {
 | 
			
		||||
        state.form = { status: ProcdefStatus.Enable.value } as any;
 | 
			
		||||
        state.form.condition = `{{/* DBMS-执行sql规则;  param参数描述如下 */}}
 | 
			
		||||
{{/* stmtType: select / read / insert / update / delete / ddl ;  */}}
 | 
			
		||||
{{ if eq .bizType "db_sql_exec_flow"}}
 | 
			
		||||
    {{/* 不是select和read语句时,开启流程审批 */}}
 | 
			
		||||
    {{ if and (ne .param.stmtType "select") (ne .param.stmtType "read") }}
 | 
			
		||||
        1
 | 
			
		||||
    {{ end }}
 | 
			
		||||
{{ end }}
 | 
			
		||||
 | 
			
		||||
{{/* Redis-执行命令规则;   param参数描述如下 */}}
 | 
			
		||||
{{/* cmdType: read(读命令) / write(写命令);  */}}
 | 
			
		||||
{{/* cmd: get/set/hset...等 */}}
 | 
			
		||||
{{ if eq .bizType "redis_run_cmd_flow"}}
 | 
			
		||||
    {{ if eq .param.cmdType "write" }}
 | 
			
		||||
        1
 | 
			
		||||
    {{ end }}
 | 
			
		||||
{{ end }}`;
 | 
			
		||||
        state.tasks = [];
 | 
			
		||||
    }
 | 
			
		||||
});
 | 
			
		||||
@@ -160,25 +205,26 @@ const deleteTask = (idx: any) => {
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
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;
 | 
			
		||||
        }
 | 
			
		||||
    try {
 | 
			
		||||
        await formRef.value.validate();
 | 
			
		||||
    } catch (e: any) {
 | 
			
		||||
        ElMessage.error('请正确填写信息');
 | 
			
		||||
        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 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 = () => {
 | 
			
		||||
 
 | 
			
		||||
@@ -18,6 +18,10 @@
 | 
			
		||||
                <el-link @click="showProcdefTasks(data)" icon="view" type="primary" :underline="false"> </el-link>
 | 
			
		||||
            </template>
 | 
			
		||||
 | 
			
		||||
            <template #codePaths="{ data }">
 | 
			
		||||
                <TagCodePath :path="data.tags?.map((tag: any) => tag.codePath)" />
 | 
			
		||||
            </template>
 | 
			
		||||
 | 
			
		||||
            <template #action="{ data }">
 | 
			
		||||
                <el-button link v-if="actionBtns[perms.save]" @click="editFlowDef(data)" type="primary">编辑</el-button>
 | 
			
		||||
            </template>
 | 
			
		||||
@@ -42,6 +46,7 @@ import { SearchItem } from '@/components/SearchForm';
 | 
			
		||||
import ProcdefEdit from './ProcdefEdit.vue';
 | 
			
		||||
import ProcdefTasks from './components/ProcdefTasks.vue';
 | 
			
		||||
import { ProcdefStatus } from './enums';
 | 
			
		||||
import TagCodePath from '../ops/component/TagCodePath.vue';
 | 
			
		||||
 | 
			
		||||
const perms = {
 | 
			
		||||
    save: 'flow:procdef:save',
 | 
			
		||||
@@ -55,12 +60,13 @@ const columns = [
 | 
			
		||||
    TableColumn.new('status', '状态').typeTag(ProcdefStatus),
 | 
			
		||||
    TableColumn.new('remark', '备注'),
 | 
			
		||||
    TableColumn.new('tasks', '审批节点').isSlot().alignCenter().setMinWidth(60),
 | 
			
		||||
    TableColumn.new('codePaths', '关联资源').isSlot().setMinWidth('250px'),
 | 
			
		||||
    TableColumn.new('creator', '创建账号'),
 | 
			
		||||
    TableColumn.new('createTime', '创建时间').isTime(),
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
// 该用户拥有的的操作列按钮权限
 | 
			
		||||
const actionBtns = hasPerms([perms.save, perms.del]);
 | 
			
		||||
const actionBtns: any = hasPerms([perms.save, perms.del]);
 | 
			
		||||
const actionColumn = TableColumn.new('action', '操作').isSlot().fixedRight().setMinWidth(160).noShowOverflowTooltip().alignCenter();
 | 
			
		||||
 | 
			
		||||
const pageTableRef: Ref<any> = ref(null);
 | 
			
		||||
 
 | 
			
		||||
@@ -1,28 +1,20 @@
 | 
			
		||||
<template>
 | 
			
		||||
    <div>
 | 
			
		||||
        <el-drawer :title="props.title" v-model="visible" :before-close="cancel" size="40%" :close-on-click-modal="!props.instTaskId">
 | 
			
		||||
        <el-drawer :title="props.title" v-model="visible" :before-close="cancel" size="50%" :close-on-click-modal="!props.instTaskId">
 | 
			
		||||
            <template #header>
 | 
			
		||||
                <DrawerHeader :header="title" :back="cancel" />
 | 
			
		||||
            </template>
 | 
			
		||||
 | 
			
		||||
            <div>
 | 
			
		||||
                <el-divider content-position="left">流程信息</el-divider>
 | 
			
		||||
                <el-descriptions :column="2" border>
 | 
			
		||||
                <el-descriptions :column="3" border>
 | 
			
		||||
                    <el-descriptions-item label="流程名">{{ procinst.procdefName }}</el-descriptions-item>
 | 
			
		||||
                    <el-descriptions-item label="业务">
 | 
			
		||||
                        <enum-tag :enums="FlowBizType" :value="procinst.bizType"></enum-tag>
 | 
			
		||||
                    </el-descriptions-item>
 | 
			
		||||
 | 
			
		||||
                    <el-descriptions-item label="发起人">
 | 
			
		||||
                        <AccountInfo :account-id="procinst.creatorId" :username="procinst.creator" />
 | 
			
		||||
                        <!-- {{ 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>
 | 
			
		||||
@@ -31,6 +23,13 @@
 | 
			
		||||
                        <enum-tag :enums="ProcinstBizStatus" :value="procinst.bizStatus"></enum-tag>
 | 
			
		||||
                    </el-descriptions-item>
 | 
			
		||||
 | 
			
		||||
                    <el-descriptions-item label="发起时间">{{ formatDate(procinst.createTime) }}</el-descriptions-item>
 | 
			
		||||
 | 
			
		||||
                    <div v-if="procinst.duration">
 | 
			
		||||
                        <el-descriptions-item label="结束时间">{{ formatDate(procinst.endTime) }}</el-descriptions-item>
 | 
			
		||||
                        <el-descriptions-item label="持续时间">{{ formatTime(procinst.duration) }}</el-descriptions-item>
 | 
			
		||||
                    </div>
 | 
			
		||||
 | 
			
		||||
                    <el-descriptions-item label="备注">
 | 
			
		||||
                        {{ procinst.remark }}
 | 
			
		||||
                    </el-descriptions-item>
 | 
			
		||||
@@ -44,14 +43,7 @@
 | 
			
		||||
 | 
			
		||||
            <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>
 | 
			
		||||
                <component v-if="procinst.bizType" ref="keyValueRef" :is="bizComponents[procinst.bizType]" :procinst="procinst"> </component>
 | 
			
		||||
            </div>
 | 
			
		||||
 | 
			
		||||
            <div v-if="props.instTaskId">
 | 
			
		||||
@@ -86,14 +78,14 @@ 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';
 | 
			
		||||
import { formatDate } from '@/common/utils/format';
 | 
			
		||||
 | 
			
		||||
const DbSqlExecBiz = defineAsyncComponent(() => import('./flowbiz/DbSqlExecBiz.vue'));
 | 
			
		||||
const RedisRunWriteCmdBiz = defineAsyncComponent(() => import('./flowbiz/RedisRunWriteCmdBiz.vue'));
 | 
			
		||||
const DbSqlExecBiz = defineAsyncComponent(() => import('./flowbiz/dbms/DbSqlExecBiz.vue'));
 | 
			
		||||
const RedisRunCmdBiz = defineAsyncComponent(() => import('./flowbiz/redis/RedisRunCmdBiz.vue'));
 | 
			
		||||
 | 
			
		||||
const props = defineProps({
 | 
			
		||||
    procinstId: {
 | 
			
		||||
@@ -114,9 +106,9 @@ const visible = defineModel<boolean>('visible', { default: false });
 | 
			
		||||
const emit = defineEmits(['cancel', 'val-change']);
 | 
			
		||||
 | 
			
		||||
// 业务组件
 | 
			
		||||
const bizComponents = shallowReactive({
 | 
			
		||||
const bizComponents: any = shallowReactive({
 | 
			
		||||
    db_sql_exec_flow: DbSqlExecBiz,
 | 
			
		||||
    redis_run_write_cmd_flow: RedisRunWriteCmdBiz,
 | 
			
		||||
    redis_run_cmd_flow: RedisRunCmdBiz,
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const state = reactive({
 | 
			
		||||
 
 | 
			
		||||
@@ -9,7 +9,7 @@
 | 
			
		||||
            :columns="columns"
 | 
			
		||||
        >
 | 
			
		||||
            <template #tableHeader>
 | 
			
		||||
                <!-- <el-button v-auth="perms.addAccount" type="primary" icon="plus" @click="editFlowDef(false)">添加</el-button> -->
 | 
			
		||||
                <el-button type="primary" icon="plus" @click="startProcInst()">发起流程</el-button>
 | 
			
		||||
            </template>
 | 
			
		||||
 | 
			
		||||
            <template #action="{ data }">
 | 
			
		||||
@@ -36,6 +36,8 @@
 | 
			
		||||
            @val-change="valChange()"
 | 
			
		||||
            @cancel="procinstDetail.procinstId = 0"
 | 
			
		||||
        />
 | 
			
		||||
 | 
			
		||||
        <ProcInstEdit v-model:visible="procinstEdit.visible" :title="procinstEdit.title" @val-change="search" />
 | 
			
		||||
    </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
@@ -49,6 +51,7 @@ import ProcinstDetail from './ProcinstDetail.vue';
 | 
			
		||||
import { FlowBizType, ProcinstBizStatus, ProcinstStatus } from './enums';
 | 
			
		||||
import { ElMessage } from 'element-plus';
 | 
			
		||||
import { formatTime } from '@/common/utils/format';
 | 
			
		||||
import ProcInstEdit from './ProcInstEdit.vue';
 | 
			
		||||
 | 
			
		||||
const searchItems = [
 | 
			
		||||
    SearchItem.select('status', '流程状态').withEnum(ProcinstStatus),
 | 
			
		||||
@@ -73,7 +76,7 @@ const columns = [
 | 
			
		||||
        }
 | 
			
		||||
        return formatTime(duration);
 | 
			
		||||
    }),
 | 
			
		||||
    TableColumn.new('bizHandleRes', '业务处理结果'),
 | 
			
		||||
    // TableColumn.new('bizHandleRes', '业务处理结果'),
 | 
			
		||||
    TableColumn.new('action', '操作').isSlot().fixedRight().setMinWidth(160).noShowOverflowTooltip().alignCenter(),
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
@@ -98,9 +101,13 @@ const state = reactive({
 | 
			
		||||
        procinstId: 0,
 | 
			
		||||
        instTaskId: 0,
 | 
			
		||||
    },
 | 
			
		||||
    procinstEdit: {
 | 
			
		||||
        title: '发起流程',
 | 
			
		||||
        visible: false,
 | 
			
		||||
    },
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const { selectionData, query, procinstDetail } = toRefs(state);
 | 
			
		||||
const { selectionData, query, procinstDetail, procinstEdit } = toRefs(state);
 | 
			
		||||
 | 
			
		||||
const search = async () => {
 | 
			
		||||
    pageTableRef.value.search();
 | 
			
		||||
@@ -118,6 +125,10 @@ const showProcinst = (data: any) => {
 | 
			
		||||
    state.procinstDetail.visible = true;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const startProcInst = () => {
 | 
			
		||||
    state.procinstEdit.visible = true;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const valChange = () => {
 | 
			
		||||
    state.procinstDetail.visible = false;
 | 
			
		||||
    search();
 | 
			
		||||
 
 | 
			
		||||
@@ -2,13 +2,14 @@ import Api from '@/common/Api';
 | 
			
		||||
 | 
			
		||||
export const procdefApi = {
 | 
			
		||||
    list: Api.newGet('/flow/procdefs'),
 | 
			
		||||
    getByKey: Api.newGet('/flow/procdefs/{key}'),
 | 
			
		||||
    getByResource: Api.newGet('/flow/procdefs/{resourceType}/{resourceCode}'),
 | 
			
		||||
    save: Api.newPost('/flow/procdefs'),
 | 
			
		||||
    del: Api.newDelete('/flow/procdefs/{id}'),
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const procinstApi = {
 | 
			
		||||
    list: Api.newGet('/flow/procinsts'),
 | 
			
		||||
    start: Api.newPost('/flow/procinsts/start'),
 | 
			
		||||
    detail: Api.newGet('/flow/procinsts/{id}'),
 | 
			
		||||
    cancel: Api.newPost('/flow/procinsts/{id}/cancel'),
 | 
			
		||||
    tasks: Api.newGet('/flow/procinsts/tasks'),
 | 
			
		||||
 
 | 
			
		||||
@@ -3,7 +3,7 @@
 | 
			
		||||
        <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.completeTime">{{ `${formatDate(task.completeTime)}` }}</div>
 | 
			
		||||
                <div v-if="task.remark">{{ task.remark }}</div>
 | 
			
		||||
            </template>
 | 
			
		||||
        </el-step>
 | 
			
		||||
@@ -14,8 +14,7 @@
 | 
			
		||||
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 { formatDate } from '@/common/utils/format';
 | 
			
		||||
import { ElSteps, ElStep } from 'element-plus';
 | 
			
		||||
 | 
			
		||||
const props = defineProps({
 | 
			
		||||
@@ -23,8 +22,8 @@ const props = defineProps({
 | 
			
		||||
    tasks: {
 | 
			
		||||
        type: [String, Object],
 | 
			
		||||
    },
 | 
			
		||||
    procdefKey: {
 | 
			
		||||
        type: String,
 | 
			
		||||
    procdef: {
 | 
			
		||||
        type: [Object],
 | 
			
		||||
    },
 | 
			
		||||
    // 流程实例任务列表
 | 
			
		||||
    procinstTasks: {
 | 
			
		||||
@@ -54,7 +53,7 @@ watch(
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
watch(
 | 
			
		||||
    () => props.procdefKey,
 | 
			
		||||
    () => props.procdef,
 | 
			
		||||
    async (newValue: any) => {
 | 
			
		||||
        if (newValue) {
 | 
			
		||||
            parseTasksByKey(newValue);
 | 
			
		||||
@@ -63,15 +62,14 @@ watch(
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
onMounted(() => {
 | 
			
		||||
    if (props.procdefKey) {
 | 
			
		||||
        parseTasksByKey(props.procdefKey);
 | 
			
		||||
    if (props.procdef) {
 | 
			
		||||
        parseTasksByKey(props.procdef);
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
    parseTasks(props.tasks);
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const parseTasksByKey = async (key: string) => {
 | 
			
		||||
    const procdef = await procdefApi.getByKey.request({ key });
 | 
			
		||||
const parseTasksByKey = async (procdef: any) => {
 | 
			
		||||
    parseTasks(procdef.tasks);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -29,6 +29,6 @@ export const ProcinstTaskStatus = {
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const FlowBizType = {
 | 
			
		||||
    DbSqlExec: EnumValue.of('db_sql_exec_flow', 'DBMS-执行SQL'),
 | 
			
		||||
    RedisRunWriteCmd: EnumValue.of('redis_run_write_cmd_flow', 'Redis-执行write命令'),
 | 
			
		||||
    DbSqlExec: EnumValue.of('db_sql_exec_flow', 'DBMS-执行SQL').setTagType('warning'),
 | 
			
		||||
    RedisRunWriteCmd: EnumValue.of('redis_run_cmd_flow', 'Redis-执行命令').setTagType('danger'),
 | 
			
		||||
};
 | 
			
		||||
 
 | 
			
		||||
@@ -1,79 +0,0 @@
 | 
			
		||||
<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>
 | 
			
		||||
@@ -1,80 +0,0 @@
 | 
			
		||||
<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>
 | 
			
		||||
							
								
								
									
										103
									
								
								mayfly_go_web/src/views/flow/flowbiz/dbms/DbSqlExecBiz.vue
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										103
									
								
								mayfly_go_web/src/views/flow/flowbiz/dbms/DbSqlExecBiz.vue
									
									
									
									
									
										Executable file
									
								
							@@ -0,0 +1,103 @@
 | 
			
		||||
<template>
 | 
			
		||||
    <div>
 | 
			
		||||
        <el-descriptions :column="3" border>
 | 
			
		||||
            <el-descriptions-item :span="3" label="标签"><TagCodePath :path="db.codePaths" /></el-descriptions-item>
 | 
			
		||||
 | 
			
		||||
            <el-descriptions-item :span="1" label="名称">{{ db?.name }}</el-descriptions-item>
 | 
			
		||||
            <el-descriptions-item :span="1" label="主机">
 | 
			
		||||
                <SvgIcon :name="getDbDialect(db?.type).getInfo().icon" :size="20" />
 | 
			
		||||
                {{ `${db?.host}:${db?.port}` }}
 | 
			
		||||
            </el-descriptions-item>
 | 
			
		||||
            <el-descriptions-item :span="1" label="数据库">{{ dbName }}</el-descriptions-item>
 | 
			
		||||
 | 
			
		||||
            <el-descriptions-item label="执行SQL">
 | 
			
		||||
                <monaco-editor height="300px" language="sql" v-model="sql" :options="{ readOnly: true }" />
 | 
			
		||||
            </el-descriptions-item>
 | 
			
		||||
        </el-descriptions>
 | 
			
		||||
 | 
			
		||||
        <div v-if="runRes && runRes.length > 0">
 | 
			
		||||
            <el-divider content-position="left">处理结果</el-divider>
 | 
			
		||||
            <el-table :data="runRes" :max-height="400">
 | 
			
		||||
                <el-table-column prop="sql" label="SQL" show-overflow-tooltip />
 | 
			
		||||
 | 
			
		||||
                <el-table-column prop="res" label="执行结果" :min-width="30" show-overflow-tooltip>
 | 
			
		||||
                    <template #default="scope">
 | 
			
		||||
                        <el-popover placement="top" :width="400" trigger="hover">
 | 
			
		||||
                            <template #reference>
 | 
			
		||||
                                <el-link icon="view" :type="scope.row.errorMsg ? 'danger' : 'success'" :underline="false"> </el-link>
 | 
			
		||||
                            </template>
 | 
			
		||||
 | 
			
		||||
                            <el-text v-if="scope.row.errorMsg">{{ scope.row.errorMsg }}</el-text>
 | 
			
		||||
                            <el-table v-else :data="scope.row.res" size="small">
 | 
			
		||||
                                <el-table-column v-for="col in scope.row.columns" :key="col.name" :label="col.name" :prop="col.name" />
 | 
			
		||||
                            </el-table>
 | 
			
		||||
                        </el-popover>
 | 
			
		||||
                    </template>
 | 
			
		||||
                </el-table-column>
 | 
			
		||||
 | 
			
		||||
                <!-- <el-table-column prop="errorMsg" label="错误信息" :min-width="60" show-overflow-tooltip /> -->
 | 
			
		||||
            </el-table>
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script lang="ts" setup>
 | 
			
		||||
import { toRefs, reactive, watch, onMounted } from 'vue';
 | 
			
		||||
import { dbApi } from '@/views/ops/db/api';
 | 
			
		||||
import MonacoEditor from '@/components/monaco/MonacoEditor.vue';
 | 
			
		||||
import { getDbDialect } from '@/views/ops/db/dialect';
 | 
			
		||||
import { tagApi } from '@/views/ops/tag/api';
 | 
			
		||||
import { TagResourceTypeEnum } from '@/common/commonEnum';
 | 
			
		||||
import TagCodePath from '@/views/ops/component/TagCodePath.vue';
 | 
			
		||||
import SvgIcon from '@/components/svgIcon/index.vue';
 | 
			
		||||
 | 
			
		||||
const props = defineProps({
 | 
			
		||||
    procinst: {
 | 
			
		||||
        type: [Object],
 | 
			
		||||
        default: () => {},
 | 
			
		||||
    },
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const state = reactive({
 | 
			
		||||
    // sqlExec: {
 | 
			
		||||
    //     sql: '',
 | 
			
		||||
    // } as any,
 | 
			
		||||
    db: {} as any,
 | 
			
		||||
    dbName: '',
 | 
			
		||||
    sql: '',
 | 
			
		||||
    runRes: [],
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const { db, dbName, sql, runRes } = toRefs(state);
 | 
			
		||||
 | 
			
		||||
onMounted(() => {
 | 
			
		||||
    parseBizForm(props.procinst.bizForm);
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
watch(
 | 
			
		||||
    () => props.procinst.bizForm,
 | 
			
		||||
    (newValue: any) => {
 | 
			
		||||
        parseBizForm(newValue);
 | 
			
		||||
    }
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
const parseBizForm = async (bizFormStr: string) => {
 | 
			
		||||
    if (props.procinst.bizHandleRes) {
 | 
			
		||||
        state.runRes = JSON.parse(props.procinst.bizHandleRes);
 | 
			
		||||
    } else {
 | 
			
		||||
        state.runRes = [];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const bizForm = JSON.parse(bizFormStr);
 | 
			
		||||
    state.sql = bizForm.sql;
 | 
			
		||||
    state.dbName = bizForm.dbName;
 | 
			
		||||
 | 
			
		||||
    const dbRes = await dbApi.dbs.request({ id: bizForm.dbId });
 | 
			
		||||
    state.db = dbRes.list?.[0];
 | 
			
		||||
 | 
			
		||||
    tagApi.listByQuery.request({ type: TagResourceTypeEnum.DbName.value, codes: state.db.code }).then((res) => {
 | 
			
		||||
        state.db.codePaths = res.map((item: any) => item.codePath);
 | 
			
		||||
    });
 | 
			
		||||
};
 | 
			
		||||
</script>
 | 
			
		||||
<style lang="scss"></style>
 | 
			
		||||
							
								
								
									
										83
									
								
								mayfly_go_web/src/views/flow/flowbiz/dbms/DbSqlExecFlowBizForm.vue
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										83
									
								
								mayfly_go_web/src/views/flow/flowbiz/dbms/DbSqlExecFlowBizForm.vue
									
									
									
									
									
										Executable file
									
								
							@@ -0,0 +1,83 @@
 | 
			
		||||
<template>
 | 
			
		||||
    <el-form :model="bizForm" ref="formRef" :rules="rules" label-width="auto">
 | 
			
		||||
        <el-form-item prop="dbId" label="数据库" required>
 | 
			
		||||
            <db-select-tree
 | 
			
		||||
                placeholder="请选择数据库"
 | 
			
		||||
                v-model:db-id="bizForm.dbId"
 | 
			
		||||
                v-model:db-name="bizForm.dbName"
 | 
			
		||||
                v-model:db-type="dbType"
 | 
			
		||||
                @select-db="changeResourceCode"
 | 
			
		||||
            />
 | 
			
		||||
        </el-form-item>
 | 
			
		||||
 | 
			
		||||
        <el-form-item prop="sql" label="SQL" required>
 | 
			
		||||
            <div class="w100">
 | 
			
		||||
                <monaco-editor height="300px" language="sql" v-model="bizForm.sql" />
 | 
			
		||||
            </div>
 | 
			
		||||
        </el-form-item>
 | 
			
		||||
    </el-form>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script lang="ts" setup>
 | 
			
		||||
import { ref, watch } from 'vue';
 | 
			
		||||
import DbSelectTree from '@/views/ops/db/component/DbSelectTree.vue';
 | 
			
		||||
import MonacoEditor from '@/components/monaco/MonacoEditor.vue';
 | 
			
		||||
import { registerDbCompletionItemProvider } from '@/views/ops/db/db';
 | 
			
		||||
import { TagResourceTypeEnum } from '@/common/commonEnum';
 | 
			
		||||
 | 
			
		||||
const rules = {
 | 
			
		||||
    dbId: [
 | 
			
		||||
        {
 | 
			
		||||
            required: true,
 | 
			
		||||
            message: '请选择数据库',
 | 
			
		||||
            trigger: ['change', 'blur'],
 | 
			
		||||
        },
 | 
			
		||||
    ],
 | 
			
		||||
    sql: [
 | 
			
		||||
        {
 | 
			
		||||
            required: true,
 | 
			
		||||
            message: '请输入执行SQL',
 | 
			
		||||
            trigger: ['change', 'blur'],
 | 
			
		||||
        },
 | 
			
		||||
    ],
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const emit = defineEmits(['changeResourceCode']);
 | 
			
		||||
 | 
			
		||||
const formRef: any = ref(null);
 | 
			
		||||
 | 
			
		||||
const bizForm = defineModel<any>('bizForm', {
 | 
			
		||||
    default: {
 | 
			
		||||
        dbId: 0,
 | 
			
		||||
        dbName: '',
 | 
			
		||||
        sql: '',
 | 
			
		||||
    },
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const dbType = ref('');
 | 
			
		||||
 | 
			
		||||
watch(
 | 
			
		||||
    () => bizForm.value.dbId,
 | 
			
		||||
    () => {
 | 
			
		||||
        registerDbCompletionItemProvider(bizForm.value.dbId, bizForm.value.dbName, [bizForm.value.dbName], dbType.value);
 | 
			
		||||
    }
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
const changeResourceCode = async (db: any) => {
 | 
			
		||||
    emit('changeResourceCode', TagResourceTypeEnum.Db.value, db.code);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const validateBizForm = async () => {
 | 
			
		||||
    return formRef.value.validate();
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const resetBizForm = () => {
 | 
			
		||||
    //重置表单域
 | 
			
		||||
    formRef.value.resetFields();
 | 
			
		||||
    bizForm.value.dbId = 0;
 | 
			
		||||
    bizForm.value.dbName = '';
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
defineExpose({ validateBizForm, resetBizForm });
 | 
			
		||||
</script>
 | 
			
		||||
<style lang="scss"></style>
 | 
			
		||||
							
								
								
									
										89
									
								
								mayfly_go_web/src/views/flow/flowbiz/redis/RedisRunCmdBiz.vue
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										89
									
								
								mayfly_go_web/src/views/flow/flowbiz/redis/RedisRunCmdBiz.vue
									
									
									
									
									
										Executable file
									
								
							@@ -0,0 +1,89 @@
 | 
			
		||||
<template>
 | 
			
		||||
    <div>
 | 
			
		||||
        <el-descriptions :column="3" border>
 | 
			
		||||
            <el-descriptions-item :span="3" label="标签"><TagCodePath :path="redis.codePaths" /></el-descriptions-item>
 | 
			
		||||
 | 
			
		||||
            <el-descriptions-item :span="2" label="编号">{{ redis?.code }}</el-descriptions-item>
 | 
			
		||||
            <el-descriptions-item :span="1" label="名称">{{ redis?.name }}</el-descriptions-item>
 | 
			
		||||
 | 
			
		||||
            <el-descriptions-item :span="1" label="主机">{{ `${redis?.host}` }}</el-descriptions-item>
 | 
			
		||||
            <el-descriptions-item :span="1" label="库">{{ state.db }}</el-descriptions-item>
 | 
			
		||||
            <el-descriptions-item :span="1" label="mode">
 | 
			
		||||
                {{ redis.mode }}
 | 
			
		||||
            </el-descriptions-item>
 | 
			
		||||
 | 
			
		||||
            <el-descriptions-item :span="3" label="执行Cmd">
 | 
			
		||||
                <el-input type="textarea" disabled v-model="cmd" rows="5" />
 | 
			
		||||
            </el-descriptions-item>
 | 
			
		||||
        </el-descriptions>
 | 
			
		||||
 | 
			
		||||
        <div v-if="runRes && runRes.length > 0">
 | 
			
		||||
            <el-divider content-position="left">处理结果</el-divider>
 | 
			
		||||
            <el-table :data="runRes" :max-height="400">
 | 
			
		||||
                <el-table-column prop="cmd" label="命令" show-overflow-tooltip />
 | 
			
		||||
                <el-table-column prop="res" label="执行结果" :min-width="50" show-overflow-tooltip> </el-table-column>
 | 
			
		||||
            </el-table>
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script lang="ts" setup>
 | 
			
		||||
import { toRefs, reactive, watch, onMounted } from 'vue';
 | 
			
		||||
import { redisApi } from '@/views/ops/redis/api';
 | 
			
		||||
import TagCodePath from '@/views/ops/component/TagCodePath.vue';
 | 
			
		||||
import { tagApi } from '@/views/ops/tag/api';
 | 
			
		||||
import { TagResourceTypeEnum } from '@/common/commonEnum';
 | 
			
		||||
 | 
			
		||||
const props = defineProps({
 | 
			
		||||
    procinst: {
 | 
			
		||||
        type: [Object],
 | 
			
		||||
        default: () => {},
 | 
			
		||||
    },
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const state = reactive({
 | 
			
		||||
    cmd: '',
 | 
			
		||||
    runRes: [],
 | 
			
		||||
    db: 0,
 | 
			
		||||
    redis: {} as any,
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const { cmd, redis, runRes } = toRefs(state);
 | 
			
		||||
 | 
			
		||||
onMounted(() => {
 | 
			
		||||
    parseRunCmdForm(props.procinst.bizForm);
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
watch(
 | 
			
		||||
    () => props.procinst.bizForm,
 | 
			
		||||
    (newValue: any) => {
 | 
			
		||||
        parseRunCmdForm(newValue);
 | 
			
		||||
    }
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
const parseRunCmdForm = async (bizFormStr: string) => {
 | 
			
		||||
    if (props.procinst.bizHandleRes) {
 | 
			
		||||
        state.runRes = JSON.parse(props.procinst.bizHandleRes);
 | 
			
		||||
    } else {
 | 
			
		||||
        state.runRes = [];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (!bizFormStr) {
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
    const bizForm = JSON.parse(bizFormStr);
 | 
			
		||||
    state.cmd = bizForm.cmd;
 | 
			
		||||
    state.db = bizForm.db;
 | 
			
		||||
 | 
			
		||||
    const res = await redisApi.redisList.request({ id: bizForm.id });
 | 
			
		||||
    if (!res.list) {
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
    state.redis = res.list?.[0];
 | 
			
		||||
 | 
			
		||||
    tagApi.listByQuery.request({ type: TagResourceTypeEnum.Redis.value, codes: state.redis.code }).then((res) => {
 | 
			
		||||
        state.redis.codePaths = res.map((item: any) => item.codePath);
 | 
			
		||||
    });
 | 
			
		||||
};
 | 
			
		||||
</script>
 | 
			
		||||
<style lang="scss"></style>
 | 
			
		||||
							
								
								
									
										148
									
								
								mayfly_go_web/src/views/flow/flowbiz/redis/RedisRunCmdFlowBizForm.vue
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										148
									
								
								mayfly_go_web/src/views/flow/flowbiz/redis/RedisRunCmdFlowBizForm.vue
									
									
									
									
									
										Executable file
									
								
							@@ -0,0 +1,148 @@
 | 
			
		||||
<template>
 | 
			
		||||
    <el-form :model="bizForm" ref="formRef" :rules="rules" label-width="auto">
 | 
			
		||||
        <el-form-item prop="id" label="库" required>
 | 
			
		||||
            <TagTreeResourceSelect
 | 
			
		||||
                v-bind="$attrs"
 | 
			
		||||
                v-model="selectRedis"
 | 
			
		||||
                @change="changeRedis"
 | 
			
		||||
                :resource-type="TagResourceTypeEnum.Redis.value"
 | 
			
		||||
                :tag-path-node-type="NodeTypeTagPath"
 | 
			
		||||
                placeholder="请选择Redis实例与库"
 | 
			
		||||
            >
 | 
			
		||||
            </TagTreeResourceSelect>
 | 
			
		||||
        </el-form-item>
 | 
			
		||||
 | 
			
		||||
        <el-form-item prop="cmd" label="CMD" required>
 | 
			
		||||
            <el-input type="textarea" v-model="bizForm.cmd" placeholder="如: SET 'key' 'value'; 多条命令;分割" :rows="5" />
 | 
			
		||||
        </el-form-item>
 | 
			
		||||
    </el-form>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script lang="ts" setup>
 | 
			
		||||
import { computed, ref } from 'vue';
 | 
			
		||||
import { TagResourceTypeEnum } from '@/common/commonEnum';
 | 
			
		||||
import TagTreeResourceSelect from '@/views/ops/component/TagTreeResourceSelect.vue';
 | 
			
		||||
import { NodeType, TagTreeNode } from '@/views/ops/component/tag';
 | 
			
		||||
import { redisApi } from '@/views/ops/redis/api';
 | 
			
		||||
import { sleep } from '@/common/utils/loading';
 | 
			
		||||
 | 
			
		||||
const rules = {
 | 
			
		||||
    id: [
 | 
			
		||||
        {
 | 
			
		||||
            required: true,
 | 
			
		||||
            message: '请选择Redis实例',
 | 
			
		||||
            trigger: ['change', 'blur'],
 | 
			
		||||
        },
 | 
			
		||||
    ],
 | 
			
		||||
    cmd: [
 | 
			
		||||
        {
 | 
			
		||||
            required: true,
 | 
			
		||||
            message: '请输入执行CMD',
 | 
			
		||||
            trigger: ['change', 'blur'],
 | 
			
		||||
        },
 | 
			
		||||
    ],
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// tagpath 节点类型
 | 
			
		||||
const NodeTypeTagPath = new NodeType(TagTreeNode.TagPath).withLoadNodesFunc(async (parentNode: TagTreeNode) => {
 | 
			
		||||
    const res = await redisApi.redisList.request({ tagPath: parentNode.key });
 | 
			
		||||
    if (!res.total) {
 | 
			
		||||
        return [];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const redisInfos = res.list;
 | 
			
		||||
    await sleep(100);
 | 
			
		||||
    return redisInfos.map((x: any) => {
 | 
			
		||||
        x.tagPath = parentNode.key;
 | 
			
		||||
        return new TagTreeNode(`${x.code}`, x.name, NodeTypeRedis).withParams(x);
 | 
			
		||||
    });
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
// redis实例节点类型
 | 
			
		||||
const NodeTypeRedis = new NodeType(1).withLoadNodesFunc(async (parentNode: TagTreeNode) => {
 | 
			
		||||
    const redisInfo = parentNode.params;
 | 
			
		||||
 | 
			
		||||
    let dbs: TagTreeNode[] = redisInfo.db.split(',').map((x: string) => {
 | 
			
		||||
        return new TagTreeNode(x, `db${x}`, 2 as any).withIsLeaf(true).withParams({
 | 
			
		||||
            id: redisInfo.id,
 | 
			
		||||
            db: x,
 | 
			
		||||
            name: `db${x}`,
 | 
			
		||||
            keys: 0,
 | 
			
		||||
            tagPath: redisInfo.tagPath,
 | 
			
		||||
            redisName: redisInfo.name,
 | 
			
		||||
            code: redisInfo.code,
 | 
			
		||||
        });
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    if (redisInfo.mode == 'cluster') {
 | 
			
		||||
        return dbs;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const res = await redisApi.redisInfo.request({ id: redisInfo.id, host: redisInfo.host, section: 'Keyspace' });
 | 
			
		||||
    for (let db in res.Keyspace) {
 | 
			
		||||
        for (let d of dbs) {
 | 
			
		||||
            if (db == d.params.name) {
 | 
			
		||||
                d.params.keys = res.Keyspace[db]?.split(',')[0]?.split('=')[1] || 0;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    // 替换label
 | 
			
		||||
    dbs.forEach((e: any) => {
 | 
			
		||||
        e.label = `${e.params.name}`;
 | 
			
		||||
    });
 | 
			
		||||
    return dbs;
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const emit = defineEmits(['changeResourceCode']);
 | 
			
		||||
 | 
			
		||||
const formRef: any = ref(null);
 | 
			
		||||
 | 
			
		||||
const bizForm = defineModel<any>('bizForm', {
 | 
			
		||||
    default: {
 | 
			
		||||
        id: 0,
 | 
			
		||||
        db: 0,
 | 
			
		||||
        cmd: '',
 | 
			
		||||
    },
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const redisName = ref('');
 | 
			
		||||
const tagPath = ref('');
 | 
			
		||||
 | 
			
		||||
const selectRedis = computed({
 | 
			
		||||
    get: () => {
 | 
			
		||||
        return redisName.value ? `${tagPath.value} > ${redisName.value} > db${bizForm.value.db}` : '';
 | 
			
		||||
    },
 | 
			
		||||
    set: () => {
 | 
			
		||||
        //
 | 
			
		||||
    },
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const changeRedis = (nodeData: TagTreeNode) => {
 | 
			
		||||
    const params = nodeData.params;
 | 
			
		||||
    tagPath.value = params.tagPath;
 | 
			
		||||
    redisName.value = params.redisName;
 | 
			
		||||
    bizForm.value.id = params.id;
 | 
			
		||||
    bizForm.value.db = parseInt(params.db);
 | 
			
		||||
 | 
			
		||||
    changeResourceCode(params.code);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const changeResourceCode = async (redisCode: any) => {
 | 
			
		||||
    emit('changeResourceCode', TagResourceTypeEnum.Redis.value, redisCode);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const validateBizForm = async () => {
 | 
			
		||||
    return formRef.value.validate();
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const resetBizForm = () => {
 | 
			
		||||
    //重置表单域
 | 
			
		||||
    formRef.value.resetFields();
 | 
			
		||||
    bizForm.value.id = 0;
 | 
			
		||||
    bizForm.value.db = 0;
 | 
			
		||||
    bizForm.value.cmd = '';
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
defineExpose({ validateBizForm, resetBizForm });
 | 
			
		||||
</script>
 | 
			
		||||
<style lang="scss"></style>
 | 
			
		||||
@@ -6,7 +6,15 @@
 | 
			
		||||
                <el-card shadow="hover" header="个人信息">
 | 
			
		||||
                    <div class="personal-user">
 | 
			
		||||
                        <div class="personal-user-left">
 | 
			
		||||
                            <el-upload class="h100 personal-user-left-upload" action="" multiple :limit="1">
 | 
			
		||||
                            <el-upload
 | 
			
		||||
                                class="h100 personal-user-left-upload"
 | 
			
		||||
                                :action="getUploadFileUrl(`avatar_${userInfo.username}`)"
 | 
			
		||||
                                :limit="1"
 | 
			
		||||
                                :show-file-list="false"
 | 
			
		||||
                                :before-upload="beforeAvatarUpload"
 | 
			
		||||
                                :on-success="handleAvatarSuccess"
 | 
			
		||||
                                accept=".png,.jpg,.jpeg"
 | 
			
		||||
                            >
 | 
			
		||||
                                <img :src="userInfo.photo" />
 | 
			
		||||
                            </el-upload>
 | 
			
		||||
                        </div>
 | 
			
		||||
@@ -35,7 +43,7 @@
 | 
			
		||||
                                        </el-col>
 | 
			
		||||
                                        <el-col :xs="24" :sm="12" class="personal-item mb6">
 | 
			
		||||
                                            <div class="personal-item-label">上次登录时间:</div>
 | 
			
		||||
                                            <div class="personal-item-value">{{ dateFormat(userInfo.lastLoginTime) }}</div>
 | 
			
		||||
                                            <div class="personal-item-value">{{ formatDate(userInfo.lastLoginTime) }}</div>
 | 
			
		||||
                                        </el-col>
 | 
			
		||||
                                    </el-row>
 | 
			
		||||
                                </el-col>
 | 
			
		||||
@@ -84,12 +92,12 @@
 | 
			
		||||
                            <el-table :data="state.machine.opLogs" :height="state.resourceOpTableHeight" stripe size="small" empty-text="暂无操作记录">
 | 
			
		||||
                                <el-table-column prop="createTime" show-overflow-tooltip width="135">
 | 
			
		||||
                                    <template #default="scope">
 | 
			
		||||
                                        {{ dateFormat(scope.row.createTime) }}
 | 
			
		||||
                                        {{ formatDate(scope.row.createTime) }}
 | 
			
		||||
                                    </template>
 | 
			
		||||
                                </el-table-column>
 | 
			
		||||
                                <el-table-column prop="codePath" min-width="400" show-overflow-tooltip>
 | 
			
		||||
                                    <template #default="scope">
 | 
			
		||||
                                        <TagCodePath :path="scope.row.codePath" />
 | 
			
		||||
                                        <TagCodePath :path="scope.row.codePath" :tagInfos="state.machine.tagInfos" />
 | 
			
		||||
                                    </template>
 | 
			
		||||
                                </el-table-column>
 | 
			
		||||
                                <el-table-column width="30">
 | 
			
		||||
@@ -118,12 +126,12 @@
 | 
			
		||||
                            <el-table :data="state.db.opLogs" :height="state.resourceOpTableHeight" stripe size="small" empty-text="暂无操作记录">
 | 
			
		||||
                                <el-table-column prop="createTime" show-overflow-tooltip min-width="135">
 | 
			
		||||
                                    <template #default="scope">
 | 
			
		||||
                                        {{ dateFormat(scope.row.createTime) }}
 | 
			
		||||
                                        {{ formatDate(scope.row.createTime) }}
 | 
			
		||||
                                    </template>
 | 
			
		||||
                                </el-table-column>
 | 
			
		||||
                                <el-table-column prop="codePath" min-width="380" show-overflow-tooltip>
 | 
			
		||||
                                    <template #default="scope">
 | 
			
		||||
                                        <TagCodePath :path="scope.row.codePath" />
 | 
			
		||||
                                        <TagCodePath :path="scope.row.codePath" :tagInfos="state.db.tagInfos" />
 | 
			
		||||
                                    </template>
 | 
			
		||||
                                </el-table-column>
 | 
			
		||||
                                <el-table-column width="30">
 | 
			
		||||
@@ -159,12 +167,12 @@
 | 
			
		||||
                            <el-table :data="state.redis.opLogs" :height="state.resourceOpTableHeight" stripe size="small" empty-text="暂无操作记录">
 | 
			
		||||
                                <el-table-column prop="createTime" show-overflow-tooltip min-width="135">
 | 
			
		||||
                                    <template #default="scope">
 | 
			
		||||
                                        {{ dateFormat(scope.row.createTime) }}
 | 
			
		||||
                                        {{ formatDate(scope.row.createTime) }}
 | 
			
		||||
                                    </template>
 | 
			
		||||
                                </el-table-column>
 | 
			
		||||
                                <el-table-column prop="codePath" min-width="380" show-overflow-tooltip>
 | 
			
		||||
                                    <template #default="scope">
 | 
			
		||||
                                        <TagCodePath :path="scope.row.codePath" />
 | 
			
		||||
                                        <TagCodePath :path="scope.row.codePath" :tagInfos="state.redis.tagInfos" />
 | 
			
		||||
                                    </template>
 | 
			
		||||
                                </el-table-column>
 | 
			
		||||
                                <el-table-column width="30">
 | 
			
		||||
@@ -198,12 +206,12 @@
 | 
			
		||||
                            <el-table :data="state.mongo.opLogs" :height="state.resourceOpTableHeight" stripe size="small" empty-text="暂无操作记录">
 | 
			
		||||
                                <el-table-column prop="createTime" show-overflow-tooltip min-width="135">
 | 
			
		||||
                                    <template #default="scope">
 | 
			
		||||
                                        {{ dateFormat(scope.row.createTime) }}
 | 
			
		||||
                                        {{ formatDate(scope.row.createTime) }}
 | 
			
		||||
                                    </template>
 | 
			
		||||
                                </el-table-column>
 | 
			
		||||
                                <el-table-column prop="codePath" min-width="380" show-overflow-tooltip>
 | 
			
		||||
                                    <template #default="scope">
 | 
			
		||||
                                        <TagCodePath :path="scope.row.codePath" />
 | 
			
		||||
                                        <TagCodePath :path="scope.row.codePath" :tagInfos="state.mongo.tagInfos" />
 | 
			
		||||
                                    </template>
 | 
			
		||||
                                </el-table-column>
 | 
			
		||||
                                <el-table-column width="30">
 | 
			
		||||
@@ -228,7 +236,7 @@
 | 
			
		||||
                <el-table-column property="msg" label="消息"></el-table-column>
 | 
			
		||||
                <el-table-column property="createTime" label="时间" width="150">
 | 
			
		||||
                    <template #default="scope">
 | 
			
		||||
                        {{ dateFormat(scope.row.createTime) }}
 | 
			
		||||
                        {{ formatDate(scope.row.createTime) }}
 | 
			
		||||
                    </template>
 | 
			
		||||
                </el-table-column>
 | 
			
		||||
            </el-table>
 | 
			
		||||
@@ -249,20 +257,23 @@
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script lang="ts" setup>
 | 
			
		||||
import { toRefs, reactive, onMounted, computed } from 'vue';
 | 
			
		||||
import { computed, onMounted, reactive, toRefs } from 'vue';
 | 
			
		||||
// import * as echarts from 'echarts';
 | 
			
		||||
import { formatAxis } from '@/common/utils/format';
 | 
			
		||||
import { formatAxis, formatDate } from '@/common/utils/format';
 | 
			
		||||
import { indexApi } from './api';
 | 
			
		||||
import { useRouter } from 'vue-router';
 | 
			
		||||
import { storeToRefs } from 'pinia';
 | 
			
		||||
import { useUserInfo } from '@/store/userInfo';
 | 
			
		||||
import { personApi } from '../personal/api';
 | 
			
		||||
import { dateFormat } from '@/common/utils/date';
 | 
			
		||||
import SvgIcon from '@/components/svgIcon/index.vue';
 | 
			
		||||
import { TagResourceTypeEnum } from '@/common/commonEnum';
 | 
			
		||||
import { resourceOpLogApi } from '../ops/tag/api';
 | 
			
		||||
import TagCodePath from '../ops/component/TagCodePath.vue';
 | 
			
		||||
import { useAutoOpenResource } from '@/store/autoOpenResource';
 | 
			
		||||
import { getAllTagInfoByCodePaths } from '../ops/component/tag';
 | 
			
		||||
import { ElMessage } from 'element-plus';
 | 
			
		||||
import { getFileUrl, getUploadFileUrl } from '@/common/request';
 | 
			
		||||
import { saveUser } from '@/common/utils/storage';
 | 
			
		||||
 | 
			
		||||
const router = useRouter();
 | 
			
		||||
const { userInfo } = storeToRefs(useUserInfo());
 | 
			
		||||
@@ -288,18 +299,22 @@ const state = reactive({
 | 
			
		||||
    machine: {
 | 
			
		||||
        num: 0,
 | 
			
		||||
        opLogs: [],
 | 
			
		||||
        tagInfos: {},
 | 
			
		||||
    },
 | 
			
		||||
    db: {
 | 
			
		||||
        num: 0,
 | 
			
		||||
        opLogs: [],
 | 
			
		||||
        tagInfos: {},
 | 
			
		||||
    },
 | 
			
		||||
    redis: {
 | 
			
		||||
        num: 0,
 | 
			
		||||
        opLogs: [],
 | 
			
		||||
        tagInfos: {},
 | 
			
		||||
    },
 | 
			
		||||
    mongo: {
 | 
			
		||||
        num: 0,
 | 
			
		||||
        opLogs: [],
 | 
			
		||||
        tagInfos: {},
 | 
			
		||||
    },
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
@@ -354,25 +369,56 @@ const getMsgs = async () => {
 | 
			
		||||
    return await personApi.getMsgs.request(state.msgDialog.query);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const beforeAvatarUpload = (rawFile: any) => {
 | 
			
		||||
    if (rawFile.size >= 512 * 1024) {
 | 
			
		||||
        ElMessage.error('头像不能超过512KB!');
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
    return true;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const handleAvatarSuccess = (response: any, uploadFile: any) => {
 | 
			
		||||
    userInfo.value.photo = URL.createObjectURL(uploadFile.raw);
 | 
			
		||||
 | 
			
		||||
    const newUser = { ...userInfo.value };
 | 
			
		||||
    newUser.photo = getFileUrl(`avatar_${userInfo.value.username}`);
 | 
			
		||||
    // 存储用户信息到浏览器缓存
 | 
			
		||||
    saveUser(newUser);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// 初始化数字滚动
 | 
			
		||||
const initData = async () => {
 | 
			
		||||
    resourceOpLogApi.getAccountResourceOpLogs
 | 
			
		||||
        .request({ resourceType: TagResourceTypeEnum.MachineAuthCert.value, pageSize: state.defaultLogSize })
 | 
			
		||||
        .then((res: any) => {
 | 
			
		||||
        .then(async (res: any) => {
 | 
			
		||||
            const tagInfos = await getAllTagInfoByCodePaths(res.list?.map((item: any) => item.codePath));
 | 
			
		||||
            state.machine.tagInfos = tagInfos;
 | 
			
		||||
            state.machine.opLogs = res.list;
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
    resourceOpLogApi.getAccountResourceOpLogs.request({ resourceType: TagResourceTypeEnum.DbName.value, pageSize: state.defaultLogSize }).then((res: any) => {
 | 
			
		||||
        state.db.opLogs = res.list;
 | 
			
		||||
    });
 | 
			
		||||
    resourceOpLogApi.getAccountResourceOpLogs
 | 
			
		||||
        .request({ resourceType: TagResourceTypeEnum.DbName.value, pageSize: state.defaultLogSize })
 | 
			
		||||
        .then(async (res: any) => {
 | 
			
		||||
            const tagInfos = await getAllTagInfoByCodePaths(res.list?.map((item: any) => item.codePath));
 | 
			
		||||
            state.db.tagInfos = tagInfos;
 | 
			
		||||
            state.db.opLogs = res.list;
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
    resourceOpLogApi.getAccountResourceOpLogs.request({ resourceType: TagResourceTypeEnum.Redis.value, pageSize: state.defaultLogSize }).then((res: any) => {
 | 
			
		||||
        state.redis.opLogs = res.list;
 | 
			
		||||
    });
 | 
			
		||||
    resourceOpLogApi.getAccountResourceOpLogs
 | 
			
		||||
        .request({ resourceType: TagResourceTypeEnum.Redis.value, pageSize: state.defaultLogSize })
 | 
			
		||||
        .then(async (res: any) => {
 | 
			
		||||
            const tagInfos = await getAllTagInfoByCodePaths(res.list?.map((item: any) => item.codePath));
 | 
			
		||||
            state.redis.tagInfos = tagInfos;
 | 
			
		||||
            state.redis.opLogs = res.list;
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
    resourceOpLogApi.getAccountResourceOpLogs.request({ resourceType: TagResourceTypeEnum.Mongo.value, pageSize: state.defaultLogSize }).then((res: any) => {
 | 
			
		||||
        state.mongo.opLogs = res.list;
 | 
			
		||||
    });
 | 
			
		||||
    resourceOpLogApi.getAccountResourceOpLogs
 | 
			
		||||
        .request({ resourceType: TagResourceTypeEnum.Mongo.value, pageSize: state.defaultLogSize })
 | 
			
		||||
        .then(async (res: any) => {
 | 
			
		||||
            const tagInfos = await getAllTagInfoByCodePaths(res.list?.map((item: any) => item.codePath));
 | 
			
		||||
            state.mongo.tagInfos = tagInfos;
 | 
			
		||||
            state.mongo.opLogs = res.list;
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
    indexApi.machineDashbord.request().then((res: any) => {
 | 
			
		||||
        state.machine.num = res.machineNum;
 | 
			
		||||
@@ -425,7 +471,7 @@ const toPage = (item: any, codePath = '') => {
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style scoped lang="scss">
 | 
			
		||||
@import '@/theme/mixins/index.scss';
 | 
			
		||||
@use '@/theme/mixins/index.scss' as mixins;
 | 
			
		||||
 | 
			
		||||
.personal {
 | 
			
		||||
    .personal-user {
 | 
			
		||||
@@ -463,7 +509,7 @@ const toPage = (item: any, codePath = '') => {
 | 
			
		||||
 | 
			
		||||
            .personal-title {
 | 
			
		||||
                font-size: 18px;
 | 
			
		||||
                @include text-ellipsis(1);
 | 
			
		||||
                @include mixins.text-ellipsis(1);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            .personal-item {
 | 
			
		||||
@@ -473,11 +519,11 @@ const toPage = (item: any, codePath = '') => {
 | 
			
		||||
 | 
			
		||||
                .personal-item-label {
 | 
			
		||||
                    color: gray;
 | 
			
		||||
                    @include text-ellipsis(1);
 | 
			
		||||
                    @include mixins.text-ellipsis(1);
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                .personal-item-value {
 | 
			
		||||
                    @include text-ellipsis(1);
 | 
			
		||||
                    @include mixins.text-ellipsis(1);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
@@ -508,7 +554,7 @@ const toPage = (item: any, codePath = '') => {
 | 
			
		||||
 | 
			
		||||
                    .personal-info-li-title {
 | 
			
		||||
                        display: inline-block;
 | 
			
		||||
                        @include text-ellipsis(1);
 | 
			
		||||
                        @include mixins.text-ellipsis(1);
 | 
			
		||||
                        color: grey;
 | 
			
		||||
                        text-decoration: none;
 | 
			
		||||
                    }
 | 
			
		||||
 
 | 
			
		||||
@@ -132,7 +132,7 @@ import { nextTick, onMounted, ref, toRefs, reactive, computed } from 'vue';
 | 
			
		||||
import { useRoute, useRouter } from 'vue-router';
 | 
			
		||||
import { ElMessage } from 'element-plus';
 | 
			
		||||
import { initRouter } from '@/router/index';
 | 
			
		||||
import { saveToken, saveUser } from '@/common/utils/storage';
 | 
			
		||||
import { getRefreshToken, saveRefreshToken, saveToken, saveUser } from '@/common/utils/storage';
 | 
			
		||||
import { formatAxis } from '@/common/utils/format';
 | 
			
		||||
import openApi from '@/common/openApi';
 | 
			
		||||
import { RsaEncrypt } from '@/common/rsa';
 | 
			
		||||
@@ -144,6 +144,7 @@ import { personApi } from '@/views/personal/api';
 | 
			
		||||
import { AccountUsernamePattern } from '@/common/pattern';
 | 
			
		||||
import { getToken } from '@/common/utils/storage';
 | 
			
		||||
import { useThemeConfig } from '@/store/themeConfig';
 | 
			
		||||
import { getFileUrl } from '@/common/request';
 | 
			
		||||
 | 
			
		||||
const rules = {
 | 
			
		||||
    username: [{ required: true, message: '请输入用户名', trigger: 'blur' }],
 | 
			
		||||
@@ -279,19 +280,20 @@ const login = () => {
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const otpVerify = async () => {
 | 
			
		||||
    otpFormRef.value.validate(async (valid: boolean) => {
 | 
			
		||||
        if (!valid) {
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
        try {
 | 
			
		||||
            state.loading.otpConfirm = true;
 | 
			
		||||
            const accessToken = await openApi.otpVerify(state.otpDialog.form);
 | 
			
		||||
            await signInSuccess(accessToken);
 | 
			
		||||
            state.otpDialog.visible = false;
 | 
			
		||||
        } finally {
 | 
			
		||||
            state.loading.otpConfirm = false;
 | 
			
		||||
        }
 | 
			
		||||
    });
 | 
			
		||||
    try {
 | 
			
		||||
        await otpFormRef.value.validate();
 | 
			
		||||
    } catch (e: any) {
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    try {
 | 
			
		||||
        state.loading.otpConfirm = true;
 | 
			
		||||
        const res = await openApi.otpVerify(state.otpDialog.form);
 | 
			
		||||
        await signInSuccess(res.token, res.refresh_token);
 | 
			
		||||
        state.otpDialog.visible = false;
 | 
			
		||||
    } finally {
 | 
			
		||||
        state.loading.otpConfirm = false;
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// 登录
 | 
			
		||||
@@ -327,46 +329,55 @@ const onSignIn = async () => {
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const updateUserInfo = async () => {
 | 
			
		||||
    baseInfoFormRef.value.validate(async (valid: boolean) => {
 | 
			
		||||
        if (!valid) {
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
        try {
 | 
			
		||||
            state.loading.updateUserConfirm = true;
 | 
			
		||||
            const form = state.baseInfoDialog.form;
 | 
			
		||||
            await personApi.updateAccount.request(state.baseInfoDialog.form);
 | 
			
		||||
            state.baseInfoDialog.visible = false;
 | 
			
		||||
            useUserInfo().userInfo.username = form.username;
 | 
			
		||||
            useUserInfo().userInfo.name = form.name;
 | 
			
		||||
            await toIndex();
 | 
			
		||||
        } finally {
 | 
			
		||||
            state.loading.updateUserConfirm = false;
 | 
			
		||||
        }
 | 
			
		||||
    });
 | 
			
		||||
    try {
 | 
			
		||||
        await baseInfoFormRef.value.validate();
 | 
			
		||||
    } catch (e: any) {
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    try {
 | 
			
		||||
        state.loading.updateUserConfirm = true;
 | 
			
		||||
        const form = state.baseInfoDialog.form;
 | 
			
		||||
        await personApi.updateAccount.request(state.baseInfoDialog.form);
 | 
			
		||||
        state.baseInfoDialog.visible = false;
 | 
			
		||||
        useUserInfo().userInfo.username = form.username;
 | 
			
		||||
        useUserInfo().userInfo.name = form.name;
 | 
			
		||||
        await toIndex();
 | 
			
		||||
    } finally {
 | 
			
		||||
        state.loading.updateUserConfirm = false;
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const loginResDeal = (loginRes: any) => {
 | 
			
		||||
const loginResDeal = async (loginRes: any) => {
 | 
			
		||||
    state.loginRes = loginRes;
 | 
			
		||||
    // 用户信息
 | 
			
		||||
    const userInfos = {
 | 
			
		||||
        name: loginRes.name,
 | 
			
		||||
        username: loginRes.username,
 | 
			
		||||
        // 头像
 | 
			
		||||
        photo: letterAvatar(loginRes.username),
 | 
			
		||||
        time: new Date().getTime(),
 | 
			
		||||
        lastLoginTime: loginRes.lastLoginTime,
 | 
			
		||||
        lastLoginIp: loginRes.lastLoginIp,
 | 
			
		||||
        photo: '',
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    const avatarFileKey = `avatar_${loginRes.username}`;
 | 
			
		||||
    const avatarFileDetail = await openApi.getFileDetail([avatarFileKey]);
 | 
			
		||||
    // 说明存在头像文件
 | 
			
		||||
    if (avatarFileDetail.length > 0) {
 | 
			
		||||
        userInfos.photo = getFileUrl(avatarFileKey);
 | 
			
		||||
    } else {
 | 
			
		||||
        userInfos.photo = letterAvatar(loginRes.username);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // 存储用户信息到浏览器缓存
 | 
			
		||||
    saveUser(userInfos);
 | 
			
		||||
    // 1、请注意执行顺序(存储用户信息到vuex)
 | 
			
		||||
    useUserInfo().setUserInfo(userInfos);
 | 
			
		||||
 | 
			
		||||
    const token = loginRes.token;
 | 
			
		||||
    // 如果不需要    otp校验,则该token即为accessToken,否则为otp校验token
 | 
			
		||||
    // 如果不需要otp校验,则该token即为accessToken,否则为otp校验token
 | 
			
		||||
    if (loginRes.otp == -1) {
 | 
			
		||||
        signInSuccess(token);
 | 
			
		||||
        signInSuccess(token, loginRes.refresh_token);
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -379,12 +390,16 @@ const loginResDeal = (loginRes: any) => {
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// 登录成功后的跳转
 | 
			
		||||
const signInSuccess = async (accessToken: string = '') => {
 | 
			
		||||
const signInSuccess = async (accessToken: string = '', refreshToken = '') => {
 | 
			
		||||
    if (!accessToken) {
 | 
			
		||||
        accessToken = getToken();
 | 
			
		||||
    }
 | 
			
		||||
    if (!refreshToken) {
 | 
			
		||||
        refreshToken = getRefreshToken();
 | 
			
		||||
    }
 | 
			
		||||
    // 存储 token 到浏览器缓存
 | 
			
		||||
    saveToken(accessToken);
 | 
			
		||||
    saveRefreshToken(refreshToken);
 | 
			
		||||
 | 
			
		||||
    // 初始化路由
 | 
			
		||||
    await initRouter();
 | 
			
		||||
@@ -415,26 +430,27 @@ const toIndex = async () => {
 | 
			
		||||
    }, 300);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const changePwd = () => {
 | 
			
		||||
    changePwdFormRef.value.validate(async (valid: boolean) => {
 | 
			
		||||
        if (!valid) {
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
        try {
 | 
			
		||||
            state.loading.changePwd = true;
 | 
			
		||||
            const form = state.changePwdDialog.form;
 | 
			
		||||
            const changePwdReq: any = { ...form };
 | 
			
		||||
            changePwdReq.oldPassword = await RsaEncrypt(form.oldPassword);
 | 
			
		||||
            changePwdReq.newPassword = await RsaEncrypt(form.newPassword);
 | 
			
		||||
            await openApi.changePwd(changePwdReq);
 | 
			
		||||
            ElMessage.success('密码修改成功, 新密码已填充至登录密码框');
 | 
			
		||||
            state.loginForm.password = state.changePwdDialog.form.newPassword;
 | 
			
		||||
            state.changePwdDialog.visible = false;
 | 
			
		||||
            getCaptcha();
 | 
			
		||||
        } finally {
 | 
			
		||||
            state.loading.changePwd = false;
 | 
			
		||||
        }
 | 
			
		||||
    });
 | 
			
		||||
const changePwd = async () => {
 | 
			
		||||
    try {
 | 
			
		||||
        await changePwdFormRef.value.validate();
 | 
			
		||||
    } catch (e: any) {
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    try {
 | 
			
		||||
        state.loading.changePwd = true;
 | 
			
		||||
        const form = state.changePwdDialog.form;
 | 
			
		||||
        const changePwdReq: any = { ...form };
 | 
			
		||||
        changePwdReq.oldPassword = await RsaEncrypt(form.oldPassword);
 | 
			
		||||
        changePwdReq.newPassword = await RsaEncrypt(form.newPassword);
 | 
			
		||||
        await openApi.changePwd(changePwdReq);
 | 
			
		||||
        ElMessage.success('密码修改成功, 新密码已填充至登录密码框');
 | 
			
		||||
        state.loginForm.password = state.changePwdDialog.form.newPassword;
 | 
			
		||||
        state.changePwdDialog.visible = false;
 | 
			
		||||
        getCaptcha();
 | 
			
		||||
    } finally {
 | 
			
		||||
        state.loading.changePwd = false;
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const cancelChangePwd = () => {
 | 
			
		||||
 
 | 
			
		||||
@@ -39,6 +39,11 @@
 | 
			
		||||
                            />
 | 
			
		||||
 | 
			
		||||
                            <el-option :key="TagResourceTypeEnum.Db.value" :label="TagResourceTypeEnum.Db.label" :value="TagResourceTypeEnum.Db.value" />
 | 
			
		||||
                            <el-option
 | 
			
		||||
                                :key="TagResourceTypeEnum.Redis.value"
 | 
			
		||||
                                :label="TagResourceTypeEnum.Redis.label"
 | 
			
		||||
                                :value="TagResourceTypeEnum.Redis.value"
 | 
			
		||||
                            />
 | 
			
		||||
                        </el-select>
 | 
			
		||||
                    </el-form-item>
 | 
			
		||||
                    <el-form-item prop="resourceCode" label="资源编号" required>
 | 
			
		||||
@@ -46,7 +51,7 @@
 | 
			
		||||
                    </el-form-item>
 | 
			
		||||
                </template>
 | 
			
		||||
 | 
			
		||||
                <el-form-item prop="name" label="名称" required>
 | 
			
		||||
                <el-form-item v-if="form.type == AuthCertTypeEnum.Public.value" prop="name" label="名称" required>
 | 
			
		||||
                    <el-input :disabled="form.id" v-model="form.name" placeholder="请输入凭证名 (全局唯一)"></el-input>
 | 
			
		||||
                </el-form-item>
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -22,7 +22,7 @@
 | 
			
		||||
                </template>
 | 
			
		||||
            </el-table-column>
 | 
			
		||||
 | 
			
		||||
            <el-table-column prop="name" label="名称" show-overflow-tooltip min-width="100px"> </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">
 | 
			
		||||
@@ -117,17 +117,17 @@ const cancelEdit = () => {
 | 
			
		||||
 | 
			
		||||
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) {
 | 
			
		||||
    //     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;
 | 
			
		||||
@@ -135,8 +135,8 @@ const btnOk = async (authCert: any) => {
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (authCerts.value?.filter((x: any) => x.username == authCert.username || x.name == authCert.name).length > 0) {
 | 
			
		||||
        ElMessage.error('该名称或用户名已存在于该账号列表中');
 | 
			
		||||
    if (authCerts.value?.filter((x: any) => x.username == authCert.username).length > 0) {
 | 
			
		||||
        ElMessage.error('该用户名已存在于该账号列表中');
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,13 +1,13 @@
 | 
			
		||||
<template>
 | 
			
		||||
    <div v-if="paths">
 | 
			
		||||
        <el-row v-for="(path, idx) in paths?.slice(0, 1)" :key="idx">
 | 
			
		||||
            <span v-for="item in parseTagPath(path)" :key="item.code">
 | 
			
		||||
    <div v-if="codePaths">
 | 
			
		||||
        <el-row v-for="(path, idx) in codePaths?.slice(0, 1)" :key="idx">
 | 
			
		||||
            <span v-for="item in path" :key="item.code">
 | 
			
		||||
                <SvgIcon
 | 
			
		||||
                    :name="EnumValue.getEnumByValue(TagResourceTypeEnum, item.type)?.extra.icon"
 | 
			
		||||
                    :color="EnumValue.getEnumByValue(TagResourceTypeEnum, item.type)?.extra.iconColor"
 | 
			
		||||
                    class="mr2"
 | 
			
		||||
                />
 | 
			
		||||
                <span> {{ item.code }}</span>
 | 
			
		||||
                <span> {{ item.name ? item.name : item.code }}</span>
 | 
			
		||||
                <SvgIcon v-if="!item.isEnd" class="mr5 ml5" name="arrow-right" />
 | 
			
		||||
            </span>
 | 
			
		||||
 | 
			
		||||
@@ -24,7 +24,7 @@
 | 
			
		||||
                            :color="EnumValue.getEnumByValue(TagResourceTypeEnum, item.type)?.extra.iconColor"
 | 
			
		||||
                            class="mr2"
 | 
			
		||||
                        />
 | 
			
		||||
                        <span> {{ item.code }}</span>
 | 
			
		||||
                        <span> {{ item.name ? item.name : item.code }}</span>
 | 
			
		||||
                        <SvgIcon v-if="!item.isEnd" class="mr5 ml5" name="arrow-right" />
 | 
			
		||||
                    </span>
 | 
			
		||||
                </el-row>
 | 
			
		||||
@@ -36,14 +36,21 @@
 | 
			
		||||
<script lang="ts" setup>
 | 
			
		||||
import { TagResourceTypeEnum } from '@/common/commonEnum';
 | 
			
		||||
import EnumValue from '@/common/Enum';
 | 
			
		||||
import { computed } from 'vue';
 | 
			
		||||
import { computed, onMounted, ref, watch } from 'vue';
 | 
			
		||||
import { getAllTagInfoByCodePaths } from './tag';
 | 
			
		||||
 | 
			
		||||
const props = defineProps({
 | 
			
		||||
    path: {
 | 
			
		||||
        type: [String, Array<string>],
 | 
			
		||||
    },
 | 
			
		||||
    tagInfos: {
 | 
			
		||||
        type: Object, // key: code , value: code info
 | 
			
		||||
    },
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const codePaths: any = ref([]);
 | 
			
		||||
let allTagInfos: any = {};
 | 
			
		||||
 | 
			
		||||
const paths = computed(() => {
 | 
			
		||||
    if (Array.isArray(props.path)) {
 | 
			
		||||
        return props.path;
 | 
			
		||||
@@ -52,6 +59,32 @@ const paths = computed(() => {
 | 
			
		||||
    return [props.path];
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
onMounted(() => {
 | 
			
		||||
    setCodePaths();
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
watch(
 | 
			
		||||
    () => props.path,
 | 
			
		||||
    () => {
 | 
			
		||||
        setCodePaths();
 | 
			
		||||
    }
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
const setCodePaths = async () => {
 | 
			
		||||
    if (!paths.value) {
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (!props.tagInfos || Object.keys(props.tagInfos).length == 0) {
 | 
			
		||||
        const tagInfos = await getAllTagInfoByCodePaths(paths.value as any);
 | 
			
		||||
        allTagInfos = tagInfos;
 | 
			
		||||
    } else {
 | 
			
		||||
        allTagInfos = props.tagInfos;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    codePaths.value = paths.value.map((p) => parseTagPath(p));
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const parseTagPath = (tagPath: string = '') => {
 | 
			
		||||
    if (!tagPath) {
 | 
			
		||||
        return [];
 | 
			
		||||
@@ -61,27 +94,52 @@ const parseTagPath = (tagPath: string = '') => {
 | 
			
		||||
    for (let code of codes) {
 | 
			
		||||
        const typeAndCode = code.split('|');
 | 
			
		||||
 | 
			
		||||
        let tagInfo;
 | 
			
		||||
        if (typeAndCode.length == 1) {
 | 
			
		||||
            const tagCode = typeAndCode[0];
 | 
			
		||||
            if (!tagCode) {
 | 
			
		||||
                continue;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            res.push({
 | 
			
		||||
            tagInfo = {
 | 
			
		||||
                type: TagResourceTypeEnum.Tag.value,
 | 
			
		||||
                code: typeAndCode[0],
 | 
			
		||||
            });
 | 
			
		||||
            };
 | 
			
		||||
            res.push(tagInfo);
 | 
			
		||||
            continue;
 | 
			
		||||
        } else {
 | 
			
		||||
            tagInfo = {
 | 
			
		||||
                type: typeAndCode[0],
 | 
			
		||||
                code: typeAndCode[1],
 | 
			
		||||
                name: '',
 | 
			
		||||
            };
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        res.push({
 | 
			
		||||
            type: typeAndCode[0],
 | 
			
		||||
            code: typeAndCode[1],
 | 
			
		||||
        });
 | 
			
		||||
        const ti = getTagInfo(tagInfo.type, tagInfo.code);
 | 
			
		||||
        if (ti) {
 | 
			
		||||
            tagInfo.name = ti.name;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        res.push(tagInfo);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    res[res.length - 1].isEnd = true;
 | 
			
		||||
    return res;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const getTagInfo = (type: any, code: string) => {
 | 
			
		||||
    if (type == TagResourceTypeEnum.Tag.value) {
 | 
			
		||||
        return {};
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (allTagInfos && Object.keys(allTagInfos).length > 0) {
 | 
			
		||||
        const key = `${type}|${code}`;
 | 
			
		||||
        if (allTagInfos[key]) {
 | 
			
		||||
            return allTagInfos[key];
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return {};
 | 
			
		||||
};
 | 
			
		||||
</script>
 | 
			
		||||
<style lang="scss"></style>
 | 
			
		||||
 
 | 
			
		||||
@@ -18,7 +18,7 @@
 | 
			
		||||
                :default-expanded-keys="props.defaultExpandedKeys"
 | 
			
		||||
            >
 | 
			
		||||
                <template #default="{ node, data }">
 | 
			
		||||
                    <span @dblclick="treeNodeDblclick(data)" :class="data.type.nodeDblclickFunc ? 'none-select' : ''">
 | 
			
		||||
                    <span :id="node.key" @dblclick="treeNodeDblclick(data)" :class="data.type.nodeDblclickFunc ? 'none-select' : ''">
 | 
			
		||||
                        <span v-if="data.type.value == TagTreeNode.TagPath">
 | 
			
		||||
                            <tag-info :tag-path="data.label" />
 | 
			
		||||
                        </span>
 | 
			
		||||
@@ -35,7 +35,9 @@
 | 
			
		||||
                            </slot>
 | 
			
		||||
                        </span>
 | 
			
		||||
 | 
			
		||||
                        <slot :node="node" :data="data" name="suffix"></slot>
 | 
			
		||||
                        <span class="label-suffix">
 | 
			
		||||
                            <slot :node="node" :data="data" name="suffix"></slot>
 | 
			
		||||
                        </span>
 | 
			
		||||
                    </span>
 | 
			
		||||
                </template>
 | 
			
		||||
            </el-tree>
 | 
			
		||||
@@ -46,11 +48,12 @@
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script lang="ts" setup>
 | 
			
		||||
import { onMounted, reactive, ref, watch, toRefs } from 'vue';
 | 
			
		||||
import { nextTick, onMounted, reactive, ref, toRefs, watch } from 'vue';
 | 
			
		||||
import { NodeType, TagTreeNode } from './tag';
 | 
			
		||||
import TagInfo from './TagInfo.vue';
 | 
			
		||||
import { Contextmenu } from '@/components/contextmenu';
 | 
			
		||||
import { tagApi } from '../tag/api';
 | 
			
		||||
import { isPrefixSubsequence } from '@/common/utils/string';
 | 
			
		||||
 | 
			
		||||
const props = defineProps({
 | 
			
		||||
    resourceType: {
 | 
			
		||||
@@ -103,8 +106,7 @@ watch(filterText, (val) => {
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const filterNode = (value: string, data: any) => {
 | 
			
		||||
    if (!value) return true;
 | 
			
		||||
    return data.label.includes(value);
 | 
			
		||||
    return !value || isPrefixSubsequence(value, data.label);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
@@ -124,7 +126,7 @@ const loadTags = async () => {
 | 
			
		||||
 * @param { Object } node
 | 
			
		||||
 * @param { Object } resolve
 | 
			
		||||
 */
 | 
			
		||||
const loadNode = async (node: any, resolve: any) => {
 | 
			
		||||
const loadNode = async (node: any, resolve: (data: any) => void, reject: () => void) => {
 | 
			
		||||
    if (typeof resolve !== 'function') {
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
@@ -139,14 +141,16 @@ const loadNode = async (node: any, resolve: any) => {
 | 
			
		||||
        }
 | 
			
		||||
    } catch (e: any) {
 | 
			
		||||
        console.error(e);
 | 
			
		||||
        // 调用 reject 以保持节点状态,并允许远程加载继续。
 | 
			
		||||
        return reject();
 | 
			
		||||
    }
 | 
			
		||||
    return resolve(nodes);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const treeNodeClick = (data: any) => {
 | 
			
		||||
const treeNodeClick = async (data: any) => {
 | 
			
		||||
    if (!data.disabled && !data.type.nodeDblclickFunc && data.type.nodeClickFunc) {
 | 
			
		||||
        emit('nodeClick', data);
 | 
			
		||||
        data.type.nodeClickFunc(data);
 | 
			
		||||
        await data.type.nodeClickFunc(data);
 | 
			
		||||
    }
 | 
			
		||||
    // 关闭可能存在的右击菜单
 | 
			
		||||
    contextmenuRef.value.closeContextmenu();
 | 
			
		||||
@@ -205,6 +209,17 @@ const getNode = (nodeKey: any) => {
 | 
			
		||||
 | 
			
		||||
const setCurrentKey = (nodeKey: any) => {
 | 
			
		||||
    treeRef.value.setCurrentKey(nodeKey);
 | 
			
		||||
 | 
			
		||||
    // 通过Id获取到对应的dom元素
 | 
			
		||||
    const node = document.getElementById(nodeKey);
 | 
			
		||||
    if (node) {
 | 
			
		||||
        setTimeout(() => {
 | 
			
		||||
            nextTick(() => {
 | 
			
		||||
                // 通过scrollIntoView方法将对应的dom元素定位到可见区域 【block: 'center'】这个属性是在垂直方向居中显示
 | 
			
		||||
                node.scrollIntoView({ block: 'center' });
 | 
			
		||||
            });
 | 
			
		||||
        }, 100);
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
defineExpose({
 | 
			
		||||
@@ -222,5 +237,13 @@ defineExpose({
 | 
			
		||||
        display: inline-block;
 | 
			
		||||
        min-width: 100%;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .label-suffix {
 | 
			
		||||
        position: absolute;
 | 
			
		||||
        right: 10px;
 | 
			
		||||
        color: #c4c9c4;
 | 
			
		||||
        font-size: 10px;
 | 
			
		||||
        margin-top: 2px;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
 
 | 
			
		||||
@@ -1,54 +1,56 @@
 | 
			
		||||
<template>
 | 
			
		||||
    <div class="w100" style="border: 1px solid var(--el-border-color)">
 | 
			
		||||
        <el-input v-model="filterTag" clearable placeholder="输入关键字过滤" size="small" />
 | 
			
		||||
        <el-scrollbar :style="{ height: props.height }">
 | 
			
		||||
            <el-tree
 | 
			
		||||
                v-bind="$attrs"
 | 
			
		||||
                ref="tagTreeRef"
 | 
			
		||||
                style="width: 100%"
 | 
			
		||||
                :data="state.tags"
 | 
			
		||||
                :default-expanded-keys="checkedTags"
 | 
			
		||||
                :default-checked-keys="checkedTags"
 | 
			
		||||
                multiple
 | 
			
		||||
                :render-after-expand="true"
 | 
			
		||||
                show-checkbox
 | 
			
		||||
                check-strictly
 | 
			
		||||
                :node-key="$props.nodeKey"
 | 
			
		||||
                :props="{
 | 
			
		||||
                    value: $props.nodeKey,
 | 
			
		||||
                    label: 'codePath',
 | 
			
		||||
                    children: 'children',
 | 
			
		||||
                    disabled: 'disabled',
 | 
			
		||||
                }"
 | 
			
		||||
                @check="tagTreeNodeCheck"
 | 
			
		||||
                :filter-node-method="filterNode"
 | 
			
		||||
            >
 | 
			
		||||
                <template #default="{ data }">
 | 
			
		||||
                    <span class="custom-tree-node">
 | 
			
		||||
                        <SvgIcon
 | 
			
		||||
                            :name="EnumValue.getEnumByValue(TagResourceTypeEnum, data.type)?.extra.icon"
 | 
			
		||||
                            :color="EnumValue.getEnumByValue(TagResourceTypeEnum, data.type)?.extra.iconColor"
 | 
			
		||||
                        />
 | 
			
		||||
    <div class="w100 tag-tree-check">
 | 
			
		||||
        <el-input v-model="filterTag" @input="onFilterValChanged" clearable placeholder="输入关键字过滤" size="small" />
 | 
			
		||||
        <div class="mt3" style="border: 1px solid var(--el-border-color)">
 | 
			
		||||
            <el-scrollbar :style="{ height: props.height }">
 | 
			
		||||
                <el-tree
 | 
			
		||||
                    v-bind="$attrs"
 | 
			
		||||
                    ref="tagTreeRef"
 | 
			
		||||
                    :data="state.tags"
 | 
			
		||||
                    :default-expanded-keys="state.defaultExpandedKeys"
 | 
			
		||||
                    :default-checked-keys="checkedTags"
 | 
			
		||||
                    multiple
 | 
			
		||||
                    :render-after-expand="true"
 | 
			
		||||
                    show-checkbox
 | 
			
		||||
                    check-strictly
 | 
			
		||||
                    :node-key="$props.nodeKey"
 | 
			
		||||
                    :props="{
 | 
			
		||||
                        value: $props.nodeKey,
 | 
			
		||||
                        label: 'codePath',
 | 
			
		||||
                        children: 'children',
 | 
			
		||||
                        disabled: 'disabled',
 | 
			
		||||
                    }"
 | 
			
		||||
                    @check="tagTreeNodeCheck"
 | 
			
		||||
                    :filter-node-method="filterNode"
 | 
			
		||||
                >
 | 
			
		||||
                    <template #default="{ data }">
 | 
			
		||||
                        <span>
 | 
			
		||||
                            <SvgIcon
 | 
			
		||||
                                :name="EnumValue.getEnumByValue(TagResourceTypeEnum, data.type)?.extra.icon"
 | 
			
		||||
                                :color="EnumValue.getEnumByValue(TagResourceTypeEnum, data.type)?.extra.iconColor"
 | 
			
		||||
                            />
 | 
			
		||||
 | 
			
		||||
                        <span class="font13 ml5">
 | 
			
		||||
                            {{ data.code }}
 | 
			
		||||
                            <span style="color: #3c8dbc">【</span>
 | 
			
		||||
                            {{ data.name }}
 | 
			
		||||
                            <span style="color: #3c8dbc">】</span>
 | 
			
		||||
                            <el-tag v-if="data.children !== null" size="small">{{ data.children.length }} </el-tag>
 | 
			
		||||
                            <span class="font13 ml5">
 | 
			
		||||
                                {{ data.name }}
 | 
			
		||||
                                <span style="color: #3c8dbc">【</span>
 | 
			
		||||
                                {{ data.code }}
 | 
			
		||||
                                <span style="color: #3c8dbc">】</span>
 | 
			
		||||
                                <el-tag v-if="data.children !== null" size="small">{{ data.children.length }} </el-tag>
 | 
			
		||||
                            </span>
 | 
			
		||||
                        </span>
 | 
			
		||||
                    </span>
 | 
			
		||||
                </template>
 | 
			
		||||
            </el-tree>
 | 
			
		||||
        </el-scrollbar>
 | 
			
		||||
                    </template>
 | 
			
		||||
                </el-tree>
 | 
			
		||||
            </el-scrollbar>
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script lang="ts" setup>
 | 
			
		||||
import { ref, reactive, onMounted, watch } from 'vue';
 | 
			
		||||
import { ref, reactive, onMounted } from 'vue';
 | 
			
		||||
import { tagApi } from '../tag/api';
 | 
			
		||||
import { TagResourceTypeEnum } from '@/common/commonEnum';
 | 
			
		||||
import EnumValue from '@/common/Enum';
 | 
			
		||||
import { isPrefixSubsequence } from '@/common/utils/string';
 | 
			
		||||
 | 
			
		||||
const props = defineProps({
 | 
			
		||||
    height: {
 | 
			
		||||
@@ -56,7 +58,7 @@ const props = defineProps({
 | 
			
		||||
        default: 'calc(100vh - 330px)',
 | 
			
		||||
    },
 | 
			
		||||
    tagType: {
 | 
			
		||||
        type: Number,
 | 
			
		||||
        type: [Number, Array<Number>],
 | 
			
		||||
        default: TagResourceTypeEnum.Tag.value,
 | 
			
		||||
    },
 | 
			
		||||
    nodeKey: {
 | 
			
		||||
@@ -73,15 +75,22 @@ const tagTreeRef: any = ref(null);
 | 
			
		||||
const filterTag = ref('');
 | 
			
		||||
 | 
			
		||||
const state = reactive({
 | 
			
		||||
    defaultExpandedKeys: [] as any,
 | 
			
		||||
    tags: [],
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
onMounted(() => {
 | 
			
		||||
    state.defaultExpandedKeys = checkedTags.value;
 | 
			
		||||
    search();
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const search = async () => {
 | 
			
		||||
    state.tags = await tagApi.getTagTrees.request({ type: props.tagType });
 | 
			
		||||
    let tagType: any = props.tagType;
 | 
			
		||||
    if (Array.isArray(props.tagType)) {
 | 
			
		||||
        tagType = props.tagType.join(',');
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    state.tags = await tagApi.getTagTrees.request({ type: tagType });
 | 
			
		||||
 | 
			
		||||
    setTimeout(() => {
 | 
			
		||||
        const checkedNodes = tagTreeRef.value.getCheckedNodes();
 | 
			
		||||
@@ -93,15 +102,12 @@ const search = async () => {
 | 
			
		||||
    }, 200);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
watch(filterTag, (val) => {
 | 
			
		||||
    tagTreeRef.value!.filter(val);
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const filterNode = (value: string, data: any) => {
 | 
			
		||||
    if (!value) {
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
    return data.codePath.toLowerCase().includes(value) || data.name.includes(value);
 | 
			
		||||
    return !value || isPrefixSubsequence(value, data.codePath) || isPrefixSubsequence(value, data.name);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const onFilterValChanged = (val: string) => {
 | 
			
		||||
    tagTreeRef.value!.filter(val);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const tagTreeNodeCheck = (data: any) => {
 | 
			
		||||
@@ -150,4 +156,12 @@ const disableParentNodes = (node: any, disable = true) => {
 | 
			
		||||
    disableParentNodes(node.parent, disable);
 | 
			
		||||
};
 | 
			
		||||
</script>
 | 
			
		||||
<style lang="scss" scoped></style>
 | 
			
		||||
<style lang="scss" scoped>
 | 
			
		||||
.tag-tree-check {
 | 
			
		||||
    .el-tree {
 | 
			
		||||
        min-width: 100%;
 | 
			
		||||
        // 横向滚动生效
 | 
			
		||||
        display: inline-block;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,7 @@
 | 
			
		||||
import { OptionsApi, SearchItem } from '@/components/SearchForm';
 | 
			
		||||
import { ContextmenuItem } from '@/components/contextmenu';
 | 
			
		||||
import { tagApi } from '../tag/api';
 | 
			
		||||
import {OptionsApi, SearchItem} from '@/components/SearchForm';
 | 
			
		||||
import {ContextmenuItem} from '@/components/contextmenu';
 | 
			
		||||
import {TagResourceTypeEnum} from '@/common/commonEnum';
 | 
			
		||||
import {tagApi} from '../tag/api';
 | 
			
		||||
 | 
			
		||||
export class TagTreeNode {
 | 
			
		||||
    /**
 | 
			
		||||
@@ -161,7 +162,7 @@ export class NodeType {
 | 
			
		||||
 */
 | 
			
		||||
export function getTagPathSearchItem(resourceType: number) {
 | 
			
		||||
    return SearchItem.select('tagPath', '标签').withOptionsApi(
 | 
			
		||||
        OptionsApi.new(tagApi.getResourceTagPaths, { resourceType }).withConvertFn((res: any) => {
 | 
			
		||||
        OptionsApi.new(tagApi.getResourceTagPaths, {resourceType}).withConvertFn((res: any) => {
 | 
			
		||||
            return res.map((x: any) => {
 | 
			
		||||
                return {
 | 
			
		||||
                    label: x,
 | 
			
		||||
@@ -178,7 +179,8 @@ export function getTagPathSearchItem(resourceType: number) {
 | 
			
		||||
 * @returns {1: ['xxx'], 11: ['yyy']}
 | 
			
		||||
 */
 | 
			
		||||
export function getTagTypeCodeByPath(codePath: string) {
 | 
			
		||||
    const result = {};
 | 
			
		||||
    const result: any = {};
 | 
			
		||||
    if (!codePath) return result
 | 
			
		||||
    const parts = codePath.split('/'); // 切分字符串并保留数字和对应的值部分
 | 
			
		||||
 | 
			
		||||
    for (let part of parts) {
 | 
			
		||||
@@ -199,3 +201,50 @@ export function getTagTypeCodeByPath(codePath: string) {
 | 
			
		||||
 | 
			
		||||
    return result;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 完善标签路径信息
 | 
			
		||||
 * @param codePaths 标签路径
 | 
			
		||||
 * @returns
 | 
			
		||||
 */
 | 
			
		||||
export async function getAllTagInfoByCodePaths(codePaths: string[]) {
 | 
			
		||||
    if (!codePaths) return
 | 
			
		||||
    const allTypeAndCode: any = {};
 | 
			
		||||
 | 
			
		||||
    for (let codePath of codePaths) {
 | 
			
		||||
        const typeAndCode = getTagTypeCodeByPath(codePath);
 | 
			
		||||
        for (let type in typeAndCode) {
 | 
			
		||||
            allTypeAndCode[type] = [...new Set(typeAndCode[type].concat(allTypeAndCode[type] || []))];
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    for (let type in allTypeAndCode) {
 | 
			
		||||
        if (type == TagResourceTypeEnum.Tag.value) {
 | 
			
		||||
            continue;
 | 
			
		||||
        }
 | 
			
		||||
        const tagInfo = await tagApi.listByQuery.request({type: type, codes: allTypeAndCode[type]});
 | 
			
		||||
        allTypeAndCode[type] = tagInfo;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const code2CodeInfo: any = {};
 | 
			
		||||
    for (let type in allTypeAndCode) {
 | 
			
		||||
        for (let code of allTypeAndCode[type]) {
 | 
			
		||||
            code2CodeInfo[`${type}|${code.code}`] = code;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return code2CodeInfo;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function expandCodePath(codePath: string) {
 | 
			
		||||
    const parts = codePath.split('/');
 | 
			
		||||
    const result = [];
 | 
			
		||||
    let currentPath = '';
 | 
			
		||||
 | 
			
		||||
    for (let i = 0; i < parts.length - 1; i++) {
 | 
			
		||||
        currentPath += parts[i] + '/';
 | 
			
		||||
        result.push(currentPath);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return result;
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -10,20 +10,12 @@
 | 
			
		||||
            width="38%"
 | 
			
		||||
        >
 | 
			
		||||
            <el-form :model="form" ref="dbForm" :rules="rules" label-width="auto">
 | 
			
		||||
                <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-input v-model.trim="form.name" placeholder="请输入数据库别名" auto-complete="off"></el-input>
 | 
			
		||||
                </el-form-item>
 | 
			
		||||
 | 
			
		||||
                <el-form-item prop="authCertName" label="授权凭证" required>
 | 
			
		||||
                    <el-select @change="changeAuthCert" v-model="form.authCertName" placeholder="请选择授权凭证" filterable>
 | 
			
		||||
                    <el-select 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 }}
 | 
			
		||||
 | 
			
		||||
@@ -39,8 +31,15 @@
 | 
			
		||||
                    </el-select>
 | 
			
		||||
                </el-form-item>
 | 
			
		||||
 | 
			
		||||
                <el-form-item prop="getDatabaseMode" label="获库方式" required>
 | 
			
		||||
                    <el-select v-model="form.getDatabaseMode" @change="onChangeGetDatabaseMode" placeholder="请选择库名获取方式">
 | 
			
		||||
                        <el-option v-for="item in DbGetDbNamesMode" :key="item.value" :label="item.label" :value="item.value"> </el-option>
 | 
			
		||||
                    </el-select>
 | 
			
		||||
                </el-form-item>
 | 
			
		||||
 | 
			
		||||
                <el-form-item prop="database" label="数据库名">
 | 
			
		||||
                    <el-select
 | 
			
		||||
                        :disabled="form.getDatabaseMode == DbGetDbNamesMode.Auto.value || !form.authCertName"
 | 
			
		||||
                        v-model="dbNamesSelected"
 | 
			
		||||
                        multiple
 | 
			
		||||
                        clearable
 | 
			
		||||
@@ -49,8 +48,9 @@
 | 
			
		||||
                        filterable
 | 
			
		||||
                        :filter-method="filterDbNames"
 | 
			
		||||
                        allow-create
 | 
			
		||||
                        placeholder="请确保数据库实例信息填写完整后获取库名"
 | 
			
		||||
                        style="width: 100%"
 | 
			
		||||
                        placeholder="获库方式为‘指定库名’时,可选择"
 | 
			
		||||
                        @focus="getAllDatabase(form.authCertName)"
 | 
			
		||||
                        :loading="state.loadingDbNames"
 | 
			
		||||
                    >
 | 
			
		||||
                        <template #header>
 | 
			
		||||
                            <el-checkbox v-model="checkAllDbNames" :indeterminate="indeterminateDbNames" @change="handleCheckAll"> 全选 </el-checkbox>
 | 
			
		||||
@@ -62,8 +62,6 @@
 | 
			
		||||
                <el-form-item prop="remark" label="备注">
 | 
			
		||||
                    <el-input v-model.trim="form.remark" auto-complete="off" type="textarea"></el-input>
 | 
			
		||||
                </el-form-item>
 | 
			
		||||
 | 
			
		||||
                <procdef-select-form-item v-model="form.flowProcdefKey" />
 | 
			
		||||
            </el-form>
 | 
			
		||||
 | 
			
		||||
            <template #footer>
 | 
			
		||||
@@ -77,19 +75,17 @@
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script lang="ts" setup>
 | 
			
		||||
import { toRefs, reactive, watch, ref, watchEffect } from 'vue';
 | 
			
		||||
import { toRefs, reactive, watch, ref } from 'vue';
 | 
			
		||||
import { dbApi } from './api';
 | 
			
		||||
import { ElMessage } from 'element-plus';
 | 
			
		||||
// import TagTreeSelect from '../component/TagTreeSelect.vue';
 | 
			
		||||
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 EnumTag from '@/components/enumtag/EnumTag.vue';
 | 
			
		||||
import { AuthCertCiphertextTypeEnum } from '../tag/enums';
 | 
			
		||||
import { resourceAuthCertApi } from '../tag/api';
 | 
			
		||||
import { TagResourceTypeEnum } from '@/common/commonEnum';
 | 
			
		||||
import { DbGetDbNamesMode } from './enums';
 | 
			
		||||
 | 
			
		||||
const props = defineProps({
 | 
			
		||||
    visible: {
 | 
			
		||||
@@ -125,18 +121,6 @@ const rules = {
 | 
			
		||||
            trigger: ['change', 'blur'],
 | 
			
		||||
        },
 | 
			
		||||
    ],
 | 
			
		||||
    code: [
 | 
			
		||||
        {
 | 
			
		||||
            required: true,
 | 
			
		||||
            message: '请输入编码',
 | 
			
		||||
            trigger: ['change', 'blur'],
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
            pattern: ResourceCodePattern.pattern,
 | 
			
		||||
            message: ResourceCodePattern.message,
 | 
			
		||||
            trigger: ['blur'],
 | 
			
		||||
        },
 | 
			
		||||
    ],
 | 
			
		||||
    name: [
 | 
			
		||||
        {
 | 
			
		||||
            required: true,
 | 
			
		||||
@@ -151,10 +135,10 @@ const rules = {
 | 
			
		||||
            trigger: ['change', 'blur'],
 | 
			
		||||
        },
 | 
			
		||||
    ],
 | 
			
		||||
    database: [
 | 
			
		||||
    getDatabaseMode: [
 | 
			
		||||
        {
 | 
			
		||||
            required: true,
 | 
			
		||||
            message: '请添加数据库',
 | 
			
		||||
            message: '请选择库名获取方式',
 | 
			
		||||
            trigger: ['change', 'blur'],
 | 
			
		||||
        },
 | 
			
		||||
    ],
 | 
			
		||||
@@ -176,39 +160,45 @@ const state = reactive({
 | 
			
		||||
    authCerts: [] as any,
 | 
			
		||||
    form: {
 | 
			
		||||
        id: null,
 | 
			
		||||
        // tagId: [],
 | 
			
		||||
        name: null,
 | 
			
		||||
        code: '',
 | 
			
		||||
        getDatabaseMode: DbGetDbNamesMode.Auto.value,
 | 
			
		||||
        database: '',
 | 
			
		||||
        remark: '',
 | 
			
		||||
        instanceId: null as any,
 | 
			
		||||
        authCertName: '',
 | 
			
		||||
        flowProcdefKey: '',
 | 
			
		||||
    },
 | 
			
		||||
    instances: [] as any,
 | 
			
		||||
    loadingDbNames: false,
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const { dialogVisible, allDatabases, form, dbNamesSelected } = toRefs(state);
 | 
			
		||||
 | 
			
		||||
watchEffect(() => {
 | 
			
		||||
    state.dialogVisible = props.visible;
 | 
			
		||||
    if (!state.dialogVisible) {
 | 
			
		||||
        return;
 | 
			
		||||
watch(
 | 
			
		||||
    () => props.visible,
 | 
			
		||||
    () => {
 | 
			
		||||
        state.dialogVisible = props.visible;
 | 
			
		||||
        if (!state.dialogVisible) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        const db: any = props.db;
 | 
			
		||||
        if (db.code) {
 | 
			
		||||
            state.form = { ...db };
 | 
			
		||||
            if (db.getDatabaseMode == DbGetDbNamesMode.Assign.value) {
 | 
			
		||||
                // 将数据库名使用空格切割,获取所有数据库列表
 | 
			
		||||
                state.dbNamesSelected = db.database.split(' ');
 | 
			
		||||
            }
 | 
			
		||||
        } else {
 | 
			
		||||
            state.form = { getDatabaseMode: DbGetDbNamesMode.Auto.value } as any;
 | 
			
		||||
            state.dbNamesSelected = [];
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    const db: any = props.db;
 | 
			
		||||
    if (db.code) {
 | 
			
		||||
        state.form = { ...db };
 | 
			
		||||
        // state.form.tagId = newValue.db.tags.map((t: any) => t.tagId);
 | 
			
		||||
        // 将数据库名使用空格切割,获取所有数据库列表
 | 
			
		||||
        state.dbNamesSelected = db.database.split(' ');
 | 
			
		||||
    } else {
 | 
			
		||||
        state.form = {} as any;
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
const onChangeGetDatabaseMode = (val: any) => {
 | 
			
		||||
    if (val == DbGetDbNamesMode.Auto.value) {
 | 
			
		||||
        state.dbNamesSelected = [];
 | 
			
		||||
    }
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const changeAuthCert = (val: string) => {
 | 
			
		||||
    getAllDatabase(val);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const getAuthCerts = async () => {
 | 
			
		||||
@@ -222,15 +212,20 @@ const getAuthCerts = async () => {
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const getAllDatabase = async (authCertName: string) => {
 | 
			
		||||
    const req = { ...(props.instance as any) };
 | 
			
		||||
    req.authCert = state.authCerts?.find((x: any) => x.name == authCertName);
 | 
			
		||||
    let dbs = await dbApi.getAllDatabase.request(req);
 | 
			
		||||
    state.allDatabases = dbs;
 | 
			
		||||
    try {
 | 
			
		||||
        state.loadingDbNames = true;
 | 
			
		||||
        const req = { ...(props.instance as any) };
 | 
			
		||||
        req.authCert = state.authCerts?.find((x: any) => x.name == authCertName);
 | 
			
		||||
        let dbs = await dbApi.getAllDatabase.request(req);
 | 
			
		||||
        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];
 | 
			
		||||
        // 如果是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];
 | 
			
		||||
        }
 | 
			
		||||
    } finally {
 | 
			
		||||
        state.loadingDbNames = false;
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,93 +1,112 @@
 | 
			
		||||
<template>
 | 
			
		||||
    <div class="db-list">
 | 
			
		||||
        <page-table
 | 
			
		||||
            ref="pageTableRef"
 | 
			
		||||
            :page-api="dbApi.dbs"
 | 
			
		||||
            :before-query-fn="checkRouteTagPath"
 | 
			
		||||
            :search-items="searchItems"
 | 
			
		||||
            v-model:query-form="query"
 | 
			
		||||
            :columns="columns"
 | 
			
		||||
            lazy
 | 
			
		||||
        <el-drawer
 | 
			
		||||
            :title="title"
 | 
			
		||||
            v-model="dialogVisible"
 | 
			
		||||
            @open="search"
 | 
			
		||||
            :before-close="cancel"
 | 
			
		||||
            :destroy-on-close="true"
 | 
			
		||||
            :close-on-click-modal="true"
 | 
			
		||||
            size="60%"
 | 
			
		||||
        >
 | 
			
		||||
            <template #instanceSelect>
 | 
			
		||||
                <el-select remote :remote-method="getInstances" v-model="query.instanceId" placeholder="输入并选择实例" filterable clearable>
 | 
			
		||||
                    <el-option v-for="item in state.instances" :key="item.id" :label="`${item.name}`" :value="item.id">
 | 
			
		||||
                        {{ item.name }}
 | 
			
		||||
                        <el-divider direction="vertical" border-style="dashed" />
 | 
			
		||||
 | 
			
		||||
                        {{ item.type }} / {{ item.host }}:{{ item.port }}
 | 
			
		||||
                        <el-divider direction="vertical" border-style="dashed" />
 | 
			
		||||
                        {{ item.username }}
 | 
			
		||||
                    </el-option>
 | 
			
		||||
                </el-select>
 | 
			
		||||
            </template>
 | 
			
		||||
 | 
			
		||||
            <template #type="{ data }">
 | 
			
		||||
                <el-tooltip :content="data.type" placement="top">
 | 
			
		||||
                    <SvgIcon :name="getDbDialect(data.type).getInfo().icon" :size="20" />
 | 
			
		||||
                </el-tooltip>
 | 
			
		||||
            </template>
 | 
			
		||||
 | 
			
		||||
            <template #host="{ data }">
 | 
			
		||||
                {{ `${data.host}:${data.port}` }}
 | 
			
		||||
            </template>
 | 
			
		||||
 | 
			
		||||
            <template #database="{ data }">
 | 
			
		||||
                <el-popover placement="bottom" :width="200" trigger="click">
 | 
			
		||||
                    <template #reference>
 | 
			
		||||
                        <el-button @click="state.currentDbs = data.database" type="primary" link>查看库</el-button>
 | 
			
		||||
            <template #header>
 | 
			
		||||
                <DrawerHeader :header="title" :back="cancel">
 | 
			
		||||
                    <template #extra>
 | 
			
		||||
                        <div class="mr20">
 | 
			
		||||
                            <span>{{ $props.instance?.tags?.[0]?.codePath }}</span>
 | 
			
		||||
                            <el-divider direction="vertical" border-style="dashed" />
 | 
			
		||||
                            <SvgIcon :name="getDbDialect($props.instance?.type).getInfo()?.icon" :size="20" />
 | 
			
		||||
                            <el-divider direction="vertical" border-style="dashed" />
 | 
			
		||||
                            <span>{{ $props.instance?.host }}:{{ $props.instance?.port }}</span>
 | 
			
		||||
                        </div>
 | 
			
		||||
                    </template>
 | 
			
		||||
                    <el-table :data="filterDbs" size="small">
 | 
			
		||||
                        <el-table-column prop="dbName" label="数据库">
 | 
			
		||||
                            <template #header>
 | 
			
		||||
                                <el-input v-model="state.dbNameSearch" size="small" placeholder="库名: 输入可过滤" clearable />
 | 
			
		||||
                            </template>
 | 
			
		||||
                        </el-table-column>
 | 
			
		||||
                    </el-table>
 | 
			
		||||
                </el-popover>
 | 
			
		||||
                </DrawerHeader>
 | 
			
		||||
            </template>
 | 
			
		||||
 | 
			
		||||
            <template #tagPath="{ data }">
 | 
			
		||||
                <ResourceTags :tags="data.tags" />
 | 
			
		||||
            </template>
 | 
			
		||||
            <page-table
 | 
			
		||||
                ref="pageTableRef"
 | 
			
		||||
                :page-api="dbApi.dbs"
 | 
			
		||||
                v-model:query-form="query"
 | 
			
		||||
                :columns="columns"
 | 
			
		||||
                lazy
 | 
			
		||||
                show-selection
 | 
			
		||||
                v-model:selection-data="state.selectionData"
 | 
			
		||||
            >
 | 
			
		||||
                <template #tableHeader>
 | 
			
		||||
                    <el-button v-auth="perms.saveDb" type="primary" circle icon="Plus" @click="editDb(null)"> </el-button>
 | 
			
		||||
                    <el-button v-auth="perms.delDb" :disabled="state.selectionData.length < 1" @click="deleteDb" type="danger" circle icon="delete"></el-button>
 | 
			
		||||
                </template>
 | 
			
		||||
 | 
			
		||||
            <template #action="{ data }">
 | 
			
		||||
                <el-button type="primary" @click="onShowSqlExec(data)" link>SQL记录</el-button>
 | 
			
		||||
                <el-divider direction="vertical" border-style="dashed" />
 | 
			
		||||
                <template #type="{ data }">
 | 
			
		||||
                    <el-tooltip :content="data.type" placement="top">
 | 
			
		||||
                        <SvgIcon :name="getDbDialect(data.type).getInfo().icon" :size="20" />
 | 
			
		||||
                    </el-tooltip>
 | 
			
		||||
                </template>
 | 
			
		||||
 | 
			
		||||
                <el-dropdown @command="handleMoreActionCommand">
 | 
			
		||||
                    <span class="el-dropdown-link-more">
 | 
			
		||||
                        更多
 | 
			
		||||
                        <el-icon class="el-icon--right">
 | 
			
		||||
                            <arrow-down />
 | 
			
		||||
                        </el-icon>
 | 
			
		||||
                    </span>
 | 
			
		||||
                    <template #dropdown>
 | 
			
		||||
                        <el-dropdown-menu>
 | 
			
		||||
                            <el-dropdown-item :command="{ type: 'detail', data }"> 详情 </el-dropdown-item>
 | 
			
		||||
                            <el-dropdown-item :command="{ type: 'dumpDb', data }"> 导出 </el-dropdown-item>
 | 
			
		||||
                            <el-dropdown-item :command="{ type: 'backupDb', data }" v-if="actionBtns[perms.backupDb] && supportAction('backupDb', data.type)">
 | 
			
		||||
                                备份任务
 | 
			
		||||
                            </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>
 | 
			
		||||
                    </template>
 | 
			
		||||
                </el-dropdown>
 | 
			
		||||
            </template>
 | 
			
		||||
        </page-table>
 | 
			
		||||
                <template #database="{ data }">
 | 
			
		||||
                    <el-popover placement="bottom" :width="200" trigger="click">
 | 
			
		||||
                        <template #reference>
 | 
			
		||||
                            <el-button @click="getDbNames(data)" type="primary" link>查看库</el-button>
 | 
			
		||||
                        </template>
 | 
			
		||||
                        <el-table :data="filterDbs" v-loading="state.loadingDbNames" size="small">
 | 
			
		||||
                            <el-table-column prop="dbName" label="数据库">
 | 
			
		||||
                                <template #header>
 | 
			
		||||
                                    <el-input v-model="state.dbNameSearch" size="small" placeholder="库名: 输入可过滤" clearable />
 | 
			
		||||
                                </template>
 | 
			
		||||
                            </el-table-column>
 | 
			
		||||
                        </el-table>
 | 
			
		||||
                    </el-popover>
 | 
			
		||||
                </template>
 | 
			
		||||
 | 
			
		||||
        <el-dialog width="750px" :title="`${db} 数据库导出`" v-model="exportDialog.visible">
 | 
			
		||||
                <template #tagPath="{ data }">
 | 
			
		||||
                    <ResourceTags :tags="data.tags" />
 | 
			
		||||
                </template>
 | 
			
		||||
 | 
			
		||||
                <template #action="{ data }">
 | 
			
		||||
                    <el-button v-auth="perms.saveDb" @click="editDb(data)" type="primary" link>编辑</el-button>
 | 
			
		||||
 | 
			
		||||
                    <el-divider direction="vertical" border-style="dashed" />
 | 
			
		||||
 | 
			
		||||
                    <el-button type="primary" @click="onShowSqlExec(data)" link>SQL记录</el-button>
 | 
			
		||||
 | 
			
		||||
                    <el-divider direction="vertical" border-style="dashed" />
 | 
			
		||||
 | 
			
		||||
                    <el-dropdown @command="handleMoreActionCommand">
 | 
			
		||||
                        <span class="el-dropdown-link-more">
 | 
			
		||||
                            更多
 | 
			
		||||
                            <el-icon class="el-icon--right">
 | 
			
		||||
                                <arrow-down />
 | 
			
		||||
                            </el-icon>
 | 
			
		||||
                        </span>
 | 
			
		||||
                        <template #dropdown>
 | 
			
		||||
                            <el-dropdown-menu>
 | 
			
		||||
                                <el-dropdown-item :command="{ type: 'dumpDb', data }"> 导出 </el-dropdown-item>
 | 
			
		||||
                                <!-- <el-dropdown-item
 | 
			
		||||
                                    :command="{ type: 'backupDb', data }"
 | 
			
		||||
                                    v-if="actionBtns[perms.backupDb] && supportAction('backupDb', data.type)"
 | 
			
		||||
                                >
 | 
			
		||||
                                    备份任务
 | 
			
		||||
                                </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>
 | 
			
		||||
                        </template>
 | 
			
		||||
                    </el-dropdown>
 | 
			
		||||
                </template>
 | 
			
		||||
            </page-table>
 | 
			
		||||
        </el-drawer>
 | 
			
		||||
 | 
			
		||||
        <el-dialog width="750px" :title="`${exportDialog.db} 数据库导出`" v-model="exportDialog.visible">
 | 
			
		||||
            <el-row justify="space-between">
 | 
			
		||||
                <el-col :span="9">
 | 
			
		||||
                    <el-form-item label="导出内容: ">
 | 
			
		||||
@@ -168,128 +187,98 @@
 | 
			
		||||
            <db-restore-list :dbId="dbRestoreDialog.dbId" :dbNames="dbRestoreDialog.dbs" />
 | 
			
		||||
        </el-dialog>
 | 
			
		||||
 | 
			
		||||
        <el-dialog v-if="infoDialog.visible" v-model="infoDialog.visible" :before-close="onBeforeCloseInfoDialog">
 | 
			
		||||
            <el-descriptions title="详情" :column="3" border>
 | 
			
		||||
                <el-descriptions-item :span="2" label="名称">{{ infoDialog.data?.name }}</el-descriptions-item>
 | 
			
		||||
                <el-descriptions-item :span="1" label="id">{{ infoDialog.data?.id }}</el-descriptions-item>
 | 
			
		||||
 | 
			
		||||
                <el-descriptions-item :span="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="2" label="主机">{{ infoDialog.instance?.host }}</el-descriptions-item>
 | 
			
		||||
                <el-descriptions-item :span="1" label="端口">{{ infoDialog.instance?.port }}</el-descriptions-item>
 | 
			
		||||
 | 
			
		||||
                <el-descriptions-item :span="2" label="授权凭证">{{ infoDialog.instance.authCertName }}</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-dialog>
 | 
			
		||||
 | 
			
		||||
        <db-edit @val-change="search()" :title="dbEditDialog.title" v-model:visible="dbEditDialog.visible" v-model:db="dbEditDialog.data"></db-edit>
 | 
			
		||||
        <db-edit
 | 
			
		||||
            @confirm="confirmEditDb"
 | 
			
		||||
            @cancel="cancelEditDb"
 | 
			
		||||
            :title="dbEditDialog.title"
 | 
			
		||||
            v-model:visible="dbEditDialog.visible"
 | 
			
		||||
            :instance="props.instance"
 | 
			
		||||
            v-model:db="dbEditDialog.data"
 | 
			
		||||
        ></db-edit>
 | 
			
		||||
    </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script lang="ts" setup>
 | 
			
		||||
import { computed, defineAsyncComponent, onMounted, reactive, ref, Ref, toRefs } from 'vue';
 | 
			
		||||
import { computed, defineAsyncComponent, reactive, ref, Ref, toRefs } from 'vue';
 | 
			
		||||
import { dbApi } from './api';
 | 
			
		||||
import config from '@/common/config';
 | 
			
		||||
import { joinClientParams } from '@/common/request';
 | 
			
		||||
import { isTrue } from '@/common/assert';
 | 
			
		||||
import { dateFormat } from '@/common/utils/date';
 | 
			
		||||
import PageTable from '@/components/pagetable/PageTable.vue';
 | 
			
		||||
import { TableColumn } from '@/components/pagetable';
 | 
			
		||||
import { hasPerms } from '@/components/auth/auth';
 | 
			
		||||
import DbSqlExecLog from './DbSqlExecLog.vue';
 | 
			
		||||
import { DbType } from './dialect';
 | 
			
		||||
import { TagResourceTypeEnum } from '@/common/commonEnum';
 | 
			
		||||
import { useRoute } from 'vue-router';
 | 
			
		||||
import { getDbDialect } from './dialect/index';
 | 
			
		||||
import { getTagPathSearchItem } from '../component/tag';
 | 
			
		||||
import { SearchItem } from '@/components/SearchForm';
 | 
			
		||||
import DbBackupList from './DbBackupList.vue';
 | 
			
		||||
import DbBackupHistoryList from './DbBackupHistoryList.vue';
 | 
			
		||||
import DbRestoreList from './DbRestoreList.vue';
 | 
			
		||||
import ResourceTags from '../component/ResourceTags.vue';
 | 
			
		||||
import { sleep } from '@/common/utils/loading';
 | 
			
		||||
import { DbGetDbNamesMode } from './enums';
 | 
			
		||||
import { DbInst } from './db';
 | 
			
		||||
import { ElMessage, ElMessageBox } from 'element-plus';
 | 
			
		||||
import DrawerHeader from '@/components/drawer-header/DrawerHeader.vue';
 | 
			
		||||
 | 
			
		||||
const DbEdit = defineAsyncComponent(() => import('./DbEdit.vue'));
 | 
			
		||||
 | 
			
		||||
const searchItems = [
 | 
			
		||||
    getTagPathSearchItem(TagResourceTypeEnum.DbName.value),
 | 
			
		||||
    SearchItem.slot('instanceId', '实例', 'instanceSelect'),
 | 
			
		||||
    SearchItem.input('code', '编号'),
 | 
			
		||||
];
 | 
			
		||||
const props = defineProps({
 | 
			
		||||
    instance: {
 | 
			
		||||
        type: [Object],
 | 
			
		||||
        required: true,
 | 
			
		||||
    },
 | 
			
		||||
    title: {
 | 
			
		||||
        type: String,
 | 
			
		||||
    },
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const dialogVisible = defineModel<boolean>('visible');
 | 
			
		||||
 | 
			
		||||
const emit = defineEmits(['cancel']);
 | 
			
		||||
 | 
			
		||||
const columns = ref([
 | 
			
		||||
    TableColumn.new('tags[0].tagPath', '关联标签').isSlot('tagPath').setAddWidth(20),
 | 
			
		||||
    TableColumn.new('name', '名称'),
 | 
			
		||||
    TableColumn.new('type', '类型').isSlot().setAddWidth(-15).alignCenter(),
 | 
			
		||||
    TableColumn.new('instanceName', '实例名'),
 | 
			
		||||
    TableColumn.new('host', 'ip:port').isSlot().setAddWidth(40),
 | 
			
		||||
    TableColumn.new('authCertName', '授权凭证'),
 | 
			
		||||
    TableColumn.new('getDatabaseMode', '获库方式').typeTag(DbGetDbNamesMode),
 | 
			
		||||
    TableColumn.new('database', '库').isSlot().setMinWidth(80),
 | 
			
		||||
    TableColumn.new('flowProcdefKey', '关联流程'),
 | 
			
		||||
    TableColumn.new('remark', '备注'),
 | 
			
		||||
    TableColumn.new('code', '编号'),
 | 
			
		||||
    TableColumn.new('action', '操作').isSlot().setMinWidth(210).fixedRight().alignCenter(),
 | 
			
		||||
]);
 | 
			
		||||
 | 
			
		||||
const perms = {
 | 
			
		||||
    base: 'db',
 | 
			
		||||
    saveDb: 'db:save',
 | 
			
		||||
    delDb: 'db:del',
 | 
			
		||||
    backupDb: 'db:backup',
 | 
			
		||||
    restoreDb: 'db:restore',
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// 该用户拥有的的操作列按钮权限
 | 
			
		||||
// const actionBtns = hasPerms([perms.base, perms.saveDb]);
 | 
			
		||||
const actionBtns = hasPerms(Object.values(perms));
 | 
			
		||||
const actionColumn = TableColumn.new('action', '操作').isSlot().setMinWidth(180).fixedRight().alignCenter();
 | 
			
		||||
const actionBtns: any = hasPerms(Object.values(perms));
 | 
			
		||||
 | 
			
		||||
const route = useRoute();
 | 
			
		||||
const pageTableRef: Ref<any> = ref(null);
 | 
			
		||||
const state = reactive({
 | 
			
		||||
    row: {} as any,
 | 
			
		||||
    dbId: 0,
 | 
			
		||||
    db: '',
 | 
			
		||||
    currentDbs: '',
 | 
			
		||||
    loadingDbNames: false,
 | 
			
		||||
    currentDbNames: [],
 | 
			
		||||
    dbNameSearch: '',
 | 
			
		||||
    instances: [] as any,
 | 
			
		||||
    /**
 | 
			
		||||
     * 选中的数据
 | 
			
		||||
     */
 | 
			
		||||
    selectionData: [],
 | 
			
		||||
    selectionData: [] as any,
 | 
			
		||||
    /**
 | 
			
		||||
     * 查询条件
 | 
			
		||||
     */
 | 
			
		||||
    query: {
 | 
			
		||||
        tagPath: '',
 | 
			
		||||
        instanceId: null,
 | 
			
		||||
        instanceId: 0,
 | 
			
		||||
        pageNum: 1,
 | 
			
		||||
        pageSize: 0,
 | 
			
		||||
    },
 | 
			
		||||
    infoDialog: {
 | 
			
		||||
        visible: false,
 | 
			
		||||
        data: null as any,
 | 
			
		||||
        instance: null as any,
 | 
			
		||||
        query: {
 | 
			
		||||
            instanceId: 0,
 | 
			
		||||
        },
 | 
			
		||||
    },
 | 
			
		||||
    // sql执行记录弹框
 | 
			
		||||
    sqlExecLogDialog: {
 | 
			
		||||
        title: '',
 | 
			
		||||
        visible: false,
 | 
			
		||||
        dbs: [],
 | 
			
		||||
        dbs: [] as any,
 | 
			
		||||
        dbId: 0,
 | 
			
		||||
    },
 | 
			
		||||
    // 数据库备份弹框
 | 
			
		||||
@@ -320,6 +309,7 @@ const state = reactive({
 | 
			
		||||
    exportDialog: {
 | 
			
		||||
        visible: false,
 | 
			
		||||
        dbId: 0,
 | 
			
		||||
        db: '',
 | 
			
		||||
        type: 3,
 | 
			
		||||
        data: [] as any,
 | 
			
		||||
        value: [],
 | 
			
		||||
@@ -338,64 +328,77 @@ const state = reactive({
 | 
			
		||||
    },
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const { db, query, infoDialog, sqlExecLogDialog, exportDialog, dbEditDialog, dbBackupDialog, dbBackupHistoryDialog, dbRestoreDialog } = toRefs(state);
 | 
			
		||||
const { query, sqlExecLogDialog, exportDialog, dbEditDialog, dbBackupDialog, dbBackupHistoryDialog, dbRestoreDialog } = toRefs(state);
 | 
			
		||||
 | 
			
		||||
onMounted(async () => {
 | 
			
		||||
    if (Object.keys(actionBtns).length > 0) {
 | 
			
		||||
        columns.value.push(actionColumn);
 | 
			
		||||
const search = async () => {
 | 
			
		||||
    state.query.instanceId = props.instance?.id;
 | 
			
		||||
    pageTableRef.value.search();
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const getDbNames = async (db: any) => {
 | 
			
		||||
    try {
 | 
			
		||||
        state.loadingDbNames = true;
 | 
			
		||||
        state.currentDbNames = await DbInst.getDbNames(db);
 | 
			
		||||
    } finally {
 | 
			
		||||
        state.loadingDbNames = false;
 | 
			
		||||
    }
 | 
			
		||||
    search();
 | 
			
		||||
});
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const filterDbs = computed(() => {
 | 
			
		||||
    const dbsStr = state.currentDbs;
 | 
			
		||||
    if (!dbsStr) {
 | 
			
		||||
    const dbNames = state.currentDbNames;
 | 
			
		||||
    if (!dbNames) {
 | 
			
		||||
        return [];
 | 
			
		||||
    }
 | 
			
		||||
    const dbs = dbsStr.split(' ').map((db: any) => {
 | 
			
		||||
        return { dbName: db };
 | 
			
		||||
    const dbNameObjs = dbNames.map((x) => {
 | 
			
		||||
        return {
 | 
			
		||||
            dbName: x,
 | 
			
		||||
        };
 | 
			
		||||
    });
 | 
			
		||||
    return dbs.filter((db: any) => {
 | 
			
		||||
    return dbNameObjs.filter((db: any) => {
 | 
			
		||||
        return db.dbName.includes(state.dbNameSearch);
 | 
			
		||||
    });
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const checkRouteTagPath = (query: any) => {
 | 
			
		||||
    if (route.query.tagPath) {
 | 
			
		||||
        query.tagPath = route.query.tagPath as string;
 | 
			
		||||
    }
 | 
			
		||||
    return query;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const search = async (tagPath: string = '') => {
 | 
			
		||||
    if (tagPath) {
 | 
			
		||||
        state.query.tagPath = tagPath;
 | 
			
		||||
    }
 | 
			
		||||
    pageTableRef.value.search();
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const showInfo = async (info: any) => {
 | 
			
		||||
    state.infoDialog.data = info;
 | 
			
		||||
    state.infoDialog.query.instanceId = info.instanceId;
 | 
			
		||||
    const res = await dbApi.getInstance.request(state.infoDialog.query);
 | 
			
		||||
    state.infoDialog.instance = res;
 | 
			
		||||
    state.infoDialog.visible = true;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const onBeforeCloseInfoDialog = () => {
 | 
			
		||||
    state.infoDialog.visible = false;
 | 
			
		||||
    state.infoDialog.data = null;
 | 
			
		||||
    state.infoDialog.instance = null;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const getInstances = async (instanceName = '') => {
 | 
			
		||||
    if (!instanceName) {
 | 
			
		||||
        state.instances = [];
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
    const data = await dbApi.instances.request({ name: instanceName });
 | 
			
		||||
const editDb = (data: any) => {
 | 
			
		||||
    if (data) {
 | 
			
		||||
        state.instances = data.list;
 | 
			
		||||
        state.dbEditDialog.data = { ...data };
 | 
			
		||||
    } else {
 | 
			
		||||
        state.dbEditDialog.data = {
 | 
			
		||||
            instanceId: props.instance.id,
 | 
			
		||||
        };
 | 
			
		||||
    }
 | 
			
		||||
    state.dbEditDialog.title = data ? '编辑数据库' : '新增数据库';
 | 
			
		||||
    state.dbEditDialog.visible = true;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const confirmEditDb = async (db: any) => {
 | 
			
		||||
    db.instanceId = props.instance.id;
 | 
			
		||||
    await dbApi.saveDb.request(db);
 | 
			
		||||
    ElMessage.success('保存成功');
 | 
			
		||||
    search();
 | 
			
		||||
    cancelEditDb();
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const cancelEditDb = () => {
 | 
			
		||||
    state.dbEditDialog.visible = false;
 | 
			
		||||
    state.dbEditDialog.data = {};
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const deleteDb = async () => {
 | 
			
		||||
    try {
 | 
			
		||||
        await ElMessageBox.confirm(`确定删除【${state.selectionData.map((x: any) => x.name).join(', ')}】库?`, '提示', {
 | 
			
		||||
            confirmButtonText: '确定',
 | 
			
		||||
            cancelButtonText: '取消',
 | 
			
		||||
            type: 'warning',
 | 
			
		||||
        });
 | 
			
		||||
        for (let db of state.selectionData) {
 | 
			
		||||
            await dbApi.deleteDb.request({ id: db.id });
 | 
			
		||||
        }
 | 
			
		||||
        ElMessage.success('删除成功');
 | 
			
		||||
    } catch (err) {
 | 
			
		||||
        //
 | 
			
		||||
    } finally {
 | 
			
		||||
        search();
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@@ -403,10 +406,6 @@ const handleMoreActionCommand = (commond: any) => {
 | 
			
		||||
    const data = commond.data;
 | 
			
		||||
    const type = commond.type;
 | 
			
		||||
    switch (type) {
 | 
			
		||||
        case 'detail': {
 | 
			
		||||
            showInfo(data);
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        case 'dumpDb': {
 | 
			
		||||
            onDumpDbs(data);
 | 
			
		||||
            return;
 | 
			
		||||
@@ -429,7 +428,9 @@ const handleMoreActionCommand = (commond: any) => {
 | 
			
		||||
const onShowSqlExec = async (row: any) => {
 | 
			
		||||
    state.sqlExecLogDialog.title = `${row.name}`;
 | 
			
		||||
    state.sqlExecLogDialog.dbId = row.id;
 | 
			
		||||
    state.sqlExecLogDialog.dbs = row.database.split(' ');
 | 
			
		||||
    DbInst.getDbNames(row).then((res) => {
 | 
			
		||||
        state.sqlExecLogDialog.dbs = res;
 | 
			
		||||
    });
 | 
			
		||||
    state.sqlExecLogDialog.visible = true;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@@ -442,26 +443,32 @@ const onBeforeCloseSqlExecDialog = () => {
 | 
			
		||||
const onShowDbBackupDialog = async (row: any) => {
 | 
			
		||||
    state.dbBackupDialog.title = `${row.name}`;
 | 
			
		||||
    state.dbBackupDialog.dbId = row.id;
 | 
			
		||||
    state.dbBackupDialog.dbs = row.database.split(' ');
 | 
			
		||||
    DbInst.getDbNames(row).then((res) => {
 | 
			
		||||
        state.sqlExecLogDialog.dbs = res;
 | 
			
		||||
    });
 | 
			
		||||
    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(' ');
 | 
			
		||||
    DbInst.getDbNames(row).then((res) => {
 | 
			
		||||
        state.sqlExecLogDialog.dbs = res;
 | 
			
		||||
    });
 | 
			
		||||
    state.dbBackupHistoryDialog.visible = true;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const onShowDbRestoreDialog = async (row: any) => {
 | 
			
		||||
    state.dbRestoreDialog.title = `${row.name}`;
 | 
			
		||||
    state.dbRestoreDialog.dbId = row.id;
 | 
			
		||||
    state.dbRestoreDialog.dbs = row.database.split(' ');
 | 
			
		||||
    DbInst.getDbNames(row).then((res) => {
 | 
			
		||||
        state.sqlExecLogDialog.dbs = res;
 | 
			
		||||
    });
 | 
			
		||||
    state.dbRestoreDialog.visible = true;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const onDumpDbs = async (row: any) => {
 | 
			
		||||
    const dbs = row.database.split(' ');
 | 
			
		||||
    const dbs = await DbInst.getDbNames(row);
 | 
			
		||||
    const data = [];
 | 
			
		||||
    for (let name of dbs) {
 | 
			
		||||
        data.push({
 | 
			
		||||
@@ -469,6 +476,7 @@ const onDumpDbs = async (row: any) => {
 | 
			
		||||
            label: name,
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
    state.exportDialog.db = row.name;
 | 
			
		||||
    state.exportDialog.value = [];
 | 
			
		||||
    state.exportDialog.data = data;
 | 
			
		||||
    state.exportDialog.dbId = row.id;
 | 
			
		||||
@@ -512,7 +520,10 @@ const supportAction = (action: string, dbType: string): boolean => {
 | 
			
		||||
    return actions.includes(action);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
defineExpose({ search });
 | 
			
		||||
const cancel = () => {
 | 
			
		||||
    dialogVisible.value = false;
 | 
			
		||||
    emit('cancel');
 | 
			
		||||
};
 | 
			
		||||
</script>
 | 
			
		||||
<style lang="scss">
 | 
			
		||||
.db-list {
 | 
			
		||||
 
 | 
			
		||||
@@ -45,14 +45,14 @@
 | 
			
		||||
            <el-descriptions :column="1" border>
 | 
			
		||||
                <el-descriptions-item :span="1" label="数据库名称">{{ infoDialog.data.dbName }}</el-descriptions-item>
 | 
			
		||||
                <el-descriptions-item v-if="infoDialog.data.pointInTime" :span="1" label="恢复时间点">{{
 | 
			
		||||
                    dateFormat(infoDialog.data.pointInTime)
 | 
			
		||||
                    formatDate(infoDialog.data.pointInTime)
 | 
			
		||||
                }}</el-descriptions-item>
 | 
			
		||||
                <el-descriptions-item v-if="!infoDialog.data.pointInTime" :span="1" label="数据库备份">{{
 | 
			
		||||
                    infoDialog.data.dbBackupHistoryName
 | 
			
		||||
                }}</el-descriptions-item>
 | 
			
		||||
                <el-descriptions-item :span="1" label="开始时间">{{ dateFormat(infoDialog.data.startTime) }}</el-descriptions-item>
 | 
			
		||||
                <el-descriptions-item :span="1" label="开始时间">{{ formatDate(infoDialog.data.startTime) }}</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="执行时间">{{ formatDate(infoDialog.data.lastTime) }}</el-descriptions-item>
 | 
			
		||||
                <el-descriptions-item :span="1" label="执行结果">{{ infoDialog.data.lastResult }}</el-descriptions-item>
 | 
			
		||||
            </el-descriptions>
 | 
			
		||||
        </el-dialog>
 | 
			
		||||
@@ -66,7 +66,7 @@ import PageTable from '@/components/pagetable/PageTable.vue';
 | 
			
		||||
import { TableColumn } from '@/components/pagetable';
 | 
			
		||||
import { SearchItem } from '@/components/SearchForm';
 | 
			
		||||
import { ElMessage, ElMessageBox } from 'element-plus';
 | 
			
		||||
import { dateFormat } from '@/common/utils/date';
 | 
			
		||||
import { formatDate } from '@/common/utils/format';
 | 
			
		||||
const DbRestoreEdit = defineAsyncComponent(() => import('./DbRestoreEdit.vue'));
 | 
			
		||||
const pageTableRef: Ref<any> = ref(null);
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,81 +1,137 @@
 | 
			
		||||
<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-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-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-divider content-position="left">基本信息</el-divider>
 | 
			
		||||
 | 
			
		||||
                        <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="taskName" label="任务名" required>
 | 
			
		||||
                    <el-input v-model.trim="form.taskName" placeholder="请输入任务名" auto-complete="off" />
 | 
			
		||||
                </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>
 | 
			
		||||
                    <el-row class="w100">
 | 
			
		||||
                        <el-col :span="12">
 | 
			
		||||
                            <el-form-item prop="status" label="启用状态">
 | 
			
		||||
                                <el-switch v-model="form.status" inline-prompt active-text="启用" inactive-text="禁用" :active-value="1" :inactive-value="-1" />
 | 
			
		||||
                            </el-form-item>
 | 
			
		||||
                        </el-col>
 | 
			
		||||
 | 
			
		||||
                        <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-col :span="12">
 | 
			
		||||
                            <el-form-item prop="cronAble" label="定时迁移" required>
 | 
			
		||||
                                <el-radio-group v-model="form.cronAble">
 | 
			
		||||
                                    <el-radio label="是" :value="1" />
 | 
			
		||||
                                    <el-radio label="否" :value="-1" />
 | 
			
		||||
                                </el-radio-group>
 | 
			
		||||
                            </el-form-item>
 | 
			
		||||
                        </el-col>
 | 
			
		||||
                    </el-row>
 | 
			
		||||
                </el-form-item>
 | 
			
		||||
 | 
			
		||||
                <el-form-item prop="cron" label="cron" :required="form.cronAble == 1">
 | 
			
		||||
                    <CrontabInput v-model="form.cron" />
 | 
			
		||||
                </el-form-item>
 | 
			
		||||
 | 
			
		||||
                <el-form-item prop="srcDbId" label="源数据库" class="w100" 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="mode" label="迁移方式" required>
 | 
			
		||||
                    <el-radio-group v-model="form.mode">
 | 
			
		||||
                        <el-radio label="迁移到数据库" :value="1" />
 | 
			
		||||
                        <el-radio label="迁移到文件(自动命名)" :value="2" />
 | 
			
		||||
                    </el-radio-group>
 | 
			
		||||
                </el-form-item>
 | 
			
		||||
 | 
			
		||||
                <el-form-item v-if="form.mode === 2">
 | 
			
		||||
                    <el-row class="w100">
 | 
			
		||||
                        <el-col :span="12">
 | 
			
		||||
                            <el-form-item prop="targetFileDbType" label="文件数据库类型" :required="form.mode === 2">
 | 
			
		||||
                                <el-select v-model="form.targetFileDbType" placeholder="数据库类型" clearable filterable>
 | 
			
		||||
                                    <el-option
 | 
			
		||||
                                        v-for="(dbTypeAndDialect, key) in getDbDialectMap()"
 | 
			
		||||
                                        :key="key"
 | 
			
		||||
                                        :value="dbTypeAndDialect[0]"
 | 
			
		||||
                                        :label="dbTypeAndDialect[1].getInfo().name"
 | 
			
		||||
                                    >
 | 
			
		||||
                                        <SvgIcon :name="dbTypeAndDialect[1].getInfo().icon" :size="20" />
 | 
			
		||||
                                        {{ dbTypeAndDialect[1].getInfo().name }}
 | 
			
		||||
                                    </el-option>
 | 
			
		||||
                                    <template #prefix>
 | 
			
		||||
                                        <SvgIcon :name="getDbDialect(form.targetFileDbType!).getInfo().icon" :size="20" />
 | 
			
		||||
                                    </template>
 | 
			
		||||
                                </el-select>
 | 
			
		||||
                            </el-form-item>
 | 
			
		||||
                        </el-col>
 | 
			
		||||
 | 
			
		||||
                        <el-col :span="12">
 | 
			
		||||
                            <el-form-item label="文件保留天数">
 | 
			
		||||
                                <el-input-number v-model="form.fileSaveDays" :min="-1" :max="1000">
 | 
			
		||||
                                    <template #suffix>
 | 
			
		||||
                                        <span>天</span>
 | 
			
		||||
                                    </template>
 | 
			
		||||
                                </el-input-number>
 | 
			
		||||
                            </el-form-item>
 | 
			
		||||
                        </el-col>
 | 
			
		||||
                    </el-row>
 | 
			
		||||
                </el-form-item>
 | 
			
		||||
 | 
			
		||||
                <el-form-item prop="strategy" label="迁移策略" required>
 | 
			
		||||
                    <el-radio-group v-model="form.strategy">
 | 
			
		||||
                        <el-radio label="全量" :value="1" />
 | 
			
		||||
                        <el-radio label="增量(暂不可用)" :value="2" disabled />
 | 
			
		||||
                    </el-radio-group>
 | 
			
		||||
                </el-form-item>
 | 
			
		||||
 | 
			
		||||
                <el-form-item v-if="form.mode == 1" prop="targetDbId" label="目标数据库" class="w100" :required="form.mode === 1">
 | 
			
		||||
                    <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="nameCase" label="转换表、字段名" required>
 | 
			
		||||
                    <el-radio-group v-model="form.nameCase">
 | 
			
		||||
                        <el-radio label="无" :value="1" />
 | 
			
		||||
                        <el-radio label="大写" :value="2" />
 | 
			
		||||
                        <el-radio label="小写" :value="3" />
 | 
			
		||||
                    </el-radio-group>
 | 
			
		||||
                </el-form-item>
 | 
			
		||||
 | 
			
		||||
                <el-divider content-position="left">数据库对象</el-divider>
 | 
			
		||||
                <el-form-item>
 | 
			
		||||
                    <el-input v-model="state.filterSrcTableText" placeholder="过滤表" size="small" />
 | 
			
		||||
                </el-form-item>
 | 
			
		||||
                <el-form-item class="w100">
 | 
			
		||||
                    <el-tree
 | 
			
		||||
                        ref="srcTreeRef"
 | 
			
		||||
                        class="w100"
 | 
			
		||||
                        style="max-height: 200px; 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-form>
 | 
			
		||||
 | 
			
		||||
            <template #footer>
 | 
			
		||||
@@ -84,15 +140,20 @@
 | 
			
		||||
                    <el-button type="primary" :loading="saveBtnLoading" @click="btnOk">确 定</el-button>
 | 
			
		||||
                </div>
 | 
			
		||||
            </template>
 | 
			
		||||
        </el-dialog>
 | 
			
		||||
        </el-drawer>
 | 
			
		||||
    </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script lang="ts" setup>
 | 
			
		||||
import { computed, nextTick, reactive, ref, toRefs, watch } from 'vue';
 | 
			
		||||
import { 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';
 | 
			
		||||
import CrontabInput from '@/components/crontab/CrontabInput.vue';
 | 
			
		||||
import { getDbDialect, getDbDialectMap } from '@/views/ops/db/dialect';
 | 
			
		||||
import SvgIcon from '@/components/svgIcon/index.vue';
 | 
			
		||||
import _ from 'lodash';
 | 
			
		||||
import DrawerHeader from '@/components/drawer-header/DrawerHeader.vue';
 | 
			
		||||
 | 
			
		||||
const props = defineProps({
 | 
			
		||||
    data: {
 | 
			
		||||
@@ -108,15 +169,56 @@ const emit = defineEmits(['update:visible', 'cancel', 'val-change']);
 | 
			
		||||
 | 
			
		||||
const dialogVisible = defineModel<boolean>('visible', { default: false });
 | 
			
		||||
 | 
			
		||||
const rules = {};
 | 
			
		||||
const rules = {
 | 
			
		||||
    taskName: [
 | 
			
		||||
        {
 | 
			
		||||
            required: true,
 | 
			
		||||
            message: '请输入任务名',
 | 
			
		||||
            trigger: ['change', 'blur'],
 | 
			
		||||
        },
 | 
			
		||||
    ],
 | 
			
		||||
    srcDbId: [
 | 
			
		||||
        {
 | 
			
		||||
            required: true,
 | 
			
		||||
            message: '请选择源库',
 | 
			
		||||
            trigger: ['change', 'blur'],
 | 
			
		||||
        },
 | 
			
		||||
    ],
 | 
			
		||||
    targetDbId: [
 | 
			
		||||
        {
 | 
			
		||||
            required: true,
 | 
			
		||||
            message: '请选择目标库',
 | 
			
		||||
            trigger: ['change', 'blur'],
 | 
			
		||||
        },
 | 
			
		||||
    ],
 | 
			
		||||
    targetFileDbType: [
 | 
			
		||||
        {
 | 
			
		||||
            required: true,
 | 
			
		||||
            message: '请选择目标文件语言类型',
 | 
			
		||||
            trigger: ['change', 'blur'],
 | 
			
		||||
        },
 | 
			
		||||
    ],
 | 
			
		||||
    cron: [
 | 
			
		||||
        {
 | 
			
		||||
            required: true,
 | 
			
		||||
            message: '请选择cron表达式',
 | 
			
		||||
            trigger: ['change', 'blur'],
 | 
			
		||||
        },
 | 
			
		||||
    ],
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const dbForm: any = ref(null);
 | 
			
		||||
 | 
			
		||||
const basicTab = 'basic';
 | 
			
		||||
const tableTab = 'table';
 | 
			
		||||
 | 
			
		||||
type FormData = {
 | 
			
		||||
    id?: number;
 | 
			
		||||
    taskName: string;
 | 
			
		||||
    status: number;
 | 
			
		||||
    cronAble: 1 | -1;
 | 
			
		||||
    cron: string;
 | 
			
		||||
    mode: 1 | 2;
 | 
			
		||||
    targetFileDbType?: string;
 | 
			
		||||
    fileSaveDays?: number;
 | 
			
		||||
    dbType: 1 | 2;
 | 
			
		||||
    srcDbId?: number;
 | 
			
		||||
    srcDbName?: string;
 | 
			
		||||
    srcDbType?: string;
 | 
			
		||||
@@ -136,6 +238,9 @@ type FormData = {
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const basicFormData = {
 | 
			
		||||
    mode: 1,
 | 
			
		||||
    status: 1,
 | 
			
		||||
    cronAble: -1,
 | 
			
		||||
    strategy: 1,
 | 
			
		||||
    nameCase: 1,
 | 
			
		||||
    deleteTable: 1,
 | 
			
		||||
@@ -149,7 +254,6 @@ const srcTableListDisabled = ref(false);
 | 
			
		||||
const defaultKeys = ['tab-check', 'all', 'table-list'];
 | 
			
		||||
 | 
			
		||||
const state = reactive({
 | 
			
		||||
    tabActiveName: 'basic',
 | 
			
		||||
    form: basicFormData,
 | 
			
		||||
    submitForm: {} as any,
 | 
			
		||||
    srcTableFields: [] as string[],
 | 
			
		||||
@@ -172,20 +276,14 @@ const state = reactive({
 | 
			
		||||
    ],
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const { tabActiveName, form, submitForm } = toRefs(state);
 | 
			
		||||
const { 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;
 | 
			
		||||
@@ -196,8 +294,7 @@ watch(dialogVisible, async (newValue: boolean) => {
 | 
			
		||||
        });
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    state.form = props.data as FormData;
 | 
			
		||||
    state.form = _.cloneDeep(props.data) as FormData;
 | 
			
		||||
    let { srcDbId, targetDbId } = state.form;
 | 
			
		||||
 | 
			
		||||
    //  初始化src数据源
 | 
			
		||||
@@ -224,6 +321,10 @@ watch(dialogVisible, async (newValue: boolean) => {
 | 
			
		||||
 | 
			
		||||
    // 初始化勾选迁移表
 | 
			
		||||
    srcTreeRef.value.setCheckedKeys(state.form.checkedKeys.split(','));
 | 
			
		||||
 | 
			
		||||
    // 初始化默认值
 | 
			
		||||
    state.form.cronAble = state.form.cronAble || 0;
 | 
			
		||||
    state.form.mode = state.form.mode || 1;
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
watch(
 | 
			
		||||
@@ -259,6 +360,7 @@ const handleSrcTableCheckChange = (data: { id: string; name: string }, checked:
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    if (data.id && (data.id + '').startsWith('list-item')) {
 | 
			
		||||
        //
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@@ -322,10 +424,4 @@ const cancel = () => {
 | 
			
		||||
    emit('cancel');
 | 
			
		||||
};
 | 
			
		||||
</script>
 | 
			
		||||
<style lang="scss">
 | 
			
		||||
.db-transfer-edit {
 | 
			
		||||
    .el-select {
 | 
			
		||||
        width: 100%;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
<style lang="scss"></style>
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										247
									
								
								mayfly_go_web/src/views/ops/db/DbTransferFile.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										247
									
								
								mayfly_go_web/src/views/ops/db/DbTransferFile.vue
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,247 @@
 | 
			
		||||
<template>
 | 
			
		||||
    <div class="db-transfer-file">
 | 
			
		||||
        <el-dialog :title="title" v-model="dialogVisible" :close-on-click-modal="false" :destroy-on-close="true" width="1000px">
 | 
			
		||||
            <page-table
 | 
			
		||||
                ref="pageTableRef"
 | 
			
		||||
                :data="state.tableData"
 | 
			
		||||
                v-model:query-form="state.query"
 | 
			
		||||
                :show-selection="true"
 | 
			
		||||
                v-model:selection-data="state.selectionData"
 | 
			
		||||
                :columns="columns"
 | 
			
		||||
                @page-num-change="
 | 
			
		||||
                    (args) => {
 | 
			
		||||
                        state.query.pageNum = args.pageNum;
 | 
			
		||||
                        search();
 | 
			
		||||
                    }
 | 
			
		||||
                "
 | 
			
		||||
                @page-size-change="
 | 
			
		||||
                    (args) => {
 | 
			
		||||
                        state.query.pageSize = args.pageNum;
 | 
			
		||||
                        search();
 | 
			
		||||
                    }
 | 
			
		||||
                "
 | 
			
		||||
            >
 | 
			
		||||
                <template #tableHeader>
 | 
			
		||||
                    <el-button v-auth="perms.del" :disabled="state.selectionData.length < 1" @click="del()" type="danger" icon="delete">删除</el-button>
 | 
			
		||||
                </template>
 | 
			
		||||
 | 
			
		||||
                <template #fileKey="{ data }">
 | 
			
		||||
                    <FileInfo :fileKey="data.fileKey" :canDownload="actionBtns[perms.down] && data.status === 2" />
 | 
			
		||||
                </template>
 | 
			
		||||
 | 
			
		||||
                <template #fileDbType="{ data }">
 | 
			
		||||
                    <span>
 | 
			
		||||
                        <SvgIcon :name="getDbDialect(data.fileDbType).getInfo().icon" :size="18" />
 | 
			
		||||
                        {{ data.fileDbType }}
 | 
			
		||||
                    </span>
 | 
			
		||||
                </template>
 | 
			
		||||
 | 
			
		||||
                <template #action="{ data }">
 | 
			
		||||
                    <el-button v-if="actionBtns[perms.run] && data.status === DbTransferFileStatusEnum.Success.value" @click="openRun(data)" type="primary" link
 | 
			
		||||
                        >执行</el-button
 | 
			
		||||
                    >
 | 
			
		||||
                    <el-button v-if="data.logId" @click="openLog(data)" type="success" link>日志</el-button>
 | 
			
		||||
                </template>
 | 
			
		||||
            </page-table>
 | 
			
		||||
            <TerminalLog v-model:log-id="state.logsDialog.logId" v-model:visible="state.logsDialog.visible" :title="state.logsDialog.title" />
 | 
			
		||||
        </el-dialog>
 | 
			
		||||
 | 
			
		||||
        <el-dialog :title="state.runDialog.title" v-model="state.runDialog.visible" :destroy-on-close="true" width="600px">
 | 
			
		||||
            <el-form :model="state.runDialog.runForm" ref="runFormRef" label-width="auto" :rules="state.runDialog.formRules">
 | 
			
		||||
                <el-form-item label="文件数据库类型" prop="dbType">
 | 
			
		||||
                    <SvgIcon :name="getDbDialect(state.runDialog.runForm.dbType).getInfo().icon" :size="18" /> {{ state.runDialog.runForm.dbType }}
 | 
			
		||||
                </el-form-item>
 | 
			
		||||
                <el-form-item label="选择目标数据库" prop="targetDbId" required>
 | 
			
		||||
                    <db-select-tree
 | 
			
		||||
                        placeholder="请选择目标数据库"
 | 
			
		||||
                        v-model:db-id="state.runDialog.runForm.targetDbId"
 | 
			
		||||
                        v-model:inst-name="state.runDialog.runForm.targetInstName"
 | 
			
		||||
                        v-model:db-name="state.runDialog.runForm.targetDbName"
 | 
			
		||||
                        v-model:tag-path="state.runDialog.runForm.targetTagPath"
 | 
			
		||||
                        v-model:db-type="state.runDialog.runForm.targetDbType"
 | 
			
		||||
                        @select-db="state.runDialog.onSelectRunTargetDb"
 | 
			
		||||
                    />
 | 
			
		||||
                </el-form-item>
 | 
			
		||||
            </el-form>
 | 
			
		||||
 | 
			
		||||
            <template #footer>
 | 
			
		||||
                <div>
 | 
			
		||||
                    <el-button @click="state.runDialog.cancel()">取 消</el-button>
 | 
			
		||||
                    <el-button type="primary" :loading="state.runDialog.loading" @click="state.runDialog.btnOk">确 定</el-button>
 | 
			
		||||
                </div>
 | 
			
		||||
            </template>
 | 
			
		||||
        </el-dialog>
 | 
			
		||||
    </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script lang="ts" setup>
 | 
			
		||||
import { onMounted, reactive, Ref, ref, watch } from 'vue';
 | 
			
		||||
import { dbApi } from '@/views/ops/db/api';
 | 
			
		||||
import { getDbDialect } from '@/views/ops/db/dialect';
 | 
			
		||||
import PageTable from '@/components/pagetable/PageTable.vue';
 | 
			
		||||
import { TableColumn } from '@/components/pagetable';
 | 
			
		||||
import { ElMessage, ElMessageBox } from 'element-plus';
 | 
			
		||||
import { hasPerms } from '@/components/auth/auth';
 | 
			
		||||
import TerminalLog from '@/components/terminal/TerminalLog.vue';
 | 
			
		||||
import DbSelectTree from '@/views/ops/db/component/DbSelectTree.vue';
 | 
			
		||||
import { getClientId } from '@/common/utils/storage';
 | 
			
		||||
import FileInfo from '@/components/file/FileInfo.vue';
 | 
			
		||||
import { DbTransferFileStatusEnum } from './enums';
 | 
			
		||||
const props = defineProps({
 | 
			
		||||
    data: {
 | 
			
		||||
        type: [Object],
 | 
			
		||||
    },
 | 
			
		||||
    title: {
 | 
			
		||||
        type: String,
 | 
			
		||||
    },
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const dialogVisible = defineModel<boolean>('visible', { default: false });
 | 
			
		||||
 | 
			
		||||
const columns = ref([
 | 
			
		||||
    TableColumn.new('fileKey', '文件').setMinWidth(280).isSlot(),
 | 
			
		||||
    TableColumn.new('createTime', '执行时间').setMinWidth(180).isTime(),
 | 
			
		||||
    TableColumn.new('fileDbType', 'sql语言').setMinWidth(90).isSlot(),
 | 
			
		||||
    TableColumn.new('status', '状态').typeTag(DbTransferFileStatusEnum),
 | 
			
		||||
]);
 | 
			
		||||
 | 
			
		||||
const perms = {
 | 
			
		||||
    del: 'db:transfer:files:del',
 | 
			
		||||
    down: 'db:transfer:files:down',
 | 
			
		||||
    run: 'db:transfer:files:run',
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const actionBtns = hasPerms([perms.del, perms.down, perms.run]);
 | 
			
		||||
 | 
			
		||||
const actionWidth = ((actionBtns[perms.run] ? 1 : 0) + 1) * 55;
 | 
			
		||||
 | 
			
		||||
const actionColumn = TableColumn.new('action', '操作').isSlot().setMinWidth(actionWidth).fixedRight().alignCenter();
 | 
			
		||||
 | 
			
		||||
onMounted(async () => {
 | 
			
		||||
    if (Object.keys(actionBtns).length > 0) {
 | 
			
		||||
        columns.value.push(actionColumn);
 | 
			
		||||
    }
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const runFormRef: any = ref(null);
 | 
			
		||||
 | 
			
		||||
const state = reactive({
 | 
			
		||||
    query: {
 | 
			
		||||
        taskId: props.data?.id,
 | 
			
		||||
        name: null,
 | 
			
		||||
        pageNum: 1,
 | 
			
		||||
        pageSize: 10,
 | 
			
		||||
    },
 | 
			
		||||
    logsDialog: {
 | 
			
		||||
        logId: 0,
 | 
			
		||||
        title: '数据库迁移日志',
 | 
			
		||||
        visible: false,
 | 
			
		||||
        data: null as any,
 | 
			
		||||
        running: false,
 | 
			
		||||
    },
 | 
			
		||||
    runDialog: {
 | 
			
		||||
        title: '指定数据库执行sql文件',
 | 
			
		||||
        visible: false,
 | 
			
		||||
        data: null as any,
 | 
			
		||||
        formRules: {
 | 
			
		||||
            targetDbId: [
 | 
			
		||||
                {
 | 
			
		||||
                    required: true,
 | 
			
		||||
                    message: '请选择目标数据库',
 | 
			
		||||
                    trigger: ['change', 'blur'],
 | 
			
		||||
                },
 | 
			
		||||
            ],
 | 
			
		||||
        },
 | 
			
		||||
        runForm: {
 | 
			
		||||
            id: 0,
 | 
			
		||||
            dbType: '',
 | 
			
		||||
            clientId: '',
 | 
			
		||||
            targetDbId: 0,
 | 
			
		||||
            targetDbName: '',
 | 
			
		||||
            targetTagPath: '',
 | 
			
		||||
            targetInstName: '',
 | 
			
		||||
            targetDbType: '',
 | 
			
		||||
        },
 | 
			
		||||
        loading: false,
 | 
			
		||||
        cancel: function () {
 | 
			
		||||
            state.runDialog.visible = false;
 | 
			
		||||
            state.runDialog.runForm = {} as any;
 | 
			
		||||
        },
 | 
			
		||||
        btnOk: function () {
 | 
			
		||||
            runFormRef.value.validate(async (valid: boolean) => {
 | 
			
		||||
                if (!valid) {
 | 
			
		||||
                    ElMessage.error('请正确填写信息');
 | 
			
		||||
                    return false;
 | 
			
		||||
                }
 | 
			
		||||
                console.log(state.runDialog.runForm);
 | 
			
		||||
                if (state.runDialog.runForm.targetDbType !== state.runDialog.runForm.dbType) {
 | 
			
		||||
                    ElMessage.warning(`请选择[${state.runDialog.runForm.dbType}]数据库`);
 | 
			
		||||
                    return false;
 | 
			
		||||
                }
 | 
			
		||||
                state.runDialog.runForm.clientId = getClientId();
 | 
			
		||||
                await dbApi.dbTransferFileRun.request(state.runDialog.runForm);
 | 
			
		||||
                ElMessage.success('保存成功');
 | 
			
		||||
                state.runDialog.cancel();
 | 
			
		||||
                await search();
 | 
			
		||||
            });
 | 
			
		||||
        },
 | 
			
		||||
        onSelectRunTargetDb: function (param: any) {
 | 
			
		||||
            if (param.type !== state.runDialog.runForm.dbType) {
 | 
			
		||||
                ElMessage.warning(`请选择[${state.runDialog.runForm.dbType}]数据库`);
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
    },
 | 
			
		||||
    selectionData: [], // 选中的数据
 | 
			
		||||
    tableData: [],
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const search = async () => {
 | 
			
		||||
    const { total, list } = await dbApi.dbTransferFileList.request(state.query);
 | 
			
		||||
    state.tableData = list;
 | 
			
		||||
    pageTableRef.value.total = total;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const pageTableRef: Ref<any> = ref(null);
 | 
			
		||||
 | 
			
		||||
const del = async function () {
 | 
			
		||||
    try {
 | 
			
		||||
        await ElMessageBox.confirm(`将会删除sql文件,确定删除?`, '提示', {
 | 
			
		||||
            confirmButtonText: '确定',
 | 
			
		||||
            cancelButtonText: '取消',
 | 
			
		||||
            type: 'warning',
 | 
			
		||||
        });
 | 
			
		||||
        await dbApi.dbTransferFileDel.request({ fileId: state.selectionData.map((x: any) => x.id).join(',') });
 | 
			
		||||
        ElMessage.success('删除成功');
 | 
			
		||||
        await search();
 | 
			
		||||
    } catch (err) {
 | 
			
		||||
        //
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const openLog = function (data: any) {
 | 
			
		||||
    state.logsDialog.logId = data.logId;
 | 
			
		||||
    state.logsDialog.visible = true;
 | 
			
		||||
    state.logsDialog.title = '数据库迁移日志';
 | 
			
		||||
    state.logsDialog.running = data.state === 1;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// 运行sql,弹出选择需要运行的库,默认运行当前数据库,需要保证数据库类型与sql文件一致
 | 
			
		||||
const openRun = function (data: any) {
 | 
			
		||||
    console.log(data);
 | 
			
		||||
    state.runDialog.runForm = { id: data.id, dbType: data.fileDbType } as any;
 | 
			
		||||
    console.log(state.runDialog.runForm);
 | 
			
		||||
    state.runDialog.visible = true;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
watch(dialogVisible, async (newValue: boolean) => {
 | 
			
		||||
    if (!newValue) {
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
    state.query.taskId = props.data?.id;
 | 
			
		||||
    state.query.pageNum = 1;
 | 
			
		||||
    state.query.pageSize = 10;
 | 
			
		||||
 | 
			
		||||
    await search();
 | 
			
		||||
});
 | 
			
		||||
</script>
 | 
			
		||||
<style lang="scss"></style>
 | 
			
		||||
@@ -14,11 +14,16 @@
 | 
			
		||||
                <el-button v-auth="perms.del" :disabled="selectionData.length < 1" @click="del()" type="danger" icon="delete">删除</el-button>
 | 
			
		||||
            </template>
 | 
			
		||||
 | 
			
		||||
            <template #taskName="{ data }">
 | 
			
		||||
                <span :style="`${data.taskName ? '' : 'color:red'}`">
 | 
			
		||||
                    {{ data.taskName || '请设置' }}
 | 
			
		||||
                </span>
 | 
			
		||||
            </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 }}
 | 
			
		||||
                        {{ data.srcDbName }}
 | 
			
		||||
                    </span>
 | 
			
		||||
                </el-tooltip>
 | 
			
		||||
            </template>
 | 
			
		||||
@@ -26,21 +31,43 @@
 | 
			
		||||
                <el-tooltip :content="`${data.targetTagPath} > ${data.targetInstName} > ${data.targetDbName}`">
 | 
			
		||||
                    <span>
 | 
			
		||||
                        <SvgIcon :name="getDbDialect(data.targetDbType).getInfo().icon" :size="18" />
 | 
			
		||||
                        {{ data.targetInstName }}
 | 
			
		||||
                        {{ data.targetDbName }}
 | 
			
		||||
                    </span>
 | 
			
		||||
                </el-tooltip>
 | 
			
		||||
            </template>
 | 
			
		||||
 | 
			
		||||
            <template #status="{ data }">
 | 
			
		||||
                <span v-if="actionBtns[perms.status]">
 | 
			
		||||
                    <el-switch
 | 
			
		||||
                        v-model="data.status"
 | 
			
		||||
                        @click="updStatus(data.id, data.status)"
 | 
			
		||||
                        inline-prompt
 | 
			
		||||
                        active-text="启用"
 | 
			
		||||
                        inactive-text="禁用"
 | 
			
		||||
                        :active-value="1"
 | 
			
		||||
                        :inactive-value="-1"
 | 
			
		||||
                    />
 | 
			
		||||
                </span>
 | 
			
		||||
                <span v-else>
 | 
			
		||||
                    <el-tag v-if="data.status == 1" class="ml-2" type="success">启用</el-tag>
 | 
			
		||||
                    <el-tag v-else class="ml-2" type="danger">禁用</el-tag>
 | 
			
		||||
                </span>
 | 
			
		||||
            </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="actionBtns[perms.log]" type="warning" 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>
 | 
			
		||||
                <el-button v-if="actionBtns[perms.run] && data.runningState !== 1 && data.status === 1" type="success" link @click="reRun(data)"
 | 
			
		||||
                    >运行</el-button
 | 
			
		||||
                >
 | 
			
		||||
                <el-button v-if="actionBtns[perms.files] && data.mode === 2" type="success" link @click="openFiles(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" />
 | 
			
		||||
        <db-transfer-file :title="filesDialog.title" v-model:visible="filesDialog.visible" v-model:data="filesDialog.data" />
 | 
			
		||||
 | 
			
		||||
        <TerminalLog v-model:log-id="logsDialog.logId" v-model:visible="logsDialog.visible" :title="logsDialog.title" />
 | 
			
		||||
    </div>
 | 
			
		||||
@@ -57,6 +84,7 @@ import { SearchItem } from '@/components/SearchForm';
 | 
			
		||||
import { getDbDialect } from '@/views/ops/db/dialect';
 | 
			
		||||
import { DbTransferRunningStateEnum } from './enums';
 | 
			
		||||
import TerminalLog from '@/components/terminal/TerminalLog.vue';
 | 
			
		||||
import DbTransferFile from './DbTransferFile.vue';
 | 
			
		||||
 | 
			
		||||
const DbTransferEdit = defineAsyncComponent(() => import('./DbTransferEdit.vue'));
 | 
			
		||||
 | 
			
		||||
@@ -66,23 +94,25 @@ const perms = {
 | 
			
		||||
    status: 'db:transfer:status',
 | 
			
		||||
    log: 'db:transfer:log',
 | 
			
		||||
    run: 'db:transfer:run',
 | 
			
		||||
    files: 'db:transfer:files',
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const searchItems = [SearchItem.input('name', '名称')];
 | 
			
		||||
 | 
			
		||||
const columns = ref([
 | 
			
		||||
    TableColumn.new('srcDb', '源库').setMinWidth(200).isSlot(),
 | 
			
		||||
    TableColumn.new('targetDb', '目标库').setMinWidth(200).isSlot(),
 | 
			
		||||
    TableColumn.new('taskName', '任务名').setMinWidth(150).isSlot(),
 | 
			
		||||
    TableColumn.new('srcDb', '源库').setMinWidth(150).isSlot(),
 | 
			
		||||
    // TableColumn.new('targetDb', '目标库').setMinWidth(150).isSlot(),
 | 
			
		||||
    TableColumn.new('runningState', '执行状态').typeTag(DbTransferRunningStateEnum),
 | 
			
		||||
    TableColumn.new('creator', '创建人'),
 | 
			
		||||
    TableColumn.new('createTime', '创建时间').isTime(),
 | 
			
		||||
    TableColumn.new('status', '状态').isSlot(),
 | 
			
		||||
    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 actionBtns = hasPerms([perms.save, perms.del, perms.status, perms.log, perms.run, perms.files]);
 | 
			
		||||
const actionWidth =
 | 
			
		||||
    ((actionBtns[perms.save] ? 1 : 0) + (actionBtns[perms.log] ? 1 : 0) + (actionBtns[perms.run] ? 1 : 0) + (actionBtns[perms.files] ? 1 : 0)) * 55;
 | 
			
		||||
const actionColumn = TableColumn.new('action', '操作').isSlot().setMinWidth(actionWidth).fixedRight().alignCenter();
 | 
			
		||||
const pageTableRef: Ref<any> = ref(null);
 | 
			
		||||
 | 
			
		||||
@@ -114,9 +144,15 @@ const state = reactive({
 | 
			
		||||
        data: null as any,
 | 
			
		||||
        running: false,
 | 
			
		||||
    },
 | 
			
		||||
    filesDialog: {
 | 
			
		||||
        taskId: 0,
 | 
			
		||||
        title: '迁移文件列表',
 | 
			
		||||
        visible: false,
 | 
			
		||||
        data: null as any,
 | 
			
		||||
    },
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const { selectionData, query, editDialog, logsDialog } = toRefs(state);
 | 
			
		||||
const { selectionData, query, editDialog, logsDialog, filesDialog } = toRefs(state);
 | 
			
		||||
 | 
			
		||||
onMounted(async () => {
 | 
			
		||||
    if (Object.keys(actionBtns).length > 0) {
 | 
			
		||||
@@ -131,10 +167,10 @@ const search = () => {
 | 
			
		||||
const edit = async (data: any) => {
 | 
			
		||||
    if (!data) {
 | 
			
		||||
        state.editDialog.data = null;
 | 
			
		||||
        state.editDialog.title = '新增数据库迁移任务';
 | 
			
		||||
        state.editDialog.title = '新增数据库迁移任务(迁移不会对源库造成修改)';
 | 
			
		||||
    } else {
 | 
			
		||||
        state.editDialog.data = data;
 | 
			
		||||
        state.editDialog.title = '修改数据库迁移任务';
 | 
			
		||||
        state.editDialog.title = '修改数据库迁移任务(迁移不会对源库造成修改)';
 | 
			
		||||
    }
 | 
			
		||||
    state.editDialog.visible = true;
 | 
			
		||||
};
 | 
			
		||||
@@ -178,6 +214,22 @@ const reRun = async (data: any) => {
 | 
			
		||||
    }, 2000);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const openFiles = async (data: any) => {
 | 
			
		||||
    state.filesDialog.visible = true;
 | 
			
		||||
    state.filesDialog.title = '迁移文件管理';
 | 
			
		||||
    state.filesDialog.taskId = data.id;
 | 
			
		||||
    state.filesDialog.data = data;
 | 
			
		||||
};
 | 
			
		||||
const updStatus = async (id: any, status: 1 | -1) => {
 | 
			
		||||
    try {
 | 
			
		||||
        await dbApi.updateDbTransferTaskStatus.request({ taskId: id, status });
 | 
			
		||||
        ElMessage.success(`${status === 1 ? '启用' : '禁用'}成功`);
 | 
			
		||||
        search();
 | 
			
		||||
    } catch (err) {
 | 
			
		||||
        //
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const del = async () => {
 | 
			
		||||
    try {
 | 
			
		||||
        await ElMessageBox.confirm(`确定删除任务?`, '提示', {
 | 
			
		||||
 
 | 
			
		||||
@@ -1,171 +0,0 @@
 | 
			
		||||
<template>
 | 
			
		||||
    <div>
 | 
			
		||||
        <el-drawer :title="title" v-model="dialogVisible" :before-close="cancel" :destroy-on-close="true" :close-on-click-modal="false" size="50%">
 | 
			
		||||
            <template #header>
 | 
			
		||||
                <DrawerHeader :header="title" :back="cancel" />
 | 
			
		||||
            </template>
 | 
			
		||||
 | 
			
		||||
            <el-table :data="state.dbs" stripe>
 | 
			
		||||
                <el-table-column prop="name" label="名称" show-overflow-tooltip min-width="100"> </el-table-column>
 | 
			
		||||
                <el-table-column prop="authCertName" label="授权凭证" min-width="120" show-overflow-tooltip> </el-table-column>
 | 
			
		||||
                <el-table-column prop="database" label="库" min-width="80">
 | 
			
		||||
                    <template #default="scope">
 | 
			
		||||
                        <el-popover placement="bottom" :width="200" trigger="click">
 | 
			
		||||
                            <template #reference>
 | 
			
		||||
                                <el-button @click="state.currentDbs = scope.row.database" type="primary" link>查看库</el-button>
 | 
			
		||||
                            </template>
 | 
			
		||||
                            <el-table :data="filterDbs" size="small">
 | 
			
		||||
                                <el-table-column prop="dbName" label="数据库">
 | 
			
		||||
                                    <template #header>
 | 
			
		||||
                                        <el-input v-model="state.dbNameSearch" size="small" placeholder="库名: 输入可过滤" clearable />
 | 
			
		||||
                                    </template>
 | 
			
		||||
                                </el-table-column>
 | 
			
		||||
                            </el-table>
 | 
			
		||||
                        </el-popover>
 | 
			
		||||
                    </template>
 | 
			
		||||
                </el-table-column>
 | 
			
		||||
 | 
			
		||||
                <el-table-column prop="remark" label="备注" show-overflow-tooltip min-width="120"> </el-table-column>
 | 
			
		||||
                <el-table-column prop="flowProcdefKey" label="关联流程" min-width="120" show-overflow-tooltip> </el-table-column>
 | 
			
		||||
                <el-table-column prop="code" label="编号" show-overflow-tooltip min-width="120"> </el-table-column>
 | 
			
		||||
                <el-table-column min-wdith="120px">
 | 
			
		||||
                    <template #header>
 | 
			
		||||
                        操作
 | 
			
		||||
                        <el-button v-auth="perms.saveDb" type="primary" circle size="small" icon="Plus" @click="editDb(null)"> </el-button>
 | 
			
		||||
                    </template>
 | 
			
		||||
                    <template #default="scope">
 | 
			
		||||
                        <el-button v-auth="perms.saveDb" @click="editDb(scope.row)" type="primary" icon="edit" link></el-button>
 | 
			
		||||
                        <el-button class="ml1" v-auth="perms.delDb" type="danger" @click="deleteDb(scope.row)" icon="delete" link></el-button>
 | 
			
		||||
                    </template>
 | 
			
		||||
                </el-table-column>
 | 
			
		||||
            </el-table>
 | 
			
		||||
 | 
			
		||||
            <db-edit
 | 
			
		||||
                @confirm="confirmEditDb"
 | 
			
		||||
                @cancel="cancelEditDb"
 | 
			
		||||
                :title="dbEditDialog.title"
 | 
			
		||||
                v-model:visible="dbEditDialog.visible"
 | 
			
		||||
                :instance="props.instance"
 | 
			
		||||
                v-model:db="dbEditDialog.data"
 | 
			
		||||
            ></db-edit>
 | 
			
		||||
        </el-drawer>
 | 
			
		||||
    </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script lang="ts" setup>
 | 
			
		||||
import { computed, reactive, toRefs, watchEffect } from 'vue';
 | 
			
		||||
import { dbApi } from './api';
 | 
			
		||||
import { ElMessage, ElMessageBox } from 'element-plus';
 | 
			
		||||
import DrawerHeader from '@/components/drawer-header/DrawerHeader.vue';
 | 
			
		||||
import DbEdit from './DbEdit.vue';
 | 
			
		||||
 | 
			
		||||
const props = defineProps({
 | 
			
		||||
    visible: {
 | 
			
		||||
        type: Boolean,
 | 
			
		||||
    },
 | 
			
		||||
    instance: {
 | 
			
		||||
        type: [Object],
 | 
			
		||||
        required: true,
 | 
			
		||||
    },
 | 
			
		||||
    title: {
 | 
			
		||||
        type: String,
 | 
			
		||||
    },
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const perms = {
 | 
			
		||||
    base: 'db',
 | 
			
		||||
    saveDb: 'db:save',
 | 
			
		||||
    delDb: 'db:del',
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
//定义事件
 | 
			
		||||
const emit = defineEmits(['update:visible', 'cancel', 'val-change']);
 | 
			
		||||
 | 
			
		||||
const state = reactive({
 | 
			
		||||
    dialogVisible: false,
 | 
			
		||||
    dbs: [] as any,
 | 
			
		||||
    currentDbs: '', // 当前数据库名,空格分割库名
 | 
			
		||||
    dbNameSearch: '',
 | 
			
		||||
    dbEditDialog: {
 | 
			
		||||
        visible: false,
 | 
			
		||||
        data: null as any,
 | 
			
		||||
        title: '新增数据库',
 | 
			
		||||
    },
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const { dialogVisible, dbEditDialog } = toRefs(state);
 | 
			
		||||
 | 
			
		||||
watchEffect(() => {
 | 
			
		||||
    state.dialogVisible = props.visible;
 | 
			
		||||
    if (!state.dialogVisible) {
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    getDbs();
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const filterDbs = computed(() => {
 | 
			
		||||
    const dbsStr = state.currentDbs;
 | 
			
		||||
    if (!dbsStr) {
 | 
			
		||||
        return [];
 | 
			
		||||
    }
 | 
			
		||||
    const dbs = dbsStr.split(' ').map((db: any) => {
 | 
			
		||||
        return { dbName: db };
 | 
			
		||||
    });
 | 
			
		||||
    return dbs.filter((db: any) => {
 | 
			
		||||
        return db.dbName.includes(state.dbNameSearch);
 | 
			
		||||
    });
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const cancel = () => {
 | 
			
		||||
    emit('update:visible', false);
 | 
			
		||||
    emit('cancel');
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const getDbs = () => {
 | 
			
		||||
    dbApi.dbs.request({ pageSize: 200, instanceId: props.instance.id }).then((res: any) => {
 | 
			
		||||
        state.dbs = res.list || [];
 | 
			
		||||
    });
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const editDb = (data: any) => {
 | 
			
		||||
    if (data) {
 | 
			
		||||
        state.dbEditDialog.data = { ...data };
 | 
			
		||||
    } else {
 | 
			
		||||
        state.dbEditDialog.data = {
 | 
			
		||||
            instanceId: props.instance.id,
 | 
			
		||||
        };
 | 
			
		||||
    }
 | 
			
		||||
    state.dbEditDialog.title = data ? '编辑数据库' : '新增数据库';
 | 
			
		||||
    state.dbEditDialog.visible = true;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const deleteDb = async (db: any) => {
 | 
			
		||||
    try {
 | 
			
		||||
        await ElMessageBox.confirm(`确定删除【${db.name}】库?`, '提示', {
 | 
			
		||||
            confirmButtonText: '确定',
 | 
			
		||||
            cancelButtonText: '取消',
 | 
			
		||||
            type: 'warning',
 | 
			
		||||
        });
 | 
			
		||||
        await dbApi.deleteDb.request({ id: db.id });
 | 
			
		||||
        ElMessage.success('删除成功');
 | 
			
		||||
        getDbs();
 | 
			
		||||
    } catch (err) {
 | 
			
		||||
        //
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const confirmEditDb = async (db: any) => {
 | 
			
		||||
    db.instanceId = props.instance.id;
 | 
			
		||||
    await dbApi.saveDb.request(db);
 | 
			
		||||
    ElMessage.success('保存成功');
 | 
			
		||||
    getDbs();
 | 
			
		||||
    cancelEditDb();
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const cancelEditDb = () => {
 | 
			
		||||
    state.dbEditDialog.visible = false;
 | 
			
		||||
    state.dbEditDialog.data = {};
 | 
			
		||||
};
 | 
			
		||||
</script>
 | 
			
		||||
<style lang="scss"></style>
 | 
			
		||||
@@ -22,15 +22,6 @@
 | 
			
		||||
                    />
 | 
			
		||||
                </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-input v-model.trim="form.name" placeholder="请输入数据库别名" auto-complete="off"></el-input>
 | 
			
		||||
                </el-form-item>
 | 
			
		||||
@@ -132,7 +123,6 @@ import SshTunnelSelect from '../component/SshTunnelSelect.vue';
 | 
			
		||||
import { DbType, getDbDialect, getDbDialectMap } from './dialect';
 | 
			
		||||
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';
 | 
			
		||||
@@ -161,18 +151,6 @@ const rules = {
 | 
			
		||||
            trigger: ['change'],
 | 
			
		||||
        },
 | 
			
		||||
    ],
 | 
			
		||||
    code: [
 | 
			
		||||
        {
 | 
			
		||||
            required: true,
 | 
			
		||||
            message: '请输入编码',
 | 
			
		||||
            trigger: ['change', 'blur'],
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
            pattern: ResourceCodePattern.pattern,
 | 
			
		||||
            message: ResourceCodePattern.message,
 | 
			
		||||
            trigger: ['blur'],
 | 
			
		||||
        },
 | 
			
		||||
    ],
 | 
			
		||||
    name: [
 | 
			
		||||
        {
 | 
			
		||||
            required: true,
 | 
			
		||||
 
 | 
			
		||||
@@ -35,7 +35,7 @@
 | 
			
		||||
            <template #action="{ data }">
 | 
			
		||||
                <el-button @click="showInfo(data)" link>详情</el-button>
 | 
			
		||||
                <el-button v-if="actionBtns[perms.saveInstance]" @click="editInstance(data)" type="primary" link>编辑</el-button>
 | 
			
		||||
                <el-button v-if="actionBtns[perms.saveDb]" @click="editDb(data)" type="primary" link>库配置</el-button>
 | 
			
		||||
                <el-button v-if="actionBtns[perms.saveDb]" @click="editDb(data)" type="primary" link>库管理</el-button>
 | 
			
		||||
            </template>
 | 
			
		||||
        </page-table>
 | 
			
		||||
 | 
			
		||||
@@ -53,10 +53,10 @@
 | 
			
		||||
 | 
			
		||||
                <el-descriptions-item :span="3" label="SSH隧道">{{ infoDialog.data.sshTunnelMachineId > 0 ? '是' : '否' }} </el-descriptions-item>
 | 
			
		||||
 | 
			
		||||
                <el-descriptions-item :span="2" label="创建时间">{{ dateFormat(infoDialog.data.createTime) }} </el-descriptions-item>
 | 
			
		||||
                <el-descriptions-item :span="2" label="创建时间">{{ formatDate(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="2" label="更新时间">{{ formatDate(infoDialog.data.updateTime) }} </el-descriptions-item>
 | 
			
		||||
                <el-descriptions-item :span="1" label="修改者">{{ infoDialog.data.modifier }}</el-descriptions-item>
 | 
			
		||||
            </el-descriptions>
 | 
			
		||||
        </el-dialog>
 | 
			
		||||
@@ -68,7 +68,7 @@
 | 
			
		||||
            v-model:data="instanceEditDialog.data"
 | 
			
		||||
        ></instance-edit>
 | 
			
		||||
 | 
			
		||||
        <instance-db-conf :title="dbEditDialog.title" v-model:visible="dbEditDialog.visible" :instance="dbEditDialog.instance" />
 | 
			
		||||
        <DbList :title="dbEditDialog.title" v-model:visible="dbEditDialog.visible" :instance="dbEditDialog.instance" />
 | 
			
		||||
    </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
@@ -76,7 +76,7 @@
 | 
			
		||||
import { defineAsyncComponent, onMounted, reactive, ref, Ref, toRefs } from 'vue';
 | 
			
		||||
import { ElMessage, ElMessageBox } from 'element-plus';
 | 
			
		||||
import { dbApi } from './api';
 | 
			
		||||
import { dateFormat } from '@/common/utils/date';
 | 
			
		||||
import { formatDate } from '@/common/utils/format';
 | 
			
		||||
import PageTable from '@/components/pagetable/PageTable.vue';
 | 
			
		||||
import { TableColumn } from '@/components/pagetable';
 | 
			
		||||
import { hasPerms } from '@/components/auth/auth';
 | 
			
		||||
@@ -89,7 +89,7 @@ import { getTagPathSearchItem } from '../component/tag';
 | 
			
		||||
import { TagResourceTypeEnum } from '@/common/commonEnum';
 | 
			
		||||
 | 
			
		||||
const InstanceEdit = defineAsyncComponent(() => import('./InstanceEdit.vue'));
 | 
			
		||||
const InstanceDbConf = defineAsyncComponent(() => import('./InstanceDbConf.vue'));
 | 
			
		||||
const DbList = defineAsyncComponent(() => import('./DbList.vue'));
 | 
			
		||||
 | 
			
		||||
const props = defineProps({
 | 
			
		||||
    lazy: {
 | 
			
		||||
@@ -104,7 +104,7 @@ const perms = {
 | 
			
		||||
    saveDb: 'db:save',
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const searchItems = [getTagPathSearchItem(TagResourceTypeEnum.Db.value), SearchItem.input('code', '编号'), SearchItem.input('name', '名称')];
 | 
			
		||||
const searchItems = [SearchItem.input('keyword', '关键字').withPlaceholder('host / 名称 / 编号'), getTagPathSearchItem(TagResourceTypeEnum.Db.value)];
 | 
			
		||||
 | 
			
		||||
const columns = ref([
 | 
			
		||||
    TableColumn.new('tags[0].tagPath', '关联标签').isSlot('tagPath').setAddWidth(20),
 | 
			
		||||
@@ -118,7 +118,7 @@ const columns = ref([
 | 
			
		||||
]);
 | 
			
		||||
 | 
			
		||||
// 该用户拥有的的操作列按钮权限
 | 
			
		||||
const actionBtns = hasPerms(Object.values(perms));
 | 
			
		||||
const actionBtns: any = hasPerms(Object.values(perms));
 | 
			
		||||
const actionColumn = TableColumn.new('action', '操作').isSlot().setMinWidth(180).fixedRight().alignCenter();
 | 
			
		||||
const pageTableRef: Ref<any> = ref(null);
 | 
			
		||||
 | 
			
		||||
@@ -215,7 +215,7 @@ const deleteInstance = async () => {
 | 
			
		||||
 | 
			
		||||
const editDb = (data: any) => {
 | 
			
		||||
    state.dbEditDialog.instance = data;
 | 
			
		||||
    state.dbEditDialog.title = `配置 "${data.name}" 数据库`;
 | 
			
		||||
    state.dbEditDialog.title = `管理 "${data.name}" 数据库`;
 | 
			
		||||
    state.dbEditDialog.visible = true;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -47,10 +47,8 @@
 | 
			
		||||
                    </template>
 | 
			
		||||
 | 
			
		||||
                    <template #suffix="{ data }">
 | 
			
		||||
                        <span class="db-table-size" v-if="data.type.value == SqlExecNodeType.Table && data.params.size">{{ ` ${data.params.size}` }}</span>
 | 
			
		||||
                        <span class="db-table-size" v-if="data.type.value == SqlExecNodeType.TableMenu && data.params.dbTableSize">{{
 | 
			
		||||
                            ` ${data.params.dbTableSize}`
 | 
			
		||||
                        }}</span>
 | 
			
		||||
                        <span v-if="data.type.value == SqlExecNodeType.Table && data.params.size">{{ ` ${data.params.size}` }}</span>
 | 
			
		||||
                        <span v-if="data.type.value == SqlExecNodeType.TableMenu && data.params.dbTableSize">{{ ` ${data.params.dbTableSize}` }}</span>
 | 
			
		||||
                    </template>
 | 
			
		||||
                </tag-tree>
 | 
			
		||||
            </Pane>
 | 
			
		||||
@@ -60,16 +58,71 @@
 | 
			
		||||
                    <el-row>
 | 
			
		||||
                        <el-col :span="24" v-if="state.db">
 | 
			
		||||
                            <el-descriptions :column="4" size="small" border>
 | 
			
		||||
                                <el-descriptions-item label-align="right" label="操作"
 | 
			
		||||
                                    ><el-button
 | 
			
		||||
                                <el-descriptions-item label-align="right" label="操作">
 | 
			
		||||
                                    <el-button
 | 
			
		||||
                                        :disabled="!state.db || !nowDbInst.id"
 | 
			
		||||
                                        type="primary"
 | 
			
		||||
                                        icon="Search"
 | 
			
		||||
                                        @click="addQueryTab({ id: nowDbInst.id, dbs: nowDbInst.databases }, state.db)"
 | 
			
		||||
                                        size="small"
 | 
			
		||||
                                        >新建查询</el-button
 | 
			
		||||
                                    ></el-descriptions-item
 | 
			
		||||
                                >
 | 
			
		||||
                                        link
 | 
			
		||||
                                        @click="
 | 
			
		||||
                                            addQueryTab(
 | 
			
		||||
                                                { id: nowDbInst.id, dbs: nowDbInst.databases, nodeKey: getSqlMenuNodeKey(nowDbInst.id, state.db) },
 | 
			
		||||
                                                state.db
 | 
			
		||||
                                            )
 | 
			
		||||
                                        "
 | 
			
		||||
                                        title="新建查询"
 | 
			
		||||
                                    >
 | 
			
		||||
                                    </el-button>
 | 
			
		||||
 | 
			
		||||
                                    <template v-if="!dbConfig.locationTreeNode">
 | 
			
		||||
                                        <el-divider direction="vertical" border-style="dashed" />
 | 
			
		||||
                                        <el-button @click="locationNowTreeNode(null)" title="定位至左侧树的指定位置" icon="Location" link></el-button>
 | 
			
		||||
                                    </template>
 | 
			
		||||
 | 
			
		||||
                                    <el-divider direction="vertical" border-style="dashed" />
 | 
			
		||||
                                    <!-- 数据库展示配置 -->
 | 
			
		||||
                                    <el-popover
 | 
			
		||||
                                        popper-style="max-height: 550px; overflow: auto; max-width: 450px"
 | 
			
		||||
                                        placement="bottom"
 | 
			
		||||
                                        width="auto"
 | 
			
		||||
                                        title="数据库展示配置"
 | 
			
		||||
                                        trigger="click"
 | 
			
		||||
                                    >
 | 
			
		||||
                                        <el-row>
 | 
			
		||||
                                            <el-checkbox
 | 
			
		||||
                                                v-model="dbConfig.showColumnComment"
 | 
			
		||||
                                                label="显示字段备注"
 | 
			
		||||
                                                :true-value="1"
 | 
			
		||||
                                                :false-value="0"
 | 
			
		||||
                                                size="small"
 | 
			
		||||
                                            />
 | 
			
		||||
                                        </el-row>
 | 
			
		||||
 | 
			
		||||
                                        <el-row>
 | 
			
		||||
                                            <el-checkbox
 | 
			
		||||
                                                v-model="dbConfig.locationTreeNode"
 | 
			
		||||
                                                label="自动定位树节点"
 | 
			
		||||
                                                :true-value="1"
 | 
			
		||||
                                                :false-value="0"
 | 
			
		||||
                                                size="small"
 | 
			
		||||
                                            />
 | 
			
		||||
                                        </el-row>
 | 
			
		||||
 | 
			
		||||
                                        <el-row>
 | 
			
		||||
                                            <el-checkbox
 | 
			
		||||
                                                v-model="dbConfig.cacheTable"
 | 
			
		||||
                                                label="缓存表信息-[不开启则实时获取表信息]"
 | 
			
		||||
                                                :true-value="1"
 | 
			
		||||
                                                :false-value="0"
 | 
			
		||||
                                                size="small"
 | 
			
		||||
                                            />
 | 
			
		||||
                                        </el-row>
 | 
			
		||||
 | 
			
		||||
                                        <template #reference>
 | 
			
		||||
                                            <el-link type="primary" icon="setting" :underline="false"></el-link>
 | 
			
		||||
                                        </template>
 | 
			
		||||
                                    </el-popover>
 | 
			
		||||
                                </el-descriptions-item>
 | 
			
		||||
 | 
			
		||||
                                <el-descriptions-item label-align="right" label="tag">{{ nowDbInst.tagPath }}</el-descriptions-item>
 | 
			
		||||
 | 
			
		||||
@@ -105,7 +158,9 @@
 | 
			
		||||
                            <el-tab-pane class="h100" closable v-for="dt in state.tabs.values()" :label="dt.label" :name="dt.key" :key="dt.key">
 | 
			
		||||
                                <template #label>
 | 
			
		||||
                                    <el-popover :show-after="1000" placement="bottom-start" trigger="hover" :width="250">
 | 
			
		||||
                                        <template #reference> {{ dt.label }} </template>
 | 
			
		||||
                                        <template #reference>
 | 
			
		||||
                                            <span @contextmenu.prevent="onTabContextmenu(dt, $event)" class="font12">{{ dt.label }}</span>
 | 
			
		||||
                                        </template>
 | 
			
		||||
                                        <template #default>
 | 
			
		||||
                                            <el-descriptions :column="1" size="small">
 | 
			
		||||
                                                <el-descriptions-item label="tagPath">
 | 
			
		||||
@@ -132,6 +187,7 @@
 | 
			
		||||
                                    :db-name="dt.db"
 | 
			
		||||
                                    :table-name="dt.params.table"
 | 
			
		||||
                                    :table-height="state.dataTabsTableHeight"
 | 
			
		||||
                                    :ref="(el: any) => (dt.componentRef = el)"
 | 
			
		||||
                                ></db-table-data-op>
 | 
			
		||||
 | 
			
		||||
                                <db-sql-editor
 | 
			
		||||
@@ -140,6 +196,7 @@
 | 
			
		||||
                                    :db-name="dt.db"
 | 
			
		||||
                                    :sql-name="dt.params.sqlName"
 | 
			
		||||
                                    @save-sql-success="reloadSqls"
 | 
			
		||||
                                    :ref="(el: any) => (dt.componentRef = el)"
 | 
			
		||||
                                >
 | 
			
		||||
                                </db-sql-editor>
 | 
			
		||||
 | 
			
		||||
@@ -148,7 +205,6 @@
 | 
			
		||||
                                    :db-id="dt.params.id"
 | 
			
		||||
                                    :db="dt.params.db"
 | 
			
		||||
                                    :db-type="dt.params.type"
 | 
			
		||||
                                    :flow-procdef-key="dt.params.flowProcdefKey"
 | 
			
		||||
                                    :height="state.tablesOpHeight"
 | 
			
		||||
                                />
 | 
			
		||||
                            </el-tab-pane>
 | 
			
		||||
@@ -163,33 +219,41 @@
 | 
			
		||||
            :dbId="tableCreateDialog.dbId"
 | 
			
		||||
            :db="tableCreateDialog.db"
 | 
			
		||||
            :dbType="tableCreateDialog.dbType"
 | 
			
		||||
            :flow-procdef-key="tableCreateDialog.flowProcdefKey"
 | 
			
		||||
            :version="tableCreateDialog.version"
 | 
			
		||||
            :data="tableCreateDialog.data"
 | 
			
		||||
            v-model:visible="tableCreateDialog.visible"
 | 
			
		||||
            @submit-sql="onSubmitEditTableSql"
 | 
			
		||||
        />
 | 
			
		||||
 | 
			
		||||
        <el-dialog width="55%" :title="`'${state.chooseTableName}' DDL`" v-model="state.ddlDialog.visible">
 | 
			
		||||
            <monaco-editor height="400px" language="sql" v-model="state.ddlDialog.ddl" :options="{ readOnly: true }" />
 | 
			
		||||
        </el-dialog>
 | 
			
		||||
 | 
			
		||||
        <contextmenu ref="tabContextmenuRef" :dropdown="state.tabContextmenu.dropdown" :items="state.tabContextmenu.items" />
 | 
			
		||||
    </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script lang="ts" setup>
 | 
			
		||||
import { defineAsyncComponent, h, onBeforeUnmount, onMounted, reactive, ref, toRefs, watch } from 'vue';
 | 
			
		||||
import { defineAsyncComponent, h, onBeforeUnmount, onMounted, reactive, ref, toRefs, useTemplateRef, watch } from 'vue';
 | 
			
		||||
import { ElCheckbox, ElMessage, ElMessageBox } from 'element-plus';
 | 
			
		||||
import { formatByteSize } from '@/common/utils/format';
 | 
			
		||||
import { DbInst, registerDbCompletionItemProvider, TabInfo, TabType } from './db';
 | 
			
		||||
import { NodeType, TagTreeNode, getTagTypeCodeByPath } from '../component/tag';
 | 
			
		||||
import { DbInst, DbThemeConfig, registerDbCompletionItemProvider, TabInfo, TabType } from './db';
 | 
			
		||||
import { getTagTypeCodeByPath, NodeType, TagTreeNode } from '../component/tag';
 | 
			
		||||
import TagTree from '../component/TagTree.vue';
 | 
			
		||||
import { dbApi } from './api';
 | 
			
		||||
import { dispposeCompletionItemProvider } from '@/components/monaco/completionItemProvider';
 | 
			
		||||
import SvgIcon from '@/components/svgIcon/index.vue';
 | 
			
		||||
import { ContextmenuItem } from '@/components/contextmenu';
 | 
			
		||||
import { Contextmenu, ContextmenuItem } from '@/components/contextmenu';
 | 
			
		||||
import { getDbDialect, schemaDbTypes } from './dialect/index';
 | 
			
		||||
import { sleep } from '@/common/utils/loading';
 | 
			
		||||
import { TagResourceTypeEnum } from '@/common/commonEnum';
 | 
			
		||||
import { Pane, Splitpanes } from 'splitpanes';
 | 
			
		||||
import { useEventListener } from '@vueuse/core';
 | 
			
		||||
import { useEventListener, useStorage } from '@vueuse/core';
 | 
			
		||||
import SqlExecBox from '@/views/ops/db/component/sqleditor/SqlExecBox';
 | 
			
		||||
import { useAutoOpenResource } from '@/store/autoOpenResource';
 | 
			
		||||
import { storeToRefs } from 'pinia';
 | 
			
		||||
import { format as sqlFormatter } from 'sql-formatter';
 | 
			
		||||
import MonacoEditor from '@/components/monaco/MonacoEditor.vue';
 | 
			
		||||
 | 
			
		||||
const DbTableOp = defineAsyncComponent(() => import('./component/table/DbTableOp.vue'));
 | 
			
		||||
const DbSqlEditor = defineAsyncComponent(() => import('./component/sqleditor/DbSqlEditor.vue'));
 | 
			
		||||
@@ -232,10 +296,10 @@ const SqlIcon = {
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// node节点点击时,触发改变db事件
 | 
			
		||||
const nodeClickChangeDb = (nodeData: TagTreeNode) => {
 | 
			
		||||
const nodeClickChangeDb = async (nodeData: TagTreeNode) => {
 | 
			
		||||
    const params = nodeData.params;
 | 
			
		||||
    if (params.db) {
 | 
			
		||||
        changeDb(
 | 
			
		||||
        await changeDb(
 | 
			
		||||
            {
 | 
			
		||||
                id: params.id,
 | 
			
		||||
                host: `${params.host}`,
 | 
			
		||||
@@ -243,7 +307,6 @@ const nodeClickChangeDb = (nodeData: TagTreeNode) => {
 | 
			
		||||
                type: params.type,
 | 
			
		||||
                tagPath: params.tagPath,
 | 
			
		||||
                databases: params.dbs,
 | 
			
		||||
                flowProcdefKey: params.flowProcdefKey,
 | 
			
		||||
            },
 | 
			
		||||
            params.db
 | 
			
		||||
        );
 | 
			
		||||
@@ -271,9 +334,12 @@ const NodeTypeTagPath = new NodeType(TagTreeNode.TagPath)
 | 
			
		||||
    .withContextMenuItems([ContextmenuItemRefresh]);
 | 
			
		||||
 | 
			
		||||
// 数据库实例节点类型
 | 
			
		||||
const NodeTypeDbInst = new NodeType(SqlExecNodeType.DbInst).withLoadNodesFunc((parentNode: TagTreeNode) => {
 | 
			
		||||
const NodeTypeDbInst = new NodeType(SqlExecNodeType.DbInst).withLoadNodesFunc(async (parentNode: TagTreeNode) => {
 | 
			
		||||
    const params = parentNode.params;
 | 
			
		||||
    const dbs = params.database.split(' ')?.sort();
 | 
			
		||||
    const dbs = (await DbInst.getDbNames(params))?.sort();
 | 
			
		||||
 | 
			
		||||
    // 查询数据库版本信息
 | 
			
		||||
    const version = await dbApi.getCompatibleDbVersion.request({ id: params.id, db: dbs[0] });
 | 
			
		||||
 | 
			
		||||
    return dbs.map((x: any) => {
 | 
			
		||||
        return new TagTreeNode(`${parentNode.key}.${x}`, x, NodeTypeDb)
 | 
			
		||||
@@ -282,10 +348,10 @@ const NodeTypeDbInst = new NodeType(SqlExecNodeType.DbInst).withLoadNodesFunc((p
 | 
			
		||||
                id: params.id,
 | 
			
		||||
                name: params.name,
 | 
			
		||||
                type: params.type,
 | 
			
		||||
                version: version || 'unset',
 | 
			
		||||
                host: `${params.host}:${params.port}`,
 | 
			
		||||
                dbs: dbs,
 | 
			
		||||
                db: x,
 | 
			
		||||
                flowProcdefKey: params.flowProcdefKey,
 | 
			
		||||
            })
 | 
			
		||||
            .withIcon(DbIcon);
 | 
			
		||||
    });
 | 
			
		||||
@@ -319,7 +385,12 @@ const getNodeTypeTables = (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(`${params.id}.${params.db}.table-menu`, '表', NodeTypeTableMenu)
 | 
			
		||||
            .withParams({
 | 
			
		||||
                ...params,
 | 
			
		||||
                key: tableKey,
 | 
			
		||||
            })
 | 
			
		||||
            .withIcon(TableIcon),
 | 
			
		||||
        new TagTreeNode(sqlKey, 'SQL', NodeTypeSqlMenu).withParams({ ...params, key: sqlKey }).withIcon(SqlIcon),
 | 
			
		||||
    ];
 | 
			
		||||
};
 | 
			
		||||
@@ -346,10 +417,10 @@ const NodeTypeTableMenu = new NodeType(SqlExecNodeType.TableMenu)
 | 
			
		||||
    ])
 | 
			
		||||
    .withLoadNodesFunc(async (parentNode: TagTreeNode) => {
 | 
			
		||||
        const params = parentNode.params;
 | 
			
		||||
        let { id, db, type, flowProcdefKey, schema } = params;
 | 
			
		||||
        let { id, db, type, schema, version } = params;
 | 
			
		||||
        // 获取当前库的所有表信息
 | 
			
		||||
        let tables = await DbInst.getInst(id).loadTables(db, state.reloadStatus);
 | 
			
		||||
        state.reloadStatus = false;
 | 
			
		||||
        state.reloadStatus = !dbConfig.value.cacheTable;
 | 
			
		||||
        let dbTableSize = 0;
 | 
			
		||||
        const tablesNode = tables.map((x: any) => {
 | 
			
		||||
            const tableSize = x.dataLength + x.indexLength;
 | 
			
		||||
@@ -362,7 +433,7 @@ const NodeTypeTableMenu = new NodeType(SqlExecNodeType.TableMenu)
 | 
			
		||||
                    db,
 | 
			
		||||
                    type,
 | 
			
		||||
                    schema,
 | 
			
		||||
                    flowProcdefKey: flowProcdefKey,
 | 
			
		||||
                    version,
 | 
			
		||||
                    key: key,
 | 
			
		||||
                    parentKey: parentNode.key,
 | 
			
		||||
                    tableName: x.tableName,
 | 
			
		||||
@@ -378,7 +449,7 @@ const NodeTypeTableMenu = new NodeType(SqlExecNodeType.TableMenu)
 | 
			
		||||
    })
 | 
			
		||||
    .withNodeDblclickFunc((node: TagTreeNode) => {
 | 
			
		||||
        const params = node.params;
 | 
			
		||||
        addTablesOpTab({ id: params.id, db: params.db, type: params.type, nodeKey: node.key });
 | 
			
		||||
        addTablesOpTab({ id: params.id, db: params.db, type: params.type, version: params.version, nodeKey: node.key });
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
// 数据库sql模板菜单节点
 | 
			
		||||
@@ -406,6 +477,7 @@ const NodeTypeTable = new NodeType(SqlExecNodeType.Table)
 | 
			
		||||
        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)),
 | 
			
		||||
        new ContextmenuItem('ddl', 'DDL').withIcon('Document').withOnClick((data: any) => onGenDdl(data)),
 | 
			
		||||
    ])
 | 
			
		||||
    .withNodeClickFunc((nodeData: TagTreeNode) => {
 | 
			
		||||
        const params = nodeData.params;
 | 
			
		||||
@@ -422,7 +494,24 @@ const NodeTypeSql = new NodeType(SqlExecNodeType.Sql)
 | 
			
		||||
        new ContextmenuItem('delSql', '删除').withIcon('delete').withOnClick((data: any) => deleteSql(data.params.id, data.params.db, data.params.sqlName)),
 | 
			
		||||
    ]);
 | 
			
		||||
 | 
			
		||||
const tabContextmenuItems = [
 | 
			
		||||
    new ContextmenuItem(1, '关闭').withIcon('Close').withOnClick((data: any) => {
 | 
			
		||||
        onRemoveTab(data.key);
 | 
			
		||||
    }),
 | 
			
		||||
 | 
			
		||||
    new ContextmenuItem(2, '关闭其他').withIcon('CircleClose').withOnClick((data: any) => {
 | 
			
		||||
        const tabName = data.key;
 | 
			
		||||
        const tabNames = [...state.tabs.keys()];
 | 
			
		||||
        for (let tab of tabNames) {
 | 
			
		||||
            if (tab !== tabName) {
 | 
			
		||||
                onRemoveTab(tab);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }),
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
const tagTreeRef: any = ref(null);
 | 
			
		||||
const tabContextmenuRef: any = useTemplateRef('tabContextmenuRef');
 | 
			
		||||
 | 
			
		||||
const tabs: Map<string, TabInfo> = new Map();
 | 
			
		||||
const state = reactive({
 | 
			
		||||
@@ -435,6 +524,10 @@ const state = reactive({
 | 
			
		||||
    activeName: '',
 | 
			
		||||
    reloadStatus: false,
 | 
			
		||||
    tabs,
 | 
			
		||||
    tabContextmenu: {
 | 
			
		||||
        dropdown: { x: 0, y: 0 },
 | 
			
		||||
        items: tabContextmenuItems,
 | 
			
		||||
    },
 | 
			
		||||
    dataTabsTableHeight: '600px',
 | 
			
		||||
    tablesOpHeight: '600',
 | 
			
		||||
    dbServerInfo: {
 | 
			
		||||
@@ -446,16 +539,23 @@ const state = reactive({
 | 
			
		||||
        title: '',
 | 
			
		||||
        activeName: '',
 | 
			
		||||
        dbId: 0,
 | 
			
		||||
        version: '',
 | 
			
		||||
        db: '',
 | 
			
		||||
        dbType: '',
 | 
			
		||||
        flowProcdefKey: '',
 | 
			
		||||
        data: {},
 | 
			
		||||
        parentKey: '',
 | 
			
		||||
    },
 | 
			
		||||
    chooseTableName: '',
 | 
			
		||||
    ddlDialog: {
 | 
			
		||||
        visible: false,
 | 
			
		||||
        ddl: '',
 | 
			
		||||
    },
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const { nowDbInst, tableCreateDialog } = toRefs(state);
 | 
			
		||||
 | 
			
		||||
const dbConfig = useStorage('dbConfig', DbThemeConfig);
 | 
			
		||||
 | 
			
		||||
const serverInfoReqParam = ref({
 | 
			
		||||
    instanceId: 0,
 | 
			
		||||
});
 | 
			
		||||
@@ -465,6 +565,7 @@ const autoOpenResourceStore = useAutoOpenResource();
 | 
			
		||||
const { autoOpenResource } = storeToRefs(autoOpenResourceStore);
 | 
			
		||||
 | 
			
		||||
onMounted(() => {
 | 
			
		||||
    state.reloadStatus = !dbConfig.value.cacheTable;
 | 
			
		||||
    autoOpenDb(autoOpenResource.value.dbCodePath);
 | 
			
		||||
    setHeight();
 | 
			
		||||
    // 监听浏览器窗口大小变化,更新对应组件高度
 | 
			
		||||
@@ -487,7 +588,7 @@ const autoOpenDb = (codePath: string) => {
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const typeAndCodes = getTagTypeCodeByPath(codePath);
 | 
			
		||||
    const typeAndCodes: any = getTagTypeCodeByPath(codePath);
 | 
			
		||||
    const tagPath = typeAndCodes[TagResourceTypeEnum.Tag.value].join('/') + '/';
 | 
			
		||||
 | 
			
		||||
    const dbCode = typeAndCodes[TagResourceTypeEnum.DbName.value][0];
 | 
			
		||||
@@ -497,7 +598,7 @@ const autoOpenDb = (codePath: string) => {
 | 
			
		||||
        // 置空
 | 
			
		||||
        autoOpenResourceStore.setDbCodePath('');
 | 
			
		||||
        tagTreeRef.value.setCurrentKey(dbCode);
 | 
			
		||||
    }, 600);
 | 
			
		||||
    }, 1000);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
@@ -517,8 +618,8 @@ const showDbInfo = async (db: any) => {
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// 选择数据库,改变当前正在操作的数据库信息
 | 
			
		||||
const changeDb = (db: any, dbName: string) => {
 | 
			
		||||
    state.nowDbInst = DbInst.getOrNewInst(db);
 | 
			
		||||
const changeDb = async (db: any, dbName: string) => {
 | 
			
		||||
    state.nowDbInst = await DbInst.getOrNewInst(db);
 | 
			
		||||
    state.nowDbInst.databases = db.databases;
 | 
			
		||||
    state.db = dbName;
 | 
			
		||||
};
 | 
			
		||||
@@ -528,9 +629,9 @@ const loadTableData = async (db: any, dbName: string, tableName: string) => {
 | 
			
		||||
    if (tableName == '') {
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
    changeDb(db, dbName);
 | 
			
		||||
    await changeDb(db, dbName);
 | 
			
		||||
 | 
			
		||||
    const key = `${db.id}:\`${dbName}\`.${tableName}`;
 | 
			
		||||
    const key = `tableData:${db.id}.${dbName}.${tableName}`;
 | 
			
		||||
    let tab = state.tabs.get(key);
 | 
			
		||||
    state.activeName = key;
 | 
			
		||||
    // 如果存在该表tab,则直接返回
 | 
			
		||||
@@ -557,7 +658,7 @@ const addQueryTab = async (db: any, dbName: string, sqlName: string = '') => {
 | 
			
		||||
        ElMessage.warning('请选择数据库实例及对应的schema');
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
    changeDb(db, dbName);
 | 
			
		||||
    await changeDb(db, dbName);
 | 
			
		||||
 | 
			
		||||
    const dbId = db.id;
 | 
			
		||||
    let label;
 | 
			
		||||
@@ -565,7 +666,7 @@ const addQueryTab = async (db: any, dbName: string, sqlName: string = '') => {
 | 
			
		||||
    // 存在sql模板名,则该模板名只允许一个tab
 | 
			
		||||
    if (sqlName) {
 | 
			
		||||
        label = `查询-${sqlName}`;
 | 
			
		||||
        key = `查询:${dbId}:${dbName}.${sqlName}`;
 | 
			
		||||
        key = `query:${dbId}.${dbName}.${sqlName}`;
 | 
			
		||||
    } else {
 | 
			
		||||
        let count = 1;
 | 
			
		||||
        state.tabs.forEach((v) => {
 | 
			
		||||
@@ -574,7 +675,7 @@ const addQueryTab = async (db: any, dbName: string, sqlName: string = '') => {
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
        label = `新查询-${count}`;
 | 
			
		||||
        key = `新查询${count}:${dbId}:${dbName}`;
 | 
			
		||||
        key = `query:${count}.${dbId}.${dbName}`;
 | 
			
		||||
    }
 | 
			
		||||
    state.activeName = key;
 | 
			
		||||
    let tab = state.tabs.get(key);
 | 
			
		||||
@@ -608,10 +709,10 @@ const addTablesOpTab = async (db: any) => {
 | 
			
		||||
        ElMessage.warning('请选择数据库实例及对应的schema');
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
    changeDb(db, dbName);
 | 
			
		||||
    await changeDb(db, dbName);
 | 
			
		||||
 | 
			
		||||
    const dbId = db.id;
 | 
			
		||||
    let key = `表操作:${dbId}:${dbName}.tablesOp`;
 | 
			
		||||
    let key = `tablesOp:${dbId}.${dbName}`;
 | 
			
		||||
    state.activeName = key;
 | 
			
		||||
 | 
			
		||||
    let tab = state.tabs.get(key);
 | 
			
		||||
@@ -642,15 +743,22 @@ const onRemoveTab = (targetName: string) => {
 | 
			
		||||
        if (tabName !== targetName) {
 | 
			
		||||
            continue;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        state.tabs.delete(targetName);
 | 
			
		||||
        if (activeName != targetName) {
 | 
			
		||||
            break;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // 如果删除的tab是当前激活的tab,则切换到前一个或后一个tab
 | 
			
		||||
        const nextTab = tabNames[i + 1] || tabNames[i - 1];
 | 
			
		||||
        if (nextTab) {
 | 
			
		||||
            activeName = nextTab;
 | 
			
		||||
        } else {
 | 
			
		||||
            activeName = '';
 | 
			
		||||
        }
 | 
			
		||||
        state.tabs.delete(targetName);
 | 
			
		||||
        state.activeName = activeName;
 | 
			
		||||
        onTabChange();
 | 
			
		||||
        break;
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@@ -669,6 +777,32 @@ const onTabChange = () => {
 | 
			
		||||
        // 注册sql提示
 | 
			
		||||
        registerDbCompletionItemProvider(nowTab.dbId, nowTab.db, nowTab.params.dbs, nowDbInst.value.type);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // 激活当前tab(需要调用DbTableData组件的active,否则表头与数据会出现错位,暂不知为啥,先这样处理)
 | 
			
		||||
    nowTab?.componentRef?.active();
 | 
			
		||||
 | 
			
		||||
    if (dbConfig.value.locationTreeNode) {
 | 
			
		||||
        locationNowTreeNode(nowTab);
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// 右键点击时:传 x,y 坐标值到子组件中(props)
 | 
			
		||||
const onTabContextmenu = (v: any, e: any) => {
 | 
			
		||||
    console.log('on tab cm');
 | 
			
		||||
    const { clientX, clientY } = e;
 | 
			
		||||
    state.tabContextmenu.dropdown.x = clientX;
 | 
			
		||||
    state.tabContextmenu.dropdown.y = clientY;
 | 
			
		||||
    tabContextmenuRef.value.openContextmenu(v);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 定位至当前树节点
 | 
			
		||||
 */
 | 
			
		||||
const locationNowTreeNode = (nowTab: any = null) => {
 | 
			
		||||
    if (!nowTab) {
 | 
			
		||||
        nowTab = state.tabs.get(state.activeName);
 | 
			
		||||
    }
 | 
			
		||||
    tagTreeRef.value.setCurrentKey(nowTab?.treeNodeKey);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const reloadSqls = (dbId: number, db: string) => {
 | 
			
		||||
@@ -700,7 +834,7 @@ const reloadNode = (nodeKey: string) => {
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const onEditTable = async (data: any) => {
 | 
			
		||||
    let { db, id, tableName, tableComment, type, parentKey, key, flowProcdefKey } = data.params;
 | 
			
		||||
    let { db, id, tableName, tableComment, type, parentKey, key, version } = data.params;
 | 
			
		||||
    // data.label就是表名
 | 
			
		||||
    if (tableName) {
 | 
			
		||||
        state.tableCreateDialog.title = '修改表';
 | 
			
		||||
@@ -717,14 +851,14 @@ const onEditTable = async (data: any) => {
 | 
			
		||||
 | 
			
		||||
    state.tableCreateDialog.activeName = '1';
 | 
			
		||||
    state.tableCreateDialog.dbId = id;
 | 
			
		||||
    state.tableCreateDialog.version = version;
 | 
			
		||||
    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;
 | 
			
		||||
    let { db, id, tableName, parentKey, schema } = data.params;
 | 
			
		||||
    await ElMessageBox.confirm(`此操作是永久性且无法撤销,确定删除【${tableName}】? `, '提示', {
 | 
			
		||||
        confirmButtonText: '确定',
 | 
			
		||||
        cancelButtonText: '取消',
 | 
			
		||||
@@ -735,20 +869,33 @@ const onDeleteTable = async (data: any) => {
 | 
			
		||||
    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;
 | 
			
		||||
    dbApi.sqlExec.request({ id, db, sql: `drop table ${schemaStr + dialect.quoteIdentifier(tableName)}` }).then((res) => {
 | 
			
		||||
        let success = true;
 | 
			
		||||
        for (let re of res) {
 | 
			
		||||
            if (re.errorMsg) {
 | 
			
		||||
                success = false;
 | 
			
		||||
                ElMessage.error(`${re.sql} -> 执行失败: ${re.errorMsg}`);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        if (success) {
 | 
			
		||||
            ElMessage.success('删除成功');
 | 
			
		||||
            setTimeout(() => {
 | 
			
		||||
                parentKey && reloadNode(parentKey);
 | 
			
		||||
            }, 1000);
 | 
			
		||||
        }
 | 
			
		||||
        ElMessage.success('删除成功');
 | 
			
		||||
        setTimeout(() => {
 | 
			
		||||
            parentKey && reloadNode(parentKey);
 | 
			
		||||
        }, 1000);
 | 
			
		||||
    });
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const onGenDdl = async (data: any) => {
 | 
			
		||||
    let { db, id, tableName, type } = data.params;
 | 
			
		||||
    state.chooseTableName = tableName;
 | 
			
		||||
    let res = await dbApi.tableDdl.request({ id, db, tableName });
 | 
			
		||||
    state.ddlDialog.ddl = sqlFormatter(res, { language: getDbDialect(type).getInfo().formatSqlDialect as any });
 | 
			
		||||
    state.ddlDialog.visible = true;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const onRenameTable = async (data: any) => {
 | 
			
		||||
    let { db, id, tableName, parentKey, flowProcdefKey } = data.params;
 | 
			
		||||
    let { db, id, tableName, parentKey } = data.params;
 | 
			
		||||
    let tableData = { db, oldTableName: tableName, tableName };
 | 
			
		||||
 | 
			
		||||
    let value = ref(tableName);
 | 
			
		||||
@@ -771,7 +918,6 @@ const onRenameTable = async (data: any) => {
 | 
			
		||||
        dbId: id as any,
 | 
			
		||||
        db: db as any,
 | 
			
		||||
        dbType: nowDbInst.value.getDialect().getInfo().formatSqlDialect,
 | 
			
		||||
        flowProcdefKey: flowProcdefKey,
 | 
			
		||||
        runSuccessCallback: () => {
 | 
			
		||||
            setTimeout(() => {
 | 
			
		||||
                parentKey && reloadNode(parentKey);
 | 
			
		||||
@@ -831,7 +977,6 @@ const getNowDbInfo = () => {
 | 
			
		||||
        name: di.name,
 | 
			
		||||
        type: di.type,
 | 
			
		||||
        host: di.host,
 | 
			
		||||
        flowProcdefKey: di.flowProcdefKey,
 | 
			
		||||
        dbName: state.db,
 | 
			
		||||
    };
 | 
			
		||||
};
 | 
			
		||||
@@ -839,11 +984,6 @@ const getNowDbInfo = () => {
 | 
			
		||||
 | 
			
		||||
<style lang="scss">
 | 
			
		||||
.db-sql-exec {
 | 
			
		||||
    .db-table-size {
 | 
			
		||||
        color: #c4c9c4;
 | 
			
		||||
        font-size: 9px;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .db-op {
 | 
			
		||||
        height: calc(100vh - 106px);
 | 
			
		||||
    }
 | 
			
		||||
@@ -857,7 +997,7 @@ const getNowDbInfo = () => {
 | 
			
		||||
            margin: 0 0 5px;
 | 
			
		||||
 | 
			
		||||
            .el-tabs__item {
 | 
			
		||||
                padding: 0 10px;
 | 
			
		||||
                padding: 0 5px;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,46 +1,33 @@
 | 
			
		||||
<template>
 | 
			
		||||
    <div class="sync-task-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-drawer :title="title" v-model="dialogVisible" :before-close="cancel" :destroy-on-close="true" :close-on-click-modal="false" size="45%">
 | 
			
		||||
            <template #header>
 | 
			
		||||
                <DrawerHeader :header="title" :back="cancel" />
 | 
			
		||||
            </template>
 | 
			
		||||
 | 
			
		||||
            <el-form :model="form" ref="dbForm" :rules="rules" label-width="auto">
 | 
			
		||||
                <el-tabs v-model="tabActiveName" style="height: 450px">
 | 
			
		||||
                <el-tabs v-model="tabActiveName">
 | 
			
		||||
                    <el-tab-pane label="基本信息" :name="basicTab">
 | 
			
		||||
                        <el-form-item>
 | 
			
		||||
                            <el-row>
 | 
			
		||||
                                <el-col :span="11">
 | 
			
		||||
                                <el-col :span="12">
 | 
			
		||||
                                    <el-form-item prop="taskName" label="任务名" required>
 | 
			
		||||
                                        <el-input v-model.trim="form.taskName" placeholder="请输入同步任务名" auto-complete="off" />
 | 
			
		||||
                                    </el-form-item>
 | 
			
		||||
                                </el-col>
 | 
			
		||||
 | 
			
		||||
                                <el-col :span="11">
 | 
			
		||||
                                <el-col :span="12">
 | 
			
		||||
                                    <el-form-item prop="taskCron" label="cron" required>
 | 
			
		||||
                                        <CrontabInput v-model="form.taskCron" />
 | 
			
		||||
                                    </el-form-item>
 | 
			
		||||
                                </el-col>
 | 
			
		||||
 | 
			
		||||
                                <el-col :span="2">
 | 
			
		||||
                                    <el-form-item prop="status" label="状态" label-width="60" required>
 | 
			
		||||
                                        <el-switch
 | 
			
		||||
                                            v-model="form.status"
 | 
			
		||||
                                            inline-prompt
 | 
			
		||||
                                            active-text="启用"
 | 
			
		||||
                                            inactive-text="禁用"
 | 
			
		||||
                                            :active-value="1"
 | 
			
		||||
                                            :inactive-value="-1"
 | 
			
		||||
                                        />
 | 
			
		||||
                                    </el-form-item>
 | 
			
		||||
                                </el-col>
 | 
			
		||||
                            </el-row>
 | 
			
		||||
                        </el-form-item>
 | 
			
		||||
 | 
			
		||||
                        <el-form-item prop="status" label="状态" label-width="60" required>
 | 
			
		||||
                            <el-switch v-model="form.status" inline-prompt active-text="启用" inactive-text="禁用" :active-value="1" :inactive-value="-1" />
 | 
			
		||||
                        </el-form-item>
 | 
			
		||||
 | 
			
		||||
                        <el-form-item prop="srcDbId" label="源数据库" required>
 | 
			
		||||
                            <db-select-tree
 | 
			
		||||
                                placeholder="请选择源数据库"
 | 
			
		||||
@@ -69,36 +56,73 @@
 | 
			
		||||
                            <monaco-editor height="150px" class="task-sql" language="sql" v-model="form.dataSql" />
 | 
			
		||||
                        </el-form-item>
 | 
			
		||||
 | 
			
		||||
                        <el-form-item prop="targetTableName" label="目标库表" required>
 | 
			
		||||
                            <el-select v-model="form.targetTableName" filterable placeholder="请选择目标数据库表">
 | 
			
		||||
                                <el-option
 | 
			
		||||
                                    v-for="item in state.targetTableList"
 | 
			
		||||
                                    :key="item.tableName"
 | 
			
		||||
                                    :label="item.tableName + (item.tableComment && '-' + item.tableComment)"
 | 
			
		||||
                                    :value="item.tableName"
 | 
			
		||||
                                />
 | 
			
		||||
                            </el-select>
 | 
			
		||||
                        <el-form-item>
 | 
			
		||||
                            <el-row class="w100">
 | 
			
		||||
                                <el-col :span="12">
 | 
			
		||||
                                    <el-form-item prop="targetTableName" label="目标库表" required>
 | 
			
		||||
                                        <el-select v-model="form.targetTableName" filterable placeholder="请选择目标数据库表">
 | 
			
		||||
                                            <el-option
 | 
			
		||||
                                                v-for="item in state.targetTableList"
 | 
			
		||||
                                                :key="item.tableName"
 | 
			
		||||
                                                :label="item.tableName + (item.tableComment && '-' + item.tableComment)"
 | 
			
		||||
                                                :value="item.tableName"
 | 
			
		||||
                                            />
 | 
			
		||||
                                        </el-select>
 | 
			
		||||
                                    </el-form-item>
 | 
			
		||||
                                </el-col>
 | 
			
		||||
 | 
			
		||||
                                <el-col :span="12">
 | 
			
		||||
                                    <el-form-item prop="pageSize" label="分页大小" required>
 | 
			
		||||
                                        <el-input type="number" v-model.number="form.pageSize" placeholder="同步数据时查询的每页数据大小" auto-complete="off" />
 | 
			
		||||
                                    </el-form-item>
 | 
			
		||||
                                </el-col>
 | 
			
		||||
                            </el-row>
 | 
			
		||||
                        </el-form-item>
 | 
			
		||||
 | 
			
		||||
                        <el-form-item>
 | 
			
		||||
                            <el-row>
 | 
			
		||||
                                <el-col :span="8">
 | 
			
		||||
                                    <el-form-item prop="pageSize" label="分页大小" required>
 | 
			
		||||
                                        <el-input type="number" v-model.number="form.pageSize" placeholder="同步数据时查询的每页数据大小" auto-complete="off" />
 | 
			
		||||
                                    <el-form-item class="w100" prop="updField">
 | 
			
		||||
                                        <template #label>
 | 
			
		||||
                                            更新字段
 | 
			
		||||
                                            <el-tooltip content="查询数据源的时候会带上这个字段当前最大值,支持带别名,如:t.create_time" placement="top">
 | 
			
		||||
                                                <el-icon>
 | 
			
		||||
                                                    <question-filled />
 | 
			
		||||
                                                </el-icon>
 | 
			
		||||
                                            </el-tooltip>
 | 
			
		||||
                                        </template>
 | 
			
		||||
                                        <el-input v-model.trim="form.updField" placeholder="查询数据源的时候会带上这个字段当前最大值" auto-complete="off" />
 | 
			
		||||
                                    </el-form-item>
 | 
			
		||||
                                </el-col>
 | 
			
		||||
 | 
			
		||||
                                <el-col :span="8">
 | 
			
		||||
                                    <el-tooltip content="查询数据源的时候会带上这个字段当前最大值,支持带别名,如:t.create_time" placement="top">
 | 
			
		||||
                                        <el-form-item prop="updField" label="更新字段" required>
 | 
			
		||||
                                            <el-input v-model.trim="form.updField" placeholder="查询数据源的时候会带上这个字段当前最大值" auto-complete="off" />
 | 
			
		||||
                                        </el-form-item>
 | 
			
		||||
                                    </el-tooltip>
 | 
			
		||||
                                    <el-form-item class="w100" prop="updFieldVal">
 | 
			
		||||
                                        <template #label>
 | 
			
		||||
                                            更新值
 | 
			
		||||
                                            <el-tooltip content="记录更新字段当前值,如:当前时间,当前日期等,下次查询数据时会带上该值条件" placement="top">
 | 
			
		||||
                                                <el-icon>
 | 
			
		||||
                                                    <question-filled />
 | 
			
		||||
                                                </el-icon>
 | 
			
		||||
                                            </el-tooltip>
 | 
			
		||||
                                        </template>
 | 
			
		||||
                                        <el-input v-model.trim="form.updFieldVal" placeholder="更新字段当前最大值" auto-complete="off" />
 | 
			
		||||
                                    </el-form-item>
 | 
			
		||||
                                </el-col>
 | 
			
		||||
 | 
			
		||||
                                <el-col :span="8">
 | 
			
		||||
                                    <el-form-item prop="updFieldVal" label="更新值">
 | 
			
		||||
                                        <el-input v-model.trim="form.updFieldVal" placeholder="更新字段当前最大值" auto-complete="off" />
 | 
			
		||||
                                    <el-form-item class="w100" prop="updFieldSrc">
 | 
			
		||||
                                        <template #label>
 | 
			
		||||
                                            值来源
 | 
			
		||||
                                            <el-tooltip
 | 
			
		||||
                                                content="从查询结果中取更新值的字段名,默认同更新字段,如果查询结果指定了字段别名且与原更新字段不一致,则取这个字段值为当前更新值"
 | 
			
		||||
                                                placement="top"
 | 
			
		||||
                                            >
 | 
			
		||||
                                                <el-icon>
 | 
			
		||||
                                                    <question-filled />
 | 
			
		||||
                                                </el-icon>
 | 
			
		||||
                                            </el-tooltip>
 | 
			
		||||
                                        </template>
 | 
			
		||||
                                        <el-input v-model.trim="form.updFieldSrc" placeholder="更新值来源" auto-complete="off" />
 | 
			
		||||
                                    </el-form-item>
 | 
			
		||||
                                </el-col>
 | 
			
		||||
                            </el-row>
 | 
			
		||||
@@ -115,7 +139,7 @@
 | 
			
		||||
                                            <el-option
 | 
			
		||||
                                                v-for="item in state.targetColumnList"
 | 
			
		||||
                                                :key="item.columnName"
 | 
			
		||||
                                                :label="item.columnName + ` ${item.showDataType}` + (item.columnComment && ' - ' + item.columnComment)"
 | 
			
		||||
                                                :label="item.columnName + ` ${item.columnType}` + (item.columnComment && ' - ' + item.columnComment)"
 | 
			
		||||
                                                :value="item.columnName"
 | 
			
		||||
                                            />
 | 
			
		||||
                                        </el-select>
 | 
			
		||||
@@ -183,7 +207,18 @@
 | 
			
		||||
                    <el-button type="primary" :loading="saveBtnLoading" @click="btnOk">确 定</el-button>
 | 
			
		||||
                </div>
 | 
			
		||||
            </template>
 | 
			
		||||
        </el-dialog>
 | 
			
		||||
        </el-drawer>
 | 
			
		||||
 | 
			
		||||
        <!-- <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-dialog> -->
 | 
			
		||||
    </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
@@ -196,6 +231,7 @@ import MonacoEditor from '@/components/monaco/MonacoEditor.vue';
 | 
			
		||||
import { DbInst, registerDbCompletionItemProvider } from '@/views/ops/db/db';
 | 
			
		||||
import { compatibleDuplicateStrategy, DbType, DuplicateStrategy, getDbDialect } from '@/views/ops/db/dialect';
 | 
			
		||||
import CrontabInput from '@/components/crontab/CrontabInput.vue';
 | 
			
		||||
import DrawerHeader from '@/components/drawer-header/DrawerHeader.vue';
 | 
			
		||||
 | 
			
		||||
const props = defineProps({
 | 
			
		||||
    data: {
 | 
			
		||||
@@ -253,6 +289,7 @@ type FormData = {
 | 
			
		||||
    pageSize?: number;
 | 
			
		||||
    updField?: string;
 | 
			
		||||
    updFieldVal?: string;
 | 
			
		||||
    updFieldSrc?: string;
 | 
			
		||||
    fieldMap?: { src: string; target: string }[];
 | 
			
		||||
    status?: 1 | 2;
 | 
			
		||||
    duplicateStrategy?: -1 | 1 | 2;
 | 
			
		||||
@@ -326,9 +363,9 @@ watch(dialogVisible, async (newValue: boolean) => {
 | 
			
		||||
        const db = dbInfoRes.list[0];
 | 
			
		||||
        // 初始化实例
 | 
			
		||||
        db.databases = db.database?.split(' ').sort() || [];
 | 
			
		||||
        state.srcDbInst = DbInst.getOrNewInst(db);
 | 
			
		||||
        state.srcDbInst = await DbInst.getOrNewInst(db);
 | 
			
		||||
        state.form.srcDbType = state.srcDbInst.type;
 | 
			
		||||
        state.form.srcInstName = db.instanceName;
 | 
			
		||||
        state.form.srcInstName = db.name;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    //  初始化target数据源
 | 
			
		||||
@@ -338,9 +375,9 @@ watch(dialogVisible, async (newValue: boolean) => {
 | 
			
		||||
        const db = dbInfoRes.list[0];
 | 
			
		||||
        // 初始化实例
 | 
			
		||||
        db.databases = db.database?.split(' ').sort() || [];
 | 
			
		||||
        state.targetDbInst = DbInst.getOrNewInst(db);
 | 
			
		||||
        state.targetDbInst = await DbInst.getOrNewInst(db);
 | 
			
		||||
        state.form.targetDbType = state.targetDbInst.type;
 | 
			
		||||
        state.form.targetInstName = db.instanceName;
 | 
			
		||||
        state.form.targetInstName = db.name;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (targetDbId && state.form.targetDbName) {
 | 
			
		||||
@@ -397,12 +434,12 @@ const refreshPreviewInsertSql = () => {
 | 
			
		||||
const onSelectSrcDb = async (params: any) => {
 | 
			
		||||
    //  初始化数据源
 | 
			
		||||
    params.databases = params.dbs; // 数据源里需要这个值
 | 
			
		||||
    state.srcDbInst = DbInst.getOrNewInst(params);
 | 
			
		||||
    state.srcDbInst = await DbInst.getOrNewInst(params);
 | 
			
		||||
    registerDbCompletionItemProvider(params.id, params.db, params.dbs, params.type);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const onSelectTargetDb = async (params: any) => {
 | 
			
		||||
    state.targetDbInst = DbInst.getOrNewInst(params);
 | 
			
		||||
    state.targetDbInst = await DbInst.getOrNewInst(params);
 | 
			
		||||
    await loadDbTables(params.id, params.db);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@@ -465,7 +502,7 @@ const handleGetSrcFields = async () => {
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    let filedMap = {};
 | 
			
		||||
    let filedMap: any = {};
 | 
			
		||||
    if (state.form.fieldMap && state.form.fieldMap.length > 0) {
 | 
			
		||||
        state.form.fieldMap.forEach((a: any) => {
 | 
			
		||||
            filedMap[a.src] = a.target;
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,5 @@
 | 
			
		||||
import Api from '@/common/Api';
 | 
			
		||||
import { Base64 } from 'js-base64';
 | 
			
		||||
import { AesEncrypt } from '@/common/crypto';
 | 
			
		||||
 | 
			
		||||
export const dbApi = {
 | 
			
		||||
    // 获取权限列表
 | 
			
		||||
@@ -16,17 +16,7 @@ export const dbApi = {
 | 
			
		||||
    pgSchemas: Api.newGet('/dbs/{id}/pg/schemas'),
 | 
			
		||||
    // 获取表即列提示
 | 
			
		||||
    hintTables: Api.newGet('/dbs/{id}/hint-tables'),
 | 
			
		||||
    sqlExec: Api.newPost('/dbs/{id}/exec-sql').withBeforeHandler((param: any) => {
 | 
			
		||||
        // sql编码处理
 | 
			
		||||
        if (param.sql) {
 | 
			
		||||
            // 判断是开发环境就打印sql
 | 
			
		||||
            if (process.env.NODE_ENV === 'development') {
 | 
			
		||||
                console.log(param.sql);
 | 
			
		||||
            }
 | 
			
		||||
            param.sql = Base64.encode(param.sql);
 | 
			
		||||
        }
 | 
			
		||||
        return param;
 | 
			
		||||
    }),
 | 
			
		||||
    sqlExec: Api.newPost('/dbs/{id}/exec-sql').withBeforeHandler(async (param: any) => await encryptField(param, 'sql')),
 | 
			
		||||
    // 保存sql
 | 
			
		||||
    saveSql: Api.newPost('/dbs/{id}/sql'),
 | 
			
		||||
    // 获取保存的sql
 | 
			
		||||
@@ -36,10 +26,13 @@ export const dbApi = {
 | 
			
		||||
    deleteDbSql: Api.newDelete('/dbs/{id}/sql'),
 | 
			
		||||
    // 获取数据库sql执行记录
 | 
			
		||||
    getSqlExecs: Api.newGet('/dbs/sql-execs'),
 | 
			
		||||
    // 获取数据库兼容版本
 | 
			
		||||
    getCompatibleDbVersion: Api.newGet('/dbs/{id}/version'),
 | 
			
		||||
 | 
			
		||||
    instances: Api.newGet('/instances'),
 | 
			
		||||
    getInstance: Api.newGet('/instances/{instanceId}'),
 | 
			
		||||
    getAllDatabase: Api.newPost('/instances/databases'),
 | 
			
		||||
    getDbNamesByAc: Api.newGet('/instances/databases/{authCertName}'),
 | 
			
		||||
    getInstanceServerInfo: Api.newGet('/instances/{instanceId}/server-info'),
 | 
			
		||||
    testConn: Api.newPost('/instances/test-conn'),
 | 
			
		||||
    saveInstance: Api.newPost('/instances'),
 | 
			
		||||
@@ -69,13 +62,7 @@ export const dbApi = {
 | 
			
		||||
 | 
			
		||||
    // 数据同步相关
 | 
			
		||||
    datasyncTasks: Api.newGet('/datasync/tasks'),
 | 
			
		||||
    saveDatasyncTask: Api.newPost('/datasync/tasks/save').withBeforeHandler((param: any) => {
 | 
			
		||||
        // sql编码处理
 | 
			
		||||
        if (param.dataSql) {
 | 
			
		||||
            param.dataSql = Base64.encode(param.dataSql);
 | 
			
		||||
        }
 | 
			
		||||
        return param;
 | 
			
		||||
    }),
 | 
			
		||||
    saveDatasyncTask: Api.newPost('/datasync/tasks/save').withBeforeHandler(async (param: any) => await encryptField(param, 'dataSql')),
 | 
			
		||||
    getDatasyncTask: Api.newGet('/datasync/tasks/{taskId}'),
 | 
			
		||||
    deleteDatasyncTask: Api.newDelete('/datasync/tasks/{taskId}/del'),
 | 
			
		||||
    updateDatasyncTaskStatus: Api.newPost('/datasync/tasks/{taskId}/status'),
 | 
			
		||||
@@ -87,12 +74,31 @@ export const dbApi = {
 | 
			
		||||
    dbTransferTasks: Api.newGet('/dbTransfer'),
 | 
			
		||||
    saveDbTransferTask: Api.newPost('/dbTransfer/save'),
 | 
			
		||||
    deleteDbTransferTask: Api.newDelete('/dbTransfer/{taskId}/del'),
 | 
			
		||||
    updateDbTransferTaskStatus: Api.newPost('/dbTransfer/{taskId}/status'),
 | 
			
		||||
    runDbTransferTask: Api.newPost('/dbTransfer/{taskId}/run'),
 | 
			
		||||
    stopDbTransferTask: Api.newPost('/dbTransfer/{taskId}/stop'),
 | 
			
		||||
    dbTransferTaskLogs: Api.newGet('/dbTransfer/{taskId}/logs'),
 | 
			
		||||
    dbTransferFileList: Api.newGet('/dbTransfer/files/{taskId}'),
 | 
			
		||||
    dbTransferFileDel: Api.newPost('/dbTransfer/files/del/{fileId}'),
 | 
			
		||||
    dbTransferFileRun: Api.newPost('/dbTransfer/files/run'),
 | 
			
		||||
    dbTransferFileDown: Api.newGet('/dbTransfer/files/down/{fileUuid}'),
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const dbSqlExecApi = {
 | 
			
		||||
    // 根据业务key获取sql执行信息
 | 
			
		||||
    getSqlExecByBizKey: Api.newGet('/dbs/sql-execs'),
 | 
			
		||||
};
 | 
			
		||||
const encryptField = async (param: any, field: string) => {
 | 
			
		||||
    // sql编码处理
 | 
			
		||||
    if (!param['_encrypted'] && param[field]) {
 | 
			
		||||
        // 判断是开发环境就打印sql
 | 
			
		||||
        if (process.env.NODE_ENV === 'development') {
 | 
			
		||||
            console.log(param[field]);
 | 
			
		||||
        }
 | 
			
		||||
        // 使用rsa公钥加密sql
 | 
			
		||||
        param['_encrypted'] = 1;
 | 
			
		||||
        param[field] = AesEncrypt(param[field]);
 | 
			
		||||
        // console.log('解密结果', DesDecrypt(param[field]));
 | 
			
		||||
    }
 | 
			
		||||
    return param;
 | 
			
		||||
};
 | 
			
		||||
 
 | 
			
		||||
@@ -25,26 +25,15 @@ import SvgIcon from '@/components/svgIcon/index.vue';
 | 
			
		||||
import { getDbDialect, noSchemaTypes } from '@/views/ops/db/dialect';
 | 
			
		||||
import TagTreeResourceSelect from '../../component/TagTreeResourceSelect.vue';
 | 
			
		||||
import { computed } from 'vue';
 | 
			
		||||
import { DbInst } from '../db';
 | 
			
		||||
 | 
			
		||||
const props = defineProps({
 | 
			
		||||
    dbId: {
 | 
			
		||||
        type: Number,
 | 
			
		||||
    },
 | 
			
		||||
    instName: {
 | 
			
		||||
        type: String,
 | 
			
		||||
    },
 | 
			
		||||
    dbName: {
 | 
			
		||||
        type: String,
 | 
			
		||||
    },
 | 
			
		||||
    tagPath: {
 | 
			
		||||
        type: String,
 | 
			
		||||
    },
 | 
			
		||||
    dbType: {
 | 
			
		||||
        type: String,
 | 
			
		||||
    },
 | 
			
		||||
});
 | 
			
		||||
const dbId = defineModel<number>('dbId');
 | 
			
		||||
const instName = defineModel<string>('instName');
 | 
			
		||||
const dbName = defineModel<string>('dbName');
 | 
			
		||||
const tagPath = defineModel<string>('tagPath');
 | 
			
		||||
const dbType = defineModel<string>('dbType');
 | 
			
		||||
 | 
			
		||||
const emits = defineEmits(['update:dbName', 'update:tagPath', 'update:instName', 'update:dbId', 'update:dbType', 'selectDb']);
 | 
			
		||||
const emits = defineEmits(['selectDb']);
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 树节点类型
 | 
			
		||||
@@ -62,7 +51,7 @@ class SqlExecNodeType {
 | 
			
		||||
 | 
			
		||||
const selectNode = computed({
 | 
			
		||||
    get: () => {
 | 
			
		||||
        return props.dbName ? `${props.tagPath} > ${props.instName} > ${props.dbName}` : '';
 | 
			
		||||
        return dbName.value ? `${tagPath.value} > ${instName.value} > ${dbName.value}` : '';
 | 
			
		||||
    },
 | 
			
		||||
    set: () => {
 | 
			
		||||
        //
 | 
			
		||||
@@ -101,9 +90,9 @@ const noSchemaType = (type: string) => {
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// 数据库实例节点类型
 | 
			
		||||
const NodeTypeDbInst = new NodeType(SqlExecNodeType.DbInst).withLoadNodesFunc((parentNode: TagTreeNode) => {
 | 
			
		||||
const NodeTypeDbInst = new NodeType(SqlExecNodeType.DbInst).withLoadNodesFunc(async (parentNode: TagTreeNode) => {
 | 
			
		||||
    const params = parentNode.params;
 | 
			
		||||
    const dbs = params.database.split(' ')?.sort();
 | 
			
		||||
    const dbs = (await DbInst.getDbNames(params))?.sort();
 | 
			
		||||
    let fn: NodeType;
 | 
			
		||||
    if (noSchemaType(params.type)) {
 | 
			
		||||
        fn = MysqlNodeTypes;
 | 
			
		||||
@@ -115,6 +104,7 @@ const NodeTypeDbInst = new NodeType(SqlExecNodeType.DbInst).withLoadNodesFunc((p
 | 
			
		||||
            .withParams({
 | 
			
		||||
                tagPath: params.tagPath,
 | 
			
		||||
                id: params.id,
 | 
			
		||||
                code: params.code,
 | 
			
		||||
                instanceId: params.instanceId,
 | 
			
		||||
                name: params.name,
 | 
			
		||||
                type: params.type,
 | 
			
		||||
@@ -155,12 +145,12 @@ const NodeTypePostgresSchema = new NodeType(SqlExecNodeType.PgSchema);
 | 
			
		||||
 | 
			
		||||
const changeNode = (nodeData: TagTreeNode) => {
 | 
			
		||||
    const params = nodeData.params;
 | 
			
		||||
    // postgres
 | 
			
		||||
    emits('update:dbName', params.db);
 | 
			
		||||
    emits('update:instName', params.name);
 | 
			
		||||
    emits('update:dbId', params.id);
 | 
			
		||||
    emits('update:tagPath', params.tagPath);
 | 
			
		||||
    emits('update:dbType', params.type);
 | 
			
		||||
    dbName.value = params.db;
 | 
			
		||||
    instName.value = params.name;
 | 
			
		||||
    dbId.value = params.id;
 | 
			
		||||
    tagPath.value = params.tagPath;
 | 
			
		||||
    dbType.value = params.type;
 | 
			
		||||
 | 
			
		||||
    emits('selectDb', params);
 | 
			
		||||
};
 | 
			
		||||
</script>
 | 
			
		||||
 
 | 
			
		||||
@@ -28,7 +28,7 @@
 | 
			
		||||
                        :limit="100"
 | 
			
		||||
                    >
 | 
			
		||||
                        <el-tooltip :show-after="1000" class="box-item" effect="dark" content="SQL脚本执行" placement="top">
 | 
			
		||||
                            <el-link type="success" :underline="false" icon="Document"></el-link>
 | 
			
		||||
                            <el-link v-auth="'db:sqlscript:run'" type="success" :underline="false" icon="Document"></el-link>
 | 
			
		||||
                        </el-tooltip>
 | 
			
		||||
                    </el-upload>
 | 
			
		||||
                </div>
 | 
			
		||||
@@ -52,7 +52,7 @@
 | 
			
		||||
 | 
			
		||||
            <Pane :size="100 - state.editorSize">
 | 
			
		||||
                <div class="mt5 sql-exec-res h100">
 | 
			
		||||
                    <el-tabs class="h100 w100" v-if="state.execResTabs.length > 0" @tab-remove="onRemoveTab" v-model="state.activeTab">
 | 
			
		||||
                    <el-tabs class="h100 w100" v-if="state.execResTabs.length > 0" @tab-remove="onRemoveTab" @tab-change="active" v-model="state.activeTab">
 | 
			
		||||
                        <el-tab-pane class="h100" closable v-for="dt in state.execResTabs" :label="dt.id" :name="dt.id" :key="dt.id">
 | 
			
		||||
                            <template #label>
 | 
			
		||||
                                <el-popover :show-after="1000" placement="top-start" title="执行信息" trigger="hover" :width="300">
 | 
			
		||||
@@ -296,46 +296,50 @@ const onRunSql = async (newTab = false) => {
 | 
			
		||||
    notBlank(sql && sql.trim(), '请选中需要执行的sql');
 | 
			
		||||
    // 去除字符串前的空格、换行等
 | 
			
		||||
    sql = sql.replace(/(^\s*)/g, '');
 | 
			
		||||
    let execRemark = '';
 | 
			
		||||
    let canRun = true;
 | 
			
		||||
 | 
			
		||||
    const sqls = splitSql(sql);
 | 
			
		||||
 | 
			
		||||
    // 简单截取前十个字符
 | 
			
		||||
    const sqlPrefix = sql.slice(0, 10).toLowerCase();
 | 
			
		||||
    if (
 | 
			
		||||
    const nonQuery =
 | 
			
		||||
        sqlPrefix.startsWith('update') ||
 | 
			
		||||
        sqlPrefix.startsWith('insert') ||
 | 
			
		||||
        sqlPrefix.startsWith('delete') ||
 | 
			
		||||
        sqlPrefix.startsWith('alert') ||
 | 
			
		||||
        sqlPrefix.startsWith('drop') ||
 | 
			
		||||
        sqlPrefix.startsWith('create')
 | 
			
		||||
    ) {
 | 
			
		||||
        const res: any = await ElMessageBox.prompt('请输入备注', 'Tip', {
 | 
			
		||||
            confirmButtonText: '确定',
 | 
			
		||||
            cancelButtonText: '取消',
 | 
			
		||||
            inputPattern: /^[\s\S]*.*[^\s][\s\S]*$/,
 | 
			
		||||
            inputErrorMessage: '请输入执行该sql的备注信息',
 | 
			
		||||
        });
 | 
			
		||||
        execRemark = res.value;
 | 
			
		||||
        if (!execRemark) {
 | 
			
		||||
            canRun = false;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    if (!canRun) {
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // 启用工单审批
 | 
			
		||||
    if (execRemark && getNowDbInst().flowProcdefKey) {
 | 
			
		||||
        try {
 | 
			
		||||
            await getNowDbInst().runSql(props.dbName, sql, execRemark);
 | 
			
		||||
            ElMessage.success('工单提交成功');
 | 
			
		||||
            return;
 | 
			
		||||
        } catch (e) {
 | 
			
		||||
            ElMessage.success('工单提交失败');
 | 
			
		||||
            return;
 | 
			
		||||
        sqlPrefix.startsWith('create');
 | 
			
		||||
 | 
			
		||||
    if (sqls.length == 1) {
 | 
			
		||||
        let execRemark;
 | 
			
		||||
        if (nonQuery) {
 | 
			
		||||
            const res: any = await ElMessageBox.prompt('请输入备注', 'Tip', {
 | 
			
		||||
                confirmButtonText: '确定',
 | 
			
		||||
                cancelButtonText: '取消',
 | 
			
		||||
                inputErrorMessage: '输入执行该sql的备注信息',
 | 
			
		||||
            });
 | 
			
		||||
            execRemark = res.value;
 | 
			
		||||
        }
 | 
			
		||||
        runSql(sql, execRemark, newTab);
 | 
			
		||||
    } else {
 | 
			
		||||
        let isFirst = true;
 | 
			
		||||
        for (let s of sqls) {
 | 
			
		||||
            if (isFirst) {
 | 
			
		||||
                isFirst = false;
 | 
			
		||||
                runSql(s, '', newTab);
 | 
			
		||||
            } else {
 | 
			
		||||
                runSql(s, '', true);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 执行单条sql
 | 
			
		||||
 *
 | 
			
		||||
 * @param sql 单条sql
 | 
			
		||||
 * @param newTab 是否新建tab
 | 
			
		||||
 */
 | 
			
		||||
const runSql = async (sql: string, remark = '', newTab = false) => {
 | 
			
		||||
    let execRes: ExecResTab;
 | 
			
		||||
    let i = 0;
 | 
			
		||||
    let id;
 | 
			
		||||
@@ -363,12 +367,16 @@ const onRunSql = async (newTab = false) => {
 | 
			
		||||
        execRes.errorMsg = '';
 | 
			
		||||
        execRes.sql = '';
 | 
			
		||||
 | 
			
		||||
        const { data, execute, isFetching, abort } = getNowDbInst().execSql(props.dbName, sql, execRemark);
 | 
			
		||||
        const { data, execute, isFetching, abort } = getNowDbInst().execSql(props.dbName, sql, remark);
 | 
			
		||||
        execRes.loading = isFetching;
 | 
			
		||||
        execRes.abortFn = abort;
 | 
			
		||||
 | 
			
		||||
        await execute();
 | 
			
		||||
        const colAndData: any = data.value;
 | 
			
		||||
        const colAndData: any = (data.value as any)[0];
 | 
			
		||||
        if (colAndData.errorMsg) {
 | 
			
		||||
            throw { msg: colAndData.errorMsg };
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (colAndData.res.length == 0) {
 | 
			
		||||
            state.tableDataEmptyText = '查无数据';
 | 
			
		||||
        }
 | 
			
		||||
@@ -388,7 +396,8 @@ const onRunSql = async (newTab = false) => {
 | 
			
		||||
        execRes.data = [];
 | 
			
		||||
        execRes.tableColumn = [];
 | 
			
		||||
        execRes.table = '';
 | 
			
		||||
        execRes.errorMsg = e.msg;
 | 
			
		||||
        // 要实时响应,故需要用索引改变数据才生效
 | 
			
		||||
        state.execResTabs[i].errorMsg = e.msg;
 | 
			
		||||
        return;
 | 
			
		||||
    } finally {
 | 
			
		||||
        execRes.sql = sql;
 | 
			
		||||
@@ -410,6 +419,64 @@ const onRunSql = async (newTab = false) => {
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
function splitSql(sql: string) {
 | 
			
		||||
    let state = 'normal';
 | 
			
		||||
    let buffer = '';
 | 
			
		||||
    let result = [];
 | 
			
		||||
    let inString = null; // 用于记录当前字符串的引号类型(' 或 ")
 | 
			
		||||
 | 
			
		||||
    for (let i = 0; i < sql.length; i++) {
 | 
			
		||||
        const char = sql[i];
 | 
			
		||||
        const nextChar = sql[i + 1];
 | 
			
		||||
 | 
			
		||||
        if (state === 'normal') {
 | 
			
		||||
            if (char === '-' && nextChar === '-') {
 | 
			
		||||
                state = 'singleLineComment';
 | 
			
		||||
                i++; // 跳过下一个字符
 | 
			
		||||
            } else if (char === '/' && nextChar === '*') {
 | 
			
		||||
                state = 'multiLineComment';
 | 
			
		||||
                i++; // 跳过下一个字符
 | 
			
		||||
            } else if (char === "'" || char === '"') {
 | 
			
		||||
                state = 'string';
 | 
			
		||||
                inString = char;
 | 
			
		||||
                buffer += char;
 | 
			
		||||
            } else if (char === ';') {
 | 
			
		||||
                if (buffer.trim()) {
 | 
			
		||||
                    result.push(buffer.trim());
 | 
			
		||||
                }
 | 
			
		||||
                buffer = '';
 | 
			
		||||
            } else {
 | 
			
		||||
                buffer += char;
 | 
			
		||||
            }
 | 
			
		||||
        } else if (state === 'string') {
 | 
			
		||||
            buffer += char;
 | 
			
		||||
            if (char === '\\') {
 | 
			
		||||
                // 处理转义字符
 | 
			
		||||
                buffer += nextChar;
 | 
			
		||||
                i++;
 | 
			
		||||
            } else if (char === inString) {
 | 
			
		||||
                state = 'normal';
 | 
			
		||||
                inString = null;
 | 
			
		||||
            }
 | 
			
		||||
        } else if (state === 'singleLineComment') {
 | 
			
		||||
            if (char === '\n') {
 | 
			
		||||
                state = 'normal';
 | 
			
		||||
            }
 | 
			
		||||
        } else if (state === 'multiLineComment') {
 | 
			
		||||
            if (char === '*' && nextChar === '/') {
 | 
			
		||||
                state = 'normal';
 | 
			
		||||
                i++; // 跳过下一个字符
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (buffer.trim()) {
 | 
			
		||||
        result.push(buffer.trim());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return result;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 获取sql,如果有鼠标选中,则返回选中内容,否则返回输入框内所有内容
 | 
			
		||||
 */
 | 
			
		||||
@@ -707,6 +774,19 @@ const initMonacoEditor = () => {
 | 
			
		||||
        },
 | 
			
		||||
    });
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const active = () => {
 | 
			
		||||
    const resTab = state.execResTabs[state.activeTab - 1];
 | 
			
		||||
    if (!resTab || !resTab.dbTableRef) {
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    resTab.dbTableRef?.active();
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
defineExpose({
 | 
			
		||||
    active,
 | 
			
		||||
});
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style lang="scss">
 | 
			
		||||
 
 | 
			
		||||
@@ -6,7 +6,7 @@ export type SqlExecProps = {
 | 
			
		||||
    dbId: number;
 | 
			
		||||
    db: string;
 | 
			
		||||
    dbType?: string;
 | 
			
		||||
    flowProcdefKey?: string;
 | 
			
		||||
    flowProcdef?: any;
 | 
			
		||||
    runSuccessCallback?: Function;
 | 
			
		||||
    cancelCallback?: Function;
 | 
			
		||||
};
 | 
			
		||||
 
 | 
			
		||||
@@ -1,19 +1,8 @@
 | 
			
		||||
<template>
 | 
			
		||||
    <div>
 | 
			
		||||
        <el-dialog title="待执行SQL" v-model="dialogVisible" :show-close="false" width="600px">
 | 
			
		||||
        <el-dialog title="待执行SQL" v-model="dialogVisible" :show-close="false" width="600px" :close-on-click-modal="false">
 | 
			
		||||
            <monaco-editor height="300px" class="codesql" language="sql" v-model="sqlValue" />
 | 
			
		||||
            <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>
 | 
			
		||||
            <el-input @keyup.enter="runSql" ref="remarkInputRef" v-model="remark" placeholder="执行备注" class="mt5" />
 | 
			
		||||
 | 
			
		||||
            <template #footer>
 | 
			
		||||
                <span class="dialog-footer">
 | 
			
		||||
@@ -28,13 +17,13 @@
 | 
			
		||||
<script lang="ts" setup>
 | 
			
		||||
import { toRefs, ref, reactive, onMounted } from 'vue';
 | 
			
		||||
import { dbApi } from '@/views/ops/db/api';
 | 
			
		||||
import { ElDialog, ElButton, ElInput, ElMessage, InputInstance, ElDivider } from 'element-plus';
 | 
			
		||||
import { ElDialog, ElButton, ElInput, ElMessage, InputInstance } from 'element-plus';
 | 
			
		||||
// import base style
 | 
			
		||||
import MonacoEditor from '@/components/monaco/MonacoEditor.vue';
 | 
			
		||||
import { format as sqlFormatter } from 'sql-formatter';
 | 
			
		||||
 | 
			
		||||
import { SqlExecProps } from './SqlExecBox';
 | 
			
		||||
import ProcdefTasks from '@/views/flow/components/ProcdefTasks.vue';
 | 
			
		||||
import { isTrue } from '@/common/assert';
 | 
			
		||||
 | 
			
		||||
const props = withDefaults(defineProps<SqlExecProps>(), {});
 | 
			
		||||
 | 
			
		||||
@@ -58,14 +47,10 @@ onMounted(() => {
 | 
			
		||||
 * 执行sql
 | 
			
		||||
 */
 | 
			
		||||
const runSql = async () => {
 | 
			
		||||
    // 存在流程审批,则备注为必填
 | 
			
		||||
    if (!state.remark && props.flowProcdefKey) {
 | 
			
		||||
        ElMessage.error('请输入执行的备注信息');
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    try {
 | 
			
		||||
        state.btnLoading = true;
 | 
			
		||||
        runSuccess = true;
 | 
			
		||||
 | 
			
		||||
        const res = await dbApi.sqlExec.request({
 | 
			
		||||
            id: props.dbId,
 | 
			
		||||
            db: props.db,
 | 
			
		||||
@@ -73,21 +58,15 @@ const runSql = async () => {
 | 
			
		||||
            sql: state.sqlValue.trim(),
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        // 存在流程审批
 | 
			
		||||
        if (props.flowProcdefKey) {
 | 
			
		||||
            runSuccess = false;
 | 
			
		||||
            ElMessage.success('工单提交成功');
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        for (let re of res.res) {
 | 
			
		||||
            if (re.result !== 'success') {
 | 
			
		||||
                ElMessage.error(`${re.sql} \n执行失败: ${re.result}`);
 | 
			
		||||
                throw new Error(re.result);
 | 
			
		||||
        let isSuccess = true;
 | 
			
		||||
        for (let re of res) {
 | 
			
		||||
            if (re.errorMsg) {
 | 
			
		||||
                isSuccess = false;
 | 
			
		||||
                ElMessage.error(`${re.sql} \n执行失败: ${re.errorMsg}`);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        runSuccess = true;
 | 
			
		||||
        isTrue(isSuccess, '存在执行失败sql');
 | 
			
		||||
        ElMessage.success('执行成功');
 | 
			
		||||
    } catch (e) {
 | 
			
		||||
        runSuccess = false;
 | 
			
		||||
@@ -96,9 +75,9 @@ const runSql = async () => {
 | 
			
		||||
            if (props.runSuccessCallback) {
 | 
			
		||||
                props.runSuccessCallback();
 | 
			
		||||
            }
 | 
			
		||||
            cancel();
 | 
			
		||||
        }
 | 
			
		||||
        state.btnLoading = false;
 | 
			
		||||
        cancel();
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@@ -113,7 +92,7 @@ const cancel = () => {
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const open = () => {
 | 
			
		||||
    state.sqlValue = sqlFormatter(props.sql, { language: props.dbType || 'mysql' });
 | 
			
		||||
    state.sqlValue = sqlFormatter(props.sql, { language: (props.dbType || 'mysql') as any });
 | 
			
		||||
    state.dialogVisible = true;
 | 
			
		||||
    setTimeout(() => {
 | 
			
		||||
        remarkInputRef.value?.focus();
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,6 @@
 | 
			
		||||
<template>
 | 
			
		||||
    <div class="string-input-container w100" v-if="dataType == DataType.String">
 | 
			
		||||
    <div class="string-input-container w100" v-if="dataType == DataType.String || dataType == DataType.Number">
 | 
			
		||||
        <el-input
 | 
			
		||||
            v-if="dataType == DataType.String"
 | 
			
		||||
            :ref="(el: any) => focus && el?.focus()"
 | 
			
		||||
            :disabled="disabled"
 | 
			
		||||
            @blur="handleBlur"
 | 
			
		||||
@@ -13,18 +12,6 @@
 | 
			
		||||
        <SvgIcon v-if="showEditorIcon" @mousedown="openEditor" class="string-input-container-icon" name="FullScreen" :size="10" />
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
    <el-input
 | 
			
		||||
        v-else-if="dataType == DataType.Number"
 | 
			
		||||
        :ref="(el: any) => focus && el?.focus()"
 | 
			
		||||
        :disabled="disabled"
 | 
			
		||||
        @blur="handleBlur"
 | 
			
		||||
        class="w100 mb4"
 | 
			
		||||
        size="small"
 | 
			
		||||
        v-model.number="itemValue"
 | 
			
		||||
        :placeholder="placeholder"
 | 
			
		||||
        type="number"
 | 
			
		||||
    />
 | 
			
		||||
 | 
			
		||||
    <el-date-picker
 | 
			
		||||
        v-else-if="dataType == DataType.Date"
 | 
			
		||||
        :ref="(el: any) => focus && el?.focus()"
 | 
			
		||||
@@ -38,7 +25,7 @@
 | 
			
		||||
        :clearable="false"
 | 
			
		||||
        type="Date"
 | 
			
		||||
        value-format="YYYY-MM-DD"
 | 
			
		||||
        placeholder="选择日期"
 | 
			
		||||
        :placeholder="`选择日期-${placeholder}`"
 | 
			
		||||
    />
 | 
			
		||||
 | 
			
		||||
    <el-date-picker
 | 
			
		||||
@@ -54,7 +41,7 @@
 | 
			
		||||
        :clearable="false"
 | 
			
		||||
        type="datetime"
 | 
			
		||||
        value-format="YYYY-MM-DD HH:mm:ss"
 | 
			
		||||
        placeholder="选择日期时间"
 | 
			
		||||
        :placeholder="`选择日期时间-${placeholder}`"
 | 
			
		||||
    />
 | 
			
		||||
 | 
			
		||||
    <el-time-picker
 | 
			
		||||
@@ -69,13 +56,13 @@
 | 
			
		||||
        v-model="itemValue"
 | 
			
		||||
        :clearable="false"
 | 
			
		||||
        value-format="HH:mm:ss"
 | 
			
		||||
        placeholder="选择时间"
 | 
			
		||||
        :placeholder="`选择时间-${placeholder}`"
 | 
			
		||||
    />
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script lang="ts" setup>
 | 
			
		||||
import { computed, ref, Ref } from 'vue';
 | 
			
		||||
import { ElInput } from 'element-plus';
 | 
			
		||||
import { ElInput, ElMessage } from 'element-plus';
 | 
			
		||||
import { DataType } from '../../dialect/index';
 | 
			
		||||
import SvgIcon from '@/components/svgIcon/index.vue';
 | 
			
		||||
import MonacoEditorDialog from '@/components/monaco/MonacoEditorDialog';
 | 
			
		||||
@@ -130,6 +117,10 @@ const handleBlur = () => {
 | 
			
		||||
    if (editorOpening.value) {
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
    if (props.dataType == DataType.Number && itemValue.value && !/^-?\d*\.?\d+$/.test(itemValue.value)) {
 | 
			
		||||
        ElMessage.error('输入内容与类型不匹配');
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
    emit('update:modelValue', itemValue.value);
 | 
			
		||||
    emit('blur');
 | 
			
		||||
};
 | 
			
		||||
@@ -161,6 +152,10 @@ const getEditorLangByValue = (value: any) => {
 | 
			
		||||
<style lang="scss">
 | 
			
		||||
.string-input-container {
 | 
			
		||||
    position: relative;
 | 
			
		||||
 | 
			
		||||
    .el-input__wrapper {
 | 
			
		||||
        padding: 1px 3px;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
.string-input-container-show-icon {
 | 
			
		||||
    .el-input__inner {
 | 
			
		||||
@@ -183,6 +178,10 @@ const getEditorLangByValue = (value: any) => {
 | 
			
		||||
    .el-input__prefix {
 | 
			
		||||
        display: none;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .el-input__wrapper {
 | 
			
		||||
        padding: 1px 3px;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.edit-time-picker-popper {
 | 
			
		||||
 
 | 
			
		||||
@@ -15,6 +15,7 @@
 | 
			
		||||
                    fixed
 | 
			
		||||
                    class="table"
 | 
			
		||||
                    :row-event-handlers="rowEventHandlers"
 | 
			
		||||
                    @scroll="onTableScroll"
 | 
			
		||||
                >
 | 
			
		||||
                    <template #header="{ columns }">
 | 
			
		||||
                        <div v-for="(column, i) in columns" :key="i">
 | 
			
		||||
@@ -36,7 +37,7 @@
 | 
			
		||||
                                    <!-- 字段列的数据类型 -->
 | 
			
		||||
                                    <div class="column-type">
 | 
			
		||||
                                        <span v-if="column.dataTypeSubscript === 'icon-clock'">
 | 
			
		||||
                                            <SvgIcon :size="10" name="Clock" style="cursor: unset" />
 | 
			
		||||
                                            <SvgIcon :size="9" name="Clock" style="cursor: unset" />
 | 
			
		||||
                                        </span>
 | 
			
		||||
                                        <span class="font8" v-else>{{ column.dataTypeSubscript }}</span>
 | 
			
		||||
                                    </div>
 | 
			
		||||
@@ -59,9 +60,7 @@
 | 
			
		||||
                                    </div>
 | 
			
		||||
 | 
			
		||||
                                    <div v-else class="header-column-title">
 | 
			
		||||
                                        <b class="el-text">
 | 
			
		||||
                                            {{ column.title }}
 | 
			
		||||
                                        </b>
 | 
			
		||||
                                        <b class="el-text"> {{ column.title }} </b>
 | 
			
		||||
                                    </div>
 | 
			
		||||
 | 
			
		||||
                                    <!-- 字段列右部分内容 -->
 | 
			
		||||
@@ -96,7 +95,7 @@
 | 
			
		||||
                                    />
 | 
			
		||||
                                </div>
 | 
			
		||||
 | 
			
		||||
                                <div v-else :class="isUpdated(rowIndex, column.dataKey) ? 'update_field_active' : ''">
 | 
			
		||||
                                <div v-else :class="isUpdated(rowIndex, column.dataKey) ? 'update_field_active ml2 mr2' : 'ml2 mr2'">
 | 
			
		||||
                                    <span v-if="rowData[column.dataKey!] === null" style="color: var(--el-color-info-light-5)"> NULL </span>
 | 
			
		||||
 | 
			
		||||
                                    <span v-else :title="rowData[column.dataKey!]" class="el-text el-text--small is-truncated">
 | 
			
		||||
@@ -121,7 +120,7 @@
 | 
			
		||||
 | 
			
		||||
                    <template #empty>
 | 
			
		||||
                        <div style="text-align: center">
 | 
			
		||||
                            <el-empty class="h100" :description="props.emptyText" :image-size="100" />
 | 
			
		||||
                            <el-empty :description="props.emptyText" :image-size="100" />
 | 
			
		||||
                        </div>
 | 
			
		||||
                    </template>
 | 
			
		||||
                </el-table-v2>
 | 
			
		||||
@@ -134,7 +133,7 @@
 | 
			
		||||
                    <el-button id="copyValue" @click="copyGenTxt(state.genTxtDialog.txt)" icon="CopyDocument" type="success" size="small">一键复制</el-button>
 | 
			
		||||
                </div>
 | 
			
		||||
            </template>
 | 
			
		||||
            <el-input v-model="state.genTxtDialog.txt" type="textarea" rows="20" />
 | 
			
		||||
            <el-input v-model="state.genTxtDialog.txt" type="textarea" :rows="20" />
 | 
			
		||||
        </el-dialog>
 | 
			
		||||
 | 
			
		||||
        <DbTableDataForm
 | 
			
		||||
@@ -157,11 +156,11 @@
 | 
			
		||||
import { onBeforeUnmount, onMounted, reactive, ref, toRefs, watch } from 'vue';
 | 
			
		||||
import { ElInput, ElMessage } from 'element-plus';
 | 
			
		||||
import { copyToClipboard } from '@/common/utils/string';
 | 
			
		||||
import { DbInst } from '@/views/ops/db/db';
 | 
			
		||||
import { DbInst, DbThemeConfig } from '@/views/ops/db/db';
 | 
			
		||||
import { Contextmenu, ContextmenuItem } from '@/components/contextmenu';
 | 
			
		||||
import SvgIcon from '@/components/svgIcon/index.vue';
 | 
			
		||||
import { exportCsv, exportFile } from '@/common/utils/export';
 | 
			
		||||
import { dateStrFormat } from '@/common/utils/date';
 | 
			
		||||
import { formatDate } from '@/common/utils/format';
 | 
			
		||||
import { useIntervalFn, useStorage } from '@vueuse/core';
 | 
			
		||||
import { ColumnTypeSubscript, compatibleMysql, DataType, DbDialect, getDbDialect } from '../../dialect/index';
 | 
			
		||||
import ColumnFormItem from './ColumnFormItem.vue';
 | 
			
		||||
@@ -259,12 +258,10 @@ const cmDataDel = new ContextmenuItem('deleteData', '删除')
 | 
			
		||||
        return state.table == '';
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
const cmDataEdit = new ContextmenuItem('editData', '编辑行')
 | 
			
		||||
    .withIcon('edit')
 | 
			
		||||
    .withOnClick(() => onEditRowData())
 | 
			
		||||
    .withHideFunc(() => {
 | 
			
		||||
        return state.table == '';
 | 
			
		||||
    });
 | 
			
		||||
const cmFormView = new ContextmenuItem('formView', '表单视图').withIcon('Document').withOnClick(() => onEditRowData());
 | 
			
		||||
// .withHideFunc(() => {
 | 
			
		||||
//     return state.table == '';
 | 
			
		||||
// });
 | 
			
		||||
 | 
			
		||||
const cmDataGenInsertSql = new ContextmenuItem('genInsertSql', 'Insert SQL')
 | 
			
		||||
    .withIcon('tickets')
 | 
			
		||||
@@ -364,7 +361,7 @@ const state = reactive({
 | 
			
		||||
 | 
			
		||||
const { tableHeight, datas } = toRefs(state);
 | 
			
		||||
 | 
			
		||||
const dbConfig = useStorage('dbConfig', { showColumnComment: false });
 | 
			
		||||
const dbConfig = useStorage('dbConfig', DbThemeConfig);
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 行号字段列
 | 
			
		||||
@@ -476,9 +473,9 @@ const setTableColumns = (columns: any) => {
 | 
			
		||||
    state.columns = columns.map((x: any) => {
 | 
			
		||||
        const columnName = x.columnName;
 | 
			
		||||
        // 数据类型
 | 
			
		||||
        x.dataType = dbDialect.getDataType(x.dataType);
 | 
			
		||||
        x.dataType = dbDialect.getDataType(x.columnType);
 | 
			
		||||
        x.dataTypeSubscript = ColumnTypeSubscript[x.dataType];
 | 
			
		||||
        x.remark = `${x.showDataType} ${x.columnComment ? ' |  ' + x.columnComment : ''}`;
 | 
			
		||||
        x.remark = `${x.columnType} ${x.columnComment ? ' |  ' + x.columnComment : ''}`;
 | 
			
		||||
 | 
			
		||||
        return {
 | 
			
		||||
            ...x,
 | 
			
		||||
@@ -486,7 +483,7 @@ const setTableColumns = (columns: any) => {
 | 
			
		||||
            dataKey: columnName,
 | 
			
		||||
            width: DbInst.flexColumnWidth(columnName, state.datas),
 | 
			
		||||
            title: columnName,
 | 
			
		||||
            align: 'center',
 | 
			
		||||
            align: x.dataType == DataType.Number ? 'right' : 'left',
 | 
			
		||||
            headerClass: 'table-column',
 | 
			
		||||
            class: 'table-column',
 | 
			
		||||
            sortable: true,
 | 
			
		||||
@@ -596,7 +593,7 @@ const dataContextmenuClick = (event: any, rowIndex: number, column: any, data: a
 | 
			
		||||
    const { clientX, clientY } = event;
 | 
			
		||||
    state.contextmenu.dropdown.x = clientX;
 | 
			
		||||
    state.contextmenu.dropdown.y = clientY;
 | 
			
		||||
    state.contextmenu.items = [cmDataCopyCell, cmDataDel, cmDataEdit, cmDataGenInsertSql, cmDataGenJson, cmDataExportCsv, cmDataExportSql];
 | 
			
		||||
    state.contextmenu.items = [cmDataCopyCell, cmDataDel, cmFormView, cmDataGenInsertSql, cmDataGenJson, cmDataExportCsv, cmDataExportSql];
 | 
			
		||||
    contextmenuRef.value.openContextmenu({ column, rowData: data });
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@@ -624,12 +621,12 @@ const onDeleteData = async () => {
 | 
			
		||||
const onEditRowData = () => {
 | 
			
		||||
    const selectionDatas = Array.from(selectionRowsMap.values());
 | 
			
		||||
    if (selectionDatas.length > 1) {
 | 
			
		||||
        ElMessage.warning('只能编辑一行数据');
 | 
			
		||||
        ElMessage.warning('只能选择一行数据');
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
    const data = selectionDatas[0];
 | 
			
		||||
    state.tableDataFormDialog.data = data;
 | 
			
		||||
    state.tableDataFormDialog.title = `编辑表'${props.table}'数据`;
 | 
			
		||||
    state.tableDataFormDialog.data = { ...data };
 | 
			
		||||
    state.tableDataFormDialog.title = state.table ? `'${props.table}'表单数据` : '表单视图';
 | 
			
		||||
    state.tableDataFormDialog.visible = true;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@@ -645,7 +642,7 @@ const onGenerateJson = async () => {
 | 
			
		||||
    // 按列字段重新排序对象key
 | 
			
		||||
    const jsonObj = [];
 | 
			
		||||
    for (let selectionData of selectionDatas) {
 | 
			
		||||
        let obj = {};
 | 
			
		||||
        let obj: any = {};
 | 
			
		||||
        for (let column of state.columns) {
 | 
			
		||||
            if (column.show) {
 | 
			
		||||
                obj[column.title] = selectionData[column.dataKey];
 | 
			
		||||
@@ -674,13 +671,13 @@ const onExportCsv = () => {
 | 
			
		||||
            columnNames.push(column.columnName);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    exportCsv(`数据导出-${state.table}-${dateStrFormat('yyyyMMddHHmm', new Date().toString())}`, columnNames, dataList);
 | 
			
		||||
    exportCsv(`数据导出-${state.table}-${formatDate(new Date(), 'yyyyMMddHHmm')}`, columnNames, dataList);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const onExportSql = async () => {
 | 
			
		||||
    const selectionDatas = state.datas;
 | 
			
		||||
    exportFile(
 | 
			
		||||
        `数据导出-${state.table}-${dateStrFormat('yyyyMMddHHmm', new Date().toString())}.sql`,
 | 
			
		||||
        `数据导出-${state.table}-${formatDate(new Date(), 'yyyyMMddHHmm')}.sql`,
 | 
			
		||||
        await getNowDbInst().genInsertSql(state.db, state.table, selectionDatas)
 | 
			
		||||
    );
 | 
			
		||||
};
 | 
			
		||||
@@ -749,7 +746,7 @@ const submitUpdateFields = async () => {
 | 
			
		||||
 | 
			
		||||
    for (let updateRow of cellUpdateMap.values()) {
 | 
			
		||||
        const rowData = { ...updateRow.rowData };
 | 
			
		||||
        let updateColumnValue = {};
 | 
			
		||||
        let updateColumnValue: any = {};
 | 
			
		||||
 | 
			
		||||
        for (let k of updateRow.columnsMap.keys()) {
 | 
			
		||||
            const v = updateRow.columnsMap.get(k);
 | 
			
		||||
@@ -763,7 +760,7 @@ const submitUpdateFields = async () => {
 | 
			
		||||
        res += await dbInst.genUpdateSql(db, state.table, updateColumnValue, rowData);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    dbInst.promptExeSql(db, res, cancelUpdateFields, () => {
 | 
			
		||||
    dbInst.promptExeSql(db, res, null, () => {
 | 
			
		||||
        triggerRefresh();
 | 
			
		||||
        cellUpdateMap.clear();
 | 
			
		||||
        changeUpdatedField();
 | 
			
		||||
@@ -810,11 +807,11 @@ const getFormatTimeValue = (dataType: DataType, originValue: string): string =>
 | 
			
		||||
 | 
			
		||||
    switch (dataType) {
 | 
			
		||||
        case DataType.Time:
 | 
			
		||||
            return dateStrFormat('HH:mm:ss', originValue);
 | 
			
		||||
            return formatDate(originValue, 'HH:mm:ss');
 | 
			
		||||
        case DataType.Date:
 | 
			
		||||
            return dateStrFormat('yyyy-MM-dd', originValue);
 | 
			
		||||
            return formatDate(originValue, 'YYYY-MM-DD');
 | 
			
		||||
        case DataType.DateTime:
 | 
			
		||||
            return dateStrFormat('yyyy-MM-dd HH:mm:ss', originValue);
 | 
			
		||||
            return formatDate(originValue, 'YYYY-MM-DD HH:mm:ss');
 | 
			
		||||
        default:
 | 
			
		||||
            return originValue;
 | 
			
		||||
    }
 | 
			
		||||
@@ -832,11 +829,23 @@ const triggerRefresh = () => {
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const scrollLeftValue = ref(0);
 | 
			
		||||
const onTableScroll = (param: any) => {
 | 
			
		||||
    scrollLeftValue.value = param.scrollLeft;
 | 
			
		||||
};
 | 
			
		||||
/**
 | 
			
		||||
 * 激活表格,恢复滚动位置,否则会造成表头与数据单元格错位(暂不知为啥,先这样解决)
 | 
			
		||||
 */
 | 
			
		||||
const active = () => {
 | 
			
		||||
    setTimeout(() => tableRef.value.scrollToLeft(scrollLeftValue.value));
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const getNowDbInst = () => {
 | 
			
		||||
    return DbInst.getInst(state.dbId);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
defineExpose({
 | 
			
		||||
    active,
 | 
			
		||||
    submitUpdateFields,
 | 
			
		||||
    cancelUpdateFields,
 | 
			
		||||
});
 | 
			
		||||
@@ -880,8 +889,8 @@ defineExpose({
 | 
			
		||||
        color: var(--el-color-info-light-3);
 | 
			
		||||
        font-weight: bold;
 | 
			
		||||
        position: absolute;
 | 
			
		||||
        top: -5px;
 | 
			
		||||
        padding: 2px;
 | 
			
		||||
        top: -7px;
 | 
			
		||||
        padding: 1px;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .column-right {
 | 
			
		||||
 
 | 
			
		||||
@@ -6,10 +6,10 @@
 | 
			
		||||
                :key="column.columnName"
 | 
			
		||||
                class="w100 mb5"
 | 
			
		||||
                :prop="column.columnName"
 | 
			
		||||
                :required="column.nullable != 'YES' && !column.isPrimaryKey && !column.isIdentity"
 | 
			
		||||
                :required="props.tableName != '' && !column.nullable && !column.isPrimaryKey && !column.isIdentity"
 | 
			
		||||
            >
 | 
			
		||||
                <template #label>
 | 
			
		||||
                    <span class="pointer" :title="`${column.showDataType} | ${column.columnComment}`">
 | 
			
		||||
                    <span class="pointer" :title="column?.columnComment ? `${column.columnType} | ${column.columnComment}` : column.columnType">
 | 
			
		||||
                        {{ column.columnName }}
 | 
			
		||||
                    </span>
 | 
			
		||||
                </template>
 | 
			
		||||
@@ -17,13 +17,13 @@
 | 
			
		||||
                <ColumnFormItem
 | 
			
		||||
                    v-model="modelValue[`${column.columnName}`]"
 | 
			
		||||
                    :data-type="dbInst.getDialect().getDataType(column.dataType)"
 | 
			
		||||
                    :placeholder="`${column.showDataType}  ${column.columnComment}`"
 | 
			
		||||
                    :placeholder="column?.columnComment ? `${column.columnType} | ${column.columnComment}` : column.columnType"
 | 
			
		||||
                    :column-name="column.columnName"
 | 
			
		||||
                    :disabled="column.isIdentity"
 | 
			
		||||
                />
 | 
			
		||||
            </el-form-item>
 | 
			
		||||
        </el-form>
 | 
			
		||||
        <template #footer>
 | 
			
		||||
        <template #footer v-if="props.tableName">
 | 
			
		||||
            <span class="dialog-footer">
 | 
			
		||||
                <el-button @click="closeDialog">取消</el-button>
 | 
			
		||||
                <el-button type="primary" @click="confirm">确定</el-button>
 | 
			
		||||
@@ -37,7 +37,6 @@ 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;
 | 
			
		||||
@@ -86,35 +85,35 @@ const closeDialog = () => {
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const confirm = async () => {
 | 
			
		||||
    dataForm.value.validate(async (valid: boolean) => {
 | 
			
		||||
        if (!valid) {
 | 
			
		||||
            ElMessage.error('请正确填写数据信息');
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
    try {
 | 
			
		||||
        await dataForm.value.validate();
 | 
			
		||||
    } catch (e: any) {
 | 
			
		||||
        ElMessage.error('请正确填写数据信息');
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
        const dbInst = props.dbInst;
 | 
			
		||||
        const data = modelValue.value;
 | 
			
		||||
        const db = props.dbName;
 | 
			
		||||
        const tableName = props.tableName;
 | 
			
		||||
    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');
 | 
			
		||||
    let sql = '';
 | 
			
		||||
    if (oldValue) {
 | 
			
		||||
        const updateColumnValue: any = {};
 | 
			
		||||
        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>
 | 
			
		||||
 
 | 
			
		||||
@@ -12,15 +12,29 @@
 | 
			
		||||
                        width="auto"
 | 
			
		||||
                        title="表格字段配置"
 | 
			
		||||
                        trigger="click"
 | 
			
		||||
                        @hide="triggerCheckedColumns"
 | 
			
		||||
                    >
 | 
			
		||||
                        <div v-for="(item, index) in columns" :key="index">
 | 
			
		||||
                        <div><el-input v-model="checkedShowColumns.searchKey" size="small" placeholder="输入列名或备注过滤" /></div>
 | 
			
		||||
                        <div>
 | 
			
		||||
                            <el-checkbox
 | 
			
		||||
                                v-model="item.show"
 | 
			
		||||
                                :label="`${!item.columnComment ? item.columnName : item.columnName + ' [' + item.columnComment + ']'}`"
 | 
			
		||||
                                :true-value="true"
 | 
			
		||||
                                :false-value="false"
 | 
			
		||||
                                v-model="checkedShowColumns.checkedAllColumn"
 | 
			
		||||
                                :indeterminate="checkedShowColumns.isIndeterminate"
 | 
			
		||||
                                @change="handleCheckAllColumnChange"
 | 
			
		||||
                                size="small"
 | 
			
		||||
                            />
 | 
			
		||||
                            >
 | 
			
		||||
                                选择所有
 | 
			
		||||
                            </el-checkbox>
 | 
			
		||||
 | 
			
		||||
                            <el-checkbox-group v-model="checkedShowColumns.columnNames" @change="handleCheckedColumnChange">
 | 
			
		||||
                                <div v-for="(item, index) in filterCheckedColumns" :key="index">
 | 
			
		||||
                                    <el-checkbox
 | 
			
		||||
                                        :key="index"
 | 
			
		||||
                                        :label="`${!item.columnComment ? item.columnName : item.columnName + ' [' + item.columnComment + ']'}`"
 | 
			
		||||
                                        :value="item.columnName"
 | 
			
		||||
                                        size="small"
 | 
			
		||||
                                    />
 | 
			
		||||
                                </div>
 | 
			
		||||
                            </el-checkbox-group>
 | 
			
		||||
                        </div>
 | 
			
		||||
                        <template #reference>
 | 
			
		||||
                            <el-link icon="Operation" size="small" :underline="false"></el-link>
 | 
			
		||||
@@ -36,33 +50,6 @@
 | 
			
		||||
                    </el-tooltip>
 | 
			
		||||
                    <el-divider direction="vertical" border-style="dashed" />
 | 
			
		||||
 | 
			
		||||
                    <el-tooltip :show-after="500" class="box-item" effect="dark" content="commit" placement="top">
 | 
			
		||||
                        <template #content>
 | 
			
		||||
                            1. 右击数据/表头可显示操作菜单 <br />
 | 
			
		||||
                            2. 按住Ctrl点击数据则为多选 <br />
 | 
			
		||||
                            3. 双击单元格可编辑数据 <br />
 | 
			
		||||
                            4. 鼠标悬停字段名或标签树的表名可提示相关备注
 | 
			
		||||
                        </template>
 | 
			
		||||
                        <el-link icon="QuestionFilled" :underline="false"> </el-link>
 | 
			
		||||
                    </el-tooltip>
 | 
			
		||||
                    <el-divider direction="vertical" border-style="dashed" />
 | 
			
		||||
 | 
			
		||||
                    <!-- 表数据展示配置 -->
 | 
			
		||||
                    <el-popover
 | 
			
		||||
                        popper-style="max-height: 550px; overflow: auto; max-width: 450px"
 | 
			
		||||
                        placement="bottom"
 | 
			
		||||
                        width="auto"
 | 
			
		||||
                        title="展示配置"
 | 
			
		||||
                        trigger="click"
 | 
			
		||||
                    >
 | 
			
		||||
                        <el-checkbox v-model="dbConfig.showColumnComment" label="显示字段备注" :true-value="true" :false-value="false" size="small" />
 | 
			
		||||
                        <template #reference>
 | 
			
		||||
                            <el-link type="primary" icon="setting" :underline="false"></el-link>
 | 
			
		||||
                        </template>
 | 
			
		||||
                    </el-popover>
 | 
			
		||||
 | 
			
		||||
                    <el-divider direction="vertical" border-style="dashed" />
 | 
			
		||||
 | 
			
		||||
                    <el-tooltip :show-after="500" v-if="hasUpdatedFileds" class="box-item" effect="dark" content="提交修改" placement="top">
 | 
			
		||||
                        <el-link @click="submitUpdateFields()" type="success" :underline="false" class="font12">提交</el-link>
 | 
			
		||||
                    </el-tooltip>
 | 
			
		||||
@@ -98,7 +85,7 @@
 | 
			
		||||
                        <el-divider direction="vertical" />
 | 
			
		||||
 | 
			
		||||
                        <span style="color: var(--el-color-info-light-3)">
 | 
			
		||||
                            {{ item.showDataType }}
 | 
			
		||||
                            {{ item.columnType }}
 | 
			
		||||
 | 
			
		||||
                            <template v-if="item.columnComment">
 | 
			
		||||
                                <el-divider direction="vertical" />
 | 
			
		||||
@@ -255,8 +242,8 @@ import { DbInst } from '@/views/ops/db/db';
 | 
			
		||||
import DbTableData from './DbTableData.vue';
 | 
			
		||||
import { DbDialect } from '@/views/ops/db/dialect';
 | 
			
		||||
import SvgIcon from '@/components/svgIcon/index.vue';
 | 
			
		||||
import { useEventListener, useStorage } from '@vueuse/core';
 | 
			
		||||
import { copyToClipboard } from '@/common/utils/string';
 | 
			
		||||
import { useEventListener } from '@vueuse/core';
 | 
			
		||||
import { copyToClipboard, fuzzyMatchField } from '@/common/utils/string';
 | 
			
		||||
import DbTableDataForm from './DbTableDataForm.vue';
 | 
			
		||||
 | 
			
		||||
const props = defineProps({
 | 
			
		||||
@@ -285,8 +272,6 @@ const condDialogInputRef: Ref = ref(null);
 | 
			
		||||
 | 
			
		||||
const defaultPageSize = DbInst.DefaultLimit;
 | 
			
		||||
 | 
			
		||||
const dbConfig = useStorage('dbConfig', { showColumnComment: false });
 | 
			
		||||
 | 
			
		||||
const state = reactive({
 | 
			
		||||
    datas: [],
 | 
			
		||||
    sql: '', // 当前数据tab执行的sql
 | 
			
		||||
@@ -329,9 +314,17 @@ const state = reactive({
 | 
			
		||||
    tableHeight: '600px',
 | 
			
		||||
    hasUpdatedFileds: false,
 | 
			
		||||
    dbDialect: {} as DbDialect,
 | 
			
		||||
 | 
			
		||||
    checkedShowColumns: {
 | 
			
		||||
        searchKey: '',
 | 
			
		||||
        checkedAllColumn: true,
 | 
			
		||||
        isIndeterminate: false,
 | 
			
		||||
        columnNames: [] as any,
 | 
			
		||||
    },
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const { datas, condition, loading, columns, pageNum, pageSize, pageSizes, sql, hasUpdatedFileds, conditionDialog, addDataDialog } = toRefs(state);
 | 
			
		||||
const { datas, condition, loading, columns, checkedShowColumns, pageNum, pageSize, pageSizes, sql, hasUpdatedFileds, conditionDialog, addDataDialog } =
 | 
			
		||||
    toRefs(state);
 | 
			
		||||
 | 
			
		||||
watch(
 | 
			
		||||
    () => props.tableHeight,
 | 
			
		||||
@@ -351,6 +344,8 @@ onMounted(async () => {
 | 
			
		||||
 | 
			
		||||
    state.dbDialect = getNowDbInst().getDialect();
 | 
			
		||||
    useEventListener('click', handlerWindowClick);
 | 
			
		||||
 | 
			
		||||
    state.checkedShowColumns.columnNames = state.columns.map((item: any) => item.columnName);
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const handlerWindowClick = () => {
 | 
			
		||||
@@ -391,7 +386,8 @@ const selectData = async () => {
 | 
			
		||||
 | 
			
		||||
        let sql = dbInst.getDefaultSelectSql(db, table, state.condition, state.orderBy, state.pageNum, state.pageSize);
 | 
			
		||||
        state.sql = sql;
 | 
			
		||||
        const colAndData: any = await dbInst.runSql(db, sql);
 | 
			
		||||
        const res: any = await dbInst.runSql(db, sql);
 | 
			
		||||
        const colAndData: any = res[0];
 | 
			
		||||
        state.datas = colAndData.res;
 | 
			
		||||
    } finally {
 | 
			
		||||
        state.loading = false;
 | 
			
		||||
@@ -414,6 +410,7 @@ const handleSetPageNum = async () => {
 | 
			
		||||
    state.pageNum = state.setPageNum;
 | 
			
		||||
    await selectData();
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const handleCount = async () => {
 | 
			
		||||
    state.counting = true;
 | 
			
		||||
 | 
			
		||||
@@ -421,7 +418,8 @@ const handleCount = async () => {
 | 
			
		||||
        const db = props.dbName;
 | 
			
		||||
        const table = props.tableName;
 | 
			
		||||
        const dbInst = getNowDbInst();
 | 
			
		||||
        const countRes = await dbInst.runSql(db, dbInst.getDefaultCountSql(table, state.condition));
 | 
			
		||||
        let countRes = await dbInst.runSql(db, dbInst.getDefaultCountSql(table, state.condition));
 | 
			
		||||
        countRes = countRes[0];
 | 
			
		||||
        state.total = parseInt(countRes.res[0].count || countRes.res[0].COUNT || 0);
 | 
			
		||||
        state.showTotal = true;
 | 
			
		||||
    } catch (e) {
 | 
			
		||||
@@ -431,6 +429,24 @@ const handleCount = async () => {
 | 
			
		||||
    state.counting = false;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const handleCheckAllColumnChange = (val: boolean) => {
 | 
			
		||||
    state.checkedShowColumns.columnNames = val ? state.columns.map((x: any) => x.columnName) : [];
 | 
			
		||||
    state.checkedShowColumns.isIndeterminate = false;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const handleCheckedColumnChange = (value: string[]) => {
 | 
			
		||||
    const checkedCount = value.length;
 | 
			
		||||
    state.checkedShowColumns.checkedAllColumn = checkedCount === state.columns.length;
 | 
			
		||||
    state.checkedShowColumns.isIndeterminate = checkedCount > 0 && checkedCount < state.columns.length;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const triggerCheckedColumns = () => {
 | 
			
		||||
    const checkedColumnNames = state.checkedShowColumns.columnNames;
 | 
			
		||||
    for (let column of state.columns) {
 | 
			
		||||
        column.show = checkedColumnNames.includes(column.columnName);
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// 完整的条件,每次选中后会重置条件框内容,故需要这个变量在获取建议时将文本框内容保存
 | 
			
		||||
let completeCond = '';
 | 
			
		||||
// 是否存在列建议
 | 
			
		||||
@@ -444,10 +460,7 @@ const getColumnTips = (queryString: string, callback: any) => {
 | 
			
		||||
 | 
			
		||||
    let res = [];
 | 
			
		||||
    if (columnNameSearch) {
 | 
			
		||||
        columnNameSearch = columnNameSearch.toLowerCase();
 | 
			
		||||
        res = columns.filter((data: any) => {
 | 
			
		||||
            return data.columnName.toLowerCase().includes(columnNameSearch);
 | 
			
		||||
        });
 | 
			
		||||
        res = fuzzyMatchField(columnNameSearch, columns, (x: any) => x.columnName);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    completeCond = condition.value;
 | 
			
		||||
@@ -490,16 +503,25 @@ const chooseCondColumnName = () => {
 | 
			
		||||
 * 过滤条件列名
 | 
			
		||||
 */
 | 
			
		||||
const filterCondColumns = computed(() => {
 | 
			
		||||
    return filterColumns(state.columnNameSearch);
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const filterCheckedColumns = computed(() => {
 | 
			
		||||
    return filterColumns(state.checkedShowColumns.searchKey);
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const filterColumns = (searchKey: string) => {
 | 
			
		||||
    const columns = state.columns;
 | 
			
		||||
    let columnNameSearch = state.columnNameSearch;
 | 
			
		||||
    if (!columnNameSearch) {
 | 
			
		||||
    if (!searchKey) {
 | 
			
		||||
        return columns;
 | 
			
		||||
    }
 | 
			
		||||
    columnNameSearch = columnNameSearch.toLowerCase();
 | 
			
		||||
    return columns.filter((data: any) => {
 | 
			
		||||
        return data.columnName.toLowerCase().includes(columnNameSearch) || data.columnComment.toLowerCase().includes(columnNameSearch);
 | 
			
		||||
    });
 | 
			
		||||
});
 | 
			
		||||
    return fuzzyMatchField(
 | 
			
		||||
        searchKey,
 | 
			
		||||
        columns,
 | 
			
		||||
        (x: any) => x.columnName,
 | 
			
		||||
        (x: any) => x.columnComment
 | 
			
		||||
    );
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 条件查询,点击列信息后显示输入对应的值
 | 
			
		||||
@@ -507,7 +529,7 @@ const filterCondColumns = computed(() => {
 | 
			
		||||
const onConditionRowClick = (event: any) => {
 | 
			
		||||
    const row = event[0];
 | 
			
		||||
    state.conditionDialog.title = `请输入 [${row.columnName}] 的值`;
 | 
			
		||||
    state.conditionDialog.placeholder = `${row.showDataType}  ${row.columnComment}`;
 | 
			
		||||
    state.conditionDialog.placeholder = `${row.columnType}  ${row.columnComment}`;
 | 
			
		||||
    state.conditionDialog.columnRow = row;
 | 
			
		||||
    state.conditionDialog.visible = true;
 | 
			
		||||
    setTimeout(() => {
 | 
			
		||||
@@ -583,6 +605,10 @@ const onShowAddDataDialog = async () => {
 | 
			
		||||
    state.addDataDialog.title = `添加'${props.tableName}'表数据`;
 | 
			
		||||
    state.addDataDialog.visible = true;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
defineExpose({
 | 
			
		||||
    active: () => dbTableRef.value.active(),
 | 
			
		||||
});
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style lang="scss">
 | 
			
		||||
 
 | 
			
		||||
@@ -30,7 +30,7 @@
 | 
			
		||||
 | 
			
		||||
                                    <el-select v-else-if="item.prop === 'type'" filterable size="small" v-model="scope.row.type">
 | 
			
		||||
                                        <el-option
 | 
			
		||||
                                            v-for="pgsqlType in getDbDialect(dbType).getInfo().columnTypes"
 | 
			
		||||
                                            v-for="pgsqlType in getDbDialect(dbType!).getInfo().columnTypes"
 | 
			
		||||
                                            :key="pgsqlType.dataType"
 | 
			
		||||
                                            :value="pgsqlType.udtName"
 | 
			
		||||
                                            :label="pgsqlType.dataType"
 | 
			
		||||
@@ -127,7 +127,7 @@
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script lang="ts" setup>
 | 
			
		||||
import { reactive, ref, toRefs, watch } from 'vue';
 | 
			
		||||
import { computed, reactive, ref, toRefs, watch } from 'vue';
 | 
			
		||||
import { ElMessage } from 'element-plus';
 | 
			
		||||
import SqlExecBox from '../sqleditor/SqlExecBox';
 | 
			
		||||
import { DbType, getDbDialect, IndexDefinition, RowDefinition } from '../../dialect/index';
 | 
			
		||||
@@ -152,7 +152,7 @@ const props = defineProps({
 | 
			
		||||
    dbType: {
 | 
			
		||||
        type: String,
 | 
			
		||||
    },
 | 
			
		||||
    flowProcdefKey: {
 | 
			
		||||
    version: {
 | 
			
		||||
        type: String,
 | 
			
		||||
    },
 | 
			
		||||
});
 | 
			
		||||
@@ -160,7 +160,7 @@ const props = defineProps({
 | 
			
		||||
//定义事件
 | 
			
		||||
const emit = defineEmits(['update:visible', 'cancel', 'val-change', 'submit-sql']);
 | 
			
		||||
 | 
			
		||||
let dbDialect = getDbDialect(props.dbType);
 | 
			
		||||
let dbDialect: any = computed(() => getDbDialect(props.dbType!, props.version));
 | 
			
		||||
 | 
			
		||||
type ColName = {
 | 
			
		||||
    prop: string;
 | 
			
		||||
@@ -274,7 +274,7 @@ const { dialogVisible, btnloading, activeName, tableData } = toRefs(state);
 | 
			
		||||
 | 
			
		||||
watch(props, async (newValue) => {
 | 
			
		||||
    state.dialogVisible = newValue.visible;
 | 
			
		||||
    dbDialect = getDbDialect(newValue.dbType);
 | 
			
		||||
    dbDialect.value = getDbDialect(newValue.dbType!);
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
// 切换到索引tab时,刷新索引字段下拉选项
 | 
			
		||||
@@ -309,11 +309,11 @@ const addRow = () => {
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const addIndex = () => {
 | 
			
		||||
    state.tableData.indexs.res.push(dbDialect.getDefaultIndex());
 | 
			
		||||
    state.tableData.indexs.res.push(dbDialect.value.getDefaultIndex());
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const addDefaultRows = () => {
 | 
			
		||||
    state.tableData.fields.res.push(...dbDialect.getDefaultRows());
 | 
			
		||||
    state.tableData.fields.res.push(...dbDialect.value.getDefaultRows());
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const deleteRow = (index: any) => {
 | 
			
		||||
@@ -334,8 +334,7 @@ const submit = async () => {
 | 
			
		||||
        sql: sql,
 | 
			
		||||
        dbId: props.dbId as any,
 | 
			
		||||
        db: props.db as any,
 | 
			
		||||
        dbType: dbDialect.getInfo().formatSqlDialect,
 | 
			
		||||
        flowProcdefKey: props.flowProcdefKey,
 | 
			
		||||
        dbType: dbDialect.value.getInfo().formatSqlDialect,
 | 
			
		||||
        runSuccessCallback: () => {
 | 
			
		||||
            emit('submit-sql', { tableName: state.tableData.tableName });
 | 
			
		||||
            // cancel();
 | 
			
		||||
@@ -371,11 +370,11 @@ const filterChangedData = (oldArr: object[], nowArr: object[], key: string): { d
 | 
			
		||||
        return data;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    let oldMap = {},
 | 
			
		||||
        newMap = {};
 | 
			
		||||
    oldArr.forEach((a) => (oldMap[a[key]] = a));
 | 
			
		||||
    let oldMap: any = {},
 | 
			
		||||
        newMap: any = {};
 | 
			
		||||
    oldArr.forEach((a: any) => (oldMap[a[key]] = a));
 | 
			
		||||
 | 
			
		||||
    nowArr.forEach((a) => {
 | 
			
		||||
    nowArr.forEach((a: any) => {
 | 
			
		||||
        let k = a[key];
 | 
			
		||||
        newMap[k] = a;
 | 
			
		||||
        // 取oldName,因为修改了name,但是oldName不会变
 | 
			
		||||
@@ -388,7 +387,7 @@ const filterChangedData = (oldArr: object[], nowArr: object[], key: string): { d
 | 
			
		||||
        }
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    oldArr.forEach((a) => {
 | 
			
		||||
    oldArr.forEach((a: any) => {
 | 
			
		||||
        let k = a[key];
 | 
			
		||||
        let newData = newMap[k];
 | 
			
		||||
        if (!newData) {
 | 
			
		||||
@@ -415,21 +414,22 @@ const genSql = () => {
 | 
			
		||||
    let data = state.tableData;
 | 
			
		||||
    // 创建表
 | 
			
		||||
    if (!props.data?.edit) {
 | 
			
		||||
        let createTable = dbDialect.getCreateTableSql(data);
 | 
			
		||||
        let createTable = dbDialect.value.getCreateTableSql(data);
 | 
			
		||||
        let createIndex = '';
 | 
			
		||||
        if (data.indexs.res.length > 0) {
 | 
			
		||||
            createIndex = dbDialect.getCreateIndexSql(data);
 | 
			
		||||
            createIndex = dbDialect.value.getCreateIndexSql(data);
 | 
			
		||||
        }
 | 
			
		||||
        return createTable + ';' + createIndex;
 | 
			
		||||
    } else {
 | 
			
		||||
        // 修改列
 | 
			
		||||
        let changeColData = filterChangedData(state.tableData.fields.oldFields, state.tableData.fields.res, 'name');
 | 
			
		||||
        let colSql = changeColData.changed ? dbDialect.getModifyColumnSql(data, data.tableName, changeColData) : '';
 | 
			
		||||
        let colSql = changeColData.changed ? dbDialect.value.getModifyColumnSql(data, data.tableName, changeColData) : '';
 | 
			
		||||
        // 修改索引
 | 
			
		||||
        let changeIdxData = filterChangedData(state.tableData.indexs.oldIndexs, state.tableData.indexs.res, 'indexName');
 | 
			
		||||
        let idxSql = changeIdxData.changed ? dbDialect.getModifyIndexSql(data, data.tableName, changeIdxData) : '';
 | 
			
		||||
        let idxSql = changeIdxData.changed ? dbDialect.value.getModifyIndexSql(data, data.tableName, changeIdxData) : '';
 | 
			
		||||
        // 修改表名,表注释
 | 
			
		||||
        let tableInfoSql = data.tableName !== data.oldTableName || data.tableComment !== data.oldTableComment ? dbDialect.getModifyTableInfoSql(data) : '';
 | 
			
		||||
        let tableInfoSql =
 | 
			
		||||
            data.tableName !== data.oldTableName || data.tableComment !== data.oldTableComment ? dbDialect.value.getModifyTableInfoSql(data) : '';
 | 
			
		||||
 | 
			
		||||
        let sqlArr = [];
 | 
			
		||||
        colSql && sqlArr.push(colSql);
 | 
			
		||||
 
 | 
			
		||||
@@ -1,9 +1,9 @@
 | 
			
		||||
<template>
 | 
			
		||||
    <div class="db-table">
 | 
			
		||||
        <el-row class="mb5">
 | 
			
		||||
            <el-popover v-model:visible="showDumpInfo" :width="470" placement="right" trigger="click">
 | 
			
		||||
            <el-popover v-model:visible="state.dumpInfo.visible" trigger="click" :width="470" placement="right">
 | 
			
		||||
                <template #reference>
 | 
			
		||||
                    <el-button class="ml5" type="success" size="small">导出</el-button>
 | 
			
		||||
                    <el-button :disabled="state.dumpInfo.tables?.length == 0" class="ml5" type="success" size="small">导出</el-button>
 | 
			
		||||
                </template>
 | 
			
		||||
                <el-form-item label="导出内容: ">
 | 
			
		||||
                    <el-radio-group v-model="dumpInfo.type">
 | 
			
		||||
@@ -13,16 +13,15 @@
 | 
			
		||||
                    </el-radio-group>
 | 
			
		||||
                </el-form-item>
 | 
			
		||||
 | 
			
		||||
                <el-form-item label="导出表: ">
 | 
			
		||||
                    <el-table @selection-change="handleDumpTableSelectionChange" max-height="300" size="small" :data="tables">
 | 
			
		||||
                        <el-table-column type="selection" width="45" />
 | 
			
		||||
                <el-form-item>
 | 
			
		||||
                    <el-table :data="state.dumpInfo.tables" empty-text="请先选择要导出的表" max-height="300" size="small">
 | 
			
		||||
                        <el-table-column property="tableName" label="表名" min-width="150" show-overflow-tooltip> </el-table-column>
 | 
			
		||||
                        <el-table-column property="tableComment" label="备注" min-width="150" show-overflow-tooltip> </el-table-column>
 | 
			
		||||
                    </el-table>
 | 
			
		||||
                </el-form-item>
 | 
			
		||||
 | 
			
		||||
                <div style="text-align: right">
 | 
			
		||||
                    <el-button @click="showDumpInfo = false" size="small">取消</el-button>
 | 
			
		||||
                    <el-button @click="state.dumpInfo.visible = false" size="small">取消</el-button>
 | 
			
		||||
                    <el-button @click="dump(db)" type="success" size="small">确定</el-button>
 | 
			
		||||
                </div>
 | 
			
		||||
            </el-popover>
 | 
			
		||||
@@ -30,7 +29,9 @@
 | 
			
		||||
            <el-button type="primary" size="small" @click="openEditTable(false)">创建表</el-button>
 | 
			
		||||
        </el-row>
 | 
			
		||||
 | 
			
		||||
        <el-table v-loading="loading" border stripe :data="filterTableInfos" size="small" :height="height">
 | 
			
		||||
        <el-table v-loading="loading" @selection-change="handleDumpTableSelectionChange" border stripe :data="filterTableInfos" size="small" :height="height">
 | 
			
		||||
            <el-table-column type="selection" width="30" />
 | 
			
		||||
 | 
			
		||||
            <el-table-column property="tableName" label="表名" min-width="150" show-overflow-tooltip>
 | 
			
		||||
                <template #header>
 | 
			
		||||
                    <el-input v-model="tableNameSearch" size="small" placeholder="表名: 输入可过滤" clearable />
 | 
			
		||||
@@ -82,7 +83,7 @@
 | 
			
		||||
        <el-dialog width="40%" :title="`${chooseTableName} 字段信息`" v-model="columnDialog.visible">
 | 
			
		||||
            <el-table border stripe :data="columnDialog.columns" size="small">
 | 
			
		||||
                <el-table-column prop="columnName" label="名称" show-overflow-tooltip> </el-table-column>
 | 
			
		||||
                <el-table-column width="120" prop="showDataType" label="类型" show-overflow-tooltip> </el-table-column>
 | 
			
		||||
                <el-table-column width="120" prop="columnType" label="类型" show-overflow-tooltip> </el-table-column>
 | 
			
		||||
                <el-table-column width="80" prop="nullable" label="是否可为空" show-overflow-tooltip> </el-table-column>
 | 
			
		||||
                <el-table-column prop="columnComment" label="备注" show-overflow-tooltip> </el-table-column>
 | 
			
		||||
            </el-table>
 | 
			
		||||
@@ -108,7 +109,6 @@
 | 
			
		||||
            :dbId="dbId"
 | 
			
		||||
            :db="db"
 | 
			
		||||
            :dbType="dbType"
 | 
			
		||||
            :flow-procdef-key="props.flowProcdefKey"
 | 
			
		||||
            :data="tableCreateDialog.data"
 | 
			
		||||
            v-model:visible="tableCreateDialog.visible"
 | 
			
		||||
            @submit-sql="onSubmitSql"
 | 
			
		||||
@@ -130,6 +130,7 @@ 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';
 | 
			
		||||
import { fuzzyMatchField } from '@/common/utils/string';
 | 
			
		||||
 | 
			
		||||
const DbTableOp = defineAsyncComponent(() => import('./DbTableOp.vue'));
 | 
			
		||||
 | 
			
		||||
@@ -150,9 +151,6 @@ const props = defineProps({
 | 
			
		||||
        type: [String],
 | 
			
		||||
        required: true,
 | 
			
		||||
    },
 | 
			
		||||
    flowProcdefKey: {
 | 
			
		||||
        type: [String],
 | 
			
		||||
    },
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const state = reactive({
 | 
			
		||||
@@ -161,8 +159,8 @@ const state = reactive({
 | 
			
		||||
    tables: [],
 | 
			
		||||
    tableNameSearch: '',
 | 
			
		||||
    tableCommentSearch: '',
 | 
			
		||||
    showDumpInfo: false,
 | 
			
		||||
    dumpInfo: {
 | 
			
		||||
        visible: false,
 | 
			
		||||
        id: 0,
 | 
			
		||||
        db: '',
 | 
			
		||||
        type: 3,
 | 
			
		||||
@@ -201,19 +199,7 @@ const state = reactive({
 | 
			
		||||
    },
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const {
 | 
			
		||||
    loading,
 | 
			
		||||
    tables,
 | 
			
		||||
    tableNameSearch,
 | 
			
		||||
    tableCommentSearch,
 | 
			
		||||
    showDumpInfo,
 | 
			
		||||
    dumpInfo,
 | 
			
		||||
    chooseTableName,
 | 
			
		||||
    columnDialog,
 | 
			
		||||
    indexDialog,
 | 
			
		||||
    ddlDialog,
 | 
			
		||||
    tableCreateDialog,
 | 
			
		||||
} = toRefs(state);
 | 
			
		||||
const { loading, tableNameSearch, tableCommentSearch, dumpInfo, chooseTableName, columnDialog, indexDialog, ddlDialog, tableCreateDialog } = toRefs(state);
 | 
			
		||||
 | 
			
		||||
onMounted(async () => {
 | 
			
		||||
    getTables();
 | 
			
		||||
@@ -230,17 +216,11 @@ const filterTableInfos = computed(() => {
 | 
			
		||||
    if (!tableNameSearch && !tableCommentSearch) {
 | 
			
		||||
        return tables;
 | 
			
		||||
    }
 | 
			
		||||
    return tables.filter((data: any) => {
 | 
			
		||||
        let tnMatch = true;
 | 
			
		||||
        let tcMatch = true;
 | 
			
		||||
        if (tableNameSearch) {
 | 
			
		||||
            tnMatch = data.tableName.toLowerCase().includes(tableNameSearch.toLowerCase());
 | 
			
		||||
        }
 | 
			
		||||
        if (tableCommentSearch) {
 | 
			
		||||
            tcMatch = data.tableComment.includes(tableCommentSearch);
 | 
			
		||||
        }
 | 
			
		||||
        return tnMatch && tcMatch;
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    if (tableNameSearch) {
 | 
			
		||||
        return fuzzyMatchField(tableNameSearch, tables, (table: any) => table.tableName);
 | 
			
		||||
    }
 | 
			
		||||
    return fuzzyMatchField(tableCommentSearch, tables, (table: any) => table.tableComment);
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const getTables = async () => {
 | 
			
		||||
@@ -259,21 +239,22 @@ const getTables = async () => {
 | 
			
		||||
 * 选择导出数据库表
 | 
			
		||||
 */
 | 
			
		||||
const handleDumpTableSelectionChange = (vals: any) => {
 | 
			
		||||
    state.dumpInfo.tables = vals.map((x: any) => x.tableName);
 | 
			
		||||
    state.dumpInfo.tables = vals;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 数据库信息导出
 | 
			
		||||
 */
 | 
			
		||||
const dump = (db: string) => {
 | 
			
		||||
    isTrue(state.dumpInfo.tables.length > 0, '请选择要导出的表');
 | 
			
		||||
    isTrue(state.dumpInfo.tables.length > 0, '请先选择要导出的表');
 | 
			
		||||
    const tableNames = state.dumpInfo.tables.map((x: any) => x.tableName);
 | 
			
		||||
    const a = document.createElement('a');
 | 
			
		||||
    a.setAttribute(
 | 
			
		||||
        'href',
 | 
			
		||||
        `${config.baseApiUrl}/dbs/${props.dbId}/dump?db=${db}&type=${state.dumpInfo.type}&tables=${state.dumpInfo.tables.join(',')}&${joinClientParams()}`
 | 
			
		||||
        `${config.baseApiUrl}/dbs/${props.dbId}/dump?db=${db}&type=${state.dumpInfo.type}&tables=${tableNames.join(',')}&${joinClientParams()}`
 | 
			
		||||
    );
 | 
			
		||||
    a.click();
 | 
			
		||||
    state.showDumpInfo = false;
 | 
			
		||||
    state.dumpInfo.visible = false;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const showColumns = async (row: any) => {
 | 
			
		||||
@@ -327,7 +308,6 @@ const dropTable = async (row: any) => {
 | 
			
		||||
            sql: `DROP TABLE ${tableName}`,
 | 
			
		||||
            dbId: props.dbId as any,
 | 
			
		||||
            db: props.db as any,
 | 
			
		||||
            flowProcdefKey: props.flowProcdefKey,
 | 
			
		||||
            runSuccessCallback: async () => {
 | 
			
		||||
                await getTables();
 | 
			
		||||
            },
 | 
			
		||||
 
 | 
			
		||||
@@ -8,6 +8,8 @@ import { editor, languages, Position } from 'monaco-editor';
 | 
			
		||||
import { registerCompletionItemProvider } from '@/components/monaco/completionItemProvider';
 | 
			
		||||
import { DbDialect, EditorCompletionItem, getDbDialect } from './dialect';
 | 
			
		||||
import { type RemovableRef, useLocalStorage } from '@vueuse/core';
 | 
			
		||||
import { DbGetDbNamesMode } from './enums';
 | 
			
		||||
import { ElMessage } from 'element-plus';
 | 
			
		||||
 | 
			
		||||
const hintsStorage: RemovableRef<Map<string, any>> = useLocalStorage('db-table-hints', new Map());
 | 
			
		||||
const tableStorage: RemovableRef<Map<string, any>> = useLocalStorage('db-tables', new Map());
 | 
			
		||||
@@ -40,11 +42,8 @@ export class DbInst {
 | 
			
		||||
     */
 | 
			
		||||
    type: string;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 流程定义key,若存在则需要审批执行
 | 
			
		||||
     */
 | 
			
		||||
    flowProcdefKey: string;
 | 
			
		||||
 | 
			
		||||
    /** 兼容版本 */
 | 
			
		||||
    version: string;
 | 
			
		||||
    /**
 | 
			
		||||
     * dbName -> db
 | 
			
		||||
     */
 | 
			
		||||
@@ -225,12 +224,18 @@ export class DbInst {
 | 
			
		||||
     * @param remark 执行备注
 | 
			
		||||
     */
 | 
			
		||||
    async runSql(dbName: string, sql: string, remark: string = '') {
 | 
			
		||||
        return await dbApi.sqlExec.request({
 | 
			
		||||
        const res = await dbApi.sqlExec.request({
 | 
			
		||||
            id: this.id,
 | 
			
		||||
            db: dbName,
 | 
			
		||||
            sql: sql.trim(),
 | 
			
		||||
            remark,
 | 
			
		||||
        });
 | 
			
		||||
        for (let re of res) {
 | 
			
		||||
            if (re.errorMsg) {
 | 
			
		||||
                ElMessage.error(`${re.sql} -> 执行失败: ${re.errorMsg}`);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        return res;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
@@ -310,7 +315,7 @@ export class DbInst {
 | 
			
		||||
     * @param columnValue 要更新的列以及对应的值 field->columnName; value->columnValue
 | 
			
		||||
     * @param rowData 表的一行完整数据(需要获取主键信息)
 | 
			
		||||
     */
 | 
			
		||||
    async genUpdateSql(dbName: string, table: string, columnValue: {}, rowData: {}) {
 | 
			
		||||
    async genUpdateSql(dbName: string, table: string, columnValue: any, rowData: any) {
 | 
			
		||||
        let schema = '';
 | 
			
		||||
        let dbArr = dbName.split('/');
 | 
			
		||||
        if (dbArr.length == 2) {
 | 
			
		||||
@@ -359,7 +364,6 @@ export class DbInst {
 | 
			
		||||
            dbType: this.getDialect().getInfo().formatSqlDialect,
 | 
			
		||||
            runSuccessCallback: successFunc,
 | 
			
		||||
            cancelCallback: cancelFunc,
 | 
			
		||||
            flowProcdefKey: this.flowProcdefKey,
 | 
			
		||||
        });
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
@@ -377,12 +381,17 @@ export class DbInst {
 | 
			
		||||
     * @param inst 数据库实例,后端返回的列表接口中的信息
 | 
			
		||||
     * @returns DbInst
 | 
			
		||||
     */
 | 
			
		||||
    static getOrNewInst(inst: any) {
 | 
			
		||||
    static async getOrNewInst(inst: any) {
 | 
			
		||||
        if (!inst) {
 | 
			
		||||
            throw new Error('inst不能为空');
 | 
			
		||||
        }
 | 
			
		||||
        let dbInst = dbInstCache.get(inst.id);
 | 
			
		||||
        if (dbInst) {
 | 
			
		||||
            // 可能同一个库关联多个标签,展示需要
 | 
			
		||||
            if (inst.tagPath) {
 | 
			
		||||
                dbInst.tagPath = inst.tagPath;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return dbInst;
 | 
			
		||||
        }
 | 
			
		||||
        console.info(`new dbInst: ${inst.id}, tagPath: ${inst.tagPath}`);
 | 
			
		||||
@@ -393,7 +402,10 @@ export class DbInst {
 | 
			
		||||
        dbInst.name = inst.name;
 | 
			
		||||
        dbInst.type = inst.type;
 | 
			
		||||
        dbInst.databases = inst.databases;
 | 
			
		||||
        dbInst.flowProcdefKey = inst.flowProcdefKey;
 | 
			
		||||
 | 
			
		||||
        if (dbInst.databases?.[0]) {
 | 
			
		||||
            dbInst.version = await dbApi.getCompatibleDbVersion.request({ id: inst.id, db: dbInst.databases?.[0] });
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        dbInstCache.set(dbInst.id, dbInst);
 | 
			
		||||
        return dbInst;
 | 
			
		||||
@@ -402,7 +414,6 @@ export class DbInst {
 | 
			
		||||
    /**
 | 
			
		||||
     * 获取数据库实例id,若不存在,则新建一个并缓存
 | 
			
		||||
     * @param dbId 数据库实例id
 | 
			
		||||
     * @param dbType 第一次获取时为必传项,即第一次创建时
 | 
			
		||||
     * @returns 数据库实例
 | 
			
		||||
     */
 | 
			
		||||
    static getInst(dbId?: number): DbInst {
 | 
			
		||||
@@ -413,7 +424,26 @@ export class DbInst {
 | 
			
		||||
        if (dbInst) {
 | 
			
		||||
            return dbInst;
 | 
			
		||||
        }
 | 
			
		||||
        throw new Error('dbInst不存在! 请在合适调用点使用DbInst.newInst()新建该实例');
 | 
			
		||||
        throw new Error('dbInst不存在! 请在合适调用点使用DbInst.getInstA()新建该实例');
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 获取数据库实例信息,若不存在,调接口获取数据库信息
 | 
			
		||||
     * @param dbId 数据库id
 | 
			
		||||
     * @returns
 | 
			
		||||
     */
 | 
			
		||||
    static async getInstA(dbId?: number): Promise<DbInst> {
 | 
			
		||||
        if (!dbId) {
 | 
			
		||||
            throw new Error('dbId不能为空');
 | 
			
		||||
        }
 | 
			
		||||
        let dbInst = dbInstCache.get(dbId);
 | 
			
		||||
        if (dbInst) {
 | 
			
		||||
            return Promise.resolve(dbInst);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        const dbInfoRes = await dbApi.dbs.request({ id: dbId });
 | 
			
		||||
        const db = dbInfoRes.list[0];
 | 
			
		||||
        return Promise.resolve(DbInst.getOrNewInst(db));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
@@ -444,8 +474,8 @@ export class DbInst {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // 获取列名称的长度 加上排序图标长度、abc为字段类型简称占位符
 | 
			
		||||
        const columnWidth: number = getTextWidth(prop + 'abc') + 23;
 | 
			
		||||
        // 获取列名称的长度 加上排序图标长度、abc为字段类型简称占位符、排序图标等
 | 
			
		||||
        const columnWidth: number = getTextWidth(prop + 'abc') + 10;
 | 
			
		||||
        // prop为该列的字段名(传字符串);tableData为该表格的数据源(传变量);
 | 
			
		||||
        if (!tableData || !tableData.length || tableData.length === 0 || tableData === undefined) {
 | 
			
		||||
            return columnWidth;
 | 
			
		||||
@@ -465,7 +495,7 @@ export class DbInst {
 | 
			
		||||
                maxWidthText = nowText;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        const contentWidth: number = getTextWidth(maxWidthText) + 15;
 | 
			
		||||
        const contentWidth: number = getTextWidth(maxWidthText) + 3;
 | 
			
		||||
        const flexWidth: number = contentWidth > columnWidth ? contentWidth : columnWidth;
 | 
			
		||||
        return flexWidth > 500 ? 500 : flexWidth;
 | 
			
		||||
    };
 | 
			
		||||
@@ -477,17 +507,17 @@ export class DbInst {
 | 
			
		||||
        }
 | 
			
		||||
        for (let col of columns) {
 | 
			
		||||
            if (col.charMaxLength > 0) {
 | 
			
		||||
                col.showDataType = `${col.dataType}(${col.charMaxLength})`;
 | 
			
		||||
                col.columnType = `${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.columnType = `${col.dataType}(${col.numPrecision},${col.numScale})`;
 | 
			
		||||
                    col.showScale = col.numScale;
 | 
			
		||||
                } else {
 | 
			
		||||
                    col.showDataType = `${col.dataType}(${col.numPrecision})`;
 | 
			
		||||
                    col.columnType = `${col.dataType}(${col.numPrecision})`;
 | 
			
		||||
                    col.showScale = null;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
@@ -495,9 +525,22 @@ export class DbInst {
 | 
			
		||||
                continue;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            col.showDataType = col.dataType;
 | 
			
		||||
            col.columnType = col.dataType;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 根据数据库配置信息获取对应的库名列表
 | 
			
		||||
     * @param db db配置信息
 | 
			
		||||
     * @returns 库名列表
 | 
			
		||||
     */
 | 
			
		||||
    static async getDbNames(db: any) {
 | 
			
		||||
        if (db.getDatabaseMode == DbGetDbNamesMode.Assign.value) {
 | 
			
		||||
            return db.database.split(' ');
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return await dbApi.getDbNamesByAc.request({ authCertName: db.authCertName });
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
@@ -582,6 +625,11 @@ export class TabInfo {
 | 
			
		||||
     */
 | 
			
		||||
    params: any;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 组件ref
 | 
			
		||||
     */
 | 
			
		||||
    componentRef: any;
 | 
			
		||||
 | 
			
		||||
    getNowDbInst() {
 | 
			
		||||
        return DbInst.getInst(this.dbId);
 | 
			
		||||
    }
 | 
			
		||||
@@ -625,7 +673,7 @@ export function registerDbCompletionItemProvider(dbId: number, db: string, dbs:
 | 
			
		||||
        triggerCharacters: ['.', ' '],
 | 
			
		||||
        provideCompletionItems: async (model: editor.ITextModel, position: Position): Promise<languages.CompletionList | null | undefined> => {
 | 
			
		||||
            let word = model.getWordUntilPosition(position);
 | 
			
		||||
            const dbInst = DbInst.getInst(dbId);
 | 
			
		||||
            const dbInst = await DbInst.getInstA(dbId);
 | 
			
		||||
            const { lineNumber, column } = position;
 | 
			
		||||
            const { startColumn, endColumn } = word;
 | 
			
		||||
 | 
			
		||||
@@ -818,3 +866,23 @@ function getTableName4SqlCtx(sql: string, alias: string = '', defaultDb: string)
 | 
			
		||||
        return tables.length > 0 ? tables[0] : undefined;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 数据库主题配置
 | 
			
		||||
 */
 | 
			
		||||
export const DbThemeConfig = {
 | 
			
		||||
    /**
 | 
			
		||||
     * 表数据表头是否显示备注
 | 
			
		||||
     */
 | 
			
		||||
    showColumnComment: true,
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 是否自动定位至树节点
 | 
			
		||||
     */
 | 
			
		||||
    locationTreeNode: true,
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 是否缓存表信息
 | 
			
		||||
     */
 | 
			
		||||
    cacheTable: true,
 | 
			
		||||
};
 | 
			
		||||
 
 | 
			
		||||
@@ -1,13 +1,14 @@
 | 
			
		||||
import { MysqlDialect } from './mysql_dialect';
 | 
			
		||||
import { PostgresqlDialect } from './postgres_dialect';
 | 
			
		||||
import { DMDialect } from '@/views/ops/db/dialect/dm_dialect';
 | 
			
		||||
import { OracleDialect } from '@/views/ops/db/dialect/oracle_dialect';
 | 
			
		||||
import { MariadbDialect } from '@/views/ops/db/dialect/mariadb_dialect';
 | 
			
		||||
import { SqliteDialect } from '@/views/ops/db/dialect/sqlite_dialect';
 | 
			
		||||
import { MssqlDialect } from '@/views/ops/db/dialect/mssql_dialect';
 | 
			
		||||
import { GaussDialect } from '@/views/ops/db/dialect/gauss_dialect';
 | 
			
		||||
import { KingbaseEsDialect } from '@/views/ops/db/dialect/kingbaseES_dialect';
 | 
			
		||||
import { VastbaseDialect } from '@/views/ops/db/dialect/vastbase_dialect';
 | 
			
		||||
import {MysqlDialect} from './mysql_dialect';
 | 
			
		||||
import {PostgresqlDialect} from './postgres_dialect';
 | 
			
		||||
import {DMDialect} from '@/views/ops/db/dialect/dm_dialect';
 | 
			
		||||
import {OracleDialect} from '@/views/ops/db/dialect/oracle_dialect';
 | 
			
		||||
import {MariadbDialect} from '@/views/ops/db/dialect/mariadb_dialect';
 | 
			
		||||
import {SqliteDialect} from '@/views/ops/db/dialect/sqlite_dialect';
 | 
			
		||||
import {MssqlDialect} from '@/views/ops/db/dialect/mssql_dialect';
 | 
			
		||||
import {GaussDialect} from '@/views/ops/db/dialect/gauss_dialect';
 | 
			
		||||
import {KingbaseEsDialect} from '@/views/ops/db/dialect/kingbaseES_dialect';
 | 
			
		||||
import {VastbaseDialect} from '@/views/ops/db/dialect/vastbase_dialect';
 | 
			
		||||
import {Oracle11Dialect} from "@/views/ops/db/dialect/oracle11_dialect";
 | 
			
		||||
 | 
			
		||||
export interface sqlColumnType {
 | 
			
		||||
    udtName: string;
 | 
			
		||||
@@ -37,6 +38,7 @@ export interface IndexDefinition {
 | 
			
		||||
    indexType: string;
 | 
			
		||||
    indexComment?: string;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export const commonCustomKeywords = ['GROUP BY', 'ORDER BY', 'LEFT JOIN', 'RIGHT JOIN', 'INNER JOIN', 'SELECT * FROM'];
 | 
			
		||||
 | 
			
		||||
export interface EditorCompletionItem {
 | 
			
		||||
@@ -69,11 +71,11 @@ export enum DataType {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/** 列数据类型角标 */
 | 
			
		||||
export const ColumnTypeSubscript = {
 | 
			
		||||
export const ColumnTypeSubscript: any = {
 | 
			
		||||
    /** 字符串 */
 | 
			
		||||
    string: 'abc',
 | 
			
		||||
    string: 'ab',
 | 
			
		||||
    /** 数字 */
 | 
			
		||||
    number: '123',
 | 
			
		||||
    number: '12',
 | 
			
		||||
    /** 日期 */
 | 
			
		||||
    date: 'icon-clock',
 | 
			
		||||
    /** 时间 */
 | 
			
		||||
@@ -212,7 +214,11 @@ export interface DbDialect {
 | 
			
		||||
     * @param tableName 表名
 | 
			
		||||
     * @param changeData 改变信息
 | 
			
		||||
     */
 | 
			
		||||
    getModifyColumnSql(tableData: any, tableName: string, changeData: { del: RowDefinition[]; add: RowDefinition[]; upd: RowDefinition[] }): string;
 | 
			
		||||
    getModifyColumnSql(tableData: any, tableName: string, changeData: {
 | 
			
		||||
        del: RowDefinition[];
 | 
			
		||||
        add: RowDefinition[];
 | 
			
		||||
        upd: RowDefinition[]
 | 
			
		||||
    }): string;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 生成编辑索引sql
 | 
			
		||||
@@ -249,17 +255,21 @@ export enum DuplicateStrategy {
 | 
			
		||||
let mysqlDialect = new MysqlDialect();
 | 
			
		||||
 | 
			
		||||
let dbType2DialectMap: Map<string, DbDialect> = new Map();
 | 
			
		||||
let dbType2DialectVersionMap: Map<string, DbDialect> = new Map();
 | 
			
		||||
 | 
			
		||||
export const registerDbDialect = (dbType: string, dd: DbDialect) => {
 | 
			
		||||
    dbType2DialectMap.set(dbType, dd);
 | 
			
		||||
};
 | 
			
		||||
export const registerDbDialectVersion = (dbType: string, dd: DbDialect) => {
 | 
			
		||||
    dbType2DialectVersionMap.set(dbType, dd);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const getDbDialectMap = () => {
 | 
			
		||||
    return dbType2DialectMap;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const getDbDialect = (dbType?: string): DbDialect => {
 | 
			
		||||
    return dbType2DialectMap.get(dbType!) || mysqlDialect;
 | 
			
		||||
export const getDbDialect = (dbType: string, version = ''): DbDialect => {
 | 
			
		||||
    return dbType2DialectVersionMap.get(dbType + version) || dbType2DialectMap.get(dbType) || mysqlDialect;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
@@ -282,6 +292,7 @@ export const QuoteEscape = (str: string): string => {
 | 
			
		||||
    registerDbDialect(DbType.gauss, new GaussDialect());
 | 
			
		||||
    registerDbDialect(DbType.dm, new DMDialect());
 | 
			
		||||
    registerDbDialect(DbType.oracle, new OracleDialect());
 | 
			
		||||
    registerDbDialectVersion(DbType.oracle + '11', new Oracle11Dialect()); // oracle 11g及以前版本的一些语法兼容
 | 
			
		||||
    registerDbDialect(DbType.sqlite, new SqliteDialect());
 | 
			
		||||
    registerDbDialect(DbType.mssql, new MssqlDialect());
 | 
			
		||||
    registerDbDialect(DbType.kingbaseEs, new KingbaseEsDialect());
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										55
									
								
								mayfly_go_web/src/views/ops/db/dialect/oracle11_dialect.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										55
									
								
								mayfly_go_web/src/views/ops/db/dialect/oracle11_dialect.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,55 @@
 | 
			
		||||
/** oracle 11g 及以前的版本的一些语法兼容  */
 | 
			
		||||
import {OracleDialect} from '@/views/ops/db/dialect/oracle_dialect';
 | 
			
		||||
import {DialectInfo, RowDefinition} from '@/views/ops/db/dialect/index';
 | 
			
		||||
 | 
			
		||||
let oracle11DialectInfo: DialectInfo;
 | 
			
		||||
 | 
			
		||||
export class Oracle11Dialect extends OracleDialect {
 | 
			
		||||
 | 
			
		||||
    getInfo(): DialectInfo {
 | 
			
		||||
        if (oracle11DialectInfo) {
 | 
			
		||||
            return oracle11DialectInfo;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        oracle11DialectInfo = {} as DialectInfo;
 | 
			
		||||
        Object.assign(oracle11DialectInfo, super.getInfo());
 | 
			
		||||
        oracle11DialectInfo.name = 'Oracle11x';
 | 
			
		||||
        return oracle11DialectInfo;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // 重写创建自增列sql
 | 
			
		||||
 | 
			
		||||
    genColumnBasicSql(cl: RowDefinition, create: boolean, data = {}): string {
 | 
			
		||||
        let length = this.getTypeLengthSql(cl);
 | 
			
		||||
        // 默认值
 | 
			
		||||
        let defVal = this.getDefaultValueSql(cl, false, data);
 | 
			
		||||
        // 忽略自增配置,11g不支持直接设置自增列,需要单独设置自增序列
 | 
			
		||||
        // 如果有原名以原名为准
 | 
			
		||||
        let name = cl.oldName && cl.name !== cl.oldName ? cl.oldName : cl.name;
 | 
			
		||||
        let baseSql = ` ${this.quoteIdentifier(name)} ${cl.type}${length}`;
 | 
			
		||||
        return ` ${baseSql} ${defVal} ${cl.notNull ? 'NOT NULL' : ''} `;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    getDefaultValueSql(cl: RowDefinition, create?: boolean, data?: any): string {
 | 
			
		||||
        if (cl.value) {
 | 
			
		||||
            return ` DEFAULT ${cl.value}`;
 | 
			
		||||
        } else if (cl.auto_increment) {
 | 
			
		||||
            return ` DEFAULT ${data.tableName}_${cl.name}_SEQ.NEXTVAL`;
 | 
			
		||||
        }
 | 
			
		||||
        return '';
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    getOtherCreateTableSql(data: any): string {
 | 
			
		||||
        // 通过字段自增信息创建自增序列
 | 
			
		||||
 | 
			
		||||
        let result = '';
 | 
			
		||||
        data.fields.res.forEach((field: RowDefinition) => {
 | 
			
		||||
            let seqName = `${data.tableName}_${field.name}_SEQ`;
 | 
			
		||||
            if (field.auto_increment) {
 | 
			
		||||
                result += `CREATE SEQUENCE ${seqName} START WITH 1 INCREMENT BY 1 CACHE 20`;
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        return result;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -7,8 +7,8 @@ import {
 | 
			
		||||
    DuplicateStrategy,
 | 
			
		||||
    EditorCompletion,
 | 
			
		||||
    EditorCompletionItem,
 | 
			
		||||
    QuoteEscape,
 | 
			
		||||
    IndexDefinition,
 | 
			
		||||
    QuoteEscape,
 | 
			
		||||
    RowDefinition,
 | 
			
		||||
    sqlColumnType,
 | 
			
		||||
} from './index';
 | 
			
		||||
@@ -85,10 +85,10 @@ const replaceFunctions: EditorCompletionItem[] = [
 | 
			
		||||
    { label: 'CURRENT_DATE', insertText: 'CURRENT_DATE', description: '获取当前日期' },
 | 
			
		||||
    { label: 'CURRENT_TIMESTAMP', insertText: 'TIMESTAMP', description: '获取当前时间' },
 | 
			
		||||
    // 转换函数
 | 
			
		||||
    { label: 'TO_CHAR', insertText: 'TO_CHAR(d|n[,fmt])', description: '把日期和数字转换为制定格式的字符串' },
 | 
			
		||||
    { label: 'TO_CHAR', insertText: `TO_CHAR(d|n, 'yyyy-MM-dd HH24:mi:ss')`, description: '把日期和数字转换为制定格式的字符串' },
 | 
			
		||||
    { label: 'TO_DATE', insertText: `TO_DATE(X, 'yyyy-MM-dd HH24:mi:ss')`, description: '把一个字符串以fmt格式转换成一个日期类型' },
 | 
			
		||||
    { label: 'TO_NUMBER', insertText: 'TO_NUMBER(X,[,fmt])', description: '把一个字符串以fmt格式转换为一个数字' },
 | 
			
		||||
    { label: 'TO_TIMESTAMP', insertText: 'TO_TIMESTAMP(X,[,fmt])', description: '把一个字符串以fmt格式转换为日期类型' },
 | 
			
		||||
    { label: 'TO_NUMBER', insertText: `TO_NUMBER(X, 'yyyy-MM-dd HH24:mi:ss')`, description: '把一个字符串以fmt格式转换为一个数字' },
 | 
			
		||||
    { label: 'TO_TIMESTAMP', insertText: `TO_TIMESTAMP(X, 'yyyy-MM-dd HH24:mi:ss.ff')`, description: '把一个字符串以fmt格式转换为日期类型' },
 | 
			
		||||
    // 其他
 | 
			
		||||
    { label: 'NVL', insertText: 'NVL(X,VALUE)', description: '如果X为空,返回value,否则返回X' },
 | 
			
		||||
    { label: 'NVL2', insertText: 'NVL2(x,value1,value2)', description: '如果x非空,返回value1,否则返回value2' },
 | 
			
		||||
@@ -293,7 +293,7 @@ class OracleDialect implements DbDialect {
 | 
			
		||||
        return '';
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    genColumnBasicSql(cl: RowDefinition, create: boolean): string {
 | 
			
		||||
    genColumnBasicSql(cl: RowDefinition, create: boolean, data = {}): string {
 | 
			
		||||
        let length = this.getTypeLengthSql(cl);
 | 
			
		||||
        // 默认值
 | 
			
		||||
        let defVal = this.getDefaultValueSql(cl);
 | 
			
		||||
@@ -309,6 +309,11 @@ class OracleDialect implements DbDialect {
 | 
			
		||||
        return incr ? baseSql : ` ${baseSql} ${defVal} ${cl.notNull ? 'NOT NULL' : ''} `;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // eslint-disable-next-line @typescript-eslint/no-unused-vars,no-unused-vars
 | 
			
		||||
    getOtherCreateTableSql(data: any) {
 | 
			
		||||
        return '';
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    getCreateTableSql(data: any): string {
 | 
			
		||||
        let schemaArr = data.db.split('/');
 | 
			
		||||
        let schema = schemaArr.length > 1 ? schemaArr[schemaArr.length - 1] : schemaArr[0];
 | 
			
		||||
@@ -322,7 +327,7 @@ class OracleDialect implements DbDialect {
 | 
			
		||||
        // 创建表结构
 | 
			
		||||
        let fields: string[] = [];
 | 
			
		||||
        data.fields.res.forEach((item: any) => {
 | 
			
		||||
            item.name && fields.push(this.genColumnBasicSql(item, true));
 | 
			
		||||
            item.name && fields.push(this.genColumnBasicSql(item, true, data));
 | 
			
		||||
            // 列注释
 | 
			
		||||
            if (item.remark) {
 | 
			
		||||
                columCommentSql += ` COMMENT ON COLUMN ${dbTable}.${this.quoteIdentifier(item.name)} is '${QuoteEscape(item.remark)}'; `;
 | 
			
		||||
@@ -344,7 +349,9 @@ class OracleDialect implements DbDialect {
 | 
			
		||||
            tableCommentSql = ` COMMENT ON TABLE ${dbTable} is '${QuoteEscape(data.tableComment)}'; `;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return createSql + tableCommentSql + columCommentSql;
 | 
			
		||||
        // 其余建表信息,如:自增字段在老版本的使用方式是创建自增序列
 | 
			
		||||
        let other = this.getOtherCreateTableSql(data);
 | 
			
		||||
        return createSql + tableCommentSql + columCommentSql + other;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    getCreateIndexSql(tableData: any): string {
 | 
			
		||||
@@ -391,7 +398,7 @@ class OracleDialect implements DbDialect {
 | 
			
		||||
                        commentArr.push(commentSql);
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                modifyArr.push(` MODIFY (${this.genColumnBasicSql(a, false)})`);
 | 
			
		||||
                modifyArr.push(` MODIFY (${this.genColumnBasicSql(a, false, tableData)})`);
 | 
			
		||||
                if (a.pri) {
 | 
			
		||||
                    priArr.add(`${this.quoteIdentifier(a.name)}`);
 | 
			
		||||
                }
 | 
			
		||||
@@ -400,7 +407,7 @@ class OracleDialect implements DbDialect {
 | 
			
		||||
 | 
			
		||||
        if (changeData.add.length > 0) {
 | 
			
		||||
            changeData.add.forEach((a) => {
 | 
			
		||||
                modifyArr.push(` ADD (${this.genColumnBasicSql(a, false)})`);
 | 
			
		||||
                modifyArr.push(` ADD (${this.genColumnBasicSql(a, false, tableData)})`);
 | 
			
		||||
                if (a.remark) {
 | 
			
		||||
                    commentArr.push(`COMMENT ON COLUMN ${dbTable}.${this.quoteIdentifier(a.name)} is '${QuoteEscape(a.remark)}'`);
 | 
			
		||||
                }
 | 
			
		||||
 
 | 
			
		||||
@@ -79,7 +79,11 @@ const functions: EditorCompletionItem[] = [
 | 
			
		||||
    { label: 'sign', insertText: 'sign(X)', description: '返回数字符号 1正 -1负 0零 null' },
 | 
			
		||||
    { label: 'soundex', insertText: 'soundex(X)', description: '返回字符串X的soundex编码字符串' },
 | 
			
		||||
    { label: 'sqlite_compileoption_get', insertText: 'sqlite_compileoption_get(N)', description: '获取指定编译选项的值' },
 | 
			
		||||
    { label: 'sqlite_compileoption_used', insertText: 'sqlite_compileoption_used(X)', description: '检查SQLite编译时是否使用了指定的编译选项' },
 | 
			
		||||
    {
 | 
			
		||||
        label: 'sqlite_compileoption_used',
 | 
			
		||||
        insertText: 'sqlite_compileoption_used(X)',
 | 
			
		||||
        description: '检查SQLite编译时是否使用了指定的编译选项',
 | 
			
		||||
    },
 | 
			
		||||
    { label: 'sqlite_source_id', insertText: 'sqlite_source_id()', description: '获取sqlite源代码标识符' },
 | 
			
		||||
    { label: 'sqlite_version', insertText: 'sqlite_version()', description: '获取sqlite版本' },
 | 
			
		||||
    { label: 'substr', insertText: 'substr(X,Y[,Z])', description: '截取字符串' },
 | 
			
		||||
@@ -98,12 +102,21 @@ const functions: EditorCompletionItem[] = [
 | 
			
		||||
    { label: 'sum', insertText: 'sum(X)', description: '返回分组中非空值的总和。' },
 | 
			
		||||
    { label: 'total', insertText: 'total(X)', description: '返回YYYY-MM-DD格式的字符串' },
 | 
			
		||||
    { label: 'date', insertText: 'date(time-value[, modifier, ...])', description: '返回HH:MM:SS格式的字符串' },
 | 
			
		||||
    { label: 'time', insertText: 'time(time-value[, modifier, ...])', description: '将日期和时间字符串转换为特定的日期和时间格式' },
 | 
			
		||||
    {
 | 
			
		||||
        label: 'time',
 | 
			
		||||
        insertText: 'time(time-value[, modifier, ...])',
 | 
			
		||||
        description: '将日期和时间字符串转换为特定的日期和时间格式',
 | 
			
		||||
    },
 | 
			
		||||
    { label: 'datetime', insertText: 'datetime(time-value[, modifier, ...])', description: '计算日期和时间的儒略日数' },
 | 
			
		||||
    { label: 'julianday', insertText: 'julianday(time-value[, modifier, ...])', description: '将日期和时间格式化为指定的字符串' },
 | 
			
		||||
    {
 | 
			
		||||
        label: 'julianday',
 | 
			
		||||
        insertText: 'julianday(time-value[, modifier, ...])',
 | 
			
		||||
        description: '将日期和时间格式化为指定的字符串',
 | 
			
		||||
    },
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
let sqliteDialectInfo: DialectInfo;
 | 
			
		||||
 | 
			
		||||
class SqliteDialect implements DbDialect {
 | 
			
		||||
    getInfo(): DialectInfo {
 | 
			
		||||
        if (sqliteDialectInfo) {
 | 
			
		||||
@@ -124,7 +137,7 @@ class SqliteDialect implements DbDialect {
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        sqliteDialectInfo = {
 | 
			
		||||
            name: 'Sqlite',
 | 
			
		||||
            name: 'Sqlite3',
 | 
			
		||||
            icon: 'iconfont icon-sqlite',
 | 
			
		||||
            defaultPort: 0,
 | 
			
		||||
            formatSqlDialect: 'sql',
 | 
			
		||||
@@ -135,10 +148,8 @@ class SqliteDialect implements DbDialect {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    getDefaultSelectSql(db: string, table: string, condition: string, orderBy: string, pageNum: number, limit: number) {
 | 
			
		||||
        return `SELECT * FROM ${this.quoteIdentifier(table)} ${condition ? 'WHERE ' + condition : ''} ${orderBy ? orderBy : ''} ${this.getPageSql(
 | 
			
		||||
            pageNum,
 | 
			
		||||
            limit
 | 
			
		||||
        )};`;
 | 
			
		||||
        return `SELECT *
 | 
			
		||||
                FROM ${this.quoteIdentifier(table)} ${condition ? 'WHERE ' + condition : ''} ${orderBy ? orderBy : ''} ${this.getPageSql(pageNum, limit)};`;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    getPageSql(pageNum: number, limit: number) {
 | 
			
		||||
@@ -147,8 +158,28 @@ class SqliteDialect implements DbDialect {
 | 
			
		||||
 | 
			
		||||
    getDefaultRows(): RowDefinition[] {
 | 
			
		||||
        return [
 | 
			
		||||
            { name: 'id', type: 'integer', length: '', numScale: '', value: '', notNull: true, pri: true, auto_increment: true, remark: '主键ID' },
 | 
			
		||||
            { name: 'creator_id', type: 'bigint', length: '20', numScale: '', value: '', notNull: true, pri: false, auto_increment: false, remark: '创建人id' },
 | 
			
		||||
            {
 | 
			
		||||
                name: 'id',
 | 
			
		||||
                type: 'integer',
 | 
			
		||||
                length: '',
 | 
			
		||||
                numScale: '',
 | 
			
		||||
                value: '',
 | 
			
		||||
                notNull: true,
 | 
			
		||||
                pri: true,
 | 
			
		||||
                auto_increment: true,
 | 
			
		||||
                remark: '主键ID',
 | 
			
		||||
            },
 | 
			
		||||
            {
 | 
			
		||||
                name: 'creator_id',
 | 
			
		||||
                type: 'bigint',
 | 
			
		||||
                length: '20',
 | 
			
		||||
                numScale: '',
 | 
			
		||||
                value: '',
 | 
			
		||||
                notNull: true,
 | 
			
		||||
                pri: false,
 | 
			
		||||
                auto_increment: false,
 | 
			
		||||
                remark: '创建人id',
 | 
			
		||||
            },
 | 
			
		||||
            {
 | 
			
		||||
                name: 'creator',
 | 
			
		||||
                type: 'varchar',
 | 
			
		||||
@@ -171,8 +202,28 @@ class SqliteDialect implements DbDialect {
 | 
			
		||||
                auto_increment: false,
 | 
			
		||||
                remark: '创建时间',
 | 
			
		||||
            },
 | 
			
		||||
            { name: 'updator_id', type: 'bigint', length: '20', numScale: '', value: '', notNull: true, pri: false, auto_increment: false, remark: '修改人id' },
 | 
			
		||||
            { name: 'updator', type: 'varchar', length: '100', numScale: '', value: '', notNull: true, pri: false, auto_increment: false, remark: '修改姓名' },
 | 
			
		||||
            {
 | 
			
		||||
                name: 'updator_id',
 | 
			
		||||
                type: 'bigint',
 | 
			
		||||
                length: '20',
 | 
			
		||||
                numScale: '',
 | 
			
		||||
                value: '',
 | 
			
		||||
                notNull: true,
 | 
			
		||||
                pri: false,
 | 
			
		||||
                auto_increment: false,
 | 
			
		||||
                remark: '修改人id',
 | 
			
		||||
            },
 | 
			
		||||
            {
 | 
			
		||||
                name: 'updator',
 | 
			
		||||
                type: 'varchar',
 | 
			
		||||
                length: '100',
 | 
			
		||||
                numScale: '',
 | 
			
		||||
                value: '',
 | 
			
		||||
                notNull: true,
 | 
			
		||||
                pri: false,
 | 
			
		||||
                auto_increment: false,
 | 
			
		||||
                remark: '修改姓名',
 | 
			
		||||
            },
 | 
			
		||||
            {
 | 
			
		||||
                name: 'update_time',
 | 
			
		||||
                type: 'datetime',
 | 
			
		||||
@@ -211,6 +262,7 @@ class SqliteDialect implements DbDialect {
 | 
			
		||||
        }
 | 
			
		||||
        return ` ${this.quoteIdentifier(cl.name)} ${cl.type}${length} ${nullAble} ${defVal} `;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    getCreateTableSql(data: any): string {
 | 
			
		||||
        // 创建表结构
 | 
			
		||||
        let fields: string[] = [];
 | 
			
		||||
@@ -219,7 +271,9 @@ class SqliteDialect implements DbDialect {
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        return `CREATE TABLE ${this.quoteIdentifier(data.db)}.${this.quoteIdentifier(data.tableName)}
 | 
			
		||||
                  ( ${fields.join(',')} )`;
 | 
			
		||||
                (
 | 
			
		||||
                    ${fields.join(',')}
 | 
			
		||||
                )`;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    getCreateIndexSql(data: any): string {
 | 
			
		||||
@@ -227,13 +281,30 @@ class SqliteDialect implements DbDialect {
 | 
			
		||||
        let sql = [] as string[];
 | 
			
		||||
        data.indexs.res.forEach((a: any) => {
 | 
			
		||||
            sql.push(
 | 
			
		||||
                `CREATE ${a.unique ? 'UNIQUE' : ''} INDEX ${this.quoteIdentifier(data.db)}.${this.quoteIdentifier(a.indexName)} ON "${data.tableName}" (${a.columnNames.join(',')})`
 | 
			
		||||
                `CREATE
 | 
			
		||||
                ${a.unique ? 'UNIQUE' : ''} INDEX
 | 
			
		||||
                ${this.quoteIdentifier(data.db)}
 | 
			
		||||
                .
 | 
			
		||||
                ${this.quoteIdentifier(a.indexName)}
 | 
			
		||||
                ON
 | 
			
		||||
                "${data.tableName}"
 | 
			
		||||
                (
 | 
			
		||||
                ${a.columnNames.join(',')}
 | 
			
		||||
                )`
 | 
			
		||||
            );
 | 
			
		||||
        });
 | 
			
		||||
        return sql.join(';');
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    getModifyColumnSql(tableData: any, tableName: string, changeData: { del: RowDefinition[]; add: RowDefinition[]; upd: RowDefinition[] }): string {
 | 
			
		||||
    getModifyColumnSql(
 | 
			
		||||
        tableData: any,
 | 
			
		||||
        tableName: string,
 | 
			
		||||
        changeData: {
 | 
			
		||||
            del: RowDefinition[];
 | 
			
		||||
            add: RowDefinition[];
 | 
			
		||||
            upd: RowDefinition[];
 | 
			
		||||
        }
 | 
			
		||||
    ): string {
 | 
			
		||||
        // sqlite修改表结构需要先删除再创建
 | 
			
		||||
 | 
			
		||||
        // 1.删除旧表索引  DROP INDEX "main"."aa";
 | 
			
		||||
@@ -270,16 +341,25 @@ class SqliteDialect implements DbDialect {
 | 
			
		||||
        });
 | 
			
		||||
        // 生成sql
 | 
			
		||||
        sql.push(
 | 
			
		||||
            `INSERT INTO ${this.quoteIdentifier(tableData.db)}.${this.quoteIdentifier(tableName)} (${insertFields.join(',')}) SELECT ${queryFields.join(
 | 
			
		||||
                ','
 | 
			
		||||
            )} FROM ${this.quoteIdentifier(tableData.db)}.${this.quoteIdentifier(oldTableName)}`
 | 
			
		||||
            `INSERT INTO ${this.quoteIdentifier(tableData.db)}.${this.quoteIdentifier(tableName)} (${insertFields.join(',')})
 | 
			
		||||
             SELECT ${queryFields.join(',')}
 | 
			
		||||
             FROM ${this.quoteIdentifier(tableData.db)}.${this.quoteIdentifier(oldTableName)}`
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        // 5.创建索引
 | 
			
		||||
        tableData.indexs.res.forEach((a: any) => {
 | 
			
		||||
            a.indexName &&
 | 
			
		||||
                sql.push(
 | 
			
		||||
                    `CREATE ${a.unique ? 'UNIQUE' : ''} INDEX ${this.quoteIdentifier(tableData.db)}.${this.quoteIdentifier(a.indexName)} ON "${tableName}" (${a.columnNames.join(',')})`
 | 
			
		||||
                    `CREATE
 | 
			
		||||
                ${a.unique ? 'UNIQUE' : ''} INDEX
 | 
			
		||||
                ${this.quoteIdentifier(tableData.db)}
 | 
			
		||||
                .
 | 
			
		||||
                ${this.quoteIdentifier(a.indexName)}
 | 
			
		||||
                ON
 | 
			
		||||
                "${tableName}"
 | 
			
		||||
                (
 | 
			
		||||
                ${a.columnNames.join(',')}
 | 
			
		||||
                )`
 | 
			
		||||
                );
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
@@ -308,7 +388,14 @@ class SqliteDialect implements DbDialect {
 | 
			
		||||
 | 
			
		||||
        if (indexData.length > 0) {
 | 
			
		||||
            indexData.forEach((a) => {
 | 
			
		||||
                sql.push(`CREATE ${a.unique ? 'UNIQUE' : ''} INDEX ${this.quoteIdentifier(a.indexName)} ON ${tableName} (${a.columnNames.join(',')})`);
 | 
			
		||||
                sql.push(`CREATE
 | 
			
		||||
                ${a.unique ? 'UNIQUE' : ''} INDEX
 | 
			
		||||
                ${this.quoteIdentifier(a.indexName)}
 | 
			
		||||
                ON
 | 
			
		||||
                ${tableName}
 | 
			
		||||
                (
 | 
			
		||||
                ${a.columnNames.join(',')}
 | 
			
		||||
                )`);
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
        return sql.join(';');
 | 
			
		||||
 
 | 
			
		||||
@@ -1,11 +1,17 @@
 | 
			
		||||
import { EnumValue } from '@/common/Enum';
 | 
			
		||||
 | 
			
		||||
export const DbGetDbNamesMode = {
 | 
			
		||||
    Auto: EnumValue.of(-1, '实时获取').setTagType('warning'),
 | 
			
		||||
    Assign: EnumValue.of(1, '指定库名').setTagType('primary'),
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// 数据库sql执行类型
 | 
			
		||||
export const DbSqlExecTypeEnum = {
 | 
			
		||||
    Update: EnumValue.of(1, 'UPDATE').setTagColor('#E4F5EB'),
 | 
			
		||||
    Delete: EnumValue.of(2, 'DELETE').setTagColor('#F9E2AE'),
 | 
			
		||||
    Insert: EnumValue.of(3, 'INSERT').setTagColor('#A8DEE0'),
 | 
			
		||||
    Query: EnumValue.of(4, 'QUERY').setTagColor('#A8DEE0'),
 | 
			
		||||
    Ddl: EnumValue.of(5, 'DDL').setTagColor('#F9E2AE'),
 | 
			
		||||
    Other: EnumValue.of(-1, 'OTHER').setTagColor('#F9E2AE'),
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@@ -37,3 +43,9 @@ export const DbTransferRunningStateEnum = {
 | 
			
		||||
    Fail: EnumValue.of(-1, '失败').setTagType('danger'),
 | 
			
		||||
    Stop: EnumValue.of(-2, '手动终止').setTagType('warning'),
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const DbTransferFileStatusEnum = {
 | 
			
		||||
    Running: EnumValue.of(1, '执行中').setTagType('primary'),
 | 
			
		||||
    Success: EnumValue.of(2, '成功').setTagType('success'),
 | 
			
		||||
    Fail: EnumValue.of(-1, '失败').setTagType('danger'),
 | 
			
		||||
};
 | 
			
		||||
 
 | 
			
		||||
@@ -20,16 +20,8 @@
 | 
			
		||||
                        style="width: 100%"
 | 
			
		||||
                    />
 | 
			
		||||
                </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-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 prop="protocol" label="协议" required>
 | 
			
		||||
                    <el-radio-group v-model="form.protocol" @change="handleChangeProtocol">
 | 
			
		||||
@@ -90,7 +82,6 @@ import ResourceAuthCertTableEdit from '../component/ResourceAuthCertTableEdit.vu
 | 
			
		||||
import SshTunnelSelect from '../component/SshTunnelSelect.vue';
 | 
			
		||||
import { MachineProtocolEnum } from './enums';
 | 
			
		||||
import DrawerHeader from '@/components/drawer-header/DrawerHeader.vue';
 | 
			
		||||
import { ResourceCodePattern } from '@/common/pattern';
 | 
			
		||||
import { TagResourceTypeEnum } from '@/common/commonEnum';
 | 
			
		||||
 | 
			
		||||
const props = defineProps({
 | 
			
		||||
@@ -118,18 +109,18 @@ const rules = {
 | 
			
		||||
            trigger: ['change'],
 | 
			
		||||
        },
 | 
			
		||||
    ],
 | 
			
		||||
    code: [
 | 
			
		||||
        {
 | 
			
		||||
            required: true,
 | 
			
		||||
            message: '请输入编码',
 | 
			
		||||
            trigger: ['change', 'blur'],
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
            pattern: ResourceCodePattern.pattern,
 | 
			
		||||
            message: ResourceCodePattern.message,
 | 
			
		||||
            trigger: ['blur'],
 | 
			
		||||
        },
 | 
			
		||||
    ],
 | 
			
		||||
    // code: [
 | 
			
		||||
    //     {
 | 
			
		||||
    //         required: true,
 | 
			
		||||
    //         message: '请输入编码',
 | 
			
		||||
    //         trigger: ['change', 'blur'],
 | 
			
		||||
    //     },
 | 
			
		||||
    //     {
 | 
			
		||||
    //         pattern: ResourceCodePattern.pattern,
 | 
			
		||||
    //         message: ResourceCodePattern.message,
 | 
			
		||||
    //         trigger: ['blur'],
 | 
			
		||||
    //     },
 | 
			
		||||
    // ],
 | 
			
		||||
    name: [
 | 
			
		||||
        {
 | 
			
		||||
            required: true,
 | 
			
		||||
 
 | 
			
		||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user