mirror of
				https://gitee.com/dromara/mayfly-go
				synced 2025-11-04 08:20:25 +08:00 
			
		
		
		
	Compare commits
	
		
			11 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					2118acf244 | ||
| 
						 | 
					44a1bd626e | ||
| 
						 | 
					ea3c70a8a8 | ||
| 
						 | 
					6343173cf8 | ||
| 
						 | 
					6837a9c867 | ||
| 
						 | 
					a726927a28 | ||
| 
						 | 
					e135e4ce64 | ||
| 
						 | 
					43edef412c | ||
| 
						 | 
					2deb3109c2 | ||
| 
						 | 
					a80221a950 | ||
| 
						 | 
					10630847df | 
@@ -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
 | 
			
		||||
 
 | 
			
		||||
@@ -6,3 +6,5 @@ VITE_OPEN = false
 | 
			
		||||
 | 
			
		||||
# public path 配置线上环境路径(打包)
 | 
			
		||||
VITE_PUBLIC_PATH = ''
 | 
			
		||||
 | 
			
		||||
VITE_EDITOR=idea
 | 
			
		||||
@@ -1,68 +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.10.0",
 | 
			
		||||
    "asciinema-player": "^3.7.1",
 | 
			
		||||
    "axios": "^1.6.2",
 | 
			
		||||
    "clipboard": "^2.0.11",
 | 
			
		||||
    "cropperjs": "^1.6.1",
 | 
			
		||||
    "dayjs": "^1.11.11",
 | 
			
		||||
    "echarts": "^5.5.0",
 | 
			
		||||
    "element-plus": "^2.7.4",
 | 
			
		||||
    "js-base64": "^3.7.7",
 | 
			
		||||
    "jsencrypt": "^3.3.2",
 | 
			
		||||
    "lodash": "^4.17.21",
 | 
			
		||||
    "mitt": "^3.0.1",
 | 
			
		||||
    "monaco-editor": "^0.49.0",
 | 
			
		||||
    "monaco-sql-languages": "^0.12.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.27",
 | 
			
		||||
    "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.27",
 | 
			
		||||
    "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.77.1",
 | 
			
		||||
    "typescript": "^5.4.5",
 | 
			
		||||
    "vite": "^5.2.12",
 | 
			
		||||
    "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.7',
 | 
			
		||||
    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);
 | 
			
		||||
}
 | 
			
		||||
@@ -14,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(',')}`),
 | 
			
		||||
};
 | 
			
		||||
 
 | 
			
		||||
@@ -209,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(() => {
 | 
			
		||||
 
 | 
			
		||||
@@ -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];
 | 
			
		||||
        }
 | 
			
		||||
 
 | 
			
		||||
@@ -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">
 | 
			
		||||
 
 | 
			
		||||
@@ -109,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,
 | 
			
		||||
 
 | 
			
		||||
@@ -42,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;
 | 
			
		||||
@@ -57,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) {
 | 
			
		||||
 
 | 
			
		||||
@@ -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>
 | 
			
		||||
@@ -17,6 +17,25 @@
 | 
			
		||||
                        <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>
 | 
			
		||||
@@ -118,6 +137,7 @@ const state = reactive({
 | 
			
		||||
        name: null,
 | 
			
		||||
        defKey: null,
 | 
			
		||||
        status: null,
 | 
			
		||||
        condition: '',
 | 
			
		||||
        remark: null,
 | 
			
		||||
        // 流程的审批节点任务
 | 
			
		||||
        tasks: '',
 | 
			
		||||
@@ -141,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 = [];
 | 
			
		||||
    }
 | 
			
		||||
});
 | 
			
		||||
 
 | 
			
		||||
@@ -66,7 +66,7 @@ const columns = [
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
// 该用户拥有的的操作列按钮权限
 | 
			
		||||
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="发起时间">{{ formatDate(procinst.createTime) }}</el-descriptions-item>
 | 
			
		||||
 | 
			
		||||
                    <div v-if="procinst.duration">
 | 
			
		||||
                        <el-descriptions-item label="持续时间">{{ formatTime(procinst.duration) }}</el-descriptions-item>
 | 
			
		||||
                        <el-descriptions-item label="结束时间">{{ formatDate(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">
 | 
			
		||||
@@ -92,8 +84,8 @@ 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();
 | 
			
		||||
 
 | 
			
		||||
@@ -9,6 +9,7 @@ export const procdefApi = {
 | 
			
		||||
 | 
			
		||||
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'),
 | 
			
		||||
 
 | 
			
		||||
@@ -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>
 | 
			
		||||
@@ -89,7 +97,7 @@
 | 
			
		||||
                                </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">
 | 
			
		||||
@@ -123,7 +131,7 @@
 | 
			
		||||
                                </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">
 | 
			
		||||
@@ -164,7 +172,7 @@
 | 
			
		||||
                                </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">
 | 
			
		||||
@@ -203,7 +211,7 @@
 | 
			
		||||
                                </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">
 | 
			
		||||
@@ -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 { formatDate } from '@/common/utils/format';
 | 
			
		||||
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;
 | 
			
		||||
                    }
 | 
			
		||||
 
 | 
			
		||||
@@ -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' }],
 | 
			
		||||
@@ -347,26 +348,34 @@ const updateUserInfo = async () => {
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
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, loginRes.refresh_token);
 | 
			
		||||
        return;
 | 
			
		||||
 
 | 
			
		||||
@@ -51,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>
 | 
			
		||||
 
 | 
			
		||||
@@ -48,7 +48,7 @@
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script lang="ts" setup>
 | 
			
		||||
import { onMounted, reactive, ref, watch, toRefs, nextTick } 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';
 | 
			
		||||
@@ -147,10 +147,10 @@ const loadNode = async (node: any, resolve: (data: any) => void, reject: () => v
 | 
			
		||||
    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();
 | 
			
		||||
 
 | 
			
		||||
@@ -31,9 +31,9 @@
 | 
			
		||||
                            />
 | 
			
		||||
 | 
			
		||||
                            <span class="font13 ml5">
 | 
			
		||||
                                {{ data.code }}
 | 
			
		||||
                                <span style="color: #3c8dbc">【</span>
 | 
			
		||||
                                {{ 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>
 | 
			
		||||
 
 | 
			
		||||
@@ -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) {
 | 
			
		||||
@@ -200,6 +202,40 @@ 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 = [];
 | 
			
		||||
 
 | 
			
		||||
@@ -10,14 +10,6 @@
 | 
			
		||||
            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>
 | 
			
		||||
@@ -88,7 +80,6 @@ import { dbApi } from './api';
 | 
			
		||||
import { ElMessage } from 'element-plus';
 | 
			
		||||
import type { CheckboxValueType } from 'element-plus';
 | 
			
		||||
import { DbType } from '@/views/ops/db/dialect';
 | 
			
		||||
import { ResourceCodePattern } from '@/common/pattern';
 | 
			
		||||
 | 
			
		||||
import EnumTag from '@/components/enumtag/EnumTag.vue';
 | 
			
		||||
import { AuthCertCiphertextTypeEnum } from '../tag/enums';
 | 
			
		||||
@@ -130,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,
 | 
			
		||||
 
 | 
			
		||||
@@ -81,7 +81,7 @@
 | 
			
		||||
                        <template #dropdown>
 | 
			
		||||
                            <el-dropdown-menu>
 | 
			
		||||
                                <el-dropdown-item :command="{ type: 'dumpDb', data }"> 导出 </el-dropdown-item>
 | 
			
		||||
                                <el-dropdown-item
 | 
			
		||||
                                <!-- <el-dropdown-item
 | 
			
		||||
                                    :command="{ type: 'backupDb', data }"
 | 
			
		||||
                                    v-if="actionBtns[perms.backupDb] && supportAction('backupDb', data.type)"
 | 
			
		||||
                                >
 | 
			
		||||
@@ -98,7 +98,7 @@
 | 
			
		||||
                                    v-if="actionBtns[perms.restoreDb] && supportAction('restoreDb', data.type)"
 | 
			
		||||
                                >
 | 
			
		||||
                                    恢复任务
 | 
			
		||||
                                </el-dropdown-item>
 | 
			
		||||
                                </el-dropdown-item> -->
 | 
			
		||||
                            </el-dropdown-menu>
 | 
			
		||||
                        </template>
 | 
			
		||||
                    </el-dropdown>
 | 
			
		||||
@@ -254,7 +254,7 @@ const perms = {
 | 
			
		||||
    restoreDb: 'db:restore',
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const actionBtns = hasPerms(Object.values(perms));
 | 
			
		||||
const actionBtns: any = hasPerms(Object.values(perms));
 | 
			
		||||
 | 
			
		||||
const pageTableRef: Ref<any> = ref(null);
 | 
			
		||||
const state = reactive({
 | 
			
		||||
 
 | 
			
		||||
@@ -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(
 | 
			
		||||
@@ -323,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(`确定删除任务?`, '提示', {
 | 
			
		||||
 
 | 
			
		||||
@@ -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,
 | 
			
		||||
 
 | 
			
		||||
@@ -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);
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -108,6 +108,16 @@
 | 
			
		||||
                                            />
 | 
			
		||||
                                        </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>
 | 
			
		||||
@@ -149,7 +159,7 @@
 | 
			
		||||
                                <template #label>
 | 
			
		||||
                                    <el-popover :show-after="1000" placement="bottom-start" trigger="hover" :width="250">
 | 
			
		||||
                                        <template #reference>
 | 
			
		||||
                                            <span class="font12">{{ dt.label }}</span>
 | 
			
		||||
                                            <span @contextmenu.prevent="onTabContextmenu(dt, $event)" class="font12">{{ dt.label }}</span>
 | 
			
		||||
                                        </template>
 | 
			
		||||
                                        <template #default>
 | 
			
		||||
                                            <el-descriptions :column="1" size="small">
 | 
			
		||||
@@ -195,7 +205,6 @@
 | 
			
		||||
                                    :db-id="dt.params.id"
 | 
			
		||||
                                    :db="dt.params.db"
 | 
			
		||||
                                    :db-type="dt.params.type"
 | 
			
		||||
                                    :flow-procdef="dt.params.flowProcdef"
 | 
			
		||||
                                    :height="state.tablesOpHeight"
 | 
			
		||||
                                />
 | 
			
		||||
                            </el-tab-pane>
 | 
			
		||||
@@ -210,25 +219,31 @@
 | 
			
		||||
            :dbId="tableCreateDialog.dbId"
 | 
			
		||||
            :db="tableCreateDialog.db"
 | 
			
		||||
            :dbType="tableCreateDialog.dbType"
 | 
			
		||||
            :flow-procdef="tableCreateDialog.flowProcdef"
 | 
			
		||||
            :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';
 | 
			
		||||
@@ -237,7 +252,8 @@ 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 { procdefApi } from '@/views/flow/api';
 | 
			
		||||
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'));
 | 
			
		||||
@@ -280,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}`,
 | 
			
		||||
