mirror of
				https://gitee.com/dromara/mayfly-go
				synced 2025-11-04 08:20:25 +08:00 
			
		
		
		
	Compare commits
	
		
			25 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					2deb3109c2 | ||
| 
						 | 
					a80221a950 | ||
| 
						 | 
					10630847df | ||
| 
						 | 
					f43851698e | ||
| 
						 | 
					73884bb693 | ||
| 
						 | 
					1b5bb1de8b | ||
| 
						 | 
					4814793546 | ||
| 
						 | 
					d85bbff270 | ||
| 
						 | 
					bb1522f4dc | ||
| 
						 | 
					a7632fbf58 | ||
| 
						 | 
					c4cb4234fd | ||
| 
						 | 
					89e12678eb | ||
| 
						 | 
					137ebb8e9e | ||
| 
						 | 
					05625bd8c1 | ||
| 
						 | 
					4afeac5fdd | ||
| 
						 | 
					1d0e91f1af | ||
| 
						 | 
					cf5111a325 | ||
| 
						 | 
					78957a8ebd | ||
| 
						 | 
					4ed892a656 | ||
| 
						 | 
					3486b07003 | ||
| 
						 | 
					a5cd7caf19 | ||
| 
						 | 
					f2c7ef78c0 | ||
| 
						 | 
					653953ee76 | ||
| 
						 | 
					a831614d5a | ||
| 
						 | 
					29fd5a25d2 | 
							
								
								
									
										37
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										37
									
								
								README.md
									
									
									
									
									
								
							@@ -22,7 +22,7 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
### 介绍
 | 
					### 介绍
 | 
				
			||||||
 | 
					
 | 
				
			||||||
web 版 **linux(终端[终端回放] 文件 脚本 进程 计划任务)、数据库(mysql postgres oracle sqlserver 达梦 高斯 sqlite)、redis(单机 哨兵 集群)、mongo 等集工单流程审批于一体的统一管理操作平台**
 | 
					web 版 **linux(终端[终端回放、命令过滤] 文件 脚本 进程 计划任务)、数据库(mysql postgres oracle sqlserver 达梦 高斯 sqlite)数据操作 数据同步 数据迁移、redis(单机 哨兵 集群)、mongo 等集工单流程审批于一体的统一管理操作平台**
 | 
				
			||||||
 | 
					
 | 
				
			||||||
### 开发语言与主要框架
 | 
					### 开发语言与主要框架
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -45,56 +45,61 @@ http://go.mayfly.run
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
### 系统核心功能截图
 | 
					### 系统核心功能截图
 | 
				
			||||||
 | 
					
 | 
				
			||||||
##### 记录操作记录
 | 
					#### 首页
 | 
				
			||||||
 | 
					
 | 
				
			||||||

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

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

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

 | 
					
 | 
				
			||||||

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

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

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

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

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

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

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

 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
**其他更多功能&操作指南可查看在线文档**: https://www.yuque.com/may-fly/mayfly-go
 | 
					**其他更多功能&操作指南可查看在线文档**: https://www.yuque.com/may-fly/mayfly-go
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -14,7 +14,7 @@ services:
 | 
				
			|||||||
    restart: always
 | 
					    restart: always
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  server:
 | 
					  server:
 | 
				
			||||||
    image: mayfly-go:v1.3.1
 | 
					    image: ccr.ccs.tencentyun.com/mayfly/mayfly-go:v1.8.5
 | 
				
			||||||
    build:
 | 
					    build:
 | 
				
			||||||
      context: .
 | 
					      context: .
 | 
				
			||||||
      dockerfile: Dockerfile
 | 
					      dockerfile: Dockerfile
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -10,20 +10,20 @@
 | 
				
			|||||||
  },
 | 
					  },
 | 
				
			||||||
  "dependencies": {
 | 
					  "dependencies": {
 | 
				
			||||||
    "@element-plus/icons-vue": "^2.3.1",
 | 
					    "@element-plus/icons-vue": "^2.3.1",
 | 
				
			||||||
    "@vueuse/core": "^10.9.0",
 | 
					    "@vueuse/core": "^10.11.0",
 | 
				
			||||||
    "asciinema-player": "^3.7.0",
 | 
					    "asciinema-player": "^3.8.0",
 | 
				
			||||||
    "axios": "^1.6.2",
 | 
					    "axios": "^1.6.2",
 | 
				
			||||||
    "clipboard": "^2.0.11",
 | 
					    "clipboard": "^2.0.11",
 | 
				
			||||||
    "countup.js": "^2.8.0", 
 | 
					 | 
				
			||||||
    "cropperjs": "^1.6.1",
 | 
					    "cropperjs": "^1.6.1",
 | 
				
			||||||
    "echarts": "^5.5.0",
 | 
					    "dayjs": "^1.11.11",
 | 
				
			||||||
    "element-plus": "^2.7.1",
 | 
					    "echarts": "^5.5.1",
 | 
				
			||||||
 | 
					    "element-plus": "^2.7.7",
 | 
				
			||||||
    "js-base64": "^3.7.7",
 | 
					    "js-base64": "^3.7.7",
 | 
				
			||||||
    "jsencrypt": "^3.3.2",
 | 
					    "jsencrypt": "^3.3.2",
 | 
				
			||||||
    "lodash": "^4.17.21",
 | 
					    "lodash": "^4.17.21",
 | 
				
			||||||
    "mitt": "^3.0.1",
 | 
					    "mitt": "^3.0.1",
 | 
				
			||||||
    "monaco-editor": "^0.47.0",
 | 
					    "monaco-editor": "^0.50.0",
 | 
				
			||||||
    "monaco-sql-languages": "^0.11.0",
 | 
					    "monaco-sql-languages": "^0.12.2",
 | 
				
			||||||
    "monaco-themes": "^0.4.4",
 | 
					    "monaco-themes": "^0.4.4",
 | 
				
			||||||
    "nprogress": "^0.2.0",
 | 
					    "nprogress": "^0.2.0",
 | 
				
			||||||
    "pinia": "^2.1.7",
 | 
					    "pinia": "^2.1.7",
 | 
				
			||||||
@@ -34,8 +34,8 @@
 | 
				
			|||||||
    "sql-formatter": "^15.0.2",
 | 
					    "sql-formatter": "^15.0.2",
 | 
				
			||||||
    "trzsz": "^1.1.5",
 | 
					    "trzsz": "^1.1.5",
 | 
				
			||||||
    "uuid": "^9.0.1",
 | 
					    "uuid": "^9.0.1",
 | 
				
			||||||
    "vue": "^3.4.23",
 | 
					    "vue": "^3.4.32",
 | 
				
			||||||
    "vue-router": "^4.3.2",
 | 
					    "vue-router": "^4.4.0",
 | 
				
			||||||
    "xterm": "^5.3.0",
 | 
					    "xterm": "^5.3.0",
 | 
				
			||||||
    "xterm-addon-fit": "^0.8.0",
 | 
					    "xterm-addon-fit": "^0.8.0",
 | 
				
			||||||
    "xterm-addon-search": "^0.13.0",
 | 
					    "xterm-addon-search": "^0.13.0",
 | 
				
			||||||
@@ -48,16 +48,16 @@
 | 
				
			|||||||
    "@types/sortablejs": "^1.15.8",
 | 
					    "@types/sortablejs": "^1.15.8",
 | 
				
			||||||
    "@typescript-eslint/eslint-plugin": "^6.7.4",
 | 
					    "@typescript-eslint/eslint-plugin": "^6.7.4",
 | 
				
			||||||
    "@typescript-eslint/parser": "^6.7.4",
 | 
					    "@typescript-eslint/parser": "^6.7.4",
 | 
				
			||||||
    "@vitejs/plugin-vue": "^5.0.4",
 | 
					    "@vitejs/plugin-vue": "^5.0.5",
 | 
				
			||||||
    "@vue/compiler-sfc": "^3.4.23",
 | 
					    "@vue/compiler-sfc": "^3.4.32",
 | 
				
			||||||
    "code-inspector-plugin": "^0.4.5",
 | 
					    "code-inspector-plugin": "^0.4.5",
 | 
				
			||||||
    "dotenv": "^16.3.1",
 | 
					    "dotenv": "^16.3.1",
 | 
				
			||||||
    "eslint": "^8.35.0",
 | 
					    "eslint": "^8.35.0",
 | 
				
			||||||
    "eslint-plugin-vue": "^9.25.0",
 | 
					    "eslint-plugin-vue": "^9.25.0",
 | 
				
			||||||
    "prettier": "^3.2.5",
 | 
					    "prettier": "^3.2.5",
 | 
				
			||||||
    "sass": "^1.75.0",
 | 
					    "sass": "^1.77.8",
 | 
				
			||||||
    "typescript": "^5.4.5",
 | 
					    "typescript": "^5.5.3",
 | 
				
			||||||
    "vite": "^5.2.10",
 | 
					    "vite": "^5.3.4",
 | 
				
			||||||
    "vue-eslint-parser": "^9.4.2"
 | 
					    "vue-eslint-parser": "^9.4.2"
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  "browserslist": [
 | 
					  "browserslist": [
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -15,7 +15,7 @@ const config = {
 | 
				
			|||||||
    baseWsUrl: `${(window as any).globalConfig.BaseWsUrl || `${location.protocol == 'https:' ? 'wss:' : 'ws:'}//${getBaseApiUrl()}`}/api`,
 | 
					    baseWsUrl: `${(window as any).globalConfig.BaseWsUrl || `${location.protocol == 'https:' ? 'wss:' : 'ws:'}//${getBaseApiUrl()}`}/api`,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // 系统版本
 | 
					    // 系统版本
 | 
				
			||||||
    version: 'v1.8.1',
 | 
					    version: 'v1.8.8',
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default config;
 | 
					export default config;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -2,6 +2,7 @@ import request from './request';
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
export default {
 | 
					export default {
 | 
				
			||||||
    login: (param: any) => request.post('/auth/accounts/login', param),
 | 
					    login: (param: any) => request.post('/auth/accounts/login', param),
 | 
				
			||||||
 | 
					    refreshToken: (param: any) => request.get('/auth/accounts/refreshToken', param),
 | 
				
			||||||
    otpVerify: (param: any) => request.post('/auth/accounts/otp-verify', param),
 | 
					    otpVerify: (param: any) => request.post('/auth/accounts/otp-verify', param),
 | 
				
			||||||
    getPublicKey: () => request.get('/common/public-key'),
 | 
					    getPublicKey: () => request.get('/common/public-key'),
 | 
				
			||||||
    getConfigValue: (params: any) => request.get('/sys/configs/value', params),
 | 
					    getConfigValue: (params: any) => request.get('/sys/configs/value', params),
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -38,6 +38,7 @@ export enum ResultEnum {
 | 
				
			|||||||
    PARAM_ERROR = 405,
 | 
					    PARAM_ERROR = 405,
 | 
				
			||||||
    SERVER_ERROR = 500,
 | 
					    SERVER_ERROR = 500,
 | 
				
			||||||
    NO_PERMISSION = 501,
 | 
					    NO_PERMISSION = 501,
 | 
				
			||||||
 | 
					    ACCESS_TOKEN_INVALID = 502, // accessToken失效
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const baseUrl: string = config.baseApiUrl;
 | 
					export const baseUrl: string = config.baseApiUrl;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,31 +0,0 @@
 | 
				
			|||||||
export function dateFormat2(fmt: string, date: Date) {
 | 
					 | 
				
			||||||
    let ret;
 | 
					 | 
				
			||||||
    const opt = {
 | 
					 | 
				
			||||||
        'y+': date.getFullYear().toString(), // 年
 | 
					 | 
				
			||||||
        'M+': (date.getMonth() + 1).toString(), // 月
 | 
					 | 
				
			||||||
        'd+': date.getDate().toString(), // 日
 | 
					 | 
				
			||||||
        'H+': date.getHours().toString(), // 时
 | 
					 | 
				
			||||||
        'm+': date.getMinutes().toString(), // 分
 | 
					 | 
				
			||||||
        's+': date.getSeconds().toString(), // 秒
 | 
					 | 
				
			||||||
        'S+': date.getMilliseconds() ? date.getMilliseconds().toString() : '', // 毫秒
 | 
					 | 
				
			||||||
        // 有其他格式化字符需求可以继续添加,必须转化成字符串
 | 
					 | 
				
			||||||
    };
 | 
					 | 
				
			||||||
    for (const k in opt) {
 | 
					 | 
				
			||||||
        ret = new RegExp('(' + k + ')').exec(fmt);
 | 
					 | 
				
			||||||
        if (ret) {
 | 
					 | 
				
			||||||
            fmt = fmt.replace(ret[1], ret[1].length == 1 ? opt[k] : opt[k].padStart(ret[1].length, '0'));
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    return fmt;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export function dateStrFormat(fmt: string, dateStr: string) {
 | 
					 | 
				
			||||||
    return dateFormat2(fmt, new Date(dateStr));
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export function dateFormat(dateStr: string) {
 | 
					 | 
				
			||||||
    if (!dateStr) {
 | 
					 | 
				
			||||||
        return '';
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    return dateFormat2('yyyy-MM-dd HH:mm:ss', new Date(dateStr));
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@@ -1,3 +1,18 @@
 | 
				
			|||||||
 | 
					import dayjs from 'dayjs';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * 格式化日期
 | 
				
			||||||
 | 
					 * @param date 日期 字符串 Date 时间戳等
 | 
				
			||||||
 | 
					 * @param format 格式化格式  默认 YYYY-MM-DD HH:mm:ss
 | 
				
			||||||
 | 
					 * @returns 格式化后内容
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					export function formatDate(date: any, format: string = 'YYYY-MM-DD HH:mm:ss') {
 | 
				
			||||||
 | 
					    if (!date) {
 | 
				
			||||||
 | 
					        return '';
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    return dayjs(date).format(format);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * 格式化字节单位
 | 
					 * 格式化字节单位
 | 
				
			||||||
 * @param size byte size
 | 
					 * @param size byte size
 | 
				
			||||||
@@ -46,110 +61,6 @@ export function convertToBytes(sizeStr: string) {
 | 
				
			|||||||
    return bytes;
 | 
					    return bytes;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/*
 | 
					 | 
				
			||||||
 * 年(Y) 可用1-4个占位符
 | 
					 | 
				
			||||||
 * 月(m)、日(d)、小时(H)、分(M)、秒(S) 可用1-2个占位符
 | 
					 | 
				
			||||||
 * 星期(W) 可用1-3个占位符
 | 
					 | 
				
			||||||
 * 季度(q为阿拉伯数字,Q为中文数字)可用1或4个占位符
 | 
					 | 
				
			||||||
 *
 | 
					 | 
				
			||||||
 * let date = new Date()
 | 
					 | 
				
			||||||
 * formatDate(date, "YYYY-mm-dd HH:MM:SS")           // 2020-02-09 14:04:23
 | 
					 | 
				
			||||||
 * formatDate(date, "YYYY-mm-dd HH:MM:SS Q")         // 2020-02-09 14:09:03 一
 | 
					 | 
				
			||||||
 * formatDate(date, "YYYY-mm-dd HH:MM:SS WWW")       // 2020-02-09 14:45:12 星期日
 | 
					 | 
				
			||||||
 * formatDate(date, "YYYY-mm-dd HH:MM:SS QQQQ")      // 2020-02-09 14:09:36 第一季度
 | 
					 | 
				
			||||||
 * formatDate(date, "YYYY-mm-dd HH:MM:SS WWW QQQQ")  // 2020-02-09 14:46:12 星期日 第一季度
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
export function formatDate(date: Date, format: string) {
 | 
					 | 
				
			||||||
    let we = date.getDay(); // 星期
 | 
					 | 
				
			||||||
    let qut = Math.floor((date.getMonth() + 3) / 3).toString(); // 季度
 | 
					 | 
				
			||||||
    const opt: any = {
 | 
					 | 
				
			||||||
        'Y+': date.getFullYear().toString(), // 年
 | 
					 | 
				
			||||||
        'm+': (date.getMonth() + 1).toString(), // 月(月份从0开始,要+1)
 | 
					 | 
				
			||||||
        'd+': date.getDate().toString(), // 日
 | 
					 | 
				
			||||||
        'H+': date.getHours().toString(), // 时
 | 
					 | 
				
			||||||
        'M+': date.getMinutes().toString(), // 分
 | 
					 | 
				
			||||||
        'S+': date.getSeconds().toString(), // 秒
 | 
					 | 
				
			||||||
        'q+': qut, // 季度
 | 
					 | 
				
			||||||
    };
 | 
					 | 
				
			||||||
    // 中文数字 (星期)
 | 
					 | 
				
			||||||
    const week: any = {
 | 
					 | 
				
			||||||
        '0': '日',
 | 
					 | 
				
			||||||
        '1': '一',
 | 
					 | 
				
			||||||
        '2': '二',
 | 
					 | 
				
			||||||
        '3': '三',
 | 
					 | 
				
			||||||
        '4': '四',
 | 
					 | 
				
			||||||
        '5': '五',
 | 
					 | 
				
			||||||
        '6': '六',
 | 
					 | 
				
			||||||
    };
 | 
					 | 
				
			||||||
    // 中文数字(季度)
 | 
					 | 
				
			||||||
    const quarter: any = {
 | 
					 | 
				
			||||||
        '1': '一',
 | 
					 | 
				
			||||||
        '2': '二',
 | 
					 | 
				
			||||||
        '3': '三',
 | 
					 | 
				
			||||||
        '4': '四',
 | 
					 | 
				
			||||||
    };
 | 
					 | 
				
			||||||
    if (/(W+)/.test(format)) format = format.replace(RegExp.$1, RegExp.$1.length > 1 ? (RegExp.$1.length > 2 ? '星期' + week[we] : '周' + week[we]) : week[we]);
 | 
					 | 
				
			||||||
    if (/(Q+)/.test(format)) format = format.replace(RegExp.$1, RegExp.$1.length == 4 ? '第' + quarter[qut] + '季度' : quarter[qut]);
 | 
					 | 
				
			||||||
    for (let k in opt) {
 | 
					 | 
				
			||||||
        let r = new RegExp('(' + k + ')').exec(format);
 | 
					 | 
				
			||||||
        // 若输入的长度不为1,则前面补零
 | 
					 | 
				
			||||||
        if (r) format = format.replace(r[1], RegExp.$1.length == 1 ? opt[k] : opt[k].padStart(RegExp.$1.length, '0'));
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    return format;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * 10秒:  10 * 1000
 | 
					 | 
				
			||||||
 * 1分:   60 * 1000
 | 
					 | 
				
			||||||
 * 1小时: 60 * 60 * 1000
 | 
					 | 
				
			||||||
 * 24小时:60 * 60 * 24 * 1000
 | 
					 | 
				
			||||||
 * 3天:   60 * 60* 24 * 1000 * 3
 | 
					 | 
				
			||||||
 *
 | 
					 | 
				
			||||||
 * let data = new Date()
 | 
					 | 
				
			||||||
 * formatPast(data)                                           // 刚刚
 | 
					 | 
				
			||||||
 * formatPast(data - 11 * 1000)                               // 11秒前
 | 
					 | 
				
			||||||
 * formatPast(data - 2 * 60 * 1000)                           // 2分钟前
 | 
					 | 
				
			||||||
 * formatPast(data - 60 * 60 * 2 * 1000)                      // 2小时前
 | 
					 | 
				
			||||||
 * formatPast(data - 60 * 60 * 2 * 1000)                      // 2小时前
 | 
					 | 
				
			||||||
 * formatPast(data - 60 * 60 * 71 * 1000)                     // 2天前
 | 
					 | 
				
			||||||
 * formatPast("2020-06-01")                                   // 2020-06-01
 | 
					 | 
				
			||||||
 * formatPast("2020-06-01", "YYYY-mm-dd HH:MM:SS WWW QQQQ")   // 2020-06-01 08:00:00 星期一 第二季度
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
export function formatPast(param: any, format: string = 'YYYY-mm-dd') {
 | 
					 | 
				
			||||||
    // 传入格式处理、存储转换值
 | 
					 | 
				
			||||||
    let t: any, s: any;
 | 
					 | 
				
			||||||
    // 获取js 时间戳
 | 
					 | 
				
			||||||
    let time: any = new Date().getTime();
 | 
					 | 
				
			||||||
    // 是否是对象
 | 
					 | 
				
			||||||
    typeof param === 'string' || 'object' ? (t = new Date(param).getTime()) : (t = param);
 | 
					 | 
				
			||||||
    // 当前时间戳 - 传入时间戳
 | 
					 | 
				
			||||||
    time = Number.parseInt(`${time - t}`);
 | 
					 | 
				
			||||||
    if (time < 10000) {
 | 
					 | 
				
			||||||
        // 10秒内
 | 
					 | 
				
			||||||
        return '刚刚';
 | 
					 | 
				
			||||||
    } else if (time < 60000 && time >= 10000) {
 | 
					 | 
				
			||||||
        // 超过10秒少于1分钟内
 | 
					 | 
				
			||||||
        s = Math.floor(time / 1000);
 | 
					 | 
				
			||||||
        return `${s}秒前`;
 | 
					 | 
				
			||||||
    } else if (time < 3600000 && time >= 60000) {
 | 
					 | 
				
			||||||
        // 超过1分钟少于1小时
 | 
					 | 
				
			||||||
        s = Math.floor(time / 60000);
 | 
					 | 
				
			||||||
        return `${s}分钟前`;
 | 
					 | 
				
			||||||
    } else if (time < 86400000 && time >= 3600000) {
 | 
					 | 
				
			||||||
        // 超过1小时少于24小时
 | 
					 | 
				
			||||||
        s = Math.floor(time / 3600000);
 | 
					 | 
				
			||||||
        return `${s}小时前`;
 | 
					 | 
				
			||||||
    } else if (time < 259200000 && time >= 86400000) {
 | 
					 | 
				
			||||||
        // 超过1天少于3天内
 | 
					 | 
				
			||||||
        s = Math.floor(time / 86400000);
 | 
					 | 
				
			||||||
        return `${s}天前`;
 | 
					 | 
				
			||||||
    } else {
 | 
					 | 
				
			||||||
        // 超过3天
 | 
					 | 
				
			||||||
        let date = typeof param === 'string' || 'object' ? new Date(param) : param;
 | 
					 | 
				
			||||||
        return formatDate(date, format);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * 格式化指定时间数为人性化可阅读的内容(默认time为秒单位)
 | 
					 * 格式化指定时间数为人性化可阅读的内容(默认time为秒单位)
 | 
				
			||||||
 *
 | 
					 *
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -9,7 +9,19 @@ export function getValueByPath(obj: any, path: string) {
 | 
				
			|||||||
    const keys = path.split('.');
 | 
					    const keys = path.split('.');
 | 
				
			||||||
    let result = obj;
 | 
					    let result = obj;
 | 
				
			||||||
    for (let key of keys) {
 | 
					    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;
 | 
					            return undefined;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -23,7 +35,18 @@ export function getValueByPath(obj: any, path: string) {
 | 
				
			|||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            const index = parseInt(matchIndex[1]);
 | 
					            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 {
 | 
					        } else {
 | 
				
			||||||
            result = result[key];
 | 
					            result = result[key];
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,6 +1,7 @@
 | 
				
			|||||||
import { randomUuid } from './string';
 | 
					import { randomUuid } from './string';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const TokenKey = 'm-token';
 | 
					const TokenKey = 'm-token';
 | 
				
			||||||
 | 
					const RefreshTokenKey = 'm-refresh-token';
 | 
				
			||||||
const UserKey = 'm-user';
 | 
					const UserKey = 'm-user';
 | 
				
			||||||
const TagViewsKey = 'm-tagViews';
 | 
					const TagViewsKey = 'm-tagViews';
 | 
				
			||||||
const ClientIdKey = 'm-clientId';
 | 
					const ClientIdKey = 'm-clientId';
 | 
				
			||||||
@@ -15,6 +16,14 @@ export function saveToken(token: string) {
 | 
				
			|||||||
    setLocal(TokenKey, token);
 | 
					    setLocal(TokenKey, token);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function getRefreshToken(): string {
 | 
				
			||||||
 | 
					    return getLocal(RefreshTokenKey);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function saveRefreshToken(refreshToken: string) {
 | 
				
			||||||
 | 
					    return setLocal(RefreshTokenKey, refreshToken);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// 获取登录用户基础信息
 | 
					// 获取登录用户基础信息
 | 
				
			||||||
export function getUser() {
 | 
					export function getUser() {
 | 
				
			||||||
    return getLocal(UserKey);
 | 
					    return getLocal(UserKey);
 | 
				
			||||||
@@ -39,6 +48,7 @@ export function getThemeConfig() {
 | 
				
			|||||||
export function clearUser() {
 | 
					export function clearUser() {
 | 
				
			||||||
    removeLocal(TokenKey);
 | 
					    removeLocal(TokenKey);
 | 
				
			||||||
    removeLocal(UserKey);
 | 
					    removeLocal(UserKey);
 | 
				
			||||||
 | 
					    removeLocal(RefreshTokenKey);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function getTagViews() {
 | 
					export function getTagViews() {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -97,43 +97,6 @@ export function getTextWidth(str: string) {
 | 
				
			|||||||
    return width;
 | 
					    return width;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * 获取内容所需要占用的宽度
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
export function getContentWidth(content: any): number {
 | 
					 | 
				
			||||||
    if (!content) {
 | 
					 | 
				
			||||||
        return 50;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    // 以下分配的单位长度可根据实际需求进行调整
 | 
					 | 
				
			||||||
    let flexWidth = 0;
 | 
					 | 
				
			||||||
    for (const char of content) {
 | 
					 | 
				
			||||||
        if (flexWidth > 500) {
 | 
					 | 
				
			||||||
            break;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        if ((char >= '0' && char <= '9') || (char >= 'a' && char <= 'z')) {
 | 
					 | 
				
			||||||
            // 小写字母、数字字符
 | 
					 | 
				
			||||||
            flexWidth += 9.3;
 | 
					 | 
				
			||||||
            continue;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        if (char >= 'A' && char <= 'Z') {
 | 
					 | 
				
			||||||
            flexWidth += 9;
 | 
					 | 
				
			||||||
            continue;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        if (char >= '\u4e00' && char <= '\u9fa5') {
 | 
					 | 
				
			||||||
            // 如果是中文字符,为字符分配16个单位宽度
 | 
					 | 
				
			||||||
            flexWidth += 20;
 | 
					 | 
				
			||||||
        } else {
 | 
					 | 
				
			||||||
            // 其他种类字符
 | 
					 | 
				
			||||||
            flexWidth += 8;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    // if (flexWidth > 450) {
 | 
					 | 
				
			||||||
    //     // 设置最大宽度
 | 
					 | 
				
			||||||
    //     flexWidth = 450;
 | 
					 | 
				
			||||||
    // }
 | 
					 | 
				
			||||||
    return flexWidth;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 *
 | 
					 *
 | 
				
			||||||
 * @returns uuid
 | 
					 * @returns uuid
 | 
				
			||||||
@@ -179,3 +142,38 @@ export async function copyToClipboard(txt: string, selector: string = '#copyValu
 | 
				
			|||||||
        clipboard.destroy();
 | 
					        clipboard.destroy();
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function fuzzyMatchField(keyword: string, fields: any[], ...valueExtractFuncs: Function[]) {
 | 
				
			||||||
 | 
					    keyword = keyword?.toLowerCase();
 | 
				
			||||||
 | 
					    return fields.filter((field) => {
 | 
				
			||||||
 | 
					        for (let valueExtractFunc of valueExtractFuncs) {
 | 
				
			||||||
 | 
					            const value = valueExtractFunc(field)?.toLowerCase();
 | 
				
			||||||
 | 
					            if (isPrefixSubsequence(keyword, value)) {
 | 
				
			||||||
 | 
					                return true;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        return false;
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * 匹配是否为前缀子序列 targetTemplate=username prefix=uname -> true,prefix=uname2 -> false
 | 
				
			||||||
 | 
					 * @param prefix 字符串前缀(不连续也可以,但不改变字符的相对顺序)
 | 
				
			||||||
 | 
					 * @param targetTemplate 目标模板
 | 
				
			||||||
 | 
					 * @returns 是否匹配
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					export function isPrefixSubsequence(prefix: string, targetTemplate: string) {
 | 
				
			||||||
 | 
					    let i = 0; // 指向prefix的索引
 | 
				
			||||||
 | 
					    let j = 0; // 指向targetTemplate的索引
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    while (i < prefix.length && j < targetTemplate.length) {
 | 
				
			||||||
 | 
					        if (prefix[i] === targetTemplate[j]) {
 | 
				
			||||||
 | 
					            // 字符匹配,两个指针都向前移动
 | 
				
			||||||
 | 
					            i++;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        j++; // 目标字符串指针始终向前移动
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // 如果prefix的所有字符都被找到,返回true
 | 
				
			||||||
 | 
					    return i === prefix.length;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -159,7 +159,7 @@
 | 
				
			|||||||
                    @current-change="handlePageNumChange"
 | 
					                    @current-change="handlePageNumChange"
 | 
				
			||||||
                    @size-change="handlePageSizeChange"
 | 
					                    @size-change="handlePageSizeChange"
 | 
				
			||||||
                    style="text-align: right"
 | 
					                    style="text-align: right"
 | 
				
			||||||
                    layout="prev, pager, next, total, sizes, jumper"
 | 
					                    layout="prev, pager, next, total, sizes"
 | 
				
			||||||
                    :total="total"
 | 
					                    :total="total"
 | 
				
			||||||
                    v-model:current-page="queryForm.pageNum"
 | 
					                    v-model:current-page="queryForm.pageNum"
 | 
				
			||||||
                    v-model:page-size="queryForm.pageSize"
 | 
					                    v-model:page-size="queryForm.pageSize"
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,5 +1,5 @@
 | 
				
			|||||||
import EnumValue from '@/common/Enum';
 | 
					import EnumValue from '@/common/Enum';
 | 
				
			||||||
import { dateFormat } from '@/common/utils/date';
 | 
					import { formatDate } from '@/common/utils/format';
 | 
				
			||||||
import { getValueByPath } from '@/common/utils/object';
 | 
					import { getValueByPath } from '@/common/utils/object';
 | 
				
			||||||
import { getTextWidth } from '@/common/utils/string';
 | 
					import { getTextWidth } from '@/common/utils/string';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -172,7 +172,7 @@ export class TableColumn {
 | 
				
			|||||||
     */
 | 
					     */
 | 
				
			||||||
    isTime(): TableColumn {
 | 
					    isTime(): TableColumn {
 | 
				
			||||||
        this.setFormatFunc((data: any, prop: string) => {
 | 
					        this.setFormatFunc((data: any, prop: string) => {
 | 
				
			||||||
            return dateFormat(getValueByPath(data, prop));
 | 
					            return formatDate(getValueByPath(data, prop));
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
        return this;
 | 
					        return this;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -24,8 +24,33 @@
 | 
				
			|||||||
                <SvgIcon name="Refresh" @click="connect(0, 0)" :size="20" class="pointer-icon mr10" title="重新连接" />
 | 
					                <SvgIcon name="Refresh" @click="connect(0, 0)" :size="20" class="pointer-icon mr10" title="重新连接" />
 | 
				
			||||||
            </div>
 | 
					            </div>
 | 
				
			||||||
            <clipboard-dialog ref="clipboardRef" v-model:visible="state.clipboardDialog.visible" @close="closePaste" @submit="onsubmitClipboard" />
 | 
					            <clipboard-dialog ref="clipboardRef" v-model:visible="state.clipboardDialog.visible" @close="closePaste" @submit="onsubmitClipboard" />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            <el-dialog
 | 
				
			||||||
 | 
					                v-if="!state.fullscreen"
 | 
				
			||||||
 | 
					                destroy-on-close
 | 
				
			||||||
 | 
					                :title="state.filesystemDialog.title"
 | 
				
			||||||
 | 
					                v-model="state.filesystemDialog.visible"
 | 
				
			||||||
 | 
					                :close-on-click-modal="false"
 | 
				
			||||||
 | 
					                width="70%"
 | 
				
			||||||
 | 
					            >
 | 
				
			||||||
 | 
					                <machine-file
 | 
				
			||||||
 | 
					                    :machine-id="state.filesystemDialog.machineId"
 | 
				
			||||||
 | 
					                    :auth-cert-name="state.filesystemDialog.authCertName"
 | 
				
			||||||
 | 
					                    :protocol="state.filesystemDialog.protocol"
 | 
				
			||||||
 | 
					                    :file-id="state.filesystemDialog.fileId"
 | 
				
			||||||
 | 
					                    :path="state.filesystemDialog.path"
 | 
				
			||||||
 | 
					                />
 | 
				
			||||||
 | 
					            </el-dialog>
 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
        <el-dialog destroy-on-close :title="state.filesystemDialog.title" v-model="state.filesystemDialog.visible" :close-on-click-modal="false" width="70%">
 | 
					
 | 
				
			||||||
 | 
					        <el-dialog
 | 
				
			||||||
 | 
					            v-if="!state.fullscreen"
 | 
				
			||||||
 | 
					            destroy-on-close
 | 
				
			||||||
 | 
					            :title="state.filesystemDialog.title"
 | 
				
			||||||
 | 
					            v-model="state.filesystemDialog.visible"
 | 
				
			||||||
 | 
					            :close-on-click-modal="false"
 | 
				
			||||||
 | 
					            width="70%"
 | 
				
			||||||
 | 
					        >
 | 
				
			||||||
            <machine-file
 | 
					            <machine-file
 | 
				
			||||||
                :machine-id="state.filesystemDialog.machineId"
 | 
					                :machine-id="state.filesystemDialog.machineId"
 | 
				
			||||||
                :auth-cert-name="state.filesystemDialog.authCertName"
 | 
					                :auth-cert-name="state.filesystemDialog.authCertName"
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -67,7 +67,7 @@ const state = reactive({
 | 
				
			|||||||
        search: null as any,
 | 
					        search: null as any,
 | 
				
			||||||
        weblinks: null as any,
 | 
					        weblinks: null as any,
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    status: TerminalStatus.NoConnected,
 | 
					    status: -11,
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
onMounted(() => {
 | 
					onMounted(() => {
 | 
				
			||||||
@@ -96,6 +96,7 @@ onBeforeUnmount(() => {
 | 
				
			|||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function init() {
 | 
					function init() {
 | 
				
			||||||
 | 
					    state.status = TerminalStatus.NoConnected;
 | 
				
			||||||
    if (term) {
 | 
					    if (term) {
 | 
				
			||||||
        console.log('重新连接...');
 | 
					        console.log('重新连接...');
 | 
				
			||||||
        close();
 | 
					        close();
 | 
				
			||||||
@@ -105,7 +106,7 @@ function init() {
 | 
				
			|||||||
    });
 | 
					    });
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function initTerm() {
 | 
					async function initTerm() {
 | 
				
			||||||
    term = new Terminal({
 | 
					    term = new Terminal({
 | 
				
			||||||
        fontSize: themeConfig.value.terminalFontSize || 15,
 | 
					        fontSize: themeConfig.value.terminalFontSize || 15,
 | 
				
			||||||
        fontWeight: themeConfig.value.terminalFontWeight || 'normal',
 | 
					        fontWeight: themeConfig.value.terminalFontWeight || 'normal',
 | 
				
			||||||
@@ -155,6 +156,7 @@ function initSocket() {
 | 
				
			|||||||
        state.status = TerminalStatus.Connected;
 | 
					        state.status = TerminalStatus.Connected;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        focus();
 | 
					        focus();
 | 
				
			||||||
 | 
					        fitTerminal();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // 如果有初始要执行的命令,则发送执行命令
 | 
					        // 如果有初始要执行的命令,则发送执行命令
 | 
				
			||||||
        if (props.cmd) {
 | 
					        if (props.cmd) {
 | 
				
			||||||
@@ -209,7 +211,6 @@ function loadAddon() {
 | 
				
			|||||||
        // tell trzsz the terminal columns has been changed
 | 
					        // tell trzsz the terminal columns has been changed
 | 
				
			||||||
        trzsz.setTerminalColumns(size.cols);
 | 
					        trzsz.setTerminalColumns(size.cols);
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
    window.addEventListener('resize', () => state.addon.fit.fit());
 | 
					 | 
				
			||||||
    // enable drag files or directories to upload
 | 
					    // enable drag files or directories to upload
 | 
				
			||||||
    terminalRef.value.addEventListener('dragover', (event: Event) => event.preventDefault());
 | 
					    terminalRef.value.addEventListener('dragover', (event: Event) => event.preventDefault());
 | 
				
			||||||
    terminalRef.value.addEventListener('drop', (event: any) => {
 | 
					    terminalRef.value.addEventListener('drop', (event: any) => {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,6 +1,15 @@
 | 
				
			|||||||
 | 
					import EnumValue from '@/common/Enum';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export enum TerminalStatus {
 | 
					export enum TerminalStatus {
 | 
				
			||||||
    Error = -1,
 | 
					    Error = -1,
 | 
				
			||||||
    NoConnected = 0,
 | 
					    NoConnected = 0,
 | 
				
			||||||
    Connected = 1,
 | 
					    Connected = 1,
 | 
				
			||||||
    Disconnected = 2,
 | 
					    Disconnected = 2,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const TerminalStatusEnum = {
 | 
				
			||||||
 | 
					    Error: EnumValue.of(TerminalStatus.Error, '连接出错').setExtra({ iconColor: 'var(--el-color-error)' }),
 | 
				
			||||||
 | 
					    NoConnected: EnumValue.of(TerminalStatus.NoConnected, '未连接').setExtra({ iconColor: 'var(--el-color-primary)' }),
 | 
				
			||||||
 | 
					    Connected: EnumValue.of(TerminalStatus.Connected, '连接成功').setExtra({ iconColor: 'var(--el-color-success)' }),
 | 
				
			||||||
 | 
					    Disconnected: EnumValue.of(TerminalStatus.Disconnected, '连接失败').setExtra({ iconColor: 'var(--el-color-error)' }),
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,5 +1,5 @@
 | 
				
			|||||||
import router from '@/router';
 | 
					import router from '@/router';
 | 
				
			||||||
import { getClientId, getToken } from '@/common/utils/storage';
 | 
					import { clearUser, getClientId, getRefreshToken, getToken, saveRefreshToken, saveToken } from '@/common/utils/storage';
 | 
				
			||||||
import { templateResolve } from '@/common/utils/string';
 | 
					import { templateResolve } from '@/common/utils/string';
 | 
				
			||||||
import { ElMessage } from 'element-plus';
 | 
					import { ElMessage } from 'element-plus';
 | 
				
			||||||
import { createFetch } from '@vueuse/core';
 | 
					import { createFetch } from '@vueuse/core';
 | 
				
			||||||
@@ -8,6 +8,7 @@ import { Result, ResultEnum } from '@/common/request';
 | 
				
			|||||||
import config from '@/common/config';
 | 
					import config from '@/common/config';
 | 
				
			||||||
import { unref } from 'vue';
 | 
					import { unref } from 'vue';
 | 
				
			||||||
import { URL_401 } from '@/router/staticRouter';
 | 
					import { URL_401 } from '@/router/staticRouter';
 | 
				
			||||||
 | 
					import openApi from '@/common/openApi';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const baseUrl: string = config.baseApiUrl;
 | 
					const baseUrl: string = config.baseApiUrl;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -88,6 +89,18 @@ export function useApiFetch<T>(api: Api, params: any = null, reqOptions: Request
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    return {
 | 
					    return {
 | 
				
			||||||
        execute: async function () {
 | 
					        execute: async function () {
 | 
				
			||||||
 | 
					            return execUaf(uaf);
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        isFetching: uaf.isFetching,
 | 
				
			||||||
 | 
					        data: uaf.data,
 | 
				
			||||||
 | 
					        abort: uaf.abort,
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					let refreshingToken = false;
 | 
				
			||||||
 | 
					let queue: any[] = [];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					async function execUaf(uaf: any) {
 | 
				
			||||||
    try {
 | 
					    try {
 | 
				
			||||||
        await uaf.execute(true);
 | 
					        await uaf.execute(true);
 | 
				
			||||||
    } catch (e: any) {
 | 
					    } catch (e: any) {
 | 
				
			||||||
@@ -119,14 +132,50 @@ export function useApiFetch<T>(api: Api, params: any = null, reqOptions: Request
 | 
				
			|||||||
        return Promise.reject(result);
 | 
					        return Promise.reject(result);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const resultCode = result.code;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // 如果返回为成功结果,则将结果的data赋值给响应式data
 | 
					    // 如果返回为成功结果,则将结果的data赋值给响应式data
 | 
				
			||||||
            if (result.code === ResultEnum.SUCCESS) {
 | 
					    if (resultCode === ResultEnum.SUCCESS) {
 | 
				
			||||||
        uaf.data.value = result.data;
 | 
					        uaf.data.value = result.data;
 | 
				
			||||||
        return;
 | 
					        return;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // 如果是accessToken失效,则使用refreshToken刷新token
 | 
				
			||||||
 | 
					    if (resultCode == ResultEnum.ACCESS_TOKEN_INVALID) {
 | 
				
			||||||
 | 
					        if (refreshingToken) {
 | 
				
			||||||
 | 
					            // 请求加入队列等待, 防止并发多次请求refreshToken
 | 
				
			||||||
 | 
					            return new Promise((resolve) => {
 | 
				
			||||||
 | 
					                queue.push(() => {
 | 
				
			||||||
 | 
					                    resolve(execUaf(uaf));
 | 
				
			||||||
 | 
					                });
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        try {
 | 
				
			||||||
 | 
					            refreshingToken = true;
 | 
				
			||||||
 | 
					            const res = await openApi.refreshToken({ refresh_token: getRefreshToken() });
 | 
				
			||||||
 | 
					            saveToken(res.token);
 | 
				
			||||||
 | 
					            saveRefreshToken(res.refresh_token);
 | 
				
			||||||
 | 
					            // 重新缓存后端用户权限code
 | 
				
			||||||
 | 
					            await openApi.getPermissions();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // 执行accessToken失效的请求
 | 
				
			||||||
 | 
					            queue.forEach((resolve: any) => {
 | 
				
			||||||
 | 
					                resolve();
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					        } catch (e: any) {
 | 
				
			||||||
 | 
					            clearUser();
 | 
				
			||||||
 | 
					        } finally {
 | 
				
			||||||
 | 
					            refreshingToken = false;
 | 
				
			||||||
 | 
					            queue = [];
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        await execUaf(uaf);
 | 
				
			||||||
 | 
					        return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // 如果提示没有权限,则跳转至无权限页面
 | 
					    // 如果提示没有权限,则跳转至无权限页面
 | 
				
			||||||
            if (result.code === ResultEnum.NO_PERMISSION) {
 | 
					    if (resultCode === ResultEnum.NO_PERMISSION) {
 | 
				
			||||||
        router.push({
 | 
					        router.push({
 | 
				
			||||||
            path: URL_401,
 | 
					            path: URL_401,
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
@@ -134,15 +183,10 @@ export function useApiFetch<T>(api: Api, params: any = null, reqOptions: Request
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // 如果返回的code不为成功,则会返回对应的错误msg,则直接统一通知即可。忽略登录超时或没有权限的提示(直接跳转至401页面)
 | 
					    // 如果返回的code不为成功,则会返回对应的错误msg,则直接统一通知即可。忽略登录超时或没有权限的提示(直接跳转至401页面)
 | 
				
			||||||
            if (result.msg && result?.code != ResultEnum.NO_PERMISSION) {
 | 
					    if (result.msg && resultCode != ResultEnum.NO_PERMISSION) {
 | 
				
			||||||
        ElMessage.error(result.msg);
 | 
					        ElMessage.error(result.msg);
 | 
				
			||||||
        uaf.error.value = new Error(result.msg);
 | 
					        uaf.error.value = new Error(result.msg);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return Promise.reject(result);
 | 
					    return Promise.reject(result);
 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
        isFetching: uaf.isFetching,
 | 
					 | 
				
			||||||
        data: uaf.data,
 | 
					 | 
				
			||||||
        abort: uaf.abort,
 | 
					 | 
				
			||||||
    };
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,20 +0,0 @@
 | 
				
			|||||||
import { saveThemeConfig } from '@/common/utils/storage';
 | 
					 | 
				
			||||||
import { isDark } from './user.vue';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export const switchDark = () => {
 | 
					 | 
				
			||||||
    themeConfig.value.isDark = isDark.value;
 | 
					 | 
				
			||||||
    if (isDark.value) {
 | 
					 | 
				
			||||||
        themeConfig.value.editorTheme = 'vs-dark';
 | 
					 | 
				
			||||||
    } else {
 | 
					 | 
				
			||||||
        themeConfig.value.editorTheme = 'vs';
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    // 如果终端主题不是自定义主题,则切换主题
 | 
					 | 
				
			||||||
    if (themeConfig.value.terminalTheme != 'custom') {
 | 
					 | 
				
			||||||
        if (isDark.value) {
 | 
					 | 
				
			||||||
            themeConfig.value.terminalTheme = 'dark';
 | 
					 | 
				
			||||||
        } else {
 | 
					 | 
				
			||||||
            themeConfig.value.terminalTheme = 'solarizedLight';
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    saveThemeConfig(themeConfig.value);
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
							
								
								
									
										29
									
								
								mayfly_go_web/src/store/autoOpenResource.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								mayfly_go_web/src/store/autoOpenResource.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,29 @@
 | 
				
			|||||||
 | 
					import { defineStore } from 'pinia';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * 自动打开资源
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					export const useAutoOpenResource = defineStore('autoOpenResource', {
 | 
				
			||||||
 | 
					    state: () => ({
 | 
				
			||||||
 | 
					        autoOpenResource: {
 | 
				
			||||||
 | 
					            machineCodePath: '',
 | 
				
			||||||
 | 
					            dbCodePath: '',
 | 
				
			||||||
 | 
					            redisCodePath: '',
 | 
				
			||||||
 | 
					            mongoCodePath: '',
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					    }),
 | 
				
			||||||
 | 
					    actions: {
 | 
				
			||||||
 | 
					        setMachineCodePath(codePath: string) {
 | 
				
			||||||
 | 
					            this.autoOpenResource.machineCodePath = codePath;
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        setDbCodePath(codePath: string) {
 | 
				
			||||||
 | 
					            this.autoOpenResource.dbCodePath = codePath;
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        setRedisCodePath(codePath: string) {
 | 
				
			||||||
 | 
					            this.autoOpenResource.redisCodePath = codePath;
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        setMongoCodePath(codePath: string) {
 | 
				
			||||||
 | 
					            this.autoOpenResource.mongoCodePath = codePath;
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
@@ -1,5 +1,5 @@
 | 
				
			|||||||
import { defineStore } from 'pinia';
 | 
					import { defineStore } from 'pinia';
 | 
				
			||||||
import { dateFormat2 } from '@/common/utils/date';
 | 
					import { formatDate } from '@/common/utils/format';
 | 
				
			||||||
import { useUserInfo } from '@/store/userInfo';
 | 
					import { useUserInfo } from '@/store/userInfo';
 | 
				
			||||||
import { getSysStyleConfig } from '@/common/sysconfig';
 | 
					import { getSysStyleConfig } from '@/common/sysconfig';
 | 
				
			||||||
import { getLocal, getThemeConfig } from '@/common/utils/storage';
 | 
					import { getLocal, getThemeConfig } from '@/common/utils/storage';
 | 
				
			||||||
@@ -114,7 +114,7 @@ export const useThemeConfig = defineStore('themeConfig', {
 | 
				
			|||||||
            // 默认布局,可选 1、默认 defaults 2、经典 classic 3、横向 transverse 4、分栏 columns
 | 
					            // 默认布局,可选 1、默认 defaults 2、经典 classic 3、横向 transverse 4、分栏 columns
 | 
				
			||||||
            layout: 'classic',
 | 
					            layout: 'classic',
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            terminalTheme: 'solarizedLight',
 | 
					            terminalTheme: 'light',
 | 
				
			||||||
            // ssh终端字体颜色
 | 
					            // ssh终端字体颜色
 | 
				
			||||||
            terminalForeground: '#C5C8C6',
 | 
					            terminalForeground: '#C5C8C6',
 | 
				
			||||||
            // ssh终端背景色
 | 
					            // ssh终端背景色
 | 
				
			||||||
@@ -191,7 +191,7 @@ export const useThemeConfig = defineStore('themeConfig', {
 | 
				
			|||||||
        },
 | 
					        },
 | 
				
			||||||
        // 设置水印时间为当前时间
 | 
					        // 设置水印时间为当前时间
 | 
				
			||||||
        setWatermarkNowTime() {
 | 
					        setWatermarkNowTime() {
 | 
				
			||||||
            this.themeConfig.watermarkText[1] = dateFormat2('yyyy-MM-dd HH:mm:ss', new Date());
 | 
					            this.themeConfig.watermarkText[1] = formatDate(new Date());
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
        // 切换暗黑模式
 | 
					        // 切换暗黑模式
 | 
				
			||||||
        switchDark(isDark: boolean) {
 | 
					        switchDark(isDark: boolean) {
 | 
				
			||||||
@@ -207,7 +207,7 @@ export const useThemeConfig = defineStore('themeConfig', {
 | 
				
			|||||||
                if (isDark) {
 | 
					                if (isDark) {
 | 
				
			||||||
                    this.themeConfig.terminalTheme = 'dark';
 | 
					                    this.themeConfig.terminalTheme = 'dark';
 | 
				
			||||||
                } else {
 | 
					                } else {
 | 
				
			||||||
                    this.themeConfig.terminalTheme = 'solarizedLight';
 | 
					                    this.themeConfig.terminalTheme = 'light';
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,6 +1,6 @@
 | 
				
			|||||||
<template>
 | 
					<template>
 | 
				
			||||||
    <div>
 | 
					    <div>
 | 
				
			||||||
        <el-drawer @open="initSort" :title="title" v-model="visible" :before-close="cancel" :destroy-on-close="true" :close-on-click-modal="false">
 | 
					        <el-drawer @open="initSort" :title="title" v-model="visible" :before-close="cancel" :destroy-on-close="true" :close-on-click-modal="false" size="40%">
 | 
				
			||||||
            <template #header>
 | 
					            <template #header>
 | 
				
			||||||
                <DrawerHeader :header="title" :back="cancel" />
 | 
					                <DrawerHeader :header="title" :back="cancel" />
 | 
				
			||||||
            </template>
 | 
					            </template>
 | 
				
			||||||
@@ -21,6 +21,10 @@
 | 
				
			|||||||
                    <el-input v-model.trim="form.remark" placeholder="备注" auto-complete="off" clearable></el-input>
 | 
					                    <el-input v-model.trim="form.remark" placeholder="备注" auto-complete="off" clearable></el-input>
 | 
				
			||||||
                </el-form-item>
 | 
					                </el-form-item>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                <el-form-item ref="tagSelectRef" prop="codePaths" label="关联资源">
 | 
				
			||||||
 | 
					                    <tag-tree-check height="300px" v-model="form.codePaths" :tag-type="[TagResourceTypeEnum.DbName.value, TagResourceTypeEnum.Redis.value]" />
 | 
				
			||||||
 | 
					                </el-form-item>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                <el-divider content-position="left">审批节点</el-divider>
 | 
					                <el-divider content-position="left">审批节点</el-divider>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                <el-table ref="taskTableRef" :data="tasks" row-key="taskKey" stripe style="width: 100%">
 | 
					                <el-table ref="taskTableRef" :data="tasks" row-key="taskKey" stripe style="width: 100%">
 | 
				
			||||||
@@ -70,6 +74,8 @@ import AccountSelectFormItem from '@/views/system/account/components/AccountSele
 | 
				
			|||||||
import Sortable from 'sortablejs';
 | 
					import Sortable from 'sortablejs';
 | 
				
			||||||
import { randomUuid } from '../../common/utils/string';
 | 
					import { randomUuid } from '../../common/utils/string';
 | 
				
			||||||
import { ProcdefStatus } from './enums';
 | 
					import { ProcdefStatus } from './enums';
 | 
				
			||||||
 | 
					import TagTreeCheck from '../ops/component/TagTreeCheck.vue';
 | 
				
			||||||
 | 
					import { TagResourceTypeEnum } from '@/common/commonEnum';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const props = defineProps({
 | 
					const props = defineProps({
 | 
				
			||||||
    data: {
 | 
					    data: {
 | 
				
			||||||
@@ -115,6 +121,7 @@ const state = reactive({
 | 
				
			|||||||
        remark: null,
 | 
					        remark: null,
 | 
				
			||||||
        // 流程的审批节点任务
 | 
					        // 流程的审批节点任务
 | 
				
			||||||
        tasks: '',
 | 
					        tasks: '',
 | 
				
			||||||
 | 
					        codePaths: [],
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    sortable: '' as any,
 | 
					    sortable: '' as any,
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
@@ -126,6 +133,7 @@ const { isFetching: saveBtnLoading, execute: saveFlowDefExec } = procdefApi.save
 | 
				
			|||||||
watch(props, (newValue: any) => {
 | 
					watch(props, (newValue: any) => {
 | 
				
			||||||
    if (newValue.data) {
 | 
					    if (newValue.data) {
 | 
				
			||||||
        state.form = { ...newValue.data };
 | 
					        state.form = { ...newValue.data };
 | 
				
			||||||
 | 
					        state.form.codePaths = newValue.data.tags?.map((tag: any) => tag.codePath);
 | 
				
			||||||
        const tasks = JSON.parse(state.form.tasks);
 | 
					        const tasks = JSON.parse(state.form.tasks);
 | 
				
			||||||
        tasks.forEach((t: any) => {
 | 
					        tasks.forEach((t: any) => {
 | 
				
			||||||
            t.userId = Number.parseInt(t.userId);
 | 
					            t.userId = Number.parseInt(t.userId);
 | 
				
			||||||
@@ -160,11 +168,13 @@ const deleteTask = (idx: any) => {
 | 
				
			|||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const btnOk = async () => {
 | 
					const btnOk = async () => {
 | 
				
			||||||
    formRef.value.validate(async (valid: boolean) => {
 | 
					    try {
 | 
				
			||||||
        if (!valid) {
 | 
					        await formRef.value.validate();
 | 
				
			||||||
            ElMessage.error('表单填写有误');
 | 
					    } catch (e: any) {
 | 
				
			||||||
 | 
					        ElMessage.error('请正确填写信息');
 | 
				
			||||||
        return false;
 | 
					        return false;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const checkRes = checkTasks();
 | 
					    const checkRes = checkTasks();
 | 
				
			||||||
    if (checkRes.err) {
 | 
					    if (checkRes.err) {
 | 
				
			||||||
        ElMessage.error(checkRes.err);
 | 
					        ElMessage.error(checkRes.err);
 | 
				
			||||||
@@ -178,7 +188,6 @@ const btnOk = async () => {
 | 
				
			|||||||
    //重置表单域
 | 
					    //重置表单域
 | 
				
			||||||
    formRef.value.resetFields();
 | 
					    formRef.value.resetFields();
 | 
				
			||||||
    state.form = {} as any;
 | 
					    state.form = {} as any;
 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const checkTasks = () => {
 | 
					const checkTasks = () => {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -18,6 +18,10 @@
 | 
				
			|||||||
                <el-link @click="showProcdefTasks(data)" icon="view" type="primary" :underline="false"> </el-link>
 | 
					                <el-link @click="showProcdefTasks(data)" icon="view" type="primary" :underline="false"> </el-link>
 | 
				
			||||||
            </template>
 | 
					            </template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            <template #codePaths="{ data }">
 | 
				
			||||||
 | 
					                <TagCodePath :path="data.tags?.map((tag: any) => tag.codePath)" />
 | 
				
			||||||
 | 
					            </template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            <template #action="{ data }">
 | 
					            <template #action="{ data }">
 | 
				
			||||||
                <el-button link v-if="actionBtns[perms.save]" @click="editFlowDef(data)" type="primary">编辑</el-button>
 | 
					                <el-button link v-if="actionBtns[perms.save]" @click="editFlowDef(data)" type="primary">编辑</el-button>
 | 
				
			||||||
            </template>
 | 
					            </template>
 | 
				
			||||||
@@ -42,6 +46,7 @@ import { SearchItem } from '@/components/SearchForm';
 | 
				
			|||||||
import ProcdefEdit from './ProcdefEdit.vue';
 | 
					import ProcdefEdit from './ProcdefEdit.vue';
 | 
				
			||||||
import ProcdefTasks from './components/ProcdefTasks.vue';
 | 
					import ProcdefTasks from './components/ProcdefTasks.vue';
 | 
				
			||||||
import { ProcdefStatus } from './enums';
 | 
					import { ProcdefStatus } from './enums';
 | 
				
			||||||
 | 
					import TagCodePath from '../ops/component/TagCodePath.vue';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const perms = {
 | 
					const perms = {
 | 
				
			||||||
    save: 'flow:procdef:save',
 | 
					    save: 'flow:procdef:save',
 | 
				
			||||||
@@ -55,6 +60,7 @@ const columns = [
 | 
				
			|||||||
    TableColumn.new('status', '状态').typeTag(ProcdefStatus),
 | 
					    TableColumn.new('status', '状态').typeTag(ProcdefStatus),
 | 
				
			||||||
    TableColumn.new('remark', '备注'),
 | 
					    TableColumn.new('remark', '备注'),
 | 
				
			||||||
    TableColumn.new('tasks', '审批节点').isSlot().alignCenter().setMinWidth(60),
 | 
					    TableColumn.new('tasks', '审批节点').isSlot().alignCenter().setMinWidth(60),
 | 
				
			||||||
 | 
					    TableColumn.new('codePaths', '关联资源').isSlot().setMinWidth('250px'),
 | 
				
			||||||
    TableColumn.new('creator', '创建账号'),
 | 
					    TableColumn.new('creator', '创建账号'),
 | 
				
			||||||
    TableColumn.new('createTime', '创建时间').isTime(),
 | 
					    TableColumn.new('createTime', '创建时间').isTime(),
 | 
				
			||||||
];
 | 
					];
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -17,11 +17,11 @@
 | 
				
			|||||||
                        <AccountInfo :account-id="procinst.creatorId" :username="procinst.creator" />
 | 
					                        <AccountInfo :account-id="procinst.creatorId" :username="procinst.creator" />
 | 
				
			||||||
                        <!-- {{ procinst.creator }} -->
 | 
					                        <!-- {{ procinst.creator }} -->
 | 
				
			||||||
                    </el-descriptions-item>
 | 
					                    </el-descriptions-item>
 | 
				
			||||||
                    <el-descriptions-item label="发起时间">{{ dateFormat(procinst.createTime) }}</el-descriptions-item>
 | 
					                    <el-descriptions-item label="发起时间">{{ formatDate(procinst.createTime) }}</el-descriptions-item>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    <div v-if="procinst.duration">
 | 
					                    <div v-if="procinst.duration">
 | 
				
			||||||
                        <el-descriptions-item label="持续时间">{{ formatTime(procinst.duration) }}</el-descriptions-item>
 | 
					                        <el-descriptions-item label="持续时间">{{ formatTime(procinst.duration) }}</el-descriptions-item>
 | 
				
			||||||
                        <el-descriptions-item label="结束时间">{{ dateFormat(procinst.endTime) }}</el-descriptions-item>
 | 
					                        <el-descriptions-item label="结束时间">{{ formatDate(procinst.endTime) }}</el-descriptions-item>
 | 
				
			||||||
                    </div>
 | 
					                    </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    <el-descriptions-item label="流程状态">
 | 
					                    <el-descriptions-item label="流程状态">
 | 
				
			||||||
@@ -86,11 +86,11 @@ import { procinstApi } from './api';
 | 
				
			|||||||
import { ElMessage } from 'element-plus';
 | 
					import { ElMessage } from 'element-plus';
 | 
				
			||||||
import DrawerHeader from '@/components/drawer-header/DrawerHeader.vue';
 | 
					import DrawerHeader from '@/components/drawer-header/DrawerHeader.vue';
 | 
				
			||||||
import { FlowBizType, ProcinstBizStatus, ProcinstTaskStatus, ProcinstStatus } from './enums';
 | 
					import { FlowBizType, ProcinstBizStatus, ProcinstTaskStatus, ProcinstStatus } from './enums';
 | 
				
			||||||
import { dateFormat } from '@/common/utils/date';
 | 
					 | 
				
			||||||
import ProcdefTasks from './components/ProcdefTasks.vue';
 | 
					import ProcdefTasks from './components/ProcdefTasks.vue';
 | 
				
			||||||
import { formatTime } from '@/common/utils/format';
 | 
					import { formatTime } from '@/common/utils/format';
 | 
				
			||||||
import EnumTag from '@/components/enumtag/EnumTag.vue';
 | 
					import EnumTag from '@/components/enumtag/EnumTag.vue';
 | 
				
			||||||
import AccountInfo from '@/views/system/account/components/AccountInfo.vue';
 | 
					import AccountInfo from '@/views/system/account/components/AccountInfo.vue';
 | 
				
			||||||
 | 
					import { formatDate } from '@/common/utils/format';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const DbSqlExecBiz = defineAsyncComponent(() => import('./flowbiz/DbSqlExecBiz.vue'));
 | 
					const DbSqlExecBiz = defineAsyncComponent(() => import('./flowbiz/DbSqlExecBiz.vue'));
 | 
				
			||||||
const RedisRunWriteCmdBiz = defineAsyncComponent(() => import('./flowbiz/RedisRunWriteCmdBiz.vue'));
 | 
					const RedisRunWriteCmdBiz = defineAsyncComponent(() => import('./flowbiz/RedisRunWriteCmdBiz.vue'));
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -2,7 +2,7 @@ import Api from '@/common/Api';
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
export const procdefApi = {
 | 
					export const procdefApi = {
 | 
				
			||||||
    list: Api.newGet('/flow/procdefs'),
 | 
					    list: Api.newGet('/flow/procdefs'),
 | 
				
			||||||
    getByKey: Api.newGet('/flow/procdefs/{key}'),
 | 
					    getByResource: Api.newGet('/flow/procdefs/{resourceType}/{resourceCode}'),
 | 
				
			||||||
    save: Api.newPost('/flow/procdefs'),
 | 
					    save: Api.newPost('/flow/procdefs'),
 | 
				
			||||||
    del: Api.newDelete('/flow/procdefs/{id}'),
 | 
					    del: Api.newDelete('/flow/procdefs/{id}'),
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -3,7 +3,7 @@
 | 
				
			|||||||
        <el-step v-for="task in tasksArr" :status="getStepStatus(task)" :title="task.name" :key="task.taskKey">
 | 
					        <el-step v-for="task in tasksArr" :status="getStepStatus(task)" :title="task.name" :key="task.taskKey">
 | 
				
			||||||
            <template #description>
 | 
					            <template #description>
 | 
				
			||||||
                <div>{{ `${task.accountUsername}(${task.accountName})` }}</div>
 | 
					                <div>{{ `${task.accountUsername}(${task.accountName})` }}</div>
 | 
				
			||||||
                <div v-if="task.completeTime">{{ `${dateFormat(task.completeTime)}` }}</div>
 | 
					                <div v-if="task.completeTime">{{ `${formatDate(task.completeTime)}` }}</div>
 | 
				
			||||||
                <div v-if="task.remark">{{ task.remark }}</div>
 | 
					                <div v-if="task.remark">{{ task.remark }}</div>
 | 
				
			||||||
            </template>
 | 
					            </template>
 | 
				
			||||||
        </el-step>
 | 
					        </el-step>
 | 
				
			||||||
@@ -14,8 +14,7 @@
 | 
				
			|||||||
import { toRefs, reactive, watch, onMounted } from 'vue';
 | 
					import { toRefs, reactive, watch, onMounted } from 'vue';
 | 
				
			||||||
import { accountApi } from '../../system/api';
 | 
					import { accountApi } from '../../system/api';
 | 
				
			||||||
import { ProcinstTaskStatus } from '../enums';
 | 
					import { ProcinstTaskStatus } from '../enums';
 | 
				
			||||||
import { dateFormat } from '@/common/utils/date';
 | 
					import { formatDate } from '@/common/utils/format';
 | 
				
			||||||
import { procdefApi } from '../api';
 | 
					 | 
				
			||||||
import { ElSteps, ElStep } from 'element-plus';
 | 
					import { ElSteps, ElStep } from 'element-plus';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const props = defineProps({
 | 
					const props = defineProps({
 | 
				
			||||||
@@ -23,8 +22,8 @@ const props = defineProps({
 | 
				
			|||||||
    tasks: {
 | 
					    tasks: {
 | 
				
			||||||
        type: [String, Object],
 | 
					        type: [String, Object],
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    procdefKey: {
 | 
					    procdef: {
 | 
				
			||||||
        type: String,
 | 
					        type: [Object],
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    // 流程实例任务列表
 | 
					    // 流程实例任务列表
 | 
				
			||||||
    procinstTasks: {
 | 
					    procinstTasks: {
 | 
				
			||||||
@@ -54,7 +53,7 @@ watch(
 | 
				
			|||||||
);
 | 
					);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
watch(
 | 
					watch(
 | 
				
			||||||
    () => props.procdefKey,
 | 
					    () => props.procdef,
 | 
				
			||||||
    async (newValue: any) => {
 | 
					    async (newValue: any) => {
 | 
				
			||||||
        if (newValue) {
 | 
					        if (newValue) {
 | 
				
			||||||
            parseTasksByKey(newValue);
 | 
					            parseTasksByKey(newValue);
 | 
				
			||||||
@@ -63,15 +62,14 @@ watch(
 | 
				
			|||||||
);
 | 
					);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
onMounted(() => {
 | 
					onMounted(() => {
 | 
				
			||||||
    if (props.procdefKey) {
 | 
					    if (props.procdef) {
 | 
				
			||||||
        parseTasksByKey(props.procdefKey);
 | 
					        parseTasksByKey(props.procdef);
 | 
				
			||||||
        return;
 | 
					        return;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    parseTasks(props.tasks);
 | 
					    parseTasks(props.tasks);
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const parseTasksByKey = async (key: string) => {
 | 
					const parseTasksByKey = async (procdef: any) => {
 | 
				
			||||||
    const procdef = await procdefApi.getByKey.request({ key });
 | 
					 | 
				
			||||||
    parseTasks(procdef.tasks);
 | 
					    parseTasks(procdef.tasks);
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,16 +1,13 @@
 | 
				
			|||||||
<template>
 | 
					<template>
 | 
				
			||||||
    <div>
 | 
					    <div>
 | 
				
			||||||
        <el-descriptions :column="3" border>
 | 
					        <el-descriptions :column="3" border>
 | 
				
			||||||
            <el-descriptions-item :span="2" label="名称">{{ db?.name }}</el-descriptions-item>
 | 
					            <el-descriptions-item :span="3" label="标签"><TagCodePath :path="db.codePaths" /></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?.name }}</el-descriptions-item>
 | 
				
			||||||
            <el-descriptions-item :span="1" label="主机">{{ `${db?.host}:${db?.port}` }}</el-descriptions-item>
 | 
					            <el-descriptions-item :span="1" label="主机">{{ `${db?.host}:${db?.port}` }}</el-descriptions-item>
 | 
				
			||||||
            <el-descriptions-item :span="1" label="类型">
 | 
					            <el-descriptions-item :span="1" label="类型">
 | 
				
			||||||
                <SvgIcon :name="getDbDialect(db?.type).getInfo().icon" :size="20" />{{ db?.type }}
 | 
					                <SvgIcon :name="getDbDialect(db?.type).getInfo().icon" :size="20" />{{ db?.type }}
 | 
				
			||||||
            </el-descriptions-item>
 | 
					            </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.db }}</el-descriptions-item>
 | 
				
			||||||
            <el-descriptions-item label="表">
 | 
					            <el-descriptions-item label="表">
 | 
				
			||||||
@@ -33,7 +30,9 @@ import { dbApi } from '@/views/ops/db/api';
 | 
				
			|||||||
import { DbSqlExecTypeEnum } from '@/views/ops/db/enums';
 | 
					import { DbSqlExecTypeEnum } from '@/views/ops/db/enums';
 | 
				
			||||||
import MonacoEditor from '@/components/monaco/MonacoEditor.vue';
 | 
					import MonacoEditor from '@/components/monaco/MonacoEditor.vue';
 | 
				
			||||||
import { getDbDialect } from '@/views/ops/db/dialect';
 | 
					import { getDbDialect } from '@/views/ops/db/dialect';
 | 
				
			||||||
import ResourceTags from '@/views/ops/component/ResourceTags.vue';
 | 
					import { tagApi } from '@/views/ops/tag/api';
 | 
				
			||||||
 | 
					import { TagResourceTypeEnum } from '@/common/commonEnum';
 | 
				
			||||||
 | 
					import TagCodePath from '@/views/ops/component/TagCodePath.vue';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const props = defineProps({
 | 
					const props = defineProps({
 | 
				
			||||||
    // 业务key
 | 
					    // 业务key
 | 
				
			||||||
@@ -74,6 +73,10 @@ const getDbSqlExec = async (bizKey: string) => {
 | 
				
			|||||||
    state.sqlExec = res.list?.[0];
 | 
					    state.sqlExec = res.list?.[0];
 | 
				
			||||||
    const dbRes = await dbApi.dbs.request({ id: state.sqlExec.dbId });
 | 
					    const dbRes = await dbApi.dbs.request({ id: state.sqlExec.dbId });
 | 
				
			||||||
    state.db = dbRes.list?.[0];
 | 
					    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>
 | 
					</script>
 | 
				
			||||||
<style lang="scss"></style>
 | 
					<style lang="scss"></style>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,11 +1,10 @@
 | 
				
			|||||||
<template>
 | 
					<template>
 | 
				
			||||||
    <div>
 | 
					    <div>
 | 
				
			||||||
        <el-descriptions :column="3" border>
 | 
					        <el-descriptions :column="3" border>
 | 
				
			||||||
            <el-descriptions-item :span="1" label="名称">{{ redis?.name }}</el-descriptions-item>
 | 
					            <el-descriptions-item :span="3" label="标签"><TagCodePath :path="redis.codePaths" /></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="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="主机">{{ `${redis?.host}` }}</el-descriptions-item>
 | 
				
			||||||
            <el-descriptions-item :span="1" label="库">{{ state.db }}</el-descriptions-item>
 | 
					            <el-descriptions-item :span="1" label="库">{{ state.db }}</el-descriptions-item>
 | 
				
			||||||
@@ -22,8 +21,10 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
<script lang="ts" setup>
 | 
					<script lang="ts" setup>
 | 
				
			||||||
import { toRefs, reactive, watch, onMounted } from 'vue';
 | 
					import { toRefs, reactive, watch, onMounted } from 'vue';
 | 
				
			||||||
import ResourceTags from '@/views/ops/component/ResourceTags.vue';
 | 
					 | 
				
			||||||
import { redisApi } from '@/views/ops/redis/api';
 | 
					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({
 | 
					const props = defineProps({
 | 
				
			||||||
    // 业务表单
 | 
					    // 业务表单
 | 
				
			||||||
@@ -75,6 +76,10 @@ const parseRunCmdForm = async (bizForm: string) => {
 | 
				
			|||||||
        return;
 | 
					        return;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    state.redis = res.list?.[0];
 | 
					    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>
 | 
					</script>
 | 
				
			||||||
<style lang="scss"></style>
 | 
					<style lang="scss"></style>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,137 +1,541 @@
 | 
				
			|||||||
<template>
 | 
					<template>
 | 
				
			||||||
    <div class="home-container">
 | 
					    <div class="home-container personal">
 | 
				
			||||||
        <el-row :gutter="15">
 | 
					        <el-row :gutter="15">
 | 
				
			||||||
            <el-col :sm="6" class="mb15">
 | 
					            <!-- 个人信息 -->
 | 
				
			||||||
                <div @click="toPage({ id: 'personal' })" class="home-card-item home-card-first">
 | 
					            <el-col :xs="24" :sm="16">
 | 
				
			||||||
                    <div class="flex-margin flex">
 | 
					                <el-card shadow="hover" header="个人信息">
 | 
				
			||||||
 | 
					                    <div class="personal-user">
 | 
				
			||||||
 | 
					                        <div class="personal-user-left">
 | 
				
			||||||
 | 
					                            <el-upload class="h100 personal-user-left-upload" action="" multiple :limit="1">
 | 
				
			||||||
                                <img :src="userInfo.photo" />
 | 
					                                <img :src="userInfo.photo" />
 | 
				
			||||||
                        <div class="home-card-first-right ml15">
 | 
					                            </el-upload>
 | 
				
			||||||
                            <div class="flex-margin">
 | 
					 | 
				
			||||||
                                <div class="home-card-first-right-title">{{ `${currentTime}, ${userInfo.username}` }}</div>
 | 
					 | 
				
			||||||
                            </div>
 | 
					 | 
				
			||||||
                        </div>
 | 
					 | 
				
			||||||
                    </div>
 | 
					 | 
				
			||||||
                        </div>
 | 
					                        </div>
 | 
				
			||||||
 | 
					                        <div class="personal-user-right">
 | 
				
			||||||
 | 
					                            <el-row>
 | 
				
			||||||
 | 
					                                <el-col :span="24" class="personal-title mb18"
 | 
				
			||||||
 | 
					                                    >{{ currentTime }},{{ userInfo.name }},生活变的再糟糕,也不妨碍我变得更好!
 | 
				
			||||||
                                </el-col>
 | 
					                                </el-col>
 | 
				
			||||||
            <el-col :sm="3" class="mb15" v-for="(v, k) in topCardItemList as any" :key="k">
 | 
					                                <el-col :span="24">
 | 
				
			||||||
                <div @click="toPage(v)" class="home-card-item home-card-item-box" :style="{ background: v.color }">
 | 
					                                    <el-row>
 | 
				
			||||||
                    <div class="home-card-item-flex">
 | 
					                                        <el-col :xs="24" :sm="12" class="personal-item mb6">
 | 
				
			||||||
                        <div class="home-card-item-title pb3">{{ v.title }}</div>
 | 
					                                            <div class="personal-item-label">用户名:</div>
 | 
				
			||||||
                        <div class="home-card-item-title-num pb6" :id="v.id"></div>
 | 
					                                            <div class="personal-item-value">{{ userInfo.username }}</div>
 | 
				
			||||||
                    </div>
 | 
					                                        </el-col>
 | 
				
			||||||
                    <i :class="v.icon" :style="{ color: v.iconColor }"></i>
 | 
					                                        <el-col :xs="24" :sm="12" class="personal-item mb6">
 | 
				
			||||||
                </div>
 | 
					                                            <div class="personal-item-label">角色:</div>
 | 
				
			||||||
 | 
					                                            <div class="personal-item-value">{{ roleInfo }}</div>
 | 
				
			||||||
                                        </el-col>
 | 
					                                        </el-col>
 | 
				
			||||||
                                    </el-row>
 | 
					                                    </el-row>
 | 
				
			||||||
 | 
					                                </el-col>
 | 
				
			||||||
 | 
					                                <el-col :span="24">
 | 
				
			||||||
 | 
					                                    <el-row>
 | 
				
			||||||
 | 
					                                        <el-col :xs="24" :sm="12" class="personal-item mb6">
 | 
				
			||||||
 | 
					                                            <div class="personal-item-label">上次登录IP:</div>
 | 
				
			||||||
 | 
					                                            <div class="personal-item-value">{{ userInfo.lastLoginIp }}</div>
 | 
				
			||||||
 | 
					                                        </el-col>
 | 
				
			||||||
 | 
					                                        <el-col :xs="24" :sm="12" class="personal-item mb6">
 | 
				
			||||||
 | 
					                                            <div class="personal-item-label">上次登录时间:</div>
 | 
				
			||||||
 | 
					                                            <div class="personal-item-value">{{ formatDate(userInfo.lastLoginTime) }}</div>
 | 
				
			||||||
 | 
					                                        </el-col>
 | 
				
			||||||
 | 
					                                    </el-row>
 | 
				
			||||||
 | 
					                                </el-col>
 | 
				
			||||||
 | 
					                            </el-row>
 | 
				
			||||||
 | 
					                        </div>
 | 
				
			||||||
 | 
					                    </div>
 | 
				
			||||||
 | 
					                </el-card>
 | 
				
			||||||
 | 
					            </el-col>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            <!-- 消息通知 -->
 | 
				
			||||||
 | 
					            <el-col :xs="24" :sm="8" class="pl15 personal-info">
 | 
				
			||||||
 | 
					                <el-card shadow="hover">
 | 
				
			||||||
 | 
					                    <template #header>
 | 
				
			||||||
 | 
					                        <span>消息通知</span>
 | 
				
			||||||
 | 
					                        <span @click="showMsgs" class="personal-info-more">更多</span>
 | 
				
			||||||
 | 
					                    </template>
 | 
				
			||||||
 | 
					                    <div class="personal-info-box">
 | 
				
			||||||
 | 
					                        <ul class="personal-info-ul">
 | 
				
			||||||
 | 
					                            <li v-for="(v, k) in state.msgs as any" :key="k" class="personal-info-li">
 | 
				
			||||||
 | 
					                                <a class="personal-info-li-title">{{ `[${getMsgTypeDesc(v.type)}] ${v.msg}` }}</a>
 | 
				
			||||||
 | 
					                            </li>
 | 
				
			||||||
 | 
					                        </ul>
 | 
				
			||||||
 | 
					                    </div>
 | 
				
			||||||
 | 
					                </el-card>
 | 
				
			||||||
 | 
					            </el-col>
 | 
				
			||||||
 | 
					        </el-row>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        <el-row :gutter="20" class="mt20 resource-info">
 | 
				
			||||||
 | 
					            <el-col :sm="12">
 | 
				
			||||||
 | 
					                <el-card shadow="hover">
 | 
				
			||||||
 | 
					                    <template #header>
 | 
				
			||||||
 | 
					                        <el-row justify="center">
 | 
				
			||||||
 | 
					                            <div class="resource-num pointer-icon" @click="toPage('machine')">
 | 
				
			||||||
 | 
					                                <SvgIcon
 | 
				
			||||||
 | 
					                                    class="mb5 mr5"
 | 
				
			||||||
 | 
					                                    :size="28"
 | 
				
			||||||
 | 
					                                    :name="TagResourceTypeEnum.Machine.extra.icon"
 | 
				
			||||||
 | 
					                                    :color="TagResourceTypeEnum.Machine.extra.iconColor"
 | 
				
			||||||
 | 
					                                />
 | 
				
			||||||
 | 
					                                <span class="">{{ state.machine.num }}</span>
 | 
				
			||||||
 | 
					                            </div>
 | 
				
			||||||
 | 
					                        </el-row>
 | 
				
			||||||
 | 
					                    </template>
 | 
				
			||||||
 | 
					                    <el-row>
 | 
				
			||||||
 | 
					                        <el-col :sm="24">
 | 
				
			||||||
 | 
					                            <el-table :data="state.machine.opLogs" :height="state.resourceOpTableHeight" stripe size="small" empty-text="暂无操作记录">
 | 
				
			||||||
 | 
					                                <el-table-column prop="createTime" show-overflow-tooltip width="135">
 | 
				
			||||||
 | 
					                                    <template #default="scope">
 | 
				
			||||||
 | 
					                                        {{ formatDate(scope.row.createTime) }}
 | 
				
			||||||
 | 
					                                    </template>
 | 
				
			||||||
 | 
					                                </el-table-column>
 | 
				
			||||||
 | 
					                                <el-table-column prop="codePath" min-width="400" show-overflow-tooltip>
 | 
				
			||||||
 | 
					                                    <template #default="scope">
 | 
				
			||||||
 | 
					                                        <TagCodePath :path="scope.row.codePath" />
 | 
				
			||||||
 | 
					                                    </template>
 | 
				
			||||||
 | 
					                                </el-table-column>
 | 
				
			||||||
 | 
					                                <el-table-column width="30">
 | 
				
			||||||
 | 
					                                    <template #default="scope">
 | 
				
			||||||
 | 
					                                        <el-link @click="toPage('machine', scope.row.codePath)" type="primary" icon="Position"></el-link>
 | 
				
			||||||
 | 
					                                    </template>
 | 
				
			||||||
 | 
					                                </el-table-column>
 | 
				
			||||||
 | 
					                            </el-table>
 | 
				
			||||||
 | 
					                        </el-col>
 | 
				
			||||||
 | 
					                    </el-row>
 | 
				
			||||||
 | 
					                </el-card>
 | 
				
			||||||
 | 
					            </el-col>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            <el-col :sm="12">
 | 
				
			||||||
 | 
					                <el-card shadow="hover">
 | 
				
			||||||
 | 
					                    <template #header>
 | 
				
			||||||
 | 
					                        <el-row justify="center">
 | 
				
			||||||
 | 
					                            <div class="resource-num pointer-icon" @click="toPage('db')">
 | 
				
			||||||
 | 
					                                <SvgIcon class="mb5 mr5" :size="28" :name="TagResourceTypeEnum.Db.extra.icon" :color="TagResourceTypeEnum.Db.extra.iconColor" />
 | 
				
			||||||
 | 
					                                <span class="">{{ state.db.num }}</span>
 | 
				
			||||||
 | 
					                            </div>
 | 
				
			||||||
 | 
					                        </el-row>
 | 
				
			||||||
 | 
					                    </template>
 | 
				
			||||||
 | 
					                    <el-row>
 | 
				
			||||||
 | 
					                        <el-col :sm="24">
 | 
				
			||||||
 | 
					                            <el-table :data="state.db.opLogs" :height="state.resourceOpTableHeight" stripe size="small" empty-text="暂无操作记录">
 | 
				
			||||||
 | 
					                                <el-table-column prop="createTime" show-overflow-tooltip min-width="135">
 | 
				
			||||||
 | 
					                                    <template #default="scope">
 | 
				
			||||||
 | 
					                                        {{ formatDate(scope.row.createTime) }}
 | 
				
			||||||
 | 
					                                    </template>
 | 
				
			||||||
 | 
					                                </el-table-column>
 | 
				
			||||||
 | 
					                                <el-table-column prop="codePath" min-width="380" show-overflow-tooltip>
 | 
				
			||||||
 | 
					                                    <template #default="scope">
 | 
				
			||||||
 | 
					                                        <TagCodePath :path="scope.row.codePath" />
 | 
				
			||||||
 | 
					                                    </template>
 | 
				
			||||||
 | 
					                                </el-table-column>
 | 
				
			||||||
 | 
					                                <el-table-column width="30">
 | 
				
			||||||
 | 
					                                    <template #default="scope">
 | 
				
			||||||
 | 
					                                        <el-link @click="toPage('db', scope.row.codePath)" type="primary" icon="Position"></el-link>
 | 
				
			||||||
 | 
					                                    </template>
 | 
				
			||||||
 | 
					                                </el-table-column>
 | 
				
			||||||
 | 
					                            </el-table>
 | 
				
			||||||
 | 
					                        </el-col>
 | 
				
			||||||
 | 
					                    </el-row>
 | 
				
			||||||
 | 
					                </el-card>
 | 
				
			||||||
 | 
					            </el-col>
 | 
				
			||||||
 | 
					        </el-row>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        <el-row :gutter="20" class="mt20 resource-info">
 | 
				
			||||||
 | 
					            <el-col :sm="12">
 | 
				
			||||||
 | 
					                <el-card shadow="hover">
 | 
				
			||||||
 | 
					                    <template #header>
 | 
				
			||||||
 | 
					                        <el-row justify="center">
 | 
				
			||||||
 | 
					                            <div class="resource-num pointer-icon" @click="toPage('redis')">
 | 
				
			||||||
 | 
					                                <SvgIcon
 | 
				
			||||||
 | 
					                                    class="mb5 mr5"
 | 
				
			||||||
 | 
					                                    :size="28"
 | 
				
			||||||
 | 
					                                    :name="TagResourceTypeEnum.Redis.extra.icon"
 | 
				
			||||||
 | 
					                                    :color="TagResourceTypeEnum.Redis.extra.iconColor"
 | 
				
			||||||
 | 
					                                />
 | 
				
			||||||
 | 
					                                <span class="">{{ state.redis.num }}</span>
 | 
				
			||||||
 | 
					                            </div>
 | 
				
			||||||
 | 
					                        </el-row>
 | 
				
			||||||
 | 
					                    </template>
 | 
				
			||||||
 | 
					                    <el-row>
 | 
				
			||||||
 | 
					                        <el-col :sm="24">
 | 
				
			||||||
 | 
					                            <el-table :data="state.redis.opLogs" :height="state.resourceOpTableHeight" stripe size="small" empty-text="暂无操作记录">
 | 
				
			||||||
 | 
					                                <el-table-column prop="createTime" show-overflow-tooltip min-width="135">
 | 
				
			||||||
 | 
					                                    <template #default="scope">
 | 
				
			||||||
 | 
					                                        {{ formatDate(scope.row.createTime) }}
 | 
				
			||||||
 | 
					                                    </template>
 | 
				
			||||||
 | 
					                                </el-table-column>
 | 
				
			||||||
 | 
					                                <el-table-column prop="codePath" min-width="380" show-overflow-tooltip>
 | 
				
			||||||
 | 
					                                    <template #default="scope">
 | 
				
			||||||
 | 
					                                        <TagCodePath :path="scope.row.codePath" />
 | 
				
			||||||
 | 
					                                    </template>
 | 
				
			||||||
 | 
					                                </el-table-column>
 | 
				
			||||||
 | 
					                                <el-table-column width="30">
 | 
				
			||||||
 | 
					                                    <template #default="scope">
 | 
				
			||||||
 | 
					                                        <el-link @click="toPage('redis', scope.row.codePath)" type="primary" icon="Position"></el-link>
 | 
				
			||||||
 | 
					                                    </template>
 | 
				
			||||||
 | 
					                                </el-table-column>
 | 
				
			||||||
 | 
					                            </el-table>
 | 
				
			||||||
 | 
					                        </el-col>
 | 
				
			||||||
 | 
					                    </el-row>
 | 
				
			||||||
 | 
					                </el-card>
 | 
				
			||||||
 | 
					            </el-col>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            <el-col :sm="12">
 | 
				
			||||||
 | 
					                <el-card shadow="hover">
 | 
				
			||||||
 | 
					                    <template #header>
 | 
				
			||||||
 | 
					                        <el-row justify="center">
 | 
				
			||||||
 | 
					                            <div class="resource-num pointer-icon" @click="toPage('mongo')">
 | 
				
			||||||
 | 
					                                <SvgIcon
 | 
				
			||||||
 | 
					                                    class="mb5 mr5"
 | 
				
			||||||
 | 
					                                    :size="28"
 | 
				
			||||||
 | 
					                                    :name="TagResourceTypeEnum.Mongo.extra.icon"
 | 
				
			||||||
 | 
					                                    :color="TagResourceTypeEnum.Mongo.extra.iconColor"
 | 
				
			||||||
 | 
					                                />
 | 
				
			||||||
 | 
					                                <span class="">{{ state.mongo.num }}</span>
 | 
				
			||||||
 | 
					                            </div>
 | 
				
			||||||
 | 
					                        </el-row>
 | 
				
			||||||
 | 
					                    </template>
 | 
				
			||||||
 | 
					                    <el-row>
 | 
				
			||||||
 | 
					                        <el-col :sm="24">
 | 
				
			||||||
 | 
					                            <el-table :data="state.mongo.opLogs" :height="state.resourceOpTableHeight" stripe size="small" empty-text="暂无操作记录">
 | 
				
			||||||
 | 
					                                <el-table-column prop="createTime" show-overflow-tooltip min-width="135">
 | 
				
			||||||
 | 
					                                    <template #default="scope">
 | 
				
			||||||
 | 
					                                        {{ formatDate(scope.row.createTime) }}
 | 
				
			||||||
 | 
					                                    </template>
 | 
				
			||||||
 | 
					                                </el-table-column>
 | 
				
			||||||
 | 
					                                <el-table-column prop="codePath" min-width="380" show-overflow-tooltip>
 | 
				
			||||||
 | 
					                                    <template #default="scope">
 | 
				
			||||||
 | 
					                                        <TagCodePath :path="scope.row.codePath" />
 | 
				
			||||||
 | 
					                                    </template>
 | 
				
			||||||
 | 
					                                </el-table-column>
 | 
				
			||||||
 | 
					                                <el-table-column width="30">
 | 
				
			||||||
 | 
					                                    <template #default="scope">
 | 
				
			||||||
 | 
					                                        <el-link @click="toPage('mongo', scope.row.codePath)" type="primary" icon="Position"></el-link>
 | 
				
			||||||
 | 
					                                    </template>
 | 
				
			||||||
 | 
					                                </el-table-column>
 | 
				
			||||||
 | 
					                            </el-table>
 | 
				
			||||||
 | 
					                        </el-col>
 | 
				
			||||||
 | 
					                    </el-row>
 | 
				
			||||||
 | 
					                </el-card>
 | 
				
			||||||
 | 
					            </el-col>
 | 
				
			||||||
 | 
					        </el-row>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        <el-dialog width="900px" title="消息" v-model="msgDialog.visible">
 | 
				
			||||||
 | 
					            <el-table border :data="msgDialog.msgs.list" size="small">
 | 
				
			||||||
 | 
					                <el-table-column property="type" label="类型" width="60">
 | 
				
			||||||
 | 
					                    <template #default="scope">
 | 
				
			||||||
 | 
					                        {{ getMsgTypeDesc(scope.row.type) }}
 | 
				
			||||||
 | 
					                    </template>
 | 
				
			||||||
 | 
					                </el-table-column>
 | 
				
			||||||
 | 
					                <el-table-column property="msg" label="消息"></el-table-column>
 | 
				
			||||||
 | 
					                <el-table-column property="createTime" label="时间" width="150">
 | 
				
			||||||
 | 
					                    <template #default="scope">
 | 
				
			||||||
 | 
					                        {{ formatDate(scope.row.createTime) }}
 | 
				
			||||||
 | 
					                    </template>
 | 
				
			||||||
 | 
					                </el-table-column>
 | 
				
			||||||
 | 
					            </el-table>
 | 
				
			||||||
 | 
					            <el-row type="flex" class="mt5" justify="center">
 | 
				
			||||||
 | 
					                <el-pagination
 | 
				
			||||||
 | 
					                    small
 | 
				
			||||||
 | 
					                    @current-change="searchMsg"
 | 
				
			||||||
 | 
					                    style="text-align: center"
 | 
				
			||||||
 | 
					                    background
 | 
				
			||||||
 | 
					                    layout="prev, pager, next, total, jumper"
 | 
				
			||||||
 | 
					                    :total="msgDialog.msgs.total"
 | 
				
			||||||
 | 
					                    v-model:current-page="msgDialog.query.pageNum"
 | 
				
			||||||
 | 
					                    :page-size="msgDialog.query.pageSize"
 | 
				
			||||||
 | 
					                />
 | 
				
			||||||
 | 
					            </el-row>
 | 
				
			||||||
 | 
					        </el-dialog>
 | 
				
			||||||
    </div>
 | 
					    </div>
 | 
				
			||||||
</template>
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<script lang="ts" setup>
 | 
					<script lang="ts" setup>
 | 
				
			||||||
import { toRefs, reactive, onMounted, nextTick, computed } from 'vue';
 | 
					import { toRefs, reactive, onMounted, computed } from 'vue';
 | 
				
			||||||
// import * as echarts from 'echarts';
 | 
					// import * as echarts from 'echarts';
 | 
				
			||||||
import { CountUp } from 'countup.js';
 | 
					 | 
				
			||||||
import { formatAxis } from '@/common/utils/format';
 | 
					import { formatAxis } from '@/common/utils/format';
 | 
				
			||||||
import { indexApi } from './api';
 | 
					import { indexApi } from './api';
 | 
				
			||||||
import { useRouter } from 'vue-router';
 | 
					import { useRouter } from 'vue-router';
 | 
				
			||||||
import { storeToRefs } from 'pinia';
 | 
					import { storeToRefs } from 'pinia';
 | 
				
			||||||
import { useUserInfo } from '@/store/userInfo';
 | 
					import { useUserInfo } from '@/store/userInfo';
 | 
				
			||||||
 | 
					import { personApi } from '../personal/api';
 | 
				
			||||||
 | 
					import { 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';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const router = useRouter();
 | 
					const router = useRouter();
 | 
				
			||||||
const { userInfo } = storeToRefs(useUserInfo());
 | 
					const { userInfo } = storeToRefs(useUserInfo());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const state = reactive({
 | 
					const state = reactive({
 | 
				
			||||||
    topCardItemList: [
 | 
					    accountInfo: {
 | 
				
			||||||
        {
 | 
					        roles: [],
 | 
				
			||||||
            title: 'Linux机器',
 | 
					 | 
				
			||||||
            id: 'machineNum',
 | 
					 | 
				
			||||||
            color: '#F95959',
 | 
					 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
        {
 | 
					    msgs: [],
 | 
				
			||||||
            title: '数据库',
 | 
					    msgDialog: {
 | 
				
			||||||
            id: 'dbNum',
 | 
					        visible: false,
 | 
				
			||||||
            color: '#8595F4',
 | 
					        query: {
 | 
				
			||||||
 | 
					            pageSize: 10,
 | 
				
			||||||
 | 
					            pageNum: 1,
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
        {
 | 
					        msgs: {
 | 
				
			||||||
            title: 'redis',
 | 
					            list: [],
 | 
				
			||||||
            id: 'redisNum',
 | 
					            total: null,
 | 
				
			||||||
            color: '#1abc9c',
 | 
					 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            title: 'Mongo',
 | 
					 | 
				
			||||||
            id: 'mongoNum',
 | 
					 | 
				
			||||||
            color: '#FEBB50',
 | 
					 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    ],
 | 
					    resourceOpTableHeight: 180,
 | 
				
			||||||
 | 
					    defaultLogSize: 5,
 | 
				
			||||||
 | 
					    machine: {
 | 
				
			||||||
 | 
					        num: 0,
 | 
				
			||||||
 | 
					        opLogs: [],
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    db: {
 | 
				
			||||||
 | 
					        num: 0,
 | 
				
			||||||
 | 
					        opLogs: [],
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    redis: {
 | 
				
			||||||
 | 
					        num: 0,
 | 
				
			||||||
 | 
					        opLogs: [],
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    mongo: {
 | 
				
			||||||
 | 
					        num: 0,
 | 
				
			||||||
 | 
					        opLogs: [],
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const { topCardItemList } = toRefs(state);
 | 
					const { msgDialog } = toRefs(state);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const roleInfo = computed(() => {
 | 
				
			||||||
 | 
					    if (state.accountInfo.roles.length == 0) {
 | 
				
			||||||
 | 
					        return '';
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    return state.accountInfo.roles.map((val: any) => val.roleName).join('、');
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// 当前时间提示语
 | 
					// 当前时间提示语
 | 
				
			||||||
const currentTime = computed(() => {
 | 
					const currentTime = computed(() => {
 | 
				
			||||||
    return formatAxis(new Date());
 | 
					    return formatAxis(new Date());
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// 初始化数字滚动
 | 
					// 页面加载时
 | 
				
			||||||
const initNumCountUp = async () => {
 | 
					onMounted(() => {
 | 
				
			||||||
    indexApi.machineDashbord.request().then((res: any) => {
 | 
					    initData();
 | 
				
			||||||
        nextTick(() => {
 | 
					    getAccountInfo();
 | 
				
			||||||
            new CountUp('machineNum', res.machineNum).start();
 | 
					
 | 
				
			||||||
 | 
					    getMsgs().then((res) => {
 | 
				
			||||||
 | 
					        state.msgs = res.list;
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const showMsgs = async () => {
 | 
				
			||||||
 | 
					    state.msgDialog.query.pageNum = 1;
 | 
				
			||||||
 | 
					    searchMsg();
 | 
				
			||||||
 | 
					    state.msgDialog.visible = true;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const searchMsg = async () => {
 | 
				
			||||||
 | 
					    state.msgDialog.msgs = await getMsgs();
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const getMsgTypeDesc = (type: number) => {
 | 
				
			||||||
 | 
					    if (type == 1) {
 | 
				
			||||||
 | 
					        return '登录';
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    if (type == 2) {
 | 
				
			||||||
 | 
					        return '通知';
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const getAccountInfo = async () => {
 | 
				
			||||||
 | 
					    state.accountInfo = await personApi.accountInfo.request();
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const getMsgs = async () => {
 | 
				
			||||||
 | 
					    return await personApi.getMsgs.request(state.msgDialog.query);
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// 初始化数字滚动
 | 
				
			||||||
 | 
					const initData = async () => {
 | 
				
			||||||
 | 
					    resourceOpLogApi.getAccountResourceOpLogs
 | 
				
			||||||
 | 
					        .request({ resourceType: TagResourceTypeEnum.MachineAuthCert.value, pageSize: state.defaultLogSize })
 | 
				
			||||||
 | 
					        .then((res: any) => {
 | 
				
			||||||
 | 
					            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.Redis.value, pageSize: state.defaultLogSize }).then((res: any) => {
 | 
				
			||||||
 | 
					        state.redis.opLogs = res.list;
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    resourceOpLogApi.getAccountResourceOpLogs.request({ resourceType: TagResourceTypeEnum.Mongo.value, pageSize: state.defaultLogSize }).then((res: any) => {
 | 
				
			||||||
 | 
					        state.mongo.opLogs = res.list;
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    indexApi.machineDashbord.request().then((res: any) => {
 | 
				
			||||||
 | 
					        state.machine.num = res.machineNum;
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    indexApi.dbDashbord.request().then((res: any) => {
 | 
					    indexApi.dbDashbord.request().then((res: any) => {
 | 
				
			||||||
        nextTick(() => {
 | 
					        state.db.num = res.dbNum;
 | 
				
			||||||
            new CountUp('dbNum', res.dbNum).start();
 | 
					 | 
				
			||||||
        });
 | 
					 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    indexApi.redisDashbord.request().then((res: any) => {
 | 
					    indexApi.redisDashbord.request().then((res: any) => {
 | 
				
			||||||
        nextTick(() => {
 | 
					        state.redis.num = res.redisNum;
 | 
				
			||||||
            new CountUp('redisNum', res.redisNum).start();
 | 
					 | 
				
			||||||
        });
 | 
					 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    indexApi.mongoDashbord.request().then((res: any) => {
 | 
					    indexApi.mongoDashbord.request().then((res: any) => {
 | 
				
			||||||
        nextTick(() => {
 | 
					        state.mongo.num = res.mongoNum;
 | 
				
			||||||
            new CountUp('mongoNum', res.mongoNum).start();
 | 
					 | 
				
			||||||
        });
 | 
					 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const toPage = (item: any) => {
 | 
					const toPage = (item: any, codePath = '') => {
 | 
				
			||||||
    switch (item.id) {
 | 
					    let path;
 | 
				
			||||||
 | 
					    switch (item) {
 | 
				
			||||||
        case 'personal': {
 | 
					        case 'personal': {
 | 
				
			||||||
            router.push('/personal');
 | 
					            router.push('/personal');
 | 
				
			||||||
            break;
 | 
					            break;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        case 'mongoNum': {
 | 
					        case 'mongo': {
 | 
				
			||||||
            router.push('/mongo/mongo-data-operation');
 | 
					            useAutoOpenResource().setMongoCodePath(codePath);
 | 
				
			||||||
 | 
					            path = '/mongo/mongo-data-operation';
 | 
				
			||||||
            break;
 | 
					            break;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        case 'machineNum': {
 | 
					        case 'machine': {
 | 
				
			||||||
            router.push('/machine/machines-op');
 | 
					            useAutoOpenResource().setMachineCodePath(codePath);
 | 
				
			||||||
 | 
					            path = '/machine/machines-op';
 | 
				
			||||||
            break;
 | 
					            break;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        case 'dbNum': {
 | 
					        case 'db': {
 | 
				
			||||||
            router.push('/dbms/sql-exec');
 | 
					            useAutoOpenResource().setDbCodePath(codePath);
 | 
				
			||||||
 | 
					            path = '/dbms/sql-exec';
 | 
				
			||||||
            break;
 | 
					            break;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        case 'redisNum': {
 | 
					        case 'redis': {
 | 
				
			||||||
            router.push('/redis/data-operation');
 | 
					            useAutoOpenResource().setRedisCodePath(codePath);
 | 
				
			||||||
 | 
					            path = '/redis/data-operation';
 | 
				
			||||||
            break;
 | 
					            break;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
// 页面加载时
 | 
					    router.push({ path });
 | 
				
			||||||
onMounted(() => {
 | 
					};
 | 
				
			||||||
    initNumCountUp();
 | 
					 | 
				
			||||||
    // initHomeLaboratory();
 | 
					 | 
				
			||||||
    // initHomeOvertime();
 | 
					 | 
				
			||||||
});
 | 
					 | 
				
			||||||
</script>
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<style scoped lang="scss">
 | 
					<style scoped lang="scss">
 | 
				
			||||||
 | 
					@import '@/theme/mixins/index.scss';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.personal {
 | 
				
			||||||
 | 
					    .personal-user {
 | 
				
			||||||
 | 
					        height: 130px;
 | 
				
			||||||
 | 
					        display: flex;
 | 
				
			||||||
 | 
					        align-items: center;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        .personal-user-left {
 | 
				
			||||||
 | 
					            width: 100px;
 | 
				
			||||||
 | 
					            height: 130px;
 | 
				
			||||||
 | 
					            border-radius: 3px;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            ::v-deep(.el-upload) {
 | 
				
			||||||
 | 
					                height: 100%;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            .personal-user-left-upload {
 | 
				
			||||||
 | 
					                img {
 | 
				
			||||||
 | 
					                    width: 100%;
 | 
				
			||||||
 | 
					                    height: 100%;
 | 
				
			||||||
 | 
					                    border-radius: 3px;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                &:hover {
 | 
				
			||||||
 | 
					                    img {
 | 
				
			||||||
 | 
					                        animation: logoAnimation 0.3s ease-in-out;
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        .personal-user-right {
 | 
				
			||||||
 | 
					            flex: 1;
 | 
				
			||||||
 | 
					            padding: 0 15px;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            .personal-title {
 | 
				
			||||||
 | 
					                font-size: 18px;
 | 
				
			||||||
 | 
					                @include text-ellipsis(1);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            .personal-item {
 | 
				
			||||||
 | 
					                display: flex;
 | 
				
			||||||
 | 
					                align-items: center;
 | 
				
			||||||
 | 
					                font-size: 13px;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                .personal-item-label {
 | 
				
			||||||
 | 
					                    color: gray;
 | 
				
			||||||
 | 
					                    @include text-ellipsis(1);
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                .personal-item-value {
 | 
				
			||||||
 | 
					                    @include text-ellipsis(1);
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    .personal-info {
 | 
				
			||||||
 | 
					        .personal-info-more {
 | 
				
			||||||
 | 
					            float: right;
 | 
				
			||||||
 | 
					            color: gray;
 | 
				
			||||||
 | 
					            font-size: 13px;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            &:hover {
 | 
				
			||||||
 | 
					                color: var(--el-color-primary);
 | 
				
			||||||
 | 
					                cursor: pointer;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        .personal-info-box {
 | 
				
			||||||
 | 
					            height: 130px;
 | 
				
			||||||
 | 
					            overflow: hidden;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            .personal-info-ul {
 | 
				
			||||||
 | 
					                list-style: none;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                .personal-info-li {
 | 
				
			||||||
 | 
					                    font-size: 13px;
 | 
				
			||||||
 | 
					                    padding-bottom: 10px;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    .personal-info-li-title {
 | 
				
			||||||
 | 
					                        display: inline-block;
 | 
				
			||||||
 | 
					                        @include text-ellipsis(1);
 | 
				
			||||||
 | 
					                        color: grey;
 | 
				
			||||||
 | 
					                        text-decoration: none;
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    & a:hover {
 | 
				
			||||||
 | 
					                        color: var(--el-color-primary);
 | 
				
			||||||
 | 
					                        cursor: pointer;
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.resource-info {
 | 
				
			||||||
 | 
					    text-align: center;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    ::v-deep(.el-card__header) {
 | 
				
			||||||
 | 
					        padding: 2px 20px;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    .resource-num {
 | 
				
			||||||
 | 
					        font-weight: 700;
 | 
				
			||||||
 | 
					        font-size: 2vw;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.home-container {
 | 
					.home-container {
 | 
				
			||||||
    overflow-x: hidden;
 | 
					    overflow-x: hidden;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -182,7 +586,7 @@ onMounted(() => {
 | 
				
			|||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            .home-card-item-title-num {
 | 
					            .home-card-item-title-num {
 | 
				
			||||||
                font-size: 18px;
 | 
					                font-size: 2vw;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            .home-card-item-tip-num {
 | 
					            .home-card-item-tip-num {
 | 
				
			||||||
@@ -190,124 +594,5 @@ onMounted(() => {
 | 
				
			|||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					 | 
				
			||||||
    .home-card-first {
 | 
					 | 
				
			||||||
        background: var(--bg-main-color);
 | 
					 | 
				
			||||||
        border: 1px solid var(--el-border-color-light, #ebeef5);
 | 
					 | 
				
			||||||
        display: flex;
 | 
					 | 
				
			||||||
        align-items: center;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        img {
 | 
					 | 
				
			||||||
            width: 60px;
 | 
					 | 
				
			||||||
            height: 60px;
 | 
					 | 
				
			||||||
            border-radius: 100%;
 | 
					 | 
				
			||||||
            border: 2px solid var(--el-color-primary-light-5);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        .home-card-first-right {
 | 
					 | 
				
			||||||
            flex: 1;
 | 
					 | 
				
			||||||
            display: flex;
 | 
					 | 
				
			||||||
            flex-direction: column;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            .home-card-first-right-msg {
 | 
					 | 
				
			||||||
                font-size: 13px;
 | 
					 | 
				
			||||||
                color: gray;
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    .home-monitor {
 | 
					 | 
				
			||||||
        height: 200px;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        .flex-warp-item {
 | 
					 | 
				
			||||||
            width: 50%;
 | 
					 | 
				
			||||||
            height: 100px;
 | 
					 | 
				
			||||||
            display: flex;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            .flex-warp-item-box {
 | 
					 | 
				
			||||||
                margin: auto;
 | 
					 | 
				
			||||||
                height: auto;
 | 
					 | 
				
			||||||
                text-align: center;
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    .home-warning-card {
 | 
					 | 
				
			||||||
        height: 292px;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        ::v-deep(.el-card) {
 | 
					 | 
				
			||||||
            height: 100%;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    .home-dynamic {
 | 
					 | 
				
			||||||
        height: 200px;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        .home-dynamic-item {
 | 
					 | 
				
			||||||
            display: flex;
 | 
					 | 
				
			||||||
            width: 100%;
 | 
					 | 
				
			||||||
            height: 60px;
 | 
					 | 
				
			||||||
            overflow: hidden;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            &:first-of-type {
 | 
					 | 
				
			||||||
                .home-dynamic-item-line {
 | 
					 | 
				
			||||||
                    i {
 | 
					 | 
				
			||||||
                        color: orange !important;
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            .home-dynamic-item-left {
 | 
					 | 
				
			||||||
                text-align: right;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                .home-dynamic-item-left-time1 {
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                .home-dynamic-item-left-time2 {
 | 
					 | 
				
			||||||
                    font-size: 13px;
 | 
					 | 
				
			||||||
                    color: gray;
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            .home-dynamic-item-line {
 | 
					 | 
				
			||||||
                height: 60px;
 | 
					 | 
				
			||||||
                border-right: 2px dashed #dfdfdf;
 | 
					 | 
				
			||||||
                margin: 0 20px;
 | 
					 | 
				
			||||||
                position: relative;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                i {
 | 
					 | 
				
			||||||
                    color: var(--el-color-primary);
 | 
					 | 
				
			||||||
                    font-size: 12px;
 | 
					 | 
				
			||||||
                    position: absolute;
 | 
					 | 
				
			||||||
                    top: 1px;
 | 
					 | 
				
			||||||
                    left: -6px;
 | 
					 | 
				
			||||||
                    transform: rotate(46deg);
 | 
					 | 
				
			||||||
                    background: white;
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            .home-dynamic-item-right {
 | 
					 | 
				
			||||||
                flex: 1;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                .home-dynamic-item-right-title {
 | 
					 | 
				
			||||||
                    i {
 | 
					 | 
				
			||||||
                        margin-right: 5px;
 | 
					 | 
				
			||||||
                        border: 1px solid #dfdfdf;
 | 
					 | 
				
			||||||
                        width: 20px;
 | 
					 | 
				
			||||||
                        height: 20px;
 | 
					 | 
				
			||||||
                        border-radius: 100%;
 | 
					 | 
				
			||||||
                        padding: 3px 2px 2px;
 | 
					 | 
				
			||||||
                        text-align: center;
 | 
					 | 
				
			||||||
                        color: var(--el-color-primary);
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                .home-dynamic-item-right-label {
 | 
					 | 
				
			||||||
                    font-size: 13px;
 | 
					 | 
				
			||||||
                    color: gray;
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
</style>
 | 
					</style>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -132,7 +132,7 @@ import { nextTick, onMounted, ref, toRefs, reactive, computed } from 'vue';
 | 
				
			|||||||
import { useRoute, useRouter } from 'vue-router';
 | 
					import { useRoute, useRouter } from 'vue-router';
 | 
				
			||||||
import { ElMessage } from 'element-plus';
 | 
					import { ElMessage } from 'element-plus';
 | 
				
			||||||
import { initRouter } from '@/router/index';
 | 
					import { initRouter } from '@/router/index';
 | 
				
			||||||
import { saveToken, saveUser } from '@/common/utils/storage';
 | 
					import { getRefreshToken, saveRefreshToken, saveToken, saveUser } from '@/common/utils/storage';
 | 
				
			||||||
import { formatAxis } from '@/common/utils/format';
 | 
					import { formatAxis } from '@/common/utils/format';
 | 
				
			||||||
import openApi from '@/common/openApi';
 | 
					import openApi from '@/common/openApi';
 | 
				
			||||||
import { RsaEncrypt } from '@/common/rsa';
 | 
					import { RsaEncrypt } from '@/common/rsa';
 | 
				
			||||||
@@ -279,19 +279,20 @@ const login = () => {
 | 
				
			|||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const otpVerify = async () => {
 | 
					const otpVerify = async () => {
 | 
				
			||||||
    otpFormRef.value.validate(async (valid: boolean) => {
 | 
					    try {
 | 
				
			||||||
        if (!valid) {
 | 
					        await otpFormRef.value.validate();
 | 
				
			||||||
 | 
					    } catch (e: any) {
 | 
				
			||||||
        return false;
 | 
					        return false;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    try {
 | 
					    try {
 | 
				
			||||||
        state.loading.otpConfirm = true;
 | 
					        state.loading.otpConfirm = true;
 | 
				
			||||||
            const accessToken = await openApi.otpVerify(state.otpDialog.form);
 | 
					        const res = await openApi.otpVerify(state.otpDialog.form);
 | 
				
			||||||
            await signInSuccess(accessToken);
 | 
					        await signInSuccess(res.token, res.refresh_token);
 | 
				
			||||||
        state.otpDialog.visible = false;
 | 
					        state.otpDialog.visible = false;
 | 
				
			||||||
    } finally {
 | 
					    } finally {
 | 
				
			||||||
        state.loading.otpConfirm = false;
 | 
					        state.loading.otpConfirm = false;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// 登录
 | 
					// 登录
 | 
				
			||||||
@@ -327,10 +328,12 @@ const onSignIn = async () => {
 | 
				
			|||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const updateUserInfo = async () => {
 | 
					const updateUserInfo = async () => {
 | 
				
			||||||
    baseInfoFormRef.value.validate(async (valid: boolean) => {
 | 
					    try {
 | 
				
			||||||
        if (!valid) {
 | 
					        await baseInfoFormRef.value.validate();
 | 
				
			||||||
 | 
					    } catch (e: any) {
 | 
				
			||||||
        return false;
 | 
					        return false;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    try {
 | 
					    try {
 | 
				
			||||||
        state.loading.updateUserConfirm = true;
 | 
					        state.loading.updateUserConfirm = true;
 | 
				
			||||||
        const form = state.baseInfoDialog.form;
 | 
					        const form = state.baseInfoDialog.form;
 | 
				
			||||||
@@ -342,7 +345,6 @@ const updateUserInfo = async () => {
 | 
				
			|||||||
    } finally {
 | 
					    } finally {
 | 
				
			||||||
        state.loading.updateUserConfirm = false;
 | 
					        state.loading.updateUserConfirm = false;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const loginResDeal = (loginRes: any) => {
 | 
					const loginResDeal = (loginRes: any) => {
 | 
				
			||||||
@@ -366,7 +368,7 @@ const loginResDeal = (loginRes: any) => {
 | 
				
			|||||||
    const token = loginRes.token;
 | 
					    const token = loginRes.token;
 | 
				
			||||||
    // 如果不需要    otp校验,则该token即为accessToken,否则为otp校验token
 | 
					    // 如果不需要    otp校验,则该token即为accessToken,否则为otp校验token
 | 
				
			||||||
    if (loginRes.otp == -1) {
 | 
					    if (loginRes.otp == -1) {
 | 
				
			||||||
        signInSuccess(token);
 | 
					        signInSuccess(token, loginRes.refresh_token);
 | 
				
			||||||
        return;
 | 
					        return;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -379,12 +381,16 @@ const loginResDeal = (loginRes: any) => {
 | 
				
			|||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// 登录成功后的跳转
 | 
					// 登录成功后的跳转
 | 
				
			||||||
const signInSuccess = async (accessToken: string = '') => {
 | 
					const signInSuccess = async (accessToken: string = '', refreshToken = '') => {
 | 
				
			||||||
    if (!accessToken) {
 | 
					    if (!accessToken) {
 | 
				
			||||||
        accessToken = getToken();
 | 
					        accessToken = getToken();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					    if (!refreshToken) {
 | 
				
			||||||
 | 
					        refreshToken = getRefreshToken();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
    // 存储 token 到浏览器缓存
 | 
					    // 存储 token 到浏览器缓存
 | 
				
			||||||
    saveToken(accessToken);
 | 
					    saveToken(accessToken);
 | 
				
			||||||
 | 
					    saveRefreshToken(refreshToken);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // 初始化路由
 | 
					    // 初始化路由
 | 
				
			||||||
    await initRouter();
 | 
					    await initRouter();
 | 
				
			||||||
@@ -415,11 +421,13 @@ const toIndex = async () => {
 | 
				
			|||||||
    }, 300);
 | 
					    }, 300);
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const changePwd = () => {
 | 
					const changePwd = async () => {
 | 
				
			||||||
    changePwdFormRef.value.validate(async (valid: boolean) => {
 | 
					    try {
 | 
				
			||||||
        if (!valid) {
 | 
					        await changePwdFormRef.value.validate();
 | 
				
			||||||
 | 
					    } catch (e: any) {
 | 
				
			||||||
        return false;
 | 
					        return false;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    try {
 | 
					    try {
 | 
				
			||||||
        state.loading.changePwd = true;
 | 
					        state.loading.changePwd = true;
 | 
				
			||||||
        const form = state.changePwdDialog.form;
 | 
					        const form = state.changePwdDialog.form;
 | 
				
			||||||
@@ -434,7 +442,6 @@ const changePwd = () => {
 | 
				
			|||||||
    } finally {
 | 
					    } finally {
 | 
				
			||||||
        state.loading.changePwd = false;
 | 
					        state.loading.changePwd = false;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const cancelChangePwd = () => {
 | 
					const cancelChangePwd = () => {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -39,6 +39,11 @@
 | 
				
			|||||||
                            />
 | 
					                            />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                            <el-option :key="TagResourceTypeEnum.Db.value" :label="TagResourceTypeEnum.Db.label" :value="TagResourceTypeEnum.Db.value" />
 | 
					                            <el-option :key="TagResourceTypeEnum.Db.value" :label="TagResourceTypeEnum.Db.label" :value="TagResourceTypeEnum.Db.value" />
 | 
				
			||||||
 | 
					                            <el-option
 | 
				
			||||||
 | 
					                                :key="TagResourceTypeEnum.Redis.value"
 | 
				
			||||||
 | 
					                                :label="TagResourceTypeEnum.Redis.label"
 | 
				
			||||||
 | 
					                                :value="TagResourceTypeEnum.Redis.value"
 | 
				
			||||||
 | 
					                            />
 | 
				
			||||||
                        </el-select>
 | 
					                        </el-select>
 | 
				
			||||||
                    </el-form-item>
 | 
					                    </el-form-item>
 | 
				
			||||||
                    <el-form-item prop="resourceCode" label="资源编号" required>
 | 
					                    <el-form-item prop="resourceCode" label="资源编号" required>
 | 
				
			||||||
@@ -191,12 +196,13 @@ const showResourceEdit = computed(() => {
 | 
				
			|||||||
    return state.form.type != AuthCertTypeEnum.Public.value && !props.resourceEdit;
 | 
					    return state.form.type != AuthCertTypeEnum.Public.value && !props.resourceEdit;
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
watch(
 | 
					watch(dialogVisible, (val: any) => {
 | 
				
			||||||
    () => props.authCert,
 | 
					    if (val) {
 | 
				
			||||||
    (val: any) => {
 | 
					        setForm(props.authCert);
 | 
				
			||||||
        setForm(val);
 | 
					    } else {
 | 
				
			||||||
 | 
					        cancelEdit();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
);
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const setForm = (val: any) => {
 | 
					const setForm = (val: any) => {
 | 
				
			||||||
    val = { ...val };
 | 
					    val = { ...val };
 | 
				
			||||||
@@ -246,17 +252,18 @@ const getCiphertext = async () => {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
const cancelEdit = () => {
 | 
					const cancelEdit = () => {
 | 
				
			||||||
    dialogVisible.value = false;
 | 
					    dialogVisible.value = false;
 | 
				
			||||||
    emit('cancel');
 | 
					
 | 
				
			||||||
    setTimeout(() => {
 | 
					    setTimeout(() => {
 | 
				
			||||||
        acForm.value?.resetFields();
 | 
					 | 
				
			||||||
        state.form = { ...DefaultForm };
 | 
					        state.form = { ...DefaultForm };
 | 
				
			||||||
 | 
					        acForm.value?.resetFields();
 | 
				
			||||||
 | 
					        emit('cancel');
 | 
				
			||||||
    }, 300);
 | 
					    }, 300);
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const btnOk = async () => {
 | 
					const btnOk = async () => {
 | 
				
			||||||
    acForm.value.validate(async (valid: boolean) => {
 | 
					    acForm.value.validate(async (valid: boolean) => {
 | 
				
			||||||
        if (valid) {
 | 
					        if (valid) {
 | 
				
			||||||
            emit('confirm', state.form);
 | 
					            emit('confirm', { ...state.form });
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -113,9 +113,6 @@ const deleteRow = (idx: any) => {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
const cancelEdit = () => {
 | 
					const cancelEdit = () => {
 | 
				
			||||||
    state.dvisible = false;
 | 
					    state.dvisible = false;
 | 
				
			||||||
    setTimeout(() => {
 | 
					 | 
				
			||||||
        state.form = {};
 | 
					 | 
				
			||||||
    }, 300);
 | 
					 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const btnOk = async (authCert: any) => {
 | 
					const btnOk = async (authCert: any) => {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -17,6 +17,7 @@
 | 
				
			|||||||
<script lang="ts" setup>
 | 
					<script lang="ts" setup>
 | 
				
			||||||
import { toRefs, reactive, onMounted } from 'vue';
 | 
					import { toRefs, reactive, onMounted } from 'vue';
 | 
				
			||||||
import { machineApi } from '../machine/api';
 | 
					import { machineApi } from '../machine/api';
 | 
				
			||||||
 | 
					import { MachineProtocolEnum } from '../machine/enums';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const props = defineProps({
 | 
					const props = defineProps({
 | 
				
			||||||
    modelValue: {
 | 
					    modelValue: {
 | 
				
			||||||
@@ -46,7 +47,7 @@ onMounted(async () => {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
const getSshTunnelMachines = async () => {
 | 
					const getSshTunnelMachines = async () => {
 | 
				
			||||||
    if (state.sshTunnelMachineList.length == 0) {
 | 
					    if (state.sshTunnelMachineList.length == 0) {
 | 
				
			||||||
        const res = await machineApi.list.request({ pageNum: 1, pageSize: 100, ssh: 1 });
 | 
					        const res = await machineApi.list.request({ pageNum: 1, pageSize: 100, protocol: MachineProtocolEnum.Ssh.value });
 | 
				
			||||||
        state.sshTunnelMachineList = res.list;
 | 
					        state.sshTunnelMachineList = res.list;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										87
									
								
								mayfly_go_web/src/views/ops/component/TagCodePath.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										87
									
								
								mayfly_go_web/src/views/ops/component/TagCodePath.vue
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,87 @@
 | 
				
			|||||||
 | 
					<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">
 | 
				
			||||||
 | 
					                <SvgIcon
 | 
				
			||||||
 | 
					                    :name="EnumValue.getEnumByValue(TagResourceTypeEnum, item.type)?.extra.icon"
 | 
				
			||||||
 | 
					                    :color="EnumValue.getEnumByValue(TagResourceTypeEnum, item.type)?.extra.iconColor"
 | 
				
			||||||
 | 
					                    class="mr2"
 | 
				
			||||||
 | 
					                />
 | 
				
			||||||
 | 
					                <span> {{ item.code }}</span>
 | 
				
			||||||
 | 
					                <SvgIcon v-if="!item.isEnd" class="mr5 ml5" name="arrow-right" />
 | 
				
			||||||
 | 
					            </span>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            <!-- 展示剩余的标签信息 -->
 | 
				
			||||||
 | 
					            <el-popover :show-after="300" v-if="paths.length > 1 && idx == 0" placement="bottom" width="500" trigger="hover">
 | 
				
			||||||
 | 
					                <template #reference>
 | 
				
			||||||
 | 
					                    <SvgIcon class="mt5 ml5" color="var(--el-color-primary)" name="MoreFilled" />
 | 
				
			||||||
 | 
					                </template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                <el-row v-for="i in paths.slice(1)" :key="i">
 | 
				
			||||||
 | 
					                    <span v-for="item in parseTagPath(i)" :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>
 | 
				
			||||||
 | 
					                        <SvgIcon v-if="!item.isEnd" class="mr5 ml5" name="arrow-right" />
 | 
				
			||||||
 | 
					                    </span>
 | 
				
			||||||
 | 
					                </el-row>
 | 
				
			||||||
 | 
					            </el-popover>
 | 
				
			||||||
 | 
					        </el-row>
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<script lang="ts" setup>
 | 
				
			||||||
 | 
					import { TagResourceTypeEnum } from '@/common/commonEnum';
 | 
				
			||||||
 | 
					import EnumValue from '@/common/Enum';
 | 
				
			||||||
 | 
					import { computed } from 'vue';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const props = defineProps({
 | 
				
			||||||
 | 
					    path: {
 | 
				
			||||||
 | 
					        type: [String, Array<string>],
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const paths = computed(() => {
 | 
				
			||||||
 | 
					    if (Array.isArray(props.path)) {
 | 
				
			||||||
 | 
					        return props.path;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return [props.path];
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const parseTagPath = (tagPath: string = '') => {
 | 
				
			||||||
 | 
					    if (!tagPath) {
 | 
				
			||||||
 | 
					        return [];
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    const res = [] as any;
 | 
				
			||||||
 | 
					    const codes = tagPath.split('/');
 | 
				
			||||||
 | 
					    for (let code of codes) {
 | 
				
			||||||
 | 
					        const typeAndCode = code.split('|');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (typeAndCode.length == 1) {
 | 
				
			||||||
 | 
					            const tagCode = typeAndCode[0];
 | 
				
			||||||
 | 
					            if (!tagCode) {
 | 
				
			||||||
 | 
					                continue;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            res.push({
 | 
				
			||||||
 | 
					                type: TagResourceTypeEnum.Tag.value,
 | 
				
			||||||
 | 
					                code: typeAndCode[0],
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					            continue;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        res.push({
 | 
				
			||||||
 | 
					            type: typeAndCode[0],
 | 
				
			||||||
 | 
					            code: typeAndCode[1],
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    res[res.length - 1].isEnd = true;
 | 
				
			||||||
 | 
					    return res;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					</script>
 | 
				
			||||||
 | 
					<style lang="scss"></style>
 | 
				
			||||||
@@ -18,7 +18,7 @@
 | 
				
			|||||||
                :default-expanded-keys="props.defaultExpandedKeys"
 | 
					                :default-expanded-keys="props.defaultExpandedKeys"
 | 
				
			||||||
            >
 | 
					            >
 | 
				
			||||||
                <template #default="{ node, data }">
 | 
					                <template #default="{ node, data }">
 | 
				
			||||||
                    <span @dblclick="treeNodeDblclick(data)" :class="data.type.nodeDblclickFunc ? 'none-select' : ''">
 | 
					                    <span :id="node.key" @dblclick="treeNodeDblclick(data)" :class="data.type.nodeDblclickFunc ? 'none-select' : ''">
 | 
				
			||||||
                        <span v-if="data.type.value == TagTreeNode.TagPath">
 | 
					                        <span v-if="data.type.value == TagTreeNode.TagPath">
 | 
				
			||||||
                            <tag-info :tag-path="data.label" />
 | 
					                            <tag-info :tag-path="data.label" />
 | 
				
			||||||
                        </span>
 | 
					                        </span>
 | 
				
			||||||
@@ -35,8 +35,10 @@
 | 
				
			|||||||
                            </slot>
 | 
					                            </slot>
 | 
				
			||||||
                        </span>
 | 
					                        </span>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                        <span class="label-suffix">
 | 
				
			||||||
                            <slot :node="node" :data="data" name="suffix"></slot>
 | 
					                            <slot :node="node" :data="data" name="suffix"></slot>
 | 
				
			||||||
                        </span>
 | 
					                        </span>
 | 
				
			||||||
 | 
					                    </span>
 | 
				
			||||||
                </template>
 | 
					                </template>
 | 
				
			||||||
            </el-tree>
 | 
					            </el-tree>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -46,11 +48,12 @@
 | 
				
			|||||||
</template>
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<script lang="ts" setup>
 | 
					<script lang="ts" setup>
 | 
				
			||||||
import { onMounted, reactive, ref, watch, toRefs } from 'vue';
 | 
					import { onMounted, reactive, ref, watch, toRefs, nextTick } from 'vue';
 | 
				
			||||||
import { NodeType, TagTreeNode } from './tag';
 | 
					import { NodeType, TagTreeNode } from './tag';
 | 
				
			||||||
import TagInfo from './TagInfo.vue';
 | 
					import TagInfo from './TagInfo.vue';
 | 
				
			||||||
import { Contextmenu } from '@/components/contextmenu';
 | 
					import { Contextmenu } from '@/components/contextmenu';
 | 
				
			||||||
import { tagApi } from '../tag/api';
 | 
					import { tagApi } from '../tag/api';
 | 
				
			||||||
 | 
					import { isPrefixSubsequence } from '@/common/utils/string';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const props = defineProps({
 | 
					const props = defineProps({
 | 
				
			||||||
    resourceType: {
 | 
					    resourceType: {
 | 
				
			||||||
@@ -103,8 +106,7 @@ watch(filterText, (val) => {
 | 
				
			|||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const filterNode = (value: string, data: any) => {
 | 
					const filterNode = (value: string, data: any) => {
 | 
				
			||||||
    if (!value) return true;
 | 
					    return !value || isPrefixSubsequence(value, data.label);
 | 
				
			||||||
    return data.label.includes(value);
 | 
					 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
@@ -124,7 +126,7 @@ const loadTags = async () => {
 | 
				
			|||||||
 * @param { Object } node
 | 
					 * @param { Object } node
 | 
				
			||||||
 * @param { Object } resolve
 | 
					 * @param { Object } resolve
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
const loadNode = async (node: any, resolve: any) => {
 | 
					const loadNode = async (node: any, resolve: (data: any) => void, reject: () => void) => {
 | 
				
			||||||
    if (typeof resolve !== 'function') {
 | 
					    if (typeof resolve !== 'function') {
 | 
				
			||||||
        return;
 | 
					        return;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@@ -139,6 +141,8 @@ const loadNode = async (node: any, resolve: any) => {
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
    } catch (e: any) {
 | 
					    } catch (e: any) {
 | 
				
			||||||
        console.error(e);
 | 
					        console.error(e);
 | 
				
			||||||
 | 
					        // 调用 reject 以保持节点状态,并允许远程加载继续。
 | 
				
			||||||
 | 
					        return reject();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    return resolve(nodes);
 | 
					    return resolve(nodes);
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
@@ -203,8 +207,25 @@ const getNode = (nodeKey: any) => {
 | 
				
			|||||||
    return node;
 | 
					    return node;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const setCurrentKey = (nodeKey: any) => {
 | 
				
			||||||
 | 
					    treeRef.value.setCurrentKey(nodeKey);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // 通过Id获取到对应的dom元素
 | 
				
			||||||
 | 
					    const node = document.getElementById(nodeKey);
 | 
				
			||||||
 | 
					    if (node) {
 | 
				
			||||||
 | 
					        setTimeout(() => {
 | 
				
			||||||
 | 
					            nextTick(() => {
 | 
				
			||||||
 | 
					                // 通过scrollIntoView方法将对应的dom元素定位到可见区域 【block: 'center'】这个属性是在垂直方向居中显示
 | 
				
			||||||
 | 
					                node.scrollIntoView({ block: 'center' });
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					        }, 100);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
defineExpose({
 | 
					defineExpose({
 | 
				
			||||||
    reloadNode,
 | 
					    reloadNode,
 | 
				
			||||||
 | 
					    getNode,
 | 
				
			||||||
 | 
					    setCurrentKey,
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
</script>
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -216,5 +237,13 @@ defineExpose({
 | 
				
			|||||||
        display: inline-block;
 | 
					        display: inline-block;
 | 
				
			||||||
        min-width: 100%;
 | 
					        min-width: 100%;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    .label-suffix {
 | 
				
			||||||
 | 
					        position: absolute;
 | 
				
			||||||
 | 
					        right: 10px;
 | 
				
			||||||
 | 
					        color: #c4c9c4;
 | 
				
			||||||
 | 
					        font-size: 10px;
 | 
				
			||||||
 | 
					        margin-top: 2px;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
</style>
 | 
					</style>
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										167
									
								
								mayfly_go_web/src/views/ops/component/TagTreeCheck.vue
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										167
									
								
								mayfly_go_web/src/views/ops/component/TagTreeCheck.vue
									
									
									
									
									
										Executable file
									
								
							@@ -0,0 +1,167 @@
 | 
				
			|||||||
 | 
					<template>
 | 
				
			||||||
 | 
					    <div class="w100 tag-tree-check">
 | 
				
			||||||
 | 
					        <el-input v-model="filterTag" @input="onFilterValChanged" clearable placeholder="输入关键字过滤" size="small" />
 | 
				
			||||||
 | 
					        <div class="mt3" style="border: 1px solid var(--el-border-color)">
 | 
				
			||||||
 | 
					            <el-scrollbar :style="{ height: props.height }">
 | 
				
			||||||
 | 
					                <el-tree
 | 
				
			||||||
 | 
					                    v-bind="$attrs"
 | 
				
			||||||
 | 
					                    ref="tagTreeRef"
 | 
				
			||||||
 | 
					                    :data="state.tags"
 | 
				
			||||||
 | 
					                    :default-expanded-keys="state.defaultExpandedKeys"
 | 
				
			||||||
 | 
					                    :default-checked-keys="checkedTags"
 | 
				
			||||||
 | 
					                    multiple
 | 
				
			||||||
 | 
					                    :render-after-expand="true"
 | 
				
			||||||
 | 
					                    show-checkbox
 | 
				
			||||||
 | 
					                    check-strictly
 | 
				
			||||||
 | 
					                    :node-key="$props.nodeKey"
 | 
				
			||||||
 | 
					                    :props="{
 | 
				
			||||||
 | 
					                        value: $props.nodeKey,
 | 
				
			||||||
 | 
					                        label: 'codePath',
 | 
				
			||||||
 | 
					                        children: 'children',
 | 
				
			||||||
 | 
					                        disabled: 'disabled',
 | 
				
			||||||
 | 
					                    }"
 | 
				
			||||||
 | 
					                    @check="tagTreeNodeCheck"
 | 
				
			||||||
 | 
					                    :filter-node-method="filterNode"
 | 
				
			||||||
 | 
					                >
 | 
				
			||||||
 | 
					                    <template #default="{ data }">
 | 
				
			||||||
 | 
					                        <span>
 | 
				
			||||||
 | 
					                            <SvgIcon
 | 
				
			||||||
 | 
					                                :name="EnumValue.getEnumByValue(TagResourceTypeEnum, data.type)?.extra.icon"
 | 
				
			||||||
 | 
					                                :color="EnumValue.getEnumByValue(TagResourceTypeEnum, data.type)?.extra.iconColor"
 | 
				
			||||||
 | 
					                            />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                            <span class="font13 ml5">
 | 
				
			||||||
 | 
					                                {{ data.code }}
 | 
				
			||||||
 | 
					                                <span style="color: #3c8dbc">【</span>
 | 
				
			||||||
 | 
					                                {{ data.name }}
 | 
				
			||||||
 | 
					                                <span style="color: #3c8dbc">】</span>
 | 
				
			||||||
 | 
					                                <el-tag v-if="data.children !== null" size="small">{{ data.children.length }} </el-tag>
 | 
				
			||||||
 | 
					                            </span>
 | 
				
			||||||
 | 
					                        </span>
 | 
				
			||||||
 | 
					                    </template>
 | 
				
			||||||
 | 
					                </el-tree>
 | 
				
			||||||
 | 
					            </el-scrollbar>
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<script lang="ts" setup>
 | 
				
			||||||
 | 
					import { ref, reactive, onMounted } from 'vue';
 | 
				
			||||||
 | 
					import { tagApi } from '../tag/api';
 | 
				
			||||||
 | 
					import { TagResourceTypeEnum } from '@/common/commonEnum';
 | 
				
			||||||
 | 
					import EnumValue from '@/common/Enum';
 | 
				
			||||||
 | 
					import { isPrefixSubsequence } from '@/common/utils/string';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const props = defineProps({
 | 
				
			||||||
 | 
					    height: {
 | 
				
			||||||
 | 
					        type: [String, Number],
 | 
				
			||||||
 | 
					        default: 'calc(100vh - 330px)',
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    tagType: {
 | 
				
			||||||
 | 
					        type: [Number, Array<Number>],
 | 
				
			||||||
 | 
					        default: TagResourceTypeEnum.Tag.value,
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    nodeKey: {
 | 
				
			||||||
 | 
					        type: String,
 | 
				
			||||||
 | 
					        default: 'codePath',
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const checkedTags = defineModel<Array<any>>('modelValue', {
 | 
				
			||||||
 | 
					    default: () => [],
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const tagTreeRef: any = ref(null);
 | 
				
			||||||
 | 
					const filterTag = ref('');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const state = reactive({
 | 
				
			||||||
 | 
					    defaultExpandedKeys: [] as any,
 | 
				
			||||||
 | 
					    tags: [],
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					onMounted(() => {
 | 
				
			||||||
 | 
					    state.defaultExpandedKeys = checkedTags.value;
 | 
				
			||||||
 | 
					    search();
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const search = async () => {
 | 
				
			||||||
 | 
					    let tagType: any = props.tagType;
 | 
				
			||||||
 | 
					    if (Array.isArray(props.tagType)) {
 | 
				
			||||||
 | 
					        tagType = props.tagType.join(',');
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    state.tags = await tagApi.getTagTrees.request({ type: tagType });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    setTimeout(() => {
 | 
				
			||||||
 | 
					        const checkedNodes = tagTreeRef.value.getCheckedNodes();
 | 
				
			||||||
 | 
					        console.log('check nodes: ', checkedNodes);
 | 
				
			||||||
 | 
					        // 禁用选中节点的所有父节点,不可选中
 | 
				
			||||||
 | 
					        for (let checkNodeData of checkedNodes) {
 | 
				
			||||||
 | 
					            disableParentNodes(tagTreeRef.value.getNode(checkNodeData.codePath).parent);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }, 200);
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const filterNode = (value: string, data: any) => {
 | 
				
			||||||
 | 
					    return !value || isPrefixSubsequence(value, data.codePath) || isPrefixSubsequence(value, data.name);
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const onFilterValChanged = (val: string) => {
 | 
				
			||||||
 | 
					    tagTreeRef.value!.filter(val);
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const tagTreeNodeCheck = (data: any) => {
 | 
				
			||||||
 | 
					    const node = tagTreeRef.value.getNode(data.codePath);
 | 
				
			||||||
 | 
					    console.log('check node: ', node);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (node.checked) {
 | 
				
			||||||
 | 
					        // 如果选中了子节点,则需要将父节点全部取消选中,并禁用父节点
 | 
				
			||||||
 | 
					        unCheckParentNodes(node.parent);
 | 
				
			||||||
 | 
					        disableParentNodes(node.parent);
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					        // 如果取消了选中,则需要根据条件恢复父节点的选中状态
 | 
				
			||||||
 | 
					        disableParentNodes(node.parent, false);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // 更新绑定的值
 | 
				
			||||||
 | 
					    checkedTags.value = tagTreeRef.value.getCheckedKeys(false);
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const unCheckParentNodes = (node: any) => {
 | 
				
			||||||
 | 
					    if (!node) {
 | 
				
			||||||
 | 
					        return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    tagTreeRef.value.setChecked(node, false, false);
 | 
				
			||||||
 | 
					    unCheckParentNodes(node.parent);
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * 禁用该节点以及所有父节点
 | 
				
			||||||
 | 
					 * @param node 节点
 | 
				
			||||||
 | 
					 * @param disable 是否禁用
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					const disableParentNodes = (node: any, disable = true) => {
 | 
				
			||||||
 | 
					    if (!node) {
 | 
				
			||||||
 | 
					        return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    if (!disable) {
 | 
				
			||||||
 | 
					        // 恢复为非禁用状态时,若同层级存在一个选中状态或者禁用状态,则继续禁用 不恢复非禁用状态。
 | 
				
			||||||
 | 
					        for (let oneLevelNodes of node.childNodes) {
 | 
				
			||||||
 | 
					            if (oneLevelNodes.checked || oneLevelNodes.data.disabled) {
 | 
				
			||||||
 | 
					                return;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    node.data.disabled = disable;
 | 
				
			||||||
 | 
					    disableParentNodes(node.parent, disable);
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					</script>
 | 
				
			||||||
 | 
					<style lang="scss" scoped>
 | 
				
			||||||
 | 
					.tag-tree-check {
 | 
				
			||||||
 | 
					    .el-tree {
 | 
				
			||||||
 | 
					        min-width: 100%;
 | 
				
			||||||
 | 
					        // 横向滚动生效
 | 
				
			||||||
 | 
					        display: inline-block;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					</style>
 | 
				
			||||||
@@ -6,8 +6,7 @@
 | 
				
			|||||||
            @change="changeTag"
 | 
					            @change="changeTag"
 | 
				
			||||||
            :data="tags"
 | 
					            :data="tags"
 | 
				
			||||||
            placeholder="请选择关联标签"
 | 
					            placeholder="请选择关联标签"
 | 
				
			||||||
            :render-after-expand="true"
 | 
					            :default-expanded-keys="defaultExpandedKeys"
 | 
				
			||||||
            :default-expanded-keys="[state.selectTags]"
 | 
					 | 
				
			||||||
            show-checkbox
 | 
					            show-checkbox
 | 
				
			||||||
            node-key="codePath"
 | 
					            node-key="codePath"
 | 
				
			||||||
            :props="{
 | 
					            :props="{
 | 
				
			||||||
@@ -18,6 +17,7 @@
 | 
				
			|||||||
        >
 | 
					        >
 | 
				
			||||||
            <template #default="{ data }">
 | 
					            <template #default="{ data }">
 | 
				
			||||||
                <span class="custom-tree-node">
 | 
					                <span class="custom-tree-node">
 | 
				
			||||||
 | 
					                    <SvgIcon :name="EnumValue.getEnumByValue(TagResourceTypeEnum, data.type)?.extra.icon" class="mr2" />
 | 
				
			||||||
                    <span style="font-size: 13px">
 | 
					                    <span style="font-size: 13px">
 | 
				
			||||||
                        {{ data.code }}
 | 
					                        {{ data.code }}
 | 
				
			||||||
                        <span style="color: #3c8dbc">【</span>
 | 
					                        <span style="color: #3c8dbc">【</span>
 | 
				
			||||||
@@ -32,16 +32,17 @@
 | 
				
			|||||||
</template>
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<script lang="ts" setup>
 | 
					<script lang="ts" setup>
 | 
				
			||||||
import { toRefs, reactive, onMounted } from 'vue';
 | 
					import { toRefs, reactive, onMounted, computed } from 'vue';
 | 
				
			||||||
import { tagApi } from '../tag/api';
 | 
					import { tagApi } from '../tag/api';
 | 
				
			||||||
import { TagResourceTypeEnum } from '@/common/commonEnum';
 | 
					import { TagResourceTypeEnum } from '@/common/commonEnum';
 | 
				
			||||||
 | 
					import EnumValue from '@/common/Enum';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
//定义事件
 | 
					//定义事件
 | 
				
			||||||
const emit = defineEmits(['update:modelValue', 'changeTag', 'input']);
 | 
					const emit = defineEmits(['update:modelValue', 'changeTag', 'input']);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const props = defineProps({
 | 
					const props = defineProps({
 | 
				
			||||||
    selectTags: {
 | 
					    selectTags: {
 | 
				
			||||||
        type: [Array<any>],
 | 
					        type: [Array<any>, Object],
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    tagType: {
 | 
					    tagType: {
 | 
				
			||||||
        type: Number,
 | 
					        type: Number,
 | 
				
			||||||
@@ -57,6 +58,16 @@ const state = reactive({
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
const { tags } = toRefs(state);
 | 
					const { tags } = toRefs(state);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const defaultExpandedKeys = computed(() => {
 | 
				
			||||||
 | 
					    if (Array.isArray(state.selectTags)) {
 | 
				
			||||||
 | 
					        // 如果 state.selectTags 是数组,直接返回
 | 
				
			||||||
 | 
					        return state.selectTags;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // 如果 state.selectTags 不是数组,转换为包含 state.selectTags 的数组
 | 
				
			||||||
 | 
					    return [state.selectTags];
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
onMounted(async () => {
 | 
					onMounted(async () => {
 | 
				
			||||||
    state.selectTags = props.selectTags;
 | 
					    state.selectTags = props.selectTags;
 | 
				
			||||||
    state.tags = await tagApi.getTagTrees.request({ type: props.tagType });
 | 
					    state.tags = await tagApi.getTagTrees.request({ type: props.tagType });
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -171,3 +171,44 @@ export function getTagPathSearchItem(resourceType: number) {
 | 
				
			|||||||
        })
 | 
					        })
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * 根据标签路径获取对应的类型与编号数组
 | 
				
			||||||
 | 
					 * @param codePath 编号路径  tag1/tag2/1|xxx/11|yyy/
 | 
				
			||||||
 | 
					 * @returns {1: ['xxx'], 11: ['yyy']}
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					export function getTagTypeCodeByPath(codePath: string) {
 | 
				
			||||||
 | 
					    const result = {};
 | 
				
			||||||
 | 
					    const parts = codePath.split('/'); // 切分字符串并保留数字和对应的值部分
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    for (let part of parts) {
 | 
				
			||||||
 | 
					        if (!part) {
 | 
				
			||||||
 | 
					            continue;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        let [key, value] = part.split('|'); // 分割数字和值部分
 | 
				
			||||||
 | 
					        // 如果不存在第二个参数,则说明为标签类型
 | 
				
			||||||
 | 
					        if (!value) {
 | 
				
			||||||
 | 
					            value = key;
 | 
				
			||||||
 | 
					            key = '-1';
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        if (!result[key]) {
 | 
				
			||||||
 | 
					            result[key] = [];
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        result[key].push(value);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return result;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function expandCodePath(codePath: string) {
 | 
				
			||||||
 | 
					    const parts = codePath.split('/');
 | 
				
			||||||
 | 
					    const result = [];
 | 
				
			||||||
 | 
					    let currentPath = '';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    for (let i = 0; i < parts.length - 1; i++) {
 | 
				
			||||||
 | 
					        currentPath += parts[i] + '/';
 | 
				
			||||||
 | 
					        result.push(currentPath);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return result;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -23,7 +23,7 @@
 | 
				
			|||||||
                </el-form-item>
 | 
					                </el-form-item>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                <el-form-item prop="authCertName" label="授权凭证" required>
 | 
					                <el-form-item prop="authCertName" label="授权凭证" required>
 | 
				
			||||||
                    <el-select @change="changeAuthCert" v-model="form.authCertName" placeholder="请选择授权凭证" filterable>
 | 
					                    <el-select v-model="form.authCertName" placeholder="请选择授权凭证" filterable>
 | 
				
			||||||
                        <el-option v-for="item in state.authCerts" :key="item.id" :label="`${item.name}`" :value="item.name">
 | 
					                        <el-option v-for="item in state.authCerts" :key="item.id" :label="`${item.name}`" :value="item.name">
 | 
				
			||||||
                            {{ item.name }}
 | 
					                            {{ item.name }}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -39,8 +39,15 @@
 | 
				
			|||||||
                    </el-select>
 | 
					                    </el-select>
 | 
				
			||||||
                </el-form-item>
 | 
					                </el-form-item>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                <el-form-item prop="getDatabaseMode" label="获库方式" required>
 | 
				
			||||||
 | 
					                    <el-select v-model="form.getDatabaseMode" @change="onChangeGetDatabaseMode" placeholder="请选择库名获取方式">
 | 
				
			||||||
 | 
					                        <el-option v-for="item in DbGetDbNamesMode" :key="item.value" :label="item.label" :value="item.value"> </el-option>
 | 
				
			||||||
 | 
					                    </el-select>
 | 
				
			||||||
 | 
					                </el-form-item>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                <el-form-item prop="database" label="数据库名">
 | 
					                <el-form-item prop="database" label="数据库名">
 | 
				
			||||||
                    <el-select
 | 
					                    <el-select
 | 
				
			||||||
 | 
					                        :disabled="form.getDatabaseMode == DbGetDbNamesMode.Auto.value || !form.authCertName"
 | 
				
			||||||
                        v-model="dbNamesSelected"
 | 
					                        v-model="dbNamesSelected"
 | 
				
			||||||
                        multiple
 | 
					                        multiple
 | 
				
			||||||
                        clearable
 | 
					                        clearable
 | 
				
			||||||
@@ -49,8 +56,9 @@
 | 
				
			|||||||
                        filterable
 | 
					                        filterable
 | 
				
			||||||
                        :filter-method="filterDbNames"
 | 
					                        :filter-method="filterDbNames"
 | 
				
			||||||
                        allow-create
 | 
					                        allow-create
 | 
				
			||||||
                        placeholder="请确保数据库实例信息填写完整后获取库名"
 | 
					                        placeholder="获库方式为‘指定库名’时,可选择"
 | 
				
			||||||
                        style="width: 100%"
 | 
					                        @focus="getAllDatabase(form.authCertName)"
 | 
				
			||||||
 | 
					                        :loading="state.loadingDbNames"
 | 
				
			||||||
                    >
 | 
					                    >
 | 
				
			||||||
                        <template #header>
 | 
					                        <template #header>
 | 
				
			||||||
                            <el-checkbox v-model="checkAllDbNames" :indeterminate="indeterminateDbNames" @change="handleCheckAll"> 全选 </el-checkbox>
 | 
					                            <el-checkbox v-model="checkAllDbNames" :indeterminate="indeterminateDbNames" @change="handleCheckAll"> 全选 </el-checkbox>
 | 
				
			||||||
@@ -62,8 +70,6 @@
 | 
				
			|||||||
                <el-form-item prop="remark" label="备注">
 | 
					                <el-form-item prop="remark" label="备注">
 | 
				
			||||||
                    <el-input v-model.trim="form.remark" auto-complete="off" type="textarea"></el-input>
 | 
					                    <el-input v-model.trim="form.remark" auto-complete="off" type="textarea"></el-input>
 | 
				
			||||||
                </el-form-item>
 | 
					                </el-form-item>
 | 
				
			||||||
 | 
					 | 
				
			||||||
                <procdef-select-form-item v-model="form.flowProcdefKey" />
 | 
					 | 
				
			||||||
            </el-form>
 | 
					            </el-form>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            <template #footer>
 | 
					            <template #footer>
 | 
				
			||||||
@@ -77,12 +83,10 @@
 | 
				
			|||||||
</template>
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<script lang="ts" setup>
 | 
					<script lang="ts" setup>
 | 
				
			||||||
import { toRefs, reactive, watch, ref, watchEffect } from 'vue';
 | 
					import { toRefs, reactive, watch, ref } from 'vue';
 | 
				
			||||||
import { dbApi } from './api';
 | 
					import { dbApi } from './api';
 | 
				
			||||||
import { ElMessage } from 'element-plus';
 | 
					import { ElMessage } from 'element-plus';
 | 
				
			||||||
// import TagTreeSelect from '../component/TagTreeSelect.vue';
 | 
					 | 
				
			||||||
import type { CheckboxValueType } from 'element-plus';
 | 
					import type { CheckboxValueType } from 'element-plus';
 | 
				
			||||||
import ProcdefSelectFormItem from '@/views/flow/components/ProcdefSelectFormItem.vue';
 | 
					 | 
				
			||||||
import { DbType } from '@/views/ops/db/dialect';
 | 
					import { DbType } from '@/views/ops/db/dialect';
 | 
				
			||||||
import { ResourceCodePattern } from '@/common/pattern';
 | 
					import { ResourceCodePattern } from '@/common/pattern';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -90,6 +94,7 @@ import EnumTag from '@/components/enumtag/EnumTag.vue';
 | 
				
			|||||||
import { AuthCertCiphertextTypeEnum } from '../tag/enums';
 | 
					import { AuthCertCiphertextTypeEnum } from '../tag/enums';
 | 
				
			||||||
import { resourceAuthCertApi } from '../tag/api';
 | 
					import { resourceAuthCertApi } from '../tag/api';
 | 
				
			||||||
import { TagResourceTypeEnum } from '@/common/commonEnum';
 | 
					import { TagResourceTypeEnum } from '@/common/commonEnum';
 | 
				
			||||||
 | 
					import { DbGetDbNamesMode } from './enums';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const props = defineProps({
 | 
					const props = defineProps({
 | 
				
			||||||
    visible: {
 | 
					    visible: {
 | 
				
			||||||
@@ -144,10 +149,17 @@ const rules = {
 | 
				
			|||||||
            trigger: ['change', 'blur'],
 | 
					            trigger: ['change', 'blur'],
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
    ],
 | 
					    ],
 | 
				
			||||||
    database: [
 | 
					    authCertName: [
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            required: true,
 | 
					            required: true,
 | 
				
			||||||
            message: '请添加数据库',
 | 
					            message: '请选择授权凭证',
 | 
				
			||||||
 | 
					            trigger: ['change', 'blur'],
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					    ],
 | 
				
			||||||
 | 
					    getDatabaseMode: [
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            required: true,
 | 
				
			||||||
 | 
					            message: '请选择库名获取方式',
 | 
				
			||||||
            trigger: ['change', 'blur'],
 | 
					            trigger: ['change', 'blur'],
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
    ],
 | 
					    ],
 | 
				
			||||||
@@ -169,21 +181,23 @@ const state = reactive({
 | 
				
			|||||||
    authCerts: [] as any,
 | 
					    authCerts: [] as any,
 | 
				
			||||||
    form: {
 | 
					    form: {
 | 
				
			||||||
        id: null,
 | 
					        id: null,
 | 
				
			||||||
        // tagId: [],
 | 
					 | 
				
			||||||
        name: null,
 | 
					        name: null,
 | 
				
			||||||
        code: '',
 | 
					        code: '',
 | 
				
			||||||
 | 
					        getDatabaseMode: DbGetDbNamesMode.Auto.value,
 | 
				
			||||||
        database: '',
 | 
					        database: '',
 | 
				
			||||||
        remark: '',
 | 
					        remark: '',
 | 
				
			||||||
        instanceId: null as any,
 | 
					        instanceId: null as any,
 | 
				
			||||||
        authCertName: '',
 | 
					        authCertName: '',
 | 
				
			||||||
        flowProcdefKey: '',
 | 
					 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    instances: [] as any,
 | 
					    instances: [] as any,
 | 
				
			||||||
 | 
					    loadingDbNames: false,
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const { dialogVisible, allDatabases, form, dbNamesSelected } = toRefs(state);
 | 
					const { dialogVisible, allDatabases, form, dbNamesSelected } = toRefs(state);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
watchEffect(() => {
 | 
					watch(
 | 
				
			||||||
 | 
					    () => props.visible,
 | 
				
			||||||
 | 
					    () => {
 | 
				
			||||||
        state.dialogVisible = props.visible;
 | 
					        state.dialogVisible = props.visible;
 | 
				
			||||||
        if (!state.dialogVisible) {
 | 
					        if (!state.dialogVisible) {
 | 
				
			||||||
            return;
 | 
					            return;
 | 
				
			||||||
@@ -191,17 +205,21 @@ watchEffect(() => {
 | 
				
			|||||||
        const db: any = props.db;
 | 
					        const db: any = props.db;
 | 
				
			||||||
        if (db.code) {
 | 
					        if (db.code) {
 | 
				
			||||||
            state.form = { ...db };
 | 
					            state.form = { ...db };
 | 
				
			||||||
        // state.form.tagId = newValue.db.tags.map((t: any) => t.tagId);
 | 
					            if (db.getDatabaseMode == DbGetDbNamesMode.Assign.value) {
 | 
				
			||||||
                // 将数据库名使用空格切割,获取所有数据库列表
 | 
					                // 将数据库名使用空格切割,获取所有数据库列表
 | 
				
			||||||
                state.dbNamesSelected = db.database.split(' ');
 | 
					                state.dbNamesSelected = db.database.split(' ');
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
        } else {
 | 
					        } else {
 | 
				
			||||||
        state.form = {} as any;
 | 
					            state.form = { getDatabaseMode: DbGetDbNamesMode.Auto.value } as any;
 | 
				
			||||||
            state.dbNamesSelected = [];
 | 
					            state.dbNamesSelected = [];
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
});
 | 
					    }
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const changeAuthCert = (val: string) => {
 | 
					const onChangeGetDatabaseMode = (val: any) => {
 | 
				
			||||||
    getAllDatabase(val);
 | 
					    if (val == DbGetDbNamesMode.Auto.value) {
 | 
				
			||||||
 | 
					        state.dbNamesSelected = [];
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const getAuthCerts = async () => {
 | 
					const getAuthCerts = async () => {
 | 
				
			||||||
@@ -215,6 +233,8 @@ const getAuthCerts = async () => {
 | 
				
			|||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const getAllDatabase = async (authCertName: string) => {
 | 
					const getAllDatabase = async (authCertName: string) => {
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					        state.loadingDbNames = true;
 | 
				
			||||||
        const req = { ...(props.instance as any) };
 | 
					        const req = { ...(props.instance as any) };
 | 
				
			||||||
        req.authCert = state.authCerts?.find((x: any) => x.name == authCertName);
 | 
					        req.authCert = state.authCerts?.find((x: any) => x.name == authCertName);
 | 
				
			||||||
        let dbs = await dbApi.getAllDatabase.request(req);
 | 
					        let dbs = await dbApi.getAllDatabase.request(req);
 | 
				
			||||||
@@ -225,6 +245,9 @@ const getAllDatabase = async (authCertName: string) => {
 | 
				
			|||||||
        if (instance && instance.type === DbType.oracle && dbs.length === 0) {
 | 
					        if (instance && instance.type === DbType.oracle && dbs.length === 0) {
 | 
				
			||||||
            state.allDatabases = [instance.sid];
 | 
					            state.allDatabases = [instance.sid];
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					    } finally {
 | 
				
			||||||
 | 
					        state.loadingDbNames = false;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const open = async () => {
 | 
					const open = async () => {
 | 
				
			||||||
@@ -235,18 +258,14 @@ const open = async () => {
 | 
				
			|||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const btnOk = async () => {
 | 
					const btnOk = async () => {
 | 
				
			||||||
    dbForm.value.validate(async (valid: boolean) => {
 | 
					    try {
 | 
				
			||||||
        if (!valid) {
 | 
					        await dbForm.value.validate();
 | 
				
			||||||
 | 
					    } catch (e: any) {
 | 
				
			||||||
        ElMessage.error('请正确填写信息');
 | 
					        ElMessage.error('请正确填写信息');
 | 
				
			||||||
        return false;
 | 
					        return false;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    emit('confirm', state.form);
 | 
					    emit('confirm', state.form);
 | 
				
			||||||
        // await saveDbExec();
 | 
					 | 
				
			||||||
        // ElMessage.success('保存成功');
 | 
					 | 
				
			||||||
        // emit('val-change', state.form);
 | 
					 | 
				
			||||||
        // cancel();
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const resetInputDb = () => {
 | 
					const resetInputDb = () => {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,25 +1,40 @@
 | 
				
			|||||||
<template>
 | 
					<template>
 | 
				
			||||||
    <div class="db-list">
 | 
					    <div class="db-list">
 | 
				
			||||||
 | 
					        <el-drawer
 | 
				
			||||||
 | 
					            :title="title"
 | 
				
			||||||
 | 
					            v-model="dialogVisible"
 | 
				
			||||||
 | 
					            @open="search"
 | 
				
			||||||
 | 
					            :before-close="cancel"
 | 
				
			||||||
 | 
					            :destroy-on-close="true"
 | 
				
			||||||
 | 
					            :close-on-click-modal="true"
 | 
				
			||||||
 | 
					            size="60%"
 | 
				
			||||||
 | 
					        >
 | 
				
			||||||
 | 
					            <template #header>
 | 
				
			||||||
 | 
					                <DrawerHeader :header="title" :back="cancel">
 | 
				
			||||||
 | 
					                    <template #extra>
 | 
				
			||||||
 | 
					                        <div class="mr20">
 | 
				
			||||||
 | 
					                            <span>{{ $props.instance?.tags?.[0]?.codePath }}</span>
 | 
				
			||||||
 | 
					                            <el-divider direction="vertical" border-style="dashed" />
 | 
				
			||||||
 | 
					                            <SvgIcon :name="getDbDialect($props.instance?.type).getInfo()?.icon" :size="20" />
 | 
				
			||||||
 | 
					                            <el-divider direction="vertical" border-style="dashed" />
 | 
				
			||||||
 | 
					                            <span>{{ $props.instance?.host }}:{{ $props.instance?.port }}</span>
 | 
				
			||||||
 | 
					                        </div>
 | 
				
			||||||
 | 
					                    </template>
 | 
				
			||||||
 | 
					                </DrawerHeader>
 | 
				
			||||||
 | 
					            </template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            <page-table
 | 
					            <page-table
 | 
				
			||||||
                ref="pageTableRef"
 | 
					                ref="pageTableRef"
 | 
				
			||||||
                :page-api="dbApi.dbs"
 | 
					                :page-api="dbApi.dbs"
 | 
				
			||||||
            :before-query-fn="checkRouteTagPath"
 | 
					 | 
				
			||||||
            :search-items="searchItems"
 | 
					 | 
				
			||||||
                v-model:query-form="query"
 | 
					                v-model:query-form="query"
 | 
				
			||||||
                :columns="columns"
 | 
					                :columns="columns"
 | 
				
			||||||
                lazy
 | 
					                lazy
 | 
				
			||||||
 | 
					                show-selection
 | 
				
			||||||
 | 
					                v-model:selection-data="state.selectionData"
 | 
				
			||||||
            >
 | 
					            >
 | 
				
			||||||
            <template #instanceSelect>
 | 
					                <template #tableHeader>
 | 
				
			||||||
                <el-select remote :remote-method="getInstances" v-model="query.instanceId" placeholder="输入并选择实例" filterable clearable>
 | 
					                    <el-button v-auth="perms.saveDb" type="primary" circle icon="Plus" @click="editDb(null)"> </el-button>
 | 
				
			||||||
                    <el-option v-for="item in state.instances" :key="item.id" :label="`${item.name}`" :value="item.id">
 | 
					                    <el-button v-auth="perms.delDb" :disabled="state.selectionData.length < 1" @click="deleteDb" type="danger" circle icon="delete"></el-button>
 | 
				
			||||||
                        {{ item.name }}
 | 
					 | 
				
			||||||
                        <el-divider direction="vertical" border-style="dashed" />
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                        {{ item.type }} / {{ item.host }}:{{ item.port }}
 | 
					 | 
				
			||||||
                        <el-divider direction="vertical" border-style="dashed" />
 | 
					 | 
				
			||||||
                        {{ item.username }}
 | 
					 | 
				
			||||||
                    </el-option>
 | 
					 | 
				
			||||||
                </el-select>
 | 
					 | 
				
			||||||
                </template>
 | 
					                </template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                <template #type="{ data }">
 | 
					                <template #type="{ data }">
 | 
				
			||||||
@@ -28,16 +43,12 @@
 | 
				
			|||||||
                    </el-tooltip>
 | 
					                    </el-tooltip>
 | 
				
			||||||
                </template>
 | 
					                </template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            <template #host="{ data }">
 | 
					 | 
				
			||||||
                {{ `${data.host}:${data.port}` }}
 | 
					 | 
				
			||||||
            </template>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                <template #database="{ data }">
 | 
					                <template #database="{ data }">
 | 
				
			||||||
                    <el-popover placement="bottom" :width="200" trigger="click">
 | 
					                    <el-popover placement="bottom" :width="200" trigger="click">
 | 
				
			||||||
                        <template #reference>
 | 
					                        <template #reference>
 | 
				
			||||||
                        <el-button @click="state.currentDbs = data.database" type="primary" link>查看库</el-button>
 | 
					                            <el-button @click="getDbNames(data)" type="primary" link>查看库</el-button>
 | 
				
			||||||
                        </template>
 | 
					                        </template>
 | 
				
			||||||
                    <el-table :data="filterDbs" size="small">
 | 
					                        <el-table :data="filterDbs" v-loading="state.loadingDbNames" size="small">
 | 
				
			||||||
                            <el-table-column prop="dbName" label="数据库">
 | 
					                            <el-table-column prop="dbName" label="数据库">
 | 
				
			||||||
                                <template #header>
 | 
					                                <template #header>
 | 
				
			||||||
                                    <el-input v-model="state.dbNameSearch" size="small" placeholder="库名: 输入可过滤" clearable />
 | 
					                                    <el-input v-model="state.dbNameSearch" size="small" placeholder="库名: 输入可过滤" clearable />
 | 
				
			||||||
@@ -52,7 +63,12 @@
 | 
				
			|||||||
                </template>
 | 
					                </template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                <template #action="{ data }">
 | 
					                <template #action="{ data }">
 | 
				
			||||||
 | 
					                    <el-button v-auth="perms.saveDb" @click="editDb(data)" type="primary" link>编辑</el-button>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    <el-divider direction="vertical" border-style="dashed" />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    <el-button type="primary" @click="onShowSqlExec(data)" link>SQL记录</el-button>
 | 
					                    <el-button type="primary" @click="onShowSqlExec(data)" link>SQL记录</el-button>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    <el-divider direction="vertical" border-style="dashed" />
 | 
					                    <el-divider direction="vertical" border-style="dashed" />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    <el-dropdown @command="handleMoreActionCommand">
 | 
					                    <el-dropdown @command="handleMoreActionCommand">
 | 
				
			||||||
@@ -64,9 +80,11 @@
 | 
				
			|||||||
                        </span>
 | 
					                        </span>
 | 
				
			||||||
                        <template #dropdown>
 | 
					                        <template #dropdown>
 | 
				
			||||||
                            <el-dropdown-menu>
 | 
					                            <el-dropdown-menu>
 | 
				
			||||||
                            <el-dropdown-item :command="{ type: 'detail', data }"> 详情 </el-dropdown-item>
 | 
					 | 
				
			||||||
                                <el-dropdown-item :command="{ type: 'dumpDb', data }"> 导出 </el-dropdown-item>
 | 
					                                <el-dropdown-item :command="{ type: 'dumpDb', data }"> 导出 </el-dropdown-item>
 | 
				
			||||||
                            <el-dropdown-item :command="{ type: 'backupDb', data }" v-if="actionBtns[perms.backupDb] && supportAction('backupDb', data.type)">
 | 
					                                <el-dropdown-item
 | 
				
			||||||
 | 
					                                    :command="{ type: 'backupDb', data }"
 | 
				
			||||||
 | 
					                                    v-if="actionBtns[perms.backupDb] && supportAction('backupDb', data.type)"
 | 
				
			||||||
 | 
					                                >
 | 
				
			||||||
                                    备份任务
 | 
					                                    备份任务
 | 
				
			||||||
                                </el-dropdown-item>
 | 
					                                </el-dropdown-item>
 | 
				
			||||||
                                <el-dropdown-item
 | 
					                                <el-dropdown-item
 | 
				
			||||||
@@ -86,8 +104,9 @@
 | 
				
			|||||||
                    </el-dropdown>
 | 
					                    </el-dropdown>
 | 
				
			||||||
                </template>
 | 
					                </template>
 | 
				
			||||||
            </page-table>
 | 
					            </page-table>
 | 
				
			||||||
 | 
					        </el-drawer>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        <el-dialog width="750px" :title="`${db} 数据库导出`" v-model="exportDialog.visible">
 | 
					        <el-dialog width="750px" :title="`${exportDialog.db} 数据库导出`" v-model="exportDialog.visible">
 | 
				
			||||||
            <el-row justify="space-between">
 | 
					            <el-row justify="space-between">
 | 
				
			||||||
                <el-col :span="9">
 | 
					                <el-col :span="9">
 | 
				
			||||||
                    <el-form-item label="导出内容: ">
 | 
					                    <el-form-item label="导出内容: ">
 | 
				
			||||||
@@ -168,128 +187,98 @@
 | 
				
			|||||||
            <db-restore-list :dbId="dbRestoreDialog.dbId" :dbNames="dbRestoreDialog.dbs" />
 | 
					            <db-restore-list :dbId="dbRestoreDialog.dbId" :dbNames="dbRestoreDialog.dbs" />
 | 
				
			||||||
        </el-dialog>
 | 
					        </el-dialog>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        <el-dialog v-if="infoDialog.visible" v-model="infoDialog.visible" :before-close="onBeforeCloseInfoDialog">
 | 
					        <db-edit
 | 
				
			||||||
            <el-descriptions title="详情" :column="3" border>
 | 
					            @confirm="confirmEditDb"
 | 
				
			||||||
                <el-descriptions-item :span="2" label="名称">{{ infoDialog.data?.name }}</el-descriptions-item>
 | 
					            @cancel="cancelEditDb"
 | 
				
			||||||
                <el-descriptions-item :span="1" label="id">{{ infoDialog.data?.id }}</el-descriptions-item>
 | 
					            :title="dbEditDialog.title"
 | 
				
			||||||
 | 
					            v-model:visible="dbEditDialog.visible"
 | 
				
			||||||
                <el-descriptions-item :span="3" label="关联标签"><ResourceTags :tags="infoDialog.data.tags" /></el-descriptions-item>
 | 
					            :instance="props.instance"
 | 
				
			||||||
                <el-descriptions-item :span="3" label="数据库实例名称">{{ infoDialog.instance?.name }}</el-descriptions-item>
 | 
					            v-model:db="dbEditDialog.data"
 | 
				
			||||||
 | 
					        ></db-edit>
 | 
				
			||||||
                <el-descriptions-item :span="2" label="主机">{{ infoDialog.instance?.host }}</el-descriptions-item>
 | 
					 | 
				
			||||||
                <el-descriptions-item :span="1" label="端口">{{ infoDialog.instance?.port }}</el-descriptions-item>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                <el-descriptions-item :span="2" label="授权凭证">{{ infoDialog.instance.authCertName }}</el-descriptions-item>
 | 
					 | 
				
			||||||
                <el-descriptions-item :span="1" label="类型">
 | 
					 | 
				
			||||||
                    <SvgIcon :name="getDbDialect(infoDialog.instance?.type).getInfo().icon" :size="20" />{{ infoDialog.instance?.type }}
 | 
					 | 
				
			||||||
                </el-descriptions-item>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                <el-descriptions-item :span="3" label="数据库">{{ infoDialog.data?.database }}</el-descriptions-item>
 | 
					 | 
				
			||||||
                <el-descriptions-item :span="3" label="备注">{{ infoDialog.data?.remark }}</el-descriptions-item>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                <el-descriptions-item :span="3" label="工单流程key">{{ infoDialog.data?.flowProcdefKey }}</el-descriptions-item>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                <el-descriptions-item :span="2" label="创建时间">{{ dateFormat(infoDialog.data?.createTime) }} </el-descriptions-item>
 | 
					 | 
				
			||||||
                <el-descriptions-item :span="1" label="创建者">{{ infoDialog.data?.creator }}</el-descriptions-item>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                <el-descriptions-item :span="2" label="更新时间">{{ dateFormat(infoDialog.data?.updateTime) }} </el-descriptions-item>
 | 
					 | 
				
			||||||
                <el-descriptions-item :span="1" label="修改者">{{ infoDialog.data?.modifier }}</el-descriptions-item>
 | 
					 | 
				
			||||||
            </el-descriptions>
 | 
					 | 
				
			||||||
        </el-dialog>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        <db-edit @val-change="search()" :title="dbEditDialog.title" v-model:visible="dbEditDialog.visible" v-model:db="dbEditDialog.data"></db-edit>
 | 
					 | 
				
			||||||
    </div>
 | 
					    </div>
 | 
				
			||||||
</template>
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<script lang="ts" setup>
 | 
					<script lang="ts" setup>
 | 
				
			||||||
import { computed, defineAsyncComponent, onMounted, reactive, ref, Ref, toRefs } from 'vue';
 | 
					import { computed, defineAsyncComponent, reactive, ref, Ref, toRefs } from 'vue';
 | 
				
			||||||
import { dbApi } from './api';
 | 
					import { dbApi } from './api';
 | 
				
			||||||
import config from '@/common/config';
 | 
					import config from '@/common/config';
 | 
				
			||||||
import { joinClientParams } from '@/common/request';
 | 
					import { joinClientParams } from '@/common/request';
 | 
				
			||||||
import { isTrue } from '@/common/assert';
 | 
					import { isTrue } from '@/common/assert';
 | 
				
			||||||
import { dateFormat } from '@/common/utils/date';
 | 
					 | 
				
			||||||
import PageTable from '@/components/pagetable/PageTable.vue';
 | 
					import PageTable from '@/components/pagetable/PageTable.vue';
 | 
				
			||||||
import { TableColumn } from '@/components/pagetable';
 | 
					import { TableColumn } from '@/components/pagetable';
 | 
				
			||||||
import { hasPerms } from '@/components/auth/auth';
 | 
					import { hasPerms } from '@/components/auth/auth';
 | 
				
			||||||
import DbSqlExecLog from './DbSqlExecLog.vue';
 | 
					import DbSqlExecLog from './DbSqlExecLog.vue';
 | 
				
			||||||
import { DbType } from './dialect';
 | 
					import { DbType } from './dialect';
 | 
				
			||||||
import { TagResourceTypeEnum } from '@/common/commonEnum';
 | 
					 | 
				
			||||||
import { useRoute } from 'vue-router';
 | 
					 | 
				
			||||||
import { getDbDialect } from './dialect/index';
 | 
					import { getDbDialect } from './dialect/index';
 | 
				
			||||||
import { getTagPathSearchItem } from '../component/tag';
 | 
					 | 
				
			||||||
import { SearchItem } from '@/components/SearchForm';
 | 
					 | 
				
			||||||
import DbBackupList from './DbBackupList.vue';
 | 
					import DbBackupList from './DbBackupList.vue';
 | 
				
			||||||
import DbBackupHistoryList from './DbBackupHistoryList.vue';
 | 
					import DbBackupHistoryList from './DbBackupHistoryList.vue';
 | 
				
			||||||
import DbRestoreList from './DbRestoreList.vue';
 | 
					import DbRestoreList from './DbRestoreList.vue';
 | 
				
			||||||
import ResourceTags from '../component/ResourceTags.vue';
 | 
					import ResourceTags from '../component/ResourceTags.vue';
 | 
				
			||||||
import { sleep } from '@/common/utils/loading';
 | 
					import { sleep } from '@/common/utils/loading';
 | 
				
			||||||
 | 
					import { DbGetDbNamesMode } from './enums';
 | 
				
			||||||
 | 
					import { DbInst } from './db';
 | 
				
			||||||
 | 
					import { ElMessage, ElMessageBox } from 'element-plus';
 | 
				
			||||||
 | 
					import DrawerHeader from '@/components/drawer-header/DrawerHeader.vue';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const DbEdit = defineAsyncComponent(() => import('./DbEdit.vue'));
 | 
					const DbEdit = defineAsyncComponent(() => import('./DbEdit.vue'));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const searchItems = [
 | 
					const props = defineProps({
 | 
				
			||||||
    getTagPathSearchItem(TagResourceTypeEnum.DbName.value),
 | 
					    instance: {
 | 
				
			||||||
    SearchItem.slot('instanceId', '实例', 'instanceSelect'),
 | 
					        type: [Object],
 | 
				
			||||||
    SearchItem.input('code', '编号'),
 | 
					        required: true,
 | 
				
			||||||
];
 | 
					    },
 | 
				
			||||||
 | 
					    title: {
 | 
				
			||||||
 | 
					        type: String,
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const dialogVisible = defineModel<boolean>('visible');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const emit = defineEmits(['cancel']);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const columns = ref([
 | 
					const columns = ref([
 | 
				
			||||||
    TableColumn.new('tags[0].tagPath', '关联标签').isSlot('tagPath').setAddWidth(20),
 | 
					 | 
				
			||||||
    TableColumn.new('name', '名称'),
 | 
					    TableColumn.new('name', '名称'),
 | 
				
			||||||
    TableColumn.new('type', '类型').isSlot().setAddWidth(-15).alignCenter(),
 | 
					 | 
				
			||||||
    TableColumn.new('instanceName', '实例名'),
 | 
					 | 
				
			||||||
    TableColumn.new('host', 'ip:port').isSlot().setAddWidth(40),
 | 
					 | 
				
			||||||
    TableColumn.new('authCertName', '授权凭证'),
 | 
					    TableColumn.new('authCertName', '授权凭证'),
 | 
				
			||||||
 | 
					    TableColumn.new('getDatabaseMode', '获库方式').typeTag(DbGetDbNamesMode),
 | 
				
			||||||
    TableColumn.new('database', '库').isSlot().setMinWidth(80),
 | 
					    TableColumn.new('database', '库').isSlot().setMinWidth(80),
 | 
				
			||||||
    TableColumn.new('flowProcdefKey', '关联流程'),
 | 
					 | 
				
			||||||
    TableColumn.new('remark', '备注'),
 | 
					    TableColumn.new('remark', '备注'),
 | 
				
			||||||
    TableColumn.new('code', '编号'),
 | 
					    TableColumn.new('code', '编号'),
 | 
				
			||||||
 | 
					    TableColumn.new('action', '操作').isSlot().setMinWidth(210).fixedRight().alignCenter(),
 | 
				
			||||||
]);
 | 
					]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const perms = {
 | 
					const perms = {
 | 
				
			||||||
 | 
					    base: 'db',
 | 
				
			||||||
 | 
					    saveDb: 'db:save',
 | 
				
			||||||
 | 
					    delDb: 'db:del',
 | 
				
			||||||
    backupDb: 'db:backup',
 | 
					    backupDb: 'db:backup',
 | 
				
			||||||
    restoreDb: 'db:restore',
 | 
					    restoreDb: 'db:restore',
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// 该用户拥有的的操作列按钮权限
 | 
					 | 
				
			||||||
// const actionBtns = hasPerms([perms.base, perms.saveDb]);
 | 
					 | 
				
			||||||
const actionBtns = hasPerms(Object.values(perms));
 | 
					const actionBtns = hasPerms(Object.values(perms));
 | 
				
			||||||
const actionColumn = TableColumn.new('action', '操作').isSlot().setMinWidth(180).fixedRight().alignCenter();
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
const route = useRoute();
 | 
					 | 
				
			||||||
const pageTableRef: Ref<any> = ref(null);
 | 
					const pageTableRef: Ref<any> = ref(null);
 | 
				
			||||||
const state = reactive({
 | 
					const state = reactive({
 | 
				
			||||||
    row: {} as any,
 | 
					    loadingDbNames: false,
 | 
				
			||||||
    dbId: 0,
 | 
					    currentDbNames: [],
 | 
				
			||||||
    db: '',
 | 
					 | 
				
			||||||
    currentDbs: '',
 | 
					 | 
				
			||||||
    dbNameSearch: '',
 | 
					    dbNameSearch: '',
 | 
				
			||||||
    instances: [] as any,
 | 
					    instances: [] as any,
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * 选中的数据
 | 
					     * 选中的数据
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    selectionData: [],
 | 
					    selectionData: [] as any,
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * 查询条件
 | 
					     * 查询条件
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    query: {
 | 
					    query: {
 | 
				
			||||||
        tagPath: '',
 | 
					        instanceId: 0,
 | 
				
			||||||
        instanceId: null,
 | 
					 | 
				
			||||||
        pageNum: 1,
 | 
					        pageNum: 1,
 | 
				
			||||||
        pageSize: 0,
 | 
					        pageSize: 0,
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    infoDialog: {
 | 
					 | 
				
			||||||
        visible: false,
 | 
					 | 
				
			||||||
        data: null as any,
 | 
					 | 
				
			||||||
        instance: null as any,
 | 
					 | 
				
			||||||
        query: {
 | 
					 | 
				
			||||||
            instanceId: 0,
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    // sql执行记录弹框
 | 
					    // sql执行记录弹框
 | 
				
			||||||
    sqlExecLogDialog: {
 | 
					    sqlExecLogDialog: {
 | 
				
			||||||
        title: '',
 | 
					        title: '',
 | 
				
			||||||
        visible: false,
 | 
					        visible: false,
 | 
				
			||||||
        dbs: [],
 | 
					        dbs: [] as any,
 | 
				
			||||||
        dbId: 0,
 | 
					        dbId: 0,
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    // 数据库备份弹框
 | 
					    // 数据库备份弹框
 | 
				
			||||||
@@ -320,6 +309,7 @@ const state = reactive({
 | 
				
			|||||||
    exportDialog: {
 | 
					    exportDialog: {
 | 
				
			||||||
        visible: false,
 | 
					        visible: false,
 | 
				
			||||||
        dbId: 0,
 | 
					        dbId: 0,
 | 
				
			||||||
 | 
					        db: '',
 | 
				
			||||||
        type: 3,
 | 
					        type: 3,
 | 
				
			||||||
        data: [] as any,
 | 
					        data: [] as any,
 | 
				
			||||||
        value: [],
 | 
					        value: [],
 | 
				
			||||||
@@ -338,64 +328,77 @@ const state = reactive({
 | 
				
			|||||||
    },
 | 
					    },
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const { db, query, infoDialog, sqlExecLogDialog, exportDialog, dbEditDialog, dbBackupDialog, dbBackupHistoryDialog, dbRestoreDialog } = toRefs(state);
 | 
					const { query, sqlExecLogDialog, exportDialog, dbEditDialog, dbBackupDialog, dbBackupHistoryDialog, dbRestoreDialog } = toRefs(state);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
onMounted(async () => {
 | 
					const search = async () => {
 | 
				
			||||||
    if (Object.keys(actionBtns).length > 0) {
 | 
					    state.query.instanceId = props.instance?.id;
 | 
				
			||||||
        columns.value.push(actionColumn);
 | 
					    pageTableRef.value.search();
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const getDbNames = async (db: any) => {
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					        state.loadingDbNames = true;
 | 
				
			||||||
 | 
					        state.currentDbNames = await DbInst.getDbNames(db);
 | 
				
			||||||
 | 
					    } finally {
 | 
				
			||||||
 | 
					        state.loadingDbNames = false;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    search();
 | 
					};
 | 
				
			||||||
});
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
const filterDbs = computed(() => {
 | 
					const filterDbs = computed(() => {
 | 
				
			||||||
    const dbsStr = state.currentDbs;
 | 
					    const dbNames = state.currentDbNames;
 | 
				
			||||||
    if (!dbsStr) {
 | 
					    if (!dbNames) {
 | 
				
			||||||
        return [];
 | 
					        return [];
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    const dbs = dbsStr.split(' ').map((db: any) => {
 | 
					    const dbNameObjs = dbNames.map((x) => {
 | 
				
			||||||
        return { dbName: db };
 | 
					        return {
 | 
				
			||||||
 | 
					            dbName: x,
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
    return dbs.filter((db: any) => {
 | 
					    return dbNameObjs.filter((db: any) => {
 | 
				
			||||||
        return db.dbName.includes(state.dbNameSearch);
 | 
					        return db.dbName.includes(state.dbNameSearch);
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const checkRouteTagPath = (query: any) => {
 | 
					const editDb = (data: any) => {
 | 
				
			||||||
    if (route.query.tagPath) {
 | 
					 | 
				
			||||||
        query.tagPath = route.query.tagPath as string;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    return query;
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const search = async (tagPath: string = '') => {
 | 
					 | 
				
			||||||
    if (tagPath) {
 | 
					 | 
				
			||||||
        state.query.tagPath = tagPath;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    pageTableRef.value.search();
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const showInfo = async (info: any) => {
 | 
					 | 
				
			||||||
    state.infoDialog.data = info;
 | 
					 | 
				
			||||||
    state.infoDialog.query.instanceId = info.instanceId;
 | 
					 | 
				
			||||||
    const res = await dbApi.getInstance.request(state.infoDialog.query);
 | 
					 | 
				
			||||||
    state.infoDialog.instance = res;
 | 
					 | 
				
			||||||
    state.infoDialog.visible = true;
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const onBeforeCloseInfoDialog = () => {
 | 
					 | 
				
			||||||
    state.infoDialog.visible = false;
 | 
					 | 
				
			||||||
    state.infoDialog.data = null;
 | 
					 | 
				
			||||||
    state.infoDialog.instance = null;
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const getInstances = async (instanceName = '') => {
 | 
					 | 
				
			||||||
    if (!instanceName) {
 | 
					 | 
				
			||||||
        state.instances = [];
 | 
					 | 
				
			||||||
        return;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    const data = await dbApi.instances.request({ name: instanceName });
 | 
					 | 
				
			||||||
    if (data) {
 | 
					    if (data) {
 | 
				
			||||||
        state.instances = data.list;
 | 
					        state.dbEditDialog.data = { ...data };
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					        state.dbEditDialog.data = {
 | 
				
			||||||
 | 
					            instanceId: props.instance.id,
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    state.dbEditDialog.title = data ? '编辑数据库' : '新增数据库';
 | 
				
			||||||
 | 
					    state.dbEditDialog.visible = true;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const confirmEditDb = async (db: any) => {
 | 
				
			||||||
 | 
					    db.instanceId = props.instance.id;
 | 
				
			||||||
 | 
					    await dbApi.saveDb.request(db);
 | 
				
			||||||
 | 
					    ElMessage.success('保存成功');
 | 
				
			||||||
 | 
					    search();
 | 
				
			||||||
 | 
					    cancelEditDb();
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const cancelEditDb = () => {
 | 
				
			||||||
 | 
					    state.dbEditDialog.visible = false;
 | 
				
			||||||
 | 
					    state.dbEditDialog.data = {};
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const deleteDb = async () => {
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					        await ElMessageBox.confirm(`确定删除【${state.selectionData.map((x: any) => x.name).join(', ')}】库?`, '提示', {
 | 
				
			||||||
 | 
					            confirmButtonText: '确定',
 | 
				
			||||||
 | 
					            cancelButtonText: '取消',
 | 
				
			||||||
 | 
					            type: 'warning',
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					        for (let db of state.selectionData) {
 | 
				
			||||||
 | 
					            await dbApi.deleteDb.request({ id: db.id });
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        ElMessage.success('删除成功');
 | 
				
			||||||
 | 
					    } catch (err) {
 | 
				
			||||||
 | 
					        //
 | 
				
			||||||
 | 
					    } finally {
 | 
				
			||||||
 | 
					        search();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -403,10 +406,6 @@ const handleMoreActionCommand = (commond: any) => {
 | 
				
			|||||||
    const data = commond.data;
 | 
					    const data = commond.data;
 | 
				
			||||||
    const type = commond.type;
 | 
					    const type = commond.type;
 | 
				
			||||||
    switch (type) {
 | 
					    switch (type) {
 | 
				
			||||||
        case 'detail': {
 | 
					 | 
				
			||||||
            showInfo(data);
 | 
					 | 
				
			||||||
            return;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        case 'dumpDb': {
 | 
					        case 'dumpDb': {
 | 
				
			||||||
            onDumpDbs(data);
 | 
					            onDumpDbs(data);
 | 
				
			||||||
            return;
 | 
					            return;
 | 
				
			||||||
@@ -429,7 +428,9 @@ const handleMoreActionCommand = (commond: any) => {
 | 
				
			|||||||
const onShowSqlExec = async (row: any) => {
 | 
					const onShowSqlExec = async (row: any) => {
 | 
				
			||||||
    state.sqlExecLogDialog.title = `${row.name}`;
 | 
					    state.sqlExecLogDialog.title = `${row.name}`;
 | 
				
			||||||
    state.sqlExecLogDialog.dbId = row.id;
 | 
					    state.sqlExecLogDialog.dbId = row.id;
 | 
				
			||||||
    state.sqlExecLogDialog.dbs = row.database.split(' ');
 | 
					    DbInst.getDbNames(row).then((res) => {
 | 
				
			||||||
 | 
					        state.sqlExecLogDialog.dbs = res;
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
    state.sqlExecLogDialog.visible = true;
 | 
					    state.sqlExecLogDialog.visible = true;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -442,26 +443,32 @@ const onBeforeCloseSqlExecDialog = () => {
 | 
				
			|||||||
const onShowDbBackupDialog = async (row: any) => {
 | 
					const onShowDbBackupDialog = async (row: any) => {
 | 
				
			||||||
    state.dbBackupDialog.title = `${row.name}`;
 | 
					    state.dbBackupDialog.title = `${row.name}`;
 | 
				
			||||||
    state.dbBackupDialog.dbId = row.id;
 | 
					    state.dbBackupDialog.dbId = row.id;
 | 
				
			||||||
    state.dbBackupDialog.dbs = row.database.split(' ');
 | 
					    DbInst.getDbNames(row).then((res) => {
 | 
				
			||||||
 | 
					        state.sqlExecLogDialog.dbs = res;
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
    state.dbBackupDialog.visible = true;
 | 
					    state.dbBackupDialog.visible = true;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const onShowDbBackupHistoryDialog = async (row: any) => {
 | 
					const onShowDbBackupHistoryDialog = async (row: any) => {
 | 
				
			||||||
    state.dbBackupHistoryDialog.title = `${row.name}`;
 | 
					    state.dbBackupHistoryDialog.title = `${row.name}`;
 | 
				
			||||||
    state.dbBackupHistoryDialog.dbId = row.id;
 | 
					    state.dbBackupHistoryDialog.dbId = row.id;
 | 
				
			||||||
    state.dbBackupHistoryDialog.dbs = row.database.split(' ');
 | 
					    DbInst.getDbNames(row).then((res) => {
 | 
				
			||||||
 | 
					        state.sqlExecLogDialog.dbs = res;
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
    state.dbBackupHistoryDialog.visible = true;
 | 
					    state.dbBackupHistoryDialog.visible = true;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const onShowDbRestoreDialog = async (row: any) => {
 | 
					const onShowDbRestoreDialog = async (row: any) => {
 | 
				
			||||||
    state.dbRestoreDialog.title = `${row.name}`;
 | 
					    state.dbRestoreDialog.title = `${row.name}`;
 | 
				
			||||||
    state.dbRestoreDialog.dbId = row.id;
 | 
					    state.dbRestoreDialog.dbId = row.id;
 | 
				
			||||||
    state.dbRestoreDialog.dbs = row.database.split(' ');
 | 
					    DbInst.getDbNames(row).then((res) => {
 | 
				
			||||||
 | 
					        state.sqlExecLogDialog.dbs = res;
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
    state.dbRestoreDialog.visible = true;
 | 
					    state.dbRestoreDialog.visible = true;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const onDumpDbs = async (row: any) => {
 | 
					const onDumpDbs = async (row: any) => {
 | 
				
			||||||
    const dbs = row.database.split(' ');
 | 
					    const dbs = await DbInst.getDbNames(row);
 | 
				
			||||||
    const data = [];
 | 
					    const data = [];
 | 
				
			||||||
    for (let name of dbs) {
 | 
					    for (let name of dbs) {
 | 
				
			||||||
        data.push({
 | 
					        data.push({
 | 
				
			||||||
@@ -469,6 +476,7 @@ const onDumpDbs = async (row: any) => {
 | 
				
			|||||||
            label: name,
 | 
					            label: name,
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					    state.exportDialog.db = row.name;
 | 
				
			||||||
    state.exportDialog.value = [];
 | 
					    state.exportDialog.value = [];
 | 
				
			||||||
    state.exportDialog.data = data;
 | 
					    state.exportDialog.data = data;
 | 
				
			||||||
    state.exportDialog.dbId = row.id;
 | 
					    state.exportDialog.dbId = row.id;
 | 
				
			||||||
@@ -512,7 +520,10 @@ const supportAction = (action: string, dbType: string): boolean => {
 | 
				
			|||||||
    return actions.includes(action);
 | 
					    return actions.includes(action);
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
defineExpose({ search });
 | 
					const cancel = () => {
 | 
				
			||||||
 | 
					    dialogVisible.value = false;
 | 
				
			||||||
 | 
					    emit('cancel');
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
</script>
 | 
					</script>
 | 
				
			||||||
<style lang="scss">
 | 
					<style lang="scss">
 | 
				
			||||||
.db-list {
 | 
					.db-list {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -45,14 +45,14 @@
 | 
				
			|||||||
            <el-descriptions :column="1" border>
 | 
					            <el-descriptions :column="1" border>
 | 
				
			||||||
                <el-descriptions-item :span="1" label="数据库名称">{{ infoDialog.data.dbName }}</el-descriptions-item>
 | 
					                <el-descriptions-item :span="1" label="数据库名称">{{ infoDialog.data.dbName }}</el-descriptions-item>
 | 
				
			||||||
                <el-descriptions-item v-if="infoDialog.data.pointInTime" :span="1" label="恢复时间点">{{
 | 
					                <el-descriptions-item v-if="infoDialog.data.pointInTime" :span="1" label="恢复时间点">{{
 | 
				
			||||||
                    dateFormat(infoDialog.data.pointInTime)
 | 
					                    formatDate(infoDialog.data.pointInTime)
 | 
				
			||||||
                }}</el-descriptions-item>
 | 
					                }}</el-descriptions-item>
 | 
				
			||||||
                <el-descriptions-item v-if="!infoDialog.data.pointInTime" :span="1" label="数据库备份">{{
 | 
					                <el-descriptions-item v-if="!infoDialog.data.pointInTime" :span="1" label="数据库备份">{{
 | 
				
			||||||
                    infoDialog.data.dbBackupHistoryName
 | 
					                    infoDialog.data.dbBackupHistoryName
 | 
				
			||||||
                }}</el-descriptions-item>
 | 
					                }}</el-descriptions-item>
 | 
				
			||||||
                <el-descriptions-item :span="1" label="开始时间">{{ dateFormat(infoDialog.data.startTime) }}</el-descriptions-item>
 | 
					                <el-descriptions-item :span="1" label="开始时间">{{ formatDate(infoDialog.data.startTime) }}</el-descriptions-item>
 | 
				
			||||||
                <el-descriptions-item :span="1" label="是否启用">{{ infoDialog.data.enabledDesc }}</el-descriptions-item>
 | 
					                <el-descriptions-item :span="1" label="是否启用">{{ infoDialog.data.enabledDesc }}</el-descriptions-item>
 | 
				
			||||||
                <el-descriptions-item :span="1" label="执行时间">{{ dateFormat(infoDialog.data.lastTime) }}</el-descriptions-item>
 | 
					                <el-descriptions-item :span="1" label="执行时间">{{ formatDate(infoDialog.data.lastTime) }}</el-descriptions-item>
 | 
				
			||||||
                <el-descriptions-item :span="1" label="执行结果">{{ infoDialog.data.lastResult }}</el-descriptions-item>
 | 
					                <el-descriptions-item :span="1" label="执行结果">{{ infoDialog.data.lastResult }}</el-descriptions-item>
 | 
				
			||||||
            </el-descriptions>
 | 
					            </el-descriptions>
 | 
				
			||||||
        </el-dialog>
 | 
					        </el-dialog>
 | 
				
			||||||
@@ -66,7 +66,7 @@ import PageTable from '@/components/pagetable/PageTable.vue';
 | 
				
			|||||||
import { TableColumn } from '@/components/pagetable';
 | 
					import { TableColumn } from '@/components/pagetable';
 | 
				
			||||||
import { SearchItem } from '@/components/SearchForm';
 | 
					import { SearchItem } from '@/components/SearchForm';
 | 
				
			||||||
import { ElMessage, ElMessageBox } from 'element-plus';
 | 
					import { ElMessage, ElMessageBox } from 'element-plus';
 | 
				
			||||||
import { dateFormat } from '@/common/utils/date';
 | 
					import { formatDate } from '@/common/utils/format';
 | 
				
			||||||
const DbRestoreEdit = defineAsyncComponent(() => import('./DbRestoreEdit.vue'));
 | 
					const DbRestoreEdit = defineAsyncComponent(() => import('./DbRestoreEdit.vue'));
 | 
				
			||||||
const pageTableRef: Ref<any> = ref(null);
 | 
					const pageTableRef: Ref<any> = ref(null);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -259,6 +259,7 @@ const handleSrcTableCheckChange = (data: { id: string; name: string }, checked:
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    if (data.id && (data.id + '').startsWith('list-item')) {
 | 
					    if (data.id && (data.id + '').startsWith('list-item')) {
 | 
				
			||||||
 | 
					        //
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,171 +0,0 @@
 | 
				
			|||||||
<template>
 | 
					 | 
				
			||||||
    <div>
 | 
					 | 
				
			||||||
        <el-drawer :title="title" v-model="dialogVisible" :before-close="cancel" :destroy-on-close="true" :close-on-click-modal="false" size="50%">
 | 
					 | 
				
			||||||
            <template #header>
 | 
					 | 
				
			||||||
                <DrawerHeader :header="title" :back="cancel" />
 | 
					 | 
				
			||||||
            </template>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            <el-table :data="state.dbs" stripe>
 | 
					 | 
				
			||||||
                <el-table-column prop="name" label="名称" show-overflow-tooltip min-width="100"> </el-table-column>
 | 
					 | 
				
			||||||
                <el-table-column prop="authCertName" label="授权凭证" min-width="120" show-overflow-tooltip> </el-table-column>
 | 
					 | 
				
			||||||
                <el-table-column prop="database" label="库" min-width="80">
 | 
					 | 
				
			||||||
                    <template #default="scope">
 | 
					 | 
				
			||||||
                        <el-popover placement="bottom" :width="200" trigger="click">
 | 
					 | 
				
			||||||
                            <template #reference>
 | 
					 | 
				
			||||||
                                <el-button @click="state.currentDbs = scope.row.database" type="primary" link>查看库</el-button>
 | 
					 | 
				
			||||||
                            </template>
 | 
					 | 
				
			||||||
                            <el-table :data="filterDbs" size="small">
 | 
					 | 
				
			||||||
                                <el-table-column prop="dbName" label="数据库">
 | 
					 | 
				
			||||||
                                    <template #header>
 | 
					 | 
				
			||||||
                                        <el-input v-model="state.dbNameSearch" size="small" placeholder="库名: 输入可过滤" clearable />
 | 
					 | 
				
			||||||
                                    </template>
 | 
					 | 
				
			||||||
                                </el-table-column>
 | 
					 | 
				
			||||||
                            </el-table>
 | 
					 | 
				
			||||||
                        </el-popover>
 | 
					 | 
				
			||||||
                    </template>
 | 
					 | 
				
			||||||
                </el-table-column>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                <el-table-column prop="remark" label="备注" show-overflow-tooltip min-width="120"> </el-table-column>
 | 
					 | 
				
			||||||
                <el-table-column prop="flowProcdefKey" label="关联流程" min-width="120" show-overflow-tooltip> </el-table-column>
 | 
					 | 
				
			||||||
                <el-table-column prop="code" label="编号" show-overflow-tooltip min-width="120"> </el-table-column>
 | 
					 | 
				
			||||||
                <el-table-column min-wdith="120px">
 | 
					 | 
				
			||||||
                    <template #header>
 | 
					 | 
				
			||||||
                        操作
 | 
					 | 
				
			||||||
                        <el-button v-auth="perms.saveDb" type="primary" circle size="small" icon="Plus" @click="editDb(null)"> </el-button>
 | 
					 | 
				
			||||||
                    </template>
 | 
					 | 
				
			||||||
                    <template #default="scope">
 | 
					 | 
				
			||||||
                        <el-button v-auth="perms.saveDb" @click="editDb(scope.row)" type="primary" icon="edit" link></el-button>
 | 
					 | 
				
			||||||
                        <el-button class="ml1" v-auth="perms.delDb" type="danger" @click="deleteDb(scope.row)" icon="delete" link></el-button>
 | 
					 | 
				
			||||||
                    </template>
 | 
					 | 
				
			||||||
                </el-table-column>
 | 
					 | 
				
			||||||
            </el-table>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            <db-edit
 | 
					 | 
				
			||||||
                @confirm="confirmEditDb"
 | 
					 | 
				
			||||||
                @cancel="cancelEditDb"
 | 
					 | 
				
			||||||
                :title="dbEditDialog.title"
 | 
					 | 
				
			||||||
                v-model:visible="dbEditDialog.visible"
 | 
					 | 
				
			||||||
                :instance="props.instance"
 | 
					 | 
				
			||||||
                v-model:db="dbEditDialog.data"
 | 
					 | 
				
			||||||
            ></db-edit>
 | 
					 | 
				
			||||||
        </el-drawer>
 | 
					 | 
				
			||||||
    </div>
 | 
					 | 
				
			||||||
</template>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
<script lang="ts" setup>
 | 
					 | 
				
			||||||
import { computed, reactive, toRefs, watchEffect } from 'vue';
 | 
					 | 
				
			||||||
import { dbApi } from './api';
 | 
					 | 
				
			||||||
import { ElMessage, ElMessageBox } from 'element-plus';
 | 
					 | 
				
			||||||
import DrawerHeader from '@/components/drawer-header/DrawerHeader.vue';
 | 
					 | 
				
			||||||
import DbEdit from './DbEdit.vue';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const props = defineProps({
 | 
					 | 
				
			||||||
    visible: {
 | 
					 | 
				
			||||||
        type: Boolean,
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    instance: {
 | 
					 | 
				
			||||||
        type: [Object],
 | 
					 | 
				
			||||||
        required: true,
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    title: {
 | 
					 | 
				
			||||||
        type: String,
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
});
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const perms = {
 | 
					 | 
				
			||||||
    base: 'db',
 | 
					 | 
				
			||||||
    saveDb: 'db:save',
 | 
					 | 
				
			||||||
    delDb: 'db:del',
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
//定义事件
 | 
					 | 
				
			||||||
const emit = defineEmits(['update:visible', 'cancel', 'val-change']);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const state = reactive({
 | 
					 | 
				
			||||||
    dialogVisible: false,
 | 
					 | 
				
			||||||
    dbs: [] as any,
 | 
					 | 
				
			||||||
    currentDbs: '', // 当前数据库名,空格分割库名
 | 
					 | 
				
			||||||
    dbNameSearch: '',
 | 
					 | 
				
			||||||
    dbEditDialog: {
 | 
					 | 
				
			||||||
        visible: false,
 | 
					 | 
				
			||||||
        data: null as any,
 | 
					 | 
				
			||||||
        title: '新增数据库',
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
});
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const { dialogVisible, dbEditDialog } = toRefs(state);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
watchEffect(() => {
 | 
					 | 
				
			||||||
    state.dialogVisible = props.visible;
 | 
					 | 
				
			||||||
    if (!state.dialogVisible) {
 | 
					 | 
				
			||||||
        return;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    getDbs();
 | 
					 | 
				
			||||||
});
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const filterDbs = computed(() => {
 | 
					 | 
				
			||||||
    const dbsStr = state.currentDbs;
 | 
					 | 
				
			||||||
    if (!dbsStr) {
 | 
					 | 
				
			||||||
        return [];
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    const dbs = dbsStr.split(' ').map((db: any) => {
 | 
					 | 
				
			||||||
        return { dbName: db };
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
    return dbs.filter((db: any) => {
 | 
					 | 
				
			||||||
        return db.dbName.includes(state.dbNameSearch);
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
});
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const cancel = () => {
 | 
					 | 
				
			||||||
    emit('update:visible', false);
 | 
					 | 
				
			||||||
    emit('cancel');
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const getDbs = () => {
 | 
					 | 
				
			||||||
    dbApi.dbs.request({ pageSize: 200, instanceId: props.instance.id }).then((res: any) => {
 | 
					 | 
				
			||||||
        state.dbs = res.list || [];
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const editDb = (data: any) => {
 | 
					 | 
				
			||||||
    if (data) {
 | 
					 | 
				
			||||||
        state.dbEditDialog.data = { ...data };
 | 
					 | 
				
			||||||
    } else {
 | 
					 | 
				
			||||||
        state.dbEditDialog.data = {
 | 
					 | 
				
			||||||
            instanceId: props.instance.id,
 | 
					 | 
				
			||||||
        };
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    state.dbEditDialog.title = data ? '编辑数据库' : '新增数据库';
 | 
					 | 
				
			||||||
    state.dbEditDialog.visible = true;
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const deleteDb = async (db: any) => {
 | 
					 | 
				
			||||||
    try {
 | 
					 | 
				
			||||||
        await ElMessageBox.confirm(`确定删除【${db.name}】库?`, '提示', {
 | 
					 | 
				
			||||||
            confirmButtonText: '确定',
 | 
					 | 
				
			||||||
            cancelButtonText: '取消',
 | 
					 | 
				
			||||||
            type: 'warning',
 | 
					 | 
				
			||||||
        });
 | 
					 | 
				
			||||||
        await dbApi.deleteDb.request({ id: db.id });
 | 
					 | 
				
			||||||
        ElMessage.success('删除成功');
 | 
					 | 
				
			||||||
        getDbs();
 | 
					 | 
				
			||||||
    } catch (err) {
 | 
					 | 
				
			||||||
        //
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const confirmEditDb = async (db: any) => {
 | 
					 | 
				
			||||||
    db.instanceId = props.instance.id;
 | 
					 | 
				
			||||||
    await dbApi.saveDb.request(db);
 | 
					 | 
				
			||||||
    ElMessage.success('保存成功');
 | 
					 | 
				
			||||||
    getDbs();
 | 
					 | 
				
			||||||
    cancelEditDb();
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const cancelEditDb = () => {
 | 
					 | 
				
			||||||
    state.dbEditDialog.visible = false;
 | 
					 | 
				
			||||||
    state.dbEditDialog.data = {};
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
</script>
 | 
					 | 
				
			||||||
<style lang="scss"></style>
 | 
					 | 
				
			||||||
@@ -267,8 +267,9 @@ const getReqForm = async () => {
 | 
				
			|||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const testConn = async (authCert: any) => {
 | 
					const testConn = async (authCert: any) => {
 | 
				
			||||||
    dbForm.value.validate(async (valid: boolean) => {
 | 
					    try {
 | 
				
			||||||
        if (!valid) {
 | 
					        await dbForm.value.validate();
 | 
				
			||||||
 | 
					    } catch (e: any) {
 | 
				
			||||||
        ElMessage.error('请正确填写信息');
 | 
					        ElMessage.error('请正确填写信息');
 | 
				
			||||||
        return false;
 | 
					        return false;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@@ -277,12 +278,12 @@ const testConn = async (authCert: any) => {
 | 
				
			|||||||
    state.submitForm.authCerts = [authCert];
 | 
					    state.submitForm.authCerts = [authCert];
 | 
				
			||||||
    await testConnExec();
 | 
					    await testConnExec();
 | 
				
			||||||
    ElMessage.success('连接成功');
 | 
					    ElMessage.success('连接成功');
 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const btnOk = async () => {
 | 
					const btnOk = async () => {
 | 
				
			||||||
    dbForm.value.validate(async (valid: boolean) => {
 | 
					    try {
 | 
				
			||||||
        if (!valid) {
 | 
					        await dbForm.value.validate();
 | 
				
			||||||
 | 
					    } catch (e: any) {
 | 
				
			||||||
        ElMessage.error('请正确填写信息');
 | 
					        ElMessage.error('请正确填写信息');
 | 
				
			||||||
        return false;
 | 
					        return false;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@@ -293,7 +294,6 @@ const btnOk = async () => {
 | 
				
			|||||||
    state.form.id = saveInstanceRes as any;
 | 
					    state.form.id = saveInstanceRes as any;
 | 
				
			||||||
    emit('val-change', state.form);
 | 
					    emit('val-change', state.form);
 | 
				
			||||||
    cancel();
 | 
					    cancel();
 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const cancel = () => {
 | 
					const cancel = () => {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -35,7 +35,7 @@
 | 
				
			|||||||
            <template #action="{ data }">
 | 
					            <template #action="{ data }">
 | 
				
			||||||
                <el-button @click="showInfo(data)" link>详情</el-button>
 | 
					                <el-button @click="showInfo(data)" link>详情</el-button>
 | 
				
			||||||
                <el-button v-if="actionBtns[perms.saveInstance]" @click="editInstance(data)" type="primary" link>编辑</el-button>
 | 
					                <el-button v-if="actionBtns[perms.saveInstance]" @click="editInstance(data)" type="primary" link>编辑</el-button>
 | 
				
			||||||
                <el-button v-if="actionBtns[perms.saveDb]" @click="editDb(data)" type="primary" link>库配置</el-button>
 | 
					                <el-button v-if="actionBtns[perms.saveDb]" @click="editDb(data)" type="primary" link>库管理</el-button>
 | 
				
			||||||
            </template>
 | 
					            </template>
 | 
				
			||||||
        </page-table>
 | 
					        </page-table>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -53,10 +53,10 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
                <el-descriptions-item :span="3" label="SSH隧道">{{ infoDialog.data.sshTunnelMachineId > 0 ? '是' : '否' }} </el-descriptions-item>
 | 
					                <el-descriptions-item :span="3" label="SSH隧道">{{ infoDialog.data.sshTunnelMachineId > 0 ? '是' : '否' }} </el-descriptions-item>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                <el-descriptions-item :span="2" label="创建时间">{{ dateFormat(infoDialog.data.createTime) }} </el-descriptions-item>
 | 
					                <el-descriptions-item :span="2" label="创建时间">{{ formatDate(infoDialog.data.createTime) }} </el-descriptions-item>
 | 
				
			||||||
                <el-descriptions-item :span="1" label="创建者">{{ infoDialog.data.creator }}</el-descriptions-item>
 | 
					                <el-descriptions-item :span="1" label="创建者">{{ infoDialog.data.creator }}</el-descriptions-item>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                <el-descriptions-item :span="2" label="更新时间">{{ dateFormat(infoDialog.data.updateTime) }} </el-descriptions-item>
 | 
					                <el-descriptions-item :span="2" label="更新时间">{{ formatDate(infoDialog.data.updateTime) }} </el-descriptions-item>
 | 
				
			||||||
                <el-descriptions-item :span="1" label="修改者">{{ infoDialog.data.modifier }}</el-descriptions-item>
 | 
					                <el-descriptions-item :span="1" label="修改者">{{ infoDialog.data.modifier }}</el-descriptions-item>
 | 
				
			||||||
            </el-descriptions>
 | 
					            </el-descriptions>
 | 
				
			||||||
        </el-dialog>
 | 
					        </el-dialog>
 | 
				
			||||||
@@ -68,7 +68,7 @@
 | 
				
			|||||||
            v-model:data="instanceEditDialog.data"
 | 
					            v-model:data="instanceEditDialog.data"
 | 
				
			||||||
        ></instance-edit>
 | 
					        ></instance-edit>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        <instance-db-conf :title="dbEditDialog.title" v-model:visible="dbEditDialog.visible" :instance="dbEditDialog.instance" />
 | 
					        <DbList :title="dbEditDialog.title" v-model:visible="dbEditDialog.visible" :instance="dbEditDialog.instance" />
 | 
				
			||||||
    </div>
 | 
					    </div>
 | 
				
			||||||
</template>
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -76,7 +76,7 @@
 | 
				
			|||||||
import { defineAsyncComponent, onMounted, reactive, ref, Ref, toRefs } from 'vue';
 | 
					import { defineAsyncComponent, onMounted, reactive, ref, Ref, toRefs } from 'vue';
 | 
				
			||||||
import { ElMessage, ElMessageBox } from 'element-plus';
 | 
					import { ElMessage, ElMessageBox } from 'element-plus';
 | 
				
			||||||
import { dbApi } from './api';
 | 
					import { dbApi } from './api';
 | 
				
			||||||
import { dateFormat } from '@/common/utils/date';
 | 
					import { formatDate } from '@/common/utils/format';
 | 
				
			||||||
import PageTable from '@/components/pagetable/PageTable.vue';
 | 
					import PageTable from '@/components/pagetable/PageTable.vue';
 | 
				
			||||||
import { TableColumn } from '@/components/pagetable';
 | 
					import { TableColumn } from '@/components/pagetable';
 | 
				
			||||||
import { hasPerms } from '@/components/auth/auth';
 | 
					import { hasPerms } from '@/components/auth/auth';
 | 
				
			||||||
@@ -89,7 +89,7 @@ import { getTagPathSearchItem } from '../component/tag';
 | 
				
			|||||||
import { TagResourceTypeEnum } from '@/common/commonEnum';
 | 
					import { TagResourceTypeEnum } from '@/common/commonEnum';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const InstanceEdit = defineAsyncComponent(() => import('./InstanceEdit.vue'));
 | 
					const InstanceEdit = defineAsyncComponent(() => import('./InstanceEdit.vue'));
 | 
				
			||||||
const InstanceDbConf = defineAsyncComponent(() => import('./InstanceDbConf.vue'));
 | 
					const DbList = defineAsyncComponent(() => import('./DbList.vue'));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const props = defineProps({
 | 
					const props = defineProps({
 | 
				
			||||||
    lazy: {
 | 
					    lazy: {
 | 
				
			||||||
@@ -215,7 +215,7 @@ const deleteInstance = async () => {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
const editDb = (data: any) => {
 | 
					const editDb = (data: any) => {
 | 
				
			||||||
    state.dbEditDialog.instance = data;
 | 
					    state.dbEditDialog.instance = data;
 | 
				
			||||||
    state.dbEditDialog.title = `配置 "${data.name}" 数据库`;
 | 
					    state.dbEditDialog.title = `管理 "${data.name}" 数据库`;
 | 
				
			||||||
    state.dbEditDialog.visible = true;
 | 
					    state.dbEditDialog.visible = true;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -2,7 +2,12 @@
 | 
				
			|||||||
    <div class="db-sql-exec">
 | 
					    <div class="db-sql-exec">
 | 
				
			||||||
        <Splitpanes class="default-theme">
 | 
					        <Splitpanes class="default-theme">
 | 
				
			||||||
            <Pane size="20" max-size="30">
 | 
					            <Pane size="20" max-size="30">
 | 
				
			||||||
                <tag-tree :resource-type="TagResourceTypeEnum.DbName.value" :tag-path-node-type="NodeTypeTagPath" ref="tagTreeRef">
 | 
					                <tag-tree
 | 
				
			||||||
 | 
					                    :default-expanded-keys="state.defaultExpendKey"
 | 
				
			||||||
 | 
					                    :resource-type="TagResourceTypeEnum.DbName.value"
 | 
				
			||||||
 | 
					                    :tag-path-node-type="NodeTypeTagPath"
 | 
				
			||||||
 | 
					                    ref="tagTreeRef"
 | 
				
			||||||
 | 
					                >
 | 
				
			||||||
                    <template #prefix="{ data }">
 | 
					                    <template #prefix="{ data }">
 | 
				
			||||||
                        <span v-if="data.type.value == SqlExecNodeType.DbInst">
 | 
					                        <span v-if="data.type.value == SqlExecNodeType.DbInst">
 | 
				
			||||||
                            <el-popover
 | 
					                            <el-popover
 | 
				
			||||||
@@ -42,10 +47,8 @@
 | 
				
			|||||||
                    </template>
 | 
					                    </template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    <template #suffix="{ data }">
 | 
					                    <template #suffix="{ data }">
 | 
				
			||||||
                        <span class="db-table-size" v-if="data.type.value == SqlExecNodeType.Table && data.params.size">{{ ` ${data.params.size}` }}</span>
 | 
					                        <span v-if="data.type.value == SqlExecNodeType.Table && data.params.size">{{ ` ${data.params.size}` }}</span>
 | 
				
			||||||
                        <span class="db-table-size" v-if="data.type.value == SqlExecNodeType.TableMenu && data.params.dbTableSize">{{
 | 
					                        <span v-if="data.type.value == SqlExecNodeType.TableMenu && data.params.dbTableSize">{{ ` ${data.params.dbTableSize}` }}</span>
 | 
				
			||||||
                            ` ${data.params.dbTableSize}`
 | 
					 | 
				
			||||||
                        }}</span>
 | 
					 | 
				
			||||||
                    </template>
 | 
					                    </template>
 | 
				
			||||||
                </tag-tree>
 | 
					                </tag-tree>
 | 
				
			||||||
            </Pane>
 | 
					            </Pane>
 | 
				
			||||||
@@ -55,16 +58,61 @@
 | 
				
			|||||||
                    <el-row>
 | 
					                    <el-row>
 | 
				
			||||||
                        <el-col :span="24" v-if="state.db">
 | 
					                        <el-col :span="24" v-if="state.db">
 | 
				
			||||||
                            <el-descriptions :column="4" size="small" border>
 | 
					                            <el-descriptions :column="4" size="small" border>
 | 
				
			||||||
                                <el-descriptions-item label-align="right" label="操作"
 | 
					                                <el-descriptions-item label-align="right" label="操作">
 | 
				
			||||||
                                    ><el-button
 | 
					                                    <el-button
 | 
				
			||||||
                                        :disabled="!state.db || !nowDbInst.id"
 | 
					                                        :disabled="!state.db || !nowDbInst.id"
 | 
				
			||||||
                                        type="primary"
 | 
					                                        type="primary"
 | 
				
			||||||
                                        icon="Search"
 | 
					                                        icon="Search"
 | 
				
			||||||
                                        @click="addQueryTab({ id: nowDbInst.id, dbs: nowDbInst.databases }, state.db)"
 | 
					                                        link
 | 
				
			||||||
                                        size="small"
 | 
					                                        @click="
 | 
				
			||||||
                                        >新建查询</el-button
 | 
					                                            addQueryTab(
 | 
				
			||||||
                                    ></el-descriptions-item
 | 
					                                                { id: nowDbInst.id, dbs: nowDbInst.databases, nodeKey: getSqlMenuNodeKey(nowDbInst.id, state.db) },
 | 
				
			||||||
 | 
					                                                state.db
 | 
				
			||||||
 | 
					                                            )
 | 
				
			||||||
 | 
					                                        "
 | 
				
			||||||
 | 
					                                        title="新建查询"
 | 
				
			||||||
                                    >
 | 
					                                    >
 | 
				
			||||||
 | 
					                                    </el-button>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                                    <template v-if="!dbConfig.locationTreeNode">
 | 
				
			||||||
 | 
					                                        <el-divider direction="vertical" border-style="dashed" />
 | 
				
			||||||
 | 
					                                        <el-button @click="locationNowTreeNode(null)" title="定位至左侧树的指定位置" icon="Location" link></el-button>
 | 
				
			||||||
 | 
					                                    </template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                                    <el-divider direction="vertical" border-style="dashed" />
 | 
				
			||||||
 | 
					                                    <!-- 数据库展示配置 -->
 | 
				
			||||||
 | 
					                                    <el-popover
 | 
				
			||||||
 | 
					                                        popper-style="max-height: 550px; overflow: auto; max-width: 450px"
 | 
				
			||||||
 | 
					                                        placement="bottom"
 | 
				
			||||||
 | 
					                                        width="auto"
 | 
				
			||||||
 | 
					                                        title="数据库展示配置"
 | 
				
			||||||
 | 
					                                        trigger="click"
 | 
				
			||||||
 | 
					                                    >
 | 
				
			||||||
 | 
					                                        <el-row>
 | 
				
			||||||
 | 
					                                            <el-checkbox
 | 
				
			||||||
 | 
					                                                v-model="dbConfig.showColumnComment"
 | 
				
			||||||
 | 
					                                                label="显示字段备注"
 | 
				
			||||||
 | 
					                                                :true-value="1"
 | 
				
			||||||
 | 
					                                                :false-value="0"
 | 
				
			||||||
 | 
					                                                size="small"
 | 
				
			||||||
 | 
					                                            />
 | 
				
			||||||
 | 
					                                        </el-row>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                                        <el-row>
 | 
				
			||||||
 | 
					                                            <el-checkbox
 | 
				
			||||||
 | 
					                                                v-model="dbConfig.locationTreeNode"
 | 
				
			||||||
 | 
					                                                label="自动定位树节点"
 | 
				
			||||||
 | 
					                                                :true-value="1"
 | 
				
			||||||
 | 
					                                                :false-value="0"
 | 
				
			||||||
 | 
					                                                size="small"
 | 
				
			||||||
 | 
					                                            />
 | 
				
			||||||
 | 
					                                        </el-row>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                                        <template #reference>
 | 
				
			||||||
 | 
					                                            <el-link type="primary" icon="setting" :underline="false"></el-link>
 | 
				
			||||||
 | 
					                                        </template>
 | 
				
			||||||
 | 
					                                    </el-popover>
 | 
				
			||||||
 | 
					                                </el-descriptions-item>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                                <el-descriptions-item label-align="right" label="tag">{{ nowDbInst.tagPath }}</el-descriptions-item>
 | 
					                                <el-descriptions-item label-align="right" label="tag">{{ nowDbInst.tagPath }}</el-descriptions-item>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -100,7 +148,9 @@
 | 
				
			|||||||
                            <el-tab-pane class="h100" closable v-for="dt in state.tabs.values()" :label="dt.label" :name="dt.key" :key="dt.key">
 | 
					                            <el-tab-pane class="h100" closable v-for="dt in state.tabs.values()" :label="dt.label" :name="dt.key" :key="dt.key">
 | 
				
			||||||
                                <template #label>
 | 
					                                <template #label>
 | 
				
			||||||
                                    <el-popover :show-after="1000" placement="bottom-start" trigger="hover" :width="250">
 | 
					                                    <el-popover :show-after="1000" placement="bottom-start" trigger="hover" :width="250">
 | 
				
			||||||
                                        <template #reference> {{ dt.label }} </template>
 | 
					                                        <template #reference>
 | 
				
			||||||
 | 
					                                            <span class="font12">{{ dt.label }}</span>
 | 
				
			||||||
 | 
					                                        </template>
 | 
				
			||||||
                                        <template #default>
 | 
					                                        <template #default>
 | 
				
			||||||
                                            <el-descriptions :column="1" size="small">
 | 
					                                            <el-descriptions :column="1" size="small">
 | 
				
			||||||
                                                <el-descriptions-item label="tagPath">
 | 
					                                                <el-descriptions-item label="tagPath">
 | 
				
			||||||
@@ -127,6 +177,7 @@
 | 
				
			|||||||
                                    :db-name="dt.db"
 | 
					                                    :db-name="dt.db"
 | 
				
			||||||
                                    :table-name="dt.params.table"
 | 
					                                    :table-name="dt.params.table"
 | 
				
			||||||
                                    :table-height="state.dataTabsTableHeight"
 | 
					                                    :table-height="state.dataTabsTableHeight"
 | 
				
			||||||
 | 
					                                    :ref="(el: any) => (dt.componentRef = el)"
 | 
				
			||||||
                                ></db-table-data-op>
 | 
					                                ></db-table-data-op>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                                <db-sql-editor
 | 
					                                <db-sql-editor
 | 
				
			||||||
@@ -135,6 +186,7 @@
 | 
				
			|||||||
                                    :db-name="dt.db"
 | 
					                                    :db-name="dt.db"
 | 
				
			||||||
                                    :sql-name="dt.params.sqlName"
 | 
					                                    :sql-name="dt.params.sqlName"
 | 
				
			||||||
                                    @save-sql-success="reloadSqls"
 | 
					                                    @save-sql-success="reloadSqls"
 | 
				
			||||||
 | 
					                                    :ref="(el: any) => (dt.componentRef = el)"
 | 
				
			||||||
                                >
 | 
					                                >
 | 
				
			||||||
                                </db-sql-editor>
 | 
					                                </db-sql-editor>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -143,7 +195,7 @@
 | 
				
			|||||||
                                    :db-id="dt.params.id"
 | 
					                                    :db-id="dt.params.id"
 | 
				
			||||||
                                    :db="dt.params.db"
 | 
					                                    :db="dt.params.db"
 | 
				
			||||||
                                    :db-type="dt.params.type"
 | 
					                                    :db-type="dt.params.type"
 | 
				
			||||||
                                    :flow-procdef-key="dt.params.flowProcdefKey"
 | 
					                                    :flow-procdef="dt.params.flowProcdef"
 | 
				
			||||||
                                    :height="state.tablesOpHeight"
 | 
					                                    :height="state.tablesOpHeight"
 | 
				
			||||||
                                />
 | 
					                                />
 | 
				
			||||||
                            </el-tab-pane>
 | 
					                            </el-tab-pane>
 | 
				
			||||||
@@ -158,7 +210,7 @@
 | 
				
			|||||||
            :dbId="tableCreateDialog.dbId"
 | 
					            :dbId="tableCreateDialog.dbId"
 | 
				
			||||||
            :db="tableCreateDialog.db"
 | 
					            :db="tableCreateDialog.db"
 | 
				
			||||||
            :dbType="tableCreateDialog.dbType"
 | 
					            :dbType="tableCreateDialog.dbType"
 | 
				
			||||||
            :flow-procdef-key="tableCreateDialog.flowProcdefKey"
 | 
					            :flow-procdef="tableCreateDialog.flowProcdef"
 | 
				
			||||||
            :data="tableCreateDialog.data"
 | 
					            :data="tableCreateDialog.data"
 | 
				
			||||||
            v-model:visible="tableCreateDialog.visible"
 | 
					            v-model:visible="tableCreateDialog.visible"
 | 
				
			||||||
            @submit-sql="onSubmitEditTableSql"
 | 
					            @submit-sql="onSubmitEditTableSql"
 | 
				
			||||||
@@ -167,11 +219,11 @@
 | 
				
			|||||||
</template>
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<script lang="ts" setup>
 | 
					<script lang="ts" setup>
 | 
				
			||||||
import { defineAsyncComponent, h, onBeforeUnmount, onMounted, reactive, ref, toRefs } from 'vue';
 | 
					import { defineAsyncComponent, h, onBeforeUnmount, onMounted, reactive, ref, toRefs, watch } from 'vue';
 | 
				
			||||||
import { ElCheckbox, ElMessage, ElMessageBox } from 'element-plus';
 | 
					import { ElCheckbox, ElMessage, ElMessageBox } from 'element-plus';
 | 
				
			||||||
import { formatByteSize } from '@/common/utils/format';
 | 
					import { formatByteSize } from '@/common/utils/format';
 | 
				
			||||||
import { DbInst, registerDbCompletionItemProvider, TabInfo, TabType } from './db';
 | 
					import { DbInst, DbThemeConfig, registerDbCompletionItemProvider, TabInfo, TabType } from './db';
 | 
				
			||||||
import { NodeType, TagTreeNode } from '../component/tag';
 | 
					import { NodeType, TagTreeNode, getTagTypeCodeByPath } from '../component/tag';
 | 
				
			||||||
import TagTree from '../component/TagTree.vue';
 | 
					import TagTree from '../component/TagTree.vue';
 | 
				
			||||||
import { dbApi } from './api';
 | 
					import { dbApi } from './api';
 | 
				
			||||||
import { dispposeCompletionItemProvider } from '@/components/monaco/completionItemProvider';
 | 
					import { dispposeCompletionItemProvider } from '@/components/monaco/completionItemProvider';
 | 
				
			||||||
@@ -181,8 +233,11 @@ import { getDbDialect, schemaDbTypes } from './dialect/index';
 | 
				
			|||||||
import { sleep } from '@/common/utils/loading';
 | 
					import { sleep } from '@/common/utils/loading';
 | 
				
			||||||
import { TagResourceTypeEnum } from '@/common/commonEnum';
 | 
					import { TagResourceTypeEnum } from '@/common/commonEnum';
 | 
				
			||||||
import { Pane, Splitpanes } from 'splitpanes';
 | 
					import { Pane, Splitpanes } from 'splitpanes';
 | 
				
			||||||
import { useEventListener } from '@vueuse/core';
 | 
					import { useEventListener, useStorage } from '@vueuse/core';
 | 
				
			||||||
import SqlExecBox from '@/views/ops/db/component/sqleditor/SqlExecBox';
 | 
					import SqlExecBox from '@/views/ops/db/component/sqleditor/SqlExecBox';
 | 
				
			||||||
 | 
					import { useAutoOpenResource } from '@/store/autoOpenResource';
 | 
				
			||||||
 | 
					import { storeToRefs } from 'pinia';
 | 
				
			||||||
 | 
					import { procdefApi } from '@/views/flow/api';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const DbTableOp = defineAsyncComponent(() => import('./component/table/DbTableOp.vue'));
 | 
					const DbTableOp = defineAsyncComponent(() => import('./component/table/DbTableOp.vue'));
 | 
				
			||||||
const DbSqlEditor = defineAsyncComponent(() => import('./component/sqleditor/DbSqlEditor.vue'));
 | 
					const DbSqlEditor = defineAsyncComponent(() => import('./component/sqleditor/DbSqlEditor.vue'));
 | 
				
			||||||
@@ -236,7 +291,7 @@ const nodeClickChangeDb = (nodeData: TagTreeNode) => {
 | 
				
			|||||||
                type: params.type,
 | 
					                type: params.type,
 | 
				
			||||||
                tagPath: params.tagPath,
 | 
					                tagPath: params.tagPath,
 | 
				
			||||||
                databases: params.dbs,
 | 
					                databases: params.dbs,
 | 
				
			||||||
                flowProcdefKey: params.flowProcdefKey,
 | 
					                flowProcdef: params.flowProcdef,
 | 
				
			||||||
            },
 | 
					            },
 | 
				
			||||||
            params.db
 | 
					            params.db
 | 
				
			||||||
        );
 | 
					        );
 | 
				
			||||||
@@ -258,15 +313,17 @@ const NodeTypeTagPath = new NodeType(TagTreeNode.TagPath)
 | 
				
			|||||||
        await sleep(100);
 | 
					        await sleep(100);
 | 
				
			||||||
        return dbInfos?.map((x: any) => {
 | 
					        return dbInfos?.map((x: any) => {
 | 
				
			||||||
            x.tagPath = parentNode.key;
 | 
					            x.tagPath = parentNode.key;
 | 
				
			||||||
            return new TagTreeNode(`${parentNode.key}.${x.id}`, x.name, NodeTypeDbInst).withParams(x);
 | 
					            return new TagTreeNode(`${x.code}`, x.name, NodeTypeDbInst).withParams(x);
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
    })
 | 
					    })
 | 
				
			||||||
    .withContextMenuItems([ContextmenuItemRefresh]);
 | 
					    .withContextMenuItems([ContextmenuItemRefresh]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// 数据库实例节点类型
 | 
					// 数据库实例节点类型
 | 
				
			||||||
const NodeTypeDbInst = new NodeType(SqlExecNodeType.DbInst).withLoadNodesFunc((parentNode: TagTreeNode) => {
 | 
					const NodeTypeDbInst = new NodeType(SqlExecNodeType.DbInst).withLoadNodesFunc(async (parentNode: TagTreeNode) => {
 | 
				
			||||||
    const params = parentNode.params;
 | 
					    const params = parentNode.params;
 | 
				
			||||||
    const dbs = params.database.split(' ')?.sort();
 | 
					    const dbs = (await DbInst.getDbNames(params))?.sort();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const flowProcdef = await procdefApi.getByResource.request({ resourceType: TagResourceTypeEnum.DbName.value, resourceCode: params.code });
 | 
				
			||||||
    return dbs.map((x: any) => {
 | 
					    return dbs.map((x: any) => {
 | 
				
			||||||
        return new TagTreeNode(`${parentNode.key}.${x}`, x, NodeTypeDb)
 | 
					        return new TagTreeNode(`${parentNode.key}.${x}`, x, NodeTypeDb)
 | 
				
			||||||
            .withParams({
 | 
					            .withParams({
 | 
				
			||||||
@@ -277,7 +334,7 @@ const NodeTypeDbInst = new NodeType(SqlExecNodeType.DbInst).withLoadNodesFunc((p
 | 
				
			|||||||
                host: `${params.host}:${params.port}`,
 | 
					                host: `${params.host}:${params.port}`,
 | 
				
			||||||
                dbs: dbs,
 | 
					                dbs: dbs,
 | 
				
			||||||
                db: x,
 | 
					                db: x,
 | 
				
			||||||
                flowProcdefKey: params.flowProcdefKey,
 | 
					                flowProcdef: flowProcdef,
 | 
				
			||||||
            })
 | 
					            })
 | 
				
			||||||
            .withIcon(DbIcon);
 | 
					            .withIcon(DbIcon);
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
@@ -338,7 +395,7 @@ const NodeTypeTableMenu = new NodeType(SqlExecNodeType.TableMenu)
 | 
				
			|||||||
    ])
 | 
					    ])
 | 
				
			||||||
    .withLoadNodesFunc(async (parentNode: TagTreeNode) => {
 | 
					    .withLoadNodesFunc(async (parentNode: TagTreeNode) => {
 | 
				
			||||||
        const params = parentNode.params;
 | 
					        const params = parentNode.params;
 | 
				
			||||||
        let { id, db, type, flowProcdefKey, schema } = params;
 | 
					        let { id, db, type, flowProcdef, schema } = params;
 | 
				
			||||||
        // 获取当前库的所有表信息
 | 
					        // 获取当前库的所有表信息
 | 
				
			||||||
        let tables = await DbInst.getInst(id).loadTables(db, state.reloadStatus);
 | 
					        let tables = await DbInst.getInst(id).loadTables(db, state.reloadStatus);
 | 
				
			||||||
        state.reloadStatus = false;
 | 
					        state.reloadStatus = false;
 | 
				
			||||||
@@ -354,7 +411,7 @@ const NodeTypeTableMenu = new NodeType(SqlExecNodeType.TableMenu)
 | 
				
			|||||||
                    db,
 | 
					                    db,
 | 
				
			||||||
                    type,
 | 
					                    type,
 | 
				
			||||||
                    schema,
 | 
					                    schema,
 | 
				
			||||||
                    flowProcdefKey: flowProcdefKey,
 | 
					                    flowProcdef: flowProcdef,
 | 
				
			||||||
                    key: key,
 | 
					                    key: key,
 | 
				
			||||||
                    parentKey: parentNode.key,
 | 
					                    parentKey: parentNode.key,
 | 
				
			||||||
                    tableName: x.tableName,
 | 
					                    tableName: x.tableName,
 | 
				
			||||||
@@ -418,6 +475,7 @@ const tagTreeRef: any = ref(null);
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
const tabs: Map<string, TabInfo> = new Map();
 | 
					const tabs: Map<string, TabInfo> = new Map();
 | 
				
			||||||
const state = reactive({
 | 
					const state = reactive({
 | 
				
			||||||
 | 
					    defaultExpendKey: [] as any,
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * 当前操作的数据库实例
 | 
					     * 当前操作的数据库实例
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
@@ -439,7 +497,7 @@ const state = reactive({
 | 
				
			|||||||
        dbId: 0,
 | 
					        dbId: 0,
 | 
				
			||||||
        db: '',
 | 
					        db: '',
 | 
				
			||||||
        dbType: '',
 | 
					        dbType: '',
 | 
				
			||||||
        flowProcdefKey: '',
 | 
					        flowProcdef: null as any,
 | 
				
			||||||
        data: {},
 | 
					        data: {},
 | 
				
			||||||
        parentKey: '',
 | 
					        parentKey: '',
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
@@ -447,12 +505,18 @@ const state = reactive({
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
const { nowDbInst, tableCreateDialog } = toRefs(state);
 | 
					const { nowDbInst, tableCreateDialog } = toRefs(state);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const dbConfig = useStorage('dbConfig', DbThemeConfig);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const serverInfoReqParam = ref({
 | 
					const serverInfoReqParam = ref({
 | 
				
			||||||
    instanceId: 0,
 | 
					    instanceId: 0,
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
const { execute: getDbServerInfo, isFetching: loadingServerInfo, data: dbServerInfo } = dbApi.getInstanceServerInfo.useApi<any>(serverInfoReqParam);
 | 
					const { execute: getDbServerInfo, isFetching: loadingServerInfo, data: dbServerInfo } = dbApi.getInstanceServerInfo.useApi<any>(serverInfoReqParam);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const autoOpenResourceStore = useAutoOpenResource();
 | 
				
			||||||
 | 
					const { autoOpenResource } = storeToRefs(autoOpenResourceStore);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
onMounted(() => {
 | 
					onMounted(() => {
 | 
				
			||||||
 | 
					    autoOpenDb(autoOpenResource.value.dbCodePath);
 | 
				
			||||||
    setHeight();
 | 
					    setHeight();
 | 
				
			||||||
    // 监听浏览器窗口大小变化,更新对应组件高度
 | 
					    // 监听浏览器窗口大小变化,更新对应组件高度
 | 
				
			||||||
    useEventListener(window, 'resize', setHeight);
 | 
					    useEventListener(window, 'resize', setHeight);
 | 
				
			||||||
@@ -462,6 +526,31 @@ onBeforeUnmount(() => {
 | 
				
			|||||||
    dispposeCompletionItemProvider('sql');
 | 
					    dispposeCompletionItemProvider('sql');
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					watch(
 | 
				
			||||||
 | 
					    () => autoOpenResource.value.dbCodePath,
 | 
				
			||||||
 | 
					    (codePath: any) => {
 | 
				
			||||||
 | 
					        autoOpenDb(codePath);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const autoOpenDb = (codePath: string) => {
 | 
				
			||||||
 | 
					    if (!codePath) {
 | 
				
			||||||
 | 
					        return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const typeAndCodes = getTagTypeCodeByPath(codePath);
 | 
				
			||||||
 | 
					    const tagPath = typeAndCodes[TagResourceTypeEnum.Tag.value].join('/') + '/';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const dbCode = typeAndCodes[TagResourceTypeEnum.DbName.value][0];
 | 
				
			||||||
 | 
					    state.defaultExpendKey = [tagPath, dbCode];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    setTimeout(() => {
 | 
				
			||||||
 | 
					        // 置空
 | 
				
			||||||
 | 
					        autoOpenResourceStore.setDbCodePath('');
 | 
				
			||||||
 | 
					        tagTreeRef.value.setCurrentKey(dbCode);
 | 
				
			||||||
 | 
					    }, 1000);
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * 设置editor高度和数据表高度
 | 
					 * 设置editor高度和数据表高度
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
@@ -492,7 +581,7 @@ const loadTableData = async (db: any, dbName: string, tableName: string) => {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
    changeDb(db, dbName);
 | 
					    changeDb(db, dbName);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const key = `${db.id}:\`${dbName}\`.${tableName}`;
 | 
					    const key = `tableData:${db.id}.${dbName}.${tableName}`;
 | 
				
			||||||
    let tab = state.tabs.get(key);
 | 
					    let tab = state.tabs.get(key);
 | 
				
			||||||
    state.activeName = key;
 | 
					    state.activeName = key;
 | 
				
			||||||
    // 如果存在该表tab,则直接返回
 | 
					    // 如果存在该表tab,则直接返回
 | 
				
			||||||
@@ -527,7 +616,7 @@ const addQueryTab = async (db: any, dbName: string, sqlName: string = '') => {
 | 
				
			|||||||
    // 存在sql模板名,则该模板名只允许一个tab
 | 
					    // 存在sql模板名,则该模板名只允许一个tab
 | 
				
			||||||
    if (sqlName) {
 | 
					    if (sqlName) {
 | 
				
			||||||
        label = `查询-${sqlName}`;
 | 
					        label = `查询-${sqlName}`;
 | 
				
			||||||
        key = `查询:${dbId}:${dbName}.${sqlName}`;
 | 
					        key = `query:${dbId}.${dbName}.${sqlName}`;
 | 
				
			||||||
    } else {
 | 
					    } else {
 | 
				
			||||||
        let count = 1;
 | 
					        let count = 1;
 | 
				
			||||||
        state.tabs.forEach((v) => {
 | 
					        state.tabs.forEach((v) => {
 | 
				
			||||||
@@ -536,7 +625,7 @@ const addQueryTab = async (db: any, dbName: string, sqlName: string = '') => {
 | 
				
			|||||||
            }
 | 
					            }
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
        label = `新查询-${count}`;
 | 
					        label = `新查询-${count}`;
 | 
				
			||||||
        key = `新查询${count}:${dbId}:${dbName}`;
 | 
					        key = `query:${count}.${dbId}.${dbName}`;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    state.activeName = key;
 | 
					    state.activeName = key;
 | 
				
			||||||
    let tab = state.tabs.get(key);
 | 
					    let tab = state.tabs.get(key);
 | 
				
			||||||
@@ -573,7 +662,7 @@ const addTablesOpTab = async (db: any) => {
 | 
				
			|||||||
    changeDb(db, dbName);
 | 
					    changeDb(db, dbName);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const dbId = db.id;
 | 
					    const dbId = db.id;
 | 
				
			||||||
    let key = `表操作:${dbId}:${dbName}.tablesOp`;
 | 
					    let key = `tablesOp:${dbId}.${dbName}`;
 | 
				
			||||||
    state.activeName = key;
 | 
					    state.activeName = key;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    let tab = state.tabs.get(key);
 | 
					    let tab = state.tabs.get(key);
 | 
				
			||||||
@@ -604,15 +693,22 @@ const onRemoveTab = (targetName: string) => {
 | 
				
			|||||||
        if (tabName !== targetName) {
 | 
					        if (tabName !== targetName) {
 | 
				
			||||||
            continue;
 | 
					            continue;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        state.tabs.delete(targetName);
 | 
				
			||||||
 | 
					        if (activeName != targetName) {
 | 
				
			||||||
 | 
					            break;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // 如果删除的tab是当前激活的tab,则切换到前一个或后一个tab
 | 
				
			||||||
        const nextTab = tabNames[i + 1] || tabNames[i - 1];
 | 
					        const nextTab = tabNames[i + 1] || tabNames[i - 1];
 | 
				
			||||||
        if (nextTab) {
 | 
					        if (nextTab) {
 | 
				
			||||||
            activeName = nextTab;
 | 
					            activeName = nextTab;
 | 
				
			||||||
        } else {
 | 
					        } else {
 | 
				
			||||||
            activeName = '';
 | 
					            activeName = '';
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        state.tabs.delete(targetName);
 | 
					 | 
				
			||||||
        state.activeName = activeName;
 | 
					        state.activeName = activeName;
 | 
				
			||||||
        onTabChange();
 | 
					        onTabChange();
 | 
				
			||||||
 | 
					        break;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -631,6 +727,23 @@ const onTabChange = () => {
 | 
				
			|||||||
        // 注册sql提示
 | 
					        // 注册sql提示
 | 
				
			||||||
        registerDbCompletionItemProvider(nowTab.dbId, nowTab.db, nowTab.params.dbs, nowDbInst.value.type);
 | 
					        registerDbCompletionItemProvider(nowTab.dbId, nowTab.db, nowTab.params.dbs, nowDbInst.value.type);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // 激活当前tab(需要调用DbTableData组件的active,否则表头与数据会出现错位,暂不知为啥,先这样处理)
 | 
				
			||||||
 | 
					    nowTab?.componentRef?.active();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (dbConfig.value.locationTreeNode) {
 | 
				
			||||||
 | 
					        locationNowTreeNode(nowTab);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * 定位至当前树节点
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					const locationNowTreeNode = (nowTab: any = null) => {
 | 
				
			||||||
 | 
					    if (!nowTab) {
 | 
				
			||||||
 | 
					        nowTab = state.tabs.get(state.activeName);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    tagTreeRef.value.setCurrentKey(nowTab?.treeNodeKey);
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const reloadSqls = (dbId: number, db: string) => {
 | 
					const reloadSqls = (dbId: number, db: string) => {
 | 
				
			||||||
@@ -662,7 +775,7 @@ const reloadNode = (nodeKey: string) => {
 | 
				
			|||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const onEditTable = async (data: any) => {
 | 
					const onEditTable = async (data: any) => {
 | 
				
			||||||
    let { db, id, tableName, tableComment, type, parentKey, key, flowProcdefKey } = data.params;
 | 
					    let { db, id, tableName, tableComment, type, parentKey, key, flowProcdef } = data.params;
 | 
				
			||||||
    // data.label就是表名
 | 
					    // data.label就是表名
 | 
				
			||||||
    if (tableName) {
 | 
					    if (tableName) {
 | 
				
			||||||
        state.tableCreateDialog.title = '修改表';
 | 
					        state.tableCreateDialog.title = '修改表';
 | 
				
			||||||
@@ -681,12 +794,12 @@ const onEditTable = async (data: any) => {
 | 
				
			|||||||
    state.tableCreateDialog.dbId = id;
 | 
					    state.tableCreateDialog.dbId = id;
 | 
				
			||||||
    state.tableCreateDialog.db = db;
 | 
					    state.tableCreateDialog.db = db;
 | 
				
			||||||
    state.tableCreateDialog.dbType = type;
 | 
					    state.tableCreateDialog.dbType = type;
 | 
				
			||||||
    state.tableCreateDialog.flowProcdefKey = flowProcdefKey;
 | 
					    state.tableCreateDialog.flowProcdef = flowProcdef;
 | 
				
			||||||
    state.tableCreateDialog.visible = true;
 | 
					    state.tableCreateDialog.visible = true;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const onDeleteTable = async (data: any) => {
 | 
					const onDeleteTable = async (data: any) => {
 | 
				
			||||||
    let { db, id, tableName, parentKey, flowProcdefKey, schema } = data.params;
 | 
					    let { db, id, tableName, parentKey, flowProcdef, schema } = data.params;
 | 
				
			||||||
    await ElMessageBox.confirm(`此操作是永久性且无法撤销,确定删除【${tableName}】? `, '提示', {
 | 
					    await ElMessageBox.confirm(`此操作是永久性且无法撤销,确定删除【${tableName}】? `, '提示', {
 | 
				
			||||||
        confirmButtonText: '确定',
 | 
					        confirmButtonText: '确定',
 | 
				
			||||||
        cancelButtonText: '取消',
 | 
					        cancelButtonText: '取消',
 | 
				
			||||||
@@ -698,7 +811,7 @@ const onDeleteTable = async (data: any) => {
 | 
				
			|||||||
    let schemaStr = schema ? `${dialect.quoteIdentifier(schema)}.` : '';
 | 
					    let schemaStr = schema ? `${dialect.quoteIdentifier(schema)}.` : '';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    dbApi.sqlExec.request({ id, db, sql: `drop table ${schemaStr + dialect.quoteIdentifier(tableName)}` }).then(() => {
 | 
					    dbApi.sqlExec.request({ id, db, sql: `drop table ${schemaStr + dialect.quoteIdentifier(tableName)}` }).then(() => {
 | 
				
			||||||
        if (flowProcdefKey) {
 | 
					        if (flowProcdef) {
 | 
				
			||||||
            ElMessage.success('工单提交成功');
 | 
					            ElMessage.success('工单提交成功');
 | 
				
			||||||
            return;
 | 
					            return;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
@@ -710,7 +823,7 @@ const onDeleteTable = async (data: any) => {
 | 
				
			|||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const onRenameTable = async (data: any) => {
 | 
					const onRenameTable = async (data: any) => {
 | 
				
			||||||
    let { db, id, tableName, parentKey, flowProcdefKey } = data.params;
 | 
					    let { db, id, tableName, parentKey, flowProcdef } = data.params;
 | 
				
			||||||
    let tableData = { db, oldTableName: tableName, tableName };
 | 
					    let tableData = { db, oldTableName: tableName, tableName };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    let value = ref(tableName);
 | 
					    let value = ref(tableName);
 | 
				
			||||||
@@ -733,7 +846,7 @@ const onRenameTable = async (data: any) => {
 | 
				
			|||||||
        dbId: id as any,
 | 
					        dbId: id as any,
 | 
				
			||||||
        db: db as any,
 | 
					        db: db as any,
 | 
				
			||||||
        dbType: nowDbInst.value.getDialect().getInfo().formatSqlDialect,
 | 
					        dbType: nowDbInst.value.getDialect().getInfo().formatSqlDialect,
 | 
				
			||||||
        flowProcdefKey: flowProcdefKey,
 | 
					        flowProcdef: flowProcdef,
 | 
				
			||||||
        runSuccessCallback: () => {
 | 
					        runSuccessCallback: () => {
 | 
				
			||||||
            setTimeout(() => {
 | 
					            setTimeout(() => {
 | 
				
			||||||
                parentKey && reloadNode(parentKey);
 | 
					                parentKey && reloadNode(parentKey);
 | 
				
			||||||
@@ -793,7 +906,7 @@ const getNowDbInfo = () => {
 | 
				
			|||||||
        name: di.name,
 | 
					        name: di.name,
 | 
				
			||||||
        type: di.type,
 | 
					        type: di.type,
 | 
				
			||||||
        host: di.host,
 | 
					        host: di.host,
 | 
				
			||||||
        flowProcdefKey: di.flowProcdefKey,
 | 
					        flowProcdef: di.flowProcdef,
 | 
				
			||||||
        dbName: state.db,
 | 
					        dbName: state.db,
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
@@ -801,13 +914,8 @@ const getNowDbInfo = () => {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
<style lang="scss">
 | 
					<style lang="scss">
 | 
				
			||||||
.db-sql-exec {
 | 
					.db-sql-exec {
 | 
				
			||||||
    .db-table-size {
 | 
					 | 
				
			||||||
        color: #c4c9c4;
 | 
					 | 
				
			||||||
        font-size: 9px;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    .db-op {
 | 
					    .db-op {
 | 
				
			||||||
        height: calc(100vh - 108px);
 | 
					        height: calc(100vh - 106px);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    #data-exec {
 | 
					    #data-exec {
 | 
				
			||||||
@@ -819,7 +927,7 @@ const getNowDbInfo = () => {
 | 
				
			|||||||
            margin: 0 0 5px;
 | 
					            margin: 0 0 5px;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            .el-tabs__item {
 | 
					            .el-tabs__item {
 | 
				
			||||||
                padding: 0 10px;
 | 
					                padding: 0 5px;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -115,7 +115,7 @@
 | 
				
			|||||||
                                            <el-option
 | 
					                                            <el-option
 | 
				
			||||||
                                                v-for="item in state.targetColumnList"
 | 
					                                                v-for="item in state.targetColumnList"
 | 
				
			||||||
                                                :key="item.columnName"
 | 
					                                                :key="item.columnName"
 | 
				
			||||||
                                                :label="item.columnName + ` ${item.showDataType}` + (item.columnComment && ' - ' + item.columnComment)"
 | 
					                                                :label="item.columnName + ` ${item.columnType}` + (item.columnComment && ' - ' + item.columnComment)"
 | 
				
			||||||
                                                :value="item.columnName"
 | 
					                                                :value="item.columnName"
 | 
				
			||||||
                                            />
 | 
					                                            />
 | 
				
			||||||
                                        </el-select>
 | 
					                                        </el-select>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -23,8 +23,11 @@ export const dbApi = {
 | 
				
			|||||||
            if (process.env.NODE_ENV === 'development') {
 | 
					            if (process.env.NODE_ENV === 'development') {
 | 
				
			||||||
                console.log(param.sql);
 | 
					                console.log(param.sql);
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					            // 非base64编码sql,则进行base64编码(refreshToken时,会重复调用该方法,故简单判断下)
 | 
				
			||||||
 | 
					            if (!Base64.isValid(param.sql)) {
 | 
				
			||||||
                param.sql = Base64.encode(param.sql);
 | 
					                param.sql = Base64.encode(param.sql);
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
        return param;
 | 
					        return param;
 | 
				
			||||||
    }),
 | 
					    }),
 | 
				
			||||||
    // 保存sql
 | 
					    // 保存sql
 | 
				
			||||||
@@ -40,6 +43,7 @@ export const dbApi = {
 | 
				
			|||||||
    instances: Api.newGet('/instances'),
 | 
					    instances: Api.newGet('/instances'),
 | 
				
			||||||
    getInstance: Api.newGet('/instances/{instanceId}'),
 | 
					    getInstance: Api.newGet('/instances/{instanceId}'),
 | 
				
			||||||
    getAllDatabase: Api.newPost('/instances/databases'),
 | 
					    getAllDatabase: Api.newPost('/instances/databases'),
 | 
				
			||||||
 | 
					    getDbNamesByAc: Api.newGet('/instances/databases/{authCertName}'),
 | 
				
			||||||
    getInstanceServerInfo: Api.newGet('/instances/{instanceId}/server-info'),
 | 
					    getInstanceServerInfo: Api.newGet('/instances/{instanceId}/server-info'),
 | 
				
			||||||
    testConn: Api.newPost('/instances/test-conn'),
 | 
					    testConn: Api.newPost('/instances/test-conn'),
 | 
				
			||||||
    saveInstance: Api.newPost('/instances'),
 | 
					    saveInstance: Api.newPost('/instances'),
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -25,6 +25,7 @@ import SvgIcon from '@/components/svgIcon/index.vue';
 | 
				
			|||||||
import { getDbDialect, noSchemaTypes } from '@/views/ops/db/dialect';
 | 
					import { getDbDialect, noSchemaTypes } from '@/views/ops/db/dialect';
 | 
				
			||||||
import TagTreeResourceSelect from '../../component/TagTreeResourceSelect.vue';
 | 
					import TagTreeResourceSelect from '../../component/TagTreeResourceSelect.vue';
 | 
				
			||||||
import { computed } from 'vue';
 | 
					import { computed } from 'vue';
 | 
				
			||||||
 | 
					import { DbInst } from '../db';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const props = defineProps({
 | 
					const props = defineProps({
 | 
				
			||||||
    dbId: {
 | 
					    dbId: {
 | 
				
			||||||
@@ -101,9 +102,9 @@ const noSchemaType = (type: string) => {
 | 
				
			|||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// 数据库实例节点类型
 | 
					// 数据库实例节点类型
 | 
				
			||||||
const NodeTypeDbInst = new NodeType(SqlExecNodeType.DbInst).withLoadNodesFunc((parentNode: TagTreeNode) => {
 | 
					const NodeTypeDbInst = new NodeType(SqlExecNodeType.DbInst).withLoadNodesFunc(async (parentNode: TagTreeNode) => {
 | 
				
			||||||
    const params = parentNode.params;
 | 
					    const params = parentNode.params;
 | 
				
			||||||
    const dbs = params.database.split(' ')?.sort();
 | 
					    const dbs = (await DbInst.getDbNames(params))?.sort();
 | 
				
			||||||
    let fn: NodeType;
 | 
					    let fn: NodeType;
 | 
				
			||||||
    if (noSchemaType(params.type)) {
 | 
					    if (noSchemaType(params.type)) {
 | 
				
			||||||
        fn = MysqlNodeTypes;
 | 
					        fn = MysqlNodeTypes;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -52,7 +52,7 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
            <Pane :size="100 - state.editorSize">
 | 
					            <Pane :size="100 - state.editorSize">
 | 
				
			||||||
                <div class="mt5 sql-exec-res h100">
 | 
					                <div class="mt5 sql-exec-res h100">
 | 
				
			||||||
                    <el-tabs class="h100 w100" v-if="state.execResTabs.length > 0" @tab-remove="onRemoveTab" v-model="state.activeTab">
 | 
					                    <el-tabs class="h100 w100" v-if="state.execResTabs.length > 0" @tab-remove="onRemoveTab" @tab-change="active" v-model="state.activeTab">
 | 
				
			||||||
                        <el-tab-pane class="h100" closable v-for="dt in state.execResTabs" :label="dt.id" :name="dt.id" :key="dt.id">
 | 
					                        <el-tab-pane class="h100" closable v-for="dt in state.execResTabs" :label="dt.id" :name="dt.id" :key="dt.id">
 | 
				
			||||||
                            <template #label>
 | 
					                            <template #label>
 | 
				
			||||||
                                <el-popover :show-after="1000" placement="top-start" title="执行信息" trigger="hover" :width="300">
 | 
					                                <el-popover :show-after="1000" placement="top-start" title="执行信息" trigger="hover" :width="300">
 | 
				
			||||||
@@ -296,44 +296,37 @@ const onRunSql = async (newTab = false) => {
 | 
				
			|||||||
    notBlank(sql && sql.trim(), '请选中需要执行的sql');
 | 
					    notBlank(sql && sql.trim(), '请选中需要执行的sql');
 | 
				
			||||||
    // 去除字符串前的空格、换行等
 | 
					    // 去除字符串前的空格、换行等
 | 
				
			||||||
    sql = sql.replace(/(^\s*)/g, '');
 | 
					    sql = sql.replace(/(^\s*)/g, '');
 | 
				
			||||||
    let execRemark = '';
 | 
					 | 
				
			||||||
    let canRun = true;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // 简单截取前十个字符
 | 
					    // 简单截取前十个字符
 | 
				
			||||||
    const sqlPrefix = sql.slice(0, 10).toLowerCase();
 | 
					    const sqlPrefix = sql.slice(0, 10).toLowerCase();
 | 
				
			||||||
    if (
 | 
					    const nonQuery =
 | 
				
			||||||
        sqlPrefix.startsWith('update') ||
 | 
					        sqlPrefix.startsWith('update') ||
 | 
				
			||||||
        sqlPrefix.startsWith('insert') ||
 | 
					        sqlPrefix.startsWith('insert') ||
 | 
				
			||||||
        sqlPrefix.startsWith('delete') ||
 | 
					        sqlPrefix.startsWith('delete') ||
 | 
				
			||||||
        sqlPrefix.startsWith('alert') ||
 | 
					        sqlPrefix.startsWith('alert') ||
 | 
				
			||||||
        sqlPrefix.startsWith('drop') ||
 | 
					        sqlPrefix.startsWith('drop') ||
 | 
				
			||||||
        sqlPrefix.startsWith('create')
 | 
					        sqlPrefix.startsWith('create');
 | 
				
			||||||
    ) {
 | 
					
 | 
				
			||||||
        const res: any = await ElMessageBox.prompt('请输入备注', 'Tip', {
 | 
					    // 启用工单审批
 | 
				
			||||||
            confirmButtonText: '确定',
 | 
					    if (nonQuery && getNowDbInst().flowProcdef) {
 | 
				
			||||||
            cancelButtonText: '取消',
 | 
					        try {
 | 
				
			||||||
            inputPattern: /^[\s\S]*.*[^\s][\s\S]*$/,
 | 
					            getNowDbInst().promptExeSql(props.dbName, sql, null, () => {
 | 
				
			||||||
            inputErrorMessage: '请输入执行该sql的备注信息',
 | 
					                ElMessage.success('工单提交成功');
 | 
				
			||||||
            });
 | 
					            });
 | 
				
			||||||
        execRemark = res.value;
 | 
					        } catch (e) {
 | 
				
			||||||
        if (!execRemark) {
 | 
					            ElMessage.success('工单提交失败');
 | 
				
			||||||
            canRun = false;
 | 
					 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    if (!canRun) {
 | 
					 | 
				
			||||||
        return;
 | 
					        return;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // 启用工单审批
 | 
					    let execRemark;
 | 
				
			||||||
    if (execRemark && getNowDbInst().flowProcdefKey) {
 | 
					    if (nonQuery) {
 | 
				
			||||||
        try {
 | 
					        const res: any = await ElMessageBox.prompt('请输入备注', 'Tip', {
 | 
				
			||||||
            await getNowDbInst().runSql(props.dbName, sql, execRemark);
 | 
					            confirmButtonText: '确定',
 | 
				
			||||||
            ElMessage.success('工单提交成功');
 | 
					            cancelButtonText: '取消',
 | 
				
			||||||
            return;
 | 
					            inputErrorMessage: '输入执行该sql的备注信息',
 | 
				
			||||||
        } catch (e) {
 | 
					        });
 | 
				
			||||||
            ElMessage.success('工单提交失败');
 | 
					        execRemark = res.value;
 | 
				
			||||||
            return;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    let execRes: ExecResTab;
 | 
					    let execRes: ExecResTab;
 | 
				
			||||||
@@ -707,6 +700,19 @@ const initMonacoEditor = () => {
 | 
				
			|||||||
        },
 | 
					        },
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const active = () => {
 | 
				
			||||||
 | 
					    const resTab = state.execResTabs[state.activeTab - 1];
 | 
				
			||||||
 | 
					    if (!resTab || !resTab.dbTableRef) {
 | 
				
			||||||
 | 
					        return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    resTab.dbTableRef?.active();
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					defineExpose({
 | 
				
			||||||
 | 
					    active,
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
</script>
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<style lang="scss">
 | 
					<style lang="scss">
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -6,7 +6,7 @@ export type SqlExecProps = {
 | 
				
			|||||||
    dbId: number;
 | 
					    dbId: number;
 | 
				
			||||||
    db: string;
 | 
					    db: string;
 | 
				
			||||||
    dbType?: string;
 | 
					    dbType?: string;
 | 
				
			||||||
    flowProcdefKey?: string;
 | 
					    flowProcdef?: any;
 | 
				
			||||||
    runSuccessCallback?: Function;
 | 
					    runSuccessCallback?: Function;
 | 
				
			||||||
    cancelCallback?: Function;
 | 
					    cancelCallback?: Function;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,18 +1,18 @@
 | 
				
			|||||||
<template>
 | 
					<template>
 | 
				
			||||||
    <div>
 | 
					    <div>
 | 
				
			||||||
        <el-dialog title="待执行SQL" v-model="dialogVisible" :show-close="false" width="600px">
 | 
					        <el-dialog title="待执行SQL" v-model="dialogVisible" :show-close="false" width="600px" :close-on-click-modal="false">
 | 
				
			||||||
            <monaco-editor height="300px" class="codesql" language="sql" v-model="sqlValue" />
 | 
					            <monaco-editor height="300px" class="codesql" language="sql" v-model="sqlValue" />
 | 
				
			||||||
            <el-input
 | 
					            <el-input
 | 
				
			||||||
                @keyup.enter="runSql"
 | 
					                @keyup.enter="runSql"
 | 
				
			||||||
                ref="remarkInputRef"
 | 
					                ref="remarkInputRef"
 | 
				
			||||||
                v-model="remark"
 | 
					                v-model="remark"
 | 
				
			||||||
                :placeholder="props.flowProcdefKey ? '执行备注(必填)' : '执行备注(选填)'"
 | 
					                :placeholder="props.flowProcdef ? '执行备注(必填)' : '执行备注(选填)'"
 | 
				
			||||||
                class="mt5"
 | 
					                class="mt5"
 | 
				
			||||||
            />
 | 
					            />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            <div v-if="props.flowProcdefKey">
 | 
					            <div v-if="props.flowProcdef">
 | 
				
			||||||
                <el-divider content-position="left">审批节点</el-divider>
 | 
					                <el-divider content-position="left">审批节点</el-divider>
 | 
				
			||||||
                <procdef-tasks :procdef-key="props.flowProcdefKey" />
 | 
					                <procdef-tasks :procdef="props.flowProcdef" />
 | 
				
			||||||
            </div>
 | 
					            </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            <template #footer>
 | 
					            <template #footer>
 | 
				
			||||||
@@ -59,13 +59,15 @@ onMounted(() => {
 | 
				
			|||||||
 */
 | 
					 */
 | 
				
			||||||
const runSql = async () => {
 | 
					const runSql = async () => {
 | 
				
			||||||
    // 存在流程审批,则备注为必填
 | 
					    // 存在流程审批,则备注为必填
 | 
				
			||||||
    if (!state.remark && props.flowProcdefKey) {
 | 
					    if (!state.remark && props.flowProcdef) {
 | 
				
			||||||
        ElMessage.error('请输入执行的备注信息');
 | 
					        ElMessage.error('请输入执行的备注信息');
 | 
				
			||||||
        return;
 | 
					        return;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    try {
 | 
					    try {
 | 
				
			||||||
        state.btnLoading = true;
 | 
					        state.btnLoading = true;
 | 
				
			||||||
 | 
					        runSuccess = true;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        const res = await dbApi.sqlExec.request({
 | 
					        const res = await dbApi.sqlExec.request({
 | 
				
			||||||
            id: props.dbId,
 | 
					            id: props.dbId,
 | 
				
			||||||
            db: props.db,
 | 
					            db: props.db,
 | 
				
			||||||
@@ -74,8 +76,7 @@ const runSql = async () => {
 | 
				
			|||||||
        });
 | 
					        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // 存在流程审批
 | 
					        // 存在流程审批
 | 
				
			||||||
        if (props.flowProcdefKey) {
 | 
					        if (props.flowProcdef) {
 | 
				
			||||||
            runSuccess = false;
 | 
					 | 
				
			||||||
            ElMessage.success('工单提交成功');
 | 
					            ElMessage.success('工单提交成功');
 | 
				
			||||||
            return;
 | 
					            return;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
@@ -87,7 +88,6 @@ const runSql = async () => {
 | 
				
			|||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        runSuccess = true;
 | 
					 | 
				
			||||||
        ElMessage.success('执行成功');
 | 
					        ElMessage.success('执行成功');
 | 
				
			||||||
    } catch (e) {
 | 
					    } catch (e) {
 | 
				
			||||||
        runSuccess = false;
 | 
					        runSuccess = false;
 | 
				
			||||||
@@ -96,9 +96,9 @@ const runSql = async () => {
 | 
				
			|||||||
            if (props.runSuccessCallback) {
 | 
					            if (props.runSuccessCallback) {
 | 
				
			||||||
                props.runSuccessCallback();
 | 
					                props.runSuccessCallback();
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					            cancel();
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        state.btnLoading = false;
 | 
					        state.btnLoading = false;
 | 
				
			||||||
        cancel();
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -113,7 +113,7 @@ const cancel = () => {
 | 
				
			|||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const open = () => {
 | 
					const open = () => {
 | 
				
			||||||
    state.sqlValue = sqlFormatter(props.sql, { language: props.dbType || 'mysql' });
 | 
					    state.sqlValue = sqlFormatter(props.sql, { language: (props.dbType || 'mysql') as any });
 | 
				
			||||||
    state.dialogVisible = true;
 | 
					    state.dialogVisible = true;
 | 
				
			||||||
    setTimeout(() => {
 | 
					    setTimeout(() => {
 | 
				
			||||||
        remarkInputRef.value?.focus();
 | 
					        remarkInputRef.value?.focus();
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,7 +1,6 @@
 | 
				
			|||||||
<template>
 | 
					<template>
 | 
				
			||||||
    <div class="string-input-container w100" v-if="dataType == DataType.String">
 | 
					    <div class="string-input-container w100" v-if="dataType == DataType.String || dataType == DataType.Number">
 | 
				
			||||||
        <el-input
 | 
					        <el-input
 | 
				
			||||||
            v-if="dataType == DataType.String"
 | 
					 | 
				
			||||||
            :ref="(el: any) => focus && el?.focus()"
 | 
					            :ref="(el: any) => focus && el?.focus()"
 | 
				
			||||||
            :disabled="disabled"
 | 
					            :disabled="disabled"
 | 
				
			||||||
            @blur="handleBlur"
 | 
					            @blur="handleBlur"
 | 
				
			||||||
@@ -13,18 +12,6 @@
 | 
				
			|||||||
        <SvgIcon v-if="showEditorIcon" @mousedown="openEditor" class="string-input-container-icon" name="FullScreen" :size="10" />
 | 
					        <SvgIcon v-if="showEditorIcon" @mousedown="openEditor" class="string-input-container-icon" name="FullScreen" :size="10" />
 | 
				
			||||||
    </div>
 | 
					    </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    <el-input
 | 
					 | 
				
			||||||
        v-else-if="dataType == DataType.Number"
 | 
					 | 
				
			||||||
        :ref="(el: any) => focus && el?.focus()"
 | 
					 | 
				
			||||||
        :disabled="disabled"
 | 
					 | 
				
			||||||
        @blur="handleBlur"
 | 
					 | 
				
			||||||
        class="w100 mb4"
 | 
					 | 
				
			||||||
        size="small"
 | 
					 | 
				
			||||||
        v-model.number="itemValue"
 | 
					 | 
				
			||||||
        :placeholder="placeholder"
 | 
					 | 
				
			||||||
        type="number"
 | 
					 | 
				
			||||||
    />
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    <el-date-picker
 | 
					    <el-date-picker
 | 
				
			||||||
        v-else-if="dataType == DataType.Date"
 | 
					        v-else-if="dataType == DataType.Date"
 | 
				
			||||||
        :ref="(el: any) => focus && el?.focus()"
 | 
					        :ref="(el: any) => focus && el?.focus()"
 | 
				
			||||||
@@ -38,7 +25,7 @@
 | 
				
			|||||||
        :clearable="false"
 | 
					        :clearable="false"
 | 
				
			||||||
        type="Date"
 | 
					        type="Date"
 | 
				
			||||||
        value-format="YYYY-MM-DD"
 | 
					        value-format="YYYY-MM-DD"
 | 
				
			||||||
        placeholder="选择日期"
 | 
					        :placeholder="`选择日期-${placeholder}`"
 | 
				
			||||||
    />
 | 
					    />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    <el-date-picker
 | 
					    <el-date-picker
 | 
				
			||||||
@@ -54,7 +41,7 @@
 | 
				
			|||||||
        :clearable="false"
 | 
					        :clearable="false"
 | 
				
			||||||
        type="datetime"
 | 
					        type="datetime"
 | 
				
			||||||
        value-format="YYYY-MM-DD HH:mm:ss"
 | 
					        value-format="YYYY-MM-DD HH:mm:ss"
 | 
				
			||||||
        placeholder="选择日期时间"
 | 
					        :placeholder="`选择日期时间-${placeholder}`"
 | 
				
			||||||
    />
 | 
					    />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    <el-time-picker
 | 
					    <el-time-picker
 | 
				
			||||||
@@ -69,13 +56,13 @@
 | 
				
			|||||||
        v-model="itemValue"
 | 
					        v-model="itemValue"
 | 
				
			||||||
        :clearable="false"
 | 
					        :clearable="false"
 | 
				
			||||||
        value-format="HH:mm:ss"
 | 
					        value-format="HH:mm:ss"
 | 
				
			||||||
        placeholder="选择时间"
 | 
					        :placeholder="`选择时间-${placeholder}`"
 | 
				
			||||||
    />
 | 
					    />
 | 
				
			||||||
</template>
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<script lang="ts" setup>
 | 
					<script lang="ts" setup>
 | 
				
			||||||
import { computed, ref, Ref } from 'vue';
 | 
					import { computed, ref, Ref } from 'vue';
 | 
				
			||||||
import { ElInput } from 'element-plus';
 | 
					import { ElInput, ElMessage } from 'element-plus';
 | 
				
			||||||
import { DataType } from '../../dialect/index';
 | 
					import { DataType } from '../../dialect/index';
 | 
				
			||||||
import SvgIcon from '@/components/svgIcon/index.vue';
 | 
					import SvgIcon from '@/components/svgIcon/index.vue';
 | 
				
			||||||
import MonacoEditorDialog from '@/components/monaco/MonacoEditorDialog';
 | 
					import MonacoEditorDialog from '@/components/monaco/MonacoEditorDialog';
 | 
				
			||||||
@@ -130,6 +117,10 @@ const handleBlur = () => {
 | 
				
			|||||||
    if (editorOpening.value) {
 | 
					    if (editorOpening.value) {
 | 
				
			||||||
        return;
 | 
					        return;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					    if (props.dataType == DataType.Number && itemValue.value && !/^-?\d*\.?\d+$/.test(itemValue.value)) {
 | 
				
			||||||
 | 
					        ElMessage.error('输入内容与类型不匹配');
 | 
				
			||||||
 | 
					        return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
    emit('update:modelValue', itemValue.value);
 | 
					    emit('update:modelValue', itemValue.value);
 | 
				
			||||||
    emit('blur');
 | 
					    emit('blur');
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
@@ -161,6 +152,10 @@ const getEditorLangByValue = (value: any) => {
 | 
				
			|||||||
<style lang="scss">
 | 
					<style lang="scss">
 | 
				
			||||||
.string-input-container {
 | 
					.string-input-container {
 | 
				
			||||||
    position: relative;
 | 
					    position: relative;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    .el-input__wrapper {
 | 
				
			||||||
 | 
					        padding: 1px 3px;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
.string-input-container-show-icon {
 | 
					.string-input-container-show-icon {
 | 
				
			||||||
    .el-input__inner {
 | 
					    .el-input__inner {
 | 
				
			||||||
@@ -183,6 +178,10 @@ const getEditorLangByValue = (value: any) => {
 | 
				
			|||||||
    .el-input__prefix {
 | 
					    .el-input__prefix {
 | 
				
			||||||
        display: none;
 | 
					        display: none;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    .el-input__wrapper {
 | 
				
			||||||
 | 
					        padding: 1px 3px;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.edit-time-picker-popper {
 | 
					.edit-time-picker-popper {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -15,6 +15,7 @@
 | 
				
			|||||||
                    fixed
 | 
					                    fixed
 | 
				
			||||||
                    class="table"
 | 
					                    class="table"
 | 
				
			||||||
                    :row-event-handlers="rowEventHandlers"
 | 
					                    :row-event-handlers="rowEventHandlers"
 | 
				
			||||||
 | 
					                    @scroll="onTableScroll"
 | 
				
			||||||
                >
 | 
					                >
 | 
				
			||||||
                    <template #header="{ columns }">
 | 
					                    <template #header="{ columns }">
 | 
				
			||||||
                        <div v-for="(column, i) in columns" :key="i">
 | 
					                        <div v-for="(column, i) in columns" :key="i">
 | 
				
			||||||
@@ -36,7 +37,7 @@
 | 
				
			|||||||
                                    <!-- 字段列的数据类型 -->
 | 
					                                    <!-- 字段列的数据类型 -->
 | 
				
			||||||
                                    <div class="column-type">
 | 
					                                    <div class="column-type">
 | 
				
			||||||
                                        <span v-if="column.dataTypeSubscript === 'icon-clock'">
 | 
					                                        <span v-if="column.dataTypeSubscript === 'icon-clock'">
 | 
				
			||||||
                                            <SvgIcon :size="10" name="Clock" style="cursor: unset" />
 | 
					                                            <SvgIcon :size="9" name="Clock" style="cursor: unset" />
 | 
				
			||||||
                                        </span>
 | 
					                                        </span>
 | 
				
			||||||
                                        <span class="font8" v-else>{{ column.dataTypeSubscript }}</span>
 | 
					                                        <span class="font8" v-else>{{ column.dataTypeSubscript }}</span>
 | 
				
			||||||
                                    </div>
 | 
					                                    </div>
 | 
				
			||||||
@@ -59,9 +60,7 @@
 | 
				
			|||||||
                                    </div>
 | 
					                                    </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                                    <div v-else class="header-column-title">
 | 
					                                    <div v-else class="header-column-title">
 | 
				
			||||||
                                        <b class="el-text">
 | 
					                                        <b class="el-text"> {{ column.title }} </b>
 | 
				
			||||||
                                            {{ column.title }}
 | 
					 | 
				
			||||||
                                        </b>
 | 
					 | 
				
			||||||
                                    </div>
 | 
					                                    </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                                    <!-- 字段列右部分内容 -->
 | 
					                                    <!-- 字段列右部分内容 -->
 | 
				
			||||||
@@ -96,7 +95,7 @@
 | 
				
			|||||||
                                    />
 | 
					                                    />
 | 
				
			||||||
                                </div>
 | 
					                                </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                                <div v-else :class="isUpdated(rowIndex, column.dataKey) ? 'update_field_active' : ''">
 | 
					                                <div v-else :class="isUpdated(rowIndex, column.dataKey) ? 'update_field_active ml2 mr2' : 'ml2 mr2'">
 | 
				
			||||||
                                    <span v-if="rowData[column.dataKey!] === null" style="color: var(--el-color-info-light-5)"> NULL </span>
 | 
					                                    <span v-if="rowData[column.dataKey!] === null" style="color: var(--el-color-info-light-5)"> NULL </span>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                                    <span v-else :title="rowData[column.dataKey!]" class="el-text el-text--small is-truncated">
 | 
					                                    <span v-else :title="rowData[column.dataKey!]" class="el-text el-text--small is-truncated">
 | 
				
			||||||
@@ -121,7 +120,7 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
                    <template #empty>
 | 
					                    <template #empty>
 | 
				
			||||||
                        <div style="text-align: center">
 | 
					                        <div style="text-align: center">
 | 
				
			||||||
                            <el-empty class="h100" :description="props.emptyText" :image-size="100" />
 | 
					                            <el-empty :description="props.emptyText" :image-size="100" />
 | 
				
			||||||
                        </div>
 | 
					                        </div>
 | 
				
			||||||
                    </template>
 | 
					                    </template>
 | 
				
			||||||
                </el-table-v2>
 | 
					                </el-table-v2>
 | 
				
			||||||
@@ -157,11 +156,11 @@
 | 
				
			|||||||
import { onBeforeUnmount, onMounted, reactive, ref, toRefs, watch } from 'vue';
 | 
					import { onBeforeUnmount, onMounted, reactive, ref, toRefs, watch } from 'vue';
 | 
				
			||||||
import { ElInput, ElMessage } from 'element-plus';
 | 
					import { ElInput, ElMessage } from 'element-plus';
 | 
				
			||||||
import { copyToClipboard } from '@/common/utils/string';
 | 
					import { copyToClipboard } from '@/common/utils/string';
 | 
				
			||||||
import { DbInst } from '@/views/ops/db/db';
 | 
					import { DbInst, DbThemeConfig } from '@/views/ops/db/db';
 | 
				
			||||||
import { Contextmenu, ContextmenuItem } from '@/components/contextmenu';
 | 
					import { Contextmenu, ContextmenuItem } from '@/components/contextmenu';
 | 
				
			||||||
import SvgIcon from '@/components/svgIcon/index.vue';
 | 
					import SvgIcon from '@/components/svgIcon/index.vue';
 | 
				
			||||||
import { exportCsv, exportFile } from '@/common/utils/export';
 | 
					import { exportCsv, exportFile } from '@/common/utils/export';
 | 
				
			||||||
import { dateStrFormat } from '@/common/utils/date';
 | 
					import { formatDate } from '@/common/utils/format';
 | 
				
			||||||
import { useIntervalFn, useStorage } from '@vueuse/core';
 | 
					import { useIntervalFn, useStorage } from '@vueuse/core';
 | 
				
			||||||
import { ColumnTypeSubscript, compatibleMysql, DataType, DbDialect, getDbDialect } from '../../dialect/index';
 | 
					import { ColumnTypeSubscript, compatibleMysql, DataType, DbDialect, getDbDialect } from '../../dialect/index';
 | 
				
			||||||
import ColumnFormItem from './ColumnFormItem.vue';
 | 
					import ColumnFormItem from './ColumnFormItem.vue';
 | 
				
			||||||
@@ -259,12 +258,10 @@ const cmDataDel = new ContextmenuItem('deleteData', '删除')
 | 
				
			|||||||
        return state.table == '';
 | 
					        return state.table == '';
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const cmDataEdit = new ContextmenuItem('editData', '编辑行')
 | 
					const cmFormView = new ContextmenuItem('formView', '表单视图').withIcon('Document').withOnClick(() => onEditRowData());
 | 
				
			||||||
    .withIcon('edit')
 | 
					// .withHideFunc(() => {
 | 
				
			||||||
    .withOnClick(() => onEditRowData())
 | 
					//     return state.table == '';
 | 
				
			||||||
    .withHideFunc(() => {
 | 
					// });
 | 
				
			||||||
        return state.table == '';
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
const cmDataGenInsertSql = new ContextmenuItem('genInsertSql', 'Insert SQL')
 | 
					const cmDataGenInsertSql = new ContextmenuItem('genInsertSql', 'Insert SQL')
 | 
				
			||||||
    .withIcon('tickets')
 | 
					    .withIcon('tickets')
 | 
				
			||||||
@@ -364,7 +361,7 @@ const state = reactive({
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
const { tableHeight, datas } = toRefs(state);
 | 
					const { tableHeight, datas } = toRefs(state);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const dbConfig = useStorage('dbConfig', { showColumnComment: false });
 | 
					const dbConfig = useStorage('dbConfig', DbThemeConfig);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * 行号字段列
 | 
					 * 行号字段列
 | 
				
			||||||
@@ -476,9 +473,9 @@ const setTableColumns = (columns: any) => {
 | 
				
			|||||||
    state.columns = columns.map((x: any) => {
 | 
					    state.columns = columns.map((x: any) => {
 | 
				
			||||||
        const columnName = x.columnName;
 | 
					        const columnName = x.columnName;
 | 
				
			||||||
        // 数据类型
 | 
					        // 数据类型
 | 
				
			||||||
        x.dataType = dbDialect.getDataType(x.dataType);
 | 
					        x.dataType = dbDialect.getDataType(x.columnType);
 | 
				
			||||||
        x.dataTypeSubscript = ColumnTypeSubscript[x.dataType];
 | 
					        x.dataTypeSubscript = ColumnTypeSubscript[x.dataType];
 | 
				
			||||||
        x.remark = `${x.showDataType} ${x.columnComment ? ' |  ' + x.columnComment : ''}`;
 | 
					        x.remark = `${x.columnType} ${x.columnComment ? ' |  ' + x.columnComment : ''}`;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return {
 | 
					        return {
 | 
				
			||||||
            ...x,
 | 
					            ...x,
 | 
				
			||||||
@@ -486,7 +483,7 @@ const setTableColumns = (columns: any) => {
 | 
				
			|||||||
            dataKey: columnName,
 | 
					            dataKey: columnName,
 | 
				
			||||||
            width: DbInst.flexColumnWidth(columnName, state.datas),
 | 
					            width: DbInst.flexColumnWidth(columnName, state.datas),
 | 
				
			||||||
            title: columnName,
 | 
					            title: columnName,
 | 
				
			||||||
            align: 'center',
 | 
					            align: x.dataType == DataType.Number ? 'right' : 'left',
 | 
				
			||||||
            headerClass: 'table-column',
 | 
					            headerClass: 'table-column',
 | 
				
			||||||
            class: 'table-column',
 | 
					            class: 'table-column',
 | 
				
			||||||
            sortable: true,
 | 
					            sortable: true,
 | 
				
			||||||
@@ -596,7 +593,7 @@ const dataContextmenuClick = (event: any, rowIndex: number, column: any, data: a
 | 
				
			|||||||
    const { clientX, clientY } = event;
 | 
					    const { clientX, clientY } = event;
 | 
				
			||||||
    state.contextmenu.dropdown.x = clientX;
 | 
					    state.contextmenu.dropdown.x = clientX;
 | 
				
			||||||
    state.contextmenu.dropdown.y = clientY;
 | 
					    state.contextmenu.dropdown.y = clientY;
 | 
				
			||||||
    state.contextmenu.items = [cmDataCopyCell, cmDataDel, cmDataEdit, cmDataGenInsertSql, cmDataGenJson, cmDataExportCsv, cmDataExportSql];
 | 
					    state.contextmenu.items = [cmDataCopyCell, cmDataDel, cmFormView, cmDataGenInsertSql, cmDataGenJson, cmDataExportCsv, cmDataExportSql];
 | 
				
			||||||
    contextmenuRef.value.openContextmenu({ column, rowData: data });
 | 
					    contextmenuRef.value.openContextmenu({ column, rowData: data });
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -617,6 +614,10 @@ const onDeleteData = async () => {
 | 
				
			|||||||
    const db = state.db;
 | 
					    const db = state.db;
 | 
				
			||||||
    const dbInst = getNowDbInst();
 | 
					    const dbInst = getNowDbInst();
 | 
				
			||||||
    dbInst.promptExeSql(db, await dbInst.genDeleteByPrimaryKeysSql(db, state.table, deleteDatas as any), null, () => {
 | 
					    dbInst.promptExeSql(db, await dbInst.genDeleteByPrimaryKeysSql(db, state.table, deleteDatas as any), null, () => {
 | 
				
			||||||
 | 
					        // 存在流程则恢复原值,需工单流程审批完后自动执行
 | 
				
			||||||
 | 
					        if (dbInst.flowProcdef) {
 | 
				
			||||||
 | 
					            return;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
        emits('dataDelete', deleteDatas);
 | 
					        emits('dataDelete', deleteDatas);
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
@@ -624,12 +625,12 @@ const onDeleteData = async () => {
 | 
				
			|||||||
const onEditRowData = () => {
 | 
					const onEditRowData = () => {
 | 
				
			||||||
    const selectionDatas = Array.from(selectionRowsMap.values());
 | 
					    const selectionDatas = Array.from(selectionRowsMap.values());
 | 
				
			||||||
    if (selectionDatas.length > 1) {
 | 
					    if (selectionDatas.length > 1) {
 | 
				
			||||||
        ElMessage.warning('只能编辑一行数据');
 | 
					        ElMessage.warning('只能选择一行数据');
 | 
				
			||||||
        return;
 | 
					        return;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    const data = selectionDatas[0];
 | 
					    const data = selectionDatas[0];
 | 
				
			||||||
    state.tableDataFormDialog.data = data;
 | 
					    state.tableDataFormDialog.data = { ...data };
 | 
				
			||||||
    state.tableDataFormDialog.title = `编辑表'${props.table}'数据`;
 | 
					    state.tableDataFormDialog.title = state.table ? `'${props.table}'表单数据` : '表单视图';
 | 
				
			||||||
    state.tableDataFormDialog.visible = true;
 | 
					    state.tableDataFormDialog.visible = true;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -645,7 +646,7 @@ const onGenerateJson = async () => {
 | 
				
			|||||||
    // 按列字段重新排序对象key
 | 
					    // 按列字段重新排序对象key
 | 
				
			||||||
    const jsonObj = [];
 | 
					    const jsonObj = [];
 | 
				
			||||||
    for (let selectionData of selectionDatas) {
 | 
					    for (let selectionData of selectionDatas) {
 | 
				
			||||||
        let obj = {};
 | 
					        let obj: any = {};
 | 
				
			||||||
        for (let column of state.columns) {
 | 
					        for (let column of state.columns) {
 | 
				
			||||||
            if (column.show) {
 | 
					            if (column.show) {
 | 
				
			||||||
                obj[column.title] = selectionData[column.dataKey];
 | 
					                obj[column.title] = selectionData[column.dataKey];
 | 
				
			||||||
@@ -674,13 +675,13 @@ const onExportCsv = () => {
 | 
				
			|||||||
            columnNames.push(column.columnName);
 | 
					            columnNames.push(column.columnName);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    exportCsv(`数据导出-${state.table}-${dateStrFormat('yyyyMMddHHmm', new Date().toString())}`, columnNames, dataList);
 | 
					    exportCsv(`数据导出-${state.table}-${formatDate(new Date(), 'yyyyMMddHHmm')}`, columnNames, dataList);
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const onExportSql = async () => {
 | 
					const onExportSql = async () => {
 | 
				
			||||||
    const selectionDatas = state.datas;
 | 
					    const selectionDatas = state.datas;
 | 
				
			||||||
    exportFile(
 | 
					    exportFile(
 | 
				
			||||||
        `数据导出-${state.table}-${dateStrFormat('yyyyMMddHHmm', new Date().toString())}.sql`,
 | 
					        `数据导出-${state.table}-${formatDate(new Date(), 'yyyyMMddHHmm')}.sql`,
 | 
				
			||||||
        await getNowDbInst().genInsertSql(state.db, state.table, selectionDatas)
 | 
					        await getNowDbInst().genInsertSql(state.db, state.table, selectionDatas)
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
@@ -749,7 +750,7 @@ const submitUpdateFields = async () => {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    for (let updateRow of cellUpdateMap.values()) {
 | 
					    for (let updateRow of cellUpdateMap.values()) {
 | 
				
			||||||
        const rowData = { ...updateRow.rowData };
 | 
					        const rowData = { ...updateRow.rowData };
 | 
				
			||||||
        let updateColumnValue = {};
 | 
					        let updateColumnValue: any = {};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        for (let k of updateRow.columnsMap.keys()) {
 | 
					        for (let k of updateRow.columnsMap.keys()) {
 | 
				
			||||||
            const v = updateRow.columnsMap.get(k);
 | 
					            const v = updateRow.columnsMap.get(k);
 | 
				
			||||||
@@ -763,7 +764,12 @@ const submitUpdateFields = async () => {
 | 
				
			|||||||
        res += await dbInst.genUpdateSql(db, state.table, updateColumnValue, rowData);
 | 
					        res += await dbInst.genUpdateSql(db, state.table, updateColumnValue, rowData);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    dbInst.promptExeSql(db, res, cancelUpdateFields, () => {
 | 
					    dbInst.promptExeSql(db, res, null, () => {
 | 
				
			||||||
 | 
					        // 存在流程则恢复原值,需工单流程审批完后自动执行
 | 
				
			||||||
 | 
					        if (dbInst.flowProcdef) {
 | 
				
			||||||
 | 
					            cancelUpdateFields();
 | 
				
			||||||
 | 
					            return;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
        triggerRefresh();
 | 
					        triggerRefresh();
 | 
				
			||||||
        cellUpdateMap.clear();
 | 
					        cellUpdateMap.clear();
 | 
				
			||||||
        changeUpdatedField();
 | 
					        changeUpdatedField();
 | 
				
			||||||
@@ -810,11 +816,11 @@ const getFormatTimeValue = (dataType: DataType, originValue: string): string =>
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    switch (dataType) {
 | 
					    switch (dataType) {
 | 
				
			||||||
        case DataType.Time:
 | 
					        case DataType.Time:
 | 
				
			||||||
            return dateStrFormat('HH:mm:ss', originValue);
 | 
					            return formatDate(originValue, 'HH:mm:ss');
 | 
				
			||||||
        case DataType.Date:
 | 
					        case DataType.Date:
 | 
				
			||||||
            return dateStrFormat('yyyy-MM-dd', originValue);
 | 
					            return formatDate(originValue, 'YYYY-MM-DD');
 | 
				
			||||||
        case DataType.DateTime:
 | 
					        case DataType.DateTime:
 | 
				
			||||||
            return dateStrFormat('yyyy-MM-dd HH:mm:ss', originValue);
 | 
					            return formatDate(originValue, 'YYYY-MM-DD HH:mm:ss');
 | 
				
			||||||
        default:
 | 
					        default:
 | 
				
			||||||
            return originValue;
 | 
					            return originValue;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@@ -832,11 +838,23 @@ const triggerRefresh = () => {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const scrollLeftValue = ref(0);
 | 
				
			||||||
 | 
					const onTableScroll = (param: any) => {
 | 
				
			||||||
 | 
					    scrollLeftValue.value = param.scrollLeft;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * 激活表格,恢复滚动位置,否则会造成表头与数据单元格错位(暂不知为啥,先这样解决)
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					const active = () => {
 | 
				
			||||||
 | 
					    setTimeout(() => tableRef.value.scrollToLeft(scrollLeftValue.value));
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const getNowDbInst = () => {
 | 
					const getNowDbInst = () => {
 | 
				
			||||||
    return DbInst.getInst(state.dbId);
 | 
					    return DbInst.getInst(state.dbId);
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
defineExpose({
 | 
					defineExpose({
 | 
				
			||||||
 | 
					    active,
 | 
				
			||||||
    submitUpdateFields,
 | 
					    submitUpdateFields,
 | 
				
			||||||
    cancelUpdateFields,
 | 
					    cancelUpdateFields,
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
@@ -880,8 +898,8 @@ defineExpose({
 | 
				
			|||||||
        color: var(--el-color-info-light-3);
 | 
					        color: var(--el-color-info-light-3);
 | 
				
			||||||
        font-weight: bold;
 | 
					        font-weight: bold;
 | 
				
			||||||
        position: absolute;
 | 
					        position: absolute;
 | 
				
			||||||
        top: -5px;
 | 
					        top: -7px;
 | 
				
			||||||
        padding: 2px;
 | 
					        padding: 1px;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    .column-right {
 | 
					    .column-right {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -6,10 +6,10 @@
 | 
				
			|||||||
                :key="column.columnName"
 | 
					                :key="column.columnName"
 | 
				
			||||||
                class="w100 mb5"
 | 
					                class="w100 mb5"
 | 
				
			||||||
                :prop="column.columnName"
 | 
					                :prop="column.columnName"
 | 
				
			||||||
                :required="column.nullable != 'YES' && !column.isPrimaryKey && !column.isIdentity"
 | 
					                :required="props.tableName != '' && !column.nullable && !column.isPrimaryKey && !column.isIdentity"
 | 
				
			||||||
            >
 | 
					            >
 | 
				
			||||||
                <template #label>
 | 
					                <template #label>
 | 
				
			||||||
                    <span class="pointer" :title="`${column.showDataType} | ${column.columnComment}`">
 | 
					                    <span class="pointer" :title="column?.columnComment ? `${column.columnType} | ${column.columnComment}` : column.columnType">
 | 
				
			||||||
                        {{ column.columnName }}
 | 
					                        {{ column.columnName }}
 | 
				
			||||||
                    </span>
 | 
					                    </span>
 | 
				
			||||||
                </template>
 | 
					                </template>
 | 
				
			||||||
@@ -17,13 +17,13 @@
 | 
				
			|||||||
                <ColumnFormItem
 | 
					                <ColumnFormItem
 | 
				
			||||||
                    v-model="modelValue[`${column.columnName}`]"
 | 
					                    v-model="modelValue[`${column.columnName}`]"
 | 
				
			||||||
                    :data-type="dbInst.getDialect().getDataType(column.dataType)"
 | 
					                    :data-type="dbInst.getDialect().getDataType(column.dataType)"
 | 
				
			||||||
                    :placeholder="`${column.showDataType}  ${column.columnComment}`"
 | 
					                    :placeholder="column?.columnComment ? `${column.columnType} | ${column.columnComment}` : column.columnType"
 | 
				
			||||||
                    :column-name="column.columnName"
 | 
					                    :column-name="column.columnName"
 | 
				
			||||||
                    :disabled="column.isIdentity"
 | 
					                    :disabled="column.isIdentity"
 | 
				
			||||||
                />
 | 
					                />
 | 
				
			||||||
            </el-form-item>
 | 
					            </el-form-item>
 | 
				
			||||||
        </el-form>
 | 
					        </el-form>
 | 
				
			||||||
        <template #footer>
 | 
					        <template #footer v-if="props.tableName">
 | 
				
			||||||
            <span class="dialog-footer">
 | 
					            <span class="dialog-footer">
 | 
				
			||||||
                <el-button @click="closeDialog">取消</el-button>
 | 
					                <el-button @click="closeDialog">取消</el-button>
 | 
				
			||||||
                <el-button type="primary" @click="confirm">确定</el-button>
 | 
					                <el-button type="primary" @click="confirm">确定</el-button>
 | 
				
			||||||
@@ -37,7 +37,6 @@ import { ref, watch, onMounted } from 'vue';
 | 
				
			|||||||
import ColumnFormItem from './ColumnFormItem.vue';
 | 
					import ColumnFormItem from './ColumnFormItem.vue';
 | 
				
			||||||
import { DbInst } from '../../db';
 | 
					import { DbInst } from '../../db';
 | 
				
			||||||
import { ElMessage } from 'element-plus';
 | 
					import { ElMessage } from 'element-plus';
 | 
				
			||||||
import { getDbDialect } from '@/views/ops/db/dialect';
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
export interface ColumnFormItemProps {
 | 
					export interface ColumnFormItemProps {
 | 
				
			||||||
    dbInst: DbInst;
 | 
					    dbInst: DbInst;
 | 
				
			||||||
@@ -86,8 +85,9 @@ const closeDialog = () => {
 | 
				
			|||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const confirm = async () => {
 | 
					const confirm = async () => {
 | 
				
			||||||
    dataForm.value.validate(async (valid: boolean) => {
 | 
					    try {
 | 
				
			||||||
        if (!valid) {
 | 
					        await dataForm.value.validate();
 | 
				
			||||||
 | 
					    } catch (e: any) {
 | 
				
			||||||
        ElMessage.error('请正确填写数据信息');
 | 
					        ElMessage.error('请正确填写数据信息');
 | 
				
			||||||
        return false;
 | 
					        return false;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@@ -99,7 +99,7 @@ const confirm = async () => {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    let sql = '';
 | 
					    let sql = '';
 | 
				
			||||||
    if (oldValue) {
 | 
					    if (oldValue) {
 | 
				
			||||||
            const updateColumnValue = {};
 | 
					        const updateColumnValue: any = {};
 | 
				
			||||||
        Object.keys(oldValue).forEach((key) => {
 | 
					        Object.keys(oldValue).forEach((key) => {
 | 
				
			||||||
            // 如果新旧值不相等,则为需要更新的字段
 | 
					            // 如果新旧值不相等,则为需要更新的字段
 | 
				
			||||||
            if (oldValue[key] !== modelValue.value[key]) {
 | 
					            if (oldValue[key] !== modelValue.value[key]) {
 | 
				
			||||||
@@ -115,7 +115,6 @@ const confirm = async () => {
 | 
				
			|||||||
        closeDialog();
 | 
					        closeDialog();
 | 
				
			||||||
        emit('submitSuccess');
 | 
					        emit('submitSuccess');
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
</script>
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -12,16 +12,30 @@
 | 
				
			|||||||
                        width="auto"
 | 
					                        width="auto"
 | 
				
			||||||
                        title="表格字段配置"
 | 
					                        title="表格字段配置"
 | 
				
			||||||
                        trigger="click"
 | 
					                        trigger="click"
 | 
				
			||||||
 | 
					                        @hide="triggerCheckedColumns"
 | 
				
			||||||
                    >
 | 
					                    >
 | 
				
			||||||
                        <div v-for="(item, index) in columns" :key="index">
 | 
					                        <div><el-input v-model="checkedShowColumns.searchKey" size="small" placeholder="输入列名或备注过滤" /></div>
 | 
				
			||||||
 | 
					                        <div>
 | 
				
			||||||
                            <el-checkbox
 | 
					                            <el-checkbox
 | 
				
			||||||
                                v-model="item.show"
 | 
					                                v-model="checkedShowColumns.checkedAllColumn"
 | 
				
			||||||
 | 
					                                :indeterminate="checkedShowColumns.isIndeterminate"
 | 
				
			||||||
 | 
					                                @change="handleCheckAllColumnChange"
 | 
				
			||||||
 | 
					                                size="small"
 | 
				
			||||||
 | 
					                            >
 | 
				
			||||||
 | 
					                                选择所有
 | 
				
			||||||
 | 
					                            </el-checkbox>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                            <el-checkbox-group v-model="checkedShowColumns.columnNames" @change="handleCheckedColumnChange">
 | 
				
			||||||
 | 
					                                <div v-for="(item, index) in filterCheckedColumns" :key="index">
 | 
				
			||||||
 | 
					                                    <el-checkbox
 | 
				
			||||||
 | 
					                                        :key="index"
 | 
				
			||||||
                                        :label="`${!item.columnComment ? item.columnName : item.columnName + ' [' + item.columnComment + ']'}`"
 | 
					                                        :label="`${!item.columnComment ? item.columnName : item.columnName + ' [' + item.columnComment + ']'}`"
 | 
				
			||||||
                                :true-value="true"
 | 
					                                        :value="item.columnName"
 | 
				
			||||||
                                :false-value="false"
 | 
					 | 
				
			||||||
                                        size="small"
 | 
					                                        size="small"
 | 
				
			||||||
                                    />
 | 
					                                    />
 | 
				
			||||||
                                </div>
 | 
					                                </div>
 | 
				
			||||||
 | 
					                            </el-checkbox-group>
 | 
				
			||||||
 | 
					                        </div>
 | 
				
			||||||
                        <template #reference>
 | 
					                        <template #reference>
 | 
				
			||||||
                            <el-link icon="Operation" size="small" :underline="false"></el-link>
 | 
					                            <el-link icon="Operation" size="small" :underline="false"></el-link>
 | 
				
			||||||
                        </template>
 | 
					                        </template>
 | 
				
			||||||
@@ -36,33 +50,6 @@
 | 
				
			|||||||
                    </el-tooltip>
 | 
					                    </el-tooltip>
 | 
				
			||||||
                    <el-divider direction="vertical" border-style="dashed" />
 | 
					                    <el-divider direction="vertical" border-style="dashed" />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    <el-tooltip :show-after="500" class="box-item" effect="dark" content="commit" placement="top">
 | 
					 | 
				
			||||||
                        <template #content>
 | 
					 | 
				
			||||||
                            1. 右击数据/表头可显示操作菜单 <br />
 | 
					 | 
				
			||||||
                            2. 按住Ctrl点击数据则为多选 <br />
 | 
					 | 
				
			||||||
                            3. 双击单元格可编辑数据 <br />
 | 
					 | 
				
			||||||
                            4. 鼠标悬停字段名或标签树的表名可提示相关备注
 | 
					 | 
				
			||||||
                        </template>
 | 
					 | 
				
			||||||
                        <el-link icon="QuestionFilled" :underline="false"> </el-link>
 | 
					 | 
				
			||||||
                    </el-tooltip>
 | 
					 | 
				
			||||||
                    <el-divider direction="vertical" border-style="dashed" />
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    <!-- 表数据展示配置 -->
 | 
					 | 
				
			||||||
                    <el-popover
 | 
					 | 
				
			||||||
                        popper-style="max-height: 550px; overflow: auto; max-width: 450px"
 | 
					 | 
				
			||||||
                        placement="bottom"
 | 
					 | 
				
			||||||
                        width="auto"
 | 
					 | 
				
			||||||
                        title="展示配置"
 | 
					 | 
				
			||||||
                        trigger="click"
 | 
					 | 
				
			||||||
                    >
 | 
					 | 
				
			||||||
                        <el-checkbox v-model="dbConfig.showColumnComment" label="显示字段备注" :true-value="true" :false-value="false" size="small" />
 | 
					 | 
				
			||||||
                        <template #reference>
 | 
					 | 
				
			||||||
                            <el-link type="primary" icon="setting" :underline="false"></el-link>
 | 
					 | 
				
			||||||
                        </template>
 | 
					 | 
				
			||||||
                    </el-popover>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    <el-divider direction="vertical" border-style="dashed" />
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    <el-tooltip :show-after="500" v-if="hasUpdatedFileds" class="box-item" effect="dark" content="提交修改" placement="top">
 | 
					                    <el-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-link @click="submitUpdateFields()" type="success" :underline="false" class="font12">提交</el-link>
 | 
				
			||||||
                    </el-tooltip>
 | 
					                    </el-tooltip>
 | 
				
			||||||
@@ -98,7 +85,7 @@
 | 
				
			|||||||
                        <el-divider direction="vertical" />
 | 
					                        <el-divider direction="vertical" />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                        <span style="color: var(--el-color-info-light-3)">
 | 
					                        <span style="color: var(--el-color-info-light-3)">
 | 
				
			||||||
                            {{ item.showDataType }}
 | 
					                            {{ item.columnType }}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                            <template v-if="item.columnComment">
 | 
					                            <template v-if="item.columnComment">
 | 
				
			||||||
                                <el-divider direction="vertical" />
 | 
					                                <el-divider direction="vertical" />
 | 
				
			||||||
@@ -255,8 +242,8 @@ import { DbInst } from '@/views/ops/db/db';
 | 
				
			|||||||
import DbTableData from './DbTableData.vue';
 | 
					import DbTableData from './DbTableData.vue';
 | 
				
			||||||
import { DbDialect } from '@/views/ops/db/dialect';
 | 
					import { DbDialect } from '@/views/ops/db/dialect';
 | 
				
			||||||
import SvgIcon from '@/components/svgIcon/index.vue';
 | 
					import SvgIcon from '@/components/svgIcon/index.vue';
 | 
				
			||||||
import { useEventListener, useStorage } from '@vueuse/core';
 | 
					import { useEventListener } from '@vueuse/core';
 | 
				
			||||||
import { copyToClipboard } from '@/common/utils/string';
 | 
					import { copyToClipboard, fuzzyMatchField } from '@/common/utils/string';
 | 
				
			||||||
import DbTableDataForm from './DbTableDataForm.vue';
 | 
					import DbTableDataForm from './DbTableDataForm.vue';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const props = defineProps({
 | 
					const props = defineProps({
 | 
				
			||||||
@@ -285,8 +272,6 @@ const condDialogInputRef: Ref = ref(null);
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
const defaultPageSize = DbInst.DefaultLimit;
 | 
					const defaultPageSize = DbInst.DefaultLimit;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const dbConfig = useStorage('dbConfig', { showColumnComment: false });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const state = reactive({
 | 
					const state = reactive({
 | 
				
			||||||
    datas: [],
 | 
					    datas: [],
 | 
				
			||||||
    sql: '', // 当前数据tab执行的sql
 | 
					    sql: '', // 当前数据tab执行的sql
 | 
				
			||||||
@@ -329,9 +314,17 @@ const state = reactive({
 | 
				
			|||||||
    tableHeight: '600px',
 | 
					    tableHeight: '600px',
 | 
				
			||||||
    hasUpdatedFileds: false,
 | 
					    hasUpdatedFileds: false,
 | 
				
			||||||
    dbDialect: {} as DbDialect,
 | 
					    dbDialect: {} as DbDialect,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    checkedShowColumns: {
 | 
				
			||||||
 | 
					        searchKey: '',
 | 
				
			||||||
 | 
					        checkedAllColumn: true,
 | 
				
			||||||
 | 
					        isIndeterminate: false,
 | 
				
			||||||
 | 
					        columnNames: [] as any,
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const { datas, condition, loading, columns, pageNum, pageSize, pageSizes, sql, hasUpdatedFileds, conditionDialog, addDataDialog } = toRefs(state);
 | 
					const { datas, condition, loading, columns, checkedShowColumns, pageNum, pageSize, pageSizes, sql, hasUpdatedFileds, conditionDialog, addDataDialog } =
 | 
				
			||||||
 | 
					    toRefs(state);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
watch(
 | 
					watch(
 | 
				
			||||||
    () => props.tableHeight,
 | 
					    () => props.tableHeight,
 | 
				
			||||||
@@ -351,6 +344,8 @@ onMounted(async () => {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    state.dbDialect = getNowDbInst().getDialect();
 | 
					    state.dbDialect = getNowDbInst().getDialect();
 | 
				
			||||||
    useEventListener('click', handlerWindowClick);
 | 
					    useEventListener('click', handlerWindowClick);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    state.checkedShowColumns.columnNames = state.columns.map((item: any) => item.columnName);
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const handlerWindowClick = () => {
 | 
					const handlerWindowClick = () => {
 | 
				
			||||||
@@ -414,6 +409,7 @@ const handleSetPageNum = async () => {
 | 
				
			|||||||
    state.pageNum = state.setPageNum;
 | 
					    state.pageNum = state.setPageNum;
 | 
				
			||||||
    await selectData();
 | 
					    await selectData();
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const handleCount = async () => {
 | 
					const handleCount = async () => {
 | 
				
			||||||
    state.counting = true;
 | 
					    state.counting = true;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -431,6 +427,24 @@ const handleCount = async () => {
 | 
				
			|||||||
    state.counting = false;
 | 
					    state.counting = false;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const handleCheckAllColumnChange = (val: boolean) => {
 | 
				
			||||||
 | 
					    state.checkedShowColumns.columnNames = val ? state.columns.map((x: any) => x.columnName) : [];
 | 
				
			||||||
 | 
					    state.checkedShowColumns.isIndeterminate = false;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const handleCheckedColumnChange = (value: string[]) => {
 | 
				
			||||||
 | 
					    const checkedCount = value.length;
 | 
				
			||||||
 | 
					    state.checkedShowColumns.checkedAllColumn = checkedCount === state.columns.length;
 | 
				
			||||||
 | 
					    state.checkedShowColumns.isIndeterminate = checkedCount > 0 && checkedCount < state.columns.length;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const triggerCheckedColumns = () => {
 | 
				
			||||||
 | 
					    const checkedColumnNames = state.checkedShowColumns.columnNames;
 | 
				
			||||||
 | 
					    for (let column of state.columns) {
 | 
				
			||||||
 | 
					        column.show = checkedColumnNames.includes(column.columnName);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// 完整的条件,每次选中后会重置条件框内容,故需要这个变量在获取建议时将文本框内容保存
 | 
					// 完整的条件,每次选中后会重置条件框内容,故需要这个变量在获取建议时将文本框内容保存
 | 
				
			||||||
let completeCond = '';
 | 
					let completeCond = '';
 | 
				
			||||||
// 是否存在列建议
 | 
					// 是否存在列建议
 | 
				
			||||||
@@ -444,10 +458,7 @@ const getColumnTips = (queryString: string, callback: any) => {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    let res = [];
 | 
					    let res = [];
 | 
				
			||||||
    if (columnNameSearch) {
 | 
					    if (columnNameSearch) {
 | 
				
			||||||
        columnNameSearch = columnNameSearch.toLowerCase();
 | 
					        res = fuzzyMatchField(columnNameSearch, columns, (x: any) => x.columnName);
 | 
				
			||||||
        res = columns.filter((data: any) => {
 | 
					 | 
				
			||||||
            return data.columnName.toLowerCase().includes(columnNameSearch);
 | 
					 | 
				
			||||||
        });
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    completeCond = condition.value;
 | 
					    completeCond = condition.value;
 | 
				
			||||||
@@ -490,16 +501,25 @@ const chooseCondColumnName = () => {
 | 
				
			|||||||
 * 过滤条件列名
 | 
					 * 过滤条件列名
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
const filterCondColumns = computed(() => {
 | 
					const filterCondColumns = computed(() => {
 | 
				
			||||||
 | 
					    return filterColumns(state.columnNameSearch);
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const filterCheckedColumns = computed(() => {
 | 
				
			||||||
 | 
					    return filterColumns(state.checkedShowColumns.searchKey);
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const filterColumns = (searchKey: string) => {
 | 
				
			||||||
    const columns = state.columns;
 | 
					    const columns = state.columns;
 | 
				
			||||||
    let columnNameSearch = state.columnNameSearch;
 | 
					    if (!searchKey) {
 | 
				
			||||||
    if (!columnNameSearch) {
 | 
					 | 
				
			||||||
        return columns;
 | 
					        return columns;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    columnNameSearch = columnNameSearch.toLowerCase();
 | 
					    return fuzzyMatchField(
 | 
				
			||||||
    return columns.filter((data: any) => {
 | 
					        searchKey,
 | 
				
			||||||
        return data.columnName.toLowerCase().includes(columnNameSearch) || data.columnComment.toLowerCase().includes(columnNameSearch);
 | 
					        columns,
 | 
				
			||||||
    });
 | 
					        (x: any) => x.columnName,
 | 
				
			||||||
});
 | 
					        (x: any) => x.columnComment
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * 条件查询,点击列信息后显示输入对应的值
 | 
					 * 条件查询,点击列信息后显示输入对应的值
 | 
				
			||||||
@@ -507,7 +527,7 @@ const filterCondColumns = computed(() => {
 | 
				
			|||||||
const onConditionRowClick = (event: any) => {
 | 
					const onConditionRowClick = (event: any) => {
 | 
				
			||||||
    const row = event[0];
 | 
					    const row = event[0];
 | 
				
			||||||
    state.conditionDialog.title = `请输入 [${row.columnName}] 的值`;
 | 
					    state.conditionDialog.title = `请输入 [${row.columnName}] 的值`;
 | 
				
			||||||
    state.conditionDialog.placeholder = `${row.showDataType}  ${row.columnComment}`;
 | 
					    state.conditionDialog.placeholder = `${row.columnType}  ${row.columnComment}`;
 | 
				
			||||||
    state.conditionDialog.columnRow = row;
 | 
					    state.conditionDialog.columnRow = row;
 | 
				
			||||||
    state.conditionDialog.visible = true;
 | 
					    state.conditionDialog.visible = true;
 | 
				
			||||||
    setTimeout(() => {
 | 
					    setTimeout(() => {
 | 
				
			||||||
@@ -583,6 +603,10 @@ const onShowAddDataDialog = async () => {
 | 
				
			|||||||
    state.addDataDialog.title = `添加'${props.tableName}'表数据`;
 | 
					    state.addDataDialog.title = `添加'${props.tableName}'表数据`;
 | 
				
			||||||
    state.addDataDialog.visible = true;
 | 
					    state.addDataDialog.visible = true;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					defineExpose({
 | 
				
			||||||
 | 
					    active: () => dbTableRef.value.active(),
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
</script>
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<style lang="scss">
 | 
					<style lang="scss">
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -152,8 +152,8 @@ const props = defineProps({
 | 
				
			|||||||
    dbType: {
 | 
					    dbType: {
 | 
				
			||||||
        type: String,
 | 
					        type: String,
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    flowProcdefKey: {
 | 
					    flowProcdef: {
 | 
				
			||||||
        type: String,
 | 
					        type: Object,
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -335,7 +335,7 @@ const submit = async () => {
 | 
				
			|||||||
        dbId: props.dbId as any,
 | 
					        dbId: props.dbId as any,
 | 
				
			||||||
        db: props.db as any,
 | 
					        db: props.db as any,
 | 
				
			||||||
        dbType: dbDialect.getInfo().formatSqlDialect,
 | 
					        dbType: dbDialect.getInfo().formatSqlDialect,
 | 
				
			||||||
        flowProcdefKey: props.flowProcdefKey,
 | 
					        flowProcdef: props.flowProcdef,
 | 
				
			||||||
        runSuccessCallback: () => {
 | 
					        runSuccessCallback: () => {
 | 
				
			||||||
            emit('submit-sql', { tableName: state.tableData.tableName });
 | 
					            emit('submit-sql', { tableName: state.tableData.tableName });
 | 
				
			||||||
            // cancel();
 | 
					            // cancel();
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,9 +1,9 @@
 | 
				
			|||||||
<template>
 | 
					<template>
 | 
				
			||||||
    <div class="db-table">
 | 
					    <div class="db-table">
 | 
				
			||||||
        <el-row class="mb5">
 | 
					        <el-row class="mb5">
 | 
				
			||||||
            <el-popover v-model:visible="showDumpInfo" :width="470" placement="right" trigger="click">
 | 
					            <el-popover v-model:visible="state.dumpInfo.visible" trigger="click" :width="470" placement="right">
 | 
				
			||||||
                <template #reference>
 | 
					                <template #reference>
 | 
				
			||||||
                    <el-button class="ml5" type="success" size="small">导出</el-button>
 | 
					                    <el-button :disabled="state.dumpInfo.tables?.length == 0" class="ml5" type="success" size="small">导出</el-button>
 | 
				
			||||||
                </template>
 | 
					                </template>
 | 
				
			||||||
                <el-form-item label="导出内容: ">
 | 
					                <el-form-item label="导出内容: ">
 | 
				
			||||||
                    <el-radio-group v-model="dumpInfo.type">
 | 
					                    <el-radio-group v-model="dumpInfo.type">
 | 
				
			||||||
@@ -13,16 +13,15 @@
 | 
				
			|||||||
                    </el-radio-group>
 | 
					                    </el-radio-group>
 | 
				
			||||||
                </el-form-item>
 | 
					                </el-form-item>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                <el-form-item label="导出表: ">
 | 
					                <el-form-item>
 | 
				
			||||||
                    <el-table @selection-change="handleDumpTableSelectionChange" max-height="300" size="small" :data="tables">
 | 
					                    <el-table :data="state.dumpInfo.tables" empty-text="请先选择要导出的表" max-height="300" size="small">
 | 
				
			||||||
                        <el-table-column type="selection" width="45" />
 | 
					 | 
				
			||||||
                        <el-table-column property="tableName" label="表名" min-width="150" show-overflow-tooltip> </el-table-column>
 | 
					                        <el-table-column property="tableName" label="表名" min-width="150" show-overflow-tooltip> </el-table-column>
 | 
				
			||||||
                        <el-table-column property="tableComment" label="备注" min-width="150" show-overflow-tooltip> </el-table-column>
 | 
					                        <el-table-column property="tableComment" label="备注" min-width="150" show-overflow-tooltip> </el-table-column>
 | 
				
			||||||
                    </el-table>
 | 
					                    </el-table>
 | 
				
			||||||
                </el-form-item>
 | 
					                </el-form-item>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                <div style="text-align: right">
 | 
					                <div style="text-align: right">
 | 
				
			||||||
                    <el-button @click="showDumpInfo = false" size="small">取消</el-button>
 | 
					                    <el-button @click="state.dumpInfo.visible = false" size="small">取消</el-button>
 | 
				
			||||||
                    <el-button @click="dump(db)" type="success" size="small">确定</el-button>
 | 
					                    <el-button @click="dump(db)" type="success" size="small">确定</el-button>
 | 
				
			||||||
                </div>
 | 
					                </div>
 | 
				
			||||||
            </el-popover>
 | 
					            </el-popover>
 | 
				
			||||||
@@ -30,7 +29,9 @@
 | 
				
			|||||||
            <el-button type="primary" size="small" @click="openEditTable(false)">创建表</el-button>
 | 
					            <el-button type="primary" size="small" @click="openEditTable(false)">创建表</el-button>
 | 
				
			||||||
        </el-row>
 | 
					        </el-row>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        <el-table v-loading="loading" border stripe :data="filterTableInfos" size="small" :height="height">
 | 
					        <el-table v-loading="loading" @selection-change="handleDumpTableSelectionChange" border stripe :data="filterTableInfos" size="small" :height="height">
 | 
				
			||||||
 | 
					            <el-table-column type="selection" width="30" />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            <el-table-column property="tableName" label="表名" min-width="150" show-overflow-tooltip>
 | 
					            <el-table-column property="tableName" label="表名" min-width="150" show-overflow-tooltip>
 | 
				
			||||||
                <template #header>
 | 
					                <template #header>
 | 
				
			||||||
                    <el-input v-model="tableNameSearch" size="small" placeholder="表名: 输入可过滤" clearable />
 | 
					                    <el-input v-model="tableNameSearch" size="small" placeholder="表名: 输入可过滤" clearable />
 | 
				
			||||||
@@ -82,7 +83,7 @@
 | 
				
			|||||||
        <el-dialog width="40%" :title="`${chooseTableName} 字段信息`" v-model="columnDialog.visible">
 | 
					        <el-dialog width="40%" :title="`${chooseTableName} 字段信息`" v-model="columnDialog.visible">
 | 
				
			||||||
            <el-table border stripe :data="columnDialog.columns" size="small">
 | 
					            <el-table border stripe :data="columnDialog.columns" size="small">
 | 
				
			||||||
                <el-table-column prop="columnName" label="名称" show-overflow-tooltip> </el-table-column>
 | 
					                <el-table-column prop="columnName" label="名称" show-overflow-tooltip> </el-table-column>
 | 
				
			||||||
                <el-table-column width="120" prop="showDataType" label="类型" show-overflow-tooltip> </el-table-column>
 | 
					                <el-table-column width="120" prop="columnType" label="类型" show-overflow-tooltip> </el-table-column>
 | 
				
			||||||
                <el-table-column width="80" prop="nullable" label="是否可为空" show-overflow-tooltip> </el-table-column>
 | 
					                <el-table-column width="80" prop="nullable" label="是否可为空" show-overflow-tooltip> </el-table-column>
 | 
				
			||||||
                <el-table-column prop="columnComment" label="备注" show-overflow-tooltip> </el-table-column>
 | 
					                <el-table-column prop="columnComment" label="备注" show-overflow-tooltip> </el-table-column>
 | 
				
			||||||
            </el-table>
 | 
					            </el-table>
 | 
				
			||||||
@@ -108,7 +109,7 @@
 | 
				
			|||||||
            :dbId="dbId"
 | 
					            :dbId="dbId"
 | 
				
			||||||
            :db="db"
 | 
					            :db="db"
 | 
				
			||||||
            :dbType="dbType"
 | 
					            :dbType="dbType"
 | 
				
			||||||
            :flow-procdef-key="props.flowProcdefKey"
 | 
					            :flow-procdef="props.flowProcdef"
 | 
				
			||||||
            :data="tableCreateDialog.data"
 | 
					            :data="tableCreateDialog.data"
 | 
				
			||||||
            v-model:visible="tableCreateDialog.visible"
 | 
					            v-model:visible="tableCreateDialog.visible"
 | 
				
			||||||
            @submit-sql="onSubmitSql"
 | 
					            @submit-sql="onSubmitSql"
 | 
				
			||||||
@@ -130,6 +131,7 @@ import { compatibleMysql, editDbTypes, getDbDialect } from '../../dialect/index'
 | 
				
			|||||||
import { DbInst } from '../../db';
 | 
					import { DbInst } from '../../db';
 | 
				
			||||||
import MonacoEditor from '@/components/monaco/MonacoEditor.vue';
 | 
					import MonacoEditor from '@/components/monaco/MonacoEditor.vue';
 | 
				
			||||||
import { format as sqlFormatter } from 'sql-formatter';
 | 
					import { format as sqlFormatter } from 'sql-formatter';
 | 
				
			||||||
 | 
					import { fuzzyMatchField } from '@/common/utils/string';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const DbTableOp = defineAsyncComponent(() => import('./DbTableOp.vue'));
 | 
					const DbTableOp = defineAsyncComponent(() => import('./DbTableOp.vue'));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -150,8 +152,8 @@ const props = defineProps({
 | 
				
			|||||||
        type: [String],
 | 
					        type: [String],
 | 
				
			||||||
        required: true,
 | 
					        required: true,
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    flowProcdefKey: {
 | 
					    flowProcdef: {
 | 
				
			||||||
        type: [String],
 | 
					        type: [Object],
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -161,8 +163,8 @@ const state = reactive({
 | 
				
			|||||||
    tables: [],
 | 
					    tables: [],
 | 
				
			||||||
    tableNameSearch: '',
 | 
					    tableNameSearch: '',
 | 
				
			||||||
    tableCommentSearch: '',
 | 
					    tableCommentSearch: '',
 | 
				
			||||||
    showDumpInfo: false,
 | 
					 | 
				
			||||||
    dumpInfo: {
 | 
					    dumpInfo: {
 | 
				
			||||||
 | 
					        visible: false,
 | 
				
			||||||
        id: 0,
 | 
					        id: 0,
 | 
				
			||||||
        db: '',
 | 
					        db: '',
 | 
				
			||||||
        type: 3,
 | 
					        type: 3,
 | 
				
			||||||
@@ -201,19 +203,7 @@ const state = reactive({
 | 
				
			|||||||
    },
 | 
					    },
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const {
 | 
					const { loading, tableNameSearch, tableCommentSearch, dumpInfo, chooseTableName, columnDialog, indexDialog, ddlDialog, tableCreateDialog } = toRefs(state);
 | 
				
			||||||
    loading,
 | 
					 | 
				
			||||||
    tables,
 | 
					 | 
				
			||||||
    tableNameSearch,
 | 
					 | 
				
			||||||
    tableCommentSearch,
 | 
					 | 
				
			||||||
    showDumpInfo,
 | 
					 | 
				
			||||||
    dumpInfo,
 | 
					 | 
				
			||||||
    chooseTableName,
 | 
					 | 
				
			||||||
    columnDialog,
 | 
					 | 
				
			||||||
    indexDialog,
 | 
					 | 
				
			||||||
    ddlDialog,
 | 
					 | 
				
			||||||
    tableCreateDialog,
 | 
					 | 
				
			||||||
} = toRefs(state);
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
onMounted(async () => {
 | 
					onMounted(async () => {
 | 
				
			||||||
    getTables();
 | 
					    getTables();
 | 
				
			||||||
@@ -230,17 +220,11 @@ const filterTableInfos = computed(() => {
 | 
				
			|||||||
    if (!tableNameSearch && !tableCommentSearch) {
 | 
					    if (!tableNameSearch && !tableCommentSearch) {
 | 
				
			||||||
        return tables;
 | 
					        return tables;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    return tables.filter((data: any) => {
 | 
					
 | 
				
			||||||
        let tnMatch = true;
 | 
					 | 
				
			||||||
        let tcMatch = true;
 | 
					 | 
				
			||||||
    if (tableNameSearch) {
 | 
					    if (tableNameSearch) {
 | 
				
			||||||
            tnMatch = data.tableName.toLowerCase().includes(tableNameSearch.toLowerCase());
 | 
					        return fuzzyMatchField(tableNameSearch, tables, (table: any) => table.tableName);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
        if (tableCommentSearch) {
 | 
					    return fuzzyMatchField(tableCommentSearch, tables, (table: any) => table.tableComment);
 | 
				
			||||||
            tcMatch = data.tableComment.includes(tableCommentSearch);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        return tnMatch && tcMatch;
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const getTables = async () => {
 | 
					const getTables = async () => {
 | 
				
			||||||
@@ -259,21 +243,22 @@ const getTables = async () => {
 | 
				
			|||||||
 * 选择导出数据库表
 | 
					 * 选择导出数据库表
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
const handleDumpTableSelectionChange = (vals: any) => {
 | 
					const handleDumpTableSelectionChange = (vals: any) => {
 | 
				
			||||||
    state.dumpInfo.tables = vals.map((x: any) => x.tableName);
 | 
					    state.dumpInfo.tables = vals;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * 数据库信息导出
 | 
					 * 数据库信息导出
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
const dump = (db: string) => {
 | 
					const dump = (db: string) => {
 | 
				
			||||||
    isTrue(state.dumpInfo.tables.length > 0, '请选择要导出的表');
 | 
					    isTrue(state.dumpInfo.tables.length > 0, '请先选择要导出的表');
 | 
				
			||||||
 | 
					    const tableNames = state.dumpInfo.tables.map((x: any) => x.tableName);
 | 
				
			||||||
    const a = document.createElement('a');
 | 
					    const a = document.createElement('a');
 | 
				
			||||||
    a.setAttribute(
 | 
					    a.setAttribute(
 | 
				
			||||||
        'href',
 | 
					        'href',
 | 
				
			||||||
        `${config.baseApiUrl}/dbs/${props.dbId}/dump?db=${db}&type=${state.dumpInfo.type}&tables=${state.dumpInfo.tables.join(',')}&${joinClientParams()}`
 | 
					        `${config.baseApiUrl}/dbs/${props.dbId}/dump?db=${db}&type=${state.dumpInfo.type}&tables=${tableNames.join(',')}&${joinClientParams()}`
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
    a.click();
 | 
					    a.click();
 | 
				
			||||||
    state.showDumpInfo = false;
 | 
					    state.dumpInfo.visible = false;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const showColumns = async (row: any) => {
 | 
					const showColumns = async (row: any) => {
 | 
				
			||||||
@@ -327,7 +312,7 @@ const dropTable = async (row: any) => {
 | 
				
			|||||||
            sql: `DROP TABLE ${tableName}`,
 | 
					            sql: `DROP TABLE ${tableName}`,
 | 
				
			||||||
            dbId: props.dbId as any,
 | 
					            dbId: props.dbId as any,
 | 
				
			||||||
            db: props.db as any,
 | 
					            db: props.db as any,
 | 
				
			||||||
            flowProcdefKey: props.flowProcdefKey,
 | 
					            flowProcdef: props.flowProcdef,
 | 
				
			||||||
            runSuccessCallback: async () => {
 | 
					            runSuccessCallback: async () => {
 | 
				
			||||||
                await getTables();
 | 
					                await getTables();
 | 
				
			||||||
            },
 | 
					            },
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -8,6 +8,7 @@ import { editor, languages, Position } from 'monaco-editor';
 | 
				
			|||||||
import { registerCompletionItemProvider } from '@/components/monaco/completionItemProvider';
 | 
					import { registerCompletionItemProvider } from '@/components/monaco/completionItemProvider';
 | 
				
			||||||
import { DbDialect, EditorCompletionItem, getDbDialect } from './dialect';
 | 
					import { DbDialect, EditorCompletionItem, getDbDialect } from './dialect';
 | 
				
			||||||
import { type RemovableRef, useLocalStorage } from '@vueuse/core';
 | 
					import { type RemovableRef, useLocalStorage } from '@vueuse/core';
 | 
				
			||||||
 | 
					import { DbGetDbNamesMode } from './enums';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const hintsStorage: RemovableRef<Map<string, any>> = useLocalStorage('db-table-hints', new Map());
 | 
					const hintsStorage: RemovableRef<Map<string, any>> = useLocalStorage('db-table-hints', new Map());
 | 
				
			||||||
const tableStorage: RemovableRef<Map<string, any>> = useLocalStorage('db-tables', new Map());
 | 
					const tableStorage: RemovableRef<Map<string, any>> = useLocalStorage('db-tables', new Map());
 | 
				
			||||||
@@ -41,9 +42,9 @@ export class DbInst {
 | 
				
			|||||||
    type: string;
 | 
					    type: string;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * 流程定义key,若存在则需要审批执行
 | 
					     * 流程定义,若存在则需要审批执行
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    flowProcdefKey: string;
 | 
					    flowProcdef: any;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * dbName -> db
 | 
					     * dbName -> db
 | 
				
			||||||
@@ -359,7 +360,7 @@ export class DbInst {
 | 
				
			|||||||
            dbType: this.getDialect().getInfo().formatSqlDialect,
 | 
					            dbType: this.getDialect().getInfo().formatSqlDialect,
 | 
				
			||||||
            runSuccessCallback: successFunc,
 | 
					            runSuccessCallback: successFunc,
 | 
				
			||||||
            cancelCallback: cancelFunc,
 | 
					            cancelCallback: cancelFunc,
 | 
				
			||||||
            flowProcdefKey: this.flowProcdefKey,
 | 
					            flowProcdef: this.flowProcdef,
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -383,6 +384,11 @@ export class DbInst {
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
        let dbInst = dbInstCache.get(inst.id);
 | 
					        let dbInst = dbInstCache.get(inst.id);
 | 
				
			||||||
        if (dbInst) {
 | 
					        if (dbInst) {
 | 
				
			||||||
 | 
					            // 更新可能更改的流程定义
 | 
				
			||||||
 | 
					            if (inst.flowProcdef !== undefined) {
 | 
				
			||||||
 | 
					                dbInst.flowProcdef = inst.flowProcdef;
 | 
				
			||||||
 | 
					                dbInstCache.set(dbInst.id, dbInst);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
            return dbInst;
 | 
					            return dbInst;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        console.info(`new dbInst: ${inst.id}, tagPath: ${inst.tagPath}`);
 | 
					        console.info(`new dbInst: ${inst.id}, tagPath: ${inst.tagPath}`);
 | 
				
			||||||
@@ -393,7 +399,7 @@ export class DbInst {
 | 
				
			|||||||
        dbInst.name = inst.name;
 | 
					        dbInst.name = inst.name;
 | 
				
			||||||
        dbInst.type = inst.type;
 | 
					        dbInst.type = inst.type;
 | 
				
			||||||
        dbInst.databases = inst.databases;
 | 
					        dbInst.databases = inst.databases;
 | 
				
			||||||
        dbInst.flowProcdefKey = inst.flowProcdefKey;
 | 
					        dbInst.flowProcdef = inst.flowProcdef;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        dbInstCache.set(dbInst.id, dbInst);
 | 
					        dbInstCache.set(dbInst.id, dbInst);
 | 
				
			||||||
        return dbInst;
 | 
					        return dbInst;
 | 
				
			||||||
@@ -444,8 +450,8 @@ export class DbInst {
 | 
				
			|||||||
            return;
 | 
					            return;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // 获取列名称的长度 加上排序图标长度、abc为字段类型简称占位符
 | 
					        // 获取列名称的长度 加上排序图标长度、abc为字段类型简称占位符、排序图标等
 | 
				
			||||||
        const columnWidth: number = getTextWidth(prop + 'abc') + 23;
 | 
					        const columnWidth: number = getTextWidth(prop + 'abc') + 10;
 | 
				
			||||||
        // prop为该列的字段名(传字符串);tableData为该表格的数据源(传变量);
 | 
					        // prop为该列的字段名(传字符串);tableData为该表格的数据源(传变量);
 | 
				
			||||||
        if (!tableData || !tableData.length || tableData.length === 0 || tableData === undefined) {
 | 
					        if (!tableData || !tableData.length || tableData.length === 0 || tableData === undefined) {
 | 
				
			||||||
            return columnWidth;
 | 
					            return columnWidth;
 | 
				
			||||||
@@ -465,7 +471,7 @@ export class DbInst {
 | 
				
			|||||||
                maxWidthText = nowText;
 | 
					                maxWidthText = nowText;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        const contentWidth: number = getTextWidth(maxWidthText) + 15;
 | 
					        const contentWidth: number = getTextWidth(maxWidthText) + 3;
 | 
				
			||||||
        const flexWidth: number = contentWidth > columnWidth ? contentWidth : columnWidth;
 | 
					        const flexWidth: number = contentWidth > columnWidth ? contentWidth : columnWidth;
 | 
				
			||||||
        return flexWidth > 500 ? 500 : flexWidth;
 | 
					        return flexWidth > 500 ? 500 : flexWidth;
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
@@ -477,17 +483,17 @@ export class DbInst {
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
        for (let col of columns) {
 | 
					        for (let col of columns) {
 | 
				
			||||||
            if (col.charMaxLength > 0) {
 | 
					            if (col.charMaxLength > 0) {
 | 
				
			||||||
                col.showDataType = `${col.dataType}(${col.charMaxLength})`;
 | 
					                col.columnType = `${col.dataType}(${col.charMaxLength})`;
 | 
				
			||||||
                col.showLength = col.charMaxLength;
 | 
					                col.showLength = col.charMaxLength;
 | 
				
			||||||
                col.showScale = null;
 | 
					                col.showScale = null;
 | 
				
			||||||
                continue;
 | 
					                continue;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            if (col.numPrecision > 0) {
 | 
					            if (col.numPrecision > 0) {
 | 
				
			||||||
                if (col.numScale > 0) {
 | 
					                if (col.numScale > 0) {
 | 
				
			||||||
                    col.showDataType = `${col.dataType}(${col.numPrecision},${col.numScale})`;
 | 
					                    col.columnType = `${col.dataType}(${col.numPrecision},${col.numScale})`;
 | 
				
			||||||
                    col.showScale = col.numScale;
 | 
					                    col.showScale = col.numScale;
 | 
				
			||||||
                } else {
 | 
					                } else {
 | 
				
			||||||
                    col.showDataType = `${col.dataType}(${col.numPrecision})`;
 | 
					                    col.columnType = `${col.dataType}(${col.numPrecision})`;
 | 
				
			||||||
                    col.showScale = null;
 | 
					                    col.showScale = null;
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -495,9 +501,22 @@ export class DbInst {
 | 
				
			|||||||
                continue;
 | 
					                continue;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            col.showDataType = col.dataType;
 | 
					            col.columnType = col.dataType;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * 根据数据库配置信息获取对应的库名列表
 | 
				
			||||||
 | 
					     * @param db db配置信息
 | 
				
			||||||
 | 
					     * @returns 库名列表
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    static async getDbNames(db: any) {
 | 
				
			||||||
 | 
					        if (db.getDatabaseMode == DbGetDbNamesMode.Assign.value) {
 | 
				
			||||||
 | 
					            return db.database.split(' ');
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return await dbApi.getDbNamesByAc.request({ authCertName: db.authCertName });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
@@ -582,6 +601,11 @@ export class TabInfo {
 | 
				
			|||||||
     */
 | 
					     */
 | 
				
			||||||
    params: any;
 | 
					    params: any;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * 组件ref
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    componentRef: any;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    getNowDbInst() {
 | 
					    getNowDbInst() {
 | 
				
			||||||
        return DbInst.getInst(this.dbId);
 | 
					        return DbInst.getInst(this.dbId);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@@ -818,3 +842,18 @@ function getTableName4SqlCtx(sql: string, alias: string = '', defaultDb: string)
 | 
				
			|||||||
        return tables.length > 0 ? tables[0] : undefined;
 | 
					        return tables.length > 0 ? tables[0] : undefined;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * 数据库主题配置
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					export const DbThemeConfig = {
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * 表数据表头是否显示备注
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    showColumnComment: true,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * 是否自动定位至树节点
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    locationTreeNode: true,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -69,11 +69,11 @@ export enum DataType {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/** 列数据类型角标 */
 | 
					/** 列数据类型角标 */
 | 
				
			||||||
export const ColumnTypeSubscript = {
 | 
					export const ColumnTypeSubscript: any = {
 | 
				
			||||||
    /** 字符串 */
 | 
					    /** 字符串 */
 | 
				
			||||||
    string: 'abc',
 | 
					    string: 'ab',
 | 
				
			||||||
    /** 数字 */
 | 
					    /** 数字 */
 | 
				
			||||||
    number: '123',
 | 
					    number: '12',
 | 
				
			||||||
    /** 日期 */
 | 
					    /** 日期 */
 | 
				
			||||||
    date: 'icon-clock',
 | 
					    date: 'icon-clock',
 | 
				
			||||||
    /** 时间 */
 | 
					    /** 时间 */
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,5 +1,10 @@
 | 
				
			|||||||
import { EnumValue } from '@/common/Enum';
 | 
					import { EnumValue } from '@/common/Enum';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const DbGetDbNamesMode = {
 | 
				
			||||||
 | 
					    Auto: EnumValue.of(-1, '实时获取').setTagType('warning'),
 | 
				
			||||||
 | 
					    Assign: EnumValue.of(1, '指定库名').setTagType('primary'),
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// 数据库sql执行类型
 | 
					// 数据库sql执行类型
 | 
				
			||||||
export const DbSqlExecTypeEnum = {
 | 
					export const DbSqlExecTypeEnum = {
 | 
				
			||||||
    Update: EnumValue.of(1, 'UPDATE').setTagColor('#E4F5EB'),
 | 
					    Update: EnumValue.of(1, 'UPDATE').setTagColor('#E4F5EB'),
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -106,7 +106,9 @@ const props = defineProps({
 | 
				
			|||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
//定义事件
 | 
					//定义事件
 | 
				
			||||||
const emit = defineEmits(['update:visible', 'cancel', 'val-change']);
 | 
					const emit = defineEmits(['cancel', 'val-change']);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const dialogVisible = defineModel<boolean>('visible', { default: false });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const rules = {
 | 
					const rules = {
 | 
				
			||||||
    tagCodePaths: [
 | 
					    tagCodePaths: [
 | 
				
			||||||
@@ -170,23 +172,19 @@ const defaultForm = {
 | 
				
			|||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const state = reactive({
 | 
					const state = reactive({
 | 
				
			||||||
    dialogVisible: false,
 | 
					 | 
				
			||||||
    sshTunnelMachineList: [] as any,
 | 
					    sshTunnelMachineList: [] as any,
 | 
				
			||||||
    form: defaultForm,
 | 
					    form: defaultForm,
 | 
				
			||||||
    submitForm: {} as any,
 | 
					    submitForm: {} as any,
 | 
				
			||||||
    pwd: '',
 | 
					    pwd: '',
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const { dialogVisible, form, submitForm } = toRefs(state);
 | 
					const { form, submitForm } = toRefs(state);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const { isFetching: testConnBtnLoading, execute: testConnExec } = machineApi.testConn.useApi(submitForm);
 | 
					const { isFetching: testConnBtnLoading, execute: testConnExec } = machineApi.testConn.useApi(submitForm);
 | 
				
			||||||
const { isFetching: saveBtnLoading, execute: saveMachineExec } = machineApi.saveMachine.useApi(submitForm);
 | 
					const { isFetching: saveBtnLoading, execute: saveMachineExec } = machineApi.saveMachine.useApi(submitForm);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
watchEffect(() => {
 | 
					watchEffect(() => {
 | 
				
			||||||
    state.dialogVisible = props.visible;
 | 
					    if (!dialogVisible.value) {
 | 
				
			||||||
    if (!state.dialogVisible) {
 | 
					 | 
				
			||||||
        state.form = { ...defaultForm };
 | 
					 | 
				
			||||||
        state.form.authCerts = [];
 | 
					 | 
				
			||||||
        return;
 | 
					        return;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    const machine: any = props.machine;
 | 
					    const machine: any = props.machine;
 | 
				
			||||||
@@ -194,12 +192,16 @@ watchEffect(() => {
 | 
				
			|||||||
        state.form = { ...machine };
 | 
					        state.form = { ...machine };
 | 
				
			||||||
        state.form.tagCodePaths = machine.tags.map((t: any) => t.codePath);
 | 
					        state.form.tagCodePaths = machine.tags.map((t: any) => t.codePath);
 | 
				
			||||||
        state.form.authCerts = machine.authCerts || [];
 | 
					        state.form.authCerts = machine.authCerts || [];
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					        state.form = { ...defaultForm };
 | 
				
			||||||
 | 
					        state.form.authCerts = [];
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const testConn = async (authCert: any) => {
 | 
					const testConn = async (authCert: any) => {
 | 
				
			||||||
    machineForm.value.validate(async (valid: boolean) => {
 | 
					    try {
 | 
				
			||||||
        if (!valid) {
 | 
					        await machineForm.value.validate();
 | 
				
			||||||
 | 
					    } catch (e: any) {
 | 
				
			||||||
        ElMessage.error('请正确填写信息');
 | 
					        ElMessage.error('请正确填写信息');
 | 
				
			||||||
        return false;
 | 
					        return false;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@@ -208,12 +210,12 @@ const testConn = async (authCert: any) => {
 | 
				
			|||||||
    state.submitForm.authCerts = [authCert];
 | 
					    state.submitForm.authCerts = [authCert];
 | 
				
			||||||
    await testConnExec();
 | 
					    await testConnExec();
 | 
				
			||||||
    ElMessage.success('连接成功');
 | 
					    ElMessage.success('连接成功');
 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const btnOk = async () => {
 | 
					const btnOk = async () => {
 | 
				
			||||||
    machineForm.value.validate(async (valid: boolean) => {
 | 
					    try {
 | 
				
			||||||
        if (!valid) {
 | 
					        await machineForm.value.validate();
 | 
				
			||||||
 | 
					    } catch (e: any) {
 | 
				
			||||||
        ElMessage.error('请正确填写信息');
 | 
					        ElMessage.error('请正确填写信息');
 | 
				
			||||||
        return false;
 | 
					        return false;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@@ -228,7 +230,6 @@ const btnOk = async () => {
 | 
				
			|||||||
    ElMessage.success('保存成功');
 | 
					    ElMessage.success('保存成功');
 | 
				
			||||||
    emit('val-change', submitForm);
 | 
					    emit('val-change', submitForm);
 | 
				
			||||||
    cancel();
 | 
					    cancel();
 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const getReqForm = () => {
 | 
					const getReqForm = () => {
 | 
				
			||||||
@@ -250,7 +251,7 @@ const handleChangeProtocol = (val: any) => {
 | 
				
			|||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const cancel = () => {
 | 
					const cancel = () => {
 | 
				
			||||||
    emit('update:visible', false);
 | 
					    dialogVisible.value = false;
 | 
				
			||||||
    emit('cancel');
 | 
					    emit('cancel');
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
</script>
 | 
					</script>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -96,7 +96,7 @@
 | 
				
			|||||||
                    </el-tooltip>
 | 
					                    </el-tooltip>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    <el-button v-if="data.protocol == MachineProtocolEnum.Rdp.value" type="primary" @click="showRDP(data)" link>RDP</el-button>
 | 
					                    <el-button v-if="data.protocol == MachineProtocolEnum.Rdp.value" type="primary" @click="showRDP(data)" link>RDP</el-button>
 | 
				
			||||||
                    <el-button v-if="data.protocol == 3" type="primary" @click="showRDP(data)" link>VNC</el-button>
 | 
					                    <el-button v-if="data.protocol == MachineProtocolEnum.Vnc.value" type="primary" @click="showRDP(data)" link>VNC</el-button>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    <el-divider direction="vertical" border-style="dashed" />
 | 
					                    <el-divider direction="vertical" border-style="dashed" />
 | 
				
			||||||
                </span>
 | 
					                </span>
 | 
				
			||||||
@@ -160,10 +160,10 @@
 | 
				
			|||||||
                <el-descriptions-item :span="1.5" label="SSH隧道">{{ infoDialog.data.sshTunnelMachineId > 0 ? '是' : '否' }} </el-descriptions-item>
 | 
					                <el-descriptions-item :span="1.5" label="SSH隧道">{{ infoDialog.data.sshTunnelMachineId > 0 ? '是' : '否' }} </el-descriptions-item>
 | 
				
			||||||
                <el-descriptions-item :span="1.5" label="终端回放">{{ infoDialog.data.enableRecorder == 1 ? '是' : '否' }} </el-descriptions-item>
 | 
					                <el-descriptions-item :span="1.5" label="终端回放">{{ infoDialog.data.enableRecorder == 1 ? '是' : '否' }} </el-descriptions-item>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                <el-descriptions-item :span="2" label="创建时间">{{ dateFormat(infoDialog.data.createTime) }} </el-descriptions-item>
 | 
					                <el-descriptions-item :span="2" label="创建时间">{{ formatDate(infoDialog.data.createTime) }} </el-descriptions-item>
 | 
				
			||||||
                <el-descriptions-item :span="1" label="创建者">{{ infoDialog.data.creator }}</el-descriptions-item>
 | 
					                <el-descriptions-item :span="1" label="创建者">{{ infoDialog.data.creator }}</el-descriptions-item>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                <el-descriptions-item :span="2" label="更新时间">{{ dateFormat(infoDialog.data.updateTime) }} </el-descriptions-item>
 | 
					                <el-descriptions-item :span="2" label="更新时间">{{ formatDate(infoDialog.data.updateTime) }} </el-descriptions-item>
 | 
				
			||||||
                <el-descriptions-item :span="1" label="修改者">{{ infoDialog.data.modifier }}</el-descriptions-item>
 | 
					                <el-descriptions-item :span="1" label="修改者">{{ infoDialog.data.modifier }}</el-descriptions-item>
 | 
				
			||||||
            </el-descriptions>
 | 
					            </el-descriptions>
 | 
				
			||||||
        </el-dialog>
 | 
					        </el-dialog>
 | 
				
			||||||
@@ -236,12 +236,11 @@ import { defineAsyncComponent, onMounted, reactive, ref, Ref, toRefs } from 'vue
 | 
				
			|||||||
import { useRoute, useRouter } from 'vue-router';
 | 
					import { useRoute, useRouter } from 'vue-router';
 | 
				
			||||||
import { ElMessage, ElMessageBox } from 'element-plus';
 | 
					import { ElMessage, ElMessageBox } from 'element-plus';
 | 
				
			||||||
import { getMachineTerminalSocketUrl, machineApi } from './api';
 | 
					import { getMachineTerminalSocketUrl, machineApi } from './api';
 | 
				
			||||||
import { dateFormat } from '@/common/utils/date';
 | 
					 | 
				
			||||||
import ResourceTags from '../component/ResourceTags.vue';
 | 
					import ResourceTags from '../component/ResourceTags.vue';
 | 
				
			||||||
import PageTable from '@/components/pagetable/PageTable.vue';
 | 
					import PageTable from '@/components/pagetable/PageTable.vue';
 | 
				
			||||||
import { TableColumn } from '@/components/pagetable';
 | 
					import { TableColumn } from '@/components/pagetable';
 | 
				
			||||||
import { hasPerms } from '@/components/auth/auth';
 | 
					import { hasPerms } from '@/components/auth/auth';
 | 
				
			||||||
import { formatByteSize } from '@/common/utils/format';
 | 
					import { formatByteSize, formatDate } from '@/common/utils/format';
 | 
				
			||||||
import { TagResourceTypeEnum } from '@/common/commonEnum';
 | 
					import { TagResourceTypeEnum } from '@/common/commonEnum';
 | 
				
			||||||
import { SearchItem } from '@/components/SearchForm';
 | 
					import { SearchItem } from '@/components/SearchForm';
 | 
				
			||||||
import { getTagPathSearchItem } from '../component/tag';
 | 
					import { getTagPathSearchItem } from '../component/tag';
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -8,6 +8,7 @@
 | 
				
			|||||||
                    ref="tagTreeRef"
 | 
					                    ref="tagTreeRef"
 | 
				
			||||||
                    :resource-type="TagResourceTypeEnum.MachineAuthCert.value"
 | 
					                    :resource-type="TagResourceTypeEnum.MachineAuthCert.value"
 | 
				
			||||||
                    :tag-path-node-type="NodeTypeTagPath"
 | 
					                    :tag-path-node-type="NodeTypeTagPath"
 | 
				
			||||||
 | 
					                    :default-expanded-keys="state.defaultExpendKey"
 | 
				
			||||||
                >
 | 
					                >
 | 
				
			||||||
                    <template #prefix="{ data }">
 | 
					                    <template #prefix="{ data }">
 | 
				
			||||||
                        <SvgIcon
 | 
					                        <SvgIcon
 | 
				
			||||||
@@ -24,7 +25,7 @@
 | 
				
			|||||||
                    </template>
 | 
					                    </template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    <template #suffix="{ data }">
 | 
					                    <template #suffix="{ data }">
 | 
				
			||||||
                        <span style="color: #c4c9c4; font-size: 9px" v-if="data.type.value == MachineNodeType.AuthCert">{{
 | 
					                        <span v-if="data.type.value == MachineNodeType.AuthCert">{{
 | 
				
			||||||
                            ` ${data.params.selectAuthCert.username}@${data.params.ip}:${data.params.port}`
 | 
					                            ` ${data.params.selectAuthCert.username}@${data.params.ip}:${data.params.port}`
 | 
				
			||||||
                        }}</span>
 | 
					                        }}</span>
 | 
				
			||||||
                    </template>
 | 
					                    </template>
 | 
				
			||||||
@@ -33,20 +34,15 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
            <Pane>
 | 
					            <Pane>
 | 
				
			||||||
                <div class="machine-terminal-tabs card pd5">
 | 
					                <div class="machine-terminal-tabs card pd5">
 | 
				
			||||||
                    <el-tabs
 | 
					                    <el-tabs v-if="state.tabs.size > 0" type="card" @tab-remove="onRemoveTab" style="width: 100%" v-model="state.activeTermName" class="h100">
 | 
				
			||||||
                        v-if="state.tabs.size > 0"
 | 
					 | 
				
			||||||
                        type="card"
 | 
					 | 
				
			||||||
                        @tab-remove="onRemoveTab"
 | 
					 | 
				
			||||||
                        @tab-change="onTabChange"
 | 
					 | 
				
			||||||
                        style="width: 100%"
 | 
					 | 
				
			||||||
                        v-model="state.activeTermName"
 | 
					 | 
				
			||||||
                        class="h100"
 | 
					 | 
				
			||||||
                    >
 | 
					 | 
				
			||||||
                        <el-tab-pane class="h100" closable v-for="dt in state.tabs.values()" :label="dt.label" :name="dt.key" :key="dt.key">
 | 
					                        <el-tab-pane class="h100" closable v-for="dt in state.tabs.values()" :label="dt.label" :name="dt.key" :key="dt.key">
 | 
				
			||||||
                            <template #label>
 | 
					                            <template #label>
 | 
				
			||||||
                                <el-popconfirm @confirm="handleReconnect(dt, true)" title="确认重新连接?">
 | 
					                                <el-popconfirm @confirm="handleReconnect(dt, true)" title="确认重新连接?">
 | 
				
			||||||
                                    <template #reference>
 | 
					                                    <template #reference>
 | 
				
			||||||
                                        <el-icon class="mr5" :color="dt.status == 1 ? '#67c23a' : '#f56c6c'" :title="dt.status == 1 ? '' : '点击重连'"
 | 
					                                        <el-icon
 | 
				
			||||||
 | 
					                                            class="mr5"
 | 
				
			||||||
 | 
					                                            :color="EnumValue.getEnumByValue(TerminalStatusEnum, dt.status)?.extra?.iconColor"
 | 
				
			||||||
 | 
					                                            :title="dt.status == TerminalStatusEnum.Connected.value ? '' : '点击重连'"
 | 
				
			||||||
                                            ><Connection />
 | 
					                                            ><Connection />
 | 
				
			||||||
                                        </el-icon>
 | 
					                                        </el-icon>
 | 
				
			||||||
                                    </template>
 | 
					                                    </template>
 | 
				
			||||||
@@ -61,7 +57,7 @@
 | 
				
			|||||||
                                        <el-descriptions :column="1" size="small">
 | 
					                                        <el-descriptions :column="1" size="small">
 | 
				
			||||||
                                            <el-descriptions-item label="机器名"> {{ dt.params?.name }} </el-descriptions-item>
 | 
					                                            <el-descriptions-item label="机器名"> {{ dt.params?.name }} </el-descriptions-item>
 | 
				
			||||||
                                            <el-descriptions-item label="host"> {{ dt.params?.ip }} : {{ dt.params?.port }} </el-descriptions-item>
 | 
					                                            <el-descriptions-item label="host"> {{ dt.params?.ip }} : {{ dt.params?.port }} </el-descriptions-item>
 | 
				
			||||||
                                            <el-descriptions-item label="username"> {{ dt.params?.username }} </el-descriptions-item>
 | 
					                                            <el-descriptions-item label="username"> {{ dt.params?.selectAuthCert.username }} </el-descriptions-item>
 | 
				
			||||||
                                            <el-descriptions-item label="remark"> {{ dt.params?.remark }} </el-descriptions-item>
 | 
					                                            <el-descriptions-item label="remark"> {{ dt.params?.remark }} </el-descriptions-item>
 | 
				
			||||||
                                        </el-descriptions>
 | 
					                                        </el-descriptions>
 | 
				
			||||||
                                    </template>
 | 
					                                    </template>
 | 
				
			||||||
@@ -102,10 +98,10 @@
 | 
				
			|||||||
                            <el-descriptions-item :span="1.5" label="SSH隧道">{{ infoDialog.data.sshTunnelMachineId > 0 ? '是' : '否' }} </el-descriptions-item>
 | 
					                            <el-descriptions-item :span="1.5" label="SSH隧道">{{ infoDialog.data.sshTunnelMachineId > 0 ? '是' : '否' }} </el-descriptions-item>
 | 
				
			||||||
                            <el-descriptions-item :span="1.5" label="终端回放">{{ infoDialog.data.enableRecorder == 1 ? '是' : '否' }} </el-descriptions-item>
 | 
					                            <el-descriptions-item :span="1.5" label="终端回放">{{ infoDialog.data.enableRecorder == 1 ? '是' : '否' }} </el-descriptions-item>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                            <el-descriptions-item :span="2" label="创建时间">{{ dateFormat(infoDialog.data.createTime) }} </el-descriptions-item>
 | 
					                            <el-descriptions-item :span="2" label="创建时间">{{ formatDate(infoDialog.data.createTime) }} </el-descriptions-item>
 | 
				
			||||||
                            <el-descriptions-item :span="1" label="创建者">{{ infoDialog.data.creator }}</el-descriptions-item>
 | 
					                            <el-descriptions-item :span="1" label="创建者">{{ infoDialog.data.creator }}</el-descriptions-item>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                            <el-descriptions-item :span="2" label="更新时间">{{ dateFormat(infoDialog.data.updateTime) }} </el-descriptions-item>
 | 
					                            <el-descriptions-item :span="2" label="更新时间">{{ formatDate(infoDialog.data.updateTime) }} </el-descriptions-item>
 | 
				
			||||||
                            <el-descriptions-item :span="1" label="修改者">{{ infoDialog.data.modifier }}</el-descriptions-item>
 | 
					                            <el-descriptions-item :span="1" label="修改者">{{ infoDialog.data.modifier }}</el-descriptions-item>
 | 
				
			||||||
                        </el-descriptions>
 | 
					                        </el-descriptions>
 | 
				
			||||||
                    </el-dialog>
 | 
					                    </el-dialog>
 | 
				
			||||||
@@ -153,22 +149,25 @@
 | 
				
			|||||||
</template>
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<script lang="ts" setup>
 | 
					<script lang="ts" setup>
 | 
				
			||||||
import { defineAsyncComponent, nextTick, reactive, ref, toRefs, watch } from 'vue';
 | 
					import { defineAsyncComponent, nextTick, onMounted, reactive, ref, toRefs, watch } from 'vue';
 | 
				
			||||||
import { useRouter } from 'vue-router';
 | 
					import { useRouter } from 'vue-router';
 | 
				
			||||||
import { getMachineTerminalSocketUrl, machineApi } from './api';
 | 
					import { getMachineTerminalSocketUrl, machineApi } from './api';
 | 
				
			||||||
import { dateFormat } from '@/common/utils/date';
 | 
					import { formatDate } from '@/common/utils/format';
 | 
				
			||||||
import { hasPerms } from '@/components/auth/auth';
 | 
					import { hasPerms } from '@/components/auth/auth';
 | 
				
			||||||
import { TagResourceTypeEnum } from '@/common/commonEnum';
 | 
					import { TagResourceTypeEnum } from '@/common/commonEnum';
 | 
				
			||||||
import { NodeType, TagTreeNode } from '../component/tag';
 | 
					import { NodeType, TagTreeNode, getTagTypeCodeByPath } from '../component/tag';
 | 
				
			||||||
import TagTree from '../component/TagTree.vue';
 | 
					import TagTree from '../component/TagTree.vue';
 | 
				
			||||||
import { Pane, Splitpanes } from 'splitpanes';
 | 
					import { Pane, Splitpanes } from 'splitpanes';
 | 
				
			||||||
import { ContextmenuItem } from '@/components/contextmenu/index';
 | 
					import { ContextmenuItem } from '@/components/contextmenu/index';
 | 
				
			||||||
import TerminalBody from '@/components/terminal/TerminalBody.vue';
 | 
					import TerminalBody from '@/components/terminal/TerminalBody.vue';
 | 
				
			||||||
import { TerminalStatus } from '@/components/terminal/common';
 | 
					import { TerminalStatus, TerminalStatusEnum } from '@/components/terminal/common';
 | 
				
			||||||
import MachineRdp from '@/components/terminal-rdp/MachineRdp.vue';
 | 
					import MachineRdp from '@/components/terminal-rdp/MachineRdp.vue';
 | 
				
			||||||
import MachineFile from '@/views/ops/machine/file/MachineFile.vue';
 | 
					import MachineFile from '@/views/ops/machine/file/MachineFile.vue';
 | 
				
			||||||
import ResourceTags from '../component/ResourceTags.vue';
 | 
					import ResourceTags from '../component/ResourceTags.vue';
 | 
				
			||||||
import { MachineProtocolEnum } from './enums';
 | 
					import { MachineProtocolEnum } from './enums';
 | 
				
			||||||
 | 
					import { useAutoOpenResource } from '@/store/autoOpenResource';
 | 
				
			||||||
 | 
					import { storeToRefs } from 'pinia';
 | 
				
			||||||
 | 
					import EnumValue from '@/common/Enum';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// 组件
 | 
					// 组件
 | 
				
			||||||
const ScriptManage = defineAsyncComponent(() => import('./ScriptManage.vue'));
 | 
					const ScriptManage = defineAsyncComponent(() => import('./ScriptManage.vue'));
 | 
				
			||||||
@@ -196,6 +195,7 @@ class MachineNodeType {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const state = reactive({
 | 
					const state = reactive({
 | 
				
			||||||
 | 
					    defaultExpendKey: [] as any,
 | 
				
			||||||
    params: {
 | 
					    params: {
 | 
				
			||||||
        pageNum: 1,
 | 
					        pageNum: 1,
 | 
				
			||||||
        pageSize: 0,
 | 
					        pageSize: 0,
 | 
				
			||||||
@@ -252,6 +252,9 @@ const { infoDialog, serviceDialog, processDialog, fileDialog, machineStatsDialog
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
const tagTreeRef: any = ref(null);
 | 
					const tagTreeRef: any = ref(null);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const autoOpenResourceStore = useAutoOpenResource();
 | 
				
			||||||
 | 
					const { autoOpenResource } = storeToRefs(autoOpenResourceStore);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
let openIds = {};
 | 
					let openIds = {};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const NodeTypeTagPath = new NodeType(TagTreeNode.TagPath).withLoadNodesFunc(async (node: TagTreeNode) => {
 | 
					const NodeTypeTagPath = new NodeType(TagTreeNode.TagPath).withLoadNodesFunc(async (node: TagTreeNode) => {
 | 
				
			||||||
@@ -263,7 +266,7 @@ const NodeTypeTagPath = new NodeType(TagTreeNode.TagPath).withLoadNodesFunc(asyn
 | 
				
			|||||||
    // 把list 根据name字段排序
 | 
					    // 把list 根据name字段排序
 | 
				
			||||||
    res.list = res.list.sort((a: any, b: any) => a.name.localeCompare(b.name));
 | 
					    res.list = res.list.sort((a: any, b: any) => a.name.localeCompare(b.name));
 | 
				
			||||||
    return res.list.map((x: any) =>
 | 
					    return res.list.map((x: any) =>
 | 
				
			||||||
        new TagTreeNode(x.id, x.name, NodeTypeMachine)
 | 
					        new TagTreeNode(x.code, x.name, NodeTypeMachine)
 | 
				
			||||||
            .withParams(x)
 | 
					            .withParams(x)
 | 
				
			||||||
            .withDisabled(x.status == -1 && x.protocol == MachineProtocolEnum.Ssh.value)
 | 
					            .withDisabled(x.status == -1 && x.protocol == MachineProtocolEnum.Ssh.value)
 | 
				
			||||||
            .withIcon({
 | 
					            .withIcon({
 | 
				
			||||||
@@ -279,7 +282,7 @@ const NodeTypeMachine = new NodeType(MachineNodeType.Machine)
 | 
				
			|||||||
        // 获取授权凭证列表
 | 
					        // 获取授权凭证列表
 | 
				
			||||||
        const authCerts = machine.authCerts;
 | 
					        const authCerts = machine.authCerts;
 | 
				
			||||||
        return authCerts.map((x: any) =>
 | 
					        return authCerts.map((x: any) =>
 | 
				
			||||||
            new TagTreeNode(x.id, x.username, NodeTypeAuthCert)
 | 
					            new TagTreeNode(x.name, x.username, NodeTypeAuthCert)
 | 
				
			||||||
                .withParams({ ...machine, selectAuthCert: x })
 | 
					                .withParams({ ...machine, selectAuthCert: x })
 | 
				
			||||||
                .withDisabled(machine.status == -1 && machine.protocol == MachineProtocolEnum.Ssh.value)
 | 
					                .withDisabled(machine.status == -1 && machine.protocol == MachineProtocolEnum.Ssh.value)
 | 
				
			||||||
                .withIcon({
 | 
					                .withIcon({
 | 
				
			||||||
@@ -323,6 +326,52 @@ const NodeTypeAuthCert = new NodeType(MachineNodeType.AuthCert)
 | 
				
			|||||||
            .withOnClick((node: any) => serviceManager(node.params)),
 | 
					            .withOnClick((node: any) => serviceManager(node.params)),
 | 
				
			||||||
    ]);
 | 
					    ]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					watch(
 | 
				
			||||||
 | 
					    () => autoOpenResource.value.machineCodePath,
 | 
				
			||||||
 | 
					    (codePath: any) => {
 | 
				
			||||||
 | 
					        autoOpenTerminal(codePath);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					watch(
 | 
				
			||||||
 | 
					    () => state.activeTermName,
 | 
				
			||||||
 | 
					    (newValue, oldValue) => {
 | 
				
			||||||
 | 
					        fitTerminal();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        oldValue && terminalRefs[oldValue]?.blur && terminalRefs[oldValue]?.blur();
 | 
				
			||||||
 | 
					        terminalRefs[newValue]?.focus && terminalRefs[newValue]?.focus();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const nowTab = state.tabs.get(state.activeTermName);
 | 
				
			||||||
 | 
					        tagTreeRef.value.setCurrentKey(nowTab?.authCert);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					onMounted(() => {
 | 
				
			||||||
 | 
					    autoOpenTerminal(autoOpenResource.value.machineCodePath);
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const autoOpenTerminal = (codePath: string) => {
 | 
				
			||||||
 | 
					    if (!codePath) {
 | 
				
			||||||
 | 
					        return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const typeAndCodes = getTagTypeCodeByPath(codePath);
 | 
				
			||||||
 | 
					    const tagPath = typeAndCodes[TagResourceTypeEnum.Tag.value].join('/') + '/';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const machineCode = typeAndCodes[TagResourceTypeEnum.Machine.value][0];
 | 
				
			||||||
 | 
					    state.defaultExpendKey = [tagPath, machineCode];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const authCertName = typeAndCodes[TagResourceTypeEnum.MachineAuthCert.value][0];
 | 
				
			||||||
 | 
					    setTimeout(() => {
 | 
				
			||||||
 | 
					        // 置空
 | 
				
			||||||
 | 
					        autoOpenResourceStore.setMachineCodePath('');
 | 
				
			||||||
 | 
					        tagTreeRef.value.setCurrentKey(authCertName);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const acNode = tagTreeRef.value.getNode(authCertName);
 | 
				
			||||||
 | 
					        openTerminal(acNode.data.params);
 | 
				
			||||||
 | 
					    }, 1000);
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const openTerminal = (machine: any, ex?: boolean) => {
 | 
					const openTerminal = (machine: any, ex?: boolean) => {
 | 
				
			||||||
    // 授权凭证名
 | 
					    // 授权凭证名
 | 
				
			||||||
    const ac = machine.selectAuthCert.name;
 | 
					    const ac = machine.selectAuthCert.name;
 | 
				
			||||||
@@ -354,14 +403,14 @@ const openTerminal = (machine: any, ex?: boolean) => {
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    let { name, username } = machine;
 | 
					    let { name } = machine;
 | 
				
			||||||
    const labelName = `${machine.selectAuthCert.username}@${name}`;
 | 
					    const labelName = `${machine.selectAuthCert.username}@${name}`;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // 同一个机器的终端打开多次,key后添加下划线和数字区分
 | 
					    // 同一个机器的终端打开多次,key后添加下划线和数字区分
 | 
				
			||||||
    openIds[ac] = openIds[ac] ? ++openIds[ac] : 1;
 | 
					    openIds[ac] = openIds[ac] ? ++openIds[ac] : 1;
 | 
				
			||||||
    let sameIndex = openIds[ac];
 | 
					    let sameIndex = openIds[ac];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    let key = `${ac}_${username}_${sameIndex}`;
 | 
					    let key = `${ac}_${sameIndex}`;
 | 
				
			||||||
    // 只保留name的15个字,超出部分只保留前后10个字符,中间用省略号代替
 | 
					    // 只保留name的15个字,超出部分只保留前后10个字符,中间用省略号代替
 | 
				
			||||||
    const label = labelName.length > 15 ? labelName.slice(0, 10) + '...' + labelName.slice(-10) : labelName;
 | 
					    const label = labelName.length > 15 ? labelName.slice(0, 10) + '...' + labelName.slice(-10) : labelName;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -448,32 +497,30 @@ const onRemoveTab = (targetName: string) => {
 | 
				
			|||||||
        if (tabName !== targetName) {
 | 
					        if (tabName !== targetName) {
 | 
				
			||||||
            continue;
 | 
					            continue;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        state.tabs.delete(targetName);
 | 
				
			||||||
 | 
					        let info = state.tabs.get(targetName);
 | 
				
			||||||
 | 
					        if (info) {
 | 
				
			||||||
 | 
					            terminalRefs[info.key]?.close();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (activeTermName != targetName) {
 | 
				
			||||||
 | 
					            break;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // 如果删除的tab是当前激活的tab,则切换到前一个或后一个tab
 | 
				
			||||||
        const nextTab = tabNames[i + 1] || tabNames[i - 1];
 | 
					        const nextTab = tabNames[i + 1] || tabNames[i - 1];
 | 
				
			||||||
        if (nextTab) {
 | 
					        if (nextTab) {
 | 
				
			||||||
            activeTermName = nextTab;
 | 
					            activeTermName = nextTab;
 | 
				
			||||||
        } else {
 | 
					        } else {
 | 
				
			||||||
            activeTermName = '';
 | 
					            activeTermName = '';
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        let info = state.tabs.get(targetName);
 | 
					 | 
				
			||||||
        if (info) {
 | 
					 | 
				
			||||||
            terminalRefs[info.key]?.close();
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        state.tabs.delete(targetName);
 | 
					 | 
				
			||||||
        state.activeTermName = activeTermName;
 | 
					        state.activeTermName = activeTermName;
 | 
				
			||||||
        onTabChange();
 | 
					        break;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
watch(
 | 
					 | 
				
			||||||
    () => state.activeTermName,
 | 
					 | 
				
			||||||
    (newValue, oldValue) => {
 | 
					 | 
				
			||||||
        console.log('oldValue', oldValue);
 | 
					 | 
				
			||||||
        oldValue && terminalRefs[oldValue]?.blur && terminalRefs[oldValue]?.blur();
 | 
					 | 
				
			||||||
        terminalRefs[newValue]?.focus && terminalRefs[newValue]?.focus();
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const terminalStatusChange = (key: string, status: TerminalStatus) => {
 | 
					const terminalStatusChange = (key: string, status: TerminalStatus) => {
 | 
				
			||||||
    state.tabs.get(key).status = status;
 | 
					    state.tabs.get(key).status = status;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
@@ -496,18 +543,13 @@ const onResizeTagTree = () => {
 | 
				
			|||||||
    fitTerminal();
 | 
					    fitTerminal();
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const onTabChange = () => {
 | 
					 | 
				
			||||||
    fitTerminal();
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const fitTerminal = () => {
 | 
					const fitTerminal = () => {
 | 
				
			||||||
    setTimeout(() => {
 | 
					    setTimeout(() => {
 | 
				
			||||||
        let info = state.tabs.get(state.activeTermName);
 | 
					        let info = state.tabs.get(state.activeTermName);
 | 
				
			||||||
        if (info) {
 | 
					        if (info) {
 | 
				
			||||||
            terminalRefs[info.key]?.fitTerminal && terminalRefs[info.key]?.fitTerminal();
 | 
					            terminalRefs[info.key]?.fitTerminal && terminalRefs[info.key]?.fitTerminal();
 | 
				
			||||||
            terminalRefs[info.key]?.focus && terminalRefs[info.key]?.focus();
 | 
					 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }, 100);
 | 
					    });
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const handleReconnect = (tab: any, force = false) => {
 | 
					const handleReconnect = (tab: any, force = false) => {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -28,12 +28,12 @@
 | 
				
			|||||||
            <div ref="playerRef" id="rc-player"></div>
 | 
					            <div ref="playerRef" id="rc-player"></div>
 | 
				
			||||||
        </el-dialog>
 | 
					        </el-dialog>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        <el-dialog :title="title" v-model="execCmdsDialogVisible" :close-on-click-modal="false" :destroy-on-close="true" width="500">
 | 
					        <el-dialog title="执行命令记录" v-model="execCmdsDialogVisible" :destroy-on-close="true" width="500">
 | 
				
			||||||
            <el-table :data="state.execCmds" max-height="480" stripe size="small">
 | 
					            <el-table :data="state.execCmds" max-height="480" stripe size="small">
 | 
				
			||||||
                <el-table-column prop="cmd" label="命令" show-overflow-tooltip min-width="150px"> </el-table-column>
 | 
					                <el-table-column prop="cmd" label="命令" show-overflow-tooltip min-width="150px"> </el-table-column>
 | 
				
			||||||
                <el-table-column prop="time" label="执行时间" min-width="80" show-overflow-tooltip>
 | 
					                <el-table-column prop="time" label="执行时间" min-width="80" show-overflow-tooltip>
 | 
				
			||||||
                    <template #default="scope">
 | 
					                    <template #default="scope">
 | 
				
			||||||
                        {{ dateFormat(new Date(scope.row.time * 1000).toString()) }}
 | 
					                        {{ formatDate(new Date(scope.row.time * 1000).toString()) }}
 | 
				
			||||||
                    </template>
 | 
					                    </template>
 | 
				
			||||||
                </el-table-column>
 | 
					                </el-table-column>
 | 
				
			||||||
            </el-table>
 | 
					            </el-table>
 | 
				
			||||||
@@ -48,7 +48,7 @@ import * as AsciinemaPlayer from 'asciinema-player';
 | 
				
			|||||||
import 'asciinema-player/dist/bundle/asciinema-player.css';
 | 
					import 'asciinema-player/dist/bundle/asciinema-player.css';
 | 
				
			||||||
import PageTable from '@/components/pagetable/PageTable.vue';
 | 
					import PageTable from '@/components/pagetable/PageTable.vue';
 | 
				
			||||||
import { TableColumn } from '@/components/pagetable';
 | 
					import { TableColumn } from '@/components/pagetable';
 | 
				
			||||||
import { dateFormat } from '@/common/utils/date';
 | 
					import { formatDate } from '@/common/utils/format';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const props = defineProps({
 | 
					const props = defineProps({
 | 
				
			||||||
    visible: { type: Boolean },
 | 
					    visible: { type: Boolean },
 | 
				
			||||||
@@ -122,8 +122,8 @@ const playRec = async (rec: any) => {
 | 
				
			|||||||
                idleTimeLimit: 2,
 | 
					                idleTimeLimit: 2,
 | 
				
			||||||
                // fit: false,
 | 
					                // fit: false,
 | 
				
			||||||
                // terminalFontSize: 'small',
 | 
					                // terminalFontSize: 'small',
 | 
				
			||||||
                cols: 144,
 | 
					                // cols: 144,
 | 
				
			||||||
                rows: 32,
 | 
					                // rows: 32,
 | 
				
			||||||
            });
 | 
					            });
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
    } finally {
 | 
					    } finally {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -12,6 +12,8 @@ export const machineApi = {
 | 
				
			|||||||
    process: Api.newGet('/machines/{id}/process'),
 | 
					    process: Api.newGet('/machines/{id}/process'),
 | 
				
			||||||
    // 终止进程
 | 
					    // 终止进程
 | 
				
			||||||
    killProcess: Api.newDelete('/machines/{id}/process'),
 | 
					    killProcess: Api.newDelete('/machines/{id}/process'),
 | 
				
			||||||
 | 
					    users: Api.newGet('/machines/{id}/users'),
 | 
				
			||||||
 | 
					    groups: Api.newGet('/machines/{id}/groups'),
 | 
				
			||||||
    testConn: Api.newPost('/machines/test-conn'),
 | 
					    testConn: Api.newPost('/machines/test-conn'),
 | 
				
			||||||
    // 保存按钮
 | 
					    // 保存按钮
 | 
				
			||||||
    saveMachine: Api.newPost('/machines'),
 | 
					    saveMachine: Api.newPost('/machines'),
 | 
				
			||||||
@@ -58,6 +60,12 @@ export const cronJobApi = {
 | 
				
			|||||||
    execList: Api.newGet('/machine-cronjobs/execs'),
 | 
					    execList: Api.newGet('/machine-cronjobs/execs'),
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const cmdConfApi = {
 | 
				
			||||||
 | 
					    list: Api.newGet('/machine/security/cmd-confs'),
 | 
				
			||||||
 | 
					    save: Api.newPost('/machine/security/cmd-confs'),
 | 
				
			||||||
 | 
					    delete: Api.newDelete('/machine/security/cmd-confs/{id}'),
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function getMachineTerminalSocketUrl(authCertName: any) {
 | 
					export function getMachineTerminalSocketUrl(authCertName: any) {
 | 
				
			||||||
    return `${config.baseWsUrl}/machines/terminal/${authCertName}?${joinClientParams()}`;
 | 
					    return `${config.baseWsUrl}/machines/terminal/${authCertName}?${joinClientParams()}`;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -0,0 +1,69 @@
 | 
				
			|||||||
 | 
					<template>
 | 
				
			||||||
 | 
					    <div>
 | 
				
			||||||
 | 
					        <el-popover placement="right" width="auto" title="机器详情" trigger="click">
 | 
				
			||||||
 | 
					            <template #reference>
 | 
				
			||||||
 | 
					                <el-link @click="getMachineDetail" type="primary">{{ props.code }}</el-link>
 | 
				
			||||||
 | 
					            </template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            <el-descriptions v-loading="state.loading" :column="3" border>
 | 
				
			||||||
 | 
					                <el-descriptions-item :span="1" label="机器id">{{ state.machineDetail.id }}</el-descriptions-item>
 | 
				
			||||||
 | 
					                <el-descriptions-item :span="1" label="编号">{{ state.machineDetail.code }}</el-descriptions-item>
 | 
				
			||||||
 | 
					                <el-descriptions-item :span="1" label="名称">{{ state.machineDetail.name }}</el-descriptions-item>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                <el-descriptions-item :span="3" label="关联标签"><ResourceTags :tags="state.machineDetail.tags" /></el-descriptions-item>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                <el-descriptions-item :span="2" label="IP">{{ state.machineDetail.ip }}</el-descriptions-item>
 | 
				
			||||||
 | 
					                <el-descriptions-item :span="1" label="端口">{{ state.machineDetail.port }}</el-descriptions-item>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                <el-descriptions-item :span="3" label="备注">{{ state.machineDetail.remark }}</el-descriptions-item>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                <el-descriptions-item :span="1.5" label="SSH隧道">{{ state.machineDetail.sshTunnelMachineId > 0 ? '是' : '否' }} </el-descriptions-item>
 | 
				
			||||||
 | 
					                <el-descriptions-item :span="1.5" label="终端回放">{{ state.machineDetail.enableRecorder == 1 ? '是' : '否' }} </el-descriptions-item>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                <el-descriptions-item :span="2" label="创建时间">{{ formatDate(state.machineDetail.createTime) }} </el-descriptions-item>
 | 
				
			||||||
 | 
					                <el-descriptions-item :span="1" label="创建者">{{ state.machineDetail.creator }}</el-descriptions-item>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                <el-descriptions-item :span="2" label="更新时间">{{ formatDate(state.machineDetail.updateTime) }} </el-descriptions-item>
 | 
				
			||||||
 | 
					                <el-descriptions-item :span="1" label="修改者">{{ state.machineDetail.modifier }}</el-descriptions-item>
 | 
				
			||||||
 | 
					            </el-descriptions>
 | 
				
			||||||
 | 
					        </el-popover>
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<script lang="ts" setup>
 | 
				
			||||||
 | 
					import { reactive } from 'vue';
 | 
				
			||||||
 | 
					import { machineApi } from '../api';
 | 
				
			||||||
 | 
					import { formatDate } from '@/common/utils/format';
 | 
				
			||||||
 | 
					import ResourceTags from '../../component/ResourceTags.vue';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const props = defineProps({
 | 
				
			||||||
 | 
					    code: {
 | 
				
			||||||
 | 
					        type: [String],
 | 
				
			||||||
 | 
					        requierd: true,
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const state = reactive({
 | 
				
			||||||
 | 
					    loading: false,
 | 
				
			||||||
 | 
					    machineDetail: {} as any,
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const getMachineDetail = async () => {
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					        state.machineDetail = {};
 | 
				
			||||||
 | 
					        state.loading = true;
 | 
				
			||||||
 | 
					        const res = await machineApi.list.request({
 | 
				
			||||||
 | 
					            code: props.code,
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					        if (res.total == 0) {
 | 
				
			||||||
 | 
					            return;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        state.machineDetail = res.list?.[0];
 | 
				
			||||||
 | 
					    } finally {
 | 
				
			||||||
 | 
					        state.loading = false;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<style></style>
 | 
				
			||||||
@@ -1,14 +1,18 @@
 | 
				
			|||||||
<template>
 | 
					<template>
 | 
				
			||||||
    <div class="mock-data-dialog">
 | 
					    <div class="mock-data-dialog">
 | 
				
			||||||
        <el-dialog
 | 
					        <el-drawer
 | 
				
			||||||
            :title="title"
 | 
					            :title="title"
 | 
				
			||||||
            v-model="dialogVisible"
 | 
					            v-model="dialogVisible"
 | 
				
			||||||
            :close-on-click-modal="false"
 | 
					            :close-on-click-modal="false"
 | 
				
			||||||
            :before-close="cancel"
 | 
					            :before-close="cancel"
 | 
				
			||||||
            :show-close="true"
 | 
					            :show-close="true"
 | 
				
			||||||
            :destroy-on-close="true"
 | 
					            :destroy-on-close="true"
 | 
				
			||||||
            width="900px"
 | 
					            size="40%"
 | 
				
			||||||
        >
 | 
					        >
 | 
				
			||||||
 | 
					            <template #header>
 | 
				
			||||||
 | 
					                <DrawerHeader :header="title" :back="cancel" />
 | 
				
			||||||
 | 
					            </template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            <el-form :model="form" ref="formRef" :rules="rules" label-width="auto">
 | 
					            <el-form :model="form" ref="formRef" :rules="rules" label-width="auto">
 | 
				
			||||||
                <el-form-item prop="name" label="名称">
 | 
					                <el-form-item prop="name" label="名称">
 | 
				
			||||||
                    <el-input v-model="form.name" placeholder="请输入名称"></el-input>
 | 
					                    <el-input v-model="form.name" placeholder="请输入名称"></el-input>
 | 
				
			||||||
@@ -34,19 +38,13 @@
 | 
				
			|||||||
                    <el-input v-model="form.remark" placeholder="请输入备注"></el-input>
 | 
					                    <el-input v-model="form.remark" placeholder="请输入备注"></el-input>
 | 
				
			||||||
                </el-form-item>
 | 
					                </el-form-item>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                <el-form-item prop="machineIds" label="关联机器">
 | 
					 | 
				
			||||||
                    <el-select multiple v-model="form.machineIds" filterable placeholder="请选关联机器" style="width: 100%">
 | 
					 | 
				
			||||||
                        <el-option v-for="ac in state.machines" :key="ac.id" :value="ac.id" :label="ac.ip">
 | 
					 | 
				
			||||||
                            {{ ac.ip }}
 | 
					 | 
				
			||||||
                            <el-divider direction="vertical" border-style="dashed" />
 | 
					 | 
				
			||||||
                            {{ ac.tagPath }}{{ ac.name }}
 | 
					 | 
				
			||||||
                        </el-option>
 | 
					 | 
				
			||||||
                    </el-select>
 | 
					 | 
				
			||||||
                </el-form-item>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                <el-form-item prop="script" label="执行脚本" required>
 | 
					                <el-form-item prop="script" label="执行脚本" required>
 | 
				
			||||||
                    <monaco-editor style="width: 100%" v-model="form.script" language="shell" height="300px"
 | 
					                    <monaco-editor style="width: 100%" v-model="form.script" language="shell" height="200px"
 | 
				
			||||||
                /></el-form-item>
 | 
					                /></el-form-item>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                <el-form-item ref="tagSelectRef" prop="codePaths" label="关联机器">
 | 
				
			||||||
 | 
					                    <tag-tree-check height="200px" :tag-type="TagResourceTypeEnum.Machine.value" v-model="form.codePaths" />
 | 
				
			||||||
 | 
					                </el-form-item>
 | 
				
			||||||
            </el-form>
 | 
					            </el-form>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            <template #footer>
 | 
					            <template #footer>
 | 
				
			||||||
@@ -55,7 +53,7 @@
 | 
				
			|||||||
                    <el-button v-auth="'machine:script:save'" type="primary" :loading="btnLoading" @click="btnOk" :disabled="submitDisabled">保 存</el-button>
 | 
					                    <el-button v-auth="'machine:script:save'" type="primary" :loading="btnLoading" @click="btnOk" :disabled="submitDisabled">保 存</el-button>
 | 
				
			||||||
                </div>
 | 
					                </div>
 | 
				
			||||||
            </template>
 | 
					            </template>
 | 
				
			||||||
        </el-dialog>
 | 
					        </el-drawer>
 | 
				
			||||||
    </div>
 | 
					    </div>
 | 
				
			||||||
</template>
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -67,6 +65,9 @@ import { CronJobStatusEnum, CronJobSaveExecResTypeEnum } from '../enums';
 | 
				
			|||||||
import { notEmpty } from '@/common/assert';
 | 
					import { notEmpty } from '@/common/assert';
 | 
				
			||||||
import MonacoEditor from '@/components/monaco/MonacoEditor.vue';
 | 
					import MonacoEditor from '@/components/monaco/MonacoEditor.vue';
 | 
				
			||||||
import CrontabInput from '@/components/crontab/CrontabInput.vue';
 | 
					import CrontabInput from '@/components/crontab/CrontabInput.vue';
 | 
				
			||||||
 | 
					import DrawerHeader from '@/components/drawer-header/DrawerHeader.vue';
 | 
				
			||||||
 | 
					import TagTreeCheck from '../../component/TagTreeCheck.vue';
 | 
				
			||||||
 | 
					import { TagResourceTypeEnum } from '@/common/commonEnum';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const props = defineProps({
 | 
					const props = defineProps({
 | 
				
			||||||
    visible: {
 | 
					    visible: {
 | 
				
			||||||
@@ -130,11 +131,11 @@ const state = reactive({
 | 
				
			|||||||
        id: null,
 | 
					        id: null,
 | 
				
			||||||
        name: '',
 | 
					        name: '',
 | 
				
			||||||
        cron: '',
 | 
					        cron: '',
 | 
				
			||||||
        machineIds: [],
 | 
					 | 
				
			||||||
        remark: '',
 | 
					        remark: '',
 | 
				
			||||||
        script: '',
 | 
					        script: '',
 | 
				
			||||||
        status: 1,
 | 
					        status: 1,
 | 
				
			||||||
        saveExecResType: -1,
 | 
					        saveExecResType: -1,
 | 
				
			||||||
 | 
					        codePaths: [],
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    machines: [] as any,
 | 
					    machines: [] as any,
 | 
				
			||||||
    btnLoading: false,
 | 
					    btnLoading: false,
 | 
				
			||||||
@@ -154,7 +155,7 @@ watch(props, async (newValue: any) => {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
    if (newValue.data) {
 | 
					    if (newValue.data) {
 | 
				
			||||||
        state.form = { ...newValue.data };
 | 
					        state.form = { ...newValue.data };
 | 
				
			||||||
        state.form.machineIds = await cronJobApi.relateMachineIds.request({ cronJobId: state.form.id });
 | 
					        state.form.codePaths = newValue.data.tags?.map((tag: any) => tag.codePath);
 | 
				
			||||||
    } else {
 | 
					    } else {
 | 
				
			||||||
        state.form = { script: '', status: 1 } as any;
 | 
					        state.form = { script: '', status: 1 } as any;
 | 
				
			||||||
        state.chooseMachines = [];
 | 
					        state.chooseMachines = [];
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -3,6 +3,7 @@
 | 
				
			|||||||
        <el-dialog
 | 
					        <el-dialog
 | 
				
			||||||
            :title="title"
 | 
					            :title="title"
 | 
				
			||||||
            v-model="dialogVisible"
 | 
					            v-model="dialogVisible"
 | 
				
			||||||
 | 
					            @open="search()"
 | 
				
			||||||
            :close-on-click-modal="false"
 | 
					            :close-on-click-modal="false"
 | 
				
			||||||
            :before-close="cancel"
 | 
					            :before-close="cancel"
 | 
				
			||||||
            :show-close="true"
 | 
					            :show-close="true"
 | 
				
			||||||
@@ -13,20 +14,13 @@
 | 
				
			|||||||
                ref="pageTableRef"
 | 
					                ref="pageTableRef"
 | 
				
			||||||
                :page-api="cronJobApi.execList"
 | 
					                :page-api="cronJobApi.execList"
 | 
				
			||||||
                :lazy="true"
 | 
					                :lazy="true"
 | 
				
			||||||
                :data-handler-fn="parseData"
 | 
					 | 
				
			||||||
                :search-items="searchItems"
 | 
					                :search-items="searchItems"
 | 
				
			||||||
                v-model:query-form="params"
 | 
					                v-model:query-form="params"
 | 
				
			||||||
                :data="state.data.list"
 | 
					                :data="state.data.list"
 | 
				
			||||||
                :columns="columns"
 | 
					                :columns="columns"
 | 
				
			||||||
            >
 | 
					            >
 | 
				
			||||||
                <template #machineSelect>
 | 
					                <template #machineCode="{ data }">
 | 
				
			||||||
                    <el-select v-model="params.machineId" filterable placeholder="选择机器查询" clearable>
 | 
					                    <MachineDetail :code="data.machineCode" />
 | 
				
			||||||
                        <el-option v-for="ac in machineMap.values()" :key="ac.id" :value="ac.id" :label="ac.ip">
 | 
					 | 
				
			||||||
                            {{ ac.ip }}
 | 
					 | 
				
			||||||
                            <el-divider direction="vertical" border-style="dashed" />
 | 
					 | 
				
			||||||
                            {{ ac.tagPath }}{{ ac.name }}
 | 
					 | 
				
			||||||
                        </el-option>
 | 
					 | 
				
			||||||
                    </el-select>
 | 
					 | 
				
			||||||
                </template>
 | 
					                </template>
 | 
				
			||||||
            </page-table>
 | 
					            </page-table>
 | 
				
			||||||
        </el-dialog>
 | 
					        </el-dialog>
 | 
				
			||||||
@@ -34,12 +28,13 @@
 | 
				
			|||||||
</template>
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<script lang="ts" setup>
 | 
					<script lang="ts" setup>
 | 
				
			||||||
import { watch, ref, toRefs, reactive, Ref } from 'vue';
 | 
					import { ref, toRefs, reactive, Ref } from 'vue';
 | 
				
			||||||
import { cronJobApi, machineApi } from '../api';
 | 
					import { cronJobApi } from '../api';
 | 
				
			||||||
import PageTable from '@/components/pagetable/PageTable.vue';
 | 
					import PageTable from '@/components/pagetable/PageTable.vue';
 | 
				
			||||||
import { TableColumn } from '@/components/pagetable';
 | 
					import { TableColumn } from '@/components/pagetable';
 | 
				
			||||||
import { CronJobExecStatusEnum } from '../enums';
 | 
					import { CronJobExecStatusEnum } from '../enums';
 | 
				
			||||||
import { SearchItem } from '@/components/SearchForm';
 | 
					import { SearchItem } from '@/components/SearchForm';
 | 
				
			||||||
 | 
					import MachineDetail from '../component/MachineDetail.vue';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const props = defineProps({
 | 
					const props = defineProps({
 | 
				
			||||||
    visible: {
 | 
					    visible: {
 | 
				
			||||||
@@ -53,13 +48,10 @@ const props = defineProps({
 | 
				
			|||||||
    },
 | 
					    },
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const emit = defineEmits(['update:visible', 'update:data', 'cancel']);
 | 
					const searchItems = [SearchItem.input('machineCode', '机器编号'), SearchItem.select('status', '状态').withEnum(CronJobExecStatusEnum)];
 | 
				
			||||||
 | 
					 | 
				
			||||||
const searchItems = [SearchItem.slot('machineId', '机器', 'machineSelect'), SearchItem.select('status', '状态').withEnum(CronJobExecStatusEnum)];
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
const columns = ref([
 | 
					const columns = ref([
 | 
				
			||||||
    TableColumn.new('machineIp', '机器IP').setMinWidth(120),
 | 
					    TableColumn.new('machineCode', '机器编号').isSlot(),
 | 
				
			||||||
    TableColumn.new('machineName', '机器名称').setMinWidth(100),
 | 
					 | 
				
			||||||
    TableColumn.new('status', '状态').typeTag(CronJobExecStatusEnum).setMinWidth(70),
 | 
					    TableColumn.new('status', '状态').typeTag(CronJobExecStatusEnum).setMinWidth(70),
 | 
				
			||||||
    TableColumn.new('res', '执行结果').setMinWidth(250).canBeautify(),
 | 
					    TableColumn.new('res', '执行结果').setMinWidth(250).canBeautify(),
 | 
				
			||||||
    TableColumn.new('execTime', '执行时间').isTime().setMinWidth(150),
 | 
					    TableColumn.new('execTime', '执行时间').isTime().setMinWidth(150),
 | 
				
			||||||
@@ -72,10 +64,10 @@ const state = reactive({
 | 
				
			|||||||
    tags: [] as any,
 | 
					    tags: [] as any,
 | 
				
			||||||
    params: {
 | 
					    params: {
 | 
				
			||||||
        pageNum: 1,
 | 
					        pageNum: 1,
 | 
				
			||||||
        pageSize: 10,
 | 
					        pageSize: 8,
 | 
				
			||||||
        cronJobId: 0,
 | 
					        cronJobId: 0,
 | 
				
			||||||
        status: null,
 | 
					        status: null,
 | 
				
			||||||
        machineId: null,
 | 
					        machineCode: '',
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    // 列表数据
 | 
					    // 列表数据
 | 
				
			||||||
    data: {
 | 
					    data: {
 | 
				
			||||||
@@ -85,64 +77,17 @@ const state = reactive({
 | 
				
			|||||||
    machines: [],
 | 
					    machines: [],
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const machineMap: Map<number, any> = new Map();
 | 
					const { params } = toRefs(state);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const { dialogVisible, params } = toRefs(state);
 | 
					const dialogVisible = defineModel<boolean>('visible');
 | 
				
			||||||
 | 
					 | 
				
			||||||
watch(props, async (newValue: any) => {
 | 
					 | 
				
			||||||
    state.dialogVisible = newValue.visible;
 | 
					 | 
				
			||||||
    if (!newValue.visible) {
 | 
					 | 
				
			||||||
        return;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    const machineIds = await cronJobApi.relateMachineIds.request({
 | 
					 | 
				
			||||||
        cronJobId: props.data?.id,
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
    const res = await machineApi.list.request({
 | 
					 | 
				
			||||||
        ids: machineIds?.join(','),
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    res.list?.forEach((x: any) => {
 | 
					 | 
				
			||||||
        machineMap.set(x.id, x);
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    state.params.cronJobId = props.data?.id;
 | 
					 | 
				
			||||||
    search();
 | 
					 | 
				
			||||||
});
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
const search = async () => {
 | 
					const search = async () => {
 | 
				
			||||||
 | 
					    state.params.cronJobId = props.data?.id;
 | 
				
			||||||
    pageTableRef.value.search();
 | 
					    pageTableRef.value.search();
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const parseData = async (res: any) => {
 | 
					 | 
				
			||||||
    const dataList = res.list;
 | 
					 | 
				
			||||||
    // 填充机器信息
 | 
					 | 
				
			||||||
    for (let x of dataList) {
 | 
					 | 
				
			||||||
        const machineId = x.machineId;
 | 
					 | 
				
			||||||
        let machine = machineMap.get(machineId);
 | 
					 | 
				
			||||||
        // 如果未找到,则可能被移除,则调接口查询机器信息
 | 
					 | 
				
			||||||
        if (!machine) {
 | 
					 | 
				
			||||||
            const machineRes = await machineApi.list.request({ ids: machineId });
 | 
					 | 
				
			||||||
            if (!machineRes.list) {
 | 
					 | 
				
			||||||
                machine = {
 | 
					 | 
				
			||||||
                    id: machineId,
 | 
					 | 
				
			||||||
                    ip: machineId,
 | 
					 | 
				
			||||||
                    name: '该机器已被删除',
 | 
					 | 
				
			||||||
                };
 | 
					 | 
				
			||||||
            } else {
 | 
					 | 
				
			||||||
                machine = machineRes.list[0];
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            machineMap.set(machineId, machine);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        x.machineIp = machine?.ip;
 | 
					 | 
				
			||||||
        x.machineName = machine?.name;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    return res;
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const cancel = () => {
 | 
					const cancel = () => {
 | 
				
			||||||
    emit('update:visible', false);
 | 
					    dialogVisible.value = false;
 | 
				
			||||||
    setTimeout(() => {
 | 
					    setTimeout(() => {
 | 
				
			||||||
        initData();
 | 
					        initData();
 | 
				
			||||||
    }, 500);
 | 
					    }, 500);
 | 
				
			||||||
@@ -152,7 +97,7 @@ const initData = () => {
 | 
				
			|||||||
    state.data.list = [];
 | 
					    state.data.list = [];
 | 
				
			||||||
    state.data.total = 0;
 | 
					    state.data.total = 0;
 | 
				
			||||||
    state.params.pageNum = 1;
 | 
					    state.params.pageNum = 1;
 | 
				
			||||||
    state.params.machineId = null;
 | 
					    state.params.machineCode = '';
 | 
				
			||||||
    state.params.status = null;
 | 
					    state.params.status = null;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
</script>
 | 
					</script>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -19,6 +19,10 @@
 | 
				
			|||||||
                <el-tag v-else type="danger" effect="plain">未运行</el-tag>
 | 
					                <el-tag v-else type="danger" effect="plain">未运行</el-tag>
 | 
				
			||||||
            </template>
 | 
					            </template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            <template #codePaths="{ data }">
 | 
				
			||||||
 | 
					                <TagCodePath :path="data.tags?.map((tag: any) => tag.codePath)" />
 | 
				
			||||||
 | 
					            </template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            <template #action="{ data }">
 | 
					            <template #action="{ data }">
 | 
				
			||||||
                <el-button :disabled="data.status == CronJobStatusEnum.Disable.value" v-auth="perms.saveCronJob" type="primary" @click="runCronJob(data)" link
 | 
					                <el-button :disabled="data.status == CronJobStatusEnum.Disable.value" v-auth="perms.saveCronJob" type="primary" @click="runCronJob(data)" link
 | 
				
			||||||
                    >执行</el-button
 | 
					                    >执行</el-button
 | 
				
			||||||
@@ -41,6 +45,7 @@ import PageTable from '@/components/pagetable/PageTable.vue';
 | 
				
			|||||||
import { TableColumn } from '@/components/pagetable';
 | 
					import { TableColumn } from '@/components/pagetable';
 | 
				
			||||||
import { CronJobStatusEnum, CronJobSaveExecResTypeEnum } from '../enums';
 | 
					import { CronJobStatusEnum, CronJobSaveExecResTypeEnum } from '../enums';
 | 
				
			||||||
import { SearchItem } from '@/components/SearchForm';
 | 
					import { SearchItem } from '@/components/SearchForm';
 | 
				
			||||||
 | 
					import TagCodePath from '../../component/TagCodePath.vue';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const CronJobEdit = defineAsyncComponent(() => import('./CronJobEdit.vue'));
 | 
					const CronJobEdit = defineAsyncComponent(() => import('./CronJobEdit.vue'));
 | 
				
			||||||
const CronJobExecList = defineAsyncComponent(() => import('./CronJobExecList.vue'));
 | 
					const CronJobExecList = defineAsyncComponent(() => import('./CronJobExecList.vue'));
 | 
				
			||||||
@@ -61,6 +66,7 @@ const columns = ref([
 | 
				
			|||||||
    TableColumn.new('running', '运行状态').isSlot(),
 | 
					    TableColumn.new('running', '运行状态').isSlot(),
 | 
				
			||||||
    TableColumn.new('saveExecResType', '记录类型').typeTag(CronJobSaveExecResTypeEnum),
 | 
					    TableColumn.new('saveExecResType', '记录类型').typeTag(CronJobSaveExecResTypeEnum),
 | 
				
			||||||
    TableColumn.new('remark', '备注'),
 | 
					    TableColumn.new('remark', '备注'),
 | 
				
			||||||
 | 
					    TableColumn.new('codePaths', '关联机器').isSlot().setMinWidth('250px'),
 | 
				
			||||||
    TableColumn.new('action', '操作').isSlot().setMinWidth(180).fixedRight().alignCenter(),
 | 
					    TableColumn.new('action', '操作').isSlot().setMinWidth(180).fixedRight().alignCenter(),
 | 
				
			||||||
]);
 | 
					]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -3,6 +3,7 @@ import { EnumValue } from '@/common/Enum';
 | 
				
			|||||||
export const MachineProtocolEnum = {
 | 
					export const MachineProtocolEnum = {
 | 
				
			||||||
    Ssh: EnumValue.of(1, 'SSH'),
 | 
					    Ssh: EnumValue.of(1, 'SSH'),
 | 
				
			||||||
    Rdp: EnumValue.of(2, 'RDP'),
 | 
					    Rdp: EnumValue.of(2, 'RDP'),
 | 
				
			||||||
 | 
					    Vnc: EnumValue.of(3, 'VNC'),
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// 脚本执行结果类型
 | 
					// 脚本执行结果类型
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -18,12 +18,12 @@
 | 
				
			|||||||
                        </el-select>
 | 
					                        </el-select>
 | 
				
			||||||
                    </template>
 | 
					                    </template>
 | 
				
			||||||
                </el-table-column>
 | 
					                </el-table-column>
 | 
				
			||||||
                <el-table-column prop="path" label="路径" min-width="150px" show-overflow-tooltip>
 | 
					                <el-table-column prop="path" label="路径" min-width="180" show-overflow-tooltip>
 | 
				
			||||||
                    <template #default="scope">
 | 
					                    <template #default="scope">
 | 
				
			||||||
                        <el-input v-model="scope.row.path" :disabled="scope.row.id != null" clearable> </el-input>
 | 
					                        <el-input v-model="scope.row.path" :disabled="scope.row.id != null" clearable> </el-input>
 | 
				
			||||||
                    </template>
 | 
					                    </template>
 | 
				
			||||||
                </el-table-column>
 | 
					                </el-table-column>
 | 
				
			||||||
                <el-table-column label="操作" min-wdith="120px">
 | 
					                <el-table-column label="操作" min-width="130">
 | 
				
			||||||
                    <template #default="scope">
 | 
					                    <template #default="scope">
 | 
				
			||||||
                        <el-button v-if="scope.row.id == null" @click="addFiles(scope.row)" type="success" icon="success-filled" plain></el-button>
 | 
					                        <el-button v-if="scope.row.id == null" @click="addFiles(scope.row)" type="success" icon="success-filled" plain></el-button>
 | 
				
			||||||
                        <el-button v-if="scope.row.id != null" @click="getConf(scope.row)" type="primary" icon="tickets" plain></el-button>
 | 
					                        <el-button v-if="scope.row.id != null" @click="getConf(scope.row)" type="primary" icon="tickets" plain></el-button>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -22,7 +22,7 @@
 | 
				
			|||||||
            >
 | 
					            >
 | 
				
			||||||
                <el-table-column type="selection" width="30" />
 | 
					                <el-table-column type="selection" width="30" />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                <el-table-column prop="name" label="名称">
 | 
					                <el-table-column prop="name" label="名称" min-width="380">
 | 
				
			||||||
                    <template #header>
 | 
					                    <template #header>
 | 
				
			||||||
                        <div class="machine-file-table-header">
 | 
					                        <div class="machine-file-table-header">
 | 
				
			||||||
                            <div>
 | 
					                            <div>
 | 
				
			||||||
@@ -171,7 +171,7 @@
 | 
				
			|||||||
                    </template>
 | 
					                    </template>
 | 
				
			||||||
                </el-table-column>
 | 
					                </el-table-column>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                <el-table-column prop="size" label="大小" width="100" sortable>
 | 
					                <el-table-column prop="size" label="大小" min-width="90" sortable>
 | 
				
			||||||
                    <template #default="scope">
 | 
					                    <template #default="scope">
 | 
				
			||||||
                        <span style="color: #67c23a; font-weight: bold" v-if="scope.row.type == '-'"> {{ formatByteSize(scope.row.size) }} </span>
 | 
					                        <span style="color: #67c23a; font-weight: bold" v-if="scope.row.type == '-'"> {{ formatByteSize(scope.row.size) }} </span>
 | 
				
			||||||
                        <span style="color: #67c23a; font-weight: bold" v-if="scope.row.type == 'd' && scope.row.dirSize"> {{ scope.row.dirSize }} </span>
 | 
					                        <span style="color: #67c23a; font-weight: bold" v-if="scope.row.type == 'd' && scope.row.dirSize"> {{ scope.row.dirSize }} </span>
 | 
				
			||||||
@@ -182,7 +182,11 @@
 | 
				
			|||||||
                </el-table-column>
 | 
					                </el-table-column>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                <el-table-column prop="mode" label="属性" width="110"> </el-table-column>
 | 
					                <el-table-column prop="mode" label="属性" width="110"> </el-table-column>
 | 
				
			||||||
                <el-table-column prop="modTime" label="修改时间" width="165" sortable> </el-table-column>
 | 
					                <el-table-column v-if="$props.protocol == MachineProtocolEnum.Ssh.value" prop="username" label="用户" min-width="55" show-overflow-tooltip>
 | 
				
			||||||
 | 
					                </el-table-column>
 | 
				
			||||||
 | 
					                <el-table-column v-if="$props.protocol == MachineProtocolEnum.Ssh.value" prop="groupname" label="组" min-width="55" show-overflow-tooltip>
 | 
				
			||||||
 | 
					                </el-table-column>
 | 
				
			||||||
 | 
					                <el-table-column prop="modTime" label="修改时间" width="160" sortable> </el-table-column>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                <el-table-column width="100">
 | 
					                <el-table-column width="100">
 | 
				
			||||||
                    <template #header>
 | 
					                    <template #header>
 | 
				
			||||||
@@ -288,6 +292,8 @@ import MachineFileContent from './MachineFileContent.vue';
 | 
				
			|||||||
import { getToken } from '@/common/utils/storage';
 | 
					import { getToken } from '@/common/utils/storage';
 | 
				
			||||||
import { convertToBytes, formatByteSize } from '@/common/utils/format';
 | 
					import { convertToBytes, formatByteSize } from '@/common/utils/format';
 | 
				
			||||||
import { getMachineConfig } from '@/common/sysconfig';
 | 
					import { getMachineConfig } from '@/common/sysconfig';
 | 
				
			||||||
 | 
					import { MachineProtocolEnum } from '../enums';
 | 
				
			||||||
 | 
					import { fuzzyMatchField } from '@/common/utils/string';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const props = defineProps({
 | 
					const props = defineProps({
 | 
				
			||||||
    machineId: { type: Number },
 | 
					    machineId: { type: Number },
 | 
				
			||||||
@@ -303,6 +309,9 @@ const folderUploadRef: any = ref();
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
const folderType = 'd';
 | 
					const folderType = 'd';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const userMap = new Map<number, any>();
 | 
				
			||||||
 | 
					const groupMap = new Map<number, any>();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// 路径分隔符
 | 
					// 路径分隔符
 | 
				
			||||||
const pathSep = '/';
 | 
					const pathSep = '/';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -343,13 +352,27 @@ const { basePath, nowPath, loading, fileNameFilter, progressNum, uploadProgressS
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
onMounted(async () => {
 | 
					onMounted(async () => {
 | 
				
			||||||
    state.basePath = props.path;
 | 
					    state.basePath = props.path;
 | 
				
			||||||
 | 
					    const machineId = props.machineId;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (props.protocol == MachineProtocolEnum.Ssh.value) {
 | 
				
			||||||
 | 
					        machineApi.users.request({ id: machineId }).then((res: any) => {
 | 
				
			||||||
 | 
					            for (let user of res) {
 | 
				
			||||||
 | 
					                userMap.set(user.uid, user);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        machineApi.groups.request({ id: machineId }).then((res: any) => {
 | 
				
			||||||
 | 
					            for (let group of res) {
 | 
				
			||||||
 | 
					                groupMap.set(group.gid, group);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    setFiles(props.path);
 | 
					    setFiles(props.path);
 | 
				
			||||||
    state.machineConfig = await getMachineConfig();
 | 
					    state.machineConfig = await getMachineConfig();
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const filterFiles = computed(() =>
 | 
					const filterFiles = computed(() => fuzzyMatchField(state.fileNameFilter, state.files, (file: any) => file.name));
 | 
				
			||||||
    state.files.filter((data: any) => !state.fileNameFilter || data.name.toLowerCase().includes(state.fileNameFilter.toLowerCase()))
 | 
					 | 
				
			||||||
);
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
const filePathNav = computed(() => {
 | 
					const filePathNav = computed(() => {
 | 
				
			||||||
    let basePath = state.basePath;
 | 
					    let basePath = state.basePath;
 | 
				
			||||||
@@ -517,6 +540,11 @@ const lsFile = async (path: string) => {
 | 
				
			|||||||
        path,
 | 
					        path,
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
    for (const file of res) {
 | 
					    for (const file of res) {
 | 
				
			||||||
 | 
					        if (props.protocol == MachineProtocolEnum.Ssh.value) {
 | 
				
			||||||
 | 
					            file.username = userMap.get(file.uid)?.uname || file.uid;
 | 
				
			||||||
 | 
					            file.groupname = groupMap.get(file.gid)?.gname || file.gid;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        const type = file.type;
 | 
					        const type = file.type;
 | 
				
			||||||
        if (type == folderType) {
 | 
					        if (type == folderType) {
 | 
				
			||||||
            file.isFolder = true;
 | 
					            file.isFolder = true;
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										222
									
								
								mayfly_go_web/src/views/ops/machine/security/CmdConfList.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										222
									
								
								mayfly_go_web/src/views/ops/machine/security/CmdConfList.vue
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,222 @@
 | 
				
			|||||||
 | 
					<template>
 | 
				
			||||||
 | 
					    <div>
 | 
				
			||||||
 | 
					        <el-table :data="cmdConfs" stripe>
 | 
				
			||||||
 | 
					            <el-table-column prop="name" label="名称" show-overflow-tooltip min-width="100px"> </el-table-column>
 | 
				
			||||||
 | 
					            <el-table-column prop="cmds" label="过滤命令" min-width="320px" show-overflow-tooltip>
 | 
				
			||||||
 | 
					                <template #default="scope">
 | 
				
			||||||
 | 
					                    <el-tag class="ml2 mt2" v-for="cmd in scope.row.cmds" :key="cmd" type="danger">
 | 
				
			||||||
 | 
					                        {{ cmd }}
 | 
				
			||||||
 | 
					                    </el-tag>
 | 
				
			||||||
 | 
					                </template>
 | 
				
			||||||
 | 
					            </el-table-column>
 | 
				
			||||||
 | 
					            <el-table-column prop="codePaths" label="关联机器" min-width="250px" show-overflow-tooltip>
 | 
				
			||||||
 | 
					                <template #default="scope">
 | 
				
			||||||
 | 
					                    <TagCodePath :path="scope.row.tags?.map((tag: any) => tag.codePath)" />
 | 
				
			||||||
 | 
					                </template>
 | 
				
			||||||
 | 
					            </el-table-column>
 | 
				
			||||||
 | 
					            <el-table-column prop="remark" label="备注" show-overflow-tooltip width="120px"> </el-table-column>
 | 
				
			||||||
 | 
					            <el-table-column prop="creator" label="创建者" show-overflow-tooltip width="100px"> </el-table-column>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            <el-table-column label="操作" min-wdith="100px">
 | 
				
			||||||
 | 
					                <template #header>
 | 
				
			||||||
 | 
					                    <el-text tag="b">操作</el-text>
 | 
				
			||||||
 | 
					                    <el-button v-auth="'cmdconf:save'" class="ml5" type="primary" circle size="small" icon="Plus" @click="openFormDialog(false)"> </el-button>
 | 
				
			||||||
 | 
					                </template>
 | 
				
			||||||
 | 
					                <template #default="scope">
 | 
				
			||||||
 | 
					                    <el-button v-auth="'cmdconf:save'" @click="openFormDialog(scope.row)" type="primary" link>编辑</el-button>
 | 
				
			||||||
 | 
					                    <el-button v-auth="'cmdconf:del'" @click="deleteCmdConf(scope.row)" type="danger" link>删除</el-button>
 | 
				
			||||||
 | 
					                </template>
 | 
				
			||||||
 | 
					            </el-table-column>
 | 
				
			||||||
 | 
					        </el-table>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        <el-drawer title="命令配置" v-model="dialogVisible" :show-close="false" width="600px" :destroy-on-close="true" :close-on-click-modal="false">
 | 
				
			||||||
 | 
					            <template #header>
 | 
				
			||||||
 | 
					                <DrawerHeader header="命令配置" :back="cancelEdit" />
 | 
				
			||||||
 | 
					            </template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            <el-form ref="formRef" :model="state.form" :rules="rules" label-width="auto">
 | 
				
			||||||
 | 
					                <el-form-item prop="name" label="名称" required>
 | 
				
			||||||
 | 
					                    <el-input v-model="form.name" placeholder="名称"></el-input>
 | 
				
			||||||
 | 
					                </el-form-item>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                <el-form-item prop="cmds" label="过滤命令" required>
 | 
				
			||||||
 | 
					                    <el-row>
 | 
				
			||||||
 | 
					                        <el-tag
 | 
				
			||||||
 | 
					                            class="ml2 mt2"
 | 
				
			||||||
 | 
					                            v-for="tag in form.cmds"
 | 
				
			||||||
 | 
					                            :key="tag"
 | 
				
			||||||
 | 
					                            closable
 | 
				
			||||||
 | 
					                            :disable-transitions="false"
 | 
				
			||||||
 | 
					                            @close="handleCmdClose(tag)"
 | 
				
			||||||
 | 
					                            type="danger"
 | 
				
			||||||
 | 
					                        >
 | 
				
			||||||
 | 
					                            {{ tag }}
 | 
				
			||||||
 | 
					                        </el-tag>
 | 
				
			||||||
 | 
					                        <el-input
 | 
				
			||||||
 | 
					                            v-if="state.inputCmdVisible"
 | 
				
			||||||
 | 
					                            ref="cmdInputRef"
 | 
				
			||||||
 | 
					                            v-model="state.cmdInputValue"
 | 
				
			||||||
 | 
					                            class="mt3"
 | 
				
			||||||
 | 
					                            size="small"
 | 
				
			||||||
 | 
					                            @keyup.enter="handleCmdInputConfirm"
 | 
				
			||||||
 | 
					                            @blur="handleCmdInputConfirm"
 | 
				
			||||||
 | 
					                            placeholder="请输入命令正则表达式"
 | 
				
			||||||
 | 
					                        />
 | 
				
			||||||
 | 
					                        <el-button v-else class="ml2 mt2" size="small" @click="showCmdInput"> + 新建命令 </el-button>
 | 
				
			||||||
 | 
					                    </el-row>
 | 
				
			||||||
 | 
					                </el-form-item>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                <el-form-item label="备注">
 | 
				
			||||||
 | 
					                    <el-input v-model="form.remark" type="textarea" :rows="2"></el-input>
 | 
				
			||||||
 | 
					                </el-form-item>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                <el-form-item ref="tagSelectRef" prop="codePaths" label="关联机器">
 | 
				
			||||||
 | 
					                    <tag-tree-check height="calc(100vh - 430px)" :tag-type="TagResourceTypeEnum.MachineAuthCert.value" v-model="form.codePaths" />
 | 
				
			||||||
 | 
					                </el-form-item>
 | 
				
			||||||
 | 
					            </el-form>
 | 
				
			||||||
 | 
					            <template #footer>
 | 
				
			||||||
 | 
					                <div class="dialog-footer">
 | 
				
			||||||
 | 
					                    <el-button :loading="submiting" @click="cancelEdit">取 消</el-button>
 | 
				
			||||||
 | 
					                    <el-button v-auth="'cmdconf:save'" type="primary" :loading="submiting" @click="submitForm">确 定</el-button>
 | 
				
			||||||
 | 
					                </div>
 | 
				
			||||||
 | 
					            </template>
 | 
				
			||||||
 | 
					        </el-drawer>
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<script lang="ts" setup>
 | 
				
			||||||
 | 
					import { ref, toRefs, reactive, onMounted, nextTick } from 'vue';
 | 
				
			||||||
 | 
					import TagTreeCheck from '../../component/TagTreeCheck.vue';
 | 
				
			||||||
 | 
					import { TagResourceTypeEnum } from '@/common/commonEnum';
 | 
				
			||||||
 | 
					import { ElMessage, ElMessageBox } from 'element-plus';
 | 
				
			||||||
 | 
					import { cmdConfApi } from '../api';
 | 
				
			||||||
 | 
					import DrawerHeader from '@/components/drawer-header/DrawerHeader.vue';
 | 
				
			||||||
 | 
					import TagCodePath from '../../component/TagCodePath.vue';
 | 
				
			||||||
 | 
					import _ from 'lodash';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const rules = {
 | 
				
			||||||
 | 
					    tags: [
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            required: true,
 | 
				
			||||||
 | 
					            message: '请选择关联的机器',
 | 
				
			||||||
 | 
					            trigger: ['change'],
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					    ],
 | 
				
			||||||
 | 
					    cmds: [
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            required: true,
 | 
				
			||||||
 | 
					            message: '请创建命令',
 | 
				
			||||||
 | 
					            trigger: ['change', 'blur'],
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					    ],
 | 
				
			||||||
 | 
					    name: [
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            required: true,
 | 
				
			||||||
 | 
					            message: '请输入名称',
 | 
				
			||||||
 | 
					            trigger: ['change', 'blur'],
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					    ],
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const tagSelectRef: any = ref(null);
 | 
				
			||||||
 | 
					const formRef: any = ref(null);
 | 
				
			||||||
 | 
					const cmdInputRef: any = ref(null);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const DefaultForm = {
 | 
				
			||||||
 | 
					    id: 0,
 | 
				
			||||||
 | 
					    name: '',
 | 
				
			||||||
 | 
					    codePaths: [],
 | 
				
			||||||
 | 
					    cmds: [] as any,
 | 
				
			||||||
 | 
					    remark: '',
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const state = reactive({
 | 
				
			||||||
 | 
					    cmdConfs: [],
 | 
				
			||||||
 | 
					    dialogVisible: false,
 | 
				
			||||||
 | 
					    form: DefaultForm,
 | 
				
			||||||
 | 
					    submiting: false,
 | 
				
			||||||
 | 
					    inputCmdVisible: false,
 | 
				
			||||||
 | 
					    cmdInputValue: '',
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const { cmdConfs, dialogVisible, form, submiting } = toRefs(state);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					onMounted(async () => {
 | 
				
			||||||
 | 
					    getCmdConfs();
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const getCmdConfs = async () => {
 | 
				
			||||||
 | 
					    state.cmdConfs = await cmdConfApi.list.request();
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const handleCmdClose = (tag: string) => {
 | 
				
			||||||
 | 
					    state.form.cmds.splice(state.form.cmds.indexOf(tag), 1);
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const showCmdInput = () => {
 | 
				
			||||||
 | 
					    state.inputCmdVisible = true;
 | 
				
			||||||
 | 
					    nextTick(() => {
 | 
				
			||||||
 | 
					        cmdInputRef.value!.input!.focus();
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const handleCmdInputConfirm = () => {
 | 
				
			||||||
 | 
					    if (state.cmdInputValue) {
 | 
				
			||||||
 | 
					        state.form.cmds.push(state.cmdInputValue);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    state.inputCmdVisible = false;
 | 
				
			||||||
 | 
					    state.cmdInputValue = '';
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const openFormDialog = (data: any) => {
 | 
				
			||||||
 | 
					    if (!data) {
 | 
				
			||||||
 | 
					        state.form = { ...DefaultForm };
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					        state.form = _.cloneDeep(data);
 | 
				
			||||||
 | 
					        state.form.codePaths = data.tags?.map((tag: any) => tag.codePath);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    state.dialogVisible = true;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const deleteCmdConf = async (data: any) => {
 | 
				
			||||||
 | 
					    await ElMessageBox.confirm(`确定删除该[${data.name}]命令配置?`, '提示', {
 | 
				
			||||||
 | 
					        confirmButtonText: '确定',
 | 
				
			||||||
 | 
					        cancelButtonText: '取消',
 | 
				
			||||||
 | 
					        type: 'warning',
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    await cmdConfApi.delete.request({ id: data.id });
 | 
				
			||||||
 | 
					    ElMessage.success('操作成功');
 | 
				
			||||||
 | 
					    getCmdConfs();
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const cancelEdit = () => {
 | 
				
			||||||
 | 
					    state.dialogVisible = false;
 | 
				
			||||||
 | 
					    // 取消表单的校验
 | 
				
			||||||
 | 
					    setTimeout(() => {
 | 
				
			||||||
 | 
					        state.form = { ...DefaultForm };
 | 
				
			||||||
 | 
					        formRef.value.resetFields();
 | 
				
			||||||
 | 
					    }, 200);
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const submitForm = () => {
 | 
				
			||||||
 | 
					    formRef.value.validate(async (valid: boolean) => {
 | 
				
			||||||
 | 
					        if (!valid) {
 | 
				
			||||||
 | 
					            ElMessage.error('请正确填写信息');
 | 
				
			||||||
 | 
					            return false;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        try {
 | 
				
			||||||
 | 
					            state.submiting = true;
 | 
				
			||||||
 | 
					            await cmdConfApi.save.request(state.form);
 | 
				
			||||||
 | 
					            ElMessage.success('操作成功');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            cancelEdit();
 | 
				
			||||||
 | 
					            getCmdConfs();
 | 
				
			||||||
 | 
					        } finally {
 | 
				
			||||||
 | 
					            state.submiting = false;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					</script>
 | 
				
			||||||
 | 
					<style></style>
 | 
				
			||||||
@@ -0,0 +1,37 @@
 | 
				
			|||||||
 | 
					<template>
 | 
				
			||||||
 | 
					    <div class="card">
 | 
				
			||||||
 | 
					        <el-tabs v-model="activeName" class="demo-tabs" @tab-change="handleTabChange">
 | 
				
			||||||
 | 
					            <el-tab-pane label="命令配置" :name="CmdConfTab">
 | 
				
			||||||
 | 
					                <CmdConfList />
 | 
				
			||||||
 | 
					            </el-tab-pane>
 | 
				
			||||||
 | 
					        </el-tabs>
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<script lang="ts" setup>
 | 
				
			||||||
 | 
					import { toRefs, reactive, onMounted, defineAsyncComponent } from 'vue';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const CmdConfList = defineAsyncComponent(() => import('./CmdConfList.vue'));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const CmdConfTab = 'cmdConf';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const state = reactive({
 | 
				
			||||||
 | 
					    activeName: CmdConfTab,
 | 
				
			||||||
 | 
					    cmdConfs: [],
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const { activeName } = toRefs(state);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					onMounted(async () => {
 | 
				
			||||||
 | 
					    state.activeName = CmdConfTab;
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const handleTabChange = (tabName: any) => {
 | 
				
			||||||
 | 
					    if (tabName == CmdConfTab) {
 | 
				
			||||||
 | 
					        console.log('get cmd confs');
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    console.log(tabName);
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<style></style>
 | 
				
			||||||
@@ -2,7 +2,12 @@
 | 
				
			|||||||
    <div class="flex-all-center">
 | 
					    <div class="flex-all-center">
 | 
				
			||||||
        <Splitpanes class="default-theme">
 | 
					        <Splitpanes class="default-theme">
 | 
				
			||||||
            <Pane size="20" max-size="30">
 | 
					            <Pane size="20" max-size="30">
 | 
				
			||||||
                <tag-tree :resource-type="TagResourceTypeEnum.Mongo.value" :tag-path-node-type="NodeTypeTagPath">
 | 
					                <tag-tree
 | 
				
			||||||
 | 
					                    ref="tagTreeRef"
 | 
				
			||||||
 | 
					                    :default-expanded-keys="state.defaultExpendKey"
 | 
				
			||||||
 | 
					                    :resource-type="TagResourceTypeEnum.Mongo.value"
 | 
				
			||||||
 | 
					                    :tag-path-node-type="NodeTypeTagPath"
 | 
				
			||||||
 | 
					                >
 | 
				
			||||||
                    <template #prefix="{ data }">
 | 
					                    <template #prefix="{ data }">
 | 
				
			||||||
                        <span v-if="data.type.value == MongoNodeType.Mongo">
 | 
					                        <span v-if="data.type.value == MongoNodeType.Mongo">
 | 
				
			||||||
                            <el-popover :show-after="500" placement="right-start" title="mongo实例信息" trigger="hover" :width="250">
 | 
					                            <el-popover :show-after="500" placement="right-start" title="mongo实例信息" trigger="hover" :width="250">
 | 
				
			||||||
@@ -31,13 +36,8 @@
 | 
				
			|||||||
                        />
 | 
					                        />
 | 
				
			||||||
                    </template>
 | 
					                    </template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    <template #label="{ data }">
 | 
					                    <template #suffix="{ data }">
 | 
				
			||||||
                        <span v-if="data.type.value == MongoNodeType.Dbs">
 | 
					                        <span v-if="data.type.value == MongoNodeType.Dbs">{{ formatByteSize(data.params.size) }}</span>
 | 
				
			||||||
                            {{ data.params.database }}
 | 
					 | 
				
			||||||
                            <span style="color: #8492a6; font-size: 13px"> [{{ formatByteSize(data.params.size) }}] </span>
 | 
					 | 
				
			||||||
                        </span>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                        <span v-else>{{ data.label }}</span>
 | 
					 | 
				
			||||||
                    </template>
 | 
					                    </template>
 | 
				
			||||||
                </tag-tree>
 | 
					                </tag-tree>
 | 
				
			||||||
            </Pane>
 | 
					            </Pane>
 | 
				
			||||||
@@ -168,16 +168,18 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
<script lang="ts" setup>
 | 
					<script lang="ts" setup>
 | 
				
			||||||
import { mongoApi } from './api';
 | 
					import { mongoApi } from './api';
 | 
				
			||||||
import { computed, defineAsyncComponent, reactive, ref, toRefs } from 'vue';
 | 
					import { computed, defineAsyncComponent, onMounted, reactive, ref, toRefs, watch } from 'vue';
 | 
				
			||||||
import { ElMessage } from 'element-plus';
 | 
					import { ElMessage } from 'element-plus';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import { isTrue, notBlank } from '@/common/assert';
 | 
					import { isTrue, notBlank } from '@/common/assert';
 | 
				
			||||||
import { TagTreeNode, NodeType } from '../component/tag';
 | 
					import { TagTreeNode, NodeType, getTagTypeCodeByPath } from '../component/tag';
 | 
				
			||||||
import TagTree from '../component/TagTree.vue';
 | 
					import TagTree from '../component/TagTree.vue';
 | 
				
			||||||
import { formatByteSize } from '@/common/utils/format';
 | 
					import { formatByteSize } from '@/common/utils/format';
 | 
				
			||||||
import { TagResourceTypeEnum } from '@/common/commonEnum';
 | 
					import { TagResourceTypeEnum } from '@/common/commonEnum';
 | 
				
			||||||
import { sleep } from '@/common/utils/loading';
 | 
					import { sleep } from '@/common/utils/loading';
 | 
				
			||||||
import { Splitpanes, Pane } from 'splitpanes';
 | 
					import { Splitpanes, Pane } from 'splitpanes';
 | 
				
			||||||
 | 
					import { useAutoOpenResource } from '@/store/autoOpenResource';
 | 
				
			||||||
 | 
					import { storeToRefs } from 'pinia';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const MonacoEditor = defineAsyncComponent(() => import('@/components/monaco/MonacoEditor.vue'));
 | 
					const MonacoEditor = defineAsyncComponent(() => import('@/components/monaco/MonacoEditor.vue'));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -207,7 +209,7 @@ const NodeTypeTagPath = new NodeType(TagTreeNode.TagPath).withLoadNodesFunc(asyn
 | 
				
			|||||||
    await sleep(100);
 | 
					    await sleep(100);
 | 
				
			||||||
    return mongoInfos?.map((x: any) => {
 | 
					    return mongoInfos?.map((x: any) => {
 | 
				
			||||||
        x.tagPath = parentNode.key;
 | 
					        x.tagPath = parentNode.key;
 | 
				
			||||||
        return new TagTreeNode(`${parentNode.key}.${x.id}`, x.name, NodeTypeMongo).withParams(x);
 | 
					        return new TagTreeNode(`${x.code}`, x.name, NodeTypeMongo).withParams(x);
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -250,7 +252,10 @@ const NodeTypeColl = new NodeType(MongoNodeType.Coll).withNodeClickFunc((nodeDat
 | 
				
			|||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const findParamInputRef: any = ref(null);
 | 
					const findParamInputRef: any = ref(null);
 | 
				
			||||||
 | 
					const tagTreeRef: any = ref(null);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const state = reactive({
 | 
					const state = reactive({
 | 
				
			||||||
 | 
					    defaultExpendKey: [] as any,
 | 
				
			||||||
    tags: [],
 | 
					    tags: [],
 | 
				
			||||||
    mongoList: [] as any,
 | 
					    mongoList: [] as any,
 | 
				
			||||||
    activeName: '', // 当前操作的tab
 | 
					    activeName: '', // 当前操作的tab
 | 
				
			||||||
@@ -282,10 +287,42 @@ const state = reactive({
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
const { findDialog, docEditDialog } = toRefs(state);
 | 
					const { findDialog, docEditDialog } = toRefs(state);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const autoOpenResourceStore = useAutoOpenResource();
 | 
				
			||||||
 | 
					const { autoOpenResource } = storeToRefs(autoOpenResourceStore);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const nowColl = computed(() => {
 | 
					const nowColl = computed(() => {
 | 
				
			||||||
    return getNowDataTab();
 | 
					    return getNowDataTab();
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					watch(
 | 
				
			||||||
 | 
					    () => autoOpenResource.value.mongoCodePath,
 | 
				
			||||||
 | 
					    (codePath: any) => {
 | 
				
			||||||
 | 
					        autoOpenMongo(codePath);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					onMounted(() => {
 | 
				
			||||||
 | 
					    autoOpenMongo(autoOpenResource.value.mongoCodePath);
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const autoOpenMongo = (codePath: string) => {
 | 
				
			||||||
 | 
					    if (!codePath) {
 | 
				
			||||||
 | 
					        return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const typeAndCodes = getTagTypeCodeByPath(codePath);
 | 
				
			||||||
 | 
					    const tagPath = typeAndCodes[TagResourceTypeEnum.Tag.value].join('/') + '/';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const mongoCode = typeAndCodes[TagResourceTypeEnum.Mongo.value][0];
 | 
				
			||||||
 | 
					    state.defaultExpendKey = [tagPath, mongoCode];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    setTimeout(() => {
 | 
				
			||||||
 | 
					        // 置空
 | 
				
			||||||
 | 
					        autoOpenResourceStore.setMongoCodePath('');
 | 
				
			||||||
 | 
					        tagTreeRef.value.setCurrentKey(mongoCode);
 | 
				
			||||||
 | 
					    }, 600);
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const changeCollection = async (id: any, schema: string, collection: string) => {
 | 
					const changeCollection = async (id: any, schema: string, collection: string) => {
 | 
				
			||||||
    const label = `${id}:\`${schema}\`.${collection}`;
 | 
					    const label = `${id}:\`${schema}\`.${collection}`;
 | 
				
			||||||
    let dataTab = state.dataTabs[label];
 | 
					    let dataTab = state.dataTabs[label];
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -163,30 +163,30 @@ const getReqForm = () => {
 | 
				
			|||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const testConn = async () => {
 | 
					const testConn = async () => {
 | 
				
			||||||
    mongoForm.value.validate(async (valid: boolean) => {
 | 
					    try {
 | 
				
			||||||
        if (!valid) {
 | 
					        await mongoForm.value.validate();
 | 
				
			||||||
 | 
					    } catch (e: any) {
 | 
				
			||||||
        ElMessage.error('请正确填写信息');
 | 
					        ElMessage.error('请正确填写信息');
 | 
				
			||||||
        return false;
 | 
					        return false;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					 | 
				
			||||||
    state.submitForm = getReqForm();
 | 
					    state.submitForm = getReqForm();
 | 
				
			||||||
    await testConnExec();
 | 
					    await testConnExec();
 | 
				
			||||||
    ElMessage.success('连接成功');
 | 
					    ElMessage.success('连接成功');
 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const btnOk = async () => {
 | 
					const btnOk = async () => {
 | 
				
			||||||
    mongoForm.value.validate(async (valid: boolean) => {
 | 
					    try {
 | 
				
			||||||
        if (!valid) {
 | 
					        await mongoForm.value.validate();
 | 
				
			||||||
 | 
					    } catch (e: any) {
 | 
				
			||||||
        ElMessage.error('请正确填写信息');
 | 
					        ElMessage.error('请正确填写信息');
 | 
				
			||||||
        return false;
 | 
					        return false;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    state.submitForm = getReqForm();
 | 
					    state.submitForm = getReqForm();
 | 
				
			||||||
    await saveMongoExec();
 | 
					    await saveMongoExec();
 | 
				
			||||||
    ElMessage.success('保存成功');
 | 
					    ElMessage.success('保存成功');
 | 
				
			||||||
    emit('val-change', state.form);
 | 
					    emit('val-change', state.form);
 | 
				
			||||||
    cancel();
 | 
					    cancel();
 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const cancel = () => {
 | 
					const cancel = () => {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -2,7 +2,12 @@
 | 
				
			|||||||
    <div class="redis-data-op flex-all-center">
 | 
					    <div class="redis-data-op flex-all-center">
 | 
				
			||||||
        <Splitpanes class="default-theme">
 | 
					        <Splitpanes class="default-theme">
 | 
				
			||||||
            <Pane size="20" max-size="30">
 | 
					            <Pane size="20" max-size="30">
 | 
				
			||||||
                <tag-tree :resource-type="TagResourceTypeEnum.Redis.value" :tag-path-node-type="NodeTypeTagPath">
 | 
					                <tag-tree
 | 
				
			||||||
 | 
					                    ref="tagTreeRef"
 | 
				
			||||||
 | 
					                    :default-expanded-keys="state.defaultExpendKey"
 | 
				
			||||||
 | 
					                    :resource-type="TagResourceTypeEnum.Redis.value"
 | 
				
			||||||
 | 
					                    :tag-path-node-type="NodeTypeTagPath"
 | 
				
			||||||
 | 
					                >
 | 
				
			||||||
                    <template #prefix="{ data }">
 | 
					                    <template #prefix="{ data }">
 | 
				
			||||||
                        <span v-if="data.type.value == RedisNodeType.Redis">
 | 
					                        <span v-if="data.type.value == RedisNodeType.Redis">
 | 
				
			||||||
                            <el-popover :show-after="500" placement="right-start" title="redis实例信息" trigger="hover" :width="250">
 | 
					                            <el-popover :show-after="500" placement="right-start" title="redis实例信息" trigger="hover" :width="250">
 | 
				
			||||||
@@ -30,6 +35,10 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
                        <SvgIcon v-if="data.type.value == RedisNodeType.Db" name="Coin" color="#67c23a" />
 | 
					                        <SvgIcon v-if="data.type.value == RedisNodeType.Db" name="Coin" color="#67c23a" />
 | 
				
			||||||
                    </template>
 | 
					                    </template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    <template #suffix="{ data }">
 | 
				
			||||||
 | 
					                        <span v-if="data.type.value == RedisNodeType.Db">{{ data.params.keys }}</span>
 | 
				
			||||||
 | 
					                    </template>
 | 
				
			||||||
                </tag-tree>
 | 
					                </tag-tree>
 | 
				
			||||||
            </Pane>
 | 
					            </Pane>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -178,11 +187,11 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
<script lang="ts" setup>
 | 
					<script lang="ts" setup>
 | 
				
			||||||
import { redisApi } from './api';
 | 
					import { redisApi } from './api';
 | 
				
			||||||
import { ref, defineAsyncComponent, toRefs, reactive, onMounted, nextTick, Ref } from 'vue';
 | 
					import { ref, defineAsyncComponent, toRefs, reactive, onMounted, nextTick, Ref, watch } from 'vue';
 | 
				
			||||||
import { ElMessage, ElMessageBox } from 'element-plus';
 | 
					import { ElMessage, ElMessageBox } from 'element-plus';
 | 
				
			||||||
import { isTrue, notBlank, notNull } from '@/common/assert';
 | 
					import { isTrue, notBlank, notNull } from '@/common/assert';
 | 
				
			||||||
import { copyToClipboard } from '@/common/utils/string';
 | 
					import { copyToClipboard } from '@/common/utils/string';
 | 
				
			||||||
import { TagTreeNode, NodeType } from '../component/tag';
 | 
					import { TagTreeNode, NodeType, getTagTypeCodeByPath } from '../component/tag';
 | 
				
			||||||
import TagTree from '../component/TagTree.vue';
 | 
					import TagTree from '../component/TagTree.vue';
 | 
				
			||||||
import { keysToTree, sortByTreeNodes, keysToList } from './utils';
 | 
					import { keysToTree, sortByTreeNodes, keysToList } from './utils';
 | 
				
			||||||
import { Contextmenu, ContextmenuItem } from '@/components/contextmenu';
 | 
					import { Contextmenu, ContextmenuItem } from '@/components/contextmenu';
 | 
				
			||||||
@@ -190,6 +199,9 @@ import { sleep } from '@/common/utils/loading';
 | 
				
			|||||||
import { TagResourceTypeEnum } from '@/common/commonEnum';
 | 
					import { TagResourceTypeEnum } from '@/common/commonEnum';
 | 
				
			||||||
import { Splitpanes, Pane } from 'splitpanes';
 | 
					import { Splitpanes, Pane } from 'splitpanes';
 | 
				
			||||||
import { RedisInst } from './redis';
 | 
					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'));
 | 
					const KeyDetail = defineAsyncComponent(() => import('./KeyDetail.vue'));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -230,18 +242,20 @@ const NodeTypeTagPath = new NodeType(TagTreeNode.TagPath).withLoadNodesFunc(asyn
 | 
				
			|||||||
    await sleep(100);
 | 
					    await sleep(100);
 | 
				
			||||||
    return redisInfos.map((x: any) => {
 | 
					    return redisInfos.map((x: any) => {
 | 
				
			||||||
        x.tagPath = parentNode.key;
 | 
					        x.tagPath = parentNode.key;
 | 
				
			||||||
        return new TagTreeNode(`${parentNode.key}.${x.id}`, x.name, NodeTypeRedis).withParams(x);
 | 
					        return new TagTreeNode(`${x.code}`, x.name, NodeTypeRedis).withParams(x);
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// redis实例节点类型
 | 
					// redis实例节点类型
 | 
				
			||||||
const NodeTypeRedis = new NodeType(RedisNodeType.Redis).withLoadNodesFunc(async (parentNode: TagTreeNode) => {
 | 
					const NodeTypeRedis = new NodeType(RedisNodeType.Redis).withLoadNodesFunc(async (parentNode: TagTreeNode) => {
 | 
				
			||||||
    const redisInfo = parentNode.params;
 | 
					    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) => {
 | 
					    let dbs: TagTreeNode[] = redisInfo.db.split(',').map((x: string) => {
 | 
				
			||||||
        return new TagTreeNode(x, `db${x}`, NodeTypeDb).withIsLeaf(true).withParams({
 | 
					        return new TagTreeNode(x, `db${x}`, NodeTypeDb).withIsLeaf(true).withParams({
 | 
				
			||||||
            id: redisInfo.id,
 | 
					            id: redisInfo.id,
 | 
				
			||||||
            db: x,
 | 
					            db: x,
 | 
				
			||||||
            flowProcdefKey: redisInfo.flowProcdefKey,
 | 
					            flowProcdef: flowProcdef,
 | 
				
			||||||
            name: `db${x}`,
 | 
					            name: `db${x}`,
 | 
				
			||||||
            keys: 0,
 | 
					            keys: 0,
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
@@ -261,7 +275,7 @@ const NodeTypeRedis = new NodeType(RedisNodeType.Redis).withLoadNodesFunc(async
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
    // 替换label
 | 
					    // 替换label
 | 
				
			||||||
    dbs.forEach((e: any) => {
 | 
					    dbs.forEach((e: any) => {
 | 
				
			||||||
        e.label = `${e.params.name} [${e.params.keys}]`;
 | 
					        e.label = `${e.params.name}`;
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
    return dbs;
 | 
					    return dbs;
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
@@ -274,7 +288,7 @@ const NodeTypeDb = new NodeType(RedisNodeType.Db).withNodeClickFunc((nodeData: T
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    redisInst.value.id = nodeData.params.id;
 | 
					    redisInst.value.id = nodeData.params.id;
 | 
				
			||||||
    redisInst.value.db = Number.parseInt(nodeData.params.db);
 | 
					    redisInst.value.db = Number.parseInt(nodeData.params.db);
 | 
				
			||||||
    redisInst.value.flowProcdefKey = nodeData.params.flowProcdefKey;
 | 
					    redisInst.value.flowProcdef = nodeData.params.flowProcdef;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    scan();
 | 
					    scan();
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
@@ -288,9 +302,11 @@ const treeProps = {
 | 
				
			|||||||
const defaultCount = 250;
 | 
					const defaultCount = 250;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const keyTreeRef: any = ref(null);
 | 
					const keyTreeRef: any = ref(null);
 | 
				
			||||||
 | 
					const tagTreeRef: any = ref(null);
 | 
				
			||||||
const redisInst: Ref<RedisInst> = ref(new RedisInst());
 | 
					const redisInst: Ref<RedisInst> = ref(new RedisInst());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const state = reactive({
 | 
					const state = reactive({
 | 
				
			||||||
 | 
					    defaultExpendKey: [] as any,
 | 
				
			||||||
    tags: [],
 | 
					    tags: [],
 | 
				
			||||||
    redisList: [] as any,
 | 
					    redisList: [] as any,
 | 
				
			||||||
    dbList: [],
 | 
					    dbList: [],
 | 
				
			||||||
@@ -331,7 +347,37 @@ const state = reactive({
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
const { scanParam, keyTreeData, newKeyDialog } = toRefs(state);
 | 
					const { scanParam, keyTreeData, newKeyDialog } = toRefs(state);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
onMounted(async () => {});
 | 
					const autoOpenResourceStore = useAutoOpenResource();
 | 
				
			||||||
 | 
					const { autoOpenResource } = storeToRefs(autoOpenResourceStore);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					onMounted(async () => {
 | 
				
			||||||
 | 
					    autoOpenRedis(autoOpenResource.value.redisCodePath);
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					watch(
 | 
				
			||||||
 | 
					    () => autoOpenResource.value.redisCodePath,
 | 
				
			||||||
 | 
					    (codePath: any) => {
 | 
				
			||||||
 | 
					        autoOpenRedis(codePath);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const autoOpenRedis = (codePath: string) => {
 | 
				
			||||||
 | 
					    if (!codePath) {
 | 
				
			||||||
 | 
					        return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const typeAndCodes = getTagTypeCodeByPath(codePath);
 | 
				
			||||||
 | 
					    const tagPath = typeAndCodes[TagResourceTypeEnum.Tag.value].join('/') + '/';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const redisCode = typeAndCodes[TagResourceTypeEnum.Redis.value][0];
 | 
				
			||||||
 | 
					    state.defaultExpendKey = [tagPath, redisCode];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    setTimeout(() => {
 | 
				
			||||||
 | 
					        // 置空
 | 
				
			||||||
 | 
					        autoOpenResourceStore.setRedisCodePath('');
 | 
				
			||||||
 | 
					        tagTreeRef.value.setCurrentKey(redisCode);
 | 
				
			||||||
 | 
					    }, 600);
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const scan = async (appendKey = false) => {
 | 
					const scan = async (appendKey = false) => {
 | 
				
			||||||
    isTrue(state.scanParam.id != null, '请先选择redis');
 | 
					    isTrue(state.scanParam.id != null, '请先选择redis');
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -58,13 +58,6 @@
 | 
				
			|||||||
                                placeholder="请输入密码, 修改操作可不填"
 | 
					                                placeholder="请输入密码, 修改操作可不填"
 | 
				
			||||||
                                autocomplete="new-password"
 | 
					                                autocomplete="new-password"
 | 
				
			||||||
                            >
 | 
					                            >
 | 
				
			||||||
                                <!-- <template v-if="form.id && form.id != 0" #suffix>
 | 
					 | 
				
			||||||
                                    <el-popover @hide="pwd = ''" placement="right" title="原密码" :width="200" trigger="click" :content="pwd">
 | 
					 | 
				
			||||||
                                        <template #reference>
 | 
					 | 
				
			||||||
                                            <el-link @click="getPwd" :underline="false" type="primary" class="mr5">原密码</el-link>
 | 
					 | 
				
			||||||
                                        </template>
 | 
					 | 
				
			||||||
                                    </el-popover>
 | 
					 | 
				
			||||||
                                </template> -->
 | 
					 | 
				
			||||||
                            </el-input>
 | 
					                            </el-input>
 | 
				
			||||||
                        </el-form-item>
 | 
					                        </el-form-item>
 | 
				
			||||||
                        <el-form-item prop="db" label="库号" required>
 | 
					                        <el-form-item prop="db" label="库号" required>
 | 
				
			||||||
@@ -84,8 +77,6 @@
 | 
				
			|||||||
                        <el-form-item prop="remark" label="备注">
 | 
					                        <el-form-item prop="remark" label="备注">
 | 
				
			||||||
                            <el-input v-model.trim="form.remark" auto-complete="off" type="textarea"></el-input>
 | 
					                            <el-input v-model.trim="form.remark" auto-complete="off" type="textarea"></el-input>
 | 
				
			||||||
                        </el-form-item>
 | 
					                        </el-form-item>
 | 
				
			||||||
 | 
					 | 
				
			||||||
                        <procdef-select-form-item v-model="form.flowProcdefKey" />
 | 
					 | 
				
			||||||
                    </el-tab-pane>
 | 
					                    </el-tab-pane>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    <el-tab-pane label="其他配置" name="other">
 | 
					                    <el-tab-pane label="其他配置" name="other">
 | 
				
			||||||
@@ -108,12 +99,11 @@
 | 
				
			|||||||
</template>
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<script lang="ts" setup>
 | 
					<script lang="ts" setup>
 | 
				
			||||||
import { toRefs, reactive, ref, watchEffect } from 'vue';
 | 
					import { toRefs, reactive, ref, watch } from 'vue';
 | 
				
			||||||
import { redisApi } from './api';
 | 
					import { redisApi } from './api';
 | 
				
			||||||
import { ElMessage } from 'element-plus';
 | 
					import { ElMessage } from 'element-plus';
 | 
				
			||||||
import TagTreeSelect from '../component/TagTreeSelect.vue';
 | 
					import TagTreeSelect from '../component/TagTreeSelect.vue';
 | 
				
			||||||
import SshTunnelSelect from '../component/SshTunnelSelect.vue';
 | 
					import SshTunnelSelect from '../component/SshTunnelSelect.vue';
 | 
				
			||||||
import ProcdefSelectFormItem from '@/views/flow/components/ProcdefSelectFormItem.vue';
 | 
					 | 
				
			||||||
import { ResourceCodePattern } from '@/common/pattern';
 | 
					import { ResourceCodePattern } from '@/common/pattern';
 | 
				
			||||||
import DrawerHeader from '@/components/drawer-header/DrawerHeader.vue';
 | 
					import DrawerHeader from '@/components/drawer-header/DrawerHeader.vue';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -199,7 +189,6 @@ const state = reactive({
 | 
				
			|||||||
        db: '',
 | 
					        db: '',
 | 
				
			||||||
        remark: '',
 | 
					        remark: '',
 | 
				
			||||||
        sshTunnelMachineId: -1,
 | 
					        sshTunnelMachineId: -1,
 | 
				
			||||||
        flowProcdefKey: '',
 | 
					 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    submitForm: {} as any,
 | 
					    submitForm: {} as any,
 | 
				
			||||||
    dbList: [0],
 | 
					    dbList: [0],
 | 
				
			||||||
@@ -211,7 +200,9 @@ const { dialogVisible, tabActiveName, form, submitForm, dbList } = toRefs(state)
 | 
				
			|||||||
const { isFetching: testConnBtnLoading, execute: testConnExec } = redisApi.testConn.useApi(submitForm);
 | 
					const { isFetching: testConnBtnLoading, execute: testConnExec } = redisApi.testConn.useApi(submitForm);
 | 
				
			||||||
const { isFetching: saveBtnLoading, execute: saveRedisExec } = redisApi.saveRedis.useApi(submitForm);
 | 
					const { isFetching: saveBtnLoading, execute: saveRedisExec } = redisApi.saveRedis.useApi(submitForm);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
watchEffect(() => {
 | 
					watch(
 | 
				
			||||||
 | 
					    () => props.visible,
 | 
				
			||||||
 | 
					    () => {
 | 
				
			||||||
        state.dialogVisible = props.visible;
 | 
					        state.dialogVisible = props.visible;
 | 
				
			||||||
        if (!state.dialogVisible) {
 | 
					        if (!state.dialogVisible) {
 | 
				
			||||||
            return;
 | 
					            return;
 | 
				
			||||||
@@ -223,10 +214,11 @@ watchEffect(() => {
 | 
				
			|||||||
            state.form.tagCodePaths = redis.tags.map((t: any) => t.codePath);
 | 
					            state.form.tagCodePaths = redis.tags.map((t: any) => t.codePath);
 | 
				
			||||||
            convertDb(state.form.db);
 | 
					            convertDb(state.form.db);
 | 
				
			||||||
        } else {
 | 
					        } else {
 | 
				
			||||||
        state.form = { db: '0' } as any;
 | 
					            state.form = { db: '0', tagCodePaths: [] } as any;
 | 
				
			||||||
            state.dbList = [0];
 | 
					            state.dbList = [0];
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
});
 | 
					    }
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const convertDb = (db: string) => {
 | 
					const convertDb = (db: string) => {
 | 
				
			||||||
    state.dbList = db.split(',').map((x) => Number.parseInt(x));
 | 
					    state.dbList = db.split(',').map((x) => Number.parseInt(x));
 | 
				
			||||||
@@ -252,8 +244,9 @@ const getReqForm = async () => {
 | 
				
			|||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const testConn = async () => {
 | 
					const testConn = async () => {
 | 
				
			||||||
    redisForm.value.validate(async (valid: boolean) => {
 | 
					    try {
 | 
				
			||||||
        if (!valid) {
 | 
					        await redisForm.value.validate();
 | 
				
			||||||
 | 
					    } catch (e: any) {
 | 
				
			||||||
        ElMessage.error('请正确填写信息');
 | 
					        ElMessage.error('请正确填写信息');
 | 
				
			||||||
        return false;
 | 
					        return false;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@@ -261,21 +254,21 @@ const testConn = async () => {
 | 
				
			|||||||
    state.submitForm = await getReqForm();
 | 
					    state.submitForm = await getReqForm();
 | 
				
			||||||
    await testConnExec();
 | 
					    await testConnExec();
 | 
				
			||||||
    ElMessage.success('连接成功');
 | 
					    ElMessage.success('连接成功');
 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const btnOk = async () => {
 | 
					const btnOk = async () => {
 | 
				
			||||||
    redisForm.value.validate(async (valid: boolean) => {
 | 
					    try {
 | 
				
			||||||
        if (!valid) {
 | 
					        await redisForm.value.validate();
 | 
				
			||||||
 | 
					    } catch (e: any) {
 | 
				
			||||||
        ElMessage.error('请正确填写信息');
 | 
					        ElMessage.error('请正确填写信息');
 | 
				
			||||||
        return false;
 | 
					        return false;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    state.submitForm = await getReqForm();
 | 
					    state.submitForm = await getReqForm();
 | 
				
			||||||
    await saveRedisExec();
 | 
					    await saveRedisExec();
 | 
				
			||||||
    ElMessage.success('保存成功');
 | 
					    ElMessage.success('保存成功');
 | 
				
			||||||
    emit('val-change', state.form);
 | 
					    emit('val-change', state.form);
 | 
				
			||||||
    cancel();
 | 
					    cancel();
 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const cancel = () => {
 | 
					const cancel = () => {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -128,13 +128,12 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
                <el-descriptions-item :span="3" label="库">{{ detailDialog.data.db }}</el-descriptions-item>
 | 
					                <el-descriptions-item :span="3" label="库">{{ detailDialog.data.db }}</el-descriptions-item>
 | 
				
			||||||
                <el-descriptions-item :span="3" label="备注">{{ detailDialog.data.remark }}</el-descriptions-item>
 | 
					                <el-descriptions-item :span="3" label="备注">{{ detailDialog.data.remark }}</el-descriptions-item>
 | 
				
			||||||
                <el-descriptions-item :span="3" label="工单流程key">{{ detailDialog.data?.flowProcdefKey }}</el-descriptions-item>
 | 
					 | 
				
			||||||
                <el-descriptions-item :span="3" label="SSH隧道">{{ detailDialog.data.sshTunnelMachineId > 0 ? '是' : '否' }} </el-descriptions-item>
 | 
					                <el-descriptions-item :span="3" label="SSH隧道">{{ detailDialog.data.sshTunnelMachineId > 0 ? '是' : '否' }} </el-descriptions-item>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                <el-descriptions-item :span="2" label="创建时间">{{ dateFormat(detailDialog.data.createTime) }} </el-descriptions-item>
 | 
					                <el-descriptions-item :span="2" label="创建时间">{{ formatDate(detailDialog.data.createTime) }} </el-descriptions-item>
 | 
				
			||||||
                <el-descriptions-item :span="1" label="创建者">{{ detailDialog.data.creator }}</el-descriptions-item>
 | 
					                <el-descriptions-item :span="1" label="创建者">{{ detailDialog.data.creator }}</el-descriptions-item>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                <el-descriptions-item :span="2" label="更新时间">{{ dateFormat(detailDialog.data.updateTime) }} </el-descriptions-item>
 | 
					                <el-descriptions-item :span="2" label="更新时间">{{ formatDate(detailDialog.data.updateTime) }} </el-descriptions-item>
 | 
				
			||||||
                <el-descriptions-item :span="1" label="修改者">{{ detailDialog.data.modifier }}</el-descriptions-item>
 | 
					                <el-descriptions-item :span="1" label="修改者">{{ detailDialog.data.modifier }}</el-descriptions-item>
 | 
				
			||||||
            </el-descriptions>
 | 
					            </el-descriptions>
 | 
				
			||||||
        </el-dialog>
 | 
					        </el-dialog>
 | 
				
			||||||
@@ -154,7 +153,7 @@ import { redisApi } from './api';
 | 
				
			|||||||
import { onMounted, reactive, ref, Ref, toRefs } from 'vue';
 | 
					import { onMounted, reactive, ref, Ref, toRefs } from 'vue';
 | 
				
			||||||
import { ElMessage, ElMessageBox } from 'element-plus';
 | 
					import { ElMessage, ElMessageBox } from 'element-plus';
 | 
				
			||||||
import RedisEdit from './RedisEdit.vue';
 | 
					import RedisEdit from './RedisEdit.vue';
 | 
				
			||||||
import { dateFormat } from '@/common/utils/date';
 | 
					import { formatDate } from '@/common/utils/format';
 | 
				
			||||||
import ResourceTags from '../component/ResourceTags.vue';
 | 
					import ResourceTags from '../component/ResourceTags.vue';
 | 
				
			||||||
import PageTable from '@/components/pagetable/PageTable.vue';
 | 
					import PageTable from '@/components/pagetable/PageTable.vue';
 | 
				
			||||||
import { TableColumn } from '@/components/pagetable';
 | 
					import { TableColumn } from '@/components/pagetable';
 | 
				
			||||||
@@ -180,7 +179,6 @@ const columns = ref([
 | 
				
			|||||||
    TableColumn.new('name', '名称'),
 | 
					    TableColumn.new('name', '名称'),
 | 
				
			||||||
    TableColumn.new('host', 'host:port'),
 | 
					    TableColumn.new('host', 'host:port'),
 | 
				
			||||||
    TableColumn.new('mode', 'mode'),
 | 
					    TableColumn.new('mode', 'mode'),
 | 
				
			||||||
    TableColumn.new('flowProcdefKey', '关联流程'),
 | 
					 | 
				
			||||||
    TableColumn.new('remark', '备注'),
 | 
					    TableColumn.new('remark', '备注'),
 | 
				
			||||||
    TableColumn.new('code', '编号'),
 | 
					    TableColumn.new('code', '编号'),
 | 
				
			||||||
    TableColumn.new('action', '操作').isSlot().setMinWidth(200).fixedRight().alignCenter(),
 | 
					    TableColumn.new('action', '操作').isSlot().setMinWidth(200).fixedRight().alignCenter(),
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -5,7 +5,7 @@ export type CmdExecProps = {
 | 
				
			|||||||
    id: number;
 | 
					    id: number;
 | 
				
			||||||
    db: number | string;
 | 
					    db: number | string;
 | 
				
			||||||
    cmd: any[];
 | 
					    cmd: any[];
 | 
				
			||||||
    flowProcdefKey?: string;
 | 
					    flowProcdef?: any;
 | 
				
			||||||
    visible?: boolean;
 | 
					    visible?: boolean;
 | 
				
			||||||
    runSuccessFn?: Function;
 | 
					    runSuccessFn?: Function;
 | 
				
			||||||
    cancelFn?: Function;
 | 
					    cancelFn?: Function;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -4,9 +4,9 @@
 | 
				
			|||||||
            <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" />
 | 
					            <el-input @keyup.enter="runCmd" ref="remarkInputRef" v-model="remark" placeholder="请输入执行备注" class="mt5" />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            <div v-if="props.flowProcdefKey">
 | 
					            <div v-if="props.flowProcdef">
 | 
				
			||||||
                <el-divider content-position="left">审批节点</el-divider>
 | 
					                <el-divider content-position="left">审批节点</el-divider>
 | 
				
			||||||
                <procdef-tasks :procdef-key="props.flowProcdefKey" />
 | 
					                <procdef-tasks :procdef="props.flowProcdef" />
 | 
				
			||||||
            </div>
 | 
					            </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            <template #footer>
 | 
					            <template #footer>
 | 
				
			||||||
@@ -32,7 +32,7 @@ const props = withDefaults(defineProps<CmdExecProps>(), {});
 | 
				
			|||||||
const remarkInputRef = ref<InputInstance>();
 | 
					const remarkInputRef = ref<InputInstance>();
 | 
				
			||||||
const state = reactive({
 | 
					const state = reactive({
 | 
				
			||||||
    dialogVisible: false,
 | 
					    dialogVisible: false,
 | 
				
			||||||
    flowProcdefKey: '' as any,
 | 
					    flowProcdef: null as any,
 | 
				
			||||||
    cmdStr: '',
 | 
					    cmdStr: '',
 | 
				
			||||||
    remark: '',
 | 
					    remark: '',
 | 
				
			||||||
    btnLoading: false,
 | 
					    btnLoading: false,
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -13,9 +13,9 @@ export class RedisInst {
 | 
				
			|||||||
    db: number;
 | 
					    db: number;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * 流程定义key,若存在则需要审批执行
 | 
					     * 流程定义,若存在则需要审批执行
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    flowProcdefKey: string;
 | 
					    flowProcdef: any;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * 执行命令
 | 
					     * 执行命令
 | 
				
			||||||
@@ -24,11 +24,11 @@ export class RedisInst {
 | 
				
			|||||||
     */
 | 
					     */
 | 
				
			||||||
    async runCmd(cmd: any[]) {
 | 
					    async runCmd(cmd: any[]) {
 | 
				
			||||||
        // 工单流程定义存在,并且为写入命令时,弹窗输入工单相关信息并提交
 | 
					        // 工单流程定义存在,并且为写入命令时,弹窗输入工单相关信息并提交
 | 
				
			||||||
        if (this.flowProcdefKey && writeCmd[cmd[0].toUpperCase()]) {
 | 
					        if (this.flowProcdef && writeCmd[cmd[0].toUpperCase()]) {
 | 
				
			||||||
            showCmdExecBox({
 | 
					            showCmdExecBox({
 | 
				
			||||||
                id: this.id,
 | 
					                id: this.id,
 | 
				
			||||||
                db: this.db,
 | 
					                db: this.db,
 | 
				
			||||||
                flowProcdefKey: this.flowProcdefKey,
 | 
					                flowProcdef: this.flowProcdef,
 | 
				
			||||||
                cmd,
 | 
					                cmd,
 | 
				
			||||||
            });
 | 
					            });
 | 
				
			||||||
            // 报错,阻止后续继续执行
 | 
					            // 报错,阻止后续继续执行
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -11,6 +11,14 @@
 | 
				
			|||||||
                <el-button v-auth="'authcert:save'" type="primary" icon="plus" @click="edit(false)">添加</el-button>
 | 
					                <el-button v-auth="'authcert:save'" type="primary" icon="plus" @click="edit(false)">添加</el-button>
 | 
				
			||||||
            </template>
 | 
					            </template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            <template #resourceCode="{ data }">
 | 
				
			||||||
 | 
					                <SvgIcon
 | 
				
			||||||
 | 
					                    :name="EnumValue.getEnumByValue(TagResourceTypeEnum, data.resourceType)?.extra.icon"
 | 
				
			||||||
 | 
					                    :color="EnumValue.getEnumByValue(TagResourceTypeEnum, data.resourceType)?.extra.iconColor"
 | 
				
			||||||
 | 
					                />
 | 
				
			||||||
 | 
					                {{ data.resourceCode }}
 | 
				
			||||||
 | 
					            </template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            <template #action="{ data }">
 | 
					            <template #action="{ data }">
 | 
				
			||||||
                <el-button v-auth="'authcert:save'" @click="edit(data)" type="primary" link>编辑</el-button>
 | 
					                <el-button v-auth="'authcert:save'" @click="edit(data)" type="primary" link>编辑</el-button>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -41,6 +49,7 @@ import { SearchItem } from '@/components/SearchForm';
 | 
				
			|||||||
import { AuthCertCiphertextTypeEnum, AuthCertTypeEnum } from './enums';
 | 
					import { AuthCertCiphertextTypeEnum, AuthCertTypeEnum } from './enums';
 | 
				
			||||||
import { ResourceTypeEnum, TagResourceTypeEnum } from '@/common/commonEnum';
 | 
					import { ResourceTypeEnum, TagResourceTypeEnum } from '@/common/commonEnum';
 | 
				
			||||||
import ResourceAuthCertEdit from '../component/ResourceAuthCertEdit.vue';
 | 
					import ResourceAuthCertEdit from '../component/ResourceAuthCertEdit.vue';
 | 
				
			||||||
 | 
					import EnumValue from '@/common/Enum';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const pageTableRef: Ref<any> = ref(null);
 | 
					const pageTableRef: Ref<any> = ref(null);
 | 
				
			||||||
const state = reactive({
 | 
					const state = reactive({
 | 
				
			||||||
@@ -50,6 +59,7 @@ const state = reactive({
 | 
				
			|||||||
        name: null,
 | 
					        name: null,
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    searchItems: [
 | 
					    searchItems: [
 | 
				
			||||||
 | 
					        SearchItem.input('resourceCode', '资源编号'),
 | 
				
			||||||
        SearchItem.input('name', '凭证名称'),
 | 
					        SearchItem.input('name', '凭证名称'),
 | 
				
			||||||
        SearchItem.select('resourceType', '资源类型').withEnum(ResourceTypeEnum),
 | 
					        SearchItem.select('resourceType', '资源类型').withEnum(ResourceTypeEnum),
 | 
				
			||||||
        SearchItem.select('type', '凭证类型').withEnum(AuthCertTypeEnum),
 | 
					        SearchItem.select('type', '凭证类型').withEnum(AuthCertTypeEnum),
 | 
				
			||||||
@@ -60,8 +70,7 @@ const state = reactive({
 | 
				
			|||||||
        TableColumn.new('type', '凭证类型').typeTag(AuthCertTypeEnum),
 | 
					        TableColumn.new('type', '凭证类型').typeTag(AuthCertTypeEnum),
 | 
				
			||||||
        TableColumn.new('username', '用户名'),
 | 
					        TableColumn.new('username', '用户名'),
 | 
				
			||||||
        TableColumn.new('ciphertextType', '密文类型').typeTag(AuthCertCiphertextTypeEnum),
 | 
					        TableColumn.new('ciphertextType', '密文类型').typeTag(AuthCertCiphertextTypeEnum),
 | 
				
			||||||
        TableColumn.new('resourceType', '资源类型').typeTag(TagResourceTypeEnum),
 | 
					        TableColumn.new('resourceCode', '资源编号').isSlot().setAddWidth(30),
 | 
				
			||||||
        TableColumn.new('resourceCode', '资源编号'),
 | 
					 | 
				
			||||||
        TableColumn.new('remark', '备注'),
 | 
					        TableColumn.new('remark', '备注'),
 | 
				
			||||||
        TableColumn.new('creator', '创建人'),
 | 
					        TableColumn.new('creator', '创建人'),
 | 
				
			||||||
        TableColumn.new('createTime', '创建时间').isTime(),
 | 
					        TableColumn.new('createTime', '创建时间').isTime(),
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -75,20 +75,16 @@
 | 
				
			|||||||
                                <el-descriptions-item label="code">{{ currentTag.code }}</el-descriptions-item>
 | 
					                                <el-descriptions-item label="code">{{ currentTag.code }}</el-descriptions-item>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                                <el-descriptions-item label="路径" :span="2">
 | 
					                                <el-descriptions-item label="路径" :span="2">
 | 
				
			||||||
                                    <span v-for="item in parseTagPath(currentTag.codePath)" :key="item.code">
 | 
					                                    <TagCodePath :path="currentTag.codePath" />
 | 
				
			||||||
                                        <SvgIcon :name="EnumValue.getEnumByValue(TagResourceTypeEnum, item.type)?.extra.icon" class="mr2" />
 | 
					 | 
				
			||||||
                                        <span> {{ item.code }}</span>
 | 
					 | 
				
			||||||
                                        <SvgIcon v-if="!item.isEnd" class="mr5 ml5" name="arrow-right" />
 | 
					 | 
				
			||||||
                                    </span>
 | 
					 | 
				
			||||||
                                </el-descriptions-item>
 | 
					                                </el-descriptions-item>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                                <el-descriptions-item label="名称">{{ currentTag.name }}</el-descriptions-item>
 | 
					                                <el-descriptions-item label="名称">{{ currentTag.name }}</el-descriptions-item>
 | 
				
			||||||
                                <el-descriptions-item label="备注">{{ currentTag.remark }}</el-descriptions-item>
 | 
					                                <el-descriptions-item label="备注">{{ currentTag.remark }}</el-descriptions-item>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                                <el-descriptions-item label="创建者">{{ currentTag.creator }}</el-descriptions-item>
 | 
					                                <el-descriptions-item label="创建者">{{ currentTag.creator }}</el-descriptions-item>
 | 
				
			||||||
                                <el-descriptions-item label="创建时间">{{ dateFormat(currentTag.createTime) }}</el-descriptions-item>
 | 
					                                <el-descriptions-item label="创建时间">{{ formatDate(currentTag.createTime) }}</el-descriptions-item>
 | 
				
			||||||
                                <el-descriptions-item label="修改者">{{ currentTag.modifier }}</el-descriptions-item>
 | 
					                                <el-descriptions-item label="修改者">{{ currentTag.modifier }}</el-descriptions-item>
 | 
				
			||||||
                                <el-descriptions-item label="更新时间">{{ dateFormat(currentTag.updateTime) }}</el-descriptions-item>
 | 
					                                <el-descriptions-item label="更新时间">{{ formatDate(currentTag.updateTime) }}</el-descriptions-item>
 | 
				
			||||||
                            </el-descriptions>
 | 
					                            </el-descriptions>
 | 
				
			||||||
                        </el-tab-pane>
 | 
					                        </el-tab-pane>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -149,20 +145,23 @@
 | 
				
			|||||||
</template>
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<script lang="ts" setup>
 | 
					<script lang="ts" setup>
 | 
				
			||||||
import { toRefs, ref, watch, reactive, onMounted, Ref } from 'vue';
 | 
					import { toRefs, ref, watch, reactive, onMounted, Ref, defineAsyncComponent } from 'vue';
 | 
				
			||||||
import { ElMessage, ElMessageBox } from 'element-plus';
 | 
					import { ElMessage, ElMessageBox } from 'element-plus';
 | 
				
			||||||
import { tagApi } from './api';
 | 
					import { tagApi } from './api';
 | 
				
			||||||
import { dateFormat } from '@/common/utils/date';
 | 
					import { formatDate } from '@/common/utils/format';
 | 
				
			||||||
import { Contextmenu, ContextmenuItem } from '@/components/contextmenu/index';
 | 
					import { Contextmenu, ContextmenuItem } from '@/components/contextmenu/index';
 | 
				
			||||||
import { useUserInfo } from '@/store/userInfo';
 | 
					import { useUserInfo } from '@/store/userInfo';
 | 
				
			||||||
import { Splitpanes, Pane } from 'splitpanes';
 | 
					import { Splitpanes, Pane } from 'splitpanes';
 | 
				
			||||||
import MachineList from '../machine/MachineList.vue';
 | 
					 | 
				
			||||||
import RedisList from '../redis/RedisList.vue';
 | 
					 | 
				
			||||||
import MongoList from '../mongo/MongoList.vue';
 | 
					 | 
				
			||||||
import { TagResourceTypeEnum } from '@/common/commonEnum';
 | 
					import { TagResourceTypeEnum } from '@/common/commonEnum';
 | 
				
			||||||
import EnumTag from '@/components/enumtag/EnumTag.vue';
 | 
					import EnumTag from '@/components/enumtag/EnumTag.vue';
 | 
				
			||||||
import EnumValue from '@/common/Enum';
 | 
					import EnumValue from '@/common/Enum';
 | 
				
			||||||
import InstanceList from '../db/InstanceList.vue';
 | 
					import TagCodePath from '../component/TagCodePath.vue';
 | 
				
			||||||
 | 
					import { isPrefixSubsequence } from '@/common/utils/string';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const MachineList = defineAsyncComponent(() => import('../machine/MachineList.vue'));
 | 
				
			||||||
 | 
					const InstanceList = defineAsyncComponent(() => import('../db/InstanceList.vue'));
 | 
				
			||||||
 | 
					const RedisList = defineAsyncComponent(() => import('../redis/RedisList.vue'));
 | 
				
			||||||
 | 
					const MongoList = defineAsyncComponent(() => import('../mongo/MongoList.vue'));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
interface Tree {
 | 
					interface Tree {
 | 
				
			||||||
    id: number;
 | 
					    id: number;
 | 
				
			||||||
@@ -198,6 +197,9 @@ const contextmenuAdd = new ContextmenuItem('addTag', '添加子标签')
 | 
				
			|||||||
const contextmenuEdit = new ContextmenuItem('edit', '编辑')
 | 
					const contextmenuEdit = new ContextmenuItem('edit', '编辑')
 | 
				
			||||||
    .withIcon('edit')
 | 
					    .withIcon('edit')
 | 
				
			||||||
    .withPermission('tag:save')
 | 
					    .withPermission('tag:save')
 | 
				
			||||||
 | 
					    .withHideFunc((data: any) => {
 | 
				
			||||||
 | 
					        return data.type != TagResourceTypeEnum.Tag.value;
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
    .withOnClick((data: any) => showEditTagDialog(data));
 | 
					    .withOnClick((data: any) => showEditTagDialog(data));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const contextmenuDel = new ContextmenuItem('delete', '删除')
 | 
					const contextmenuDel = new ContextmenuItem('delete', '删除')
 | 
				
			||||||
@@ -345,38 +347,6 @@ const handleDrop = async (draggingNode: any, dropNode: any) => {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const parseTagPath = (tagPath: string) => {
 | 
					 | 
				
			||||||
    if (!tagPath) {
 | 
					 | 
				
			||||||
        return [];
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    const res = [] as any;
 | 
					 | 
				
			||||||
    const codes = tagPath.split('/');
 | 
					 | 
				
			||||||
    for (let code of codes) {
 | 
					 | 
				
			||||||
        const typeAndCode = code.split('|');
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if (typeAndCode.length == 1) {
 | 
					 | 
				
			||||||
            const tagCode = typeAndCode[0];
 | 
					 | 
				
			||||||
            if (!tagCode) {
 | 
					 | 
				
			||||||
                continue;
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            res.push({
 | 
					 | 
				
			||||||
                type: TagResourceTypeEnum.Tag.value,
 | 
					 | 
				
			||||||
                code: typeAndCode[0],
 | 
					 | 
				
			||||||
            });
 | 
					 | 
				
			||||||
            continue;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        res.push({
 | 
					 | 
				
			||||||
            type: typeAndCode[0],
 | 
					 | 
				
			||||||
            code: typeAndCode[1],
 | 
					 | 
				
			||||||
        });
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    res[res.length - 1].isEnd = true;
 | 
					 | 
				
			||||||
    return res;
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const tabChange = () => {
 | 
					const tabChange = () => {
 | 
				
			||||||
    setNowTabData();
 | 
					    setNowTabData();
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
@@ -402,8 +372,7 @@ const setNowTabData = () => {
 | 
				
			|||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const filterNode = (value: string, data: Tree) => {
 | 
					const filterNode = (value: string, data: Tree) => {
 | 
				
			||||||
    if (!value) return true;
 | 
					    return !value || isPrefixSubsequence(value, data.codePath) || isPrefixSubsequence(value, data.name);
 | 
				
			||||||
    return data.codePath.toLowerCase().includes(value) || data.name.includes(value);
 | 
					 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const search = async () => {
 | 
					const search = async () => {
 | 
				
			||||||
@@ -411,6 +380,11 @@ const search = async () => {
 | 
				
			|||||||
    state.data = res;
 | 
					    state.data = res;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const getDetail = async (id: number) => {
 | 
				
			||||||
 | 
					    const tags = await tagApi.listByQuery.request({ id });
 | 
				
			||||||
 | 
					    return tags?.[0];
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// 树节点右击事件
 | 
					// 树节点右击事件
 | 
				
			||||||
const nodeContextmenu = (event: any, data: any) => {
 | 
					const nodeContextmenu = (event: any, data: any) => {
 | 
				
			||||||
    const { clientX, clientY } = event;
 | 
					    const { clientX, clientY } = event;
 | 
				
			||||||
@@ -419,8 +393,8 @@ const nodeContextmenu = (event: any, data: any) => {
 | 
				
			|||||||
    contextmenuRef.value.openContextmenu(data);
 | 
					    contextmenuRef.value.openContextmenu(data);
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const treeNodeClick = (data: any) => {
 | 
					const treeNodeClick = async (data: any) => {
 | 
				
			||||||
    state.currentTag = data;
 | 
					    state.currentTag = await getDetail(data.id);
 | 
				
			||||||
    // 关闭可能存在的右击菜单
 | 
					    // 关闭可能存在的右击菜单
 | 
				
			||||||
    contextmenuRef.value.closeContextmenu();
 | 
					    contextmenuRef.value.closeContextmenu();
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -14,13 +14,12 @@
 | 
				
			|||||||
                <el-button v-auth="'team:del'" :disabled="selectionData.length < 1" @click="deleteTeam()" type="danger" icon="delete">删除</el-button>
 | 
					                <el-button v-auth="'team:del'" :disabled="selectionData.length < 1" @click="deleteTeam()" type="danger" icon="delete">删除</el-button>
 | 
				
			||||||
            </template>
 | 
					            </template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            <template #tagPath="{ data }">
 | 
					            <template #tags="{ data }">
 | 
				
			||||||
                <tag-info :tag-path="data.tagPath" />
 | 
					                <TagCodePath :path="data.tags?.map((tag: any) => tag.codePath)" />
 | 
				
			||||||
                <span class="ml5">
 | 
					 | 
				
			||||||
                    {{ data.tagPath }}
 | 
					 | 
				
			||||||
                </span>
 | 
					 | 
				
			||||||
            </template>
 | 
					            </template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            <template #validityDate="{ data }"> {{ data.validityStartDate }} ~ {{ data.validityEndDate }} </template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            <template #action="{ data }">
 | 
					            <template #action="{ data }">
 | 
				
			||||||
                <el-button @click.prevent="showMembers(data)" link type="primary">成员</el-button>
 | 
					                <el-button @click.prevent="showMembers(data)" link type="primary">成员</el-button>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -39,54 +38,30 @@
 | 
				
			|||||||
                <DrawerHeader :header="addTeamDialog.form.id ? '编辑团队' : '添加团队'" :back="cancelSaveTeam" />
 | 
					                <DrawerHeader :header="addTeamDialog.form.id ? '编辑团队' : '添加团队'" :back="cancelSaveTeam" />
 | 
				
			||||||
            </template>
 | 
					            </template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            <el-form ref="teamForm" :model="addTeamDialog.form" label-width="auto">
 | 
					            <el-form ref="teamForm" :model="addTeamDialog.form" :rules="teamFormRules" label-width="auto">
 | 
				
			||||||
                <el-form-item prop="name" label="团队名" required>
 | 
					                <el-form-item prop="name" label="团队名" required>
 | 
				
			||||||
                    <el-input :disabled="addTeamDialog.form.id" v-model="addTeamDialog.form.name" auto-complete="off"></el-input>
 | 
					                    <el-input :disabled="addTeamDialog.form.id" v-model="addTeamDialog.form.name" auto-complete="off"></el-input>
 | 
				
			||||||
                </el-form-item>
 | 
					                </el-form-item>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                <el-form-item prop="validityDate" label="生效时间" required>
 | 
				
			||||||
 | 
					                    <el-date-picker
 | 
				
			||||||
 | 
					                        v-model="addTeamDialog.form.validityDate"
 | 
				
			||||||
 | 
					                        type="datetimerange"
 | 
				
			||||||
 | 
					                        start-placeholder="生效开始时间"
 | 
				
			||||||
 | 
					                        end-placeholder="生效结束时间"
 | 
				
			||||||
 | 
					                        format="YYYY-MM-DD HH:mm:ss"
 | 
				
			||||||
 | 
					                        value-format="YYYY-MM-DD HH:mm:ss"
 | 
				
			||||||
 | 
					                        date-format="YYYY-MM-DD"
 | 
				
			||||||
 | 
					                        time-format="HH:mm:ss"
 | 
				
			||||||
 | 
					                    />
 | 
				
			||||||
 | 
					                </el-form-item>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                <el-form-item label="备注">
 | 
					                <el-form-item label="备注">
 | 
				
			||||||
                    <el-input v-model="addTeamDialog.form.remark" auto-complete="off"></el-input>
 | 
					                    <el-input v-model="addTeamDialog.form.remark" auto-complete="off"></el-input>
 | 
				
			||||||
                </el-form-item>
 | 
					                </el-form-item>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                <el-form-item prop="tag" label="标签">
 | 
					                <el-form-item prop="tag" label="标签">
 | 
				
			||||||
                    <div class="w100" style="border: 1px solid var(--el-border-color)">
 | 
					                    <TagTreeCheck height="calc(100vh - 390px)" v-model="state.addTeamDialog.form.codePaths" :tag-type="0" />
 | 
				
			||||||
                        <el-input v-model="filterTag" clearable placeholder="输入关键字过滤" size="small" />
 | 
					 | 
				
			||||||
                        <el-scrollbar style="height: calc(100vh - 330px)">
 | 
					 | 
				
			||||||
                            <el-tree
 | 
					 | 
				
			||||||
                                ref="tagTreeRef"
 | 
					 | 
				
			||||||
                                style="width: 100%"
 | 
					 | 
				
			||||||
                                :data="state.tags"
 | 
					 | 
				
			||||||
                                :default-expanded-keys="state.addTeamDialog.form.tags"
 | 
					 | 
				
			||||||
                                :default-checked-keys="state.addTeamDialog.form.tags"
 | 
					 | 
				
			||||||
                                multiple
 | 
					 | 
				
			||||||
                                :render-after-expand="true"
 | 
					 | 
				
			||||||
                                show-checkbox
 | 
					 | 
				
			||||||
                                check-strictly
 | 
					 | 
				
			||||||
                                node-key="id"
 | 
					 | 
				
			||||||
                                :props="{
 | 
					 | 
				
			||||||
                                    value: 'id',
 | 
					 | 
				
			||||||
                                    label: 'codePath',
 | 
					 | 
				
			||||||
                                    children: 'children',
 | 
					 | 
				
			||||||
                                    disabled: 'disabled',
 | 
					 | 
				
			||||||
                                }"
 | 
					 | 
				
			||||||
                                @check="tagTreeNodeCheck"
 | 
					 | 
				
			||||||
                                :filter-node-method="filterNode"
 | 
					 | 
				
			||||||
                            >
 | 
					 | 
				
			||||||
                                <template #default="{ data }">
 | 
					 | 
				
			||||||
                                    <span class="custom-tree-node">
 | 
					 | 
				
			||||||
                                        <SvgIcon :name="EnumValue.getEnumByValue(TagResourceTypeEnum, data.type)?.extra.icon" />
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                                        <span class="font13 ml5">
 | 
					 | 
				
			||||||
                                            {{ data.code }}
 | 
					 | 
				
			||||||
                                            <span style="color: #3c8dbc">【</span>
 | 
					 | 
				
			||||||
                                            {{ data.name }}
 | 
					 | 
				
			||||||
                                            <span style="color: #3c8dbc">】</span>
 | 
					 | 
				
			||||||
                                            <el-tag v-if="data.children !== null" size="small">{{ data.children.length }} </el-tag>
 | 
					 | 
				
			||||||
                                        </span>
 | 
					 | 
				
			||||||
                                    </span>
 | 
					 | 
				
			||||||
                                </template>
 | 
					 | 
				
			||||||
                            </el-tree>
 | 
					 | 
				
			||||||
                        </el-scrollbar>
 | 
					 | 
				
			||||||
                    </div>
 | 
					 | 
				
			||||||
                </el-form-item>
 | 
					                </el-form-item>
 | 
				
			||||||
            </el-form>
 | 
					            </el-form>
 | 
				
			||||||
            <template #footer>
 | 
					            <template #footer>
 | 
				
			||||||
@@ -131,7 +106,7 @@
 | 
				
			|||||||
</template>
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<script lang="ts" setup>
 | 
					<script lang="ts" setup>
 | 
				
			||||||
import { ref, toRefs, reactive, onMounted, Ref, watch } from 'vue';
 | 
					import { ref, toRefs, reactive, onMounted, Ref } from 'vue';
 | 
				
			||||||
import { tagApi } from './api';
 | 
					import { tagApi } from './api';
 | 
				
			||||||
import { ElMessage, ElMessageBox } from 'element-plus';
 | 
					import { ElMessage, ElMessageBox } from 'element-plus';
 | 
				
			||||||
import { notBlank } from '@/common/assert';
 | 
					import { notBlank } from '@/common/assert';
 | 
				
			||||||
@@ -139,19 +114,37 @@ import PageTable from '@/components/pagetable/PageTable.vue';
 | 
				
			|||||||
import { TableColumn } from '@/components/pagetable';
 | 
					import { TableColumn } from '@/components/pagetable';
 | 
				
			||||||
import { SearchItem } from '@/components/SearchForm';
 | 
					import { SearchItem } from '@/components/SearchForm';
 | 
				
			||||||
import AccountSelectFormItem from '@/views/system/account/components/AccountSelectFormItem.vue';
 | 
					import AccountSelectFormItem from '@/views/system/account/components/AccountSelectFormItem.vue';
 | 
				
			||||||
import { TagResourceTypeEnum } from '@/common/commonEnum';
 | 
					 | 
				
			||||||
import EnumValue from '@/common/Enum';
 | 
					 | 
				
			||||||
import DrawerHeader from '@/components/drawer-header/DrawerHeader.vue';
 | 
					import DrawerHeader from '@/components/drawer-header/DrawerHeader.vue';
 | 
				
			||||||
 | 
					import TagTreeCheck from '../component/TagTreeCheck.vue';
 | 
				
			||||||
 | 
					import TagCodePath from '../component/TagCodePath.vue';
 | 
				
			||||||
 | 
					import { formatDate } from '@/common/utils/format';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const teamForm: any = ref(null);
 | 
					const teamForm: any = ref(null);
 | 
				
			||||||
const tagTreeRef: any = ref(null);
 | 
					 | 
				
			||||||
const pageTableRef: Ref<any> = ref(null);
 | 
					const pageTableRef: Ref<any> = ref(null);
 | 
				
			||||||
const showMemPageTableRef: Ref<any> = ref(null);
 | 
					const showMemPageTableRef: Ref<any> = ref(null);
 | 
				
			||||||
const filterTag = ref('');
 | 
					
 | 
				
			||||||
 | 
					const teamFormRules = {
 | 
				
			||||||
 | 
					    name: [
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            required: true,
 | 
				
			||||||
 | 
					            message: '请输入团队名',
 | 
				
			||||||
 | 
					            trigger: ['change', 'blur'],
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					    ],
 | 
				
			||||||
 | 
					    validityDate: [
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            required: true,
 | 
				
			||||||
 | 
					            message: '请选择生效时间',
 | 
				
			||||||
 | 
					            trigger: ['change', 'blur'],
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					    ],
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const searchItems = [SearchItem.input('name', '团队名称')];
 | 
					const searchItems = [SearchItem.input('name', '团队名称')];
 | 
				
			||||||
const columns = [
 | 
					const columns = [
 | 
				
			||||||
    TableColumn.new('name', '团队名称'),
 | 
					    TableColumn.new('name', '团队名称'),
 | 
				
			||||||
 | 
					    TableColumn.new('tags', '分配标签').isSlot().setAddWidth(40),
 | 
				
			||||||
 | 
					    TableColumn.new('validityDate', '有效期').isSlot('validityDate').setMinWidth(310),
 | 
				
			||||||
    TableColumn.new('remark', '备注'),
 | 
					    TableColumn.new('remark', '备注'),
 | 
				
			||||||
    TableColumn.new('creator', '创建者'),
 | 
					    TableColumn.new('creator', '创建者'),
 | 
				
			||||||
    TableColumn.new('createTime', '创建时间').isTime(),
 | 
					    TableColumn.new('createTime', '创建时间').isTime(),
 | 
				
			||||||
@@ -162,10 +155,9 @@ const columns = [
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
const state = reactive({
 | 
					const state = reactive({
 | 
				
			||||||
    currentEditPermissions: false,
 | 
					    currentEditPermissions: false,
 | 
				
			||||||
    tags: [],
 | 
					 | 
				
			||||||
    addTeamDialog: {
 | 
					    addTeamDialog: {
 | 
				
			||||||
        visible: false,
 | 
					        visible: false,
 | 
				
			||||||
        form: { id: 0, name: '', remark: '', tags: [] },
 | 
					        form: { id: 0, name: '', validityDate: ['', ''], validityStartDate: '', validityEndDate: '', remark: '', codePaths: [] },
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    query: {
 | 
					    query: {
 | 
				
			||||||
        pageNum: 1,
 | 
					        pageNum: 1,
 | 
				
			||||||
@@ -211,50 +203,37 @@ const search = async () => {
 | 
				
			|||||||
    pageTableRef.value.search();
 | 
					    pageTableRef.value.search();
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
watch(filterTag, (val) => {
 | 
					 | 
				
			||||||
    tagTreeRef.value!.filter(val);
 | 
					 | 
				
			||||||
});
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const filterNode = (value: string, data: any) => {
 | 
					 | 
				
			||||||
    if (!value) {
 | 
					 | 
				
			||||||
        return true;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    return data.codePath.toLowerCase().includes(value) || data.name.includes(value);
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const showSaveTeamDialog = async (data: any) => {
 | 
					const showSaveTeamDialog = async (data: any) => {
 | 
				
			||||||
    state.tags = await tagApi.getTagTrees.request(null);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if (data) {
 | 
					    if (data) {
 | 
				
			||||||
        state.addTeamDialog.form.id = data.id;
 | 
					        state.addTeamDialog.form.id = data.id;
 | 
				
			||||||
        state.addTeamDialog.form.name = data.name;
 | 
					        state.addTeamDialog.form.name = data.name;
 | 
				
			||||||
 | 
					        state.addTeamDialog.form.validityDate = [data.validityStartDate, data.validityEndDate];
 | 
				
			||||||
        state.addTeamDialog.form.remark = data.remark;
 | 
					        state.addTeamDialog.form.remark = data.remark;
 | 
				
			||||||
        state.addTeamDialog.form.tags = await tagApi.getTeamTagIds.request({ teamId: data.id });
 | 
					        state.addTeamDialog.form.codePaths = data.tags?.map((tag: any) => tag.codePath);
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
        setTimeout(() => {
 | 
					        let end = new Date();
 | 
				
			||||||
            const checkedNodes = tagTreeRef.value.getCheckedNodes();
 | 
					        end.setFullYear(end.getFullYear() + 10);
 | 
				
			||||||
            console.log('check nodes: ', checkedNodes);
 | 
					        state.addTeamDialog.form.validityDate = [formatDate(new Date()), formatDate(end)];
 | 
				
			||||||
            // 禁用选中节点的所有父节点,不可选中
 | 
					 | 
				
			||||||
            for (let checkNodeData of checkedNodes) {
 | 
					 | 
				
			||||||
                disableParentNodes(tagTreeRef.value.getNode(checkNodeData.id).parent);
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }, 200);
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    state.addTeamDialog.visible = true;
 | 
					    state.addTeamDialog.visible = true;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const saveTeam = async () => {
 | 
					const saveTeam = async () => {
 | 
				
			||||||
    teamForm.value.validate(async (valid: any) => {
 | 
					    try {
 | 
				
			||||||
        if (valid) {
 | 
					        await teamForm.value.validate();
 | 
				
			||||||
 | 
					    } catch (e: any) {
 | 
				
			||||||
 | 
					        ElMessage.error('请正确填写信息');
 | 
				
			||||||
 | 
					        return false;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const form = state.addTeamDialog.form;
 | 
					    const form = state.addTeamDialog.form;
 | 
				
			||||||
            form.tags = tagTreeRef.value.getCheckedKeys(false);
 | 
					    form.validityStartDate = form.validityDate[0];
 | 
				
			||||||
 | 
					    form.validityEndDate = form.validityDate[1];
 | 
				
			||||||
    await tagApi.saveTeam.request(form);
 | 
					    await tagApi.saveTeam.request(form);
 | 
				
			||||||
    ElMessage.success('保存成功');
 | 
					    ElMessage.success('保存成功');
 | 
				
			||||||
    search();
 | 
					    search();
 | 
				
			||||||
    cancelSaveTeam();
 | 
					    cancelSaveTeam();
 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const cancelSaveTeam = () => {
 | 
					const cancelSaveTeam = () => {
 | 
				
			||||||
@@ -318,48 +297,5 @@ const cancelAddMember = () => {
 | 
				
			|||||||
    state.showMemDialog.memForm = {} as any;
 | 
					    state.showMemDialog.memForm = {} as any;
 | 
				
			||||||
    state.showMemDialog.addVisible = false;
 | 
					    state.showMemDialog.addVisible = false;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					 | 
				
			||||||
const tagTreeNodeCheck = (data: any) => {
 | 
					 | 
				
			||||||
    const node = tagTreeRef.value.getNode(data.id);
 | 
					 | 
				
			||||||
    console.log('check node: ', node);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if (node.checked) {
 | 
					 | 
				
			||||||
        // 如果选中了子节点,则需要将父节点全部取消选中,并禁用父节点
 | 
					 | 
				
			||||||
        unCheckParentNodes(node.parent);
 | 
					 | 
				
			||||||
        disableParentNodes(node.parent);
 | 
					 | 
				
			||||||
    } else {
 | 
					 | 
				
			||||||
        // 如果取消了选中,则需要根据条件恢复父节点的选中状态
 | 
					 | 
				
			||||||
        disableParentNodes(node.parent, false);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const unCheckParentNodes = (node: any) => {
 | 
					 | 
				
			||||||
    if (!node) {
 | 
					 | 
				
			||||||
        return;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    tagTreeRef.value.setChecked(node, false, false);
 | 
					 | 
				
			||||||
    unCheckParentNodes(node.parent);
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * 禁用该节点以及所有父节点
 | 
					 | 
				
			||||||
 * @param node 节点
 | 
					 | 
				
			||||||
 * @param disable 是否禁用
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
const disableParentNodes = (node: any, disable = true) => {
 | 
					 | 
				
			||||||
    if (!node) {
 | 
					 | 
				
			||||||
        return;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    if (!disable) {
 | 
					 | 
				
			||||||
        // 恢复为非禁用状态时,若同层级存在一个选中状态或者禁用状态,则继续禁用 不恢复非禁用状态。
 | 
					 | 
				
			||||||
        for (let oneLevelNodes of node.childNodes) {
 | 
					 | 
				
			||||||
            if (oneLevelNodes.checked || oneLevelNodes.data.disabled) {
 | 
					 | 
				
			||||||
                return;
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    node.data.disabled = disable;
 | 
					 | 
				
			||||||
    disableParentNodes(node.parent, disable);
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
</script>
 | 
					</script>
 | 
				
			||||||
<style lang="scss" scoped></style>
 | 
					<style lang="scss" scoped></style>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -9,6 +9,7 @@ export const tagApi = {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    getResourceTagPaths: Api.newGet('/tag-trees/resources/{resourceType}/tag-paths'),
 | 
					    getResourceTagPaths: Api.newGet('/tag-trees/resources/{resourceType}/tag-paths'),
 | 
				
			||||||
    countTagResource: Api.newGet('/tag-trees/resources/count'),
 | 
					    countTagResource: Api.newGet('/tag-trees/resources/count'),
 | 
				
			||||||
 | 
					    getRelateTagIds: Api.newGet('/tag-trees/relate/{relateType}/{relateId}'),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    getTeams: Api.newGet('/teams'),
 | 
					    getTeams: Api.newGet('/teams'),
 | 
				
			||||||
    saveTeam: Api.newPost('/teams'),
 | 
					    saveTeam: Api.newPost('/teams'),
 | 
				
			||||||
@@ -17,8 +18,6 @@ export const tagApi = {
 | 
				
			|||||||
    getTeamMem: Api.newGet('/teams/{teamId}/members'),
 | 
					    getTeamMem: Api.newGet('/teams/{teamId}/members'),
 | 
				
			||||||
    saveTeamMem: Api.newPost('/teams/{teamId}/members'),
 | 
					    saveTeamMem: Api.newPost('/teams/{teamId}/members'),
 | 
				
			||||||
    delTeamMem: Api.newDelete('/teams/{teamId}/members/{accountId}'),
 | 
					    delTeamMem: Api.newDelete('/teams/{teamId}/members/{accountId}'),
 | 
				
			||||||
 | 
					 | 
				
			||||||
    getTeamTagIds: Api.newGet('/teams/{teamId}/tags'),
 | 
					 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const resourceAuthCertApi = {
 | 
					export const resourceAuthCertApi = {
 | 
				
			||||||
@@ -27,3 +26,7 @@ export const resourceAuthCertApi = {
 | 
				
			|||||||
    save: Api.newPost('/auth-certs'),
 | 
					    save: Api.newPost('/auth-certs'),
 | 
				
			||||||
    delete: Api.newDelete('/auth-certs/{id}'),
 | 
					    delete: Api.newDelete('/auth-certs/{id}'),
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const resourceOpLogApi = {
 | 
				
			||||||
 | 
					    getAccountResourceOpLogs: Api.newGet('/resource-op-logs/account'),
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -14,3 +14,7 @@ export const AuthCertCiphertextTypeEnum = {
 | 
				
			|||||||
    PrivateKey: EnumValue.of(2, '秘钥').tagTypeSuccess(),
 | 
					    PrivateKey: EnumValue.of(2, '秘钥').tagTypeSuccess(),
 | 
				
			||||||
    Public: EnumValue.of(-1, '公共凭证').tagTypeSuccess(),
 | 
					    Public: EnumValue.of(-1, '公共凭证').tagTypeSuccess(),
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const TagTreeRelateTypeEnum = {
 | 
				
			||||||
 | 
					    Team: EnumValue.of(1, '团队'),
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,112 +1,6 @@
 | 
				
			|||||||
<template>
 | 
					<template>
 | 
				
			||||||
    <div class="personal">
 | 
					    <div class="personal">
 | 
				
			||||||
        <el-row>
 | 
					        <el-row>
 | 
				
			||||||
            <!-- 个人信息 -->
 | 
					 | 
				
			||||||
            <el-col :xs="24" :sm="16">
 | 
					 | 
				
			||||||
                <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">
 | 
					 | 
				
			||||||
                                <img :src="userInfo.photo" />
 | 
					 | 
				
			||||||
                            </el-upload>
 | 
					 | 
				
			||||||
                        </div>
 | 
					 | 
				
			||||||
                        <div class="personal-user-right">
 | 
					 | 
				
			||||||
                            <el-row>
 | 
					 | 
				
			||||||
                                <el-col :span="24" class="personal-title mb18"
 | 
					 | 
				
			||||||
                                    >{{ currentTime }},{{ userInfo.name }},生活变的再糟糕,也不妨碍我变得更好!
 | 
					 | 
				
			||||||
                                </el-col>
 | 
					 | 
				
			||||||
                                <el-col :span="24">
 | 
					 | 
				
			||||||
                                    <el-row>
 | 
					 | 
				
			||||||
                                        <el-col :xs="24" :sm="12" class="personal-item mb6">
 | 
					 | 
				
			||||||
                                            <div class="personal-item-label">用户名:</div>
 | 
					 | 
				
			||||||
                                            <div class="personal-item-value">{{ userInfo.username }}</div>
 | 
					 | 
				
			||||||
                                        </el-col>
 | 
					 | 
				
			||||||
                                        <el-col :xs="24" :sm="12" class="personal-item mb6">
 | 
					 | 
				
			||||||
                                            <div class="personal-item-label">角色:</div>
 | 
					 | 
				
			||||||
                                            <div class="personal-item-value">{{ roleInfo }}</div>
 | 
					 | 
				
			||||||
                                        </el-col>
 | 
					 | 
				
			||||||
                                    </el-row>
 | 
					 | 
				
			||||||
                                </el-col>
 | 
					 | 
				
			||||||
                                <el-col :span="24">
 | 
					 | 
				
			||||||
                                    <el-row>
 | 
					 | 
				
			||||||
                                        <el-col :xs="24" :sm="12" class="personal-item mb6">
 | 
					 | 
				
			||||||
                                            <div class="personal-item-label">上次登录IP:</div>
 | 
					 | 
				
			||||||
                                            <div class="personal-item-value">{{ userInfo.lastLoginIp }}</div>
 | 
					 | 
				
			||||||
                                        </el-col>
 | 
					 | 
				
			||||||
                                        <el-col :xs="24" :sm="12" class="personal-item mb6">
 | 
					 | 
				
			||||||
                                            <div class="personal-item-label">上次登录时间:</div>
 | 
					 | 
				
			||||||
                                            <div class="personal-item-value">{{ dateFormat(userInfo.lastLoginTime) }}</div>
 | 
					 | 
				
			||||||
                                        </el-col>
 | 
					 | 
				
			||||||
                                    </el-row>
 | 
					 | 
				
			||||||
                                </el-col>
 | 
					 | 
				
			||||||
                            </el-row>
 | 
					 | 
				
			||||||
                        </div>
 | 
					 | 
				
			||||||
                    </div>
 | 
					 | 
				
			||||||
                </el-card>
 | 
					 | 
				
			||||||
            </el-col>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            <!-- 消息通知 -->
 | 
					 | 
				
			||||||
            <el-col :xs="24" :sm="8" class="pl15 personal-info">
 | 
					 | 
				
			||||||
                <el-card shadow="hover">
 | 
					 | 
				
			||||||
                    <template #header>
 | 
					 | 
				
			||||||
                        <span>消息通知</span>
 | 
					 | 
				
			||||||
                        <span @click="showMsgs" class="personal-info-more">更多</span>
 | 
					 | 
				
			||||||
                    </template>
 | 
					 | 
				
			||||||
                    <div class="personal-info-box">
 | 
					 | 
				
			||||||
                        <ul class="personal-info-ul">
 | 
					 | 
				
			||||||
                            <li v-for="(v, k) in msgDialog.msgs.list as any" :key="k" class="personal-info-li">
 | 
					 | 
				
			||||||
                                <a class="personal-info-li-title">{{ `[${getMsgTypeDesc(v.type)}] ${v.msg}` }}</a>
 | 
					 | 
				
			||||||
                            </li>
 | 
					 | 
				
			||||||
                        </ul>
 | 
					 | 
				
			||||||
                    </div>
 | 
					 | 
				
			||||||
                </el-card>
 | 
					 | 
				
			||||||
            </el-col>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            <el-dialog width="900px" title="消息" v-model="msgDialog.visible">
 | 
					 | 
				
			||||||
                <el-table border :data="msgDialog.msgs.list" size="small">
 | 
					 | 
				
			||||||
                    <el-table-column property="type" label="类型" width="60">
 | 
					 | 
				
			||||||
                        <template #default="scope">
 | 
					 | 
				
			||||||
                            {{ getMsgTypeDesc(scope.row.type) }}
 | 
					 | 
				
			||||||
                        </template>
 | 
					 | 
				
			||||||
                    </el-table-column>
 | 
					 | 
				
			||||||
                    <el-table-column property="msg" label="消息"></el-table-column>
 | 
					 | 
				
			||||||
                    <el-table-column property="createTime" label="时间" width="150">
 | 
					 | 
				
			||||||
                        <template #default="scope">
 | 
					 | 
				
			||||||
                            {{ dateFormat(scope.row.createTime) }}
 | 
					 | 
				
			||||||
                        </template>
 | 
					 | 
				
			||||||
                    </el-table-column>
 | 
					 | 
				
			||||||
                </el-table>
 | 
					 | 
				
			||||||
                <el-row type="flex" class="mt5" justify="center">
 | 
					 | 
				
			||||||
                    <el-pagination
 | 
					 | 
				
			||||||
                        small
 | 
					 | 
				
			||||||
                        @current-change="getMsgs"
 | 
					 | 
				
			||||||
                        style="text-align: center"
 | 
					 | 
				
			||||||
                        background
 | 
					 | 
				
			||||||
                        layout="prev, pager, next, total, jumper"
 | 
					 | 
				
			||||||
                        :total="msgDialog.msgs.total"
 | 
					 | 
				
			||||||
                        v-model:current-page="msgDialog.query.pageNum"
 | 
					 | 
				
			||||||
                        :page-size="msgDialog.query.pageSize"
 | 
					 | 
				
			||||||
                    />
 | 
					 | 
				
			||||||
                </el-row>
 | 
					 | 
				
			||||||
            </el-dialog>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            <!-- 营销推荐 -->
 | 
					 | 
				
			||||||
            <!-- <el-col :span="24">
 | 
					 | 
				
			||||||
                <el-card shadow="hover" class="mt15" header="营销推荐">
 | 
					 | 
				
			||||||
                    <el-row :gutter="15" class="personal-recommend-row">
 | 
					 | 
				
			||||||
                        <el-col :sm="6" v-for="(v, k) in recommendList" :key="k" class="personal-recommend-col">
 | 
					 | 
				
			||||||
                            <div class="personal-recommend" :style="{ 'background-color': v.bg }">
 | 
					 | 
				
			||||||
                                <i :class="v.icon" :style="{ color: v.iconColor }"></i>
 | 
					 | 
				
			||||||
                                <div class="personal-recommend-auto">
 | 
					 | 
				
			||||||
                                    <div>{{ v.title }}</div>
 | 
					 | 
				
			||||||
                                    <div class="personal-recommend-msg">{{ v.msg }}</div>
 | 
					 | 
				
			||||||
                                </div>
 | 
					 | 
				
			||||||
                            </div>
 | 
					 | 
				
			||||||
                        </el-col>
 | 
					 | 
				
			||||||
                    </el-row>
 | 
					 | 
				
			||||||
                </el-card>
 | 
					 | 
				
			||||||
            </el-col> -->
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            <!-- 更新信息 -->
 | 
					            <!-- 更新信息 -->
 | 
				
			||||||
            <el-col :span="24">
 | 
					            <el-col :span="24">
 | 
				
			||||||
                <el-card shadow="hover" class="mt15 personal-edit" header="更新信息">
 | 
					                <el-card shadow="hover" class="mt15 personal-edit" header="更新信息">
 | 
				
			||||||
@@ -142,28 +36,6 @@
 | 
				
			|||||||
                            </div>
 | 
					                            </div>
 | 
				
			||||||
                        </div>
 | 
					                        </div>
 | 
				
			||||||
                    </span>
 | 
					                    </span>
 | 
				
			||||||
                    <!-- <div class="personal-edit-safe-box">
 | 
					 | 
				
			||||||
                        <div class="personal-edit-safe-item">
 | 
					 | 
				
			||||||
                            <div class="personal-edit-safe-item-left">
 | 
					 | 
				
			||||||
                                <div class="personal-edit-safe-item-left-label">密保手机</div>
 | 
					 | 
				
			||||||
                                <div class="personal-edit-safe-item-left-value">已绑定手机:132****4108</div>
 | 
					 | 
				
			||||||
                            </div>
 | 
					 | 
				
			||||||
                            <div class="personal-edit-safe-item-right">
 | 
					 | 
				
			||||||
                                <el-button type="text">立即修改</el-button>
 | 
					 | 
				
			||||||
                            </div>
 | 
					 | 
				
			||||||
                        </div>
 | 
					 | 
				
			||||||
                    </div>
 | 
					 | 
				
			||||||
                    <div class="personal-edit-safe-box">
 | 
					 | 
				
			||||||
                        <div class="personal-edit-safe-item">
 | 
					 | 
				
			||||||
                            <div class="personal-edit-safe-item-left">
 | 
					 | 
				
			||||||
                                <div class="personal-edit-safe-item-left-label">密保问题</div>
 | 
					 | 
				
			||||||
                                <div class="personal-edit-safe-item-left-value">已设置密保问题,账号安全大幅度提升</div>
 | 
					 | 
				
			||||||
                            </div>
 | 
					 | 
				
			||||||
                            <div class="personal-edit-safe-item-right">
 | 
					 | 
				
			||||||
                                <el-button type="text">立即设置</el-button>
 | 
					 | 
				
			||||||
                            </div>
 | 
					 | 
				
			||||||
                        </div>
 | 
					 | 
				
			||||||
                    </div> -->
 | 
					 | 
				
			||||||
                </el-card>
 | 
					                </el-card>
 | 
				
			||||||
            </el-col>
 | 
					            </el-col>
 | 
				
			||||||
        </el-row>
 | 
					        </el-row>
 | 
				
			||||||
@@ -171,33 +43,16 @@
 | 
				
			|||||||
</template>
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<script lang="ts" setup>
 | 
					<script lang="ts" setup>
 | 
				
			||||||
import { toRefs, reactive, computed, onMounted } from 'vue';
 | 
					import { toRefs, reactive, onMounted } from 'vue';
 | 
				
			||||||
import { ElMessage } from 'element-plus';
 | 
					import { ElMessage } from 'element-plus';
 | 
				
			||||||
import { formatAxis } from '@/common/utils/format';
 | 
					 | 
				
			||||||
import { personApi } from './api';
 | 
					import { personApi } from './api';
 | 
				
			||||||
import { dateFormat } from '@/common/utils/date';
 | 
					 | 
				
			||||||
import { storeToRefs } from 'pinia';
 | 
					 | 
				
			||||||
import { useUserInfo } from '@/store/userInfo';
 | 
					 | 
				
			||||||
import config from '@/common/config';
 | 
					import config from '@/common/config';
 | 
				
			||||||
import { joinClientParams } from '@/common/request';
 | 
					import { joinClientParams } from '@/common/request';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const { userInfo } = storeToRefs(useUserInfo());
 | 
					 | 
				
			||||||
const state = reactive({
 | 
					const state = reactive({
 | 
				
			||||||
    accountInfo: {
 | 
					    accountInfo: {
 | 
				
			||||||
        roles: [],
 | 
					        roles: [],
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    msgs: [],
 | 
					 | 
				
			||||||
    msgDialog: {
 | 
					 | 
				
			||||||
        visible: false,
 | 
					 | 
				
			||||||
        query: {
 | 
					 | 
				
			||||||
            pageSize: 10,
 | 
					 | 
				
			||||||
            pageNum: 1,
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
        msgs: {
 | 
					 | 
				
			||||||
            list: [],
 | 
					 | 
				
			||||||
            total: null,
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    recommendList: [],
 | 
					    recommendList: [],
 | 
				
			||||||
    accountForm: {
 | 
					    accountForm: {
 | 
				
			||||||
        password: '',
 | 
					        password: '',
 | 
				
			||||||
@@ -208,27 +63,10 @@ const state = reactive({
 | 
				
			|||||||
    },
 | 
					    },
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const { msgDialog, accountForm, authStatus } = toRefs(state);
 | 
					const { accountForm, authStatus } = toRefs(state);
 | 
				
			||||||
 | 
					 | 
				
			||||||
// 当前时间提示语
 | 
					 | 
				
			||||||
const currentTime = computed(() => {
 | 
					 | 
				
			||||||
    return formatAxis(new Date());
 | 
					 | 
				
			||||||
});
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const showMsgs = () => {
 | 
					 | 
				
			||||||
    state.msgDialog.visible = true;
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const roleInfo = computed(() => {
 | 
					 | 
				
			||||||
    if (state.accountInfo.roles.length == 0) {
 | 
					 | 
				
			||||||
        return '';
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    return state.accountInfo.roles.map((val: any) => val.name).join('、');
 | 
					 | 
				
			||||||
});
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
onMounted(async () => {
 | 
					onMounted(async () => {
 | 
				
			||||||
    getAccountInfo();
 | 
					    getAccountInfo();
 | 
				
			||||||
    getMsgs();
 | 
					 | 
				
			||||||
    state.authStatus = await personApi.authStatus.request();
 | 
					    state.authStatus = await personApi.authStatus.request();
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -277,162 +115,11 @@ const unbindOAuth2 = async () => {
 | 
				
			|||||||
    ElMessage.success('解绑成功');
 | 
					    ElMessage.success('解绑成功');
 | 
				
			||||||
    state.authStatus = await personApi.authStatus.request();
 | 
					    state.authStatus = await personApi.authStatus.request();
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					 | 
				
			||||||
const getMsgs = async () => {
 | 
					 | 
				
			||||||
    const res = await personApi.getMsgs.request(state.msgDialog.query);
 | 
					 | 
				
			||||||
    state.msgDialog.msgs = res;
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const getMsgTypeDesc = (type: number) => {
 | 
					 | 
				
			||||||
    if (type == 1) {
 | 
					 | 
				
			||||||
        return '登录';
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    if (type == 2) {
 | 
					 | 
				
			||||||
        return '通知';
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
</script>
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<style scoped lang="scss">
 | 
					<style scoped lang="scss">
 | 
				
			||||||
@import '../../theme/mixins/index.scss';
 | 
					@import '../../theme/mixins/index.scss';
 | 
				
			||||||
 | 
					 | 
				
			||||||
.personal {
 | 
					.personal {
 | 
				
			||||||
    .personal-user {
 | 
					 | 
				
			||||||
        height: 130px;
 | 
					 | 
				
			||||||
        display: flex;
 | 
					 | 
				
			||||||
        align-items: center;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        .personal-user-left {
 | 
					 | 
				
			||||||
            width: 100px;
 | 
					 | 
				
			||||||
            height: 130px;
 | 
					 | 
				
			||||||
            border-radius: 3px;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            ::v-deep(.el-upload) {
 | 
					 | 
				
			||||||
                height: 100%;
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            .personal-user-left-upload {
 | 
					 | 
				
			||||||
                img {
 | 
					 | 
				
			||||||
                    width: 100%;
 | 
					 | 
				
			||||||
                    height: 100%;
 | 
					 | 
				
			||||||
                    border-radius: 3px;
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                &:hover {
 | 
					 | 
				
			||||||
                    img {
 | 
					 | 
				
			||||||
                        animation: logoAnimation 0.3s ease-in-out;
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        .personal-user-right {
 | 
					 | 
				
			||||||
            flex: 1;
 | 
					 | 
				
			||||||
            padding: 0 15px;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            .personal-title {
 | 
					 | 
				
			||||||
                font-size: 18px;
 | 
					 | 
				
			||||||
                @include text-ellipsis(1);
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            .personal-item {
 | 
					 | 
				
			||||||
                display: flex;
 | 
					 | 
				
			||||||
                align-items: center;
 | 
					 | 
				
			||||||
                font-size: 13px;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                .personal-item-label {
 | 
					 | 
				
			||||||
                    color: gray;
 | 
					 | 
				
			||||||
                    @include text-ellipsis(1);
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                .personal-item-value {
 | 
					 | 
				
			||||||
                    @include text-ellipsis(1);
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    .personal-info {
 | 
					 | 
				
			||||||
        .personal-info-more {
 | 
					 | 
				
			||||||
            float: right;
 | 
					 | 
				
			||||||
            color: gray;
 | 
					 | 
				
			||||||
            font-size: 13px;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            &:hover {
 | 
					 | 
				
			||||||
                color: var(--el-color-primary);
 | 
					 | 
				
			||||||
                cursor: pointer;
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        .personal-info-box {
 | 
					 | 
				
			||||||
            height: 130px;
 | 
					 | 
				
			||||||
            overflow: hidden;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            .personal-info-ul {
 | 
					 | 
				
			||||||
                list-style: none;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                .personal-info-li {
 | 
					 | 
				
			||||||
                    font-size: 13px;
 | 
					 | 
				
			||||||
                    padding-bottom: 10px;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    .personal-info-li-title {
 | 
					 | 
				
			||||||
                        display: inline-block;
 | 
					 | 
				
			||||||
                        @include text-ellipsis(1);
 | 
					 | 
				
			||||||
                        color: grey;
 | 
					 | 
				
			||||||
                        text-decoration: none;
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    & a:hover {
 | 
					 | 
				
			||||||
                        color: var(--el-color-primary);
 | 
					 | 
				
			||||||
                        cursor: pointer;
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    .personal-recommend-row {
 | 
					 | 
				
			||||||
        .personal-recommend-col {
 | 
					 | 
				
			||||||
            .personal-recommend {
 | 
					 | 
				
			||||||
                position: relative;
 | 
					 | 
				
			||||||
                height: 100px;
 | 
					 | 
				
			||||||
                color: #ffffff;
 | 
					 | 
				
			||||||
                border-radius: 3px;
 | 
					 | 
				
			||||||
                overflow: hidden;
 | 
					 | 
				
			||||||
                cursor: pointer;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                &:hover {
 | 
					 | 
				
			||||||
                    i {
 | 
					 | 
				
			||||||
                        right: 0px !important;
 | 
					 | 
				
			||||||
                        bottom: 0px !important;
 | 
					 | 
				
			||||||
                        transition: all ease 0.3s;
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                i {
 | 
					 | 
				
			||||||
                    position: absolute;
 | 
					 | 
				
			||||||
                    right: -10px;
 | 
					 | 
				
			||||||
                    bottom: -10px;
 | 
					 | 
				
			||||||
                    font-size: 70px;
 | 
					 | 
				
			||||||
                    transform: rotate(-30deg);
 | 
					 | 
				
			||||||
                    transition: all ease 0.3s;
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                .personal-recommend-auto {
 | 
					 | 
				
			||||||
                    padding: 15px;
 | 
					 | 
				
			||||||
                    position: absolute;
 | 
					 | 
				
			||||||
                    left: 0;
 | 
					 | 
				
			||||||
                    top: 5%;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    .personal-recommend-msg {
 | 
					 | 
				
			||||||
                        font-size: 12px;
 | 
					 | 
				
			||||||
                        margin-top: 10px;
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    .personal-edit {
 | 
					    .personal-edit {
 | 
				
			||||||
        .personal-edit-title {
 | 
					        .personal-edit-title {
 | 
				
			||||||
            position: relative;
 | 
					            position: relative;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -114,9 +114,10 @@ watchEffect(() => {
 | 
				
			|||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const btnOk = async () => {
 | 
					const btnOk = async () => {
 | 
				
			||||||
    accountForm.value.validate(async (valid: boolean) => {
 | 
					    try {
 | 
				
			||||||
        if (!valid) {
 | 
					        await accountForm.value.validate();
 | 
				
			||||||
            ElMessage.error('表单填写有误');
 | 
					    } catch (e: any) {
 | 
				
			||||||
 | 
					        ElMessage.error('请正确填写信息');
 | 
				
			||||||
        return false;
 | 
					        return false;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -126,7 +127,6 @@ const btnOk = async () => {
 | 
				
			|||||||
    //重置表单域
 | 
					    //重置表单域
 | 
				
			||||||
    accountForm.value.resetFields();
 | 
					    accountForm.value.resetFields();
 | 
				
			||||||
    state.form = {} as any;
 | 
					    state.form = {} as any;
 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const cancel = () => {
 | 
					const cancel = () => {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -42,7 +42,7 @@
 | 
				
			|||||||
                <el-table-column property="creator" label="分配账号" width="125"></el-table-column>
 | 
					                <el-table-column property="creator" label="分配账号" width="125"></el-table-column>
 | 
				
			||||||
                <el-table-column property="createTime" label="分配时间">
 | 
					                <el-table-column property="createTime" label="分配时间">
 | 
				
			||||||
                    <template #default="scope">
 | 
					                    <template #default="scope">
 | 
				
			||||||
                        {{ dateFormat(scope.row.createTime) }}
 | 
					                        {{ formatDate(scope.row.createTime) }}
 | 
				
			||||||
                    </template>
 | 
					                    </template>
 | 
				
			||||||
                </el-table-column>
 | 
					                </el-table-column>
 | 
				
			||||||
            </el-table>
 | 
					            </el-table>
 | 
				
			||||||
@@ -60,7 +60,7 @@ import AccountEdit from './AccountEdit.vue';
 | 
				
			|||||||
import { AccountStatusEnum } from '../enums';
 | 
					import { AccountStatusEnum } from '../enums';
 | 
				
			||||||
import { accountApi } from '../api';
 | 
					import { accountApi } from '../api';
 | 
				
			||||||
import { ElMessage, ElMessageBox } from 'element-plus';
 | 
					import { ElMessage, ElMessageBox } from 'element-plus';
 | 
				
			||||||
import { dateFormat } from '@/common/utils/date';
 | 
					import { formatDate } from '@/common/utils/format';
 | 
				
			||||||
import PageTable from '@/components/pagetable/PageTable.vue';
 | 
					import PageTable from '@/components/pagetable/PageTable.vue';
 | 
				
			||||||
import { TableColumn } from '@/components/pagetable';
 | 
					import { TableColumn } from '@/components/pagetable';
 | 
				
			||||||
import { hasPerms } from '@/components/auth/auth';
 | 
					import { hasPerms } from '@/components/auth/auth';
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,7 +1,7 @@
 | 
				
			|||||||
<template>
 | 
					<template>
 | 
				
			||||||
    <div>
 | 
					    <div>
 | 
				
			||||||
        <el-dialog :title="title" v-model="dvisible" :show-close="false" :before-close="cancel" width="900px" :destroy-on-close="true">
 | 
					        <el-dialog :title="title" v-model="dvisible" :show-close="false" :before-close="cancel" width="900px" :destroy-on-close="true">
 | 
				
			||||||
            <el-form ref="configForm" :model="form" label-width="auto">
 | 
					            <el-form ref="configForm" :model="form" :rules="rules" label-width="auto">
 | 
				
			||||||
                <el-form-item prop="name" label="配置项" required>
 | 
					                <el-form-item prop="name" label="配置项" required>
 | 
				
			||||||
                    <el-input v-model="form.name"></el-input>
 | 
					                    <el-input v-model="form.name"></el-input>
 | 
				
			||||||
                </el-form-item>
 | 
					                </el-form-item>
 | 
				
			||||||
@@ -44,6 +44,24 @@
 | 
				
			|||||||
import { ref, toRefs, reactive, watch, watchEffect } from 'vue';
 | 
					import { ref, toRefs, reactive, watch, watchEffect } from 'vue';
 | 
				
			||||||
import { configApi, accountApi } from '../api';
 | 
					import { configApi, accountApi } from '../api';
 | 
				
			||||||
import { DynamicFormEdit } from '@/components/dynamic-form';
 | 
					import { DynamicFormEdit } from '@/components/dynamic-form';
 | 
				
			||||||
 | 
					import { ElMessage } from 'element-plus';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const rules = {
 | 
				
			||||||
 | 
					    name: [
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            required: true,
 | 
				
			||||||
 | 
					            message: '请输入配置项',
 | 
				
			||||||
 | 
					            trigger: ['change', 'blur'],
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					    ],
 | 
				
			||||||
 | 
					    key: [
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            required: true,
 | 
				
			||||||
 | 
					            message: '请输入配置key',
 | 
				
			||||||
 | 
					            trigger: ['change', 'blur'],
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					    ],
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const props = defineProps({
 | 
					const props = defineProps({
 | 
				
			||||||
    visible: {
 | 
					    visible: {
 | 
				
			||||||
@@ -82,7 +100,9 @@ const { dvisible, params, form } = toRefs(state);
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
const { isFetching: saveBtnLoading, execute: saveConfigExec } = configApi.save.useApi(form);
 | 
					const { isFetching: saveBtnLoading, execute: saveConfigExec } = configApi.save.useApi(form);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
watchEffect(() => {
 | 
					watch(
 | 
				
			||||||
 | 
					    () => props.visible,
 | 
				
			||||||
 | 
					    () => {
 | 
				
			||||||
        state.dvisible = props.visible;
 | 
					        state.dvisible = props.visible;
 | 
				
			||||||
        if (!state.dvisible) {
 | 
					        if (!state.dvisible) {
 | 
				
			||||||
            return;
 | 
					            return;
 | 
				
			||||||
@@ -106,7 +126,8 @@ watchEffect(() => {
 | 
				
			|||||||
        } else {
 | 
					        } else {
 | 
				
			||||||
            state.permissionAccount = [];
 | 
					            state.permissionAccount = [];
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
});
 | 
					    }
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const cancel = () => {
 | 
					const cancel = () => {
 | 
				
			||||||
    // 更新父组件visible prop对应的值为false
 | 
					    // 更新父组件visible prop对应的值为false
 | 
				
			||||||
@@ -125,8 +146,13 @@ const getAccount = (username: any) => {
 | 
				
			|||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const btnOk = async () => {
 | 
					const btnOk = async () => {
 | 
				
			||||||
    configForm.value.validate(async (valid: boolean) => {
 | 
					    try {
 | 
				
			||||||
        if (valid) {
 | 
					        await configForm.value.validate();
 | 
				
			||||||
 | 
					    } catch (e: any) {
 | 
				
			||||||
 | 
					        ElMessage.error('请正确填写信息');
 | 
				
			||||||
 | 
					        return false;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (state.params) {
 | 
					    if (state.params) {
 | 
				
			||||||
        state.form.params = JSON.stringify(state.params);
 | 
					        state.form.params = JSON.stringify(state.params);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@@ -139,8 +165,6 @@ const btnOk = async () => {
 | 
				
			|||||||
    await saveConfigExec();
 | 
					    await saveConfigExec();
 | 
				
			||||||
    emit('val-change', state.form);
 | 
					    emit('val-change', state.form);
 | 
				
			||||||
    cancel();
 | 
					    cancel();
 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
</script>
 | 
					</script>
 | 
				
			||||||
<style lang="scss"></style>
 | 
					<style lang="scss"></style>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -128,7 +128,7 @@
 | 
				
			|||||||
                    </el-col>
 | 
					                    </el-col>
 | 
				
			||||||
                </el-row>
 | 
					                </el-row>
 | 
				
			||||||
            </el-form>
 | 
					            </el-form>
 | 
				
			||||||
            e
 | 
					
 | 
				
			||||||
            <template #footer>
 | 
					            <template #footer>
 | 
				
			||||||
                <div>
 | 
					                <div>
 | 
				
			||||||
                    <el-button @click="cancel()">取 消</el-button>
 | 
					                    <el-button @click="cancel()">取 消</el-button>
 | 
				
			||||||
@@ -254,7 +254,14 @@ const changeLinkType = () => {
 | 
				
			|||||||
    state.form.meta.component = '';
 | 
					    state.form.meta.component = '';
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const btnOk = () => {
 | 
					const btnOk = async () => {
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					        await menuForm.value.validate();
 | 
				
			||||||
 | 
					    } catch (e: any) {
 | 
				
			||||||
 | 
					        ElMessage.error('请正确填写信息');
 | 
				
			||||||
 | 
					        return false;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const submitForm = { ...state.form };
 | 
					    const submitForm = { ...state.form };
 | 
				
			||||||
    if (submitForm.type == 1) {
 | 
					    if (submitForm.type == 1) {
 | 
				
			||||||
        // 如果是菜单,则解析meta,如果值为false或者''则去除该值
 | 
					        // 如果是菜单,则解析meta,如果值为false或者''则去除该值
 | 
				
			||||||
@@ -263,16 +270,12 @@ const btnOk = () => {
 | 
				
			|||||||
        submitForm.meta = null as any;
 | 
					        submitForm.meta = null as any;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    menuForm.value.validate(async (valid: any) => {
 | 
					 | 
				
			||||||
        if (valid) {
 | 
					 | 
				
			||||||
    state.submitForm = submitForm;
 | 
					    state.submitForm = submitForm;
 | 
				
			||||||
    await saveResouceExec();
 | 
					    await saveResouceExec();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    emit('val-change', submitForm);
 | 
					    emit('val-change', submitForm);
 | 
				
			||||||
    ElMessage.success('保存成功');
 | 
					    ElMessage.success('保存成功');
 | 
				
			||||||
    cancel();
 | 
					    cancel();
 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const parseMenuMeta = (meta: any) => {
 | 
					const parseMenuMeta = (meta: any) => {
 | 
				
			||||||
@@ -314,10 +317,4 @@ const cancel = () => {
 | 
				
			|||||||
    emit('cancel');
 | 
					    emit('cancel');
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
</script>
 | 
					</script>
 | 
				
			||||||
<style lang="scss">
 | 
					<style lang="scss"></style>
 | 
				
			||||||
// 	.m-dialog {
 | 
					 | 
				
			||||||
// 		.el-cascader {
 | 
					 | 
				
			||||||
// 			width: 100%;
 | 
					 | 
				
			||||||
// 		}
 | 
					 | 
				
			||||||
// 	}
 | 
					 | 
				
			||||||
</style>
 | 
					 | 
				
			||||||
 
 | 
				
			|||||||
@@ -89,9 +89,9 @@
 | 
				
			|||||||
                                </el-descriptions-item>
 | 
					                                </el-descriptions-item>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                                <el-descriptions-item label="创建者">{{ currentResource.creator }}</el-descriptions-item>
 | 
					                                <el-descriptions-item label="创建者">{{ currentResource.creator }}</el-descriptions-item>
 | 
				
			||||||
                                <el-descriptions-item label="创建时间">{{ dateFormat(currentResource.createTime) }} </el-descriptions-item>
 | 
					                                <el-descriptions-item label="创建时间">{{ formatDate(currentResource.createTime) }} </el-descriptions-item>
 | 
				
			||||||
                                <el-descriptions-item label="修改者">{{ currentResource.modifier }}</el-descriptions-item>
 | 
					                                <el-descriptions-item label="修改者">{{ currentResource.modifier }}</el-descriptions-item>
 | 
				
			||||||
                                <el-descriptions-item label="更新时间">{{ dateFormat(currentResource.updateTime) }} </el-descriptions-item>
 | 
					                                <el-descriptions-item label="更新时间">{{ formatDate(currentResource.updateTime) }} </el-descriptions-item>
 | 
				
			||||||
                            </el-descriptions>
 | 
					                            </el-descriptions>
 | 
				
			||||||
                        </el-tab-pane>
 | 
					                        </el-tab-pane>
 | 
				
			||||||
                    </el-tabs>
 | 
					                    </el-tabs>
 | 
				
			||||||
@@ -119,10 +119,11 @@ import { ElMessage, ElMessageBox } from 'element-plus';
 | 
				
			|||||||
import ResourceEdit from './ResourceEdit.vue';
 | 
					import ResourceEdit from './ResourceEdit.vue';
 | 
				
			||||||
import { ResourceTypeEnum } from '../enums';
 | 
					import { ResourceTypeEnum } from '../enums';
 | 
				
			||||||
import { resourceApi } from '../api';
 | 
					import { resourceApi } from '../api';
 | 
				
			||||||
import { dateFormat } from '@/common/utils/date';
 | 
					import { formatDate } from '@/common/utils/format';
 | 
				
			||||||
import EnumTag from '@/components/enumtag/EnumTag.vue';
 | 
					import EnumTag from '@/components/enumtag/EnumTag.vue';
 | 
				
			||||||
import { Contextmenu, ContextmenuItem } from '@/components/contextmenu';
 | 
					import { Contextmenu, ContextmenuItem } from '@/components/contextmenu';
 | 
				
			||||||
import { Splitpanes, Pane } from 'splitpanes';
 | 
					import { Splitpanes, Pane } from 'splitpanes';
 | 
				
			||||||
 | 
					import { isPrefixSubsequence } from '@/common/utils/string';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const menuTypeValue = ResourceTypeEnum.Menu.value;
 | 
					const menuTypeValue = ResourceTypeEnum.Menu.value;
 | 
				
			||||||
const permissionTypeValue = ResourceTypeEnum.Permission.value;
 | 
					const permissionTypeValue = ResourceTypeEnum.Permission.value;
 | 
				
			||||||
@@ -209,10 +210,7 @@ watch(filterResource, (val) => {
 | 
				
			|||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const filterNode = (value: string, data: any) => {
 | 
					const filterNode = (value: string, data: any) => {
 | 
				
			||||||
    if (!value) {
 | 
					    return !value || isPrefixSubsequence(value, data.name);
 | 
				
			||||||
        return true;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    return data.name.includes(value);
 | 
					 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const search = async () => {
 | 
					const search = async () => {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -19,8 +19,8 @@
 | 
				
			|||||||
            </el-tree>
 | 
					            </el-tree>
 | 
				
			||||||
            <template #footer>
 | 
					            <template #footer>
 | 
				
			||||||
                <div class="dialog-footer">
 | 
					                <div class="dialog-footer">
 | 
				
			||||||
                    <el-button @click="cancel">取 消</el-button>
 | 
					                    <el-button :loading="state.submiting" @click="cancel">取 消</el-button>
 | 
				
			||||||
                    <el-button type="primary" @click="btnOk">确 定</el-button>
 | 
					                    <el-button :loading="state.submiting" type="primary" @click="btnOk">确 定</el-button>
 | 
				
			||||||
                </div>
 | 
					                </div>
 | 
				
			||||||
            </template>
 | 
					            </template>
 | 
				
			||||||
        </el-dialog>
 | 
					        </el-dialog>
 | 
				
			||||||
@@ -66,6 +66,7 @@ const menuTree: any = ref(null);
 | 
				
			|||||||
const state = reactive({
 | 
					const state = reactive({
 | 
				
			||||||
    dialogVisible: false,
 | 
					    dialogVisible: false,
 | 
				
			||||||
    roleInfo: null as any,
 | 
					    roleInfo: null as any,
 | 
				
			||||||
 | 
					    submiting: false,
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const { dialogVisible, roleInfo } = toRefs(state);
 | 
					const { dialogVisible, roleInfo } = toRefs(state);
 | 
				
			||||||
@@ -82,12 +83,17 @@ const btnOk = async () => {
 | 
				
			|||||||
    let menuIds = menuTree.value.getCheckedKeys();
 | 
					    let menuIds = menuTree.value.getCheckedKeys();
 | 
				
			||||||
    let halfMenuIds = menuTree.value.getHalfCheckedKeys();
 | 
					    let halfMenuIds = menuTree.value.getHalfCheckedKeys();
 | 
				
			||||||
    let resources = [].concat(menuIds, halfMenuIds).join(',');
 | 
					    let resources = [].concat(menuIds, halfMenuIds).join(',');
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					        state.submiting = true;
 | 
				
			||||||
        await roleApi.saveResources.request({
 | 
					        await roleApi.saveResources.request({
 | 
				
			||||||
            id: props.role!.id,
 | 
					            id: props.role!.id,
 | 
				
			||||||
            resourceIds: resources,
 | 
					            resourceIds: resources,
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
        ElMessage.success('保存成功!');
 | 
					        ElMessage.success('保存成功!');
 | 
				
			||||||
        emit('cancel');
 | 
					        emit('cancel');
 | 
				
			||||||
 | 
					    } finally {
 | 
				
			||||||
 | 
					        state.submiting = false;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const cancel = () => {
 | 
					const cancel = () => {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,7 +1,7 @@
 | 
				
			|||||||
<template>
 | 
					<template>
 | 
				
			||||||
    <div class="role-dialog">
 | 
					    <div class="role-dialog">
 | 
				
			||||||
        <el-dialog :title="title" v-model="dvisible" :show-close="false" :before-close="cancel" width="500px" :destroy-on-close="true">
 | 
					        <el-dialog :title="title" v-model="dvisible" :show-close="false" :before-close="cancel" width="500px" :destroy-on-close="true">
 | 
				
			||||||
            <el-form ref="roleForm" :model="form" label-width="auto">
 | 
					            <el-form ref="roleForm" :model="form" :rules="rules" label-width="auto">
 | 
				
			||||||
                <el-form-item prop="name" label="角色名称" required>
 | 
					                <el-form-item prop="name" label="角色名称" required>
 | 
				
			||||||
                    <el-input v-model="form.name" auto-complete="off"></el-input>
 | 
					                    <el-input v-model="form.name" auto-complete="off"></el-input>
 | 
				
			||||||
                </el-form-item>
 | 
					                </el-form-item>
 | 
				
			||||||
@@ -32,6 +32,30 @@ import { ref, toRefs, reactive, watchEffect } from 'vue';
 | 
				
			|||||||
import { roleApi } from '../api';
 | 
					import { roleApi } from '../api';
 | 
				
			||||||
import { RoleStatusEnum } from '../enums';
 | 
					import { RoleStatusEnum } from '../enums';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const rules = {
 | 
				
			||||||
 | 
					    name: [
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            required: true,
 | 
				
			||||||
 | 
					            message: '请输入角色名称',
 | 
				
			||||||
 | 
					            trigger: ['change', 'blur'],
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					    ],
 | 
				
			||||||
 | 
					    code: [
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            required: true,
 | 
				
			||||||
 | 
					            message: '请输入角色编号',
 | 
				
			||||||
 | 
					            trigger: ['change', 'blur'],
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					    ],
 | 
				
			||||||
 | 
					    status: [
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            required: true,
 | 
				
			||||||
 | 
					            message: '请选择状态',
 | 
				
			||||||
 | 
					            trigger: ['change', 'blur'],
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					    ],
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const props = defineProps({
 | 
					const props = defineProps({
 | 
				
			||||||
    visible: {
 | 
					    visible: {
 | 
				
			||||||
        type: Boolean,
 | 
					        type: Boolean,
 | 
				
			||||||
@@ -80,13 +104,15 @@ const cancel = () => {
 | 
				
			|||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const btnOk = async () => {
 | 
					const btnOk = async () => {
 | 
				
			||||||
    roleForm.value.validate(async (valid: boolean) => {
 | 
					    try {
 | 
				
			||||||
        if (valid) {
 | 
					        await roleForm.value.validate();
 | 
				
			||||||
 | 
					    } catch (e: any) {
 | 
				
			||||||
 | 
					        return false;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    await saveRoleExec();
 | 
					    await saveRoleExec();
 | 
				
			||||||
    emit('val-change', state.form);
 | 
					    emit('val-change', state.form);
 | 
				
			||||||
    cancel();
 | 
					    cancel();
 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
</script>
 | 
					</script>
 | 
				
			||||||
<style lang="scss"></style>
 | 
					<style lang="scss"></style>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -20,7 +20,7 @@
 | 
				
			|||||||
                                        {{ data.creator }}
 | 
					                                        {{ data.creator }}
 | 
				
			||||||
                                    </el-descriptions-item>
 | 
					                                    </el-descriptions-item>
 | 
				
			||||||
                                    <el-descriptions-item label="分配时间">
 | 
					                                    <el-descriptions-item label="分配时间">
 | 
				
			||||||
                                        {{ dateFormat(data.createTime) }}
 | 
					                                        {{ formatDate(data.createTime) }}
 | 
				
			||||||
                                    </el-descriptions-item>
 | 
					                                    </el-descriptions-item>
 | 
				
			||||||
                                </el-descriptions>
 | 
					                                </el-descriptions>
 | 
				
			||||||
                            </template>
 | 
					                            </template>
 | 
				
			||||||
@@ -35,7 +35,7 @@
 | 
				
			|||||||
<script lang="ts" setup>
 | 
					<script lang="ts" setup>
 | 
				
			||||||
import { toRefs, reactive, watch } from 'vue';
 | 
					import { toRefs, reactive, watch } from 'vue';
 | 
				
			||||||
import { ResourceTypeEnum } from '../enums';
 | 
					import { ResourceTypeEnum } from '../enums';
 | 
				
			||||||
import { dateFormat } from '@/common/utils/date';
 | 
					import { formatDate } from '@/common/utils/format';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const props = defineProps({
 | 
					const props = defineProps({
 | 
				
			||||||
    visible: {
 | 
					    visible: {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -11,9 +11,11 @@ server:
 | 
				
			|||||||
    cert-file: ./default.pem
 | 
					    cert-file: ./default.pem
 | 
				
			||||||
jwt:
 | 
					jwt:
 | 
				
			||||||
  # jwt key,不设置默认使用随机字符串
 | 
					  # jwt key,不设置默认使用随机字符串
 | 
				
			||||||
  key: 
 | 
					  key: 333333000000
 | 
				
			||||||
  # 过期时间单位分钟
 | 
					  # accessToken过期时间单位分钟
 | 
				
			||||||
  expire-time: 1440
 | 
					  expire-time: 720
 | 
				
			||||||
 | 
					  # refreshToken过期时间单位分钟
 | 
				
			||||||
 | 
					  refresh-token-expire-time: 4320
 | 
				
			||||||
# 资源密码aes加密key
 | 
					# 资源密码aes加密key
 | 
				
			||||||
aes:
 | 
					aes:
 | 
				
			||||||
  key: 1111111111111111
 | 
					  key: 1111111111111111
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -3,56 +3,58 @@ module mayfly-go
 | 
				
			|||||||
go 1.22
 | 
					go 1.22
 | 
				
			||||||
 | 
					
 | 
				
			||||||
require (
 | 
					require (
 | 
				
			||||||
	gitee.com/chunanyong/dm v1.8.14
 | 
						gitee.com/chunanyong/dm v1.8.15
 | 
				
			||||||
	gitee.com/liuzongyang/libpq v1.0.9
 | 
						gitee.com/liuzongyang/libpq v1.0.9
 | 
				
			||||||
	github.com/buger/jsonparser v1.1.1
 | 
					 | 
				
			||||||
	github.com/emirpasic/gods v1.18.1
 | 
						github.com/emirpasic/gods v1.18.1
 | 
				
			||||||
	github.com/gin-gonic/gin v1.9.1
 | 
						github.com/gin-gonic/gin v1.10.0
 | 
				
			||||||
	github.com/glebarez/sqlite v1.11.0
 | 
						github.com/glebarez/sqlite v1.11.0
 | 
				
			||||||
	github.com/go-gormigrate/gormigrate/v2 v2.1.0
 | 
						github.com/go-gormigrate/gormigrate/v2 v2.1.0
 | 
				
			||||||
	github.com/go-ldap/ldap/v3 v3.4.6
 | 
						github.com/go-ldap/ldap/v3 v3.4.8
 | 
				
			||||||
	github.com/go-playground/locales v0.14.1
 | 
						github.com/go-playground/locales v0.14.1
 | 
				
			||||||
	github.com/go-playground/universal-translator v0.18.1
 | 
						github.com/go-playground/universal-translator v0.18.1
 | 
				
			||||||
	github.com/go-playground/validator/v10 v10.14.0
 | 
						github.com/go-playground/validator/v10 v10.20.0
 | 
				
			||||||
	github.com/go-sql-driver/mysql v1.8.1
 | 
						github.com/go-sql-driver/mysql v1.8.1
 | 
				
			||||||
	github.com/golang-jwt/jwt/v5 v5.2.1
 | 
						github.com/golang-jwt/jwt/v5 v5.2.1
 | 
				
			||||||
	github.com/google/uuid v1.6.0
 | 
						github.com/google/uuid v1.6.0
 | 
				
			||||||
	github.com/gorilla/websocket v1.5.1
 | 
						github.com/gorilla/websocket v1.5.3
 | 
				
			||||||
	github.com/kanzihuang/vitess/go/vt/sqlparser v0.0.0-20231018071450-ac8d9f0167e9
 | 
						github.com/kanzihuang/vitess/go/vt/sqlparser v0.0.0-20231018071450-ac8d9f0167e9
 | 
				
			||||||
	github.com/lionsoul2014/ip2region/binding/golang v0.0.0-20230712084735-068dc2aee82d
 | 
						github.com/lionsoul2014/ip2region/binding/golang v0.0.0-20230712084735-068dc2aee82d
 | 
				
			||||||
	github.com/may-fly/cast v1.6.1
 | 
						github.com/may-fly/cast v1.6.1
 | 
				
			||||||
	github.com/microsoft/go-mssqldb v1.7.0
 | 
						github.com/microsoft/go-mssqldb v1.7.2
 | 
				
			||||||
	github.com/mojocn/base64Captcha v1.3.6 // 验证码
 | 
						github.com/mojocn/base64Captcha v1.3.6 // 验证码
 | 
				
			||||||
	github.com/pkg/errors v0.9.1
 | 
						github.com/pkg/errors v0.9.1
 | 
				
			||||||
	github.com/pkg/sftp v1.13.6
 | 
						github.com/pkg/sftp v1.13.6
 | 
				
			||||||
	github.com/pquerna/otp v1.4.0
 | 
						github.com/pquerna/otp v1.4.0
 | 
				
			||||||
	github.com/redis/go-redis/v9 v9.5.1
 | 
						github.com/redis/go-redis/v9 v9.5.3
 | 
				
			||||||
	github.com/robfig/cron/v3 v3.0.1 // 定时任务
 | 
						github.com/robfig/cron/v3 v3.0.1 // 定时任务
 | 
				
			||||||
	github.com/sijms/go-ora/v2 v2.8.10
 | 
						github.com/sijms/go-ora/v2 v2.8.19
 | 
				
			||||||
	github.com/stretchr/testify v1.8.4
 | 
						github.com/stretchr/testify v1.9.0
 | 
				
			||||||
 | 
						github.com/tidwall/gjson v1.17.1
 | 
				
			||||||
	github.com/veops/go-ansiterm v0.0.5
 | 
						github.com/veops/go-ansiterm v0.0.5
 | 
				
			||||||
	go.mongodb.org/mongo-driver v1.14.0 // mongo
 | 
						go.mongodb.org/mongo-driver v1.16.0 // mongo
 | 
				
			||||||
	golang.org/x/crypto v0.22.0 // ssh
 | 
						golang.org/x/crypto v0.25.0 // ssh
 | 
				
			||||||
	golang.org/x/oauth2 v0.18.0
 | 
						golang.org/x/oauth2 v0.21.0
 | 
				
			||||||
	golang.org/x/sync v0.6.0
 | 
						golang.org/x/sync v0.7.0
 | 
				
			||||||
	gopkg.in/natefinch/lumberjack.v2 v2.2.1
 | 
						gopkg.in/natefinch/lumberjack.v2 v2.2.1
 | 
				
			||||||
	gopkg.in/yaml.v3 v3.0.1
 | 
						gopkg.in/yaml.v3 v3.0.1
 | 
				
			||||||
	// gorm
 | 
						// gorm
 | 
				
			||||||
	gorm.io/driver/mysql v1.5.6
 | 
						gorm.io/driver/mysql v1.5.7
 | 
				
			||||||
	gorm.io/gorm v1.25.9
 | 
						gorm.io/gorm v1.25.11
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
require (
 | 
					require (
 | 
				
			||||||
	filippo.io/edwards25519 v1.1.0 // indirect
 | 
						filippo.io/edwards25519 v1.1.0 // indirect
 | 
				
			||||||
	github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 // indirect
 | 
						github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 // indirect
 | 
				
			||||||
	github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc // indirect
 | 
						github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc // indirect
 | 
				
			||||||
	github.com/bytedance/sonic v1.9.1 // indirect
 | 
						github.com/bytedance/sonic v1.11.6 // indirect
 | 
				
			||||||
 | 
						github.com/bytedance/sonic/loader v0.1.1 // indirect
 | 
				
			||||||
	github.com/cespare/xxhash/v2 v2.2.0 // indirect
 | 
						github.com/cespare/xxhash/v2 v2.2.0 // indirect
 | 
				
			||||||
	github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect
 | 
						github.com/cloudwego/base64x v0.1.4 // indirect
 | 
				
			||||||
 | 
						github.com/cloudwego/iasm v0.2.0 // indirect
 | 
				
			||||||
	github.com/davecgh/go-spew v1.1.1 // indirect
 | 
						github.com/davecgh/go-spew v1.1.1 // indirect
 | 
				
			||||||
	github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
 | 
						github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
 | 
				
			||||||
	github.com/dustin/go-humanize v1.0.1 // indirect
 | 
						github.com/dustin/go-humanize v1.0.1 // indirect
 | 
				
			||||||
	github.com/gabriel-vasile/mimetype v1.4.2 // indirect
 | 
						github.com/gabriel-vasile/mimetype v1.4.3 // indirect
 | 
				
			||||||
	github.com/gin-contrib/sse v0.1.0 // indirect
 | 
						github.com/gin-contrib/sse v0.1.0 // indirect
 | 
				
			||||||
	github.com/glebarez/go-sqlite v1.21.2 // indirect
 | 
						github.com/glebarez/go-sqlite v1.21.2 // indirect
 | 
				
			||||||
	github.com/go-asn1-ber/asn1-ber v1.5.5 // indirect
 | 
						github.com/go-asn1-ber/asn1-ber v1.5.5 // indirect
 | 
				
			||||||
@@ -67,37 +69,38 @@ require (
 | 
				
			|||||||
	github.com/jinzhu/now v1.1.5 // indirect
 | 
						github.com/jinzhu/now v1.1.5 // indirect
 | 
				
			||||||
	github.com/json-iterator/go v1.1.12 // indirect
 | 
						github.com/json-iterator/go v1.1.12 // indirect
 | 
				
			||||||
	github.com/klauspost/compress v1.16.5 // indirect
 | 
						github.com/klauspost/compress v1.16.5 // indirect
 | 
				
			||||||
	github.com/klauspost/cpuid/v2 v2.2.4 // indirect
 | 
						github.com/klauspost/cpuid/v2 v2.2.7 // indirect
 | 
				
			||||||
	github.com/kr/fs v0.1.0 // indirect
 | 
						github.com/kr/fs v0.1.0 // indirect
 | 
				
			||||||
	github.com/leodido/go-urn v1.2.4 // indirect
 | 
						github.com/leodido/go-urn v1.4.0 // indirect
 | 
				
			||||||
	github.com/mattn/go-isatty v0.0.19 // indirect
 | 
						github.com/mattn/go-isatty v0.0.20 // indirect
 | 
				
			||||||
	github.com/mattn/go-runewidth v0.0.15 // indirect
 | 
						github.com/mattn/go-runewidth v0.0.15 // indirect
 | 
				
			||||||
	github.com/mattn/go-sqlite3 v1.14.17 // indirect
 | 
						github.com/mattn/go-sqlite3 v1.14.17 // indirect
 | 
				
			||||||
	github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
 | 
						github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
 | 
				
			||||||
	github.com/modern-go/reflect2 v1.0.2 // indirect
 | 
						github.com/modern-go/reflect2 v1.0.2 // indirect
 | 
				
			||||||
	github.com/montanaflynn/stats v0.7.0 // indirect
 | 
						github.com/montanaflynn/stats v0.7.1 // indirect
 | 
				
			||||||
	github.com/pelletier/go-toml/v2 v2.0.8 // indirect
 | 
						github.com/pelletier/go-toml/v2 v2.2.2 // indirect
 | 
				
			||||||
	github.com/pmezard/go-difflib v1.0.0 // indirect
 | 
						github.com/pmezard/go-difflib v1.0.0 // indirect
 | 
				
			||||||
	github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
 | 
						github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
 | 
				
			||||||
	github.com/rivo/uniseg v0.4.3 // indirect
 | 
						github.com/rivo/uniseg v0.4.3 // indirect
 | 
				
			||||||
	github.com/spf13/pflag v1.0.5 // indirect
 | 
						github.com/spf13/pflag v1.0.5 // indirect
 | 
				
			||||||
 | 
						github.com/tidwall/match v1.1.1 // indirect
 | 
				
			||||||
 | 
						github.com/tidwall/pretty v1.2.1 // indirect
 | 
				
			||||||
	github.com/tjfoc/gmsm v1.4.1 // indirect
 | 
						github.com/tjfoc/gmsm v1.4.1 // indirect
 | 
				
			||||||
	github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
 | 
						github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
 | 
				
			||||||
	github.com/ugorji/go/codec v1.2.11 // indirect
 | 
						github.com/ugorji/go/codec v1.2.12 // indirect
 | 
				
			||||||
	github.com/xdg-go/pbkdf2 v1.0.0 // indirect
 | 
						github.com/xdg-go/pbkdf2 v1.0.0 // indirect
 | 
				
			||||||
	github.com/xdg-go/scram v1.1.2 // indirect
 | 
						github.com/xdg-go/scram v1.1.2 // indirect
 | 
				
			||||||
	github.com/xdg-go/stringprep v1.0.4 // indirect
 | 
						github.com/xdg-go/stringprep v1.0.4 // indirect
 | 
				
			||||||
	github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d // indirect
 | 
						github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d // indirect
 | 
				
			||||||
	golang.org/x/arch v0.3.0 // indirect
 | 
						golang.org/x/arch v0.8.0 // indirect
 | 
				
			||||||
	golang.org/x/exp v0.0.0-20230519143937-03e91628a987 // indirect
 | 
						golang.org/x/exp v0.0.0-20230519143937-03e91628a987 // indirect
 | 
				
			||||||
	golang.org/x/image v0.13.0 // indirect
 | 
						golang.org/x/image v0.13.0 // indirect
 | 
				
			||||||
	golang.org/x/net v0.22.0 // indirect
 | 
						golang.org/x/net v0.25.0 // indirect
 | 
				
			||||||
	golang.org/x/sys v0.19.0 // indirect
 | 
						golang.org/x/sys v0.22.0 // indirect
 | 
				
			||||||
	golang.org/x/text v0.14.0 // indirect
 | 
						golang.org/x/text v0.16.0 // indirect
 | 
				
			||||||
	google.golang.org/appengine v1.6.7 // indirect
 | 
					 | 
				
			||||||
	google.golang.org/genproto v0.0.0-20230131230820-1c016267d619 // indirect
 | 
						google.golang.org/genproto v0.0.0-20230131230820-1c016267d619 // indirect
 | 
				
			||||||
	google.golang.org/grpc v1.52.3 // indirect
 | 
						google.golang.org/grpc v1.52.3 // indirect
 | 
				
			||||||
	google.golang.org/protobuf v1.31.0 // indirect
 | 
						google.golang.org/protobuf v1.34.1 // indirect
 | 
				
			||||||
	modernc.org/libc v1.22.5 // indirect
 | 
						modernc.org/libc v1.22.5 // indirect
 | 
				
			||||||
	modernc.org/mathutil v1.5.0 // indirect
 | 
						modernc.org/mathutil v1.5.0 // indirect
 | 
				
			||||||
	modernc.org/memory v1.5.0 // indirect
 | 
						modernc.org/memory v1.5.0 // indirect
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -2,7 +2,6 @@ package api
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
	"context"
 | 
						"context"
 | 
				
			||||||
	"encoding/json"
 | 
					 | 
				
			||||||
	"fmt"
 | 
						"fmt"
 | 
				
			||||||
	"mayfly-go/internal/auth/api/form"
 | 
						"mayfly-go/internal/auth/api/form"
 | 
				
			||||||
	"mayfly-go/internal/auth/config"
 | 
						"mayfly-go/internal/auth/config"
 | 
				
			||||||
@@ -14,6 +13,7 @@ import (
 | 
				
			|||||||
	"mayfly-go/pkg/cache"
 | 
						"mayfly-go/pkg/cache"
 | 
				
			||||||
	"mayfly-go/pkg/captcha"
 | 
						"mayfly-go/pkg/captcha"
 | 
				
			||||||
	"mayfly-go/pkg/errorx"
 | 
						"mayfly-go/pkg/errorx"
 | 
				
			||||||
 | 
						"mayfly-go/pkg/model"
 | 
				
			||||||
	"mayfly-go/pkg/otp"
 | 
						"mayfly-go/pkg/otp"
 | 
				
			||||||
	"mayfly-go/pkg/req"
 | 
						"mayfly-go/pkg/req"
 | 
				
			||||||
	"mayfly-go/pkg/utils/collx"
 | 
						"mayfly-go/pkg/utils/collx"
 | 
				
			||||||
@@ -50,7 +50,7 @@ func (a *AccountLogin) Login(rc *req.Ctx) {
 | 
				
			|||||||
	biz.ErrIsNilAppendErr(err, "解密密码错误: %s")
 | 
						biz.ErrIsNilAppendErr(err, "解密密码错误: %s")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	account := &sysentity.Account{Username: username}
 | 
						account := &sysentity.Account{Username: username}
 | 
				
			||||||
	err = a.AccountApp.GetBy(account, "Id", "Name", "Username", "Password", "Status", "LastLoginTime", "LastLoginIp", "OtpSecret")
 | 
						err = a.AccountApp.GetByCond(model.NewModelCond(account).Columns("Id", "Name", "Username", "Password", "Status", "LastLoginTime", "LastLoginIp", "OtpSecret"))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	failCountKey := fmt.Sprintf("account:login:failcount:%s", username)
 | 
						failCountKey := fmt.Sprintf("account:login:failcount:%s", username)
 | 
				
			||||||
	nowFailCount := cache.GetInt(failCountKey)
 | 
						nowFailCount := cache.GetInt(failCountKey)
 | 
				
			||||||
@@ -74,6 +74,7 @@ type OtpVerifyInfo struct {
 | 
				
			|||||||
	Username     string
 | 
						Username     string
 | 
				
			||||||
	OptStatus    int
 | 
						OptStatus    int
 | 
				
			||||||
	AccessToken  string
 | 
						AccessToken  string
 | 
				
			||||||
 | 
						RefreshToken string
 | 
				
			||||||
	OtpSecret    string
 | 
						OtpSecret    string
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -83,10 +84,9 @@ func (a *AccountLogin) OtpVerify(rc *req.Ctx) {
 | 
				
			|||||||
	req.BindJsonAndValid(rc, otpVerify)
 | 
						req.BindJsonAndValid(rc, otpVerify)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	tokenKey := fmt.Sprintf("otp:token:%s", otpVerify.OtpToken)
 | 
						tokenKey := fmt.Sprintf("otp:token:%s", otpVerify.OtpToken)
 | 
				
			||||||
	otpInfoJson := cache.GetStr(tokenKey)
 | 
					 | 
				
			||||||
	biz.NotEmpty(otpInfoJson, "otpToken错误或失效, 请重新登陆获取")
 | 
					 | 
				
			||||||
	otpInfo := new(OtpVerifyInfo)
 | 
						otpInfo := new(OtpVerifyInfo)
 | 
				
			||||||
	json.Unmarshal([]byte(otpInfoJson), otpInfo)
 | 
						ok := cache.Get(tokenKey, otpInfo)
 | 
				
			||||||
 | 
						biz.IsTrue(ok, "otpToken错误或失效, 请重新登陆获取")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	failCountKey := fmt.Sprintf("account:otp:failcount:%d", otpInfo.AccountId)
 | 
						failCountKey := fmt.Sprintf("account:otp:failcount:%d", otpInfo.AccountId)
 | 
				
			||||||
	failCount := cache.GetInt(failCountKey)
 | 
						failCount := cache.GetInt(failCountKey)
 | 
				
			||||||
@@ -115,7 +115,19 @@ func (a *AccountLogin) OtpVerify(rc *req.Ctx) {
 | 
				
			|||||||
	go saveLogin(la, getIpAndRegion(rc))
 | 
						go saveLogin(la, getIpAndRegion(rc))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	cache.Del(tokenKey)
 | 
						cache.Del(tokenKey)
 | 
				
			||||||
	rc.ResData = accessToken
 | 
						rc.ResData = collx.Kvs("token", accessToken, "refresh_token", otpInfo.RefreshToken)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (a *AccountLogin) RefreshToken(rc *req.Ctx) {
 | 
				
			||||||
 | 
						refreshToken := rc.Query("refresh_token")
 | 
				
			||||||
 | 
						biz.NotEmpty(refreshToken, "refresh_token不能为空")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						accountId, username, err := req.ParseToken(refreshToken)
 | 
				
			||||||
 | 
						biz.IsTrueBy(err == nil, errorx.PermissionErr)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						token, refreshToken, err := req.CreateToken(accountId, username)
 | 
				
			||||||
 | 
						biz.ErrIsNil(err)
 | 
				
			||||||
 | 
						rc.ResData = collx.Kvs("token", token, "refresh_token", refreshToken)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (a *AccountLogin) Logout(rc *req.Ctx) {
 | 
					func (a *AccountLogin) Logout(rc *req.Ctx) {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -41,18 +41,19 @@ func LastLoginCheck(account *sysentity.Account, accountLoginSecurity *config.Acc
 | 
				
			|||||||
	// 默认为不校验otp
 | 
						// 默认为不校验otp
 | 
				
			||||||
	otpStatus := OtpStatusNone
 | 
						otpStatus := OtpStatusNone
 | 
				
			||||||
	// 访问系统使用的token
 | 
						// 访问系统使用的token
 | 
				
			||||||
	accessToken, err := req.CreateToken(account.Id, username)
 | 
						accessToken, refreshToken, err := req.CreateToken(account.Id, username)
 | 
				
			||||||
	biz.ErrIsNilAppendErr(err, "token创建失败: %s")
 | 
						biz.ErrIsNilAppendErr(err, "token创建失败: %s")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// 若系统配置中设置开启otp双因素校验,则进行otp校验
 | 
						// 若系统配置中设置开启otp双因素校验,则进行otp校验
 | 
				
			||||||
	if accountLoginSecurity.UseOtp {
 | 
						if accountLoginSecurity.UseOtp {
 | 
				
			||||||
		otpInfo, otpurl, otpToken := useOtp(account, accountLoginSecurity.OtpIssuer, accessToken)
 | 
							otpInfo, otpurl, otpToken := useOtp(account, accountLoginSecurity.OtpIssuer, accessToken, refreshToken)
 | 
				
			||||||
		otpStatus = otpInfo.OptStatus
 | 
							otpStatus = otpInfo.OptStatus
 | 
				
			||||||
		if otpurl != "" {
 | 
							if otpurl != "" {
 | 
				
			||||||
			res["otpUrl"] = otpurl
 | 
								res["otpUrl"] = otpurl
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		accessToken = otpToken
 | 
							accessToken = otpToken
 | 
				
			||||||
	} else {
 | 
						} else {
 | 
				
			||||||
 | 
							res["refresh_token"] = refreshToken
 | 
				
			||||||
		// 不进行otp二次校验则直接返回accessToken
 | 
							// 不进行otp二次校验则直接返回accessToken
 | 
				
			||||||
		// 保存登录消息
 | 
							// 保存登录消息
 | 
				
			||||||
		go saveLogin(account, loginIp)
 | 
							go saveLogin(account, loginIp)
 | 
				
			||||||
@@ -64,7 +65,7 @@ func LastLoginCheck(account *sysentity.Account, accountLoginSecurity *config.Acc
 | 
				
			|||||||
	return res
 | 
						return res
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func useOtp(account *sysentity.Account, otpIssuer, accessToken string) (*OtpVerifyInfo, string, string) {
 | 
					func useOtp(account *sysentity.Account, otpIssuer, accessToken string, refreshToken string) (*OtpVerifyInfo, string, string) {
 | 
				
			||||||
	biz.ErrIsNil(account.OtpSecretDecrypt())
 | 
						biz.ErrIsNil(account.OtpSecretDecrypt())
 | 
				
			||||||
	otpSecret := account.OtpSecret
 | 
						otpSecret := account.OtpSecret
 | 
				
			||||||
	// 修改状态为已注册
 | 
						// 修改状态为已注册
 | 
				
			||||||
@@ -83,13 +84,14 @@ func useOtp(account *sysentity.Account, otpIssuer, accessToken string) (*OtpVeri
 | 
				
			|||||||
		otpUrl = key.URL()
 | 
							otpUrl = key.URL()
 | 
				
			||||||
		otpSecret = key.Secret()
 | 
							otpSecret = key.Secret()
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	// 缓存otpInfo, 只有双因素校验通过才可返回真正的accessToken
 | 
						// 缓存otpInfo, 只有双因素校验通过才可返回真正的token
 | 
				
			||||||
	otpInfo := &OtpVerifyInfo{
 | 
						otpInfo := &OtpVerifyInfo{
 | 
				
			||||||
		AccountId:    account.Id,
 | 
							AccountId:    account.Id,
 | 
				
			||||||
		Username:     account.Username,
 | 
							Username:     account.Username,
 | 
				
			||||||
		OptStatus:    otpStatus,
 | 
							OptStatus:    otpStatus,
 | 
				
			||||||
		OtpSecret:    otpSecret,
 | 
							OtpSecret:    otpSecret,
 | 
				
			||||||
		AccessToken:  accessToken,
 | 
							AccessToken:  accessToken,
 | 
				
			||||||
 | 
							RefreshToken: refreshToken,
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	cache.SetStr(fmt.Sprintf("otp:token:%s", token), jsonx.ToStr(otpInfo), time.Minute*time.Duration(3))
 | 
						cache.SetStr(fmt.Sprintf("otp:token:%s", token), jsonx.ToStr(otpInfo), time.Minute*time.Duration(3))
 | 
				
			||||||
	return otpInfo, otpUrl, token
 | 
						return otpInfo, otpUrl, token
 | 
				
			||||||
 
 | 
				
			|||||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user