@@ -291,7 +307,6 @@ const nodeClickChangeDb = (nodeData: TagTreeNode) => {
 | 
			
		||||
                type: params.type,
 | 
			
		||||
                tagPath: params.tagPath,
 | 
			
		||||
                databases: params.dbs,
 | 
			
		||||
                flowProcdef: params.flowProcdef,
 | 
			
		||||
            },
 | 
			
		||||
            params.db
 | 
			
		||||
        );
 | 
			
		||||
@@ -323,7 +338,9 @@ const NodeTypeDbInst = new NodeType(SqlExecNodeType.DbInst).withLoadNodesFunc(as
 | 
			
		||||
    const params = parentNode.params;
 | 
			
		||||
    const dbs = (await DbInst.getDbNames(params))?.sort();
 | 
			
		||||
 | 
			
		||||
    const flowProcdef = await procdefApi.getByResource.request({ resourceType: TagResourceTypeEnum.DbName.value, resourceCode: params.code });
 | 
			
		||||
    // 查询数据库版本信息
 | 
			
		||||
    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)
 | 
			
		||||
            .withParams({
 | 
			
		||||
@@ -331,10 +348,10 @@ const NodeTypeDbInst = new NodeType(SqlExecNodeType.DbInst).withLoadNodesFunc(as
 | 
			
		||||
                id: params.id,
 | 
			
		||||
                name: params.name,
 | 
			
		||||
                type: params.type,
 | 
			
		||||
                version: version || 'unset',
 | 
			
		||||
                host: `${params.host}:${params.port}`,
 | 
			
		||||
                dbs: dbs,
 | 
			
		||||
                db: x,
 | 
			
		||||
                flowProcdef: flowProcdef,
 | 
			
		||||
            })
 | 
			
		||||
            .withIcon(DbIcon);
 | 
			
		||||
    });
 | 
			
		||||
@@ -368,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),
 | 
			
		||||
    ];
 | 
			
		||||
};
 | 
			
		||||
@@ -395,10 +417,10 @@ const NodeTypeTableMenu = new NodeType(SqlExecNodeType.TableMenu)
 | 
			
		||||
    ])
 | 
			
		||||
    .withLoadNodesFunc(async (parentNode: TagTreeNode) => {
 | 
			
		||||
        const params = parentNode.params;
 | 
			
		||||
        let { id, db, type, flowProcdef, 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;
 | 
			
		||||
@@ -411,7 +433,7 @@ const NodeTypeTableMenu = new NodeType(SqlExecNodeType.TableMenu)
 | 
			
		||||
                    db,
 | 
			
		||||
                    type,
 | 
			
		||||
                    schema,
 | 
			
		||||
                    flowProcdef: flowProcdef,
 | 
			
		||||
                    version,
 | 
			
		||||
                    key: key,
 | 
			
		||||
                    parentKey: parentNode.key,
 | 
			
		||||
                    tableName: x.tableName,
 | 
			
		||||
@@ -427,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模板菜单节点
 | 
			
		||||
@@ -455,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;
 | 
			
		||||
@@ -471,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({
 | 
			
		||||
@@ -484,6 +524,10 @@ const state = reactive({
 | 
			
		||||
    activeName: '',
 | 
			
		||||
    reloadStatus: false,
 | 
			
		||||
    tabs,
 | 
			
		||||
    tabContextmenu: {
 | 
			
		||||
        dropdown: { x: 0, y: 0 },
 | 
			
		||||
        items: tabContextmenuItems,
 | 
			
		||||
    },
 | 
			
		||||
    dataTabsTableHeight: '600px',
 | 
			
		||||
    tablesOpHeight: '600',
 | 
			
		||||
    dbServerInfo: {
 | 
			
		||||
@@ -495,17 +539,22 @@ const state = reactive({
 | 
			
		||||
        title: '',
 | 
			
		||||
        activeName: '',
 | 
			
		||||
        dbId: 0,
 | 
			
		||||
        version: '',
 | 
			
		||||
        db: '',
 | 
			
		||||
        dbType: '',
 | 
			
		||||
        flowProcdef: null as any,
 | 
			
		||||
        data: {},
 | 
			
		||||
        parentKey: '',
 | 
			
		||||
    },
 | 
			
		||||
    chooseTableName: '',
 | 
			
		||||
    ddlDialog: {
 | 
			
		||||
        visible: false,
 | 
			
		||||
        ddl: '',
 | 
			
		||||
    },
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const { nowDbInst, tableCreateDialog } = toRefs(state);
 | 
			
		||||
 | 
			
		||||
const dbConfig = useStorage('dbConfig', { showColumnComment: false, locationTreeNode: false });
 | 
			
		||||
const dbConfig = useStorage('dbConfig', DbThemeConfig);
 | 
			
		||||
 | 
			
		||||
const serverInfoReqParam = ref({
 | 
			
		||||
    instanceId: 0,
 | 
			
		||||
@@ -516,6 +565,7 @@ const autoOpenResourceStore = useAutoOpenResource();
 | 
			
		||||
const { autoOpenResource } = storeToRefs(autoOpenResourceStore);
 | 
			
		||||
 | 
			
		||||
onMounted(() => {
 | 
			
		||||
    state.reloadStatus = !dbConfig.value.cacheTable;
 | 
			
		||||
    autoOpenDb(autoOpenResource.value.dbCodePath);
 | 
			
		||||
    setHeight();
 | 
			
		||||
    // 监听浏览器窗口大小变化,更新对应组件高度
 | 
			
		||||
@@ -538,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];
 | 
			
		||||
@@ -568,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;
 | 
			
		||||
};
 | 
			
		||||
@@ -579,7 +629,7 @@ const loadTableData = async (db: any, dbName: string, tableName: string) => {
 | 
			
		||||
    if (tableName == '') {
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
    changeDb(db, dbName);
 | 
			
		||||
    await changeDb(db, dbName);
 | 
			
		||||
 | 
			
		||||
    const key = `tableData:${db.id}.${dbName}.${tableName}`;
 | 
			
		||||
    let tab = state.tabs.get(key);
 | 
			
		||||
@@ -608,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;
 | 
			
		||||
@@ -659,7 +709,7 @@ const addTablesOpTab = async (db: any) => {
 | 
			
		||||
        ElMessage.warning('请选择数据库实例及对应的schema');
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
    changeDb(db, dbName);
 | 
			
		||||
    await changeDb(db, dbName);
 | 
			
		||||
 | 
			
		||||
    const dbId = db.id;
 | 
			
		||||
    let key = `tablesOp:${dbId}.${dbName}`;
 | 
			
		||||
@@ -736,6 +786,15 @@ const onTabChange = () => {
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// 右键点击时:传 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);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 定位至当前树节点
 | 
			
		||||
 */
 | 
			
		||||
@@ -775,7 +834,7 @@ const reloadNode = (nodeKey: string) => {
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const onEditTable = async (data: any) => {
 | 
			
		||||
    let { db, id, tableName, tableComment, type, parentKey, key, flowProcdef } = data.params;
 | 
			
		||||
    let { db, id, tableName, tableComment, type, parentKey, key, version } = data.params;
 | 
			
		||||
    // data.label就是表名
 | 
			
		||||
    if (tableName) {
 | 
			
		||||
        state.tableCreateDialog.title = '修改表';
 | 
			
		||||
@@ -792,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.flowProcdef = flowProcdef;
 | 
			
		||||
    state.tableCreateDialog.visible = true;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const onDeleteTable = async (data: any) => {
 | 
			
		||||
    let { db, id, tableName, parentKey, flowProcdef, schema } = data.params;
 | 
			
		||||
    let { db, id, tableName, parentKey, schema } = data.params;
 | 
			
		||||
    await ElMessageBox.confirm(`此操作是永久性且无法撤销,确定删除【${tableName}】? `, '提示', {
 | 
			
		||||
        confirmButtonText: '确定',
 | 
			
		||||
        cancelButtonText: '取消',
 | 
			
		||||
@@ -810,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 (flowProcdef) {
 | 
			
		||||
            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, flowProcdef } = data.params;
 | 
			
		||||
    let { db, id, tableName, parentKey } = data.params;
 | 
			
		||||
    let tableData = { db, oldTableName: tableName, tableName };
 | 
			
		||||
 | 
			
		||||
    let value = ref(tableName);
 | 
			
		||||
@@ -846,7 +918,6 @@ const onRenameTable = async (data: any) => {
 | 
			
		||||
        dbId: id as any,
 | 
			
		||||
        db: db as any,
 | 
			
		||||
        dbType: nowDbInst.value.getDialect().getInfo().formatSqlDialect,
 | 
			
		||||
        flowProcdef: flowProcdef,
 | 
			
		||||
        runSuccessCallback: () => {
 | 
			
		||||
            setTimeout(() => {
 | 
			
		||||
                parentKey && reloadNode(parentKey);
 | 
			
		||||
@@ -906,7 +977,6 @@ const getNowDbInfo = () => {
 | 
			
		||||
        name: di.name,
 | 
			
		||||
        type: di.type,
 | 
			
		||||
        host: di.host,
 | 
			
		||||
        flowProcdef: di.flowProcdef,
 | 
			
		||||
        dbName: state.db,
 | 
			
		||||
    };
 | 
			
		||||
};
 | 
			
		||||
 
 | 
			
		||||
@@ -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>
 | 
			
		||||
@@ -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,20 +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);
 | 
			
		||||
            }
 | 
			
		||||
            // 非base64编码sql,则进行base64编码(refreshToken时,会重复调用该方法,故简单判断下)
 | 
			
		||||
            if (!Base64.isValid(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
 | 
			
		||||
@@ -39,6 +26,8 @@ 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}'),
 | 
			
		||||
@@ -73,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'),
 | 
			
		||||
@@ -91,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;
 | 
			
		||||
};
 | 
			
		||||
 
 | 
			
		||||
@@ -27,25 +27,13 @@ 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']);
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 树节点类型
 | 
			
		||||
@@ -63,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: () => {
 | 
			
		||||
        //
 | 
			
		||||
@@ -116,6 +104,7 @@ const NodeTypeDbInst = new NodeType(SqlExecNodeType.DbInst).withLoadNodesFunc(as
 | 
			
		||||
            .withParams({
 | 
			
		||||
                tagPath: params.tagPath,
 | 
			
		||||
                id: params.id,
 | 
			
		||||
                code: params.code,
 | 
			
		||||
                instanceId: params.instanceId,
 | 
			
		||||
                name: params.name,
 | 
			
		||||
                type: params.type,
 | 
			
		||||
@@ -156,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>
 | 
			
		||||
@@ -297,6 +297,8 @@ const onRunSql = async (newTab = false) => {
 | 
			
		||||
    // 去除字符串前的空格、换行等
 | 
			
		||||
    sql = sql.replace(/(^\s*)/g, '');
 | 
			
		||||
 | 
			
		||||
    const sqls = splitSql(sql);
 | 
			
		||||
 | 
			
		||||
    // 简单截取前十个字符
 | 
			
		||||
    const sqlPrefix = sql.slice(0, 10).toLowerCase();
 | 
			
		||||
    const nonQuery =
 | 
			
		||||
@@ -307,28 +309,37 @@ const onRunSql = async (newTab = false) => {
 | 
			
		||||
        sqlPrefix.startsWith('drop') ||
 | 
			
		||||
        sqlPrefix.startsWith('create');
 | 
			
		||||
 | 
			
		||||
    // 启用工单审批
 | 
			
		||||
    if (nonQuery && getNowDbInst().flowProcdef) {
 | 
			
		||||
        try {
 | 
			
		||||
            getNowDbInst().promptExeSql(props.dbName, sql, null, () => {
 | 
			
		||||
                ElMessage.success('工单提交成功');
 | 
			
		||||
    if (sqls.length == 1) {
 | 
			
		||||
        let execRemark;
 | 
			
		||||
        if (nonQuery) {
 | 
			
		||||
            const res: any = await ElMessageBox.prompt('请输入备注', 'Tip', {
 | 
			
		||||
                confirmButtonText: '确定',
 | 
			
		||||
                cancelButtonText: '取消',
 | 
			
		||||
                inputErrorMessage: '输入执行该sql的备注信息',
 | 
			
		||||
            });
 | 
			
		||||
        } catch (e) {
 | 
			
		||||
            ElMessage.success('工单提交失败');
 | 
			
		||||
            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);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    let execRemark;
 | 
			
		||||
    if (nonQuery) {
 | 
			
		||||
        const res: any = await ElMessageBox.prompt('请输入备注', 'Tip', {
 | 
			
		||||
            confirmButtonText: '确定',
 | 
			
		||||
            cancelButtonText: '取消',
 | 
			
		||||
            inputErrorMessage: '输入执行该sql的备注信息',
 | 
			
		||||
        });
 | 
			
		||||
        execRemark = res.value;
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 执行单条sql
 | 
			
		||||
 *
 | 
			
		||||
 * @param sql 单条sql
 | 
			
		||||
 * @param newTab 是否新建tab
 | 
			
		||||
 */
 | 
			
		||||
const runSql = async (sql: string, remark = '', newTab = false) => {
 | 
			
		||||
    let execRes: ExecResTab;
 | 
			
		||||
    let i = 0;
 | 
			
		||||
    let id;
 | 
			
		||||
@@ -356,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 = '查无数据';
 | 
			
		||||
        }
 | 
			
		||||
@@ -381,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;
 | 
			
		||||
@@ -403,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,如果有鼠标选中,则返回选中内容,否则返回输入框内所有内容
 | 
			
		||||
 */
 | 
			
		||||
 
 | 
			
		||||
@@ -2,18 +2,7 @@
 | 
			
		||||
    <div>
 | 
			
		||||
        <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.flowProcdef ? '执行备注(必填)' : '执行备注(选填)'"
 | 
			
		||||
                class="mt5"
 | 
			
		||||
            />
 | 
			
		||||
 | 
			
		||||
            <div v-if="props.flowProcdef">
 | 
			
		||||
                <el-divider content-position="left">审批节点</el-divider>
 | 
			
		||||
                <procdef-tasks :procdef="props.flowProcdef" />
 | 
			
		||||
            </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,12 +47,6 @@ onMounted(() => {
 | 
			
		||||
 * 执行sql
 | 
			
		||||
 */
 | 
			
		||||
const runSql = async () => {
 | 
			
		||||
    // 存在流程审批,则备注为必填
 | 
			
		||||
    if (!state.remark && props.flowProcdef) {
 | 
			
		||||
        ElMessage.error('请输入执行的备注信息');
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    try {
 | 
			
		||||
        state.btnLoading = true;
 | 
			
		||||
        runSuccess = true;
 | 
			
		||||
@@ -75,19 +58,15 @@ const runSql = async () => {
 | 
			
		||||
            sql: state.sqlValue.trim(),
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        // 存在流程审批
 | 
			
		||||
        if (props.flowProcdef) {
 | 
			
		||||
            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}`);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        isTrue(isSuccess, '存在执行失败sql');
 | 
			
		||||
        ElMessage.success('执行成功');
 | 
			
		||||
    } catch (e) {
 | 
			
		||||
        runSuccess = false;
 | 
			
		||||
 
 | 
			
		||||
@@ -25,7 +25,7 @@
 | 
			
		||||
        :clearable="false"
 | 
			
		||||
        type="Date"
 | 
			
		||||
        value-format="YYYY-MM-DD"
 | 
			
		||||
        placeholder="选择日期"
 | 
			
		||||
        :placeholder="`选择日期-${placeholder}`"
 | 
			
		||||
    />
 | 
			
		||||
 | 
			
		||||
    <el-date-picker
 | 
			
		||||
@@ -41,7 +41,7 @@
 | 
			
		||||
        :clearable="false"
 | 
			
		||||
        type="datetime"
 | 
			
		||||
        value-format="YYYY-MM-DD HH:mm:ss"
 | 
			
		||||
        placeholder="选择日期时间"
 | 
			
		||||
        :placeholder="`选择日期时间-${placeholder}`"
 | 
			
		||||
    />
 | 
			
		||||
 | 
			
		||||
    <el-time-picker
 | 
			
		||||
@@ -56,7 +56,7 @@
 | 
			
		||||
        v-model="itemValue"
 | 
			
		||||
        :clearable="false"
 | 
			
		||||
        value-format="HH:mm:ss"
 | 
			
		||||
        placeholder="选择时间"
 | 
			
		||||
        :placeholder="`选择时间-${placeholder}`"
 | 
			
		||||
    />
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -133,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
 | 
			
		||||
@@ -156,7 +156,7 @@
 | 
			
		||||
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';
 | 
			
		||||
@@ -258,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')
 | 
			
		||||
@@ -363,7 +361,7 @@ const state = reactive({
 | 
			
		||||
 | 
			
		||||
const { tableHeight, datas } = toRefs(state);
 | 
			
		||||
 | 
			
		||||
const dbConfig = useStorage('dbConfig', { showColumnComment: false });
 | 
			
		||||
const dbConfig = useStorage('dbConfig', DbThemeConfig);
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 行号字段列
 | 
			
		||||
@@ -595,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 });
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@@ -616,10 +614,6 @@ const onDeleteData = async () => {
 | 
			
		||||
    const db = state.db;
 | 
			
		||||
    const dbInst = getNowDbInst();
 | 
			
		||||
    dbInst.promptExeSql(db, await dbInst.genDeleteByPrimaryKeysSql(db, state.table, deleteDatas as any), null, () => {
 | 
			
		||||
        // 存在流程则恢复原值,需工单流程审批完后自动执行
 | 
			
		||||
        if (dbInst.flowProcdef) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        emits('dataDelete', deleteDatas);
 | 
			
		||||
    });
 | 
			
		||||
};
 | 
			
		||||
@@ -627,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.title = state.table ? `'${props.table}'表单数据` : '表单视图';
 | 
			
		||||
    state.tableDataFormDialog.visible = true;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@@ -648,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];
 | 
			
		||||
@@ -752,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);
 | 
			
		||||
@@ -767,11 +761,6 @@ const submitUpdateFields = async () => {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    dbInst.promptExeSql(db, res, null, () => {
 | 
			
		||||
        // 存在流程则恢复原值,需工单流程审批完后自动执行
 | 
			
		||||
        if (dbInst.flowProcdef) {
 | 
			
		||||
            cancelUpdateFields();
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        triggerRefresh();
 | 
			
		||||
        cellUpdateMap.clear();
 | 
			
		||||
        changeUpdatedField();
 | 
			
		||||
 
 | 
			
		||||
@@ -6,10 +6,10 @@
 | 
			
		||||
                :key="column.columnName"
 | 
			
		||||
                class="w100 mb5"
 | 
			
		||||
                :prop="column.columnName"
 | 
			
		||||
                :required="!column.nullable && !column.isPrimaryKey && !column.isIdentity"
 | 
			
		||||
                :required="props.tableName != '' && !column.nullable && !column.isPrimaryKey && !column.isIdentity"
 | 
			
		||||
            >
 | 
			
		||||
                <template #label>
 | 
			
		||||
                    <span class="pointer" :title="`${column.columnType} | ${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.columnType}  ${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>
 | 
			
		||||
@@ -99,7 +99,7 @@ const confirm = async () => {
 | 
			
		||||
 | 
			
		||||
    let sql = '';
 | 
			
		||||
    if (oldValue) {
 | 
			
		||||
        const updateColumnValue = {};
 | 
			
		||||
        const updateColumnValue: any = {};
 | 
			
		||||
        Object.keys(oldValue).forEach((key) => {
 | 
			
		||||
            // 如果新旧值不相等,则为需要更新的字段
 | 
			
		||||
            if (oldValue[key] !== modelValue.value[key]) {
 | 
			
		||||
 
 | 
			
		||||
@@ -50,22 +50,6 @@
 | 
			
		||||
                    </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>
 | 
			
		||||
@@ -258,7 +242,7 @@ 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 { useEventListener } from '@vueuse/core';
 | 
			
		||||
import { copyToClipboard, fuzzyMatchField } from '@/common/utils/string';
 | 
			
		||||
import DbTableDataForm from './DbTableDataForm.vue';
 | 
			
		||||
 | 
			
		||||
@@ -288,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
 | 
			
		||||
@@ -404,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;
 | 
			
		||||
@@ -435,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) {
 | 
			
		||||
 
 | 
			
		||||
@@ -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,15 +152,15 @@ const props = defineProps({
 | 
			
		||||
    dbType: {
 | 
			
		||||
        type: String,
 | 
			
		||||
    },
 | 
			
		||||
    flowProcdef: {
 | 
			
		||||
        type: Object,
 | 
			
		||||
    version: {
 | 
			
		||||
        type: String,
 | 
			
		||||
    },
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
//定义事件
 | 
			
		||||
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,
 | 
			
		||||
        flowProcdef: props.flowProcdef,
 | 
			
		||||
        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);
 | 
			
		||||
 
 | 
			
		||||
@@ -109,7 +109,6 @@
 | 
			
		||||
            :dbId="dbId"
 | 
			
		||||
            :db="db"
 | 
			
		||||
            :dbType="dbType"
 | 
			
		||||
            :flow-procdef="props.flowProcdef"
 | 
			
		||||
            :data="tableCreateDialog.data"
 | 
			
		||||
            v-model:visible="tableCreateDialog.visible"
 | 
			
		||||
            @submit-sql="onSubmitSql"
 | 
			
		||||
@@ -152,9 +151,6 @@ const props = defineProps({
 | 
			
		||||
        type: [String],
 | 
			
		||||
        required: true,
 | 
			
		||||
    },
 | 
			
		||||
    flowProcdef: {
 | 
			
		||||
        type: [Object],
 | 
			
		||||
    },
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const state = reactive({
 | 
			
		||||
@@ -312,7 +308,6 @@ const dropTable = async (row: any) => {
 | 
			
		||||
            sql: `DROP TABLE ${tableName}`,
 | 
			
		||||
            dbId: props.dbId as any,
 | 
			
		||||
            db: props.db as any,
 | 
			
		||||
            flowProcdef: props.flowProcdef,
 | 
			
		||||
            runSuccessCallback: async () => {
 | 
			
		||||
                await getTables();
 | 
			
		||||
            },
 | 
			
		||||
 
 | 
			
		||||
@@ -9,6 +9,7 @@ import { registerCompletionItemProvider } from '@/components/monaco/completionIt
 | 
			
		||||
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());
 | 
			
		||||
@@ -41,11 +42,8 @@ export class DbInst {
 | 
			
		||||
     */
 | 
			
		||||
    type: string;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 流程定义,若存在则需要审批执行
 | 
			
		||||
     */
 | 
			
		||||
    flowProcdef: any;
 | 
			
		||||
 | 
			
		||||
    /** 兼容版本 */
 | 
			
		||||
    version: string;
 | 
			
		||||
    /**
 | 
			
		||||
     * dbName -> db
 | 
			
		||||
     */
 | 
			
		||||
@@ -226,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;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
@@ -311,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) {
 | 
			
		||||
@@ -360,7 +364,6 @@ export class DbInst {
 | 
			
		||||
            dbType: this.getDialect().getInfo().formatSqlDialect,
 | 
			
		||||
            runSuccessCallback: successFunc,
 | 
			
		||||
            cancelCallback: cancelFunc,
 | 
			
		||||
            flowProcdef: this.flowProcdef,
 | 
			
		||||
        });
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
@@ -378,17 +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.flowProcdef !== undefined) {
 | 
			
		||||
                dbInst.flowProcdef = inst.flowProcdef;
 | 
			
		||||
                dbInstCache.set(dbInst.id, dbInst);
 | 
			
		||||
            // 可能同一个库关联多个标签,展示需要
 | 
			
		||||
            if (inst.tagPath) {
 | 
			
		||||
                dbInst.tagPath = inst.tagPath;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return dbInst;
 | 
			
		||||
        }
 | 
			
		||||
        console.info(`new dbInst: ${inst.id}, tagPath: ${inst.tagPath}`);
 | 
			
		||||
@@ -399,7 +402,10 @@ export class DbInst {
 | 
			
		||||
        dbInst.name = inst.name;
 | 
			
		||||
        dbInst.type = inst.type;
 | 
			
		||||
        dbInst.databases = inst.databases;
 | 
			
		||||
        dbInst.flowProcdef = inst.flowProcdef;
 | 
			
		||||
 | 
			
		||||
        if (dbInst.databases?.[0]) {
 | 
			
		||||
            dbInst.version = await dbApi.getCompatibleDbVersion.request({ id: inst.id, db: dbInst.databases?.[0] });
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        dbInstCache.set(dbInst.id, dbInst);
 | 
			
		||||
        return dbInst;
 | 
			
		||||
@@ -408,7 +414,6 @@ export class DbInst {
 | 
			
		||||
    /**
 | 
			
		||||
     * 获取数据库实例id,若不存在,则新建一个并缓存
 | 
			
		||||
     * @param dbId 数据库实例id
 | 
			
		||||
     * @param dbType 第一次获取时为必传项,即第一次创建时
 | 
			
		||||
     * @returns 数据库实例
 | 
			
		||||
     */
 | 
			
		||||
    static getInst(dbId?: number): DbInst {
 | 
			
		||||
@@ -419,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));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
@@ -649,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;
 | 
			
		||||
 | 
			
		||||
@@ -842,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,7 +71,7 @@ export enum DataType {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/** 列数据类型角标 */
 | 
			
		||||
export const ColumnTypeSubscript = {
 | 
			
		||||
export const ColumnTypeSubscript: any = {
 | 
			
		||||
    /** 字符串 */
 | 
			
		||||
    string: 'ab',
 | 
			
		||||
    /** 数字 */
 | 
			
		||||
@@ -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(';');
 | 
			
		||||
 
 | 
			
		||||
@@ -11,6 +11,7 @@ export const DbSqlExecTypeEnum = {
 | 
			
		||||
    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'),
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@@ -42,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,
 | 
			
		||||
 
 | 
			
		||||
@@ -278,10 +278,8 @@ const perms = {
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const searchItems = [
 | 
			
		||||
    SearchItem.input('keyword', '关键字').withPlaceholder('ip / 名称 / 编号'),
 | 
			
		||||
    getTagPathSearchItem(TagResourceTypeEnum.MachineAuthCert.value),
 | 
			
		||||
    SearchItem.input('code', '编号'),
 | 
			
		||||
    SearchItem.input('ip', 'IP'),
 | 
			
		||||
    SearchItem.input('name', '名称'),
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
const columns = [
 | 
			
		||||
@@ -298,7 +296,7 @@ const columns = [
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
// 该用户拥有的的操作列按钮权限,使用v-if进行判断,v-auth对el-dropdown-item无效
 | 
			
		||||
const actionBtns = hasPerms([perms.updateMachine]);
 | 
			
		||||
const actionBtns: any = hasPerms([perms.updateMachine]);
 | 
			
		||||
 | 
			
		||||
const state = reactive({
 | 
			
		||||
    params: {
 | 
			
		||||
 
 | 
			
		||||
@@ -10,6 +10,10 @@
 | 
			
		||||
            @open="getTermOps()"
 | 
			
		||||
        >
 | 
			
		||||
            <page-table ref="pageTableRef" :page-api="machineApi.termOpRecs" :lazy="true" height="100%" v-model:query-form="query" :columns="columns">
 | 
			
		||||
                <template #fileKey="{ data }">
 | 
			
		||||
                    <FileInfo :fileKey="data.fileKey" />
 | 
			
		||||
                </template>
 | 
			
		||||
 | 
			
		||||
                <template #action="{ data }">
 | 
			
		||||
                    <el-button @click="playRec(data)" loading-icon="loading" :loading="data.playRecLoding" type="primary" link>回放</el-button>
 | 
			
		||||
                    <el-button @click="showExecCmds(data)" type="primary" link>命令</el-button>
 | 
			
		||||
@@ -49,6 +53,8 @@ import 'asciinema-player/dist/bundle/asciinema-player.css';
 | 
			
		||||
import PageTable from '@/components/pagetable/PageTable.vue';
 | 
			
		||||
import { TableColumn } from '@/components/pagetable';
 | 
			
		||||
import { formatDate } from '@/common/utils/format';
 | 
			
		||||
import { getFileUrl } from '@/common/request';
 | 
			
		||||
import FileInfo from '@/components/file/FileInfo.vue';
 | 
			
		||||
 | 
			
		||||
const props = defineProps({
 | 
			
		||||
    visible: { type: Boolean },
 | 
			
		||||
@@ -62,7 +68,7 @@ const columns = [
 | 
			
		||||
    TableColumn.new('creator', '操作者').setMinWidth(120),
 | 
			
		||||
    TableColumn.new('createTime', '开始时间').isTime().setMinWidth(150),
 | 
			
		||||
    TableColumn.new('endTime', '结束时间').isTime().setMinWidth(150),
 | 
			
		||||
    TableColumn.new('recordFilePath', '文件路径').setMinWidth(200),
 | 
			
		||||
    TableColumn.new('fileKey', '文件').isSlot(),
 | 
			
		||||
    TableColumn.new('action', '操作').isSlot().setMinWidth(120).fixedRight().alignCenter(),
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
@@ -109,14 +115,9 @@ const playRec = async (rec: any) => {
 | 
			
		||||
            player.dispose();
 | 
			
		||||
        }
 | 
			
		||||
        rec.playRecLoding = true;
 | 
			
		||||
        const content = await machineApi.termOpRec.request({
 | 
			
		||||
            recId: rec.id,
 | 
			
		||||
            id: rec.machineId,
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        state.playerDialogVisible = true;
 | 
			
		||||
        nextTick(() => {
 | 
			
		||||
            player = AsciinemaPlayer.create(`data:text/plain;base64,${content}`, playerRef.value, {
 | 
			
		||||
            player = AsciinemaPlayer.create(getFileUrl(rec.fileKey), playerRef.value, {
 | 
			
		||||
                autoPlay: true,
 | 
			
		||||
                speed: 1.0,
 | 
			
		||||
                idleTimeLimit: 2,
 | 
			
		||||
 
 | 
			
		||||
@@ -5,6 +5,7 @@ import { joinClientParams } from '@/common/request';
 | 
			
		||||
export const machineApi = {
 | 
			
		||||
    // 获取权限列表
 | 
			
		||||
    list: Api.newGet('/machines'),
 | 
			
		||||
    getByCodes: Api.newGet('/machines/simple'),
 | 
			
		||||
    tagList: Api.newGet('/machines/tags'),
 | 
			
		||||
    getMachinePwd: Api.newGet('/machines/{id}/pwd'),
 | 
			
		||||
    info: Api.newGet('/machines/{id}/sysinfo'),
 | 
			
		||||
@@ -46,8 +47,6 @@ export const machineApi = {
 | 
			
		||||
    delConf: Api.newDelete('/machines/{machineId}/files/{id}'),
 | 
			
		||||
    // 机器终端操作记录列表
 | 
			
		||||
    termOpRecs: Api.newGet('/machines/{machineId}/term-recs'),
 | 
			
		||||
    // 机器终端操作记录详情
 | 
			
		||||
    termOpRec: Api.newGet('/machines/{id}/term-recs/{recId}'),
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const cronJobApi = {
 | 
			
		||||
 
 | 
			
		||||
@@ -17,14 +17,7 @@
 | 
			
		||||
                                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-form-item>
 | 
			
		||||
@@ -64,7 +57,6 @@ import { mongoApi } from './api';
 | 
			
		||||
import { ElMessage } from 'element-plus';
 | 
			
		||||
import TagTreeSelect from '../component/TagTreeSelect.vue';
 | 
			
		||||
import SshTunnelSelect from '../component/SshTunnelSelect.vue';
 | 
			
		||||
import { ResourceCodePattern } from '@/common/pattern';
 | 
			
		||||
 | 
			
		||||
const props = defineProps({
 | 
			
		||||
    visible: {
 | 
			
		||||
@@ -89,18 +81,6 @@ const rules = {
 | 
			
		||||
            trigger: ['change', 'blur'],
 | 
			
		||||
        },
 | 
			
		||||
    ],
 | 
			
		||||
    code: [
 | 
			
		||||
        {
 | 
			
		||||
            required: true,
 | 
			
		||||
            message: '请输入编码',
 | 
			
		||||
            trigger: ['change', 'blur'],
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
            pattern: ResourceCodePattern.pattern,
 | 
			
		||||
            message: ResourceCodePattern.message,
 | 
			
		||||
            trigger: ['blur'],
 | 
			
		||||
        },
 | 
			
		||||
    ],
 | 
			
		||||
    name: [
 | 
			
		||||
        {
 | 
			
		||||
            required: true,
 | 
			
		||||
 
 | 
			
		||||
@@ -68,7 +68,7 @@ const props = defineProps({
 | 
			
		||||
const route = useRoute();
 | 
			
		||||
const pageTableRef: Ref<any> = ref(null);
 | 
			
		||||
 | 
			
		||||
const searchItems = [getTagPathSearchItem(TagResourceTypeEnum.Mongo.value), SearchItem.input('code', '编号')];
 | 
			
		||||
const searchItems = [SearchItem.input('keyword', '关键字').withPlaceholder('host / 名称 / 编号'), getTagPathSearchItem(TagResourceTypeEnum.Mongo.value)];
 | 
			
		||||
 | 
			
		||||
const columns = [
 | 
			
		||||
    TableColumn.new('tags[0].tagPath', '关联标签').isSlot('tagPath').setAddWidth(20),
 | 
			
		||||
 
 | 
			
		||||
@@ -201,7 +201,6 @@ import { Splitpanes, Pane } from 'splitpanes';
 | 
			
		||||
import { RedisInst } from './redis';
 | 
			
		||||
import { useAutoOpenResource } from '@/store/autoOpenResource';
 | 
			
		||||
import { storeToRefs } from 'pinia';
 | 
			
		||||
import { procdefApi } from '@/views/flow/api';
 | 
			
		||||
 | 
			
		||||
const KeyDetail = defineAsyncComponent(() => import('./KeyDetail.vue'));
 | 
			
		||||
 | 
			
		||||
@@ -249,13 +248,11 @@ const NodeTypeTagPath = new NodeType(TagTreeNode.TagPath).withLoadNodesFunc(asyn
 | 
			
		||||
// redis实例节点类型
 | 
			
		||||
const NodeTypeRedis = new NodeType(RedisNodeType.Redis).withLoadNodesFunc(async (parentNode: TagTreeNode) => {
 | 
			
		||||
    const redisInfo = parentNode.params;
 | 
			
		||||
    const flowProcdef = await procdefApi.getByResource.request({ resourceType: TagResourceTypeEnum.Redis.value, resourceCode: redisInfo.code });
 | 
			
		||||
 | 
			
		||||
    let dbs: TagTreeNode[] = redisInfo.db.split(',').map((x: string) => {
 | 
			
		||||
        return new TagTreeNode(x, `db${x}`, NodeTypeDb).withIsLeaf(true).withParams({
 | 
			
		||||
            id: redisInfo.id,
 | 
			
		||||
            db: x,
 | 
			
		||||
            flowProcdef: flowProcdef,
 | 
			
		||||
            name: `db${x}`,
 | 
			
		||||
            keys: 0,
 | 
			
		||||
        });
 | 
			
		||||
@@ -288,7 +285,6 @@ const NodeTypeDb = new NodeType(RedisNodeType.Db).withNodeClickFunc((nodeData: T
 | 
			
		||||
 | 
			
		||||
    redisInst.value.id = nodeData.params.id;
 | 
			
		||||
    redisInst.value.db = Number.parseInt(nodeData.params.db);
 | 
			
		||||
    redisInst.value.flowProcdef = nodeData.params.flowProcdef;
 | 
			
		||||
 | 
			
		||||
    scan();
 | 
			
		||||
});
 | 
			
		||||
@@ -366,7 +362,7 @@ const autoOpenRedis = (codePath: string) => {
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const typeAndCodes = getTagTypeCodeByPath(codePath);
 | 
			
		||||
    const typeAndCodes: any = getTagTypeCodeByPath(codePath);
 | 
			
		||||
    const tagPath = typeAndCodes[TagResourceTypeEnum.Tag.value].join('/') + '/';
 | 
			
		||||
 | 
			
		||||
    const redisCode = typeAndCodes[TagResourceTypeEnum.Redis.value][0];
 | 
			
		||||
 
 | 
			
		||||
@@ -1,12 +1,13 @@
 | 
			
		||||
<template>
 | 
			
		||||
    <div class="format-viewer-container">
 | 
			
		||||
        <div class="mb5 fr">
 | 
			
		||||
            <el-select v-model="selectedView" class="format-selector" size="mini" placeholder="Text">
 | 
			
		||||
            <el-select v-model="selectedView" class="format-selector" size="small" placeholder="Text">
 | 
			
		||||
                <template #prefix>
 | 
			
		||||
                    <SvgIcon name="view" />
 | 
			
		||||
                </template>
 | 
			
		||||
                <el-option v-for="item of Object.keys(viewers)" :key="item" :label="item" :value="item"> </el-option>
 | 
			
		||||
            </el-select>
 | 
			
		||||
            <el-tag type="primary" :disable-transitions="true" class="ml10">Size: {{ formatByteSize(state.contentSize) }}</el-tag>
 | 
			
		||||
        </div>
 | 
			
		||||
 | 
			
		||||
        <component ref="viewerRef" :is="components[viewerComponent]" :content="state.content" :name="selectedView"> </component>
 | 
			
		||||
@@ -16,6 +17,7 @@
 | 
			
		||||
import { ref, reactive, computed, shallowReactive, watch, toRefs, onMounted } from 'vue';
 | 
			
		||||
import ViewerText from './ViewerText.vue';
 | 
			
		||||
import ViewerJson from './ViewerJson.vue';
 | 
			
		||||
import { formatByteSize } from '@/common/utils/format';
 | 
			
		||||
 | 
			
		||||
const props = defineProps({
 | 
			
		||||
    content: {
 | 
			
		||||
@@ -27,7 +29,7 @@ const props = defineProps({
 | 
			
		||||
    },
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const components = shallowReactive({
 | 
			
		||||
const components: any = shallowReactive({
 | 
			
		||||
    ViewerText,
 | 
			
		||||
    ViewerJson,
 | 
			
		||||
});
 | 
			
		||||
@@ -35,10 +37,11 @@ const viewerRef: any = ref(null);
 | 
			
		||||
 | 
			
		||||
const state = reactive({
 | 
			
		||||
    content: '',
 | 
			
		||||
    contentSize: 0,
 | 
			
		||||
    selectedView: 'Text',
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const viewers = {
 | 
			
		||||
const viewers: any = {
 | 
			
		||||
    Text: {
 | 
			
		||||
        value: 'ViewerText',
 | 
			
		||||
    },
 | 
			
		||||
@@ -67,6 +70,7 @@ onMounted(() => {
 | 
			
		||||
 | 
			
		||||
const setContent = (content: string) => {
 | 
			
		||||
    state.content = content;
 | 
			
		||||
    state.contentSize = new Blob([content]).size;
 | 
			
		||||
    try {
 | 
			
		||||
        JSON.parse(content);
 | 
			
		||||
        state.selectedView = 'Json';
 | 
			
		||||
 
 | 
			
		||||
@@ -21,14 +21,6 @@
 | 
			
		||||
                                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="请输入redis名称" auto-complete="off"></el-input>
 | 
			
		||||
                        </el-form-item>
 | 
			
		||||
@@ -51,11 +43,15 @@
 | 
			
		||||
                            <el-input v-model.trim="form.username" placeholder="用户名"></el-input>
 | 
			
		||||
                        </el-form-item>
 | 
			
		||||
                        <el-form-item prop="password" label="密码">
 | 
			
		||||
                            <el-input type="password" show-password v-model.trim="form.password" placeholder="请输入密码" autocomplete="new-password">
 | 
			
		||||
                            </el-input>
 | 
			
		||||
                        </el-form-item>
 | 
			
		||||
                        <el-form-item v-if="form.mode == 'sentinel'" prop="redisNodePassword" label="节点密码">
 | 
			
		||||
                            <el-input
 | 
			
		||||
                                type="password"
 | 
			
		||||
                                show-password
 | 
			
		||||
                                v-model.trim="form.password"
 | 
			
		||||
                                placeholder="请输入密码, 修改操作可不填"
 | 
			
		||||
                                v-model.trim="form.redisNodePassword"
 | 
			
		||||
                                placeholder="请输入Redis节点密码"
 | 
			
		||||
                                autocomplete="new-password"
 | 
			
		||||
                            >
 | 
			
		||||
                            </el-input>
 | 
			
		||||
@@ -104,7 +100,6 @@ import { redisApi } from './api';
 | 
			
		||||
import { ElMessage } from 'element-plus';
 | 
			
		||||
import TagTreeSelect from '../component/TagTreeSelect.vue';
 | 
			
		||||
import SshTunnelSelect from '../component/SshTunnelSelect.vue';
 | 
			
		||||
import { ResourceCodePattern } from '@/common/pattern';
 | 
			
		||||
import DrawerHeader from '@/components/drawer-header/DrawerHeader.vue';
 | 
			
		||||
 | 
			
		||||
const props = defineProps({
 | 
			
		||||
@@ -129,18 +124,6 @@ const rules = {
 | 
			
		||||
            trigger: ['blur', 'change'],
 | 
			
		||||
        },
 | 
			
		||||
    ],
 | 
			
		||||
    code: [
 | 
			
		||||
        {
 | 
			
		||||
            required: true,
 | 
			
		||||
            message: '请输入编码',
 | 
			
		||||
            trigger: ['change', 'blur'],
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
            pattern: ResourceCodePattern.pattern,
 | 
			
		||||
            message: ResourceCodePattern.message,
 | 
			
		||||
            trigger: ['blur'],
 | 
			
		||||
        },
 | 
			
		||||
    ],
 | 
			
		||||
    name: [
 | 
			
		||||
        {
 | 
			
		||||
            required: true,
 | 
			
		||||
@@ -186,6 +169,7 @@ const state = reactive({
 | 
			
		||||
        host: '',
 | 
			
		||||
        username: null,
 | 
			
		||||
        password: null,
 | 
			
		||||
        redisNodePassword: null,
 | 
			
		||||
        db: '',
 | 
			
		||||
        remark: '',
 | 
			
		||||
        sshTunnelMachineId: -1,
 | 
			
		||||
 
 | 
			
		||||
@@ -172,7 +172,7 @@ const props = defineProps({
 | 
			
		||||
const route = useRoute();
 | 
			
		||||
const pageTableRef: Ref<any> = ref(null);
 | 
			
		||||
 | 
			
		||||
const searchItems = [getTagPathSearchItem(TagResourceTypeEnum.Redis.value), SearchItem.input('code', '编号')];
 | 
			
		||||
const searchItems = [SearchItem.input('keyword', '关键字').withPlaceholder('host / 名称 / 编号'), getTagPathSearchItem(TagResourceTypeEnum.Redis.value)];
 | 
			
		||||
 | 
			
		||||
const columns = ref([
 | 
			
		||||
    TableColumn.new('tags[0].tagPath', '关联标签').isSlot('tagPath').setAddWidth(20),
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,7 @@
 | 
			
		||||
<template>
 | 
			
		||||
    <div>
 | 
			
		||||
        <el-dialog title="待执行cmd" v-model="dialogVisible" :show-close="false" width="600px" @close="cancel">
 | 
			
		||||
            <el-input type="textarea" disabled v-model="state.cmdStr" class="mt5" rows="5" />
 | 
			
		||||
            <el-input type="textarea" disabled v-model="state.cmdStr" class="mt5" :rows="5" />
 | 
			
		||||
            <el-input @keyup.enter="runCmd" ref="remarkInputRef" v-model="remark" placeholder="请输入执行备注" class="mt5" />
 | 
			
		||||
 | 
			
		||||
            <div v-if="props.flowProcdef">
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,5 @@
 | 
			
		||||
import { redisApi } from './api';
 | 
			
		||||
import showCmdExecBox from './components/CmdExecBox';
 | 
			
		||||
// import showCmdExecBox from './components/CmdExecBox';
 | 
			
		||||
 | 
			
		||||
export class RedisInst {
 | 
			
		||||
    /**
 | 
			
		||||
@@ -12,28 +12,23 @@ export class RedisInst {
 | 
			
		||||
     */
 | 
			
		||||
    db: number;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 流程定义,若存在则需要审批执行
 | 
			
		||||
     */
 | 
			
		||||
    flowProcdef: any;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 执行命令
 | 
			
		||||
     * @param cmd 命令列表如:['SET', 'key', 'value']
 | 
			
		||||
     * @returns 执行结果
 | 
			
		||||
     */
 | 
			
		||||
    async runCmd(cmd: any[]) {
 | 
			
		||||
        // 工单流程定义存在,并且为写入命令时,弹窗输入工单相关信息并提交
 | 
			
		||||
        if (this.flowProcdef && writeCmd[cmd[0].toUpperCase()]) {
 | 
			
		||||
            showCmdExecBox({
 | 
			
		||||
                id: this.id,
 | 
			
		||||
                db: this.db,
 | 
			
		||||
                flowProcdef: this.flowProcdef,
 | 
			
		||||
                cmd,
 | 
			
		||||
            });
 | 
			
		||||
            // 报错,阻止后续继续执行
 | 
			
		||||
            throw new Error('提交工单执行');
 | 
			
		||||
        }
 | 
			
		||||
        // // 工单流程定义存在,并且为写入命令时,弹窗输入工单相关信息并提交
 | 
			
		||||
        // if (this.flowProcdef && writeCmd[cmd[0].toUpperCase()]) {
 | 
			
		||||
        //     showCmdExecBox({
 | 
			
		||||
        //         id: this.id,
 | 
			
		||||
        //         db: this.db,
 | 
			
		||||
        //         flowProcdef: this.flowProcdef,
 | 
			
		||||
        //         cmd,
 | 
			
		||||
        //     });
 | 
			
		||||
        //     // 报错,阻止后续继续执行
 | 
			
		||||
        //     throw new Error('提交工单执行');
 | 
			
		||||
        // }
 | 
			
		||||
 | 
			
		||||
        return await redisApi.runCmd.request({
 | 
			
		||||
            id: this.id,
 | 
			
		||||
@@ -43,96 +38,96 @@ export class RedisInst {
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const writeCmd = {
 | 
			
		||||
    APPEND: 'APPEND key value',
 | 
			
		||||
    BLMOVE: 'BLMOVE source destination LEFT|RIGHT LEFT|RIGHT timeout',
 | 
			
		||||
    BLPOP: 'BLPOP key [key ...] timeout',
 | 
			
		||||
    BRPOP: 'BRPOP key [key ...] timeout',
 | 
			
		||||
    BRPOPLPUSH: 'BRPOPLPUSH source destination timeout',
 | 
			
		||||
    BZPOPMAX: 'BZPOPMAX key [key ...] timeout',
 | 
			
		||||
    BZPOPMIN: 'BZPOPMIN key [key ...] timeout',
 | 
			
		||||
    COPY: 'COPY source destination [DB destination-db] [REPLACE]',
 | 
			
		||||
    DECR: 'DECR key',
 | 
			
		||||
    DECRBY: 'DECRBY key decrement',
 | 
			
		||||
    DEL: 'DEL key [key ...]',
 | 
			
		||||
    EVAL: 'EVAL script numkeys key [key ...] arg [arg ...]',
 | 
			
		||||
    EVALSHA: 'EVALSHA sha1 numkeys key [key ...] arg [arg ...]',
 | 
			
		||||
    EXPIRE: 'EXPIRE key seconds',
 | 
			
		||||
    EXPIREAT: 'EXPIREAT key timestamp',
 | 
			
		||||
    FLUSHALL: 'FLUSHALL',
 | 
			
		||||
    FLUSHDB: 'FLUSHDB',
 | 
			
		||||
    GEOADD: 'GEOADD key [NX|XX] [CH] longitude latitude member [longitude latitude member ...]',
 | 
			
		||||
    GETDEL: 'GETDEL key',
 | 
			
		||||
    GETSET: 'GETSET key value',
 | 
			
		||||
    HDEL: 'HDEL key field [field ...]',
 | 
			
		||||
    HINCRBY: 'HINCRBY key field increment',
 | 
			
		||||
    HINCRBYFLOAT: 'HINCRBYFLOAT key field increment',
 | 
			
		||||
    HMSET: 'HMSET key field value [field value ...]',
 | 
			
		||||
    HSET: 'HSET key field value',
 | 
			
		||||
    HSETNX: 'HSETNX key field value',
 | 
			
		||||
    INCR: 'INCR key',
 | 
			
		||||
    INCRBY: 'INCRBY key increment',
 | 
			
		||||
    INCRBYFLOAT: 'INCRBYFLOAT key increment',
 | 
			
		||||
    LINSERT: 'LINSERT key BEFORE|AFTER pivot value',
 | 
			
		||||
    LMOVE: 'LMOVE source destination LEFT|RIGHT LEFT|RIGHT',
 | 
			
		||||
    LPOP: 'LPOP key',
 | 
			
		||||
    LPUSH: 'LPUSH key value [value ...]',
 | 
			
		||||
    LPUSHX: 'LPUSHX key value',
 | 
			
		||||
    LREM: 'LREM key count value',
 | 
			
		||||
    LSET: 'LSET key index value',
 | 
			
		||||
    LTRIM: 'LTRIM key start stop',
 | 
			
		||||
    MIGRATE: 'MIGRATE host port key destination-db timeout',
 | 
			
		||||
    MOVE: 'MOVE key db',
 | 
			
		||||
    MSET: 'MSET key value [key value ...]',
 | 
			
		||||
    MSETNX: 'MSETNX key value [key value ...]',
 | 
			
		||||
    PERSIST: 'PERSIST key',
 | 
			
		||||
    PEXPIRE: 'PEXPIRE key milliseconds',
 | 
			
		||||
    PEXPIREAT: 'PEXPIREAT key milliseconds-timestamp',
 | 
			
		||||
    PSETEX: 'PSETEX key milliseconds value',
 | 
			
		||||
    PUBLISH: 'PUBLISH channel message',
 | 
			
		||||
    RENAME: 'RENAME key newkey',
 | 
			
		||||
    RENAMENX: 'RENAMENX key newkey',
 | 
			
		||||
    RESTORE: 'RESTORE key ttl serialized-value',
 | 
			
		||||
    RPOP: 'RPOP key',
 | 
			
		||||
    RPOPLPUSH: 'RPOPLPUSH source destination',
 | 
			
		||||
    RPUSH: 'RPUSH key value [value ...]',
 | 
			
		||||
    RPUSHX: 'RPUSHX key value',
 | 
			
		||||
    SADD: 'SADD key member [member ...]',
 | 
			
		||||
    SCRIPT: ['SCRIPT EXISTS script [script ...]', 'SCRIPT FLUSH', 'SCRIPT KILL', 'SCRIPT LOAD script'],
 | 
			
		||||
    SDIFFSTORE: 'SDIFFSTORE destination key [key ...]',
 | 
			
		||||
    SET: 'SET key value',
 | 
			
		||||
    SETBIT: 'SETBIT key offset value',
 | 
			
		||||
    SETEX: 'SETEX key seconds value',
 | 
			
		||||
    SETNX: 'SETNX key value',
 | 
			
		||||
    SETRANGE: 'SETRANGE key offset value',
 | 
			
		||||
    SINTERSTORE: 'SINTERSTORE destination key [key ...]',
 | 
			
		||||
    SMOVE: 'SMOVE source destination member',
 | 
			
		||||
    SORT: 'SORT key [BY pattern] [LIMIT offset count] [GET pattern [GET pattern ...]] [ASC|DESC] [ALPHA] [STORE destination]',
 | 
			
		||||
    SPOP: 'SPOP key',
 | 
			
		||||
    SREM: 'SREM key member [member ...]',
 | 
			
		||||
    SUNIONSTORE: 'SUNIONSTORE destination key [key ...]',
 | 
			
		||||
    SWAPDB: 'SWAPDB index1 index2',
 | 
			
		||||
    UNLINK: 'UNLINK key [key ...]',
 | 
			
		||||
    XADD: 'XADD key ID field string [field string ...]',
 | 
			
		||||
    XDEL: 'XDEL key ID [ID ...]',
 | 
			
		||||
    XGROUP: [
 | 
			
		||||
        'XGROUP CREATE key groupname id|$ [MKSTREAM]',
 | 
			
		||||
        'XGROUP CREATECONSUMER key groupname consumername',
 | 
			
		||||
        'XGROUP DELCONSUMER key groupname consumername',
 | 
			
		||||
        'XGROUP DESTROY key groupname',
 | 
			
		||||
        'XGROUP SETID key groupname id|$',
 | 
			
		||||
    ],
 | 
			
		||||
    XTRIM: 'XTRIM key MAXLEN [~] count',
 | 
			
		||||
    ZADD: 'ZADD key score member [score] [member]',
 | 
			
		||||
    ZDIFFSTORE: 'ZDIFFSTORE destination numkeys key [key ...]',
 | 
			
		||||
    ZINCRBY: 'ZINCRBY key increment member',
 | 
			
		||||
    ZINTERSTORE: 'ZINTERSTORE destination numkeys key [key ...] [WEIGHTS weight [weight ...]] [AGGREGATE SUM|MIN|MAX]',
 | 
			
		||||
    ZPOPMAX: 'ZPOPMAX key [count]',
 | 
			
		||||
    ZPOPMIN: 'ZPOPMIN key [count]',
 | 
			
		||||
    ZRANGESTORE: 'ZRANGESTORE dst src min max [BYSCORE|BYLEX] [REV] [LIMIT offset count]',
 | 
			
		||||
    ZREM: 'ZREM key member [member ...]',
 | 
			
		||||
    ZREMRANGEBYLEX: 'ZREMRANGEBYLEX key min max',
 | 
			
		||||
    ZREMRANGEBYRANK: 'ZREMRANGEBYRANK key start stop',
 | 
			
		||||
    ZREMRANGEBYSCORE: 'ZREMRANGEBYSCORE key min max',
 | 
			
		||||
    ZUNIONSTORE: 'ZUNIONSTORE destination numkeys key [key ...] [WEIGHTS weight [weight ...]] [AGGREGATE SUM|MIN|MAX]',
 | 
			
		||||
};
 | 
			
		||||
// const writeCmd = {
 | 
			
		||||
//     APPEND: 'APPEND key value',
 | 
			
		||||
//     BLMOVE: 'BLMOVE source destination LEFT|RIGHT LEFT|RIGHT timeout',
 | 
			
		||||
//     BLPOP: 'BLPOP key [key ...] timeout',
 | 
			
		||||
//     BRPOP: 'BRPOP key [key ...] timeout',
 | 
			
		||||
//     BRPOPLPUSH: 'BRPOPLPUSH source destination timeout',
 | 
			
		||||
//     BZPOPMAX: 'BZPOPMAX key [key ...] timeout',
 | 
			
		||||
//     BZPOPMIN: 'BZPOPMIN key [key ...] timeout',
 | 
			
		||||
//     COPY: 'COPY source destination [DB destination-db] [REPLACE]',
 | 
			
		||||
//     DECR: 'DECR key',
 | 
			
		||||
//     DECRBY: 'DECRBY key decrement',
 | 
			
		||||
//     DEL: 'DEL key [key ...]',
 | 
			
		||||
//     EVAL: 'EVAL script numkeys key [key ...] arg [arg ...]',
 | 
			
		||||
//     EVALSHA: 'EVALSHA sha1 numkeys key [key ...] arg [arg ...]',
 | 
			
		||||
//     EXPIRE: 'EXPIRE key seconds',
 | 
			
		||||
//     EXPIREAT: 'EXPIREAT key timestamp',
 | 
			
		||||
//     FLUSHALL: 'FLUSHALL',
 | 
			
		||||
//     FLUSHDB: 'FLUSHDB',
 | 
			
		||||
//     GEOADD: 'GEOADD key [NX|XX] [CH] longitude latitude member [longitude latitude member ...]',
 | 
			
		||||
//     GETDEL: 'GETDEL key',
 | 
			
		||||
//     GETSET: 'GETSET key value',
 | 
			
		||||
//     HDEL: 'HDEL key field [field ...]',
 | 
			
		||||
//     HINCRBY: 'HINCRBY key field increment',
 | 
			
		||||
//     HINCRBYFLOAT: 'HINCRBYFLOAT key field increment',
 | 
			
		||||
//     HMSET: 'HMSET key field value [field value ...]',
 | 
			
		||||
//     HSET: 'HSET key field value',
 | 
			
		||||
//     HSETNX: 'HSETNX key field value',
 | 
			
		||||
//     INCR: 'INCR key',
 | 
			
		||||
//     INCRBY: 'INCRBY key increment',
 | 
			
		||||
//     INCRBYFLOAT: 'INCRBYFLOAT key increment',
 | 
			
		||||
//     LINSERT: 'LINSERT key BEFORE|AFTER pivot value',
 | 
			
		||||
//     LMOVE: 'LMOVE source destination LEFT|RIGHT LEFT|RIGHT',
 | 
			
		||||
//     LPOP: 'LPOP key',
 | 
			
		||||
//     LPUSH: 'LPUSH key value [value ...]',
 | 
			
		||||
//     LPUSHX: 'LPUSHX key value',
 | 
			
		||||
//     LREM: 'LREM key count value',
 | 
			
		||||
//     LSET: 'LSET key index value',
 | 
			
		||||
//     LTRIM: 'LTRIM key start stop',
 | 
			
		||||
//     MIGRATE: 'MIGRATE host port key destination-db timeout',
 | 
			
		||||
//     MOVE: 'MOVE key db',
 | 
			
		||||
//     MSET: 'MSET key value [key value ...]',
 | 
			
		||||
//     MSETNX: 'MSETNX key value [key value ...]',
 | 
			
		||||
//     PERSIST: 'PERSIST key',
 | 
			
		||||
//     PEXPIRE: 'PEXPIRE key milliseconds',
 | 
			
		||||
//     PEXPIREAT: 'PEXPIREAT key milliseconds-timestamp',
 | 
			
		||||
//     PSETEX: 'PSETEX key milliseconds value',
 | 
			
		||||
//     PUBLISH: 'PUBLISH channel message',
 | 
			
		||||
//     RENAME: 'RENAME key newkey',
 | 
			
		||||
//     RENAMENX: 'RENAMENX key newkey',
 | 
			
		||||
//     RESTORE: 'RESTORE key ttl serialized-value',
 | 
			
		||||
//     RPOP: 'RPOP key',
 | 
			
		||||
//     RPOPLPUSH: 'RPOPLPUSH source destination',
 | 
			
		||||
//     RPUSH: 'RPUSH key value [value ...]',
 | 
			
		||||
//     RPUSHX: 'RPUSHX key value',
 | 
			
		||||
//     SADD: 'SADD key member [member ...]',
 | 
			
		||||
//     SCRIPT: ['SCRIPT EXISTS script [script ...]', 'SCRIPT FLUSH', 'SCRIPT KILL', 'SCRIPT LOAD script'],
 | 
			
		||||
//     SDIFFSTORE: 'SDIFFSTORE destination key [key ...]',
 | 
			
		||||
//     SET: 'SET key value',
 | 
			
		||||
//     SETBIT: 'SETBIT key offset value',
 | 
			
		||||
//     SETEX: 'SETEX key seconds value',
 | 
			
		||||
//     SETNX: 'SETNX key value',
 | 
			
		||||
//     SETRANGE: 'SETRANGE key offset value',
 | 
			
		||||
//     SINTERSTORE: 'SINTERSTORE destination key [key ...]',
 | 
			
		||||
//     SMOVE: 'SMOVE source destination member',
 | 
			
		||||
//     SORT: 'SORT key [BY pattern] [LIMIT offset count] [GET pattern [GET pattern ...]] [ASC|DESC] [ALPHA] [STORE destination]',
 | 
			
		||||
//     SPOP: 'SPOP key',
 | 
			
		||||
//     SREM: 'SREM key member [member ...]',
 | 
			
		||||
//     SUNIONSTORE: 'SUNIONSTORE destination key [key ...]',
 | 
			
		||||
//     SWAPDB: 'SWAPDB index1 index2',
 | 
			
		||||
//     UNLINK: 'UNLINK key [key ...]',
 | 
			
		||||
//     XADD: 'XADD key ID field string [field string ...]',
 | 
			
		||||
//     XDEL: 'XDEL key ID [ID ...]',
 | 
			
		||||
//     XGROUP: [
 | 
			
		||||
//         'XGROUP CREATE key groupname id|$ [MKSTREAM]',
 | 
			
		||||
//         'XGROUP CREATECONSUMER key groupname consumername',
 | 
			
		||||
//         'XGROUP DELCONSUMER key groupname consumername',
 | 
			
		||||
//         'XGROUP DESTROY key groupname',
 | 
			
		||||
//         'XGROUP SETID key groupname id|$',
 | 
			
		||||
//     ],
 | 
			
		||||
//     XTRIM: 'XTRIM key MAXLEN [~] count',
 | 
			
		||||
//     ZADD: 'ZADD key score member [score] [member]',
 | 
			
		||||
//     ZDIFFSTORE: 'ZDIFFSTORE destination numkeys key [key ...]',
 | 
			
		||||
//     ZINCRBY: 'ZINCRBY key increment member',
 | 
			
		||||
//     ZINTERSTORE: 'ZINTERSTORE destination numkeys key [key ...] [WEIGHTS weight [weight ...]] [AGGREGATE SUM|MIN|MAX]',
 | 
			
		||||
//     ZPOPMAX: 'ZPOPMAX key [count]',
 | 
			
		||||
//     ZPOPMIN: 'ZPOPMIN key [count]',
 | 
			
		||||
//     ZRANGESTORE: 'ZRANGESTORE dst src min max [BYSCORE|BYLEX] [REV] [LIMIT offset count]',
 | 
			
		||||
//     ZREM: 'ZREM key member [member ...]',
 | 
			
		||||
//     ZREMRANGEBYLEX: 'ZREMRANGEBYLEX key min max',
 | 
			
		||||
//     ZREMRANGEBYRANK: 'ZREMRANGEBYRANK key start stop',
 | 
			
		||||
//     ZREMRANGEBYSCORE: 'ZREMRANGEBYSCORE key min max',
 | 
			
		||||
//     ZUNIONSTORE: 'ZUNIONSTORE destination numkeys key [key ...] [WEIGHTS weight [weight ...]] [AGGREGATE SUM|MIN|MAX]',
 | 
			
		||||
// };
 | 
			
		||||
 
 | 
			
		||||
@@ -52,9 +52,9 @@
 | 
			
		||||
                                />
 | 
			
		||||
 | 
			
		||||
                                <span class="ml5">
 | 
			
		||||
                                    {{ data.code }}
 | 
			
		||||
                                    <span style="color: #3c8dbc">【</span>
 | 
			
		||||
                                    {{ 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>
 | 
			
		||||
 
 | 
			
		||||
@@ -33,6 +33,7 @@
 | 
			
		||||
            :before-close="cancelSaveTeam"
 | 
			
		||||
            :destroy-on-close="true"
 | 
			
		||||
            :close-on-click-modal="false"
 | 
			
		||||
            size="40%"
 | 
			
		||||
        >
 | 
			
		||||
            <template #header>
 | 
			
		||||
                <DrawerHeader :header="addTeamDialog.form.id ? '编辑团队' : '添加团队'" :back="cancelSaveTeam" />
 | 
			
		||||
 
 | 
			
		||||
@@ -23,6 +23,7 @@ export const tagApi = {
 | 
			
		||||
export const resourceAuthCertApi = {
 | 
			
		||||
    detail: Api.newGet('/auth-certs/detail'),
 | 
			
		||||
    listByQuery: Api.newGet('/auth-certs'),
 | 
			
		||||
    getByCodes: Api.newGet('/auth-certs/simple'),
 | 
			
		||||
    save: Api.newPost('/auth-certs'),
 | 
			
		||||
    delete: Api.newDelete('/auth-certs/{id}'),
 | 
			
		||||
};
 | 
			
		||||
 
 | 
			
		||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user