mirror of
				https://gitee.com/dromara/mayfly-go
				synced 2025-11-04 08:20:25 +08:00 
			
		
		
		
	Compare commits
	
		
			42 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					7f9e972828 | ||
| 
						 | 
					7b51705f4e | ||
| 
						 | 
					6bd9e5333d | ||
| 
						 | 
					112d735ac0 | ||
| 
						 | 
					52553ed53f | ||
| 
						 | 
					70d84e32d1 | ||
| 
						 | 
					3269dfa5d6 | ||
| 
						 | 
					183a6e4905 | ||
| 
						 | 
					5463ae9d7e | ||
| 
						 | 
					f25bdb07ce | ||
| 
						 | 
					aa5c08d564 | ||
| 
						 | 
					dc9a2985f3 | ||
| 
						 | 
					f4ac6d8360 | ||
| 
						 | 
					3266039aaf | ||
| 
						 | 
					e3f4c298b0 | ||
| 
						 | 
					fa58f6d2de | ||
| 
						 | 
					ae5a1fd7de | ||
| 
						 | 
					c240079df4 | ||
| 
						 | 
					aca4e6751e | ||
| 
						 | 
					ce32fc7f2c | ||
| 
						 | 
					d423572e01 | ||
| 
						 | 
					d9807b1bf0 | ||
| 
						 | 
					1bc53b4c80 | ||
| 
						 | 
					6bc2603a4d | ||
| 
						 | 
					e2c929aae1 | ||
| 
						 | 
					0d155d592b | ||
| 
						 | 
					ae510ff1ff | ||
| 
						 | 
					5b0654ad2c | ||
| 
						 | 
					466f97ecbe | ||
| 
						 | 
					27a14c22d7 | ||
| 
						 | 
					4709edcd1c | ||
| 
						 | 
					414de9f2eb | ||
| 
						 | 
					a53e7e7dab | ||
| 
						 | 
					7fa6628dc5 | ||
| 
						 | 
					62c25afea8 | ||
| 
						 | 
					481b622e3b | ||
| 
						 | 
					64f8f9a200 | ||
| 
						 | 
					0eca951465 | ||
| 
						 | 
					ef4e34c584 | ||
| 
						 | 
					d91acbc7ee | ||
| 
						 | 
					b397d1022e | ||
| 
						 | 
					b42a98aff5 | 
@@ -13,7 +13,7 @@
 | 
			
		||||
    <img src="https://img.shields.io/docker/pulls/mayflygo/mayfly-go.svg?label=docker%20pulls&color=fac858" alt="docker pulls"/>
 | 
			
		||||
  </a>
 | 
			
		||||
  <a href="https://github.com/golang/go" target="_blank">
 | 
			
		||||
    <img src="https://img.shields.io/badge/Golang-1.18%2B-yellow.svg" alt="golang"/>
 | 
			
		||||
    <img src="https://img.shields.io/badge/Golang-1.20%2B-yellow.svg" alt="golang"/>
 | 
			
		||||
  </a>
 | 
			
		||||
  <a href="https://cn.vuejs.org" target="_blank">
 | 
			
		||||
    <img src="https://img.shields.io/badge/Vue-3.x-green.svg" alt="vue">
 | 
			
		||||
@@ -35,15 +35,14 @@ web版 **linux(终端[终端回放] 文件 脚本 进程)、数据库(mysql po
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
### 系统相关资料
 | 
			
		||||
- 项目文档: https://objs.gitee.io/mayfly-go-docs
 | 
			
		||||
- 项目文档: https://www.yuque.com/may-fly/mayfly-go
 | 
			
		||||
- 系统操作视频: https://space.bilibili.com/484091081/channel/collectiondetail?sid=392854
 | 
			
		||||
- 部署文档:https://objs.gitee.io/mayfly-go-docs/download
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
### 系统核心功能截图
 | 
			
		||||
 | 
			
		||||
##### 记录操作记录
 | 
			
		||||

 | 
			
		||||

 | 
			
		||||
 | 
			
		||||
#### 机器操作
 | 
			
		||||
##### 状态查看
 | 
			
		||||
@@ -84,4 +83,4 @@ web版 **linux(终端[终端回放] 文件 脚本 进程)、数据库(mysql po
 | 
			
		||||

 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
**其他更多功能&操作指南可查看在线文档**:  https://objs.gitee.io/mayfly-go-docs
 | 
			
		||||
**其他更多功能&操作指南可查看在线文档**:  https://www.yuque.com/may-fly/mayfly-go
 | 
			
		||||
@@ -1,39 +1,39 @@
 | 
			
		||||
module.exports = {
 | 
			
		||||
	// 一行最多多少个字符
 | 
			
		||||
	printWidth: 150,
 | 
			
		||||
	// 指定每个缩进级别的空格数
 | 
			
		||||
	tabWidth: 4,
 | 
			
		||||
	// 使用制表符而不是空格缩进行
 | 
			
		||||
	useTabs: false,
 | 
			
		||||
	// 在语句末尾打印分号
 | 
			
		||||
	semi: true,
 | 
			
		||||
	// 使用单引号而不是双引号
 | 
			
		||||
	singleQuote: true,
 | 
			
		||||
	// 更改引用对象属性的时间 可选值"<as-needed|consistent|preserve>"
 | 
			
		||||
	quoteProps: 'as-needed',
 | 
			
		||||
	// 在JSX中使用单引号而不是双引号
 | 
			
		||||
	jsxSingleQuote: false,
 | 
			
		||||
	// 多行时尽可能打印尾随逗号。(例如,单行数组永远不会出现逗号结尾。) 可选值"<none|es5|all>",默认none
 | 
			
		||||
	trailingComma: 'es5',
 | 
			
		||||
	// 在对象文字中的括号之间打印空格
 | 
			
		||||
	bracketSpacing: true,
 | 
			
		||||
	// jsx 标签的反尖括号需要换行
 | 
			
		||||
	jsxBracketSameLine: false,
 | 
			
		||||
	// 在单独的箭头函数参数周围包括括号 always:(x) => x \ avoid:x => x
 | 
			
		||||
	arrowParens: 'always',
 | 
			
		||||
	// 这两个选项可用于格式化以给定字符偏移量(分别包括和不包括)开始和结束的代码
 | 
			
		||||
	rangeStart: 0,
 | 
			
		||||
	rangeEnd: Infinity,
 | 
			
		||||
	// 指定要使用的解析器,不需要写文件开头的 @prettier
 | 
			
		||||
	requirePragma: false,
 | 
			
		||||
	// 不需要自动在文件开头插入 @prettier
 | 
			
		||||
	insertPragma: false,
 | 
			
		||||
	// 使用默认的折行标准 always\never\preserve
 | 
			
		||||
	proseWrap: 'preserve',
 | 
			
		||||
	// 指定HTML文件的全局空格敏感度 css\strict\ignore
 | 
			
		||||
	htmlWhitespaceSensitivity: 'css',
 | 
			
		||||
	// Vue文件脚本和样式标签缩进
 | 
			
		||||
	vueIndentScriptAndStyle: false,
 | 
			
		||||
	// 换行符使用 lf 结尾是 可选值"<auto|lf|crlf|cr>"
 | 
			
		||||
	endOfLine: 'lf',
 | 
			
		||||
    // 一行最多多少个字符
 | 
			
		||||
    printWidth: 160,
 | 
			
		||||
    // 指定每个缩进级别的空格数
 | 
			
		||||
    tabWidth: 4,
 | 
			
		||||
    // 使用制表符而不是空格缩进行
 | 
			
		||||
    useTabs: false,
 | 
			
		||||
    // 在语句末尾打印分号
 | 
			
		||||
    semi: true,
 | 
			
		||||
    // 使用单引号而不是双引号
 | 
			
		||||
    singleQuote: true,
 | 
			
		||||
    // 更改引用对象属性的时间 可选值"<as-needed|consistent|preserve>"
 | 
			
		||||
    quoteProps: 'as-needed',
 | 
			
		||||
    // 在JSX中使用单引号而不是双引号
 | 
			
		||||
    jsxSingleQuote: false,
 | 
			
		||||
    // 多行时尽可能打印尾随逗号。(例如,单行数组永远不会出现逗号结尾。) 可选值"<none|es5|all>",默认none
 | 
			
		||||
    trailingComma: 'es5',
 | 
			
		||||
    // 在对象文字中的括号之间打印空格
 | 
			
		||||
    bracketSpacing: true,
 | 
			
		||||
    // jsx 标签的反尖括号需要换行
 | 
			
		||||
    jsxBracketSameLine: false,
 | 
			
		||||
    // 在单独的箭头函数参数周围包括括号 always:(x) => x \ avoid:x => x
 | 
			
		||||
    arrowParens: 'always',
 | 
			
		||||
    // 这两个选项可用于格式化以给定字符偏移量(分别包括和不包括)开始和结束的代码
 | 
			
		||||
    rangeStart: 0,
 | 
			
		||||
    rangeEnd: Infinity,
 | 
			
		||||
    // 指定要使用的解析器,不需要写文件开头的 @prettier
 | 
			
		||||
    requirePragma: false,
 | 
			
		||||
    // 不需要自动在文件开头插入 @prettier
 | 
			
		||||
    insertPragma: false,
 | 
			
		||||
    // 使用默认的折行标准 always\never\preserve
 | 
			
		||||
    proseWrap: 'preserve',
 | 
			
		||||
    // 指定HTML文件的全局空格敏感度 css\strict\ignore
 | 
			
		||||
    htmlWhitespaceSensitivity: 'css',
 | 
			
		||||
    // Vue文件脚本和样式标签缩进
 | 
			
		||||
    vueIndentScriptAndStyle: false,
 | 
			
		||||
    // 换行符使用 lf 结尾是 可选值"<auto|lf|crlf|cr>"
 | 
			
		||||
    endOfLine: 'lf',
 | 
			
		||||
};
 | 
			
		||||
 
 | 
			
		||||
@@ -10,26 +10,27 @@
 | 
			
		||||
  },
 | 
			
		||||
  "dependencies": {
 | 
			
		||||
    "@element-plus/icons-vue": "^2.1.0",
 | 
			
		||||
    "asciinema-player": "^3.3.0",
 | 
			
		||||
    "asciinema-player": "^3.5.0",
 | 
			
		||||
    "axios": "^1.4.0",
 | 
			
		||||
    "countup.js": "^2.0.7",
 | 
			
		||||
    "cropperjs": "^1.5.11",
 | 
			
		||||
    "echarts": "^5.4.0",
 | 
			
		||||
    "element-plus": "^2.3.5",
 | 
			
		||||
    "element-plus": "^2.3.8",
 | 
			
		||||
    "jsencrypt": "^3.3.1",
 | 
			
		||||
    "lodash": "^4.17.21",
 | 
			
		||||
    "mitt": "^3.0.0",
 | 
			
		||||
    "monaco-editor": "^0.38.0",
 | 
			
		||||
    "mitt": "^3.0.1",
 | 
			
		||||
    "monaco-editor": "^0.40.0",
 | 
			
		||||
    "monaco-sql-languages": "^0.11.0",
 | 
			
		||||
    "monaco-themes": "^0.4.4",
 | 
			
		||||
    "nprogress": "^0.2.0",
 | 
			
		||||
    "pinia": "^2.1.3",
 | 
			
		||||
    "pinia": "^2.1.4",
 | 
			
		||||
    "qrcode.vue": "^3.4.0",
 | 
			
		||||
    "screenfull": "^6.0.2",
 | 
			
		||||
    "sortablejs": "^1.13.0",
 | 
			
		||||
    "sql-formatter": "^12.1.2",
 | 
			
		||||
    "vue": "^3.3.4",
 | 
			
		||||
    "vue-clipboard3": "^1.0.1",
 | 
			
		||||
    "vue-router": "^4.2.2",
 | 
			
		||||
    "vue-router": "^4.2.4",
 | 
			
		||||
    "xterm": "^5.2.1",
 | 
			
		||||
    "xterm-addon-fit": "^0.7.0"
 | 
			
		||||
  },
 | 
			
		||||
@@ -49,7 +50,7 @@
 | 
			
		||||
    "sass": "^1.62.0",
 | 
			
		||||
    "sass-loader": "^13.2.0",
 | 
			
		||||
    "typescript": "^5.0.2",
 | 
			
		||||
    "vite": "^4.3.9",
 | 
			
		||||
    "vite": "^4.4.2",
 | 
			
		||||
    "vue-eslint-parser": "^9.1.1"
 | 
			
		||||
  },
 | 
			
		||||
  "browserslist": [
 | 
			
		||||
 
 | 
			
		||||
@@ -44,7 +44,7 @@ onMounted(() => {
 | 
			
		||||
        });
 | 
			
		||||
        // 获取缓存中的布局配置
 | 
			
		||||
        if (getLocal('themeConfig')) {
 | 
			
		||||
            themeConfigStores.setThemeConfig({ themeConfig: getLocal('themeConfig') })
 | 
			
		||||
            themeConfigStores.setThemeConfig({ themeConfig: getLocal('themeConfig') });
 | 
			
		||||
            document.documentElement.style.cssText = getLocal('themeConfigStyle');
 | 
			
		||||
        }
 | 
			
		||||
    });
 | 
			
		||||
@@ -52,7 +52,7 @@ onMounted(() => {
 | 
			
		||||
 | 
			
		||||
// 页面销毁时,关闭监听布局配置
 | 
			
		||||
onUnmounted(() => {
 | 
			
		||||
    mittBus.off('openSetingsDrawer', () => { });
 | 
			
		||||
    mittBus.off('openSetingsDrawer', () => {});
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
// 监听路由的变化,设置网站标题
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
import request from './request'
 | 
			
		||||
import request from './request';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 可用于各模块定义各自api请求
 | 
			
		||||
@@ -27,23 +27,13 @@ class Api {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 操作该权限,即请求对应的url
 | 
			
		||||
     * 请求对应的该api
 | 
			
		||||
     * @param {Object} param 请求该api的参数
 | 
			
		||||
     */
 | 
			
		||||
    request(param: any = null, options: any = null): Promise<any> {
 | 
			
		||||
        return request.send(this, param, options);
 | 
			
		||||
    request(param: any = null, options: any = null, headers: any = null): Promise<any> {
 | 
			
		||||
        return request.request(this.method, this.url, param, headers, options);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
    * 操作该权限,即请求对应的url
 | 
			
		||||
    * @param {Object} param 请求该api的参数
 | 
			
		||||
    * @param headers headers
 | 
			
		||||
    */
 | 
			
		||||
    requestWithHeaders(param: any, headers: any): Promise<any> {
 | 
			
		||||
        return request.sendWithHeaders(this, param, headers);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    /**    静态方法     **/
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
@@ -51,7 +41,7 @@ class Api {
 | 
			
		||||
     * @param url url
 | 
			
		||||
     * @param method 请求方法(get,post,put,delete...)
 | 
			
		||||
     */
 | 
			
		||||
    static create(url: string, method: string) :Api {
 | 
			
		||||
    static create(url: string, method: string): Api {
 | 
			
		||||
        return new Api(url, method);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -88,5 +78,4 @@ class Api {
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
export default Api
 | 
			
		||||
export default Api;
 | 
			
		||||
 
 | 
			
		||||
@@ -1,37 +1,94 @@
 | 
			
		||||
export interface EnumValueTag {
 | 
			
		||||
    color?: string;
 | 
			
		||||
    type?: string;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 枚举类
 | 
			
		||||
 * @author meilin.huang
 | 
			
		||||
 * 枚举值
 | 
			
		||||
 */
 | 
			
		||||
export class Enum {
 | 
			
		||||
export class EnumValue {
 | 
			
		||||
    /**
 | 
			
		||||
     * 添加枚举字段
 | 
			
		||||
     * 
 | 
			
		||||
     * @param {string} field  枚举字段名
 | 
			
		||||
     * @param {string} label  枚举名称
 | 
			
		||||
     * @param {Object} value  枚举值
 | 
			
		||||
     * 枚举值
 | 
			
		||||
     */
 | 
			
		||||
    add(field: string, label: string, value: any) {
 | 
			
		||||
        this[field] = { label, value }
 | 
			
		||||
        return this
 | 
			
		||||
    value: any;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 枚举描述
 | 
			
		||||
     */
 | 
			
		||||
    label: string;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 展示的标签信息
 | 
			
		||||
     */
 | 
			
		||||
    tag: EnumValueTag;
 | 
			
		||||
 | 
			
		||||
    constructor(value: any, label: string) {
 | 
			
		||||
        this.value = value;
 | 
			
		||||
        this.label = label;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    setTagType(type: string = 'primary'): EnumValue {
 | 
			
		||||
        this.tag = { type };
 | 
			
		||||
        return this;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    tagTypeInfo(): EnumValue {
 | 
			
		||||
        return this.setTagType('info');
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    tagTypeSuccess(): EnumValue {
 | 
			
		||||
        return this.setTagType('success');
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    tagTypeDanger(): EnumValue {
 | 
			
		||||
        return this.setTagType('danger');
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    tagTypeWarning(): EnumValue {
 | 
			
		||||
        return this.setTagType('warning');
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    setTagColor(color: string): EnumValue {
 | 
			
		||||
        this.tag = { color };
 | 
			
		||||
        return this;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static of(value: any, label: string): EnumValue {
 | 
			
		||||
        return new EnumValue(value, label);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 根据枚举value获取其label
 | 
			
		||||
     * 
 | 
			
		||||
     * @param {Object} value 
 | 
			
		||||
     * 根据枚举值获取指定枚举值对象
 | 
			
		||||
     *
 | 
			
		||||
     * @param enumValues 所有枚举值
 | 
			
		||||
     * @param value 需要匹配的枚举值
 | 
			
		||||
     * @returns 枚举值对象
 | 
			
		||||
     */
 | 
			
		||||
    getLabelByValue(value: any) {
 | 
			
		||||
        // 字段不存在返回‘’
 | 
			
		||||
        if (value === undefined || value === null) {
 | 
			
		||||
            return ''
 | 
			
		||||
        }
 | 
			
		||||
        for (const i in this) {
 | 
			
		||||
            const e: any = this[i]
 | 
			
		||||
            if (e && e.value === value) {
 | 
			
		||||
                return e.label
 | 
			
		||||
    static getEnumByValue(enumValues: EnumValue[], value: any): EnumValue | null {
 | 
			
		||||
        for (let enumValue of enumValues) {
 | 
			
		||||
            if (enumValue.value == value) {
 | 
			
		||||
                return enumValue;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        return null;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
        return ''
 | 
			
		||||
    /**
 | 
			
		||||
     * 根据枚举值获取枚举描述
 | 
			
		||||
     *
 | 
			
		||||
     * @param enums 枚举对象
 | 
			
		||||
     * @param value 枚举值
 | 
			
		||||
     * @returns 枚举描述
 | 
			
		||||
     */
 | 
			
		||||
    static getLabelByValue(enums: any, value: any) {
 | 
			
		||||
        const enumValues = Object.values(enums) as any;
 | 
			
		||||
        for (let enumValue of enumValues) {
 | 
			
		||||
            if (enumValue['value'] == value) {
 | 
			
		||||
                return enumValue['label'];
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        return '';
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default EnumValue;
 | 
			
		||||
 
 | 
			
		||||
@@ -1,46 +1,43 @@
 | 
			
		||||
class SocketBuilder {
 | 
			
		||||
 | 
			
		||||
    websocket: WebSocket;
 | 
			
		||||
  
 | 
			
		||||
    constructor(url: string) {
 | 
			
		||||
      if (typeof (WebSocket) === "undefined") {
 | 
			
		||||
        throw new Error('不支持websocket');
 | 
			
		||||
      }
 | 
			
		||||
      if (!url) {
 | 
			
		||||
        throw new Error('websocket url不能为空');
 | 
			
		||||
      }
 | 
			
		||||
      this.websocket = new WebSocket(url);
 | 
			
		||||
    }
 | 
			
		||||
  
 | 
			
		||||
    static builder(url: string) {
 | 
			
		||||
      return new SocketBuilder(url);
 | 
			
		||||
    }
 | 
			
		||||
  
 | 
			
		||||
    open(onopen: any) {
 | 
			
		||||
      this.websocket.onopen = onopen;
 | 
			
		||||
      return this;
 | 
			
		||||
    }
 | 
			
		||||
  
 | 
			
		||||
    error(onerror: any) {
 | 
			
		||||
      this.websocket.onerror = onerror;
 | 
			
		||||
      return this;
 | 
			
		||||
    }
 | 
			
		||||
  
 | 
			
		||||
    message(onmessage: any) {
 | 
			
		||||
      this.websocket.onmessage = onmessage;
 | 
			
		||||
      return this;
 | 
			
		||||
    }
 | 
			
		||||
  
 | 
			
		||||
    close(onclose: any) {
 | 
			
		||||
      this.websocket.onclose = onclose;
 | 
			
		||||
      return this;
 | 
			
		||||
    }
 | 
			
		||||
  
 | 
			
		||||
    build() {
 | 
			
		||||
      return this.websocket;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  
 | 
			
		||||
  export default SocketBuilder;
 | 
			
		||||
  
 | 
			
		||||
    constructor(url: string) {
 | 
			
		||||
        if (typeof WebSocket === 'undefined') {
 | 
			
		||||
            throw new Error('不支持websocket');
 | 
			
		||||
        }
 | 
			
		||||
        if (!url) {
 | 
			
		||||
            throw new Error('websocket url不能为空');
 | 
			
		||||
        }
 | 
			
		||||
        this.websocket = new WebSocket(url);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    static builder(url: string) {
 | 
			
		||||
        return new SocketBuilder(url);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    open(onopen: any) {
 | 
			
		||||
        this.websocket.onopen = onopen;
 | 
			
		||||
        return this;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    error(onerror: any) {
 | 
			
		||||
        this.websocket.onerror = onerror;
 | 
			
		||||
        return this;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    message(onmessage: any) {
 | 
			
		||||
        this.websocket.onmessage = onmessage;
 | 
			
		||||
        return this;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    close(onclose: any) {
 | 
			
		||||
        this.websocket.onclose = onclose;
 | 
			
		||||
        return this;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    build() {
 | 
			
		||||
        return this.websocket;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default SocketBuilder;
 | 
			
		||||
 
 | 
			
		||||
@@ -5,17 +5,17 @@ class AssertError extends Error {
 | 
			
		||||
    constructor(message: string) {
 | 
			
		||||
        super(message);
 | 
			
		||||
        // 错误类名
 | 
			
		||||
        this.name = "AssertError";
 | 
			
		||||
        this.name = 'AssertError';
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 断言表达式为true
 | 
			
		||||
 * 
 | 
			
		||||
 *
 | 
			
		||||
 * @param condition 条件表达式
 | 
			
		||||
 * @param msg 错误消息
 | 
			
		||||
 */
 | 
			
		||||
 export function isTrue(condition: boolean, msg: string) {
 | 
			
		||||
export function isTrue(condition: boolean, msg: string) {
 | 
			
		||||
    if (!condition) {
 | 
			
		||||
        throw new AssertError(msg);
 | 
			
		||||
    }
 | 
			
		||||
@@ -23,45 +23,45 @@ class AssertError extends Error {
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 断言不能为空值,即null,0,''等
 | 
			
		||||
 * 
 | 
			
		||||
 *
 | 
			
		||||
 * @param obj 对象1
 | 
			
		||||
 * @param msg 错误消息
 | 
			
		||||
 */
 | 
			
		||||
 export function notBlank(obj: any, msg: string) {
 | 
			
		||||
    isTrue(obj, msg)
 | 
			
		||||
export function notBlank(obj: any, msg: string) {
 | 
			
		||||
    isTrue(obj, msg);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 断言两对象相等
 | 
			
		||||
 * 
 | 
			
		||||
 *
 | 
			
		||||
 * @param obj1 对象1
 | 
			
		||||
 * @param obj2 对象2
 | 
			
		||||
 * @param msg 错误消息
 | 
			
		||||
 */
 | 
			
		||||
 export function isEquals(obj1: any, obj2: any, msg: string) {
 | 
			
		||||
export function isEquals(obj1: any, obj2: any, msg: string) {
 | 
			
		||||
    isTrue(obj1 === obj2, msg);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 断言对象不为null或undefiend
 | 
			
		||||
 * 
 | 
			
		||||
 *
 | 
			
		||||
 * @param obj 对象
 | 
			
		||||
 * @param msg 错误提示
 | 
			
		||||
 */
 | 
			
		||||
export function notNull(obj: any, msg: string) {
 | 
			
		||||
    if (obj == null || obj == undefined) {
 | 
			
		||||
        throw new AssertError(msg)
 | 
			
		||||
        throw new AssertError(msg);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
* 断言字符串不能为空
 | 
			
		||||
* 
 | 
			
		||||
* @param str 字符串
 | 
			
		||||
* @param msg 错误提示
 | 
			
		||||
*/
 | 
			
		||||
 * 断言字符串不能为空
 | 
			
		||||
 *
 | 
			
		||||
 * @param str 字符串
 | 
			
		||||
 * @param msg 错误提示
 | 
			
		||||
 */
 | 
			
		||||
export function notEmpty(str: string, msg: string) {
 | 
			
		||||
    if (str == null || str == undefined || str == '') {
 | 
			
		||||
        throw new AssertError(msg);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -11,7 +11,7 @@ const config = {
 | 
			
		||||
    baseWsUrl: `${(window as any).globalConfig.BaseWsUrl || `${location.protocol == 'https:' ? 'wss:' : 'ws:'}//${getBaseApiUrl()}`}/api`,
 | 
			
		||||
 | 
			
		||||
    // 系统版本
 | 
			
		||||
    version: 'v1.4.3'
 | 
			
		||||
}
 | 
			
		||||
    version: 'v1.5.0',
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export default config
 | 
			
		||||
export default config;
 | 
			
		||||
 
 | 
			
		||||
@@ -1,45 +1,38 @@
 | 
			
		||||
// import * as echarts from 'echarts'
 | 
			
		||||
 | 
			
		||||
// 引入 echarts 核心模块,核心模块提供了 echarts 使用必须要的接口。
 | 
			
		||||
import * as echarts from "echarts/core";
 | 
			
		||||
 
 | 
			
		||||
import * as echarts from 'echarts/core';
 | 
			
		||||
 | 
			
		||||
/** 图表后缀都为 Chart  */
 | 
			
		||||
import { PieChart } from "echarts/charts";
 | 
			
		||||
 
 | 
			
		||||
import { PieChart } from 'echarts/charts';
 | 
			
		||||
 | 
			
		||||
// 引入提示框,标题,直角坐标系,数据集,内置数据转换器组件,组件后缀都为 Component
 | 
			
		||||
import {
 | 
			
		||||
  TitleComponent,
 | 
			
		||||
  TooltipComponent,
 | 
			
		||||
  GridComponent,
 | 
			
		||||
  DatasetComponent,
 | 
			
		||||
  TransformComponent,
 | 
			
		||||
  LegendComponent,
 | 
			
		||||
} from "echarts/components";
 | 
			
		||||
 
 | 
			
		||||
import { TitleComponent, TooltipComponent, GridComponent, DatasetComponent, TransformComponent, LegendComponent } from 'echarts/components';
 | 
			
		||||
 | 
			
		||||
// 标签自动布局,全局过渡动画等特性
 | 
			
		||||
import { LabelLayout, UniversalTransition } from "echarts/features";
 | 
			
		||||
 
 | 
			
		||||
import { LabelLayout, UniversalTransition } from 'echarts/features';
 | 
			
		||||
 | 
			
		||||
// 引入 Canvas 渲染器,注意引入 CanvasRenderer 或者 SVGRenderer 是必须的一步
 | 
			
		||||
import { CanvasRenderer } from "echarts/renderers";
 | 
			
		||||
 
 | 
			
		||||
import { CanvasRenderer } from 'echarts/renderers';
 | 
			
		||||
 | 
			
		||||
// 注册必须的组件
 | 
			
		||||
echarts.use([
 | 
			
		||||
  TitleComponent,
 | 
			
		||||
  TooltipComponent,
 | 
			
		||||
  GridComponent,
 | 
			
		||||
  DatasetComponent,
 | 
			
		||||
  TransformComponent,
 | 
			
		||||
  LegendComponent,
 | 
			
		||||
//   BarChart,
 | 
			
		||||
  LabelLayout,
 | 
			
		||||
  UniversalTransition,
 | 
			
		||||
  CanvasRenderer,
 | 
			
		||||
//   LineChart,
 | 
			
		||||
  PieChart,
 | 
			
		||||
    TitleComponent,
 | 
			
		||||
    TooltipComponent,
 | 
			
		||||
    GridComponent,
 | 
			
		||||
    DatasetComponent,
 | 
			
		||||
    TransformComponent,
 | 
			
		||||
    LegendComponent,
 | 
			
		||||
    //   BarChart,
 | 
			
		||||
    LabelLayout,
 | 
			
		||||
    UniversalTransition,
 | 
			
		||||
    CanvasRenderer,
 | 
			
		||||
    //   LineChart,
 | 
			
		||||
    PieChart,
 | 
			
		||||
]);
 | 
			
		||||
 | 
			
		||||
export default function(dom: any, theme: any = null,  option: any) {
 | 
			
		||||
export default function (dom: any, theme: any = null, option: any) {
 | 
			
		||||
    let chart = echarts.init(dom, theme);
 | 
			
		||||
    chart.setOption(option);
 | 
			
		||||
    return chart;
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,27 +1,7 @@
 | 
			
		||||
interface BaseEnum {
 | 
			
		||||
    name: string
 | 
			
		||||
    value: any
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const success: BaseEnum = {
 | 
			
		||||
    name: 'success',
 | 
			
		||||
    value: 200
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export enum ResultEnum {
 | 
			
		||||
    SUCCESS = 200,
 | 
			
		||||
    ERROR = 400,
 | 
			
		||||
    PARAM_ERROR = 405,
 | 
			
		||||
    SERVER_ERROR = 500,
 | 
			
		||||
    NO_PERMISSION = 501
 | 
			
		||||
    NO_PERMISSION = 501,
 | 
			
		||||
}
 | 
			
		||||
// /**
 | 
			
		||||
//  * 全局公共枚举类
 | 
			
		||||
//  */
 | 
			
		||||
// export default {
 | 
			
		||||
//   // uri请求方法
 | 
			
		||||
//   requestMethod: new Enum().add('GET', 'GET', 1).add('POST', 'POST', 2).add('PUT', 'PUT', 3).add('DELETE', 'DELETE', 4),
 | 
			
		||||
//   // 结果枚举
 | 
			
		||||
//   ResultEnum: new Enum().add('SUCCESS', '操作成功', 200).add('ERROR', '操作失败', 400).add('PARAM_ERROR', '参数错误', 405).add('SERVER_ERROR', '服务器异常', 500)
 | 
			
		||||
//     .add('NO_PERMISSION', '没有权限', 501)
 | 
			
		||||
// }
 | 
			
		||||
@@ -1,11 +1,12 @@
 | 
			
		||||
import Api from './Api'
 | 
			
		||||
import request from './request';
 | 
			
		||||
 | 
			
		||||
export default {
 | 
			
		||||
    login: Api.newPost("/sys/accounts/login"),
 | 
			
		||||
    changePwd: Api.newPost("/sys/accounts/change-pwd"),
 | 
			
		||||
    getPublicKey: Api.newGet("/common/public-key"),
 | 
			
		||||
    getConfigValue: Api.newGet("/sys/configs/value"),
 | 
			
		||||
    captcha: Api.newGet("/sys/captcha"),
 | 
			
		||||
    logout: Api.newPost("/sys/accounts/logout/{token}"),
 | 
			
		||||
    getPermissions: Api.newGet("/sys/accounts/permissions")
 | 
			
		||||
}
 | 
			
		||||
    login: (param: any) => request.post('/sys/accounts/login', param),
 | 
			
		||||
    otpVerify: (param: any) => request.post('/sys/accounts/otp-verify', param),
 | 
			
		||||
    changePwd: (param: any) => request.post('/sys/accounts/change-pwd', param),
 | 
			
		||||
    getPublicKey: () => request.get('/common/public-key'),
 | 
			
		||||
    getConfigValue: (params: any) => request.get('/sys/configs/value', params),
 | 
			
		||||
    captcha: () => request.get('/sys/captcha'),
 | 
			
		||||
    logout: () => request.post('/sys/accounts/logout/{token}'),
 | 
			
		||||
    getPermissions: () => request.get('/sys/accounts/permissions'),
 | 
			
		||||
};
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,6 @@
 | 
			
		||||
import router from "../router";
 | 
			
		||||
import router from '../router';
 | 
			
		||||
import Axios from 'axios';
 | 
			
		||||
import { ResultEnum } from './enums'
 | 
			
		||||
import Api from './Api';
 | 
			
		||||
import { ResultEnum } from './enums';
 | 
			
		||||
import config from './config';
 | 
			
		||||
import { getSession } from './utils/storage';
 | 
			
		||||
import { templateResolve } from './utils/string';
 | 
			
		||||
@@ -22,8 +21,8 @@ export interface Result {
 | 
			
		||||
    data?: any;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const baseUrl: string = config.baseApiUrl
 | 
			
		||||
const baseWsUrl: string = config.baseWsUrl
 | 
			
		||||
const baseUrl: string = config.baseApiUrl;
 | 
			
		||||
const baseWsUrl: string = config.baseWsUrl;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 通知错误消息
 | 
			
		||||
@@ -37,28 +36,28 @@ function notifyErrorMsg(msg: string) {
 | 
			
		||||
// create an axios instance
 | 
			
		||||
const service = Axios.create({
 | 
			
		||||
    baseURL: baseUrl, // url = base url + request url
 | 
			
		||||
    timeout: 20000 // request timeout
 | 
			
		||||
})
 | 
			
		||||
    timeout: 20000, // request timeout
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
// request interceptor
 | 
			
		||||
service.interceptors.request.use(
 | 
			
		||||
    (config: any) => {
 | 
			
		||||
        // do something before request is sent
 | 
			
		||||
        const token = getSession("token")
 | 
			
		||||
        const token = getSession('token');
 | 
			
		||||
        if (token) {
 | 
			
		||||
            // 设置token
 | 
			
		||||
            config.headers['Authorization'] = token
 | 
			
		||||
            config.headers['Authorization'] = token;
 | 
			
		||||
        }
 | 
			
		||||
        return config
 | 
			
		||||
        return config;
 | 
			
		||||
    },
 | 
			
		||||
    error => {
 | 
			
		||||
        return Promise.reject(error)
 | 
			
		||||
    (error) => {
 | 
			
		||||
        return Promise.reject(error);
 | 
			
		||||
    }
 | 
			
		||||
)
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
// response interceptor
 | 
			
		||||
service.interceptors.response.use(
 | 
			
		||||
    response => {
 | 
			
		||||
    (response) => {
 | 
			
		||||
        // 获取请求返回结果
 | 
			
		||||
        const data: Result = response.data;
 | 
			
		||||
        // 如果提示没有权限,则移除token,使其重新登录
 | 
			
		||||
@@ -88,32 +87,31 @@ service.interceptors.response.use(
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return Promise.reject(e)
 | 
			
		||||
        return Promise.reject(e);
 | 
			
		||||
    }
 | 
			
		||||
)
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 请求uri
 | 
			
		||||
 * 该方法已处理请求结果中code != 200的message提示,如需其他错误处理(取消加载状态,重置对象状态等等),可catch继续处理
 | 
			
		||||
 * 
 | 
			
		||||
 *
 | 
			
		||||
 * @param {Object} method 请求方法(GET,POST,PUT,DELTE等)
 | 
			
		||||
 * @param {Object} uri    uri
 | 
			
		||||
 * @param {Object} params 参数
 | 
			
		||||
 */
 | 
			
		||||
function request(method: string, url: string, params: any = null, headers: any = null, options: any = null): Promise<any> {
 | 
			
		||||
    if (!url)
 | 
			
		||||
        throw new Error('请求url不能为空');
 | 
			
		||||
    if (!url) throw new Error('请求url不能为空');
 | 
			
		||||
    // 简单判断该url是否是restful风格
 | 
			
		||||
    if (url.indexOf("{") != -1) {
 | 
			
		||||
    if (url.indexOf('{') != -1) {
 | 
			
		||||
        url = templateResolve(url, params);
 | 
			
		||||
    }
 | 
			
		||||
    const query: any = {
 | 
			
		||||
        method,
 | 
			
		||||
        url: url,
 | 
			
		||||
        ...options
 | 
			
		||||
        ...options,
 | 
			
		||||
    };
 | 
			
		||||
    if (headers) {
 | 
			
		||||
        query.headers = headers
 | 
			
		||||
        query.headers = headers;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // post和put使用json格式传参
 | 
			
		||||
@@ -122,32 +120,39 @@ function request(method: string, url: string, params: any = null, headers: any =
 | 
			
		||||
    } else {
 | 
			
		||||
        query.params = params;
 | 
			
		||||
    }
 | 
			
		||||
    return service.request(query).then(res => res)
 | 
			
		||||
        .catch(e => {
 | 
			
		||||
    return service
 | 
			
		||||
        .request(query)
 | 
			
		||||
        .then((res) => res)
 | 
			
		||||
        .catch((e) => {
 | 
			
		||||
            // 如果返回的code不为成功,则会返回对应的错误msg,则直接统一通知即可
 | 
			
		||||
            if (e.msg) {
 | 
			
		||||
                notifyErrorMsg(e.msg)
 | 
			
		||||
                notifyErrorMsg(e.msg);
 | 
			
		||||
            }
 | 
			
		||||
            return Promise.reject(e);
 | 
			
		||||
        });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 根据api执行对应接口
 | 
			
		||||
 * @param api Api实例
 | 
			
		||||
 * @param params 请求参数
 | 
			
		||||
 * get请求uri
 | 
			
		||||
 * 该方法已处理请求结果中code != 200的message提示,如需其他错误处理(取消加载状态,重置对象状态等等),可catch继续处理
 | 
			
		||||
 *
 | 
			
		||||
 * @param {Object} url   uri
 | 
			
		||||
 * @param {Object} params 参数
 | 
			
		||||
 */
 | 
			
		||||
function send(api: Api, params: any, options: any): Promise<any> {
 | 
			
		||||
    return request(api.method, api.url, params, null, options);
 | 
			
		||||
function get(url: string, params: any = null, headers: any = null, options: any = null): Promise<any> {
 | 
			
		||||
    return request('get', url, params, headers, options);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 根据api执行对应接口
 | 
			
		||||
 * @param api Api实例
 | 
			
		||||
 * @param params 请求参数
 | 
			
		||||
 */
 | 
			
		||||
function sendWithHeaders(api: Api, params: any, headers: any): Promise<any> {
 | 
			
		||||
    return request(api.method, api.url, params, headers, null);
 | 
			
		||||
function post(url: string, params: any = null, headers: any = null, options: any = null): Promise<any> {
 | 
			
		||||
    return request('post', url, params, headers, options);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function put(url: string, params: any = null, headers: any = null, options: any = null): Promise<any> {
 | 
			
		||||
    return request('put', url, params, headers, options);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function del(url: string, params: any = null, headers: any = null, options: any = null): Promise<any> {
 | 
			
		||||
    return request('delete', url, params, headers, options);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function getApiUrl(url: string) {
 | 
			
		||||
@@ -155,10 +160,11 @@ function getApiUrl(url: string) {
 | 
			
		||||
    return baseUrl + url + '?token=' + getSession('token');
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
export default {
 | 
			
		||||
    request,
 | 
			
		||||
    send,
 | 
			
		||||
    sendWithHeaders,
 | 
			
		||||
    getApiUrl
 | 
			
		||||
}
 | 
			
		||||
    get,
 | 
			
		||||
    post,
 | 
			
		||||
    put,
 | 
			
		||||
    del,
 | 
			
		||||
    getApiUrl,
 | 
			
		||||
};
 | 
			
		||||
 
 | 
			
		||||
@@ -1,36 +1,36 @@
 | 
			
		||||
import openApi from './openApi';
 | 
			
		||||
import JSEncrypt from 'jsencrypt'
 | 
			
		||||
import JSEncrypt from 'jsencrypt';
 | 
			
		||||
import { notBlank } from './assert';
 | 
			
		||||
 | 
			
		||||
var encryptor: any = null
 | 
			
		||||
var encryptor: any = null;
 | 
			
		||||
 | 
			
		||||
export async function getRsaPublicKey() {
 | 
			
		||||
    let publicKey = sessionStorage.getItem('RsaPublicKey')
 | 
			
		||||
    let publicKey = sessionStorage.getItem('RsaPublicKey');
 | 
			
		||||
    if (publicKey) {
 | 
			
		||||
        return publicKey
 | 
			
		||||
        return publicKey;
 | 
			
		||||
    }
 | 
			
		||||
    publicKey = await openApi.getPublicKey.request() as string
 | 
			
		||||
    sessionStorage.setItem('RsaPublicKey', publicKey)
 | 
			
		||||
    return publicKey
 | 
			
		||||
    publicKey = (await openApi.getPublicKey()) as string;
 | 
			
		||||
    sessionStorage.setItem('RsaPublicKey', publicKey);
 | 
			
		||||
    return publicKey;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 公钥加密指定值
 | 
			
		||||
 * 
 | 
			
		||||
 *
 | 
			
		||||
 * @param value value
 | 
			
		||||
 * @returns 加密后的值
 | 
			
		||||
 */
 | 
			
		||||
export async function RsaEncrypt(value: any) {
 | 
			
		||||
    // 不存在则返回空值
 | 
			
		||||
    if (!value) {
 | 
			
		||||
        return ""
 | 
			
		||||
        return '';
 | 
			
		||||
    }
 | 
			
		||||
    if (encryptor != null) {
 | 
			
		||||
        return encryptor.encrypt(value)
 | 
			
		||||
        return encryptor.encrypt(value);
 | 
			
		||||
    }
 | 
			
		||||
    encryptor = new JSEncrypt()
 | 
			
		||||
    const publicKey = await getRsaPublicKey() as string;
 | 
			
		||||
    notBlank(publicKey, "获取公钥失败")
 | 
			
		||||
    encryptor.setPublicKey(publicKey)//设置公钥
 | 
			
		||||
    return encryptor.encrypt(value)
 | 
			
		||||
}
 | 
			
		||||
    encryptor = new JSEncrypt();
 | 
			
		||||
    const publicKey = (await getRsaPublicKey()) as string;
 | 
			
		||||
    notBlank(publicKey, '获取公钥失败');
 | 
			
		||||
    encryptor.setPublicKey(publicKey); //设置公钥
 | 
			
		||||
    return encryptor.encrypt(value);
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,8 +1,7 @@
 | 
			
		||||
 | 
			
		||||
import Config from './config'
 | 
			
		||||
import { ElNotification } from 'element-plus'
 | 
			
		||||
import Config from './config';
 | 
			
		||||
import { ElNotification } from 'element-plus';
 | 
			
		||||
import SocketBuilder from './SocketBuilder';
 | 
			
		||||
import { getSession } from '@/common/utils/storage.ts';
 | 
			
		||||
import { getSession } from '@/common/utils/storage';
 | 
			
		||||
 | 
			
		||||
export default {
 | 
			
		||||
    /**
 | 
			
		||||
@@ -38,8 +37,9 @@ export default {
 | 
			
		||||
                    title: message.title,
 | 
			
		||||
                    message: message.msg,
 | 
			
		||||
                    type: mtype as any,
 | 
			
		||||
                })
 | 
			
		||||
                });
 | 
			
		||||
            })
 | 
			
		||||
            .open((event: any) => console.log(event)).build();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
            .open((event: any) => console.log(event))
 | 
			
		||||
            .build();
 | 
			
		||||
    },
 | 
			
		||||
};
 | 
			
		||||
 
 | 
			
		||||
@@ -1,48 +1,69 @@
 | 
			
		||||
import openApi from './openApi';
 | 
			
		||||
 | 
			
		||||
// 登录是否使用验证码配置key
 | 
			
		||||
const UseLoginCaptchaConfigKey = "UseLoginCaptcha"
 | 
			
		||||
const UseWartermarkConfigKey = "UseWartermark"
 | 
			
		||||
const AccountLoginSecurity = 'AccountLoginSecurity';
 | 
			
		||||
const UseLoginCaptchaConfigKey = 'UseLoginCaptcha';
 | 
			
		||||
const UseWartermarkConfigKey = 'UseWartermark';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 获取系统配置值
 | 
			
		||||
 * 
 | 
			
		||||
 *
 | 
			
		||||
 * @param key 配置key
 | 
			
		||||
 * @returns 配置值
 | 
			
		||||
 */
 | 
			
		||||
export async function getConfigValue(key: string) : Promise<string> {
 | 
			
		||||
    return await openApi.getConfigValue.request({key}) as string
 | 
			
		||||
export async function getConfigValue(key: string): Promise<string> {
 | 
			
		||||
    return (await openApi.getConfigValue({ key })) as string;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 获取bool类型系统配置值
 | 
			
		||||
 * 
 | 
			
		||||
 *
 | 
			
		||||
 * @param key 配置key
 | 
			
		||||
 * @param defaultValue 默认值
 | 
			
		||||
 * @returns 是否为ture,1: true;其他: false
 | 
			
		||||
 */
 | 
			
		||||
export async function getBoolConfigValue(key :string, defaultValue :boolean) : Promise<boolean> {
 | 
			
		||||
    const value = await getConfigValue(key)
 | 
			
		||||
export async function getBoolConfigValue(key: string, defaultValue: boolean): Promise<boolean> {
 | 
			
		||||
    const value = await getConfigValue(key);
 | 
			
		||||
    return convertBool(value, defaultValue);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 获取账号登录安全配置
 | 
			
		||||
 *
 | 
			
		||||
 * @returns
 | 
			
		||||
 */
 | 
			
		||||
export async function getAccountLoginSecurity(): Promise<any> {
 | 
			
		||||
    const value = await getConfigValue(AccountLoginSecurity);
 | 
			
		||||
    if (!value) {
 | 
			
		||||
        return defaultValue;
 | 
			
		||||
        return null;
 | 
			
		||||
    }
 | 
			
		||||
    return value == "1";
 | 
			
		||||
    const jsonValue = JSON.parse(value);
 | 
			
		||||
    jsonValue.useCaptcha = convertBool(jsonValue.useCaptcha, true);
 | 
			
		||||
    jsonValue.useOtp = convertBool(jsonValue.useOtp, true);
 | 
			
		||||
    return jsonValue;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 是否使用登录验证码
 | 
			
		||||
 * 
 | 
			
		||||
 * @returns 
 | 
			
		||||
 *
 | 
			
		||||
 * @returns
 | 
			
		||||
 */
 | 
			
		||||
export async function useLoginCaptcha() : Promise<boolean> {
 | 
			
		||||
    return await getBoolConfigValue(UseLoginCaptchaConfigKey, true)
 | 
			
		||||
export async function useLoginCaptcha(): Promise<boolean> {
 | 
			
		||||
    return await getBoolConfigValue(UseLoginCaptchaConfigKey, true);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 是否启用水印
 | 
			
		||||
 * 
 | 
			
		||||
 * @returns 
 | 
			
		||||
 *
 | 
			
		||||
 * @returns
 | 
			
		||||
 */
 | 
			
		||||
 export async function useWartermark() : Promise<boolean> {
 | 
			
		||||
    return await getBoolConfigValue(UseWartermarkConfigKey, true)
 | 
			
		||||
}
 | 
			
		||||
export async function useWartermark(): Promise<boolean> {
 | 
			
		||||
    return await getBoolConfigValue(UseWartermarkConfigKey, true);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function convertBool(value: string, defaultValue: boolean) {
 | 
			
		||||
    if (!value) {
 | 
			
		||||
        return defaultValue;
 | 
			
		||||
    }
 | 
			
		||||
    return value == '1' || value == 'true';
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,27 +1,27 @@
 | 
			
		||||
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()          // 秒
 | 
			
		||||
        '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(), // 秒
 | 
			
		||||
        // 有其他格式化字符需求可以继续添加,必须转化成字符串
 | 
			
		||||
    };
 | 
			
		||||
    for (const k in opt) {
 | 
			
		||||
        ret = new RegExp("(" + k + ")").exec(fmt);
 | 
			
		||||
        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")))
 | 
			
		||||
            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))
 | 
			
		||||
    return dateFormat2(fmt, new Date(dateStr));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function dateFormat(dateStr: string) {
 | 
			
		||||
    return dateFormat2('yyyy-MM-dd HH:mm:ss',new Date(dateStr))
 | 
			
		||||
}
 | 
			
		||||
    return dateFormat2('yyyy-MM-dd HH:mm:ss', new Date(dateStr));
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -7,11 +7,11 @@ export function exportCsv(filename: string, columns: string[], datas: []) {
 | 
			
		||||
        for (let column of columns) {
 | 
			
		||||
            let val: any = data[column];
 | 
			
		||||
            if (typeof val == 'string' && val) {
 | 
			
		||||
                // csv格式如果有逗号,整体用双引号括起来;如果里面还有双引号就替换成两个双引号,这样导出来的格式就不会有问题了 
 | 
			
		||||
                // csv格式如果有逗号,整体用双引号括起来;如果里面还有双引号就替换成两个双引号,这样导出来的格式就不会有问题了
 | 
			
		||||
                if (val.indexOf(',') != -1) {
 | 
			
		||||
                    // 如果还有双引号,先将双引号转义,避免两边加了双引号后转义错误
 | 
			
		||||
                    if (val.indexOf('"') != -1) {
 | 
			
		||||
                        val = val.replace(/\"/g, "\"\"");
 | 
			
		||||
                        val = val.replace(/\"/g, '""');
 | 
			
		||||
                    }
 | 
			
		||||
                    // 再将逗号转义
 | 
			
		||||
                    val = `"${val}"`;
 | 
			
		||||
@@ -20,7 +20,6 @@ export function exportCsv(filename: string, columns: string[], datas: []) {
 | 
			
		||||
            } else {
 | 
			
		||||
                dataValueArr.push(val);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
        }
 | 
			
		||||
        cvsData.push(dataValueArr);
 | 
			
		||||
    }
 | 
			
		||||
@@ -36,4 +35,4 @@ export function exportCsv(filename: string, columns: string[], datas: []) {
 | 
			
		||||
    link.setAttribute('download', `${filename}.csv`);
 | 
			
		||||
    document.body.appendChild(link);
 | 
			
		||||
    link.click();
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,7 @@
 | 
			
		||||
/**
 | 
			
		||||
 * 格式化字节单位
 | 
			
		||||
 * @param size byte size
 | 
			
		||||
 * @returns 
 | 
			
		||||
 * @returns
 | 
			
		||||
 */
 | 
			
		||||
export function formatByteSize(size: any) {
 | 
			
		||||
    const value = Number(size);
 | 
			
		||||
@@ -116,8 +116,7 @@ export function formatDate(date: Date, format: string) {
 | 
			
		||||
        '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 (/(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);
 | 
			
		||||
@@ -192,4 +191,4 @@ export function formatAxis(param: any) {
 | 
			
		||||
    else if (hour < 19) return '傍晚好';
 | 
			
		||||
    else if (hour < 22) return '晚上好';
 | 
			
		||||
    else return '夜里好';
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,5 @@
 | 
			
		||||
import { nextTick } from 'vue';
 | 
			
		||||
import loadingCss from "@/theme/loading.scss?inline"
 | 
			
		||||
import loadingCss from '@/theme/loading.scss?inline';
 | 
			
		||||
 | 
			
		||||
// 定义方法
 | 
			
		||||
export const NextLoading = {
 | 
			
		||||
 
 | 
			
		||||
@@ -5,4 +5,4 @@ import mitt, { Emitter } from 'mitt';
 | 
			
		||||
const emitter: Emitter<any> = mitt<any>();
 | 
			
		||||
 | 
			
		||||
// 导出
 | 
			
		||||
export default emitter;
 | 
			
		||||
export default emitter;
 | 
			
		||||
 
 | 
			
		||||
@@ -1,30 +1,28 @@
 | 
			
		||||
// 字体图标 url
 | 
			
		||||
const cssCdnUrlList: Array<string> = [
 | 
			
		||||
	
 | 
			
		||||
];
 | 
			
		||||
const cssCdnUrlList: Array<string> = [];
 | 
			
		||||
// 第三方 js url
 | 
			
		||||
const jsCdnUrlList: Array<string> = [];
 | 
			
		||||
 | 
			
		||||
// 动态批量设置字体图标
 | 
			
		||||
export function setCssCdn() {
 | 
			
		||||
	if (cssCdnUrlList.length <= 0) return false;
 | 
			
		||||
	cssCdnUrlList.map((v) => {
 | 
			
		||||
		let link = document.createElement('link');
 | 
			
		||||
		link.rel = 'stylesheet';
 | 
			
		||||
		link.href = v;
 | 
			
		||||
		link.crossOrigin = 'anonymous';
 | 
			
		||||
		document.getElementsByTagName('head')[0].appendChild(link);
 | 
			
		||||
	});
 | 
			
		||||
    if (cssCdnUrlList.length <= 0) return false;
 | 
			
		||||
    cssCdnUrlList.map((v) => {
 | 
			
		||||
        let link = document.createElement('link');
 | 
			
		||||
        link.rel = 'stylesheet';
 | 
			
		||||
        link.href = v;
 | 
			
		||||
        link.crossOrigin = 'anonymous';
 | 
			
		||||
        document.getElementsByTagName('head')[0].appendChild(link);
 | 
			
		||||
    });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 动态批量设置第三方js
 | 
			
		||||
export function setJsCdn() {
 | 
			
		||||
	if (jsCdnUrlList.length <= 0) return false;
 | 
			
		||||
	jsCdnUrlList.map((v) => {
 | 
			
		||||
		let link = document.createElement('script');
 | 
			
		||||
		link.src = v;
 | 
			
		||||
		document.body.appendChild(link);
 | 
			
		||||
	});
 | 
			
		||||
    if (jsCdnUrlList.length <= 0) return false;
 | 
			
		||||
    jsCdnUrlList.map((v) => {
 | 
			
		||||
        let link = document.createElement('script');
 | 
			
		||||
        link.src = v;
 | 
			
		||||
        document.body.appendChild(link);
 | 
			
		||||
    });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
@@ -33,15 +31,15 @@ export function setJsCdn() {
 | 
			
		||||
 * @method jsCdn 动态批量设置第三方js
 | 
			
		||||
 */
 | 
			
		||||
const setIntroduction = {
 | 
			
		||||
	// 设置css
 | 
			
		||||
	cssCdn: () => {
 | 
			
		||||
		setCssCdn();
 | 
			
		||||
	},
 | 
			
		||||
	// 设置js
 | 
			
		||||
	jsCdn: () => {
 | 
			
		||||
		setJsCdn();
 | 
			
		||||
	},
 | 
			
		||||
    // 设置css
 | 
			
		||||
    cssCdn: () => {
 | 
			
		||||
        setCssCdn();
 | 
			
		||||
    },
 | 
			
		||||
    // 设置js
 | 
			
		||||
    jsCdn: () => {
 | 
			
		||||
        setJsCdn();
 | 
			
		||||
    },
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// 导出函数方法
 | 
			
		||||
export default setIntroduction;
 | 
			
		||||
export default setIntroduction;
 | 
			
		||||
 
 | 
			
		||||
@@ -36,21 +36,19 @@ export function clearSession() {
 | 
			
		||||
    window.sessionStorage.clear();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
export function getUserInfo4Session() {
 | 
			
		||||
    return getSession("userInfo")
 | 
			
		||||
    return getSession('userInfo');
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function setUserInfo2Session(userinfo: any) {
 | 
			
		||||
    setSession("userInfo", userinfo)
 | 
			
		||||
    setSession('userInfo', userinfo);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 获取是否开启水印
 | 
			
		||||
export function getUseWatermark4Session() {
 | 
			
		||||
    return getSession("useWatermark")
 | 
			
		||||
    return getSession('useWatermark');
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function setUseWatermark2Session(useWatermark: boolean) {
 | 
			
		||||
    setSession("useWatermark", useWatermark)
 | 
			
		||||
}
 | 
			
		||||
    setSession('useWatermark', useWatermark);
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -3,7 +3,7 @@
 | 
			
		||||
 * 解析后为 hahahahh_1
 | 
			
		||||
 * @param template 模板字符串
 | 
			
		||||
 * @param param   参数占位符
 | 
			
		||||
 * @returns 
 | 
			
		||||
 * @returns
 | 
			
		||||
 */
 | 
			
		||||
export function templateResolve(template: string, param: any) {
 | 
			
		||||
    return template.replace(/\{\w+\}/g, (word) => {
 | 
			
		||||
@@ -12,7 +12,7 @@ export function templateResolve(template: string, param: any) {
 | 
			
		||||
        if (value != null || value != undefined) {
 | 
			
		||||
            return value;
 | 
			
		||||
        }
 | 
			
		||||
        return "";
 | 
			
		||||
        return '';
 | 
			
		||||
    });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -21,11 +21,34 @@ export function letterAvatar(name: string, size = 60, color = '') {
 | 
			
		||||
    name = name || '';
 | 
			
		||||
    size = size || 60;
 | 
			
		||||
    var colours = [
 | 
			
		||||
        "#1abc9c", "#2ecc71", "#3498db", "#9b59b6", "#34495e", "#16a085", "#27ae60", "#2980b9", "#8e44ad", "#2c3e50",
 | 
			
		||||
        "#f1c40f", "#e67e22", "#e74c3c", "#00bcd4", "#95a5a6", "#f39c12", "#d35400", "#c0392b", "#bdc3c7", "#7f8c8d"
 | 
			
		||||
    ],
 | 
			
		||||
            '#1abc9c',
 | 
			
		||||
            '#2ecc71',
 | 
			
		||||
            '#3498db',
 | 
			
		||||
            '#9b59b6',
 | 
			
		||||
            '#34495e',
 | 
			
		||||
            '#16a085',
 | 
			
		||||
            '#27ae60',
 | 
			
		||||
            '#2980b9',
 | 
			
		||||
            '#8e44ad',
 | 
			
		||||
            '#2c3e50',
 | 
			
		||||
            '#f1c40f',
 | 
			
		||||
            '#e67e22',
 | 
			
		||||
            '#e74c3c',
 | 
			
		||||
            '#00bcd4',
 | 
			
		||||
            '#95a5a6',
 | 
			
		||||
            '#f39c12',
 | 
			
		||||
            '#d35400',
 | 
			
		||||
            '#c0392b',
 | 
			
		||||
            '#bdc3c7',
 | 
			
		||||
            '#7f8c8d',
 | 
			
		||||
        ],
 | 
			
		||||
        nameSplit = String(name).split(' '),
 | 
			
		||||
        initials, charIndex, colourIndex, canvas, context, dataURI;
 | 
			
		||||
        initials,
 | 
			
		||||
        charIndex,
 | 
			
		||||
        colourIndex,
 | 
			
		||||
        canvas,
 | 
			
		||||
        context,
 | 
			
		||||
        dataURI;
 | 
			
		||||
 | 
			
		||||
    if (nameSplit.length == 1) {
 | 
			
		||||
        initials = nameSplit[0] ? nameSplit[0].charAt(0) : '?';
 | 
			
		||||
@@ -33,23 +56,76 @@ export function letterAvatar(name: string, size = 60, color = '') {
 | 
			
		||||
        initials = nameSplit[0].charAt(0) + nameSplit[1].charAt(0);
 | 
			
		||||
    }
 | 
			
		||||
    if (window.devicePixelRatio) {
 | 
			
		||||
        size = (size * window.devicePixelRatio);
 | 
			
		||||
        size = size * window.devicePixelRatio;
 | 
			
		||||
    }
 | 
			
		||||
    initials = initials.toLocaleUpperCase()
 | 
			
		||||
    initials = initials.toLocaleUpperCase();
 | 
			
		||||
    charIndex = (initials == '?' ? 72 : initials.charCodeAt(0)) - 64;
 | 
			
		||||
    colourIndex = charIndex % 20;
 | 
			
		||||
    canvas = document.createElement('canvas');
 | 
			
		||||
    canvas.width = size;
 | 
			
		||||
    canvas.height = size;
 | 
			
		||||
    context = canvas.getContext("2d") as any;
 | 
			
		||||
    context = canvas.getContext('2d') as any;
 | 
			
		||||
 | 
			
		||||
    context.fillStyle = color ? color : colours[colourIndex - 1];
 | 
			
		||||
    context.fillRect(0, 0, canvas.width, canvas.height);
 | 
			
		||||
    context.font = Math.round(canvas.width / 2) + "px 'Microsoft Yahei'";
 | 
			
		||||
    context.textAlign = "center";
 | 
			
		||||
    context.fillStyle = "#FFF";
 | 
			
		||||
    context.textAlign = 'center';
 | 
			
		||||
    context.fillStyle = '#FFF';
 | 
			
		||||
    context.fillText(initials, size / 2, size / 1.5);
 | 
			
		||||
    dataURI = canvas.toDataURL();
 | 
			
		||||
    canvas = null;
 | 
			
		||||
    return dataURI;
 | 
			
		||||
};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 计算文本所占用的宽度(px) -> 该种方式较为准确
 | 
			
		||||
 * 使用span标签包裹内容,然后计算span的宽度 width: px
 | 
			
		||||
 * @param str
 | 
			
		||||
 */
 | 
			
		||||
export function getTextWidth(str: string) {
 | 
			
		||||
    let width = 0;
 | 
			
		||||
    let html = document.createElement('span');
 | 
			
		||||
    html.innerText = str;
 | 
			
		||||
    html.className = 'getTextWidth';
 | 
			
		||||
    document?.querySelector('body')?.appendChild(html);
 | 
			
		||||
    width = (document?.querySelector('.getTextWidth') as any).offsetWidth;
 | 
			
		||||
    document?.querySelector('.getTextWidth')?.remove();
 | 
			
		||||
    return width;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 获取内容所需要占用的宽度
 | 
			
		||||
 */
 | 
			
		||||
export function getContentWidth(content: any): number {
 | 
			
		||||
    if (!content) {
 | 
			
		||||
        return 50;
 | 
			
		||||
    }
 | 
			
		||||
    // 以下分配的单位长度可根据实际需求进行调整
 | 
			
		||||
    let flexWidth = 0;
 | 
			
		||||
    for (const char of content) {
 | 
			
		||||
        if (flexWidth > 500) {
 | 
			
		||||
            break;
 | 
			
		||||
        }
 | 
			
		||||
        if ((char >= '0' && char <= '9') || (char >= 'a' && char <= 'z')) {
 | 
			
		||||
            // 小写字母、数字字符
 | 
			
		||||
            flexWidth += 9.3;
 | 
			
		||||
            continue;
 | 
			
		||||
        }
 | 
			
		||||
        if (char >= 'A' && char <= 'Z') {
 | 
			
		||||
            flexWidth += 9;
 | 
			
		||||
            continue;
 | 
			
		||||
        }
 | 
			
		||||
        if (char >= '\u4e00' && char <= '\u9fa5') {
 | 
			
		||||
            // 如果是中文字符,为字符分配16个单位宽度
 | 
			
		||||
            flexWidth += 20;
 | 
			
		||||
        } else {
 | 
			
		||||
            // 其他种类字符
 | 
			
		||||
            flexWidth += 8;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    // if (flexWidth > 450) {
 | 
			
		||||
    //     // 设置最大宽度
 | 
			
		||||
    //     flexWidth = 450;
 | 
			
		||||
    // }
 | 
			
		||||
    return flexWidth;
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,6 @@
 | 
			
		||||
import { nextTick } from 'vue';
 | 
			
		||||
import * as svg from '@element-plus/icons-vue';
 | 
			
		||||
import iconfontJson from '@/assets/iconfont/iconfont.json'
 | 
			
		||||
import iconfontJson from '@/assets/iconfont/iconfont.json';
 | 
			
		||||
import SvgIcon from '@/components/svgIcon/index.vue';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
@@ -52,21 +52,21 @@ const getLocalAliIconfont = () => {
 | 
			
		||||
            resolve(iconfontJson.glyphs.map((x: any) => prefix + x.font_class));
 | 
			
		||||
        });
 | 
			
		||||
    });
 | 
			
		||||
}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// 初始化获取 css 样式,获取 element plus 自带图标
 | 
			
		||||
const elementPlusIconfont = () => {
 | 
			
		||||
    return new Promise((resolve, reject) => {
 | 
			
		||||
		nextTick(() => {
 | 
			
		||||
			const icons = svg as any;
 | 
			
		||||
			const sheetsIconList = [];
 | 
			
		||||
			for (const i in icons) {
 | 
			
		||||
				sheetsIconList.push(`${icons[i].name}`);
 | 
			
		||||
			}
 | 
			
		||||
			if (sheetsIconList.length > 0) resolve(sheetsIconList);
 | 
			
		||||
			else reject('未获取到值,请刷新重试');
 | 
			
		||||
		});
 | 
			
		||||
	});
 | 
			
		||||
        nextTick(() => {
 | 
			
		||||
            const icons = svg as any;
 | 
			
		||||
            const sheetsIconList = [];
 | 
			
		||||
            for (const i in icons) {
 | 
			
		||||
                sheetsIconList.push(`${icons[i].name}`);
 | 
			
		||||
            }
 | 
			
		||||
            if (sheetsIconList.length > 0) resolve(sheetsIconList);
 | 
			
		||||
            else reject('未获取到值,请刷新重试');
 | 
			
		||||
        });
 | 
			
		||||
    });
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// 初始化获取 css 样式,这里使用 fontawesome 的图标
 | 
			
		||||
@@ -103,7 +103,7 @@ const awesomeIconfont = () => {
 | 
			
		||||
// 定义导出方法集合
 | 
			
		||||
const initIconfont = {
 | 
			
		||||
    ali: () => {
 | 
			
		||||
    	return getLocalAliIconfont();
 | 
			
		||||
        return getLocalAliIconfont();
 | 
			
		||||
    },
 | 
			
		||||
    ele: () => {
 | 
			
		||||
        return elementPlusIconfont();
 | 
			
		||||
 
 | 
			
		||||
@@ -163,8 +163,7 @@ export function verifyPasswordStrength(val: string) {
 | 
			
		||||
    // 中:字母+数字,字母+特殊字符,数字+特殊字符
 | 
			
		||||
    if (/^(?![a-zA-z]+$)(?!\d+$)(?![!@#$%^&\.*]+$)[a-zA-Z\d!@#$%^&\.*]{6,16}$/.test(val)) v = '中';
 | 
			
		||||
    // 强:字母+数字+特殊字符
 | 
			
		||||
    if (/^(?![a-zA-z]+$)(?!\d+$)(?![!@#$%^&\.*]+$)(?![a-zA-z\d]+$)(?![a-zA-z!@#$%^&\.*]+$)(?![\d!@#$%^&\.*]+$)[a-zA-Z\d!@#$%^&\.*]{6,16}$/.test(val))
 | 
			
		||||
        v = '强';
 | 
			
		||||
    if (/^(?![a-zA-z]+$)(?!\d+$)(?![!@#$%^&\.*]+$)(?![a-zA-z\d]+$)(?![a-zA-z!@#$%^&\.*]+$)(?![\d!@#$%^&\.*]+$)[a-zA-Z\d!@#$%^&\.*]{6,16}$/.test(val)) v = '强';
 | 
			
		||||
    // 返回结果
 | 
			
		||||
    return v;
 | 
			
		||||
}
 | 
			
		||||
@@ -172,11 +171,7 @@ export function verifyPasswordStrength(val: string) {
 | 
			
		||||
// IP地址
 | 
			
		||||
export function verifyIPAddress(val: string) {
 | 
			
		||||
    // false: IP地址不正确
 | 
			
		||||
    if (
 | 
			
		||||
        !/^(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])$/.test(
 | 
			
		||||
            val
 | 
			
		||||
        )
 | 
			
		||||
    )
 | 
			
		||||
    if (!/^(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])$/.test(val))
 | 
			
		||||
        return false;
 | 
			
		||||
    // true: IP地址正确
 | 
			
		||||
    else return true;
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,5 @@
 | 
			
		||||
import { getUseWatermark4Session, getUserInfo4Session } from '@/common/utils/storage.ts';
 | 
			
		||||
import { dateFormat2 } from '@/common/utils/date.ts'
 | 
			
		||||
import { getUseWatermark4Session, getUserInfo4Session } from '@/common/utils/storage';
 | 
			
		||||
import { dateFormat2 } from '@/common/utils/date';
 | 
			
		||||
 | 
			
		||||
// 页面添加水印效果
 | 
			
		||||
const setWatermark = (str: any) => {
 | 
			
		||||
@@ -44,17 +44,17 @@ function del() {
 | 
			
		||||
const watermark = {
 | 
			
		||||
    use: () => {
 | 
			
		||||
        setTimeout(() => {
 | 
			
		||||
            const userinfo = getUserInfo4Session()
 | 
			
		||||
            const userinfo = getUserInfo4Session();
 | 
			
		||||
            if (userinfo && getUseWatermark4Session()) {
 | 
			
		||||
                set(`${userinfo.username} ${dateFormat2('yyyy-MM-dd HH:mm:ss', new Date())}`)
 | 
			
		||||
                set(`${userinfo.username} ${dateFormat2('yyyy-MM-dd HH:mm:ss', new Date())}`);
 | 
			
		||||
            } else {
 | 
			
		||||
                del();
 | 
			
		||||
            }
 | 
			
		||||
        }, 1500)
 | 
			
		||||
        }, 1500);
 | 
			
		||||
    },
 | 
			
		||||
    // 设置水印
 | 
			
		||||
    set: (str: any) => {
 | 
			
		||||
        set(str)
 | 
			
		||||
        set(str);
 | 
			
		||||
    },
 | 
			
		||||
    // 删除水印
 | 
			
		||||
    del: () => {
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										25
									
								
								mayfly_go_web/src/components/auth/auth.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								mayfly_go_web/src/components/auth/auth.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,25 @@
 | 
			
		||||
import { useUserInfo } from '@/store/userInfo';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 判断当前用户是否拥有指定权限
 | 
			
		||||
 * @param code 权限code
 | 
			
		||||
 * @returns
 | 
			
		||||
 */
 | 
			
		||||
export function hasPerm(code: string) {
 | 
			
		||||
    return useUserInfo().userInfo.permissions.some((v: any) => v === code);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 判断用户是否拥有权限对象里对应的code
 | 
			
		||||
 * @param perms { save: "xxx:save"}
 | 
			
		||||
 * @returns {"xxx:save": true}  key->permission code
 | 
			
		||||
 */
 | 
			
		||||
export function hasPerms(permCodes: any[]) {
 | 
			
		||||
    const res = {};
 | 
			
		||||
    for (let permCode of permCodes) {
 | 
			
		||||
        if (hasPerm(permCode)) {
 | 
			
		||||
            res[permCode] = true;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    return res;
 | 
			
		||||
}
 | 
			
		||||
@@ -1,12 +1,24 @@
 | 
			
		||||
<template>
 | 
			
		||||
    <transition name="el-zoom-in-center">
 | 
			
		||||
        <div aria-hidden="true" class="el-dropdown__popper el-popper is-light is-pure custom-contextmenu" role="tooltip"
 | 
			
		||||
            data-popper-placement="bottom" :style="`top: ${dropdowns.y + 5}px;left: ${dropdowns.x}px;`" :key="Math.random()"
 | 
			
		||||
            v-show="state.isShow">
 | 
			
		||||
        <div
 | 
			
		||||
            aria-hidden="true"
 | 
			
		||||
            class="el-dropdown__popper el-popper is-light is-pure custom-contextmenu"
 | 
			
		||||
            role="tooltip"
 | 
			
		||||
            data-popper-placement="bottom"
 | 
			
		||||
            :style="`top: ${dropdowns.y + 5}px;left: ${dropdowns.x}px;`"
 | 
			
		||||
            :key="Math.random()"
 | 
			
		||||
            v-show="state.isShow"
 | 
			
		||||
        >
 | 
			
		||||
            <ul class="el-dropdown-menu">
 | 
			
		||||
                <template v-for="(v, k) in state.dropdownList">
 | 
			
		||||
                    <li class="el-dropdown-menu__item" aria-disabled="false" tabindex="-1" :key="k" v-if="!v.affix"
 | 
			
		||||
                        @click="onCurrentContextmenuClick(v.contextMenuClickId)">
 | 
			
		||||
                    <li
 | 
			
		||||
                        class="el-dropdown-menu__item"
 | 
			
		||||
                        aria-disabled="false"
 | 
			
		||||
                        tabindex="-1"
 | 
			
		||||
                        :key="k"
 | 
			
		||||
                        v-if="!v.affix"
 | 
			
		||||
                        @click="onCurrentContextmenuClick(v.contextMenuClickId)"
 | 
			
		||||
                    >
 | 
			
		||||
                        <SvgIcon :name="v.icon" />
 | 
			
		||||
                        <span>{{ v.txt }}</span>
 | 
			
		||||
                    </li>
 | 
			
		||||
@@ -43,9 +55,7 @@ const emit = defineEmits(['currentContextmenuClick']);
 | 
			
		||||
// 定义变量内容
 | 
			
		||||
const state = reactive({
 | 
			
		||||
    isShow: false,
 | 
			
		||||
    dropdownList: [
 | 
			
		||||
        { contextMenuClickId: 0, txt: '刷新', affix: false, icon: 'RefreshRight' },
 | 
			
		||||
    ],
 | 
			
		||||
    dropdownList: [{ contextMenuClickId: 0, txt: '刷新', affix: false, icon: 'RefreshRight' }],
 | 
			
		||||
    item: {} as any,
 | 
			
		||||
    arrowLeft: 10,
 | 
			
		||||
});
 | 
			
		||||
@@ -100,7 +110,7 @@ watch(
 | 
			
		||||
watch(
 | 
			
		||||
    () => props.items,
 | 
			
		||||
    (x: any) => {
 | 
			
		||||
        state.dropdownList = x
 | 
			
		||||
        state.dropdownList = x;
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
        deep: true,
 | 
			
		||||
@@ -129,4 +139,4 @@ defineExpose({
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
</style>
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										56
									
								
								mayfly_go_web/src/components/enumtag/EnumTag.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										56
									
								
								mayfly_go_web/src/components/enumtag/EnumTag.vue
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,56 @@
 | 
			
		||||
<template>
 | 
			
		||||
    <el-tag v-bind="$attrs" :type="type" :color="color" effect="plain">{{ enumLabel }}</el-tag>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script lang="ts" setup>
 | 
			
		||||
import { toRefs, watch, reactive, onMounted } from 'vue';
 | 
			
		||||
import EnumValue from '@/common/Enum';
 | 
			
		||||
 | 
			
		||||
const props = defineProps({
 | 
			
		||||
    enums: {
 | 
			
		||||
        type: Object, // 需要为EnumValue类型
 | 
			
		||||
        required: true,
 | 
			
		||||
    },
 | 
			
		||||
    value: {
 | 
			
		||||
        type: Object,
 | 
			
		||||
        required: true,
 | 
			
		||||
    },
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const state = reactive({
 | 
			
		||||
    type: 'primary',
 | 
			
		||||
    color: '',
 | 
			
		||||
    enumLabel: '',
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const { type, color, enumLabel } = toRefs(state);
 | 
			
		||||
 | 
			
		||||
// 监听该值是否改变,改变则需要将其枚举值与标签进行调整
 | 
			
		||||
watch(
 | 
			
		||||
    () => props.value,
 | 
			
		||||
    (newValue: any) => {
 | 
			
		||||
        convert(newValue);
 | 
			
		||||
    }
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
onMounted(() => {
 | 
			
		||||
    convert(props.value);
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const convert = (value: any) => {
 | 
			
		||||
    const enumValue = EnumValue.getEnumByValue(Object.values(props.enums as any) as any, value) as any;
 | 
			
		||||
    if (!enumValue) {
 | 
			
		||||
        state.enumLabel = '-';
 | 
			
		||||
        state.type = 'danger';
 | 
			
		||||
        state.color = '';
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    state.enumLabel = enumValue?.label || '';
 | 
			
		||||
    if (enumValue.tag) {
 | 
			
		||||
        state.color = enumValue.tag.color;
 | 
			
		||||
        state.type = enumValue.tag.type;
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
</script>
 | 
			
		||||
<style scoped lang="scss"></style>
 | 
			
		||||
@@ -1,50 +1,47 @@
 | 
			
		||||
<template>
 | 
			
		||||
	<div class="icon-selector w100 h100">
 | 
			
		||||
		<el-input
 | 
			
		||||
			v-model="state.fontIconSearch"
 | 
			
		||||
			:placeholder="state.fontIconPlaceholder"
 | 
			
		||||
			:clearable="clearable"
 | 
			
		||||
			:disabled="disabled"
 | 
			
		||||
			:size="size"
 | 
			
		||||
			ref="inputWidthRef"
 | 
			
		||||
			@clear="onClearFontIcon"
 | 
			
		||||
			@focus="onIconFocus"
 | 
			
		||||
			@blur="onIconBlur"
 | 
			
		||||
		>
 | 
			
		||||
			<template #prepend>
 | 
			
		||||
				<SvgIcon
 | 
			
		||||
					:name="state.fontIconPrefix === '' ? prepend : state.fontIconPrefix"
 | 
			
		||||
					class="font14"
 | 
			
		||||
				/>
 | 
			
		||||
			</template>
 | 
			
		||||
		</el-input>
 | 
			
		||||
		<el-popover
 | 
			
		||||
			placement="bottom"
 | 
			
		||||
			:width="state.fontIconWidth"
 | 
			
		||||
			transition="el-zoom-in-top"
 | 
			
		||||
			popper-class="icon-selector-popper"
 | 
			
		||||
			trigger="click"
 | 
			
		||||
			:virtual-ref="inputWidthRef"
 | 
			
		||||
			virtual-triggering
 | 
			
		||||
		>
 | 
			
		||||
			<template #default>
 | 
			
		||||
				<div class="icon-selector-warp">
 | 
			
		||||
					<div class="icon-selector-warp-title">{{ title }}</div>
 | 
			
		||||
					<el-tabs v-model="state.fontIconTabActive" @tab-click="onIconClick">
 | 
			
		||||
						<el-tab-pane lazy label="ele" name="ele">
 | 
			
		||||
							<IconList :list="fontIconSheetsFilterList" :empty="emptyDescription" :prefix="state.fontIconPrefix" @get-icon="onColClick" />
 | 
			
		||||
						</el-tab-pane>
 | 
			
		||||
    <div class="icon-selector w100 h100">
 | 
			
		||||
        <el-input
 | 
			
		||||
            v-model="state.fontIconSearch"
 | 
			
		||||
            :placeholder="state.fontIconPlaceholder"
 | 
			
		||||
            :clearable="clearable"
 | 
			
		||||
            :disabled="disabled"
 | 
			
		||||
            :size="size"
 | 
			
		||||
            ref="inputWidthRef"
 | 
			
		||||
            @clear="onClearFontIcon"
 | 
			
		||||
            @focus="onIconFocus"
 | 
			
		||||
            @blur="onIconBlur"
 | 
			
		||||
        >
 | 
			
		||||
            <template #prepend>
 | 
			
		||||
                <SvgIcon :name="state.fontIconPrefix === '' ? prepend : state.fontIconPrefix" class="font14" />
 | 
			
		||||
            </template>
 | 
			
		||||
        </el-input>
 | 
			
		||||
        <el-popover
 | 
			
		||||
            placement="bottom"
 | 
			
		||||
            :width="state.fontIconWidth"
 | 
			
		||||
            transition="el-zoom-in-top"
 | 
			
		||||
            popper-class="icon-selector-popper"
 | 
			
		||||
            trigger="click"
 | 
			
		||||
            :virtual-ref="inputWidthRef"
 | 
			
		||||
            virtual-triggering
 | 
			
		||||
        >
 | 
			
		||||
            <template #default>
 | 
			
		||||
                <div class="icon-selector-warp">
 | 
			
		||||
                    <div class="icon-selector-warp-title">{{ title }}</div>
 | 
			
		||||
                    <el-tabs v-model="state.fontIconTabActive" @tab-click="onIconClick">
 | 
			
		||||
                        <el-tab-pane lazy label="ele" name="ele">
 | 
			
		||||
                            <IconList :list="fontIconSheetsFilterList" :empty="emptyDescription" :prefix="state.fontIconPrefix" @get-icon="onColClick" />
 | 
			
		||||
                        </el-tab-pane>
 | 
			
		||||
                        <el-tab-pane lazy label="ali" name="ali">
 | 
			
		||||
							<IconList :list="fontIconSheetsFilterList" :empty="emptyDescription" :prefix="state.fontIconPrefix" @get-icon="onColClick" />
 | 
			
		||||
						</el-tab-pane>
 | 
			
		||||
						<!-- <el-tab-pane lazy label="awe" name="awe">
 | 
			
		||||
                            <IconList :list="fontIconSheetsFilterList" :empty="emptyDescription" :prefix="state.fontIconPrefix" @get-icon="onColClick" />
 | 
			
		||||
                        </el-tab-pane>
 | 
			
		||||
                        <!-- <el-tab-pane lazy label="awe" name="awe">
 | 
			
		||||
							<IconList :list="fontIconSheetsFilterList" :empty="emptyDescription" :prefix="state.fontIconPrefix" @get-icon="onColClick" />
 | 
			
		||||
						</el-tab-pane> -->
 | 
			
		||||
					</el-tabs>
 | 
			
		||||
				</div>
 | 
			
		||||
			</template>
 | 
			
		||||
		</el-popover>
 | 
			
		||||
	</div>
 | 
			
		||||
                    </el-tabs>
 | 
			
		||||
                </div>
 | 
			
		||||
            </template>
 | 
			
		||||
        </el-popover>
 | 
			
		||||
    </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script setup lang="ts" name="iconSelector">
 | 
			
		||||
@@ -55,45 +52,45 @@ import '@/theme/iconSelector.scss';
 | 
			
		||||
 | 
			
		||||
// 定义父组件传过来的值
 | 
			
		||||
const props = defineProps({
 | 
			
		||||
	// 输入框前置内容
 | 
			
		||||
	prepend: {
 | 
			
		||||
		type: String,
 | 
			
		||||
		default: () => 'Pointer',
 | 
			
		||||
	},
 | 
			
		||||
	// 输入框占位文本
 | 
			
		||||
	placeholder: {
 | 
			
		||||
		type: String,
 | 
			
		||||
		default: () => '请输入内容搜索图标或者选择图标',
 | 
			
		||||
	},
 | 
			
		||||
	// 输入框占位文本
 | 
			
		||||
	size: {
 | 
			
		||||
		type: String,
 | 
			
		||||
		default: () => 'default',
 | 
			
		||||
	},
 | 
			
		||||
	// 弹窗标题
 | 
			
		||||
	title: {
 | 
			
		||||
		type: String,
 | 
			
		||||
		default: () => '请选择图标',
 | 
			
		||||
	},
 | 
			
		||||
	// 禁用
 | 
			
		||||
	disabled: {
 | 
			
		||||
		type: Boolean,
 | 
			
		||||
		default: () => false,
 | 
			
		||||
	},
 | 
			
		||||
	// 是否可清空
 | 
			
		||||
	clearable: {
 | 
			
		||||
		type: Boolean,
 | 
			
		||||
		default: () => true,
 | 
			
		||||
	},
 | 
			
		||||
	// 自定义空状态描述文字
 | 
			
		||||
	emptyDescription: {
 | 
			
		||||
		type: String,
 | 
			
		||||
		default: () => '无相关图标',
 | 
			
		||||
	},
 | 
			
		||||
	// 双向绑定值,默认为 modelValue,
 | 
			
		||||
	// 参考:https://v3.cn.vuejs.org/guide/migration/v-model.html#%E8%BF%81%E7%A7%BB%E7%AD%96%E7%95%A5
 | 
			
		||||
	// 参考:https://v3.cn.vuejs.org/guide/component-custom-events.html#%E5%A4%9A%E4%B8%AA-v-model-%E7%BB%91%E5%AE%9A
 | 
			
		||||
	modelValue: String,
 | 
			
		||||
    // 输入框前置内容
 | 
			
		||||
    prepend: {
 | 
			
		||||
        type: String,
 | 
			
		||||
        default: () => 'Pointer',
 | 
			
		||||
    },
 | 
			
		||||
    // 输入框占位文本
 | 
			
		||||
    placeholder: {
 | 
			
		||||
        type: String,
 | 
			
		||||
        default: () => '请输入内容搜索图标或者选择图标',
 | 
			
		||||
    },
 | 
			
		||||
    // 输入框占位文本
 | 
			
		||||
    size: {
 | 
			
		||||
        type: String,
 | 
			
		||||
        default: () => 'default',
 | 
			
		||||
    },
 | 
			
		||||
    // 弹窗标题
 | 
			
		||||
    title: {
 | 
			
		||||
        type: String,
 | 
			
		||||
        default: () => '请选择图标',
 | 
			
		||||
    },
 | 
			
		||||
    // 禁用
 | 
			
		||||
    disabled: {
 | 
			
		||||
        type: Boolean,
 | 
			
		||||
        default: () => false,
 | 
			
		||||
    },
 | 
			
		||||
    // 是否可清空
 | 
			
		||||
    clearable: {
 | 
			
		||||
        type: Boolean,
 | 
			
		||||
        default: () => true,
 | 
			
		||||
    },
 | 
			
		||||
    // 自定义空状态描述文字
 | 
			
		||||
    emptyDescription: {
 | 
			
		||||
        type: String,
 | 
			
		||||
        default: () => '无相关图标',
 | 
			
		||||
    },
 | 
			
		||||
    // 双向绑定值,默认为 modelValue,
 | 
			
		||||
    // 参考:https://v3.cn.vuejs.org/guide/migration/v-model.html#%E8%BF%81%E7%A7%BB%E7%AD%96%E7%95%A5
 | 
			
		||||
    // 参考:https://v3.cn.vuejs.org/guide/component-custom-events.html#%E5%A4%9A%E4%B8%AA-v-model-%E7%BB%91%E5%AE%9A
 | 
			
		||||
    modelValue: String,
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
// 定义子组件向父组件传值/事件
 | 
			
		||||
@@ -105,138 +102,138 @@ const IconList = defineAsyncComponent(() => import('@/components/iconSelector/li
 | 
			
		||||
// 定义变量内容
 | 
			
		||||
const inputWidthRef = ref();
 | 
			
		||||
const state = reactive({
 | 
			
		||||
	fontIconPrefix: '',
 | 
			
		||||
	fontIconWidth: 0,
 | 
			
		||||
	fontIconSearch: '',
 | 
			
		||||
	fontIconPlaceholder: '',
 | 
			
		||||
	fontIconTabActive: 'ele',
 | 
			
		||||
	fontIconList: {
 | 
			
		||||
		ali: [],
 | 
			
		||||
		ele: [],
 | 
			
		||||
		awe: [],
 | 
			
		||||
	},
 | 
			
		||||
    fontIconPrefix: '',
 | 
			
		||||
    fontIconWidth: 0,
 | 
			
		||||
    fontIconSearch: '',
 | 
			
		||||
    fontIconPlaceholder: '',
 | 
			
		||||
    fontIconTabActive: 'ele',
 | 
			
		||||
    fontIconList: {
 | 
			
		||||
        ali: [],
 | 
			
		||||
        ele: [],
 | 
			
		||||
        awe: [],
 | 
			
		||||
    },
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
// 处理 input 获取焦点时,modelValue 有值时,改变 input 的 placeholder 值
 | 
			
		||||
const onIconFocus = () => {
 | 
			
		||||
	if (!props.modelValue) return false;
 | 
			
		||||
	state.fontIconSearch = '';
 | 
			
		||||
	state.fontIconPlaceholder = props.modelValue;
 | 
			
		||||
    if (!props.modelValue) return false;
 | 
			
		||||
    state.fontIconSearch = '';
 | 
			
		||||
    state.fontIconPlaceholder = props.modelValue;
 | 
			
		||||
};
 | 
			
		||||
// 处理 input 失去焦点时,为空将清空 input 值,为点击选中图标时,将取原先值
 | 
			
		||||
const onIconBlur = () => {
 | 
			
		||||
	const list = fontIconTabNameList();
 | 
			
		||||
	setTimeout(() => {
 | 
			
		||||
		const icon = list.filter((icon: string) => icon === state.fontIconSearch);
 | 
			
		||||
		if (icon.length <= 0) state.fontIconSearch = '';
 | 
			
		||||
	}, 300);
 | 
			
		||||
    const list = fontIconTabNameList();
 | 
			
		||||
    setTimeout(() => {
 | 
			
		||||
        const icon = list.filter((icon: string) => icon === state.fontIconSearch);
 | 
			
		||||
        if (icon.length <= 0) state.fontIconSearch = '';
 | 
			
		||||
    }, 300);
 | 
			
		||||
};
 | 
			
		||||
// 图标搜索及图标数据显示
 | 
			
		||||
const fontIconSheetsFilterList = computed(() => {
 | 
			
		||||
	const list = fontIconTabNameList();
 | 
			
		||||
	if (!state.fontIconSearch) return list;
 | 
			
		||||
	let search = state.fontIconSearch.trim().toLowerCase();
 | 
			
		||||
	return list.filter((item: string) => {
 | 
			
		||||
		if (item.toLowerCase().indexOf(search) !== -1) return item;
 | 
			
		||||
	});
 | 
			
		||||
    const list = fontIconTabNameList();
 | 
			
		||||
    if (!state.fontIconSearch) return list;
 | 
			
		||||
    let search = state.fontIconSearch.trim().toLowerCase();
 | 
			
		||||
    return list.filter((item: string) => {
 | 
			
		||||
        if (item.toLowerCase().indexOf(search) !== -1) return item;
 | 
			
		||||
    });
 | 
			
		||||
});
 | 
			
		||||
// 根据 tab name 类型设置图标
 | 
			
		||||
const fontIconTabNameList = () => {
 | 
			
		||||
	let iconList: any = [];
 | 
			
		||||
	if (state.fontIconTabActive === 'ali') iconList = state.fontIconList.ali;
 | 
			
		||||
	else if (state.fontIconTabActive === 'ele') iconList = state.fontIconList.ele;
 | 
			
		||||
	else if (state.fontIconTabActive === 'awe') iconList = state.fontIconList.awe;
 | 
			
		||||
	return iconList;
 | 
			
		||||
    let iconList: any = [];
 | 
			
		||||
    if (state.fontIconTabActive === 'ali') iconList = state.fontIconList.ali;
 | 
			
		||||
    else if (state.fontIconTabActive === 'ele') iconList = state.fontIconList.ele;
 | 
			
		||||
    else if (state.fontIconTabActive === 'awe') iconList = state.fontIconList.awe;
 | 
			
		||||
    return iconList;
 | 
			
		||||
};
 | 
			
		||||
// 处理 icon 双向绑定数值回显
 | 
			
		||||
const initModeValueEcho = () => {
 | 
			
		||||
	if (props.modelValue === '') return ((<string | undefined>state.fontIconPlaceholder) = props.placeholder);
 | 
			
		||||
	(<string | undefined>state.fontIconPlaceholder) = props.modelValue;
 | 
			
		||||
	(<string | undefined>state.fontIconPrefix) = props.modelValue;
 | 
			
		||||
    if (props.modelValue === '') return ((<string | undefined>state.fontIconPlaceholder) = props.placeholder);
 | 
			
		||||
    (<string | undefined>state.fontIconPlaceholder) = props.modelValue;
 | 
			
		||||
    (<string | undefined>state.fontIconPrefix) = props.modelValue;
 | 
			
		||||
};
 | 
			
		||||
// 处理 icon 类型,用于回显时,tab 高亮与初始化数据
 | 
			
		||||
const initFontIconName = () => {
 | 
			
		||||
	let name = 'ele';
 | 
			
		||||
	if (props.modelValue!.indexOf('iconfont') > -1) {
 | 
			
		||||
    let name = 'ele';
 | 
			
		||||
    if (props.modelValue!.indexOf('iconfont') > -1) {
 | 
			
		||||
        name = 'ali';
 | 
			
		||||
    } else {
 | 
			
		||||
        name = 'ele';
 | 
			
		||||
    }
 | 
			
		||||
	// else if (props.modelValue!.indexOf('ele-') > -1) name = 'ele';
 | 
			
		||||
	// else if (props.modelValue!.indexOf('fa') > -1) name = 'awe';
 | 
			
		||||
	// 初始化 tab 高亮回显
 | 
			
		||||
	state.fontIconTabActive = name;
 | 
			
		||||
	return name;
 | 
			
		||||
    // else if (props.modelValue!.indexOf('ele-') > -1) name = 'ele';
 | 
			
		||||
    // else if (props.modelValue!.indexOf('fa') > -1) name = 'awe';
 | 
			
		||||
    // 初始化 tab 高亮回显
 | 
			
		||||
    state.fontIconTabActive = name;
 | 
			
		||||
    return name;
 | 
			
		||||
};
 | 
			
		||||
// 初始化数据
 | 
			
		||||
const initFontIconData = async (name: string) => {
 | 
			
		||||
	if (name === 'ali') {
 | 
			
		||||
		// 阿里字体图标使用 `iconfont xxx`
 | 
			
		||||
		if (state.fontIconList.ali.length > 0) return;
 | 
			
		||||
		const res: any = await initIconfont.ali();
 | 
			
		||||
    if (name === 'ali') {
 | 
			
		||||
        // 阿里字体图标使用 `iconfont xxx`
 | 
			
		||||
        if (state.fontIconList.ali.length > 0) return;
 | 
			
		||||
        const res: any = await initIconfont.ali();
 | 
			
		||||
        state.fontIconList.ali = res.map((i: string) => `iconfont ${i}`);
 | 
			
		||||
	} else if (name === 'ele') {
 | 
			
		||||
		// element plus 图标
 | 
			
		||||
		if (state.fontIconList.ele.length > 0) return;
 | 
			
		||||
		await initIconfont.ele().then((res: any) => {
 | 
			
		||||
			state.fontIconList.ele = res;
 | 
			
		||||
		});
 | 
			
		||||
	} else if (name === 'awe') {
 | 
			
		||||
		// fontawesome字体图标使用 `fa xxx`
 | 
			
		||||
		// if (state.fontIconList.awe.length > 0) return;
 | 
			
		||||
		// await initIconfont.awe().then((res: any) => {
 | 
			
		||||
		// 	state.fontIconList.awe = res.map((i: string) => `fa ${i}`);
 | 
			
		||||
		// });
 | 
			
		||||
	}
 | 
			
		||||
	// 初始化 input 的 placeholder
 | 
			
		||||
	// 参考(单项数据流):https://cn.vuejs.org/v2/guide/components-props.html?#%E5%8D%95%E5%90%91%E6%95%B0%E6%8D%AE%E6%B5%81
 | 
			
		||||
	state.fontIconPlaceholder = props.placeholder;
 | 
			
		||||
	// 初始化双向绑定回显
 | 
			
		||||
	initModeValueEcho();
 | 
			
		||||
    } else if (name === 'ele') {
 | 
			
		||||
        // element plus 图标
 | 
			
		||||
        if (state.fontIconList.ele.length > 0) return;
 | 
			
		||||
        await initIconfont.ele().then((res: any) => {
 | 
			
		||||
            state.fontIconList.ele = res;
 | 
			
		||||
        });
 | 
			
		||||
    } else if (name === 'awe') {
 | 
			
		||||
        // fontawesome字体图标使用 `fa xxx`
 | 
			
		||||
        // if (state.fontIconList.awe.length > 0) return;
 | 
			
		||||
        // await initIconfont.awe().then((res: any) => {
 | 
			
		||||
        // 	state.fontIconList.awe = res.map((i: string) => `fa ${i}`);
 | 
			
		||||
        // });
 | 
			
		||||
    }
 | 
			
		||||
    // 初始化 input 的 placeholder
 | 
			
		||||
    // 参考(单项数据流):https://cn.vuejs.org/v2/guide/components-props.html?#%E5%8D%95%E5%90%91%E6%95%B0%E6%8D%AE%E6%B5%81
 | 
			
		||||
    state.fontIconPlaceholder = props.placeholder;
 | 
			
		||||
    // 初始化双向绑定回显
 | 
			
		||||
    initModeValueEcho();
 | 
			
		||||
};
 | 
			
		||||
// 图标点击切换
 | 
			
		||||
const onIconClick = (pane: TabsPaneContext) => {
 | 
			
		||||
	initFontIconData(pane.paneName as string);
 | 
			
		||||
	inputWidthRef.value.focus();
 | 
			
		||||
    initFontIconData(pane.paneName as string);
 | 
			
		||||
    inputWidthRef.value.focus();
 | 
			
		||||
};
 | 
			
		||||
// 获取当前点击的 icon 图标
 | 
			
		||||
const onColClick = (v: string) => {
 | 
			
		||||
	state.fontIconPlaceholder = v;
 | 
			
		||||
	state.fontIconPrefix = v;
 | 
			
		||||
	emit('get', state.fontIconPrefix);
 | 
			
		||||
	emit('update:modelValue', state.fontIconPrefix);
 | 
			
		||||
	inputWidthRef.value.focus();
 | 
			
		||||
    state.fontIconPlaceholder = v;
 | 
			
		||||
    state.fontIconPrefix = v;
 | 
			
		||||
    emit('get', state.fontIconPrefix);
 | 
			
		||||
    emit('update:modelValue', state.fontIconPrefix);
 | 
			
		||||
    inputWidthRef.value.focus();
 | 
			
		||||
};
 | 
			
		||||
// 清空当前点击的 icon 图标
 | 
			
		||||
const onClearFontIcon = () => {
 | 
			
		||||
	state.fontIconPrefix = '';
 | 
			
		||||
	emit('clear', state.fontIconPrefix);
 | 
			
		||||
	emit('update:modelValue', state.fontIconPrefix);
 | 
			
		||||
    state.fontIconPrefix = '';
 | 
			
		||||
    emit('clear', state.fontIconPrefix);
 | 
			
		||||
    emit('update:modelValue', state.fontIconPrefix);
 | 
			
		||||
};
 | 
			
		||||
// 获取 input 的宽度
 | 
			
		||||
const getInputWidth = () => {
 | 
			
		||||
	nextTick(() => {
 | 
			
		||||
		state.fontIconWidth = inputWidthRef.value.$el.offsetWidth;
 | 
			
		||||
	});
 | 
			
		||||
    nextTick(() => {
 | 
			
		||||
        state.fontIconWidth = inputWidthRef.value.$el.offsetWidth;
 | 
			
		||||
    });
 | 
			
		||||
};
 | 
			
		||||
// 监听页面宽度改变
 | 
			
		||||
const initResize = () => {
 | 
			
		||||
	window.addEventListener('resize', () => {
 | 
			
		||||
		getInputWidth();
 | 
			
		||||
	});
 | 
			
		||||
    window.addEventListener('resize', () => {
 | 
			
		||||
        getInputWidth();
 | 
			
		||||
    });
 | 
			
		||||
};
 | 
			
		||||
// 页面加载时
 | 
			
		||||
onMounted(() => {
 | 
			
		||||
	initFontIconData(initFontIconName());
 | 
			
		||||
	initResize();
 | 
			
		||||
	getInputWidth();
 | 
			
		||||
    initFontIconData(initFontIconName());
 | 
			
		||||
    initResize();
 | 
			
		||||
    getInputWidth();
 | 
			
		||||
});
 | 
			
		||||
// 监听双向绑定 modelValue 的变化
 | 
			
		||||
watch(
 | 
			
		||||
	() => props.modelValue,
 | 
			
		||||
	() => {
 | 
			
		||||
		initModeValueEcho();
 | 
			
		||||
		initFontIconName();
 | 
			
		||||
	}
 | 
			
		||||
    () => props.modelValue,
 | 
			
		||||
    () => {
 | 
			
		||||
        initModeValueEcho();
 | 
			
		||||
        initFontIconName();
 | 
			
		||||
    }
 | 
			
		||||
);
 | 
			
		||||
</script>
 | 
			
		||||
</script>
 | 
			
		||||
 
 | 
			
		||||
@@ -1,36 +1,36 @@
 | 
			
		||||
<template>
 | 
			
		||||
	<div class="icon-selector-warp-row">
 | 
			
		||||
		<el-scrollbar ref="selectorScrollbarRef">
 | 
			
		||||
			<el-row :gutter="10" v-if="props.list.length > 0">
 | 
			
		||||
				<el-col :xs="6" :sm="4" :md="4" :lg="4" :xl="4" v-for="(v, k) in list" :key="k" @click="onColClick(v)">
 | 
			
		||||
					<div class="icon-selector-warp-item" :class="{ 'icon-selector-active': prefix === v }">
 | 
			
		||||
						<SvgIcon :name="v" />
 | 
			
		||||
					</div>
 | 
			
		||||
				</el-col>
 | 
			
		||||
			</el-row>
 | 
			
		||||
			<el-empty :image-size="100" v-if="list.length <= 0" :description="empty"></el-empty>
 | 
			
		||||
		</el-scrollbar>
 | 
			
		||||
	</div>
 | 
			
		||||
    <div class="icon-selector-warp-row">
 | 
			
		||||
        <el-scrollbar ref="selectorScrollbarRef">
 | 
			
		||||
            <el-row :gutter="10" v-if="props.list.length > 0">
 | 
			
		||||
                <el-col :xs="6" :sm="4" :md="4" :lg="4" :xl="4" v-for="(v, k) in list" :key="k" @click="onColClick(v)">
 | 
			
		||||
                    <div class="icon-selector-warp-item" :class="{ 'icon-selector-active': prefix === v }">
 | 
			
		||||
                        <SvgIcon :name="v" />
 | 
			
		||||
                    </div>
 | 
			
		||||
                </el-col>
 | 
			
		||||
            </el-row>
 | 
			
		||||
            <el-empty :image-size="100" v-if="list.length <= 0" :description="empty"></el-empty>
 | 
			
		||||
        </el-scrollbar>
 | 
			
		||||
    </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script setup lang="ts" name="iconSelectorList">
 | 
			
		||||
// 定义父组件传过来的值
 | 
			
		||||
const props = defineProps({
 | 
			
		||||
	// 图标列表数据
 | 
			
		||||
	list: {
 | 
			
		||||
		type: Array,
 | 
			
		||||
		default: () => [],
 | 
			
		||||
	},
 | 
			
		||||
	// 自定义空状态描述文字
 | 
			
		||||
	empty: {
 | 
			
		||||
		type: String,
 | 
			
		||||
		default: () => '无相关图标',
 | 
			
		||||
	},
 | 
			
		||||
	// 高亮当前选中图标
 | 
			
		||||
	prefix: {
 | 
			
		||||
		type: String,
 | 
			
		||||
		default: () => '',
 | 
			
		||||
	},
 | 
			
		||||
    // 图标列表数据
 | 
			
		||||
    list: {
 | 
			
		||||
        type: Array,
 | 
			
		||||
        default: () => [],
 | 
			
		||||
    },
 | 
			
		||||
    // 自定义空状态描述文字
 | 
			
		||||
    empty: {
 | 
			
		||||
        type: String,
 | 
			
		||||
        default: () => '无相关图标',
 | 
			
		||||
    },
 | 
			
		||||
    // 高亮当前选中图标
 | 
			
		||||
    prefix: {
 | 
			
		||||
        type: String,
 | 
			
		||||
        default: () => '',
 | 
			
		||||
    },
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
// 定义子组件向父组件传值/事件
 | 
			
		||||
@@ -38,47 +38,47 @@ const emit = defineEmits(['get-icon']);
 | 
			
		||||
 | 
			
		||||
// 当前 icon 图标点击时
 | 
			
		||||
const onColClick = (v: unknown | string) => {
 | 
			
		||||
	emit('get-icon', v);
 | 
			
		||||
    emit('get-icon', v);
 | 
			
		||||
};
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style scoped lang="scss">
 | 
			
		||||
.icon-selector-warp-row {
 | 
			
		||||
	height: 230px;
 | 
			
		||||
	overflow: hidden;
 | 
			
		||||
	.el-row {
 | 
			
		||||
		padding: 15px;
 | 
			
		||||
	}
 | 
			
		||||
	.el-scrollbar__bar.is-horizontal {
 | 
			
		||||
		display: none;
 | 
			
		||||
	}
 | 
			
		||||
	.icon-selector-warp-item {
 | 
			
		||||
		display: flex;
 | 
			
		||||
		justify-content: center;
 | 
			
		||||
		align-items: center;
 | 
			
		||||
		border: 1px solid var(--el-border-color);
 | 
			
		||||
		border-radius: 5px;
 | 
			
		||||
		margin-bottom: 10px;
 | 
			
		||||
		height: 30px;
 | 
			
		||||
		i {
 | 
			
		||||
			font-size: 20px;
 | 
			
		||||
			color: var(--el-text-color-regular);
 | 
			
		||||
		}
 | 
			
		||||
		&:hover {
 | 
			
		||||
			cursor: pointer;
 | 
			
		||||
			background-color: var(--el-color-primary-light-9);
 | 
			
		||||
			border: 1px solid var(--el-color-primary-light-5);
 | 
			
		||||
			i {
 | 
			
		||||
				color: var(--el-color-primary);
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	.icon-selector-active {
 | 
			
		||||
		background-color: var(--el-color-primary-light-9);
 | 
			
		||||
		border: 1px solid var(--el-color-primary-light-5);
 | 
			
		||||
		i {
 | 
			
		||||
			color: var(--el-color-primary);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
    height: 230px;
 | 
			
		||||
    overflow: hidden;
 | 
			
		||||
    .el-row {
 | 
			
		||||
        padding: 15px;
 | 
			
		||||
    }
 | 
			
		||||
    .el-scrollbar__bar.is-horizontal {
 | 
			
		||||
        display: none;
 | 
			
		||||
    }
 | 
			
		||||
    .icon-selector-warp-item {
 | 
			
		||||
        display: flex;
 | 
			
		||||
        justify-content: center;
 | 
			
		||||
        align-items: center;
 | 
			
		||||
        border: 1px solid var(--el-border-color);
 | 
			
		||||
        border-radius: 5px;
 | 
			
		||||
        margin-bottom: 10px;
 | 
			
		||||
        height: 30px;
 | 
			
		||||
        i {
 | 
			
		||||
            font-size: 20px;
 | 
			
		||||
            color: var(--el-text-color-regular);
 | 
			
		||||
        }
 | 
			
		||||
        &:hover {
 | 
			
		||||
            cursor: pointer;
 | 
			
		||||
            background-color: var(--el-color-primary-light-9);
 | 
			
		||||
            border: 1px solid var(--el-color-primary-light-5);
 | 
			
		||||
            i {
 | 
			
		||||
                color: var(--el-color-primary);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    .icon-selector-active {
 | 
			
		||||
        background-color: var(--el-color-primary-light-9);
 | 
			
		||||
        border: 1px solid var(--el-color-primary-light-5);
 | 
			
		||||
        i {
 | 
			
		||||
            color: var(--el-color-primary);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
</style>
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,5 @@
 | 
			
		||||
<template>
 | 
			
		||||
    <div class="monaco-editor" style="border: 1px solid #ccc;">
 | 
			
		||||
    <div class="monaco-editor" style="border: 1px solid #ccc">
 | 
			
		||||
        <div class="monaco-editor-content" ref="monacoTextarea" :style="{ height: height }"></div>
 | 
			
		||||
        <el-select v-if="canChangeMode" class="code-mode-select" v-model="languageMode" @change="changeLanguage">
 | 
			
		||||
            <el-option v-for="mode in languageArr" :key="mode.value" :label="mode.label" :value="mode.value"> </el-option>
 | 
			
		||||
@@ -12,7 +12,7 @@ import { ref, watch, toRefs, reactive, onMounted, onBeforeUnmount } from 'vue';
 | 
			
		||||
import EditorWorker from 'monaco-editor/esm/vs/editor/editor.worker.js?worker';
 | 
			
		||||
import * as monaco from 'monaco-editor';
 | 
			
		||||
import { editor, languages } from 'monaco-editor';
 | 
			
		||||
import JsonWorker from 'monaco-editor/esm/vs/language/json/json.worker?worker'
 | 
			
		||||
import JsonWorker from 'monaco-editor/esm/vs/language/json/json.worker?worker';
 | 
			
		||||
// 主题仓库 https://github.com/brijeshb42/monaco-themes
 | 
			
		||||
// 主题例子 https://editor.bitwiser.in/
 | 
			
		||||
// import Monokai from 'monaco-themes/themes/Monokai.json'
 | 
			
		||||
@@ -21,7 +21,7 @@ import JsonWorker from 'monaco-editor/esm/vs/language/json/json.worker?worker'
 | 
			
		||||
// import bop from 'monaco-themes/themes/Birds of Paradise.json'
 | 
			
		||||
// import krTheme from 'monaco-themes/themes/krTheme.json'
 | 
			
		||||
// import Dracula from 'monaco-themes/themes/Dracula.json'
 | 
			
		||||
import SolarizedLight from 'monaco-themes/themes/Solarized-light.json'
 | 
			
		||||
import SolarizedLight from 'monaco-themes/themes/Solarized-light.json';
 | 
			
		||||
import { language as shellLan } from 'monaco-editor/esm/vs/basic-languages/shell/shell.js';
 | 
			
		||||
import { ElOption, ElSelect } from 'element-plus';
 | 
			
		||||
 | 
			
		||||
@@ -49,10 +49,10 @@ const props = defineProps({
 | 
			
		||||
        type: Object,
 | 
			
		||||
        default: null,
 | 
			
		||||
    },
 | 
			
		||||
})
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
//定义事件
 | 
			
		||||
const emit = defineEmits(['update:modelValue'])
 | 
			
		||||
const emit = defineEmits(['update:modelValue']);
 | 
			
		||||
 | 
			
		||||
const languageArr = [
 | 
			
		||||
    {
 | 
			
		||||
@@ -109,11 +109,11 @@ const options = {
 | 
			
		||||
    language: 'shell',
 | 
			
		||||
    theme: 'SolarizedLight',
 | 
			
		||||
    automaticLayout: true, //自适应宽高布局
 | 
			
		||||
    foldingStrategy: 'indentation',//代码可分小段折叠
 | 
			
		||||
    foldingStrategy: 'indentation', //代码可分小段折叠
 | 
			
		||||
    roundedSelection: false, // 禁用选择文本背景的圆角
 | 
			
		||||
    matchBrackets: 'near',
 | 
			
		||||
    linkedEditing: true,
 | 
			
		||||
    cursorBlinking: 'smooth',// 光标闪烁样式
 | 
			
		||||
    cursorBlinking: 'smooth', // 光标闪烁样式
 | 
			
		||||
    mouseWheelZoom: true, // 在按住Ctrl键的同时使用鼠标滚轮时,在编辑器中缩放字体
 | 
			
		||||
    overviewRulerBorder: false, // 不要滚动条的边框
 | 
			
		||||
    tabSize: 4, // tab 缩进长度
 | 
			
		||||
@@ -125,15 +125,13 @@ const options = {
 | 
			
		||||
    minimap: {
 | 
			
		||||
        enabled: false, // 不要小地图
 | 
			
		||||
    },
 | 
			
		||||
}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const state = reactive({
 | 
			
		||||
    languageMode: 'shell',
 | 
			
		||||
})
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const {
 | 
			
		||||
    languageMode,
 | 
			
		||||
} = toRefs(state)
 | 
			
		||||
const { languageMode } = toRefs(state);
 | 
			
		||||
 | 
			
		||||
onMounted(() => {
 | 
			
		||||
    state.languageMode = props.language;
 | 
			
		||||
@@ -149,19 +147,24 @@ onBeforeUnmount(() => {
 | 
			
		||||
    if (completionItemProvider) {
 | 
			
		||||
        completionItemProvider.dispose();
 | 
			
		||||
    }
 | 
			
		||||
})
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
watch(() => props.modelValue, (newValue: any) => {
 | 
			
		||||
    if (!monacoEditorIns.hasTextFocus()) {
 | 
			
		||||
        state.languageMode = props.language;
 | 
			
		||||
        monacoEditorIns?.setValue(newValue);
 | 
			
		||||
watch(
 | 
			
		||||
    () => props.modelValue,
 | 
			
		||||
    (newValue: any) => {
 | 
			
		||||
        if (!monacoEditorIns.hasTextFocus()) {
 | 
			
		||||
            state.languageMode = props.language;
 | 
			
		||||
            monacoEditorIns?.setValue(newValue);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
watch(() => props.language, (newValue: any) => {
 | 
			
		||||
    changeLanguage(newValue);
 | 
			
		||||
})
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
watch(
 | 
			
		||||
    () => props.language,
 | 
			
		||||
    (newValue: any) => {
 | 
			
		||||
        changeLanguage(newValue);
 | 
			
		||||
    }
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
const monacoTextarea: any = ref(null);
 | 
			
		||||
 | 
			
		||||
@@ -171,14 +174,14 @@ let completionItemProvider: any = null;
 | 
			
		||||
self.MonacoEnvironment = {
 | 
			
		||||
    getWorker(_: any, label: string) {
 | 
			
		||||
        if (label === 'json') {
 | 
			
		||||
            return new JsonWorker()
 | 
			
		||||
            return new JsonWorker();
 | 
			
		||||
        }
 | 
			
		||||
        return new EditorWorker();
 | 
			
		||||
    }
 | 
			
		||||
    },
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const initMonacoEditorIns = () => {
 | 
			
		||||
    console.log('初始化monaco编辑器')
 | 
			
		||||
    console.log('初始化monaco编辑器');
 | 
			
		||||
    // options参数参考 https://microsoft.github.io/monaco-editor/api/interfaces/monaco.editor.IStandaloneEditorConstructionOptions.html#language
 | 
			
		||||
    // 初始化一些主题
 | 
			
		||||
    monaco.editor.defineTheme('SolarizedLight', SolarizedLight);
 | 
			
		||||
@@ -190,7 +193,7 @@ const initMonacoEditorIns = () => {
 | 
			
		||||
    // 监听内容改变,双向绑定
 | 
			
		||||
    monacoEditorIns.onDidChangeModelContent(() => {
 | 
			
		||||
        emit('update:modelValue', monacoEditorIns.getModel()?.getValue());
 | 
			
		||||
    })
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    // 动态设置主题
 | 
			
		||||
    // monaco.editor.setTheme('hc-black');
 | 
			
		||||
@@ -199,25 +202,25 @@ const initMonacoEditorIns = () => {
 | 
			
		||||
const changeLanguage = (value: any) => {
 | 
			
		||||
    console.log('change lan');
 | 
			
		||||
    // 获取当前的文档模型
 | 
			
		||||
    let oldModel = monacoEditorIns.getModel()
 | 
			
		||||
    let oldModel = monacoEditorIns.getModel();
 | 
			
		||||
    if (!oldModel) {
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
    // 创建一个新的文档模型
 | 
			
		||||
    let newModel = monaco.editor.createModel(oldModel.getValue(), value)
 | 
			
		||||
    let newModel = monaco.editor.createModel(oldModel.getValue(), value);
 | 
			
		||||
    // 设置成新的
 | 
			
		||||
    monacoEditorIns.setModel(newModel)
 | 
			
		||||
    monacoEditorIns.setModel(newModel);
 | 
			
		||||
    // 销毁旧的模型
 | 
			
		||||
    if (oldModel) {
 | 
			
		||||
        oldModel.dispose()
 | 
			
		||||
        oldModel.dispose();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    registerCompletionItemProvider();
 | 
			
		||||
}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const setEditorValue = (value: any) => {
 | 
			
		||||
    monacoEditorIns.getModel()?.setValue(value)
 | 
			
		||||
}
 | 
			
		||||
    monacoEditorIns.getModel()?.setValue(value);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 注册联想补全提示
 | 
			
		||||
@@ -227,44 +230,43 @@ const registerCompletionItemProvider = () => {
 | 
			
		||||
        completionItemProvider.dispose();
 | 
			
		||||
    }
 | 
			
		||||
    if (state.languageMode == 'shell') {
 | 
			
		||||
        registeShell()
 | 
			
		||||
        registeShell();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const registeShell = () => {
 | 
			
		||||
    completionItemProvider = monaco.languages.registerCompletionItemProvider('shell', {
 | 
			
		||||
        provideCompletionItems: async () => {
 | 
			
		||||
            let suggestions: languages.CompletionItem[] = []
 | 
			
		||||
            let suggestions: languages.CompletionItem[] = [];
 | 
			
		||||
            shellLan.keywords.forEach((item: any) => {
 | 
			
		||||
                suggestions.push({
 | 
			
		||||
                    label: item,
 | 
			
		||||
                    kind: monaco.languages.CompletionItemKind.Keyword,
 | 
			
		||||
                    insertText: item,
 | 
			
		||||
                } as any);
 | 
			
		||||
            })
 | 
			
		||||
            });
 | 
			
		||||
            shellLan.builtins.forEach((item: any) => {
 | 
			
		||||
                suggestions.push({
 | 
			
		||||
                    label: item,
 | 
			
		||||
                    kind: monaco.languages.CompletionItemKind.Property,
 | 
			
		||||
                    insertText: item,
 | 
			
		||||
                } as any);
 | 
			
		||||
            })
 | 
			
		||||
            });
 | 
			
		||||
            return {
 | 
			
		||||
                suggestions: suggestions
 | 
			
		||||
                suggestions: suggestions,
 | 
			
		||||
            };
 | 
			
		||||
        }
 | 
			
		||||
    })
 | 
			
		||||
        },
 | 
			
		||||
    });
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const format = () => {
 | 
			
		||||
    /*
 | 
			
		||||
    触发自动格式化;
 | 
			
		||||
   */
 | 
			
		||||
    monacoEditorIns.trigger('', 'editor.action.formatDocument', '')
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
defineExpose({ format })
 | 
			
		||||
    monacoEditorIns.trigger('', 'editor.action.formatDocument', '');
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
defineExpose({ format });
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style lang="scss">
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										432
									
								
								mayfly_go_web/src/components/pagetable/PageTable.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										432
									
								
								mayfly_go_web/src/components/pagetable/PageTable.vue
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,432 @@
 | 
			
		||||
<template>
 | 
			
		||||
    <div class="page-table">
 | 
			
		||||
        <!-- 
 | 
			
		||||
            实现:通过我们配置好的 查询条件
 | 
			
		||||
                首先去创建form表单,根据我们配置的查询条件去做一个循环判断,展示出不用类型所对应不同的输入框
 | 
			
		||||
                比如:text对应普通的输入框,select对应下拉选择,dateTime对应日期时间选择器
 | 
			
		||||
                在使用时,父组件会传来一个queryForm空的对象,
 | 
			
		||||
                循环出来的输入框会绑定表格配置中的prop字段绑定在queryForm对象中
 | 
			
		||||
         -->
 | 
			
		||||
        <el-card>
 | 
			
		||||
            <div class="query" ref="queryRef">
 | 
			
		||||
                <div>
 | 
			
		||||
                    <div v-if="props.query.length > 0">
 | 
			
		||||
                        <el-form :model="props.queryForm" label-width="auto" :size="props.size">
 | 
			
		||||
                            <el-row
 | 
			
		||||
                                v-for="i in Math.ceil((props.query.length + 1) / (defaultQueryCount + 1))"
 | 
			
		||||
                                :key="i"
 | 
			
		||||
                                v-show="i == 1 || isOpenMoreQuery"
 | 
			
		||||
                                :class="i > 1 && isOpenMoreQuery ? 'is-open' : ''"
 | 
			
		||||
                            >
 | 
			
		||||
                                <el-form-item
 | 
			
		||||
                                    :label="item.label"
 | 
			
		||||
                                    style="margin-right: 12px; margin-bottom: 0px"
 | 
			
		||||
                                    v-for="item in getRowQueryItem(i)"
 | 
			
		||||
                                    :key="item.prop"
 | 
			
		||||
                                >
 | 
			
		||||
                                    <!-- 这里只获取指定个数的筛选条件 -->
 | 
			
		||||
                                    <el-input
 | 
			
		||||
                                        v-model="queryForm[item.prop]"
 | 
			
		||||
                                        :placeholder="'输入' + item.label + '关键字'"
 | 
			
		||||
                                        clearable
 | 
			
		||||
                                        v-if="item.type == 'text'"
 | 
			
		||||
                                    ></el-input>
 | 
			
		||||
 | 
			
		||||
                                    <el-select-v2
 | 
			
		||||
                                        v-model="queryForm[item.prop]"
 | 
			
		||||
                                        :options="item.options"
 | 
			
		||||
                                        clearable
 | 
			
		||||
                                        :placeholder="'选择' + item.label + '关键字'"
 | 
			
		||||
                                        v-else-if="item.type == 'select'"
 | 
			
		||||
                                    />
 | 
			
		||||
 | 
			
		||||
                                    <el-date-picker
 | 
			
		||||
                                        v-model="queryForm[item.prop]"
 | 
			
		||||
                                        clearable
 | 
			
		||||
                                        type="datetimerange"
 | 
			
		||||
                                        format="YYYY-MM-DD hh:mm:ss"
 | 
			
		||||
                                        value-format="x"
 | 
			
		||||
                                        range-separator="至"
 | 
			
		||||
                                        start-placeholder="开始时间"
 | 
			
		||||
                                        end-placeholder="结束时间"
 | 
			
		||||
                                        v-else-if="item.type == 'date'"
 | 
			
		||||
                                    />
 | 
			
		||||
 | 
			
		||||
                                    <template v-else-if="item.slot == 'queryBtns'">
 | 
			
		||||
                                        <template v-if="props.query?.length > defaultQueryCount">
 | 
			
		||||
                                            <el-button
 | 
			
		||||
                                                @click="isOpenMoreQuery = !isOpenMoreQuery"
 | 
			
		||||
                                                v-if="!isOpenMoreQuery"
 | 
			
		||||
                                                icon="ArrowDownBold"
 | 
			
		||||
                                                circle
 | 
			
		||||
                                            ></el-button>
 | 
			
		||||
                                            <el-button @click="isOpenMoreQuery = !isOpenMoreQuery" v-else icon="ArrowUpBold" circle></el-button>
 | 
			
		||||
                                        </template>
 | 
			
		||||
 | 
			
		||||
                                        <el-button @click="queryData()" type="primary" icon="search" plain>查询</el-button>
 | 
			
		||||
                                        <el-button @click="reset()" icon="RefreshRight">重置</el-button>
 | 
			
		||||
                                    </template>
 | 
			
		||||
 | 
			
		||||
                                    <slot :name="item.slot"></slot>
 | 
			
		||||
                                </el-form-item>
 | 
			
		||||
                            </el-row>
 | 
			
		||||
                        </el-form>
 | 
			
		||||
                    </div>
 | 
			
		||||
                </div>
 | 
			
		||||
 | 
			
		||||
                <div class="slot">
 | 
			
		||||
                    <!-- 查询栏右侧slot插槽(用来添加表格其他操作,比如,新增数据,删除数据等其他操作) -->
 | 
			
		||||
                    <slot name="queryRight"></slot>
 | 
			
		||||
 | 
			
		||||
                    <!-- 
 | 
			
		||||
                    动态表头显示,根据表格每条配置项中的show字段来决定改列是否显示或者隐藏 
 | 
			
		||||
                    columns 就是我们表格配置的数组对象
 | 
			
		||||
                    -->
 | 
			
		||||
                    <el-popover
 | 
			
		||||
                        placement="bottom"
 | 
			
		||||
                        title="表格配置"
 | 
			
		||||
                        popper-style="max-height: 550px; overflow: auto; max-width: 450px"
 | 
			
		||||
                        width="auto"
 | 
			
		||||
                        trigger="click"
 | 
			
		||||
                    >
 | 
			
		||||
                        <div v-for="(item, index) in props.columns" :key="index">
 | 
			
		||||
                            <el-checkbox v-model="item.show" :label="item.label" :true-label="true" :false-label="false" />
 | 
			
		||||
                        </div>
 | 
			
		||||
                        <template #reference>
 | 
			
		||||
                            <!-- 一个Element Plus中的图标 -->
 | 
			
		||||
                            <el-button icon="Operation" :size="props.size"></el-button>
 | 
			
		||||
                        </template>
 | 
			
		||||
                    </el-popover>
 | 
			
		||||
                </div>
 | 
			
		||||
            </div>
 | 
			
		||||
 | 
			
		||||
            <el-table
 | 
			
		||||
                v-bind="$attrs"
 | 
			
		||||
                max-height="700"
 | 
			
		||||
                @selection-change="handleSelectionChange"
 | 
			
		||||
                :data="props.data"
 | 
			
		||||
                highlight-current-row
 | 
			
		||||
                v-loading="loadingData"
 | 
			
		||||
                :size="props.size"
 | 
			
		||||
            >
 | 
			
		||||
                <el-table-column v-if="props.showSelection" type="selection" width="40" />
 | 
			
		||||
 | 
			
		||||
                <template v-for="(item, index) in columns">
 | 
			
		||||
                    <el-table-column
 | 
			
		||||
                        :key="index"
 | 
			
		||||
                        v-if="item.show"
 | 
			
		||||
                        :prop="item.prop"
 | 
			
		||||
                        :label="item.label"
 | 
			
		||||
                        :fixed="item.fixed"
 | 
			
		||||
                        :align="item.align"
 | 
			
		||||
                        :show-overflow-tooltip="item.showOverflowTooltip"
 | 
			
		||||
                        :min-width="item.minWidth"
 | 
			
		||||
                        :sortable="item.sortable || false"
 | 
			
		||||
                        :type="item.type"
 | 
			
		||||
                        :width="item.width"
 | 
			
		||||
                    >
 | 
			
		||||
                        <!-- 插槽:预留功能 -->
 | 
			
		||||
                        <template #default="scope" v-if="item.slot">
 | 
			
		||||
                            <slot :name="item.prop" :data="scope.row"></slot>
 | 
			
		||||
                        </template>
 | 
			
		||||
 | 
			
		||||
                        <!-- 枚举类型使用tab展示 -->
 | 
			
		||||
                        <template #default="scope" v-else-if="item.type == 'tag'">
 | 
			
		||||
                            <enum-tag :size="props.size" :enums="item.typeParam" :value="scope.row[item.prop]"></enum-tag>
 | 
			
		||||
                        </template>
 | 
			
		||||
 | 
			
		||||
                        <template #default="scope" v-else>
 | 
			
		||||
                            <!-- 配置了美化文本按钮以及文本内容大于指定长度,则显示美化按钮 -->
 | 
			
		||||
                            <el-popover
 | 
			
		||||
                                v-if="item.isBeautify && scope.row[item.prop]?.length > 35"
 | 
			
		||||
                                effect="light"
 | 
			
		||||
                                trigger="click"
 | 
			
		||||
                                placement="top"
 | 
			
		||||
                                width="600px"
 | 
			
		||||
                            >
 | 
			
		||||
                                <template #default>
 | 
			
		||||
                                    <el-input
 | 
			
		||||
                                        input-style="color: black;"
 | 
			
		||||
                                        :autosize="{ minRows: 3, maxRows: 15 }"
 | 
			
		||||
                                        disabled
 | 
			
		||||
                                        v-model="formatVal"
 | 
			
		||||
                                        type="textarea"
 | 
			
		||||
                                    />
 | 
			
		||||
                                </template>
 | 
			
		||||
                                <template #reference>
 | 
			
		||||
                                    <el-link
 | 
			
		||||
                                        @click="formatText(scope.row[item.prop])"
 | 
			
		||||
                                        :underline="false"
 | 
			
		||||
                                        type="success"
 | 
			
		||||
                                        icon="MagicStick"
 | 
			
		||||
                                        class="mr5"
 | 
			
		||||
                                    ></el-link>
 | 
			
		||||
                                </template>
 | 
			
		||||
                            </el-popover>
 | 
			
		||||
 | 
			
		||||
                            <span>{{ item.getValueByData(scope.row) }}</span>
 | 
			
		||||
                        </template>
 | 
			
		||||
                    </el-table-column>
 | 
			
		||||
                </template>
 | 
			
		||||
            </el-table>
 | 
			
		||||
 | 
			
		||||
            <el-row style="margin-top: 20px" type="flex" justify="end">
 | 
			
		||||
                <el-pagination
 | 
			
		||||
                    :small="props.size == 'small'"
 | 
			
		||||
                    @current-change="handlePageChange"
 | 
			
		||||
                    @size-change="handleSizeChange"
 | 
			
		||||
                    style="text-align: right"
 | 
			
		||||
                    layout="prev, pager, next, total, sizes, jumper"
 | 
			
		||||
                    :total="props.total"
 | 
			
		||||
                    v-model:current-page="state.pageNum"
 | 
			
		||||
                    v-model:page-size="state.pageSize"
 | 
			
		||||
                    :page-sizes="pageSizes"
 | 
			
		||||
                />
 | 
			
		||||
            </el-row>
 | 
			
		||||
        </el-card>
 | 
			
		||||
    </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script lang="ts" setup>
 | 
			
		||||
import { toRefs, watch, reactive, onMounted } from 'vue';
 | 
			
		||||
import { TableColumn, TableQuery } from './index';
 | 
			
		||||
import EnumTag from '@/components/enumtag/EnumTag.vue';
 | 
			
		||||
 | 
			
		||||
const emit = defineEmits(['update:queryForm', 'update:pageNum', 'update:pageSize', 'update:selectionData', 'pageChange'])
 | 
			
		||||
 | 
			
		||||
const props = defineProps({
 | 
			
		||||
    size: {
 | 
			
		||||
        type: String,
 | 
			
		||||
        default: '',
 | 
			
		||||
    },
 | 
			
		||||
    inputWidth: {
 | 
			
		||||
        type: [Number, String],
 | 
			
		||||
        default: 0,
 | 
			
		||||
    },
 | 
			
		||||
    // 是否显示选择列
 | 
			
		||||
    showSelection: {
 | 
			
		||||
        type: Boolean,
 | 
			
		||||
        default: false,
 | 
			
		||||
    },
 | 
			
		||||
    // 当前选择的数据
 | 
			
		||||
    selectionData: {
 | 
			
		||||
        type: Array<any>
 | 
			
		||||
    },
 | 
			
		||||
    // 列信息
 | 
			
		||||
    columns: {
 | 
			
		||||
        type: Array<TableColumn>,
 | 
			
		||||
        default: function () {
 | 
			
		||||
            return [];
 | 
			
		||||
        },
 | 
			
		||||
        required: true,
 | 
			
		||||
    },
 | 
			
		||||
    // 表格数据
 | 
			
		||||
    data: {
 | 
			
		||||
        type: Array,
 | 
			
		||||
        required: true,
 | 
			
		||||
    },
 | 
			
		||||
    total: {
 | 
			
		||||
        type: [Number],
 | 
			
		||||
        default: 0,
 | 
			
		||||
    },
 | 
			
		||||
    pageNum: {
 | 
			
		||||
        type: Number,
 | 
			
		||||
        default: 1,
 | 
			
		||||
    },
 | 
			
		||||
    pageSize: {
 | 
			
		||||
        type: [Number],
 | 
			
		||||
        default: 10,
 | 
			
		||||
    },
 | 
			
		||||
    // 查询条件配置
 | 
			
		||||
    query: {
 | 
			
		||||
        type: Array<TableQuery>,
 | 
			
		||||
        default: function () {
 | 
			
		||||
            return [];
 | 
			
		||||
        }
 | 
			
		||||
    },
 | 
			
		||||
    // 绑定的查询表单
 | 
			
		||||
    queryForm: {
 | 
			
		||||
        type: Object,
 | 
			
		||||
        default: function () {
 | 
			
		||||
            return {};
 | 
			
		||||
        }
 | 
			
		||||
    },
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
const state = reactive({
 | 
			
		||||
    pageSizes: [] as any, // 可选每页显示的数据量
 | 
			
		||||
    pageSize: 10,
 | 
			
		||||
    pageNum: 1,
 | 
			
		||||
    isOpenMoreQuery: false,
 | 
			
		||||
    defaultQueryCount: 2, // 默认显示的查询参数个数,展开后每行显示查询条件个数为该值加1。第一行用最后一列来占用按钮
 | 
			
		||||
    queryForm: {} as any,
 | 
			
		||||
    loadingData: false,
 | 
			
		||||
    // 输入框宽度
 | 
			
		||||
    inputWidth: "200px" as any,
 | 
			
		||||
    formatVal: '', // 格式化后的值
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
const {
 | 
			
		||||
    pageSizes,
 | 
			
		||||
    isOpenMoreQuery,
 | 
			
		||||
    defaultQueryCount,
 | 
			
		||||
    queryForm,
 | 
			
		||||
    loadingData,
 | 
			
		||||
    inputWidth,
 | 
			
		||||
    formatVal,
 | 
			
		||||
} = toRefs(state)
 | 
			
		||||
 | 
			
		||||
watch(() => props.queryForm, (newValue: any) => {
 | 
			
		||||
    state.queryForm = newValue;
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
watch(() => props.pageNum, (newValue: any) => {
 | 
			
		||||
    state.pageNum = newValue;
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
watch(() => props.pageSize, (newValue: any) => {
 | 
			
		||||
    state.pageSize = newValue;
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
watch(() => props.data, (newValue: any) => {
 | 
			
		||||
    if (newValue && newValue.length > 0) {
 | 
			
		||||
        props.columns.forEach(item => {
 | 
			
		||||
            if (item.autoWidth && item.show) {
 | 
			
		||||
                item.autoCalculateMinWidth(props.data);
 | 
			
		||||
            }
 | 
			
		||||
        })
 | 
			
		||||
    }
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
onMounted(() => {
 | 
			
		||||
    const pageSize = props.pageSize;
 | 
			
		||||
 | 
			
		||||
    state.pageNum = props.pageNum;
 | 
			
		||||
    state.pageSize = pageSize;
 | 
			
		||||
    state.queryForm = props.queryForm;
 | 
			
		||||
    state.pageSizes = [pageSize, pageSize * 2, pageSize * 3, pageSize * 4];
 | 
			
		||||
 | 
			
		||||
    // 如果没传输入框宽度,则根据组件size设置默认宽度
 | 
			
		||||
    if (!props.inputWidth) {
 | 
			
		||||
        state.inputWidth = props.size == 'small' ? '150px' : '200px';
 | 
			
		||||
    } else {
 | 
			
		||||
        state.inputWidth = props.inputWidth;
 | 
			
		||||
    }
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
const formatText = (data: any)=> {
 | 
			
		||||
    state.formatVal = '';
 | 
			
		||||
    try {
 | 
			
		||||
        state.formatVal = JSON.stringify(JSON.parse(data), null, 4);
 | 
			
		||||
    }  catch (e) {
 | 
			
		||||
        state.formatVal = data;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const getRowQueryItem = (row: number) => {
 | 
			
		||||
    // 第一行需要加个查询等按钮列
 | 
			
		||||
    if (row === 1) {
 | 
			
		||||
        const res = props.query.slice(row - 1, defaultQueryCount.value);
 | 
			
		||||
        // 查询等按钮列
 | 
			
		||||
        res.push(TableQuery.slot("", "", "queryBtns"));
 | 
			
		||||
        return res
 | 
			
		||||
    }
 | 
			
		||||
    const columnCount = defaultQueryCount.value + 1;
 | 
			
		||||
    return props.query.slice((row - 1) * columnCount - 1, row * columnCount - 1);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const handleSelectionChange = (val: any) => {
 | 
			
		||||
    emit('update:selectionData', val);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const handlePageChange = () => {
 | 
			
		||||
    emit('update:pageNum', state.pageNum);
 | 
			
		||||
    execQuery();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const handleSizeChange = () => {
 | 
			
		||||
    changePageNum(1);
 | 
			
		||||
    emit('update:pageSize', state.pageSize);
 | 
			
		||||
    execQuery();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const queryData = () => {
 | 
			
		||||
    changePageNum(1);
 | 
			
		||||
    execQuery();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const reset = () => {
 | 
			
		||||
    // 将查询参数绑定的值置空,并重新粗发查询接口
 | 
			
		||||
    for (let qi of props.query) {
 | 
			
		||||
        state.queryForm[qi.prop] = null;
 | 
			
		||||
    }
 | 
			
		||||
    changePageNum(1);
 | 
			
		||||
    emit('update:queryForm', state.queryForm);
 | 
			
		||||
    execQuery();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const changePageNum = (pageNum: number) => {
 | 
			
		||||
    state.pageNum = pageNum;
 | 
			
		||||
    emit('update:pageNum', state.pageNum);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const execQuery = () => {
 | 
			
		||||
    emit('pageChange');
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 是否正在加载数据
 | 
			
		||||
 */
 | 
			
		||||
const loading = (loading: boolean) => {
 | 
			
		||||
    state.loadingData = loading;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
defineExpose({ loading })
 | 
			
		||||
</script>
 | 
			
		||||
<style scoped lang="scss">
 | 
			
		||||
.page-table {
 | 
			
		||||
    .query {
 | 
			
		||||
        margin-bottom: 10px;
 | 
			
		||||
        overflow: hidden;
 | 
			
		||||
 | 
			
		||||
        .is-open {
 | 
			
		||||
            // padding: 10px 0;
 | 
			
		||||
            max-height: 200px;
 | 
			
		||||
            margin-top: 10px;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        display: flex;
 | 
			
		||||
        align-items: flex-start;
 | 
			
		||||
        justify-content: space-between;
 | 
			
		||||
 | 
			
		||||
        .slot {
 | 
			
		||||
            display: flex;
 | 
			
		||||
            justify-content: flex-end;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .page {
 | 
			
		||||
        margin-top: 10px;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
::v-deep(.el-form-item__label) {
 | 
			
		||||
    font-weight: bold;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.el-select-v2 {
 | 
			
		||||
    width: v-bind(inputWidth);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.el-input {
 | 
			
		||||
    width: v-bind(inputWidth);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.el-select {
 | 
			
		||||
    width: v-bind(inputWidth);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.el-date-editor {
 | 
			
		||||
    width: 380px !important;
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
							
								
								
									
										309
									
								
								mayfly_go_web/src/components/pagetable/index.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										309
									
								
								mayfly_go_web/src/components/pagetable/index.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,309 @@
 | 
			
		||||
import EnumValue from '@/common/Enum';
 | 
			
		||||
import { dateFormat } from '@/common/utils/date';
 | 
			
		||||
import { getTextWidth } from '@/common/utils/string';
 | 
			
		||||
 | 
			
		||||
export class TableColumn {
 | 
			
		||||
    /**
 | 
			
		||||
     * 属性字段
 | 
			
		||||
     */
 | 
			
		||||
    prop: string;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 显示表头
 | 
			
		||||
     */
 | 
			
		||||
    label: string;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 是否自动计算宽度
 | 
			
		||||
     */
 | 
			
		||||
    autoWidth: boolean = true;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 自动计算宽度时,累加该值(可能列值会进行转换 如添加图标等,宽度需要比计算出来的更宽些)
 | 
			
		||||
     */
 | 
			
		||||
    addWidth: number = 0;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 最小宽度
 | 
			
		||||
     */
 | 
			
		||||
    minWidth: number | string;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 是否插槽,是的话插槽名则为prop属性名
 | 
			
		||||
     */
 | 
			
		||||
    slot: boolean = false;
 | 
			
		||||
 | 
			
		||||
    showOverflowTooltip: boolean = true;
 | 
			
		||||
 | 
			
		||||
    sortable: boolean = false;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 官方:对应列的类型。 如果设置了selection则显示多选框;
 | 
			
		||||
     * 如果设置了 index 则显示该行的索引(从 1 开始计算);
 | 
			
		||||
     *
 | 
			
		||||
     * 新增 tag类型,用于枚举值转换后用tag进行展示
 | 
			
		||||
     *
 | 
			
		||||
     */
 | 
			
		||||
    type: string;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 类型展示需要的额外参数,如枚举转换的EnumValue值等
 | 
			
		||||
     */
 | 
			
		||||
    typeParam: any;
 | 
			
		||||
 | 
			
		||||
    width: number | string;
 | 
			
		||||
 | 
			
		||||
    fixed: any;
 | 
			
		||||
 | 
			
		||||
    align: string = 'left';
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 指定格式化函数对原始值进行格式化,如时间格式化等
 | 
			
		||||
     * param1: data, param2: prop
 | 
			
		||||
     */
 | 
			
		||||
    formatFunc: Function;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 是否显示该列
 | 
			
		||||
     */
 | 
			
		||||
    show: boolean = true;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 是否展示美化按钮(主要用于美化json文本等)
 | 
			
		||||
     */
 | 
			
		||||
    isBeautify: boolean = false;
 | 
			
		||||
 | 
			
		||||
    constructor(prop: string, label: string) {
 | 
			
		||||
        this.prop = prop;
 | 
			
		||||
        this.label = label;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 获取该列在指定行数据中的值
 | 
			
		||||
     * @param rowData 该行对应的数据
 | 
			
		||||
     * @returns 该列对应的值
 | 
			
		||||
     */
 | 
			
		||||
    getValueByData(rowData: any) {
 | 
			
		||||
        if (this.formatFunc) {
 | 
			
		||||
            return this.formatFunc(rowData, this.prop);
 | 
			
		||||
        }
 | 
			
		||||
        return rowData[this.prop];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    static new(prop: string, label: string): TableColumn {
 | 
			
		||||
        return new TableColumn(prop, label);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    noShowOverflowTooltip(): TableColumn {
 | 
			
		||||
        this.showOverflowTooltip = false;
 | 
			
		||||
        return this;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    setMinWidth(minWidth: number | string): TableColumn {
 | 
			
		||||
        this.minWidth = minWidth;
 | 
			
		||||
        this.autoWidth = false;
 | 
			
		||||
        return this;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    setAddWidth(addWidth: number): TableColumn {
 | 
			
		||||
        this.addWidth = addWidth;
 | 
			
		||||
        return this;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 居中对齐
 | 
			
		||||
     * @returns this
 | 
			
		||||
     */
 | 
			
		||||
    alignCenter(): TableColumn {
 | 
			
		||||
        this.align = 'center';
 | 
			
		||||
        return this;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 使用标签类型展示该列(用于枚举值友好展示)
 | 
			
		||||
     * @param param 枚举对象
 | 
			
		||||
     * @returns this
 | 
			
		||||
     */
 | 
			
		||||
    typeTag(param: any): TableColumn {
 | 
			
		||||
        this.type = 'tag';
 | 
			
		||||
        this.typeParam = param;
 | 
			
		||||
        return this;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    typeText(): TableColumn {
 | 
			
		||||
        this.type = 'text';
 | 
			
		||||
        return this;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    typeJson(): TableColumn {
 | 
			
		||||
        this.type = 'jsonText';
 | 
			
		||||
        return this;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 标识该列为插槽
 | 
			
		||||
     * @returns this
 | 
			
		||||
     */
 | 
			
		||||
    isSlot(): TableColumn {
 | 
			
		||||
        this.slot = true;
 | 
			
		||||
        return this;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 设置该列的格式化回调函数
 | 
			
		||||
     * @param func 格式化回调函数(参数为 -> data: 该行对应的数据,prop: 该列对应的prop属性值)
 | 
			
		||||
     * @returns
 | 
			
		||||
     */
 | 
			
		||||
    setFormatFunc(func: Function): TableColumn {
 | 
			
		||||
        this.formatFunc = func;
 | 
			
		||||
        return this;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 为时间字段,则使用默认时间格式函数
 | 
			
		||||
     * @returns this
 | 
			
		||||
     */
 | 
			
		||||
    isTime(): TableColumn {
 | 
			
		||||
        this.setFormatFunc((data: any, prop: string) => {
 | 
			
		||||
            return dateFormat(data[prop]);
 | 
			
		||||
        });
 | 
			
		||||
        return this;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 标识该列枚举类,需进行枚举值转换
 | 
			
		||||
     * @returns this
 | 
			
		||||
     */
 | 
			
		||||
    isEnum(enums: any): TableColumn {
 | 
			
		||||
        this.setFormatFunc((data: any, prop: string) => {
 | 
			
		||||
            return EnumValue.getLabelByValue(enums, data[prop]);
 | 
			
		||||
        });
 | 
			
		||||
        return this;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fixedRight(): TableColumn {
 | 
			
		||||
        this.fixed = 'right';
 | 
			
		||||
        return this;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fixedLeft(): TableColumn {
 | 
			
		||||
        this.fixed = 'left';
 | 
			
		||||
        return this;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    canBeautify(): TableColumn {
 | 
			
		||||
        this.isBeautify = true;
 | 
			
		||||
        return this;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 自动计算最小宽度
 | 
			
		||||
     * @param str 字符串
 | 
			
		||||
     * @param tableData 表数据
 | 
			
		||||
     * @param label 表头label也参与宽度计算
 | 
			
		||||
     * @returns 列宽度
 | 
			
		||||
     */
 | 
			
		||||
    autoCalculateMinWidth = (tableData: any) => {
 | 
			
		||||
        const prop = this.prop;
 | 
			
		||||
        const label = this.label;
 | 
			
		||||
 | 
			
		||||
        if (!tableData || !tableData.length || tableData.length === 0 || tableData === undefined) {
 | 
			
		||||
            return 0;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        let maxWidthText = '';
 | 
			
		||||
        let maxWidthValue;
 | 
			
		||||
        // 为了兼容formatFunc格式化回调函数
 | 
			
		||||
        let maxData;
 | 
			
		||||
        // 获取该列中最长的数据(内容)
 | 
			
		||||
        for (let i = 0; i < tableData.length; i++) {
 | 
			
		||||
            let nowData = tableData[i];
 | 
			
		||||
            let nowValue = nowData[prop];
 | 
			
		||||
            if (!nowValue) {
 | 
			
		||||
                continue;
 | 
			
		||||
            }
 | 
			
		||||
            // 转为字符串比较长度
 | 
			
		||||
            let nowText = nowValue + '';
 | 
			
		||||
            if (nowText.length > maxWidthText.length) {
 | 
			
		||||
                maxWidthText = nowText;
 | 
			
		||||
                maxWidthValue = nowValue;
 | 
			
		||||
                maxData = nowData;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        if (this.formatFunc && maxWidthValue) {
 | 
			
		||||
            maxWidthText = this.formatFunc(maxData, prop) + '';
 | 
			
		||||
        }
 | 
			
		||||
        // 需要加上表格的内间距等,视情况加
 | 
			
		||||
        const contentWidth: number = getTextWidth(maxWidthText) + 30;
 | 
			
		||||
        // 获取label的宽度,取较大的宽度
 | 
			
		||||
        const columnWidth: number = getTextWidth(label) + 60;
 | 
			
		||||
        const flexWidth: number = contentWidth > columnWidth ? contentWidth : columnWidth;
 | 
			
		||||
        // 设置上限与累加需要额外增加的宽度
 | 
			
		||||
        this.minWidth = (flexWidth > 400 ? 400 : flexWidth) + this.addWidth;
 | 
			
		||||
    };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export class TableQuery {
 | 
			
		||||
    /**
 | 
			
		||||
     * 属性字段
 | 
			
		||||
     */
 | 
			
		||||
    prop: string;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 显示表头
 | 
			
		||||
     */
 | 
			
		||||
    label: string;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 查询类型,text、select、date
 | 
			
		||||
     */
 | 
			
		||||
    type: string;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * select可选值
 | 
			
		||||
     */
 | 
			
		||||
    options: any;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 插槽名
 | 
			
		||||
     */
 | 
			
		||||
    slot: string;
 | 
			
		||||
 | 
			
		||||
    constructor(prop: string, label: string) {
 | 
			
		||||
        this.prop = prop;
 | 
			
		||||
        this.label = label;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    static new(prop: string, label: string): TableQuery {
 | 
			
		||||
        return new TableQuery(prop, label);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    static text(prop: string, label: string): TableQuery {
 | 
			
		||||
        const tq = new TableQuery(prop, label);
 | 
			
		||||
        tq.type = 'text';
 | 
			
		||||
        return tq;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    static select(prop: string, label: string): TableQuery {
 | 
			
		||||
        const tq = new TableQuery(prop, label);
 | 
			
		||||
        tq.type = 'select';
 | 
			
		||||
        return tq;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    static date(prop: string, label: string): TableQuery {
 | 
			
		||||
        const tq = new TableQuery(prop, label);
 | 
			
		||||
        tq.type = 'date';
 | 
			
		||||
        return tq;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    static slot(prop: string, label: string, slotName: string): TableQuery {
 | 
			
		||||
        const tq = new TableQuery(prop, label);
 | 
			
		||||
        tq.slot = slotName;
 | 
			
		||||
        return tq;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    setOptions(options: any): TableQuery {
 | 
			
		||||
        this.options = options;
 | 
			
		||||
        return this;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -35,7 +35,7 @@ const props = defineProps({
 | 
			
		||||
    isEle: {
 | 
			
		||||
        type: Boolean,
 | 
			
		||||
        default: true,
 | 
			
		||||
    }
 | 
			
		||||
    },
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
// 在线链接、本地引入地址前缀
 | 
			
		||||
@@ -48,7 +48,7 @@ const getIconName = computed(() => {
 | 
			
		||||
 | 
			
		||||
// 用于判断 element plus 自带 svg 图标的显示、隐藏。不存在 空格分隔的icon name即为element plus自带icon
 | 
			
		||||
const isShowIconSvg = computed(() => {
 | 
			
		||||
    const ss = props?.name?.split(" ")
 | 
			
		||||
    const ss = props?.name?.split(' ');
 | 
			
		||||
    if (!ss) {
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
@@ -56,13 +56,13 @@ const isShowIconSvg = computed(() => {
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const isIconfont = () => {
 | 
			
		||||
    return props?.name?.startsWith("iconfont")
 | 
			
		||||
}
 | 
			
		||||
    return props?.name?.startsWith('iconfont');
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const getIconfontName = () => {
 | 
			
		||||
    // iconfont icon-xxxx 获取icon-xxx即可
 | 
			
		||||
    return props?.name?.split(" ")[1]
 | 
			
		||||
}
 | 
			
		||||
    return props?.name?.split(' ')[1];
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// 用于判断在线链接、本地引入等图标显示、隐藏
 | 
			
		||||
const isShowIconImg = computed(() => {
 | 
			
		||||
@@ -102,4 +102,4 @@ const setIconSvgInsStyle = computed(() => {
 | 
			
		||||
    cursor: pointer;
 | 
			
		||||
    vertical-align: middle;
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
</style>
 | 
			
		||||
 
 | 
			
		||||
@@ -1,21 +1,22 @@
 | 
			
		||||
import type { App } from 'vue';
 | 
			
		||||
import { useUserInfo } from '@/store/userInfo';
 | 
			
		||||
import { judementSameArr } from '@/common/utils/arrayOperation';
 | 
			
		||||
import { hasPerm } from '@/components/auth/auth';
 | 
			
		||||
 | 
			
		||||
// 用户权限指令
 | 
			
		||||
export function authDirective(app: App) {
 | 
			
		||||
    // 单个权限验证(v-auth="xxx")
 | 
			
		||||
    app.directive('auth', {
 | 
			
		||||
        mounted(el, binding) {
 | 
			
		||||
            if (!useUserInfo().userInfo.permissions.some((v: any) => v === binding.value)) {
 | 
			
		||||
            if (!hasPerm(binding.value)) {
 | 
			
		||||
                parseNoAuth(el, binding);
 | 
			
		||||
            };
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
    });
 | 
			
		||||
    // 多个权限验证,满足一个则显示(v-auths="[xxx,xxx]")
 | 
			
		||||
    app.directive('auths', {
 | 
			
		||||
        mounted(el, binding) {
 | 
			
		||||
            const value = binding.value
 | 
			
		||||
            const value = binding.value;
 | 
			
		||||
            let flag = false;
 | 
			
		||||
            useUserInfo().userInfo.permissions.map((val: any) => {
 | 
			
		||||
                value.map((v: any) => {
 | 
			
		||||
@@ -32,14 +33,14 @@ export function authDirective(app: App) {
 | 
			
		||||
        mounted(el, binding) {
 | 
			
		||||
            if (!judementSameArr(binding.value, useUserInfo().userInfo.permissions)) {
 | 
			
		||||
                parseNoAuth(el, binding);
 | 
			
		||||
            };
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
    });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 处理没有权限场景
 | 
			
		||||
 * 
 | 
			
		||||
 *
 | 
			
		||||
 * @param el  元素
 | 
			
		||||
 * @param binding 绑定至
 | 
			
		||||
 */
 | 
			
		||||
@@ -54,8 +55,8 @@ const parseNoAuth = (el: any, binding: any) => {
 | 
			
		||||
        // 移除该元素
 | 
			
		||||
        el.parentNode.removeChild(el);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const disableClickFn = (event: any) => {
 | 
			
		||||
    event && event.stopImmediatePropagation();
 | 
			
		||||
}
 | 
			
		||||
};
 | 
			
		||||
 
 | 
			
		||||
@@ -44,7 +44,7 @@ export function wavesDirective(app: App) {
 | 
			
		||||
            el.addEventListener('mousedown', onCurrentClick, false);
 | 
			
		||||
        },
 | 
			
		||||
        unmounted(el) {
 | 
			
		||||
            el.addEventListener('mousedown', () => { });
 | 
			
		||||
            el.addEventListener('mousedown', () => {});
 | 
			
		||||
        },
 | 
			
		||||
    });
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -9,22 +9,19 @@ import { registElSvgIcon } from '@/common/utils/svgIcons';
 | 
			
		||||
 | 
			
		||||
import ElementPlus from 'element-plus';
 | 
			
		||||
import 'element-plus/dist/index.css';
 | 
			
		||||
import zhCn from 'element-plus/es/locale/lang/zh-cn'
 | 
			
		||||
import zhCn from 'element-plus/es/locale/lang/zh-cn';
 | 
			
		||||
import { ElMessage } from 'element-plus';
 | 
			
		||||
 | 
			
		||||
import '@/theme/index.scss';
 | 
			
		||||
import '@/assets/font/font.css'
 | 
			
		||||
import '@/assets/iconfont/iconfont.js'
 | 
			
		||||
import '@/assets/font/font.css';
 | 
			
		||||
import '@/assets/iconfont/iconfont.js';
 | 
			
		||||
 | 
			
		||||
const app = createApp(App);
 | 
			
		||||
 | 
			
		||||
registElSvgIcon(app);
 | 
			
		||||
directive(app);
 | 
			
		||||
 | 
			
		||||
app.use(pinia)
 | 
			
		||||
    .use(router)
 | 
			
		||||
    .use(ElementPlus, { size: globalComponentSize, locale: zhCn })
 | 
			
		||||
    .mount('#app');
 | 
			
		||||
app.use(pinia).use(router).use(ElementPlus, { size: globalComponentSize, locale: zhCn }).mount('#app');
 | 
			
		||||
 | 
			
		||||
// 屏蔽警告信息
 | 
			
		||||
app.config.warnHandler = () => null;
 | 
			
		||||
@@ -32,8 +29,8 @@ app.config.warnHandler = () => null;
 | 
			
		||||
app.config.errorHandler = function (err: any, vm, info) {
 | 
			
		||||
    // 如果是断言错误,则进行提示即可
 | 
			
		||||
    if (err.name == 'AssertError') {
 | 
			
		||||
        ElMessage.error(err.message)
 | 
			
		||||
        ElMessage.error(err.message);
 | 
			
		||||
    } else {
 | 
			
		||||
        console.error(err, info)
 | 
			
		||||
        console.error(err, info);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
};
 | 
			
		||||
 
 | 
			
		||||
@@ -2,9 +2,9 @@ import { createRouter, createWebHashHistory, RouteRecordRaw } from 'vue-router';
 | 
			
		||||
import NProgress from 'nprogress';
 | 
			
		||||
import 'nprogress/nprogress.css';
 | 
			
		||||
import { getSession, clearSession } from '@/common/utils/storage';
 | 
			
		||||
import { templateResolve } from '@/common/utils/string'
 | 
			
		||||
import { templateResolve } from '@/common/utils/string';
 | 
			
		||||
import { NextLoading } from '@/common/utils/loading';
 | 
			
		||||
import { dynamicRoutes, staticRoutes, pathMatch } from './route'
 | 
			
		||||
import { dynamicRoutes, staticRoutes, pathMatch } from './route';
 | 
			
		||||
import openApi from '@/common/openApi';
 | 
			
		||||
import sockets from '@/common/sockets';
 | 
			
		||||
import pinia from '@/store/index';
 | 
			
		||||
@@ -33,17 +33,17 @@ export function initAllFun() {
 | 
			
		||||
    const token = getSession('token'); // 获取浏览器缓存 token 值
 | 
			
		||||
    if (!token) {
 | 
			
		||||
        // 无 token 停止执行下一步
 | 
			
		||||
        return false
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
    useUserInfo().setUserInfo({});
 | 
			
		||||
    router.addRoute(pathMatch); // 添加404界面
 | 
			
		||||
    resetRoute(); // 删除/重置路由
 | 
			
		||||
    // 添加动态路由
 | 
			
		||||
    setFilterRouteEnd().forEach((route: any) => {
 | 
			
		||||
        router.addRoute((route as unknown) as RouteRecordRaw);
 | 
			
		||||
        router.addRoute(route as unknown as RouteRecordRaw);
 | 
			
		||||
    });
 | 
			
		||||
    // 过滤权限菜单
 | 
			
		||||
    useRoutesList().setRoutesList(setFilterMenuFun(dynamicRoutes[0].children, useUserInfo().userInfo.menus))
 | 
			
		||||
    useRoutesList().setRoutesList(setFilterMenuFun(dynamicRoutes[0].children, useUserInfo().userInfo.menus));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 后端控制路由:执行路由数据初始化
 | 
			
		||||
@@ -52,7 +52,7 @@ export async function initBackEndControlRoutesFun() {
 | 
			
		||||
    const token = getSession('token'); // 获取浏览器缓存 token 值
 | 
			
		||||
    if (!token) {
 | 
			
		||||
        // 无 token 停止执行下一步
 | 
			
		||||
        return false
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
    useUserInfo().setUserInfo({});
 | 
			
		||||
    // 获取路由
 | 
			
		||||
@@ -63,59 +63,59 @@ export async function initBackEndControlRoutesFun() {
 | 
			
		||||
    resetRoute(); // 删除/重置路由
 | 
			
		||||
    // 添加动态路由
 | 
			
		||||
    formatTwoStageRoutes(formatFlatteningRoutes(dynamicRoutes)).forEach((route: any) => {
 | 
			
		||||
        router.addRoute((route as unknown) as RouteRecordRaw);
 | 
			
		||||
        router.addRoute(route as unknown as RouteRecordRaw);
 | 
			
		||||
    });
 | 
			
		||||
    useRoutesList().setRoutesList(dynamicRoutes[0].children)
 | 
			
		||||
    useRoutesList().setRoutesList(dynamicRoutes[0].children);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 后端控制路由,isRequestRoutes 为 true,则开启后端控制路由
 | 
			
		||||
export async function getBackEndControlRoutes() {
 | 
			
		||||
    try {
 | 
			
		||||
        const menuAndPermission = await openApi.getPermissions.request();
 | 
			
		||||
        const menuAndPermission = await openApi.getPermissions();
 | 
			
		||||
        // 赋值权限码,用于控制按钮等
 | 
			
		||||
        useUserInfo().userInfo.permissions = menuAndPermission.permissions;
 | 
			
		||||
        return menuAndPermission.menus;
 | 
			
		||||
    } catch (e: any) {
 | 
			
		||||
        console.error(e);
 | 
			
		||||
        return []
 | 
			
		||||
        return [];
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 后端控制路由,后端返回路由 转换为vue route
 | 
			
		||||
export function backEndRouterConverter(routes: any, parentPath: string = "/") {
 | 
			
		||||
export function backEndRouterConverter(routes: any, parentPath: string = '/') {
 | 
			
		||||
    if (!routes) return;
 | 
			
		||||
    return routes.map((item: any) => {
 | 
			
		||||
        if (!item.meta) {
 | 
			
		||||
            return item
 | 
			
		||||
            return item;
 | 
			
		||||
        }
 | 
			
		||||
        // 将json字符串的meta转为对象
 | 
			
		||||
        item.meta = JSON.parse(item.meta)
 | 
			
		||||
        item.meta = JSON.parse(item.meta);
 | 
			
		||||
        // 将meta.comoponet 解析为route.component
 | 
			
		||||
        if (item.meta.component) {
 | 
			
		||||
            item.component = dynamicImport(dynamicViewsModules, item.meta.component)
 | 
			
		||||
            delete item.meta['component']
 | 
			
		||||
            item.component = dynamicImport(dynamicViewsModules, item.meta.component);
 | 
			
		||||
            delete item.meta['component'];
 | 
			
		||||
        }
 | 
			
		||||
        // route.path == resource.code
 | 
			
		||||
        let path = item.code
 | 
			
		||||
        let path = item.code;
 | 
			
		||||
        // 如果不是以 / 开头,则路径需要拼接父路径
 | 
			
		||||
        if (!path.startsWith("/")) {
 | 
			
		||||
            path = parentPath + "/" + path;
 | 
			
		||||
        if (!path.startsWith('/')) {
 | 
			
		||||
            path = parentPath + '/' + path;
 | 
			
		||||
        }
 | 
			
		||||
        item.path = path
 | 
			
		||||
        delete item['code']
 | 
			
		||||
        item.path = path;
 | 
			
		||||
        delete item['code'];
 | 
			
		||||
 | 
			
		||||
        // route.meta.title == resource.name
 | 
			
		||||
        item.meta.title = item.name
 | 
			
		||||
        delete item['name']
 | 
			
		||||
        item.meta.title = item.name;
 | 
			
		||||
        delete item['name'];
 | 
			
		||||
 | 
			
		||||
        // route.name == resource.meta.routeName
 | 
			
		||||
        item.name = item.meta.routeName
 | 
			
		||||
        delete item.meta['routeName']
 | 
			
		||||
        item.name = item.meta.routeName;
 | 
			
		||||
        delete item.meta['routeName'];
 | 
			
		||||
 | 
			
		||||
        // route.redirect == resource.meta.redirect
 | 
			
		||||
        if (item.meta.redirect) {
 | 
			
		||||
            item.redirect = item.meta.redirect
 | 
			
		||||
            delete item.meta['redirect']
 | 
			
		||||
            item.redirect = item.meta.redirect;
 | 
			
		||||
            delete item.meta['redirect'];
 | 
			
		||||
        }
 | 
			
		||||
        item.children && backEndRouterConverter(item.children, item.path);
 | 
			
		||||
        return item;
 | 
			
		||||
@@ -178,9 +178,9 @@ export function formatTwoStageRoutes(arr: any) {
 | 
			
		||||
// 判断路由code 是否包含当前登录用户menus字段中,menus为字符串code数组
 | 
			
		||||
export function hasAnth(menus: any, route: any) {
 | 
			
		||||
    if (route.meta && route.meta.code) {
 | 
			
		||||
        return menus.includes(route.meta.code)
 | 
			
		||||
        return menus.includes(route.meta.code);
 | 
			
		||||
    }
 | 
			
		||||
    return true
 | 
			
		||||
    return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 递归过滤有权限的路由
 | 
			
		||||
@@ -190,7 +190,7 @@ export function setFilterMenuFun(routes: any, menus: any) {
 | 
			
		||||
        const item = { ...route };
 | 
			
		||||
        if (hasAnth(menus, item)) {
 | 
			
		||||
            if (item.children) {
 | 
			
		||||
                item.children = setFilterMenuFun(item.children, menus)
 | 
			
		||||
                item.children = setFilterMenuFun(item.children, menus);
 | 
			
		||||
            }
 | 
			
		||||
            menu.push(item);
 | 
			
		||||
        }
 | 
			
		||||
@@ -206,11 +206,11 @@ export function setFilterRoute(chil: any) {
 | 
			
		||||
        if (route.meta.code) {
 | 
			
		||||
            useUserInfo().userInfo.menus.forEach((m: any) => {
 | 
			
		||||
                if (route.meta.code == m) {
 | 
			
		||||
                    filterRoute.push({ ...route })
 | 
			
		||||
                    filterRoute.push({ ...route });
 | 
			
		||||
                }
 | 
			
		||||
            })
 | 
			
		||||
            });
 | 
			
		||||
        } else {
 | 
			
		||||
            filterRoute.push({ ...route })
 | 
			
		||||
            filterRoute.push({ ...route });
 | 
			
		||||
        }
 | 
			
		||||
    });
 | 
			
		||||
    return filterRoute;
 | 
			
		||||
@@ -253,7 +253,7 @@ router.beforeEach(async (to, from, next) => {
 | 
			
		||||
 | 
			
		||||
    // 如果有标题参数,则再原标题后加上参数来区别
 | 
			
		||||
    if (to.meta.titleRename) {
 | 
			
		||||
        to.meta.title = templateResolve(to.meta.title as string, to.query)
 | 
			
		||||
        to.meta.title = templateResolve(to.meta.title as string, to.query);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const token = getSession('token');
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,5 @@
 | 
			
		||||
import { RouteRecordRaw } from 'vue-router';
 | 
			
		||||
import Layout from '@/views/layout/index.vue'
 | 
			
		||||
import Layout from '@/views/layout/index.vue';
 | 
			
		||||
 | 
			
		||||
// 定义动态路由
 | 
			
		||||
export const dynamicRoutes = [
 | 
			
		||||
@@ -11,109 +11,109 @@ export const dynamicRoutes = [
 | 
			
		||||
        meta: {
 | 
			
		||||
            isKeepAlive: true,
 | 
			
		||||
        },
 | 
			
		||||
        children: []
 | 
			
		||||
    //     children: [
 | 
			
		||||
    //         {
 | 
			
		||||
    //             path: '/home',
 | 
			
		||||
    //             name: 'home',
 | 
			
		||||
    //             component: () => import('@/views/home/index.vue'),
 | 
			
		||||
    //             meta: {
 | 
			
		||||
    //                 title: '首页',
 | 
			
		||||
    //                 // iframe链接
 | 
			
		||||
    //                 link: '',
 | 
			
		||||
    //                 // 是否在菜单栏显示,默认显示
 | 
			
		||||
    //                 isHide: false,
 | 
			
		||||
    //                 isKeepAlive: true,
 | 
			
		||||
    //                 // tag标签是否不可删除
 | 
			
		||||
    //                 isAffix: true,
 | 
			
		||||
    //                 // 是否为iframe
 | 
			
		||||
    //                 isIframe: false,
 | 
			
		||||
    //                 icon: 'el-icon-s-home',
 | 
			
		||||
    //             },
 | 
			
		||||
    //         },
 | 
			
		||||
    //         {
 | 
			
		||||
    //             path: '/sys',
 | 
			
		||||
    //             name: 'Resource',
 | 
			
		||||
    //             redirect: '/sys/resources',
 | 
			
		||||
    //             meta: {
 | 
			
		||||
    //                 title: '系统管理',
 | 
			
		||||
    //                 // 资源code,用于校验用户是否拥有该资源权限
 | 
			
		||||
    //                 code: 'sys',
 | 
			
		||||
    //                 // isKeepAlive: true,
 | 
			
		||||
    //                 icon: 'el-icon-monitor',
 | 
			
		||||
    //             },
 | 
			
		||||
    //             children: [
 | 
			
		||||
    //                 {
 | 
			
		||||
    //                     path: 'sys/resources',
 | 
			
		||||
    //                     name: 'ResourceList',
 | 
			
		||||
    //                     component: () => import('@/views/system/resource'),
 | 
			
		||||
    //                     meta: {
 | 
			
		||||
    //                         title: '资源管理',
 | 
			
		||||
    //                         code: 'resource:list',
 | 
			
		||||
    //                         isKeepAlive: true,
 | 
			
		||||
    //                         icon: 'el-icon-menu',
 | 
			
		||||
    //                     },
 | 
			
		||||
    //                 },
 | 
			
		||||
    //                 {
 | 
			
		||||
    //                     path: 'sys/roles',
 | 
			
		||||
    //                     name: 'RoleList',
 | 
			
		||||
    //                     component: () => import('@/views/system/role'),
 | 
			
		||||
    //                     meta: {
 | 
			
		||||
    //                         title: '角色管理',
 | 
			
		||||
    //                         code: 'role:list',
 | 
			
		||||
    //                         isKeepAlive: true,
 | 
			
		||||
    //                         icon: 'el-icon-menu',
 | 
			
		||||
    //                     },
 | 
			
		||||
    //                 },
 | 
			
		||||
    //                 {
 | 
			
		||||
    //                     path: 'sys/accounts',
 | 
			
		||||
    //                     name: 'ResourceList',
 | 
			
		||||
    //                     component: () => import('@/views/system/account'),
 | 
			
		||||
    //                     meta: {
 | 
			
		||||
    //                         title: '账号管理',
 | 
			
		||||
    //                         code: 'account:list',
 | 
			
		||||
    //                         isKeepAlive: true,
 | 
			
		||||
    //                         icon: 'el-icon-menu',
 | 
			
		||||
    //                     },
 | 
			
		||||
    //                 },
 | 
			
		||||
    //             ],
 | 
			
		||||
    //         },
 | 
			
		||||
    //         {
 | 
			
		||||
    //             path: '/machine',
 | 
			
		||||
    //             name: 'Machine',
 | 
			
		||||
    //             redirect: '/machine/list',
 | 
			
		||||
    //             meta: {
 | 
			
		||||
    //                 title: '机器管理',
 | 
			
		||||
    //                 // 资源code,用于校验用户是否拥有该资源权限
 | 
			
		||||
    //                 code: 'machine',
 | 
			
		||||
    //                 // isKeepAlive: true,
 | 
			
		||||
    //                 icon: 'el-icon-monitor',
 | 
			
		||||
    //             },
 | 
			
		||||
    //             children: [
 | 
			
		||||
    //                 {
 | 
			
		||||
    //                     path: '/list',
 | 
			
		||||
    //                     name: 'MachineList',
 | 
			
		||||
    //                     component: () => import('@/views/ops/machine'),
 | 
			
		||||
    //                     meta: {
 | 
			
		||||
    //                         title: '机器列表',
 | 
			
		||||
    //                         code: 'machine:list',
 | 
			
		||||
    //                         isKeepAlive: true,
 | 
			
		||||
    //                         icon: 'el-icon-menu',
 | 
			
		||||
    //                     },
 | 
			
		||||
    //                 },
 | 
			
		||||
    //             ],
 | 
			
		||||
    //         },
 | 
			
		||||
    //         {
 | 
			
		||||
    //             path: '/personal',
 | 
			
		||||
    //             name: 'personal',
 | 
			
		||||
    //             component: () => import('@/views/personal/index.vue'),
 | 
			
		||||
    //             meta: {
 | 
			
		||||
    //                 title: '个人中心',
 | 
			
		||||
    //                 isKeepAlive: true,
 | 
			
		||||
    //                 icon: 'el-icon-user',
 | 
			
		||||
    //             },
 | 
			
		||||
    //         },
 | 
			
		||||
    //     ],
 | 
			
		||||
        children: [],
 | 
			
		||||
        //     children: [
 | 
			
		||||
        //         {
 | 
			
		||||
        //             path: '/home',
 | 
			
		||||
        //             name: 'home',
 | 
			
		||||
        //             component: () => import('@/views/home/index.vue'),
 | 
			
		||||
        //             meta: {
 | 
			
		||||
        //                 title: '首页',
 | 
			
		||||
        //                 // iframe链接
 | 
			
		||||
        //                 link: '',
 | 
			
		||||
        //                 // 是否在菜单栏显示,默认显示
 | 
			
		||||
        //                 isHide: false,
 | 
			
		||||
        //                 isKeepAlive: true,
 | 
			
		||||
        //                 // tag标签是否不可删除
 | 
			
		||||
        //                 isAffix: true,
 | 
			
		||||
        //                 // 是否为iframe
 | 
			
		||||
        //                 isIframe: false,
 | 
			
		||||
        //                 icon: 'el-icon-s-home',
 | 
			
		||||
        //             },
 | 
			
		||||
        //         },
 | 
			
		||||
        //         {
 | 
			
		||||
        //             path: '/sys',
 | 
			
		||||
        //             name: 'Resource',
 | 
			
		||||
        //             redirect: '/sys/resources',
 | 
			
		||||
        //             meta: {
 | 
			
		||||
        //                 title: '系统管理',
 | 
			
		||||
        //                 // 资源code,用于校验用户是否拥有该资源权限
 | 
			
		||||
        //                 code: 'sys',
 | 
			
		||||
        //                 // isKeepAlive: true,
 | 
			
		||||
        //                 icon: 'el-icon-monitor',
 | 
			
		||||
        //             },
 | 
			
		||||
        //             children: [
 | 
			
		||||
        //                 {
 | 
			
		||||
        //                     path: 'sys/resources',
 | 
			
		||||
        //                     name: 'ResourceList',
 | 
			
		||||
        //                     component: () => import('@/views/system/resource'),
 | 
			
		||||
        //                     meta: {
 | 
			
		||||
        //                         title: '资源管理',
 | 
			
		||||
        //                         code: 'resource:list',
 | 
			
		||||
        //                         isKeepAlive: true,
 | 
			
		||||
        //                         icon: 'el-icon-menu',
 | 
			
		||||
        //                     },
 | 
			
		||||
        //                 },
 | 
			
		||||
        //                 {
 | 
			
		||||
        //                     path: 'sys/roles',
 | 
			
		||||
        //                     name: 'RoleList',
 | 
			
		||||
        //                     component: () => import('@/views/system/role'),
 | 
			
		||||
        //                     meta: {
 | 
			
		||||
        //                         title: '角色管理',
 | 
			
		||||
        //                         code: 'role:list',
 | 
			
		||||
        //                         isKeepAlive: true,
 | 
			
		||||
        //                         icon: 'el-icon-menu',
 | 
			
		||||
        //                     },
 | 
			
		||||
        //                 },
 | 
			
		||||
        //                 {
 | 
			
		||||
        //                     path: 'sys/accounts',
 | 
			
		||||
        //                     name: 'ResourceList',
 | 
			
		||||
        //                     component: () => import('@/views/system/account'),
 | 
			
		||||
        //                     meta: {
 | 
			
		||||
        //                         title: '账号管理',
 | 
			
		||||
        //                         code: 'account:list',
 | 
			
		||||
        //                         isKeepAlive: true,
 | 
			
		||||
        //                         icon: 'el-icon-menu',
 | 
			
		||||
        //                     },
 | 
			
		||||
        //                 },
 | 
			
		||||
        //             ],
 | 
			
		||||
        //         },
 | 
			
		||||
        //         {
 | 
			
		||||
        //             path: '/machine',
 | 
			
		||||
        //             name: 'Machine',
 | 
			
		||||
        //             redirect: '/machine/list',
 | 
			
		||||
        //             meta: {
 | 
			
		||||
        //                 title: '机器管理',
 | 
			
		||||
        //                 // 资源code,用于校验用户是否拥有该资源权限
 | 
			
		||||
        //                 code: 'machine',
 | 
			
		||||
        //                 // isKeepAlive: true,
 | 
			
		||||
        //                 icon: 'el-icon-monitor',
 | 
			
		||||
        //             },
 | 
			
		||||
        //             children: [
 | 
			
		||||
        //                 {
 | 
			
		||||
        //                     path: '/list',
 | 
			
		||||
        //                     name: 'MachineList',
 | 
			
		||||
        //                     component: () => import('@/views/ops/machine'),
 | 
			
		||||
        //                     meta: {
 | 
			
		||||
        //                         title: '机器列表',
 | 
			
		||||
        //                         code: 'machine:list',
 | 
			
		||||
        //                         isKeepAlive: true,
 | 
			
		||||
        //                         icon: 'el-icon-menu',
 | 
			
		||||
        //                     },
 | 
			
		||||
        //                 },
 | 
			
		||||
        //             ],
 | 
			
		||||
        //         },
 | 
			
		||||
        //         {
 | 
			
		||||
        //             path: '/personal',
 | 
			
		||||
        //             name: 'personal',
 | 
			
		||||
        //             component: () => import('@/views/personal/index.vue'),
 | 
			
		||||
        //             meta: {
 | 
			
		||||
        //                 title: '个人中心',
 | 
			
		||||
        //                 isKeepAlive: true,
 | 
			
		||||
        //                 icon: 'el-icon-user',
 | 
			
		||||
        //             },
 | 
			
		||||
        //         },
 | 
			
		||||
        //     ],
 | 
			
		||||
    },
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
@@ -160,4 +160,4 @@ export const staticRoutes: Array<RouteRecordRaw> = [
 | 
			
		||||
export const pathMatch = {
 | 
			
		||||
    path: '/:path(.*)*',
 | 
			
		||||
    redirect: '/404',
 | 
			
		||||
};
 | 
			
		||||
};
 | 
			
		||||
 
 | 
			
		||||
@@ -9,27 +9,27 @@ import { defineStore } from 'pinia';
 | 
			
		||||
 * @methods delAllCachedViews 右键菜单`全部关闭`,删除要缓存的路由 names(关闭 Tagsview)
 | 
			
		||||
 */
 | 
			
		||||
export const useKeepALiveNames = defineStore('keepALiveNames', {
 | 
			
		||||
	state: (): KeepAliveNamesState => ({
 | 
			
		||||
		keepAliveNames: [],
 | 
			
		||||
		cachedViews: [],
 | 
			
		||||
	}),
 | 
			
		||||
	actions: {
 | 
			
		||||
		async setCacheKeepAlive(data: Array<string>) {
 | 
			
		||||
			this.keepAliveNames = data;
 | 
			
		||||
		},
 | 
			
		||||
		async addCachedView(view: any) {
 | 
			
		||||
			if (view.meta.isKeepAlive) this.cachedViews?.push(view.name);
 | 
			
		||||
		},
 | 
			
		||||
		async delCachedView(view: any) {
 | 
			
		||||
			const index = this.cachedViews.indexOf(view.name);
 | 
			
		||||
			index > -1 && this.cachedViews.splice(index, 1);
 | 
			
		||||
		},
 | 
			
		||||
		async delOthersCachedViews(view: any) {
 | 
			
		||||
			if (view.meta.isKeepAlive) this.cachedViews = [view.name];
 | 
			
		||||
			else this.cachedViews = [];
 | 
			
		||||
		},
 | 
			
		||||
		async delAllCachedViews() {
 | 
			
		||||
			this.cachedViews = [];
 | 
			
		||||
		},
 | 
			
		||||
	},
 | 
			
		||||
    state: (): KeepAliveNamesState => ({
 | 
			
		||||
        keepAliveNames: [],
 | 
			
		||||
        cachedViews: [],
 | 
			
		||||
    }),
 | 
			
		||||
    actions: {
 | 
			
		||||
        async setCacheKeepAlive(data: Array<string>) {
 | 
			
		||||
            this.keepAliveNames = data;
 | 
			
		||||
        },
 | 
			
		||||
        async addCachedView(view: any) {
 | 
			
		||||
            if (view.meta.isKeepAlive) this.cachedViews?.push(view.name);
 | 
			
		||||
        },
 | 
			
		||||
        async delCachedView(view: any) {
 | 
			
		||||
            const index = this.cachedViews.indexOf(view.name);
 | 
			
		||||
            index > -1 && this.cachedViews.splice(index, 1);
 | 
			
		||||
        },
 | 
			
		||||
        async delOthersCachedViews(view: any) {
 | 
			
		||||
            if (view.meta.isKeepAlive) this.cachedViews = [view.name];
 | 
			
		||||
            else this.cachedViews = [];
 | 
			
		||||
        },
 | 
			
		||||
        async delAllCachedViews() {
 | 
			
		||||
            this.cachedViews = [];
 | 
			
		||||
        },
 | 
			
		||||
    },
 | 
			
		||||
});
 | 
			
		||||
 
 | 
			
		||||
@@ -7,12 +7,12 @@ import { defineStore } from 'pinia';
 | 
			
		||||
 * @methods setColumnsNavHover 设置分栏布局最左侧导航鼠标移入 boolean
 | 
			
		||||
 */
 | 
			
		||||
export const useRoutesList = defineStore('routesList', {
 | 
			
		||||
	state: (): RoutesListState => ({
 | 
			
		||||
		routesList: [],
 | 
			
		||||
	}),
 | 
			
		||||
	actions: {
 | 
			
		||||
		async setRoutesList(data: Array<string>) {
 | 
			
		||||
			this.routesList = data;
 | 
			
		||||
		},
 | 
			
		||||
	},
 | 
			
		||||
});
 | 
			
		||||
    state: (): RoutesListState => ({
 | 
			
		||||
        routesList: [],
 | 
			
		||||
    }),
 | 
			
		||||
    actions: {
 | 
			
		||||
        async setRoutesList(data: Array<string>) {
 | 
			
		||||
            this.routesList = data;
 | 
			
		||||
        },
 | 
			
		||||
    },
 | 
			
		||||
});
 | 
			
		||||
 
 | 
			
		||||
@@ -116,7 +116,6 @@ export const useThemeConfig = defineStore('themeConfig', {
 | 
			
		||||
            // 编辑器主题
 | 
			
		||||
            editorTheme: 'vs',
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
            /* 后端控制路由
 | 
			
		||||
            ------------------------------- */
 | 
			
		||||
            // 是否开启后端控制路由
 | 
			
		||||
@@ -140,4 +139,4 @@ export const useThemeConfig = defineStore('themeConfig', {
 | 
			
		||||
            this.themeConfig = data.themeConfig;
 | 
			
		||||
        },
 | 
			
		||||
    },
 | 
			
		||||
})
 | 
			
		||||
});
 | 
			
		||||
 
 | 
			
		||||
@@ -8,12 +8,12 @@ export const useUserInfo = defineStore('userInfo', {
 | 
			
		||||
    actions: {
 | 
			
		||||
        // 设置用户信息
 | 
			
		||||
        async setUserInfo(data: object) {
 | 
			
		||||
            const ui = getSession('userInfo')
 | 
			
		||||
			if (ui) {
 | 
			
		||||
				this.userInfo = ui;
 | 
			
		||||
			} else {
 | 
			
		||||
				this.userInfo = data;
 | 
			
		||||
			}
 | 
			
		||||
            const ui = getSession('userInfo');
 | 
			
		||||
            if (ui) {
 | 
			
		||||
                this.userInfo = ui;
 | 
			
		||||
            } else {
 | 
			
		||||
                this.userInfo = data;
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
    }
 | 
			
		||||
})
 | 
			
		||||
    },
 | 
			
		||||
});
 | 
			
		||||
 
 | 
			
		||||
@@ -2,29 +2,44 @@
 | 
			
		||||
    <div>
 | 
			
		||||
        <el-form ref="loginFormRef" :model="loginForm" :rules="rules" class="login-content-form" size="large">
 | 
			
		||||
            <el-form-item prop="username">
 | 
			
		||||
                <el-input type="text" placeholder="请输入用户名" prefix-icon="user" v-model="loginForm.username" clearable
 | 
			
		||||
                    autocomplete="off">
 | 
			
		||||
                </el-input>
 | 
			
		||||
                <el-input type="text" placeholder="请输入用户名" prefix-icon="user" v-model="loginForm.username" clearable autocomplete="off"> </el-input>
 | 
			
		||||
            </el-form-item>
 | 
			
		||||
            <el-form-item prop="password">
 | 
			
		||||
                <el-input type="password" placeholder="请输入密码" prefix-icon="lock" v-model="loginForm.password"
 | 
			
		||||
                    autocomplete="off" @keyup.enter="login" show-password>
 | 
			
		||||
                <el-input
 | 
			
		||||
                    type="password"
 | 
			
		||||
                    placeholder="请输入密码"
 | 
			
		||||
                    prefix-icon="lock"
 | 
			
		||||
                    v-model="loginForm.password"
 | 
			
		||||
                    autocomplete="off"
 | 
			
		||||
                    @keyup.enter="login"
 | 
			
		||||
                    show-password
 | 
			
		||||
                >
 | 
			
		||||
                </el-input>
 | 
			
		||||
            </el-form-item>
 | 
			
		||||
            <el-form-item v-if="isUseLoginCaptcha" prop="captcha">
 | 
			
		||||
            <el-form-item v-if="accountLoginSecurity.useCaptcha" prop="captcha">
 | 
			
		||||
                <el-row :gutter="15">
 | 
			
		||||
                    <el-col :span="16">
 | 
			
		||||
                        <el-input type="text" maxlength="6" placeholder="请输入验证码" prefix-icon="position"
 | 
			
		||||
                            v-model="loginForm.captcha" clearable autocomplete="off" @keyup.enter="login"></el-input>
 | 
			
		||||
                        <el-input
 | 
			
		||||
                            type="text"
 | 
			
		||||
                            maxlength="6"
 | 
			
		||||
                            placeholder="请输入验证码"
 | 
			
		||||
                            prefix-icon="position"
 | 
			
		||||
                            v-model="loginForm.captcha"
 | 
			
		||||
                            clearable
 | 
			
		||||
                            autocomplete="off"
 | 
			
		||||
                            @keyup.enter="login"
 | 
			
		||||
                        ></el-input>
 | 
			
		||||
                    </el-col>
 | 
			
		||||
                    <el-col :span="8">
 | 
			
		||||
                        <div class="login-content-code">
 | 
			
		||||
                            <img class="login-content-code-img" @click="getCaptcha" width="130px" height="40px"
 | 
			
		||||
                                :src="captchaImage" style="cursor: pointer" />
 | 
			
		||||
                            <img class="login-content-code-img" @click="getCaptcha" width="130px" height="40px" :src="captchaImage" style="cursor: pointer" />
 | 
			
		||||
                        </div>
 | 
			
		||||
                    </el-col>
 | 
			
		||||
                </el-row>
 | 
			
		||||
            </el-form-item>
 | 
			
		||||
            <span v-if="showLoginFailTips" style="color: #f56c6c; font-size: 12px">
 | 
			
		||||
                提示:登录失败超过{{ accountLoginSecurity.loginFailCount }}次后将被限制{{ accountLoginSecurity.loginFailMin }}分钟内不可再次登录
 | 
			
		||||
            </span>
 | 
			
		||||
            <el-form-item>
 | 
			
		||||
                <el-button type="primary" class="login-content-submit" round @click="login" :loading="loading.signIn">
 | 
			
		||||
                    <span>登 录</span>
 | 
			
		||||
@@ -32,20 +47,21 @@
 | 
			
		||||
            </el-form-item>
 | 
			
		||||
        </el-form>
 | 
			
		||||
 | 
			
		||||
        <el-dialog title="修改密码" v-model="changePwdDialog.visible" :close-on-click-modal="false" width="450px"
 | 
			
		||||
            :destroy-on-close="true">
 | 
			
		||||
            <el-form :model="changePwdDialog.form" :rules="changePwdDialog.rules" ref="changePwdFormRef"
 | 
			
		||||
                label-width="65px">
 | 
			
		||||
        <el-dialog title="修改密码" v-model="changePwdDialog.visible" :close-on-click-modal="false" width="450px" :destroy-on-close="true">
 | 
			
		||||
            <el-form :model="changePwdDialog.form" :rules="changePwdDialog.rules" ref="changePwdFormRef" label-width="auto">
 | 
			
		||||
                <el-form-item prop="username" label="用户名" required>
 | 
			
		||||
                    <el-input v-model.trim="changePwdDialog.form.username" disabled></el-input>
 | 
			
		||||
                </el-form-item>
 | 
			
		||||
                <el-form-item prop="oldPassword" label="旧密码" required>
 | 
			
		||||
                    <el-input v-model.trim="changePwdDialog.form.oldPassword" autocomplete="new-password"
 | 
			
		||||
                        type="password"></el-input>
 | 
			
		||||
                    <el-input v-model.trim="changePwdDialog.form.oldPassword" autocomplete="new-password" type="password"></el-input>
 | 
			
		||||
                </el-form-item>
 | 
			
		||||
                <el-form-item prop="newPassword" label="新密码" required>
 | 
			
		||||
                    <el-input v-model.trim="changePwdDialog.form.newPassword" placeholder="须为8位以上且包含字⺟⼤⼩写+数字+特殊符号"
 | 
			
		||||
                        type="password" autocomplete="new-password"></el-input>
 | 
			
		||||
                    <el-input
 | 
			
		||||
                        v-model.trim="changePwdDialog.form.newPassword"
 | 
			
		||||
                        placeholder="须为8位以上且包含字⺟⼤⼩写+数字+特殊符号"
 | 
			
		||||
                        type="password"
 | 
			
		||||
                        autocomplete="new-password"
 | 
			
		||||
                    ></el-input>
 | 
			
		||||
                </el-form-item>
 | 
			
		||||
            </el-form>
 | 
			
		||||
 | 
			
		||||
@@ -56,6 +72,38 @@
 | 
			
		||||
                </div>
 | 
			
		||||
            </template>
 | 
			
		||||
        </el-dialog>
 | 
			
		||||
 | 
			
		||||
        <el-dialog
 | 
			
		||||
            title="OTP校验"
 | 
			
		||||
            v-model="otpDialog.visible"
 | 
			
		||||
            @close="loading.signIn = false"
 | 
			
		||||
            :close-on-click-modal="false"
 | 
			
		||||
            width="350px"
 | 
			
		||||
            :destroy-on-close="true"
 | 
			
		||||
        >
 | 
			
		||||
            <el-form ref="otpFormRef" :model="otpDialog.form" :rules="otpDialog.rules" @submit.native.prevent label-width="auto">
 | 
			
		||||
                <el-form-item v-if="otpDialog.otpUrl" label="二维码">
 | 
			
		||||
                    <qrcode-vue :value="otpDialog.otpUrl" :size="200" level="H" />
 | 
			
		||||
                </el-form-item>
 | 
			
		||||
 | 
			
		||||
                <el-form-item prop="code" label="OTP" required>
 | 
			
		||||
                    <el-input
 | 
			
		||||
                        style="width: 220px"
 | 
			
		||||
                        ref="otpCodeInputRef"
 | 
			
		||||
                        v-model.trim="otpDialog.form.code"
 | 
			
		||||
                        clearable
 | 
			
		||||
                        @keyup.enter="otpVerify"
 | 
			
		||||
                        placeholder="请输入令牌APP中显示的授权码"
 | 
			
		||||
                    ></el-input>
 | 
			
		||||
                </el-form-item>
 | 
			
		||||
            </el-form>
 | 
			
		||||
 | 
			
		||||
            <template #footer>
 | 
			
		||||
                <div class="dialog-footer">
 | 
			
		||||
                    <el-button @click="otpVerify" type="primary" :loading="loading.otpConfirm">确 定</el-button>
 | 
			
		||||
                </div>
 | 
			
		||||
            </template>
 | 
			
		||||
        </el-dialog>
 | 
			
		||||
    </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
@@ -68,23 +116,32 @@ import { setSession, setUserInfo2Session, setUseWatermark2Session } from '@/comm
 | 
			
		||||
import { formatAxis } from '@/common/utils/format';
 | 
			
		||||
import openApi from '@/common/openApi';
 | 
			
		||||
import { RsaEncrypt } from '@/common/rsa';
 | 
			
		||||
import { useLoginCaptcha, useWartermark } from '@/common/sysconfig';
 | 
			
		||||
import { getAccountLoginSecurity, useWartermark } from '@/common/sysconfig';
 | 
			
		||||
import { letterAvatar } from '@/common/utils/string';
 | 
			
		||||
import { useUserInfo } from '@/store/userInfo';
 | 
			
		||||
import QrcodeVue from 'qrcode.vue';
 | 
			
		||||
 | 
			
		||||
const rules = {
 | 
			
		||||
    username: [{ required: true, message: '请输入用户名', trigger: 'blur' }],
 | 
			
		||||
    password: [{ required: true, message: '请输入密码', trigger: 'blur' }],
 | 
			
		||||
    captcha: [{ required: true, message: '请输入验证码', trigger: 'blur' }],
 | 
			
		||||
}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const route = useRoute();
 | 
			
		||||
const router = useRouter();
 | 
			
		||||
const loginFormRef: any = ref(null);
 | 
			
		||||
const changePwdFormRef: any = ref(null);
 | 
			
		||||
const otpFormRef: any = ref(null);
 | 
			
		||||
const otpCodeInputRef: any = ref(null);
 | 
			
		||||
 | 
			
		||||
const state = reactive({
 | 
			
		||||
    isUseLoginCaptcha: false,
 | 
			
		||||
    accountLoginSecurity: {
 | 
			
		||||
        useCaptcha: true,
 | 
			
		||||
        useOtp: false,
 | 
			
		||||
        loginFailCount: 5,
 | 
			
		||||
        loginFailMin: 10,
 | 
			
		||||
    },
 | 
			
		||||
    showLoginFailTips: false,
 | 
			
		||||
    captchaImage: '',
 | 
			
		||||
    loginForm: {
 | 
			
		||||
        username: '',
 | 
			
		||||
@@ -110,23 +167,32 @@ const state = reactive({
 | 
			
		||||
            ],
 | 
			
		||||
        },
 | 
			
		||||
    },
 | 
			
		||||
    otpDialog: {
 | 
			
		||||
        visible: false,
 | 
			
		||||
        otpUrl: '',
 | 
			
		||||
        form: {
 | 
			
		||||
            code: '',
 | 
			
		||||
            otpToken: '',
 | 
			
		||||
        },
 | 
			
		||||
        rules: {
 | 
			
		||||
            code: [{ required: true, message: '请输入OTP授权码', trigger: 'blur' }],
 | 
			
		||||
        },
 | 
			
		||||
    },
 | 
			
		||||
    loading: {
 | 
			
		||||
        signIn: false,
 | 
			
		||||
        changePwd: false,
 | 
			
		||||
        otpConfirm: false,
 | 
			
		||||
    },
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const {
 | 
			
		||||
    isUseLoginCaptcha,
 | 
			
		||||
    captchaImage,
 | 
			
		||||
    loginForm,
 | 
			
		||||
    changePwdDialog,
 | 
			
		||||
    loading,
 | 
			
		||||
} = toRefs(state)
 | 
			
		||||
const { accountLoginSecurity, showLoginFailTips, captchaImage, loginForm, changePwdDialog, otpDialog, loading } = toRefs(state);
 | 
			
		||||
 | 
			
		||||
onMounted(async () => {
 | 
			
		||||
    nextTick(async () => {
 | 
			
		||||
        state.isUseLoginCaptcha = await useLoginCaptcha();
 | 
			
		||||
        const res = await getAccountLoginSecurity();
 | 
			
		||||
        if (res) {
 | 
			
		||||
            state.accountLoginSecurity = res;
 | 
			
		||||
        }
 | 
			
		||||
        getCaptcha();
 | 
			
		||||
    });
 | 
			
		||||
    // 移除公钥, 方便后续重新获取
 | 
			
		||||
@@ -134,10 +200,10 @@ onMounted(async () => {
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const getCaptcha = async () => {
 | 
			
		||||
    if (!state.isUseLoginCaptcha) {
 | 
			
		||||
    if (!state.accountLoginSecurity.useCaptcha) {
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
    let res: any = await openApi.captcha.request();
 | 
			
		||||
    let res: any = await openApi.captcha();
 | 
			
		||||
    state.captchaImage = res.base64Captcha;
 | 
			
		||||
    state.loginForm.cid = res.cid;
 | 
			
		||||
};
 | 
			
		||||
@@ -158,6 +224,22 @@ const login = () => {
 | 
			
		||||
    });
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const otpVerify = async () => {
 | 
			
		||||
    otpFormRef.value.validate(async (valid: boolean) => {
 | 
			
		||||
        if (!valid) {
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
        try {
 | 
			
		||||
            state.loading.otpConfirm = true;
 | 
			
		||||
            const accessToken = await openApi.otpVerify(state.otpDialog.form);
 | 
			
		||||
            await signInSuccess(accessToken);
 | 
			
		||||
            state.otpDialog.visible = false;
 | 
			
		||||
        } finally {
 | 
			
		||||
            state.loading.otpConfirm = false;
 | 
			
		||||
        }
 | 
			
		||||
    });
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// 登录
 | 
			
		||||
const onSignIn = async () => {
 | 
			
		||||
    state.loading.signIn = true;
 | 
			
		||||
@@ -166,9 +248,7 @@ const onSignIn = async () => {
 | 
			
		||||
    try {
 | 
			
		||||
        const loginReq = { ...state.loginForm };
 | 
			
		||||
        loginReq.password = await RsaEncrypt(originPwd);
 | 
			
		||||
        loginRes = await openApi.login.request(loginReq);
 | 
			
		||||
        // 存储 token 到浏览器缓存
 | 
			
		||||
        setSession('token', loginRes.token);
 | 
			
		||||
        loginRes = await openApi.login(loginReq);
 | 
			
		||||
    } catch (e: any) {
 | 
			
		||||
        state.loading.signIn = false;
 | 
			
		||||
        state.loginForm.captcha = '';
 | 
			
		||||
@@ -180,9 +260,11 @@ const onSignIn = async () => {
 | 
			
		||||
            state.changePwdDialog.visible = true;
 | 
			
		||||
        } else {
 | 
			
		||||
            getCaptcha();
 | 
			
		||||
            state.showLoginFailTips = true;
 | 
			
		||||
        }
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
    state.showLoginFailTips = false;
 | 
			
		||||
    // 用户信息
 | 
			
		||||
    const userInfos = {
 | 
			
		||||
        name: loginRes.name,
 | 
			
		||||
@@ -190,7 +272,7 @@ const onSignIn = async () => {
 | 
			
		||||
        // 头像
 | 
			
		||||
        photo: letterAvatar(state.loginForm.username),
 | 
			
		||||
        time: new Date().getTime(),
 | 
			
		||||
        permissions: loginRes.permissions,
 | 
			
		||||
        // permissions: loginRes.permissions,
 | 
			
		||||
        lastLoginTime: loginRes.lastLoginTime,
 | 
			
		||||
        lastLoginIp: loginRes.lastLoginIp,
 | 
			
		||||
    };
 | 
			
		||||
@@ -199,12 +281,28 @@ const onSignIn = async () => {
 | 
			
		||||
    setUserInfo2Session(userInfos);
 | 
			
		||||
    // 1、请注意执行顺序(存储用户信息到vuex)
 | 
			
		||||
    useUserInfo().setUserInfo(userInfos);
 | 
			
		||||
    await initRouter();
 | 
			
		||||
    signInSuccess();
 | 
			
		||||
 | 
			
		||||
    const token = loginRes.token;
 | 
			
		||||
    // 如果不需要otp校验,则该token即为accessToken,否则为otp校验token
 | 
			
		||||
    if (loginRes.otp == -1) {
 | 
			
		||||
        signInSuccess(token);
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    state.otpDialog.form.otpToken = token;
 | 
			
		||||
    state.otpDialog.otpUrl = loginRes.otpUrl;
 | 
			
		||||
    state.otpDialog.visible = true;
 | 
			
		||||
    setTimeout(() => {
 | 
			
		||||
        otpCodeInputRef.value.focus();
 | 
			
		||||
    }, 400);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// 登录成功后的跳转
 | 
			
		||||
const signInSuccess = () => {
 | 
			
		||||
const signInSuccess = async (accessToken: string = '') => {
 | 
			
		||||
    // 存储 token 到浏览器缓存
 | 
			
		||||
    setSession('token', accessToken);
 | 
			
		||||
    // 初始化路由
 | 
			
		||||
    await initRouter();
 | 
			
		||||
    // 初始化登录成功时间问候语
 | 
			
		||||
    let currentTimeInfo = currentTime.value;
 | 
			
		||||
    // 登录成功,跳到转首页
 | 
			
		||||
@@ -233,7 +331,7 @@ const changePwd = () => {
 | 
			
		||||
            const changePwdReq: any = { ...form };
 | 
			
		||||
            changePwdReq.oldPassword = await RsaEncrypt(form.oldPassword);
 | 
			
		||||
            changePwdReq.newPassword = await RsaEncrypt(form.newPassword);
 | 
			
		||||
            await openApi.changePwd.request(changePwdReq);
 | 
			
		||||
            await openApi.changePwd(changePwdReq);
 | 
			
		||||
            ElMessage.success('密码修改成功, 新密码已填充至登录密码框');
 | 
			
		||||
            state.loginForm.password = state.changePwdDialog.form.newPassword;
 | 
			
		||||
            state.changePwdDialog.visible = false;
 | 
			
		||||
 
 | 
			
		||||
@@ -1,8 +1,7 @@
 | 
			
		||||
<template>
 | 
			
		||||
    <el-form class="login-content-form">
 | 
			
		||||
        <el-form-item>
 | 
			
		||||
            <el-input type="text" placeholder="请输入手机号" prefix-icon="el-icon-user" v-model="ruleForm.userName" clearable autocomplete="off">
 | 
			
		||||
            </el-input>
 | 
			
		||||
            <el-input type="text" placeholder="请输入手机号" prefix-icon="el-icon-user" v-model="ruleForm.userName" clearable autocomplete="off"> </el-input>
 | 
			
		||||
        </el-form-item>
 | 
			
		||||
        <el-form-item>
 | 
			
		||||
            <el-row :gutter="15">
 | 
			
		||||
 
 | 
			
		||||
@@ -43,11 +43,7 @@ const state = reactive({
 | 
			
		||||
    isTabPaneShow: true,
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const {
 | 
			
		||||
    isTabPaneShow,
 | 
			
		||||
    tabsActiveName,
 | 
			
		||||
} = toRefs(state)
 | 
			
		||||
 | 
			
		||||
const { isTabPaneShow, tabsActiveName } = toRefs(state);
 | 
			
		||||
 | 
			
		||||
// 切换密码、手机登录
 | 
			
		||||
const onTabsClick = () => {
 | 
			
		||||
 
 | 
			
		||||
@@ -1,10 +1,15 @@
 | 
			
		||||
<template>
 | 
			
		||||
    <div style="width: 100%">
 | 
			
		||||
        <el-select @focus="getSshTunnelMachines" @change="change" style="width: 100%" v-model="sshTunnelMachineId"
 | 
			
		||||
            @clear="clear" placeholder="请选择SSH隧道机器" clearable>
 | 
			
		||||
            <el-option v-for="item in sshTunnelMachineList" :key="item.id" :label="`${item.ip}:${item.port} [${item.name}]`"
 | 
			
		||||
                :value="item.id">
 | 
			
		||||
            </el-option>
 | 
			
		||||
        <el-select
 | 
			
		||||
            @focus="getSshTunnelMachines"
 | 
			
		||||
            @change="change"
 | 
			
		||||
            style="width: 100%"
 | 
			
		||||
            v-model="sshTunnelMachineId"
 | 
			
		||||
            @clear="clear"
 | 
			
		||||
            placeholder="请选择SSH隧道机器"
 | 
			
		||||
            clearable
 | 
			
		||||
        >
 | 
			
		||||
            <el-option v-for="item in sshTunnelMachineList" :key="item.id" :label="`${item.ip}:${item.port} [${item.name}]`" :value="item.id"> </el-option>
 | 
			
		||||
        </el-select>
 | 
			
		||||
    </div>
 | 
			
		||||
</template>
 | 
			
		||||
@@ -17,10 +22,10 @@ const props = defineProps({
 | 
			
		||||
    modelValue: {
 | 
			
		||||
        type: Number,
 | 
			
		||||
    },
 | 
			
		||||
})
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
//定义事件
 | 
			
		||||
const emit = defineEmits(['update:modelValue'])
 | 
			
		||||
const emit = defineEmits(['update:modelValue']);
 | 
			
		||||
 | 
			
		||||
const state = reactive({
 | 
			
		||||
    // 单选则为id,多选为id数组
 | 
			
		||||
@@ -28,10 +33,7 @@ const state = reactive({
 | 
			
		||||
    sshTunnelMachineList: [] as any,
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const {
 | 
			
		||||
    sshTunnelMachineId,
 | 
			
		||||
    sshTunnelMachineList,
 | 
			
		||||
} = toRefs(state)
 | 
			
		||||
const { sshTunnelMachineId, sshTunnelMachineList } = toRefs(state);
 | 
			
		||||
 | 
			
		||||
onMounted(async () => {
 | 
			
		||||
    if (!props.modelValue || props.modelValue <= 0) {
 | 
			
		||||
@@ -52,7 +54,7 @@ const getSshTunnelMachines = async () => {
 | 
			
		||||
const clear = () => {
 | 
			
		||||
    state.sshTunnelMachineId = null;
 | 
			
		||||
    change();
 | 
			
		||||
}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const change = () => {
 | 
			
		||||
    emit('update:modelValue', state.sshTunnelMachineId);
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,5 @@
 | 
			
		||||
<template>
 | 
			
		||||
    <div
 | 
			
		||||
        style="display: inline-flex;  justify-content: center;  align-items: center; cursor: pointer;vertical-align: middle;">
 | 
			
		||||
    <div style="display: inline-flex; justify-content: center; align-items: center; cursor: pointer; vertical-align: middle">
 | 
			
		||||
        <el-popover @show="showTagInfo" placement="top-start" title="标签信息" :width="300" trigger="hover">
 | 
			
		||||
            <template #reference>
 | 
			
		||||
                <el-icon>
 | 
			
		||||
@@ -25,20 +24,18 @@ const props = defineProps({
 | 
			
		||||
        type: [String],
 | 
			
		||||
        required: true,
 | 
			
		||||
    },
 | 
			
		||||
})
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const state = reactive({
 | 
			
		||||
    tagPath: '',
 | 
			
		||||
    tags: [] as any,
 | 
			
		||||
})
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const {
 | 
			
		||||
    tags,
 | 
			
		||||
} = toRefs(state)
 | 
			
		||||
const { tags } = toRefs(state);
 | 
			
		||||
 | 
			
		||||
onMounted(async () => {
 | 
			
		||||
    state.tagPath = props.tagPath;
 | 
			
		||||
})
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const showTagInfo = async () => {
 | 
			
		||||
    if (state.tags && state.tags.length > 0) {
 | 
			
		||||
@@ -59,10 +56,7 @@ const showTagInfo = async () => {
 | 
			
		||||
        tagPaths.push(nowTag);
 | 
			
		||||
    }
 | 
			
		||||
    state.tags = await tagApi.listByQuery.request({ tagPaths: tagPaths.join(',') });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
};
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style lang="scss">
 | 
			
		||||
 | 
			
		||||
</style>
 | 
			
		||||
<style lang="scss"></style>
 | 
			
		||||
 
 | 
			
		||||
@@ -1,12 +1,22 @@
 | 
			
		||||
<template>
 | 
			
		||||
    <div>
 | 
			
		||||
        <el-tree-select @check="changeTag" style="width: 100%" v-model="selectTags" :data="tags" placeholder="请选择关联标签"
 | 
			
		||||
            :render-after-expand="true" :default-expanded-keys="[selectTags]" show-checkbox check-strictly node-key="id"
 | 
			
		||||
        <el-tree-select
 | 
			
		||||
            v-bind="$attrs"
 | 
			
		||||
            @check="changeTag"
 | 
			
		||||
            style="width: 100%"
 | 
			
		||||
            :data="tags"
 | 
			
		||||
            placeholder="请选择关联标签"
 | 
			
		||||
            :render-after-expand="true"
 | 
			
		||||
            :default-expanded-keys="[selectTags]"
 | 
			
		||||
            show-checkbox
 | 
			
		||||
            check-strictly
 | 
			
		||||
            node-key="id"
 | 
			
		||||
            :props="{
 | 
			
		||||
                value: 'id',
 | 
			
		||||
                label: 'codePath',
 | 
			
		||||
                children: 'children',
 | 
			
		||||
            }">
 | 
			
		||||
            }"
 | 
			
		||||
        >
 | 
			
		||||
            <template #default="{ data }">
 | 
			
		||||
                <span class="custom-tree-node">
 | 
			
		||||
                    <span style="font-size: 13px">
 | 
			
		||||
@@ -23,20 +33,12 @@
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script lang="ts" setup>
 | 
			
		||||
import { toRefs, reactive, onMounted } from 'vue';
 | 
			
		||||
import { useAttrs, toRefs, reactive, onMounted } from 'vue';
 | 
			
		||||
import { tagApi } from '../tag/api';
 | 
			
		||||
 | 
			
		||||
const props = defineProps({
 | 
			
		||||
    tagId: {
 | 
			
		||||
        type: Number,
 | 
			
		||||
    },
 | 
			
		||||
    tagPath: {
 | 
			
		||||
        type: String,
 | 
			
		||||
    },
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
const attrs = useAttrs();
 | 
			
		||||
//定义事件
 | 
			
		||||
const emit = defineEmits(['changeTag', 'update:tagId', 'update:tagPath'])
 | 
			
		||||
const emit = defineEmits(['changeTag', 'update:tagPath']);
 | 
			
		||||
 | 
			
		||||
const state = reactive({
 | 
			
		||||
    tags: [],
 | 
			
		||||
@@ -44,29 +46,22 @@ const state = reactive({
 | 
			
		||||
    selectTags: null as any,
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const {
 | 
			
		||||
    tags,
 | 
			
		||||
    selectTags,
 | 
			
		||||
} = toRefs(state)
 | 
			
		||||
const { tags, selectTags } = toRefs(state);
 | 
			
		||||
 | 
			
		||||
onMounted(async () => {
 | 
			
		||||
    if (props.tagId) {
 | 
			
		||||
        state.selectTags = props.tagId;
 | 
			
		||||
    if (attrs.modelValue) {
 | 
			
		||||
        state.selectTags = attrs.modelValue;
 | 
			
		||||
    }
 | 
			
		||||
    state.tags = await tagApi.getTagTrees.request(null);
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const changeTag = (tag: any, checkInfo: any) => {
 | 
			
		||||
    if (checkInfo.checkedNodes.length > 0) {
 | 
			
		||||
        emit('update:tagId', tag.id);
 | 
			
		||||
        emit('update:tagPath', tag.codePath);
 | 
			
		||||
        emit('changeTag', tag);
 | 
			
		||||
    } else {
 | 
			
		||||
        emit('update:tagId', null);
 | 
			
		||||
        emit('update:tagPath', null);
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
</script>
 | 
			
		||||
<style lang="scss">
 | 
			
		||||
 | 
			
		||||
</style>
 | 
			
		||||
<style lang="scss"></style>
 | 
			
		||||
 
 | 
			
		||||
@@ -4,10 +4,21 @@
 | 
			
		||||
            <el-col :span="24" class="el-scrollbar flex-auto" style="overflow: auto">
 | 
			
		||||
                <el-input v-model="filterText" placeholder="输入关键字->搜索已展开节点信息" clearable size="small" class="mb5" />
 | 
			
		||||
 | 
			
		||||
                <el-tree ref="treeRef" :style="{ maxHeight: state.height, height: state.height, overflow: 'auto' }"
 | 
			
		||||
                    :highlight-current="true" :indent="7" :load="loadNode" :props="treeProps" lazy node-key="key"
 | 
			
		||||
                    :expand-on-click-node="true" :filter-node-method="filterNode" @node-click="treeNodeClick"
 | 
			
		||||
                    @node-expand="treeNodeClick" @node-contextmenu="nodeContextmenu">
 | 
			
		||||
                <el-tree
 | 
			
		||||
                    ref="treeRef"
 | 
			
		||||
                    :style="{ maxHeight: state.height, height: state.height, overflow: 'auto' }"
 | 
			
		||||
                    :highlight-current="true"
 | 
			
		||||
                    :indent="7"
 | 
			
		||||
                    :load="loadNode"
 | 
			
		||||
                    :props="treeProps"
 | 
			
		||||
                    lazy
 | 
			
		||||
                    node-key="key"
 | 
			
		||||
                    :expand-on-click-node="true"
 | 
			
		||||
                    :filter-node-method="filterNode"
 | 
			
		||||
                    @node-click="treeNodeClick"
 | 
			
		||||
                    @node-expand="treeNodeClick"
 | 
			
		||||
                    @node-contextmenu="nodeContextmenu"
 | 
			
		||||
                >
 | 
			
		||||
                    <template #default="{ node, data }">
 | 
			
		||||
                        <span>
 | 
			
		||||
                            <span v-if="data.type == TagTreeNode.TagPath">
 | 
			
		||||
@@ -24,8 +35,7 @@
 | 
			
		||||
                </el-tree>
 | 
			
		||||
            </el-col>
 | 
			
		||||
        </el-row>
 | 
			
		||||
        <contextmenu :dropdown="state.dropdown" :items="state.contextmenuItems" ref="contextmenuRef"
 | 
			
		||||
            @currentContextmenuClick="onCurrentContextmenuClick" />
 | 
			
		||||
        <contextmenu :dropdown="state.dropdown" :items="state.contextmenuItems" ref="contextmenuRef" @currentContextmenuClick="onCurrentContextmenuClick" />
 | 
			
		||||
    </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
@@ -38,7 +48,7 @@ import Contextmenu from '@/components/contextmenu/index.vue';
 | 
			
		||||
const props = defineProps({
 | 
			
		||||
    height: {
 | 
			
		||||
        type: [Number, String],
 | 
			
		||||
        default: 0
 | 
			
		||||
        default: 0,
 | 
			
		||||
    },
 | 
			
		||||
    load: {
 | 
			
		||||
        type: Function,
 | 
			
		||||
@@ -47,17 +57,17 @@ const props = defineProps({
 | 
			
		||||
    loadContextmenuItems: {
 | 
			
		||||
        type: Function,
 | 
			
		||||
        required: false,
 | 
			
		||||
    }
 | 
			
		||||
})
 | 
			
		||||
    },
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const treeProps = {
 | 
			
		||||
    label: 'name',
 | 
			
		||||
    children: 'zones',
 | 
			
		||||
    isLeaf: 'isLeaf',
 | 
			
		||||
}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const emit = defineEmits(['nodeClick', 'currentContextmenuClick'])
 | 
			
		||||
const treeRef: any = ref(null)
 | 
			
		||||
const emit = defineEmits(['nodeClick', 'currentContextmenuClick']);
 | 
			
		||||
const treeRef: any = ref(null);
 | 
			
		||||
const contextmenuRef = ref();
 | 
			
		||||
 | 
			
		||||
const state = reactive({
 | 
			
		||||
@@ -69,8 +79,8 @@ const state = reactive({
 | 
			
		||||
    },
 | 
			
		||||
    contextmenuItems: [],
 | 
			
		||||
    opend: {},
 | 
			
		||||
})
 | 
			
		||||
const { filterText } = toRefs(state)
 | 
			
		||||
});
 | 
			
		||||
const { filterText } = toRefs(state);
 | 
			
		||||
 | 
			
		||||
onMounted(async () => {
 | 
			
		||||
    if (!props.height) {
 | 
			
		||||
@@ -78,40 +88,40 @@ onMounted(async () => {
 | 
			
		||||
    } else {
 | 
			
		||||
        state.height = props.height;
 | 
			
		||||
    }
 | 
			
		||||
})
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
watch(filterText, (val) => {
 | 
			
		||||
    treeRef.value?.filter(val)
 | 
			
		||||
})
 | 
			
		||||
    treeRef.value?.filter(val);
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const filterNode = (value: string, data: any) => {
 | 
			
		||||
    if (!value) return true
 | 
			
		||||
    return data.label.includes(value)
 | 
			
		||||
}
 | 
			
		||||
    if (!value) return true;
 | 
			
		||||
    return data.label.includes(value);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
* 加载树节点
 | 
			
		||||
* @param { Object } node
 | 
			
		||||
* @param { Object } resolve
 | 
			
		||||
*/
 | 
			
		||||
 * 加载树节点
 | 
			
		||||
 * @param { Object } node
 | 
			
		||||
 * @param { Object } resolve
 | 
			
		||||
 */
 | 
			
		||||
const loadNode = async (node: any, resolve: any) => {
 | 
			
		||||
    if (typeof resolve !== 'function') {
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
    let nodes = []
 | 
			
		||||
    let nodes = [];
 | 
			
		||||
    try {
 | 
			
		||||
        nodes = await props.load(node)
 | 
			
		||||
        nodes = await props.load(node);
 | 
			
		||||
    } catch (e: any) {
 | 
			
		||||
        console.error(e);
 | 
			
		||||
    }
 | 
			
		||||
    return resolve(nodes)
 | 
			
		||||
    return resolve(nodes);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const treeNodeClick = (data: any) => {
 | 
			
		||||
    emit('nodeClick', data);
 | 
			
		||||
    // 关闭可能存在的右击菜单
 | 
			
		||||
    contextmenuRef.value.closeContextmenu();
 | 
			
		||||
}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// 树节点右击事件
 | 
			
		||||
const nodeContextmenu = (event: any, data: any) => {
 | 
			
		||||
@@ -119,7 +129,7 @@ const nodeContextmenu = (event: any, data: any) => {
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
    // 加载当前节点是否需要显示右击菜单
 | 
			
		||||
    const items = props.loadContextmenuItems(data)
 | 
			
		||||
    const items = props.loadContextmenuItems(data);
 | 
			
		||||
    if (!items || items.length == 0) {
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
@@ -128,17 +138,17 @@ const nodeContextmenu = (event: any, data: any) => {
 | 
			
		||||
    state.dropdown.x = clientX;
 | 
			
		||||
    state.dropdown.y = clientY;
 | 
			
		||||
    contextmenuRef.value.openContextmenu(data);
 | 
			
		||||
}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const onCurrentContextmenuClick = (clickData: any) => {
 | 
			
		||||
    emit('currentContextmenuClick', clickData);
 | 
			
		||||
}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const reloadNode = (nodeKey: any) => {
 | 
			
		||||
    let node = getNode(nodeKey);
 | 
			
		||||
    node.loaded = false;
 | 
			
		||||
    node.expand();
 | 
			
		||||
}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const getNode = (nodeKey: any) => {
 | 
			
		||||
    let node = treeRef.value.getNode(nodeKey);
 | 
			
		||||
@@ -146,11 +156,11 @@ const getNode = (nodeKey: any) => {
 | 
			
		||||
        throw new Error('未找到节点: ' + nodeKey);
 | 
			
		||||
    }
 | 
			
		||||
    return node;
 | 
			
		||||
}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
defineExpose({
 | 
			
		||||
    reloadNode,
 | 
			
		||||
})
 | 
			
		||||
});
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style lang="scss">
 | 
			
		||||
@@ -163,4 +173,4 @@ defineExpose({
 | 
			
		||||
        min-width: 100%;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
</style>
 | 
			
		||||
 
 | 
			
		||||
@@ -2,17 +2,17 @@ export class TagTreeNode {
 | 
			
		||||
    /**
 | 
			
		||||
     * 节点id
 | 
			
		||||
     */
 | 
			
		||||
    key: any
 | 
			
		||||
    key: any;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 节点名称
 | 
			
		||||
     */
 | 
			
		||||
    label: string
 | 
			
		||||
    label: string;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 树节点类型
 | 
			
		||||
     */
 | 
			
		||||
    type: any
 | 
			
		||||
    type: any;
 | 
			
		||||
 | 
			
		||||
    isLeaf: boolean = false;
 | 
			
		||||
 | 
			
		||||
@@ -35,4 +35,4 @@ export class TagTreeNode {
 | 
			
		||||
        this.params = params;
 | 
			
		||||
        return this;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -16,17 +16,19 @@
 | 
			
		||||
                    <el-col :span="12">
 | 
			
		||||
                        <el-form-item prop="characterSet" label="charset">
 | 
			
		||||
                            <el-select filterable style="width: 80%" v-model="tableData.characterSet" size="small">
 | 
			
		||||
                                <el-option v-for="item in characterSetNameList" :key="item" :label="item" :value="item">
 | 
			
		||||
                                </el-option>
 | 
			
		||||
                                <el-option v-for="item in characterSetNameList" :key="item" :label="item" :value="item"> </el-option>
 | 
			
		||||
                            </el-select>
 | 
			
		||||
                        </el-form-item>
 | 
			
		||||
                    </el-col>
 | 
			
		||||
                    <el-col :span="12">
 | 
			
		||||
                        <el-form-item prop="characterSet" label="collation">
 | 
			
		||||
                            <el-select filterable style="width: 80%" v-model="tableData.collation" size="small">
 | 
			
		||||
                                <el-option v-for="item in collationNameList" :key="item"
 | 
			
		||||
                                <el-option
 | 
			
		||||
                                    v-for="item in collationNameList"
 | 
			
		||||
                                    :key="item"
 | 
			
		||||
                                    :label="tableData.characterSet + '_' + item"
 | 
			
		||||
                                    :value="tableData.characterSet + '_' + item">
 | 
			
		||||
                                    :value="tableData.characterSet + '_' + item"
 | 
			
		||||
                                >
 | 
			
		||||
                                </el-option>
 | 
			
		||||
                            </el-select>
 | 
			
		||||
                        </el-form-item>
 | 
			
		||||
@@ -36,38 +38,35 @@
 | 
			
		||||
                <el-tabs v-model="activeName">
 | 
			
		||||
                    <el-tab-pane label="字段" name="1">
 | 
			
		||||
                        <el-table :data="tableData.fields.res" :max-height="tableData.height">
 | 
			
		||||
                            <el-table-column :prop="item.prop" :label="item.label"
 | 
			
		||||
                                v-for="item in tableData.fields.colNames" :key="item.prop">
 | 
			
		||||
                            <el-table-column :prop="item.prop" :label="item.label" v-for="item in tableData.fields.colNames" :key="item.prop">
 | 
			
		||||
                                <template #default="scope">
 | 
			
		||||
                                    <el-input v-if="item.prop === 'name'" size="small" v-model="scope.row.name">
 | 
			
		||||
                                    </el-input>
 | 
			
		||||
                                    <el-input v-if="item.prop === 'name'" size="small" v-model="scope.row.name"> </el-input>
 | 
			
		||||
 | 
			
		||||
                                    <el-select v-if="item.prop === 'type'" filterable size="small"
 | 
			
		||||
                                        v-model="scope.row.type">
 | 
			
		||||
                                        <el-option v-for="typeValue in columnTypeList" :key="typeValue"
 | 
			
		||||
                                            :value="typeValue">{{ typeValue }}</el-option>
 | 
			
		||||
                                    <el-select v-if="item.prop === 'type'" filterable size="small" v-model="scope.row.type">
 | 
			
		||||
                                        <el-option v-for="typeValue in columnTypeList" :key="typeValue" :value="typeValue">{{ typeValue }}</el-option>
 | 
			
		||||
                                    </el-select>
 | 
			
		||||
 | 
			
		||||
                                    <el-input v-if="item.prop === 'value'" size="small" v-model="scope.row.value">
 | 
			
		||||
                                    </el-input>
 | 
			
		||||
                                    <el-input v-if="item.prop === 'value'" size="small" v-model="scope.row.value"> </el-input>
 | 
			
		||||
 | 
			
		||||
                                    <el-input v-if="item.prop === 'length'" size="small" v-model="scope.row.length">
 | 
			
		||||
                                    </el-input>
 | 
			
		||||
                                    <el-input v-if="item.prop === 'length'" size="small" v-model="scope.row.length"> </el-input>
 | 
			
		||||
 | 
			
		||||
                                    <el-checkbox v-if="item.prop === 'notNull'" size="small"
 | 
			
		||||
                                        v-model="scope.row.notNull"> </el-checkbox>
 | 
			
		||||
                                    <el-checkbox v-if="item.prop === 'notNull'" size="small" v-model="scope.row.notNull"> </el-checkbox>
 | 
			
		||||
 | 
			
		||||
                                    <el-checkbox v-if="item.prop === 'pri'" size="small" v-model="scope.row.pri">
 | 
			
		||||
                                    </el-checkbox>
 | 
			
		||||
                                    <el-checkbox v-if="item.prop === 'pri'" size="small" v-model="scope.row.pri"> </el-checkbox>
 | 
			
		||||
 | 
			
		||||
                                    <el-checkbox v-if="item.prop === 'auto_increment'" size="small"
 | 
			
		||||
                                        v-model="scope.row.auto_increment"> </el-checkbox>
 | 
			
		||||
                                    <el-checkbox v-if="item.prop === 'auto_increment'" size="small" v-model="scope.row.auto_increment"> </el-checkbox>
 | 
			
		||||
 | 
			
		||||
                                    <el-input v-if="item.prop === 'remark'" size="small" v-model="scope.row.remark">
 | 
			
		||||
                                    </el-input>
 | 
			
		||||
                                    <el-input v-if="item.prop === 'remark'" size="small" v-model="scope.row.remark"> </el-input>
 | 
			
		||||
 | 
			
		||||
                                    <el-link v-if="item.prop === 'action'" type="danger" plain size="small"
 | 
			
		||||
                                        :underline="false" @click.prevent="deleteRow(scope.$index)">删除</el-link>
 | 
			
		||||
                                    <el-link
 | 
			
		||||
                                        v-if="item.prop === 'action'"
 | 
			
		||||
                                        type="danger"
 | 
			
		||||
                                        plain
 | 
			
		||||
                                        size="small"
 | 
			
		||||
                                        :underline="false"
 | 
			
		||||
                                        @click.prevent="deleteRow(scope.$index)"
 | 
			
		||||
                                        >删除</el-link
 | 
			
		||||
                                    >
 | 
			
		||||
                                </template>
 | 
			
		||||
                            </el-table-column>
 | 
			
		||||
                        </el-table>
 | 
			
		||||
@@ -78,36 +77,44 @@
 | 
			
		||||
                    </el-tab-pane>
 | 
			
		||||
                    <el-tab-pane label="索引" name="2">
 | 
			
		||||
                        <el-table :data="tableData.indexs.res" :max-height="tableData.height">
 | 
			
		||||
                            <el-table-column :prop="item.prop" :label="item.label"
 | 
			
		||||
                                v-for="item in tableData.indexs.colNames" :key="item.prop">
 | 
			
		||||
                            <el-table-column :prop="item.prop" :label="item.label" v-for="item in tableData.indexs.colNames" :key="item.prop">
 | 
			
		||||
                                <template #default="scope">
 | 
			
		||||
                                    <el-input v-if="item.prop === 'indexName'" size="small" disabled v-model="scope.row.indexName"></el-input>
 | 
			
		||||
 | 
			
		||||
                                    <el-input v-if="item.prop === 'indexName'" size="small"
 | 
			
		||||
                                        v-model="scope.row.indexName"></el-input>
 | 
			
		||||
 | 
			
		||||
                                    <el-select v-if="item.prop === 'columnNames'" v-model="scope.row.columnNames"
 | 
			
		||||
                                        multiple collapse-tags collapse-tags-tooltip filterable placeholder="请选择字段"
 | 
			
		||||
                                        style="width: 100%">
 | 
			
		||||
                                        <el-option v-for="cl in tableData.indexs.columns" :key="cl.name"
 | 
			
		||||
                                            :label="cl.name" :value="cl.name">
 | 
			
		||||
                                    <el-select
 | 
			
		||||
                                        v-if="item.prop === 'columnNames'"
 | 
			
		||||
                                        v-model="scope.row.columnNames"
 | 
			
		||||
                                        multiple
 | 
			
		||||
                                        collapse-tags
 | 
			
		||||
                                        collapse-tags-tooltip
 | 
			
		||||
                                        filterable
 | 
			
		||||
                                        placeholder="请选择字段"
 | 
			
		||||
                                        @change="indexChanges(scope.row)"
 | 
			
		||||
                                        style="width: 100%"
 | 
			
		||||
                                    >
 | 
			
		||||
                                        <el-option v-for="cl in tableData.indexs.columns" :key="cl.name" :label="cl.name" :value="cl.name">
 | 
			
		||||
                                            {{ cl.name + ' - ' + (cl.remark || '') }}
 | 
			
		||||
                                        </el-option>
 | 
			
		||||
                                    </el-select>
 | 
			
		||||
 | 
			
		||||
                                    <el-checkbox v-if="item.prop === 'unique'" size="small" v-model="scope.row.unique">
 | 
			
		||||
                                    <el-checkbox v-if="item.prop === 'unique'" size="small" v-model="scope.row.unique" @change="indexChanges(scope.row)">
 | 
			
		||||
                                    </el-checkbox>
 | 
			
		||||
 | 
			
		||||
                                    <el-select v-if="item.prop === 'indexType'" filterable size="small"
 | 
			
		||||
                                        v-model="scope.row.indexType">
 | 
			
		||||
                                        <el-option v-for="typeValue in indexTypeList" :key="typeValue"
 | 
			
		||||
                                            :value="typeValue">{{ typeValue }}</el-option>
 | 
			
		||||
                                    <el-select v-if="item.prop === 'indexType'" filterable size="small" v-model="scope.row.indexType">
 | 
			
		||||
                                        <el-option v-for="typeValue in indexTypeList" :key="typeValue" :value="typeValue">{{ typeValue }}</el-option>
 | 
			
		||||
                                    </el-select>
 | 
			
		||||
 | 
			
		||||
                                    <el-input v-if="item.prop === 'indexComment'" size="small"
 | 
			
		||||
                                        v-model="scope.row.indexComment"> </el-input>
 | 
			
		||||
                                    <el-input v-if="item.prop === 'indexComment'" size="small" v-model="scope.row.indexComment"> </el-input>
 | 
			
		||||
 | 
			
		||||
                                    <el-link v-if="item.prop === 'action'" type="danger" plain size="small"
 | 
			
		||||
                                        :underline="false" @click.prevent="deleteIndex(scope.$index)">删除</el-link>
 | 
			
		||||
                                    <el-link
 | 
			
		||||
                                        v-if="item.prop === 'action'"
 | 
			
		||||
                                        type="danger"
 | 
			
		||||
                                        plain
 | 
			
		||||
                                        size="small"
 | 
			
		||||
                                        :underline="false"
 | 
			
		||||
                                        @click.prevent="deleteIndex(scope.$index)"
 | 
			
		||||
                                        >删除</el-link
 | 
			
		||||
                                    >
 | 
			
		||||
                                </template>
 | 
			
		||||
                            </el-table-column>
 | 
			
		||||
                        </el-table>
 | 
			
		||||
@@ -116,7 +123,6 @@
 | 
			
		||||
                            <el-button @click="addIndex()" link type="primary" icon="plus">添加索引</el-button>
 | 
			
		||||
                        </el-row>
 | 
			
		||||
                    </el-tab-pane>
 | 
			
		||||
 | 
			
		||||
                </el-tabs>
 | 
			
		||||
            </el-form>
 | 
			
		||||
            <template #footer>
 | 
			
		||||
@@ -126,12 +132,11 @@
 | 
			
		||||
    </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
<script lang="ts" setup>
 | 
			
		||||
import { watch, toRefs, reactive, ref } from 'vue';
 | 
			
		||||
import { TYPE_LIST, CHARACTER_SET_NAME_LIST, COLLATION_SUFFIX_LIST } from './service.ts';
 | 
			
		||||
import { TYPE_LIST, CHARACTER_SET_NAME_LIST, COLLATION_SUFFIX_LIST } from './service';
 | 
			
		||||
import { ElMessage } from 'element-plus';
 | 
			
		||||
import SqlExecBox from './component/SqlExecBox.ts';
 | 
			
		||||
import SqlExecBox from './component/SqlExecBox';
 | 
			
		||||
 | 
			
		||||
const props = defineProps({
 | 
			
		||||
    visible: {
 | 
			
		||||
@@ -148,11 +153,11 @@ const props = defineProps({
 | 
			
		||||
    },
 | 
			
		||||
    db: {
 | 
			
		||||
        type: String,
 | 
			
		||||
    }
 | 
			
		||||
})
 | 
			
		||||
    },
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
//定义事件
 | 
			
		||||
const emit = defineEmits(['update:visible', 'cancel', 'val-change', 'submit-sql'])
 | 
			
		||||
const emit = defineEmits(['update:visible', 'cancel', 'val-change', 'submit-sql']);
 | 
			
		||||
 | 
			
		||||
const formRef: any = ref();
 | 
			
		||||
const state = reactive({
 | 
			
		||||
@@ -259,20 +264,11 @@ const state = reactive({
 | 
			
		||||
        collation: 'utf8mb4_general_ci',
 | 
			
		||||
        tableName: '',
 | 
			
		||||
        tableComment: '',
 | 
			
		||||
        height: 550
 | 
			
		||||
        height: 550,
 | 
			
		||||
    },
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const {
 | 
			
		||||
    dialogVisible,
 | 
			
		||||
    btnloading,
 | 
			
		||||
    activeName,
 | 
			
		||||
    columnTypeList,
 | 
			
		||||
    indexTypeList,
 | 
			
		||||
    characterSetNameList,
 | 
			
		||||
    collationNameList,
 | 
			
		||||
    tableData,
 | 
			
		||||
} = toRefs(state)
 | 
			
		||||
const { dialogVisible, btnloading, activeName, columnTypeList, indexTypeList, characterSetNameList, collationNameList, tableData } = toRefs(state);
 | 
			
		||||
 | 
			
		||||
watch(props, async (newValue) => {
 | 
			
		||||
    state.dialogVisible = newValue.visible;
 | 
			
		||||
@@ -314,7 +310,7 @@ const addDefaultRows = () => {
 | 
			
		||||
        { name: 'create_time', type: 'datetime', length: '', value: 'CURRENT_TIMESTAMP', notNull: true, pri: false, auto_increment: false, remark: '创建时间' },
 | 
			
		||||
        { name: 'updator_id', type: 'bigint', length: '20', value: '', notNull: true, pri: false, auto_increment: false, remark: '修改人id' },
 | 
			
		||||
        { name: 'updator', type: 'varchar', length: '100', value: '', notNull: true, pri: false, auto_increment: false, remark: '修改人姓名' },
 | 
			
		||||
        { name: 'update_time', type: 'datetime', length: '', value: 'CURRENT_TIMESTAMP', notNull: true, pri: false, auto_increment: false, remark: '修改时间' },
 | 
			
		||||
        { name: 'update_time', type: 'datetime', length: '', value: 'CURRENT_TIMESTAMP', notNull: true, pri: false, auto_increment: false, remark: '修改时间' }
 | 
			
		||||
    );
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@@ -335,9 +331,9 @@ const submit = async () => {
 | 
			
		||||
    SqlExecBox({
 | 
			
		||||
        sql: sql,
 | 
			
		||||
        dbId: props.dbId as any,
 | 
			
		||||
        db: props.db,
 | 
			
		||||
        db: props.db as any,
 | 
			
		||||
        runSuccessCallback: () => {
 | 
			
		||||
            emit('submit-sql', {tableName: state.tableData.tableName });
 | 
			
		||||
            emit('submit-sql', { tableName: state.tableData.tableName });
 | 
			
		||||
            // cancel();
 | 
			
		||||
        },
 | 
			
		||||
    });
 | 
			
		||||
@@ -349,75 +345,79 @@ const submit = async () => {
 | 
			
		||||
 * @param nowArr 修改后的对象数组
 | 
			
		||||
 * @param key 标志对象唯一属性
 | 
			
		||||
 */
 | 
			
		||||
const filterChangedData = (oldArr: object[], nowArr: object[], key: string): { del: any[], add: any[], upd: any[] } => {
 | 
			
		||||
const filterChangedData = (oldArr: object[], nowArr: object[], key: string): { del: any[]; add: any[]; upd: any[] } => {
 | 
			
		||||
    let data = {
 | 
			
		||||
        del: [] as object[], // 删除的数据
 | 
			
		||||
        add: [] as object[], // 新增的数据
 | 
			
		||||
        upd: [] as object[] // 修改的数据
 | 
			
		||||
    }
 | 
			
		||||
        upd: [] as object[], // 修改的数据
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    // 旧数据为空
 | 
			
		||||
    if (oldArr && Array.isArray(oldArr) && oldArr.length === 0
 | 
			
		||||
        && nowArr && Array.isArray(nowArr) && nowArr.length > 0) {
 | 
			
		||||
    if (oldArr && Array.isArray(oldArr) && oldArr.length === 0 && nowArr && Array.isArray(nowArr) && nowArr.length > 0) {
 | 
			
		||||
        data.add = nowArr;
 | 
			
		||||
        return data;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // 新数据为空
 | 
			
		||||
    if (nowArr && Array.isArray(nowArr) && nowArr.length === 0
 | 
			
		||||
        && oldArr && Array.isArray(oldArr) && oldArr.length > 0) {
 | 
			
		||||
    if (nowArr && Array.isArray(nowArr) && nowArr.length === 0 && oldArr && Array.isArray(oldArr) && oldArr.length > 0) {
 | 
			
		||||
        data.del = oldArr;
 | 
			
		||||
        return data;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    let oldMap = {}, newMap = {};
 | 
			
		||||
    oldArr.forEach(a => oldMap[a[key]] = a)
 | 
			
		||||
    let oldMap = {},
 | 
			
		||||
        newMap = {};
 | 
			
		||||
    oldArr.forEach((a) => (oldMap[a[key]] = a));
 | 
			
		||||
 | 
			
		||||
    nowArr.forEach(a => {
 | 
			
		||||
        let k = a[key]
 | 
			
		||||
    nowArr.forEach((a) => {
 | 
			
		||||
        let k = a[key];
 | 
			
		||||
        newMap[k] = a;
 | 
			
		||||
        if (!oldMap.hasOwnProperty(k)) {// 新增
 | 
			
		||||
            data.add.push(a)
 | 
			
		||||
        if (!oldMap.hasOwnProperty(k)) {
 | 
			
		||||
            // 新增
 | 
			
		||||
            data.add.push(a);
 | 
			
		||||
        }
 | 
			
		||||
    })
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    oldArr.forEach(a => {
 | 
			
		||||
    oldArr.forEach((a) => {
 | 
			
		||||
        let k = a[key];
 | 
			
		||||
        let newData = newMap[k];
 | 
			
		||||
        if (!newData) { // 删除
 | 
			
		||||
            data.del.push(a)
 | 
			
		||||
        } else { // 判断每个字段是否相等,否则为修改
 | 
			
		||||
        if (!newData) {
 | 
			
		||||
            // 删除
 | 
			
		||||
            data.del.push(a);
 | 
			
		||||
        } else {
 | 
			
		||||
            // 判断每个字段是否相等,否则为修改
 | 
			
		||||
            for (let f in a) {
 | 
			
		||||
                let oldV = a[f]
 | 
			
		||||
                let newV = newData[f]
 | 
			
		||||
                let oldV = a[f];
 | 
			
		||||
                let newV = newData[f];
 | 
			
		||||
                if (oldV.toString() !== newV.toString()) {
 | 
			
		||||
                    data.upd.push(newData)
 | 
			
		||||
                    data.upd.push(newData);
 | 
			
		||||
                    break;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    })
 | 
			
		||||
    });
 | 
			
		||||
    return data;
 | 
			
		||||
}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const genSql = () => {
 | 
			
		||||
 | 
			
		||||
    const genColumnBasicSql = (cl: any) => {
 | 
			
		||||
        let val = cl.value ? (cl.value === 'CURRENT_TIMESTAMP' ? cl.value : '\'' + cl.value + '\'') : '';
 | 
			
		||||
        let defVal = `${val ? ('DEFAULT ' + val) : ''}`;
 | 
			
		||||
        let val = cl.value ? (cl.value === 'CURRENT_TIMESTAMP' ? cl.value : "'" + cl.value + "'") : '';
 | 
			
		||||
        let defVal = `${val ? 'DEFAULT ' + val : ''}`;
 | 
			
		||||
        let length = cl.length ? `(${cl.length})` : '';
 | 
			
		||||
        let onUpdate = 'update_time' === cl.name ? ' ON UPDATE CURRENT_TIMESTAMP ' : ''
 | 
			
		||||
        return ` ${cl.name} ${cl.type}${length} ${cl.notNull ? 'NOT NULL' : 'NULL'} ${cl.auto_increment ? 'AUTO_INCREMENT' : ''} ${defVal} ${onUpdate} comment '${cl.remark || ''}' `
 | 
			
		||||
    }
 | 
			
		||||
        let onUpdate = 'update_time' === cl.name ? ' ON UPDATE CURRENT_TIMESTAMP ' : '';
 | 
			
		||||
        return ` ${cl.name} ${cl.type}${length} ${cl.notNull ? 'NOT NULL' : 'NULL'} ${
 | 
			
		||||
            cl.auto_increment ? 'AUTO_INCREMENT' : ''
 | 
			
		||||
        } ${defVal} ${onUpdate} comment '${cl.remark || ''}' `;
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    let data = state.tableData;
 | 
			
		||||
    // 创建表
 | 
			
		||||
    if (!props.data?.edit) {
 | 
			
		||||
        if (state.activeName === '1') {// 创建表结构
 | 
			
		||||
        if (state.activeName === '1') {
 | 
			
		||||
            // 创建表结构
 | 
			
		||||
            let primary_key = '';
 | 
			
		||||
            let fields: string[] = [];
 | 
			
		||||
            data.fields.res.forEach((item) => {
 | 
			
		||||
              item.name && fields.push(genColumnBasicSql(item));
 | 
			
		||||
                item.name && fields.push(genColumnBasicSql(item));
 | 
			
		||||
                if (item.pri) {
 | 
			
		||||
                    primary_key += `${item.name},`;
 | 
			
		||||
                }
 | 
			
		||||
@@ -427,48 +427,52 @@ const genSql = () => {
 | 
			
		||||
                  ( ${fields.join(',')}
 | 
			
		||||
                      ${primary_key ? `, PRIMARY KEY (${primary_key.slice(0, -1)})` : ''}
 | 
			
		||||
                  ) ENGINE=InnoDB DEFAULT CHARSET=${data.characterSet} COLLATE =${data.collation} COMMENT='${data.tableComment}';`;
 | 
			
		||||
 | 
			
		||||
        } else if (state.activeName === '2' && data.indexs.res.length > 0) { // 创建索引
 | 
			
		||||
        } else if (state.activeName === '2' && data.indexs.res.length > 0) {
 | 
			
		||||
            // 创建索引
 | 
			
		||||
            let sql = `ALTER TABLE ${data.tableName}`;
 | 
			
		||||
            state.tableData.indexs.res.forEach(a => {
 | 
			
		||||
            state.tableData.indexs.res.forEach((a) => {
 | 
			
		||||
                sql += ` ADD ${a.unique ? 'UNIQUE' : ''} INDEX ${a.indexName}(${a.columnNames.join(',')}) USING ${a.indexType} COMMENT '${a.indexComment}',`;
 | 
			
		||||
            })
 | 
			
		||||
            return sql.substring(0, sql.length - 1) + ';'
 | 
			
		||||
            });
 | 
			
		||||
            return sql.substring(0, sql.length - 1) + ';';
 | 
			
		||||
        }
 | 
			
		||||
    } else { // 修改
 | 
			
		||||
        let addSql = '', updSql = '', delSql = '';
 | 
			
		||||
        if (state.activeName === '1') {// 修改列
 | 
			
		||||
            let changeData = filterChangedData(oldData.fields, state.tableData.fields.res, 'name')
 | 
			
		||||
    } else {
 | 
			
		||||
        // 修改
 | 
			
		||||
        let addSql = '',
 | 
			
		||||
            updSql = '',
 | 
			
		||||
            delSql = '';
 | 
			
		||||
        if (state.activeName === '1') {
 | 
			
		||||
            // 修改列
 | 
			
		||||
            let changeData = filterChangedData(oldData.fields, state.tableData.fields.res, 'name');
 | 
			
		||||
            if (changeData.add.length > 0) {
 | 
			
		||||
                addSql = `ALTER TABLE ${data.tableName}`
 | 
			
		||||
                changeData.add.forEach(a => {
 | 
			
		||||
                    addSql += ` ADD ${genColumnBasicSql(a)},`
 | 
			
		||||
                })
 | 
			
		||||
                addSql = addSql.substring(0, addSql.length - 1)
 | 
			
		||||
                addSql += ';'
 | 
			
		||||
                addSql = `ALTER TABLE ${data.tableName}`;
 | 
			
		||||
                changeData.add.forEach((a) => {
 | 
			
		||||
                    addSql += ` ADD ${genColumnBasicSql(a)},`;
 | 
			
		||||
                });
 | 
			
		||||
                addSql = addSql.substring(0, addSql.length - 1);
 | 
			
		||||
                addSql += ';';
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (changeData.upd.length > 0) {
 | 
			
		||||
                updSql = `ALTER TABLE ${data.tableName}`;
 | 
			
		||||
                changeData.upd.forEach(a => {
 | 
			
		||||
                    updSql += ` MODIFY ${genColumnBasicSql(a)},`
 | 
			
		||||
                })
 | 
			
		||||
                updSql = updSql.substring(0, updSql.length - 1)
 | 
			
		||||
                updSql += ';'
 | 
			
		||||
                changeData.upd.forEach((a) => {
 | 
			
		||||
                    updSql += ` MODIFY ${genColumnBasicSql(a)},`;
 | 
			
		||||
                });
 | 
			
		||||
                updSql = updSql.substring(0, updSql.length - 1);
 | 
			
		||||
                updSql += ';';
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (changeData.del.length > 0) {
 | 
			
		||||
                changeData.del.forEach(a => {
 | 
			
		||||
                    delSql += ` ALTER TABLE ${data.tableName} DROP COLUMN ${a.name}; `
 | 
			
		||||
                })
 | 
			
		||||
                changeData.del.forEach((a) => {
 | 
			
		||||
                    delSql += ` ALTER TABLE ${data.tableName} DROP COLUMN ${a.name}; `;
 | 
			
		||||
                });
 | 
			
		||||
            }
 | 
			
		||||
            return addSql + updSql + delSql;
 | 
			
		||||
 | 
			
		||||
        } else if (state.activeName === '2') { // 修改索引
 | 
			
		||||
            let changeData = filterChangedData(oldData.indexs, state.tableData.indexs.res, 'indexName')
 | 
			
		||||
        } else if (state.activeName === '2') {
 | 
			
		||||
            // 修改索引
 | 
			
		||||
            let changeData = filterChangedData(oldData.indexs, state.tableData.indexs.res, 'indexName');
 | 
			
		||||
            // 搜集修改和删除的索引,添加到drop index xx
 | 
			
		||||
            // 收集新增和修改的索引,添加到ADD xx
 | 
			
		||||
            // ALTER TABLE `test1` 
 | 
			
		||||
            // ALTER TABLE `test1`
 | 
			
		||||
            // DROP INDEX `test1_name_uindex`,
 | 
			
		||||
            // DROP INDEX `test1_column_name4_index`,
 | 
			
		||||
            // ADD UNIQUE INDEX `test1_name_uindex`(`id`) USING BTREE COMMENT 'ASDASD',
 | 
			
		||||
@@ -478,41 +482,43 @@ const genSql = () => {
 | 
			
		||||
            let addIndexs: any[] = [];
 | 
			
		||||
 | 
			
		||||
            if (changeData.upd.length > 0) {
 | 
			
		||||
                changeData.upd.forEach(a => {
 | 
			
		||||
                    dropIndexNames.push(a.indexName)
 | 
			
		||||
                    addIndexs.push(a)
 | 
			
		||||
                })
 | 
			
		||||
                changeData.upd.forEach((a) => {
 | 
			
		||||
                    dropIndexNames.push(a.indexName);
 | 
			
		||||
                    addIndexs.push(a);
 | 
			
		||||
                });
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (changeData.del.length > 0) {
 | 
			
		||||
                changeData.del.forEach(a => {
 | 
			
		||||
                    dropIndexNames.push(a.indexName)
 | 
			
		||||
                })
 | 
			
		||||
                changeData.del.forEach((a) => {
 | 
			
		||||
                    dropIndexNames.push(a.indexName);
 | 
			
		||||
                });
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (changeData.add.length > 0) {
 | 
			
		||||
                changeData.add.forEach(a => {
 | 
			
		||||
                    addIndexs.push(a)
 | 
			
		||||
                })
 | 
			
		||||
                changeData.add.forEach((a) => {
 | 
			
		||||
                    addIndexs.push(a);
 | 
			
		||||
                });
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (dropIndexNames.length > 0 || addIndexs.length > 0) {
 | 
			
		||||
                let sql = `ALTER TABLE ${data.tableName} `;
 | 
			
		||||
                if (dropIndexNames.length > 0) {
 | 
			
		||||
                    dropIndexNames.forEach(a => {
 | 
			
		||||
                        sql += `DROP INDEX ${a},`
 | 
			
		||||
                    })
 | 
			
		||||
                    sql = sql.substring(0, sql.length - 1)
 | 
			
		||||
                    dropIndexNames.forEach((a) => {
 | 
			
		||||
                        sql += `DROP INDEX ${a},`;
 | 
			
		||||
                    });
 | 
			
		||||
                    sql = sql.substring(0, sql.length - 1);
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                if (addIndexs.length > 0) {
 | 
			
		||||
                    if (dropIndexNames.length > 0){
 | 
			
		||||
                        sql += ','
 | 
			
		||||
                    if (dropIndexNames.length > 0) {
 | 
			
		||||
                        sql += ',';
 | 
			
		||||
                    }
 | 
			
		||||
                    addIndexs.forEach(a => {
 | 
			
		||||
                        sql += ` ADD ${a.unique ? 'UNIQUE' : ''} INDEX ${a.indexName}(${a.columnNames.join(',')}) USING ${a.indexType} COMMENT '${a.indexComment}',`;
 | 
			
		||||
                    })
 | 
			
		||||
                    sql = sql.substring(0, sql.length - 1)
 | 
			
		||||
                    addIndexs.forEach((a) => {
 | 
			
		||||
                        sql += ` ADD ${a.unique ? 'UNIQUE' : ''} INDEX ${a.indexName}(${a.columnNames.join(',')}) USING ${a.indexType} COMMENT '${
 | 
			
		||||
                            a.indexComment
 | 
			
		||||
                        }',`;
 | 
			
		||||
                    });
 | 
			
		||||
                    sql = sql.substring(0, sql.length - 1);
 | 
			
		||||
                }
 | 
			
		||||
                return sql;
 | 
			
		||||
            }
 | 
			
		||||
@@ -521,10 +527,10 @@ const genSql = () => {
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const reset = () => {
 | 
			
		||||
    state.activeName = '1'
 | 
			
		||||
    formRef.value.resetFields()
 | 
			
		||||
    state.tableData.tableName = ''
 | 
			
		||||
    state.tableData.tableComment = ''
 | 
			
		||||
    state.activeName = '1';
 | 
			
		||||
    formRef.value.resetFields();
 | 
			
		||||
    state.tableData.tableName = '';
 | 
			
		||||
    state.tableData.tableComment = '';
 | 
			
		||||
    state.tableData.fields.res = [
 | 
			
		||||
        {
 | 
			
		||||
            name: '',
 | 
			
		||||
@@ -537,64 +543,85 @@ const reset = () => {
 | 
			
		||||
            remark: '',
 | 
			
		||||
        },
 | 
			
		||||
    ];
 | 
			
		||||
    state.tableData.indexs.res = [{
 | 
			
		||||
        indexName: '',
 | 
			
		||||
        columnNames: [],
 | 
			
		||||
        unique: false,
 | 
			
		||||
        indexType: 'BTREE',
 | 
			
		||||
        indexComment: '',
 | 
			
		||||
    },]
 | 
			
		||||
    state.tableData.indexs.res = [
 | 
			
		||||
        {
 | 
			
		||||
            indexName: '',
 | 
			
		||||
            columnNames: [],
 | 
			
		||||
            unique: false,
 | 
			
		||||
            indexType: 'BTREE',
 | 
			
		||||
            indexComment: '',
 | 
			
		||||
        },
 | 
			
		||||
    ];
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const oldData = { indexs: [] as any[], fields: [] as any[] }
 | 
			
		||||
watch(() => props.data, (newValue: any) => {
 | 
			
		||||
    const { row, indexs, columns } = newValue;
 | 
			
		||||
    // 回显表名表注释
 | 
			
		||||
    state.tableData.tableName = row.tableName
 | 
			
		||||
    state.tableData.tableComment = row.tableComment
 | 
			
		||||
    // 回显列
 | 
			
		||||
    if (columns && Array.isArray(columns) && columns.length > 0) {
 | 
			
		||||
        oldData.fields = [];
 | 
			
		||||
        state.tableData.fields.res = [];
 | 
			
		||||
        // 索引列下拉选
 | 
			
		||||
        state.tableData.indexs.columns = [];
 | 
			
		||||
        columns.forEach(a => {
 | 
			
		||||
            let typeObj = a.columnType.replace(')', '').split('(')
 | 
			
		||||
            let type = typeObj[0];
 | 
			
		||||
            let length = typeObj.length > 1 && typeObj[1] || '';
 | 
			
		||||
            let data = {
 | 
			
		||||
                name: a.columnName,
 | 
			
		||||
                type,
 | 
			
		||||
                value: a.columnDefault || '',
 | 
			
		||||
                length,
 | 
			
		||||
                notNull: a.nullable !== 'YES',
 | 
			
		||||
                pri: a.columnKey === 'PRI',
 | 
			
		||||
                auto_increment: a.extra?.indexOf('auto_increment') > -1,
 | 
			
		||||
                remark: a.columnComment,
 | 
			
		||||
            };
 | 
			
		||||
            state.tableData.fields.res.push(data)
 | 
			
		||||
            oldData.fields.push(JSON.parse(JSON.stringify(data)))
 | 
			
		||||
            // 索引字段下拉选项
 | 
			
		||||
            state.tableData.indexs.columns.push({ name: a.columnName, remark: a.columnComment })
 | 
			
		||||
        })
 | 
			
		||||
const indexChanges = (row: any) => {
 | 
			
		||||
    let name = '';
 | 
			
		||||
    if (row.columnNames && row.columnNames.length > 0) {
 | 
			
		||||
        for (const column of row.columnNames) {
 | 
			
		||||
            name += column.replace('_', '').toLowerCase() + '_';
 | 
			
		||||
        }
 | 
			
		||||
        name = name.substring(0, name.length - 1);
 | 
			
		||||
    } else {
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
    // 回显索引
 | 
			
		||||
    if (indexs && Array.isArray(indexs) && indexs.length > 0) {
 | 
			
		||||
        oldData.indexs = [];
 | 
			
		||||
        state.tableData.indexs.res = [];
 | 
			
		||||
        // 索引过滤掉主键
 | 
			
		||||
        indexs.filter(a => a.indexName !== "PRIMARY").forEach(a => {
 | 
			
		||||
            let data = {
 | 
			
		||||
                indexName: a.indexName,
 | 
			
		||||
                columnNames: a.columnName?.split(','),
 | 
			
		||||
                unique: a.nonUnique === 0 || false,
 | 
			
		||||
                indexType: a.indexType,
 | 
			
		||||
                indexComment: a.indexComment,
 | 
			
		||||
            }
 | 
			
		||||
            state.tableData.indexs.res.push(data)
 | 
			
		||||
            oldData.indexs.push(JSON.parse(JSON.stringify(data)))
 | 
			
		||||
        })
 | 
			
		||||
    }
 | 
			
		||||
})
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
    let prefix = row.unique ? 'udx_' : 'idx_';
 | 
			
		||||
    row.indexName = prefix + name;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const oldData = { indexs: [] as any[], fields: [] as any[] };
 | 
			
		||||
watch(
 | 
			
		||||
    () => props.data,
 | 
			
		||||
    (newValue: any) => {
 | 
			
		||||
        const { row, indexs, columns } = newValue;
 | 
			
		||||
        // 回显表名表注释
 | 
			
		||||
        state.tableData.tableName = row.tableName;
 | 
			
		||||
        state.tableData.tableComment = row.tableComment;
 | 
			
		||||
        // 回显列
 | 
			
		||||
        if (columns && Array.isArray(columns) && columns.length > 0) {
 | 
			
		||||
            oldData.fields = [];
 | 
			
		||||
            state.tableData.fields.res = [];
 | 
			
		||||
            // 索引列下拉选
 | 
			
		||||
            state.tableData.indexs.columns = [];
 | 
			
		||||
            columns.forEach((a) => {
 | 
			
		||||
                let typeObj = a.columnType.replace(')', '').split('(');
 | 
			
		||||
                let type = typeObj[0];
 | 
			
		||||
                let length = (typeObj.length > 1 && typeObj[1]) || '';
 | 
			
		||||
                let data = {
 | 
			
		||||
                    name: a.columnName,
 | 
			
		||||
                    type,
 | 
			
		||||
                    value: a.columnDefault || '',
 | 
			
		||||
                    length,
 | 
			
		||||
                    notNull: a.nullable !== 'YES',
 | 
			
		||||
                    pri: a.columnKey === 'PRI',
 | 
			
		||||
                    auto_increment: a.columnKey === 'PRI' /*a.extra?.indexOf('auto_increment') > -1*/,
 | 
			
		||||
                    remark: a.columnComment,
 | 
			
		||||
                };
 | 
			
		||||
                state.tableData.fields.res.push(data);
 | 
			
		||||
                oldData.fields.push(JSON.parse(JSON.stringify(data)));
 | 
			
		||||
                // 索引字段下拉选项
 | 
			
		||||
                state.tableData.indexs.columns.push({ name: a.columnName, remark: a.columnComment });
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
        // 回显索引
 | 
			
		||||
        if (indexs && Array.isArray(indexs) && indexs.length > 0) {
 | 
			
		||||
            oldData.indexs = [];
 | 
			
		||||
            state.tableData.indexs.res = [];
 | 
			
		||||
            // 索引过滤掉主键
 | 
			
		||||
            indexs
 | 
			
		||||
                .filter((a) => a.indexName !== 'PRIMARY')
 | 
			
		||||
                .forEach((a) => {
 | 
			
		||||
                    let data = {
 | 
			
		||||
                        indexName: a.indexName,
 | 
			
		||||
                        columnNames: a.columnName?.split(','),
 | 
			
		||||
                        unique: a.nonUnique === 0 || false,
 | 
			
		||||
                        indexType: a.indexType,
 | 
			
		||||
                        indexComment: a.indexComment,
 | 
			
		||||
                    };
 | 
			
		||||
                    state.tableData.indexs.res.push(data);
 | 
			
		||||
                    oldData.indexs.push(JSON.parse(JSON.stringify(data)));
 | 
			
		||||
                });
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
);
 | 
			
		||||
</script>
 | 
			
		||||
 
 | 
			
		||||
@@ -1,12 +1,11 @@
 | 
			
		||||
<template>
 | 
			
		||||
    <div>
 | 
			
		||||
        <el-dialog :title="title" v-model="dialogVisible" :before-close="cancel" :close-on-click-modal="false"
 | 
			
		||||
            :destroy-on-close="true" width="38%">
 | 
			
		||||
            <el-form :model="form" ref="dbForm" :rules="rules" label-width="95px">
 | 
			
		||||
        <el-dialog :title="title" v-model="dialogVisible" :before-close="cancel" :close-on-click-modal="false" :destroy-on-close="true" width="38%">
 | 
			
		||||
            <el-form :model="form" ref="dbForm" :rules="rules" label-width="auto">
 | 
			
		||||
                <el-tabs v-model="tabActiveName">
 | 
			
		||||
                    <el-tab-pane label="基础信息" name="basic">
 | 
			
		||||
                        <el-form-item prop="tagId" label="标签:" required>
 | 
			
		||||
                            <tag-select v-model:tag-id="form.tagId" v-model:tag-path="form.tagPath" style="width: 100%" />
 | 
			
		||||
                            <tag-select v-model="form.tagId" v-model:tag-path="form.tagPath" style="width: 100%" />
 | 
			
		||||
                        </el-form-item>
 | 
			
		||||
 | 
			
		||||
                        <el-form-item prop="name" label="别名:" required>
 | 
			
		||||
@@ -20,8 +19,7 @@
 | 
			
		||||
                        </el-form-item>
 | 
			
		||||
                        <el-form-item prop="host" label="host:" required>
 | 
			
		||||
                            <el-col :span="18">
 | 
			
		||||
                                <el-input :disabled="form.id !== undefined" v-model.trim="form.host" placeholder="请输入主机ip"
 | 
			
		||||
                                    auto-complete="off"></el-input>
 | 
			
		||||
                                <el-input :disabled="form.id !== undefined" v-model.trim="form.host" placeholder="请输入主机ip" auto-complete="off"></el-input>
 | 
			
		||||
                            </el-col>
 | 
			
		||||
                            <el-col style="text-align: center" :span="1">:</el-col>
 | 
			
		||||
                            <el-col :span="5">
 | 
			
		||||
@@ -32,14 +30,17 @@
 | 
			
		||||
                            <el-input v-model.trim="form.username" placeholder="请输入用户名"></el-input>
 | 
			
		||||
                        </el-form-item>
 | 
			
		||||
                        <el-form-item prop="password" label="密码:">
 | 
			
		||||
                            <el-input type="password" show-password v-model.trim="form.password" placeholder="请输入密码,修改操作可不填"
 | 
			
		||||
                                autocomplete="new-password">
 | 
			
		||||
                            <el-input
 | 
			
		||||
                                type="password"
 | 
			
		||||
                                show-password
 | 
			
		||||
                                v-model.trim="form.password"
 | 
			
		||||
                                placeholder="请输入密码,修改操作可不填"
 | 
			
		||||
                                autocomplete="new-password"
 | 
			
		||||
                            >
 | 
			
		||||
                                <template v-if="form.id && form.id != 0" #suffix>
 | 
			
		||||
                                    <el-popover @hide="pwd = ''" placement="right" title="原密码" :width="200" trigger="click"
 | 
			
		||||
                                        :content="pwd">
 | 
			
		||||
                                    <el-popover @hide="pwd = ''" placement="right" title="原密码" :width="200" trigger="click" :content="pwd">
 | 
			
		||||
                                        <template #reference>
 | 
			
		||||
                                            <el-link @click="getDbPwd" :underline="false" type="primary" class="mr5">原密码
 | 
			
		||||
                                            </el-link>
 | 
			
		||||
                                            <el-link @click="getDbPwd" :underline="false" type="primary" class="mr5">原密码 </el-link>
 | 
			
		||||
                                        </template>
 | 
			
		||||
                                    </el-popover>
 | 
			
		||||
                                </template>
 | 
			
		||||
@@ -47,9 +48,18 @@
 | 
			
		||||
                        </el-form-item>
 | 
			
		||||
                        <el-form-item prop="database" label="数据库名:" required>
 | 
			
		||||
                            <el-col :span="19">
 | 
			
		||||
                                <el-select @change="changeDatabase" v-model="databaseList" multiple clearable collapse-tags
 | 
			
		||||
                                    collapse-tags-tooltip filterable allow-create placeholder="请确保数据库实例信息填写完整后获取库名"
 | 
			
		||||
                                    style="width: 100%">
 | 
			
		||||
                                <el-select
 | 
			
		||||
                                    @change="changeDatabase"
 | 
			
		||||
                                    v-model="databaseList"
 | 
			
		||||
                                    multiple
 | 
			
		||||
                                    clearable
 | 
			
		||||
                                    collapse-tags
 | 
			
		||||
                                    collapse-tags-tooltip
 | 
			
		||||
                                    filterable
 | 
			
		||||
                                    allow-create
 | 
			
		||||
                                    placeholder="请确保数据库实例信息填写完整后获取库名"
 | 
			
		||||
                                    style="width: 100%"
 | 
			
		||||
                                >
 | 
			
		||||
                                    <el-option v-for="db in allDatabases" :key="db" :label="db" :value="db" />
 | 
			
		||||
                                </el-select>
 | 
			
		||||
                            </el-col>
 | 
			
		||||
@@ -70,13 +80,18 @@
 | 
			
		||||
                        <el-form-item prop="params" label="连接参数:">
 | 
			
		||||
                            <el-input v-model.trim="form.params" placeholder="其他连接参数,形如: key1=value1&key2=value2">
 | 
			
		||||
                                <template #suffix>
 | 
			
		||||
                                    <el-link target="_blank" href="https://github.com/go-sql-driver/mysql#parameters"
 | 
			
		||||
                                        :underline="false" type="primary" class="mr5">参数参考</el-link>
 | 
			
		||||
                                    <el-link
 | 
			
		||||
                                        target="_blank"
 | 
			
		||||
                                        href="https://github.com/go-sql-driver/mysql#parameters"
 | 
			
		||||
                                        :underline="false"
 | 
			
		||||
                                        type="primary"
 | 
			
		||||
                                        class="mr5"
 | 
			
		||||
                                        >参数参考</el-link
 | 
			
		||||
                                    >
 | 
			
		||||
                                </template>
 | 
			
		||||
                            </el-input>
 | 
			
		||||
                        </el-form-item>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
                        <el-form-item prop="sshTunnelMachineId" label="SSH隧道:">
 | 
			
		||||
                            <ssh-tunnel-select v-model="form.sshTunnelMachineId" />
 | 
			
		||||
                        </el-form-item>
 | 
			
		||||
@@ -113,10 +128,10 @@ const props = defineProps({
 | 
			
		||||
    title: {
 | 
			
		||||
        type: String,
 | 
			
		||||
    },
 | 
			
		||||
})
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
//定义事件
 | 
			
		||||
const emit = defineEmits(['update:visible', 'cancel', 'val-change'])
 | 
			
		||||
const emit = defineEmits(['update:visible', 'cancel', 'val-change']);
 | 
			
		||||
 | 
			
		||||
const rules = {
 | 
			
		||||
    tagId: [
 | 
			
		||||
@@ -161,7 +176,7 @@ const rules = {
 | 
			
		||||
            trigger: ['change', 'blur'],
 | 
			
		||||
        },
 | 
			
		||||
    ],
 | 
			
		||||
}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const dbForm: any = ref(null);
 | 
			
		||||
 | 
			
		||||
@@ -190,15 +205,7 @@ const state = reactive({
 | 
			
		||||
    btnLoading: false,
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const {
 | 
			
		||||
    dialogVisible,
 | 
			
		||||
    tabActiveName,
 | 
			
		||||
    allDatabases,
 | 
			
		||||
    databaseList,
 | 
			
		||||
    form,
 | 
			
		||||
    pwd,
 | 
			
		||||
    btnLoading,
 | 
			
		||||
} = toRefs(state)
 | 
			
		||||
const { dialogVisible, tabActiveName, allDatabases, databaseList, form, pwd, btnLoading } = toRefs(state);
 | 
			
		||||
 | 
			
		||||
watch(props, (newValue: any) => {
 | 
			
		||||
    state.dialogVisible = newValue.visible;
 | 
			
		||||
 
 | 
			
		||||
@@ -1,85 +1,72 @@
 | 
			
		||||
<template>
 | 
			
		||||
    <div class="db-list">
 | 
			
		||||
        <el-card>
 | 
			
		||||
            <el-button v-auth="permissions.saveDb" type="primary" icon="plus" @click="editDb(true)">添加</el-button>
 | 
			
		||||
            <el-button v-auth="permissions.saveDb" :disabled="chooseId == null" @click="editDb(false)" type="primary"
 | 
			
		||||
                icon="edit">编辑</el-button>
 | 
			
		||||
            <el-button v-auth="permissions.delDb" :disabled="chooseId == null" @click="deleteDb(chooseId)" type="danger"
 | 
			
		||||
                icon="delete">删除</el-button>
 | 
			
		||||
            <div style="float: right">
 | 
			
		||||
                <el-select @focus="getTags" v-model="query.tagPath" placeholder="请选择标签" filterable clearable>
 | 
			
		||||
        <page-table
 | 
			
		||||
            ref="pageTableRef"
 | 
			
		||||
            :query="queryConfig"
 | 
			
		||||
            v-model:query-form="query"
 | 
			
		||||
            :show-selection="true"
 | 
			
		||||
            v-model:selection-data="state.selectionData"
 | 
			
		||||
            :data="datas"
 | 
			
		||||
            :columns="columns"
 | 
			
		||||
            :total="total"
 | 
			
		||||
            v-model:page-size="query.pageSize"
 | 
			
		||||
            v-model:page-num="query.pageNum"
 | 
			
		||||
            @pageChange="search()"
 | 
			
		||||
        >
 | 
			
		||||
            <template #tagPathSelect>
 | 
			
		||||
                <el-select @focus="getTags" v-model="query.tagPath" placeholder="请选择标签" @clear="search" filterable clearable style="width: 200px">
 | 
			
		||||
                    <el-option v-for="item in tags" :key="item" :label="item" :value="item"> </el-option>
 | 
			
		||||
                </el-select>
 | 
			
		||||
                <el-button type="success" icon="search" @click="search()" class="ml5"></el-button>
 | 
			
		||||
            </div>
 | 
			
		||||
            <el-table :data="datas" ref="table" @current-change="choose" show-overflow-tooltip stripe>
 | 
			
		||||
                <el-table-column label="选择" width="60px">
 | 
			
		||||
                    <template #default="scope">
 | 
			
		||||
                        <el-radio v-model="chooseId" :label="scope.row.id">
 | 
			
		||||
                            <i></i>
 | 
			
		||||
                        </el-radio>
 | 
			
		||||
                    </template>
 | 
			
		||||
                </el-table-column>
 | 
			
		||||
                <el-table-column prop="tagPath" label="标签路径" min-width="150" show-overflow-tooltip>
 | 
			
		||||
                    <template #default="scope">
 | 
			
		||||
                        <tag-info :tag-path="scope.row.tagPath" />
 | 
			
		||||
                        <span class="ml5">
 | 
			
		||||
                            {{ scope.row.tagPath }}
 | 
			
		||||
                        </span>
 | 
			
		||||
                    </template>
 | 
			
		||||
                </el-table-column>
 | 
			
		||||
                <el-table-column prop="name" label="名称" min-width="160" show-overflow-tooltip></el-table-column>
 | 
			
		||||
                <el-table-column min-width="170" label="host:port" show-overflow-tooltip>
 | 
			
		||||
                    <template #default="scope">
 | 
			
		||||
                        {{ `${scope.row.host}:${scope.row.port}` }}
 | 
			
		||||
                    </template>
 | 
			
		||||
                </el-table-column>
 | 
			
		||||
                <el-table-column prop="type" label="类型" min-width="90"></el-table-column>
 | 
			
		||||
                <el-table-column prop="database" label="数据库" min-width="80">
 | 
			
		||||
                    <template #default="scope">
 | 
			
		||||
                        <el-popover placement="right" trigger="click" :width="300">
 | 
			
		||||
                            <template #reference>
 | 
			
		||||
                                <el-link type="primary" :underline="false" plain @click="selectDb(scope.row.dbs)">查看
 | 
			
		||||
                                </el-link>
 | 
			
		||||
                            </template>
 | 
			
		||||
                            <el-input v-model="filterDb.param" @keyup="filterSchema" class="w-50 m-2" placeholder="搜索"
 | 
			
		||||
                                size="small">
 | 
			
		||||
                                <template #prefix>
 | 
			
		||||
                                    <el-icon class="el-input__icon">
 | 
			
		||||
                                        <search-icon />
 | 
			
		||||
                                    </el-icon>
 | 
			
		||||
                                </template>
 | 
			
		||||
                            </el-input>
 | 
			
		||||
                            <div class="el-tag--plain el-tag--success" v-for="db in filterDb.list" :key="db"
 | 
			
		||||
                                style="border:1px var(--color-success-light-3) solid; margin-top: 3px;border-radius: 5px; padding: 2px;position: relative">
 | 
			
		||||
                                <el-link type="success" plain size="small" :underline="false">{{ db }}</el-link>
 | 
			
		||||
                                <el-link type="primary" plain size="small" :underline="false"
 | 
			
		||||
                                    @click="showTableInfo(scope.row, db)" style="position: absolute; right: 4px">操作
 | 
			
		||||
                                </el-link>
 | 
			
		||||
                            </div>
 | 
			
		||||
                        </el-popover>
 | 
			
		||||
                    </template>
 | 
			
		||||
                </el-table-column>
 | 
			
		||||
                <el-table-column prop="username" label="用户名" min-width="100"></el-table-column>
 | 
			
		||||
                <el-table-column prop="remark" label="备注" min-width="150" show-overflow-tooltip></el-table-column>
 | 
			
		||||
            </template>
 | 
			
		||||
 | 
			
		||||
                <el-table-column label="操作" min-width="160" fixed="right">
 | 
			
		||||
                    <template #default="scope">
 | 
			
		||||
                        <el-link plain size="small" :underline="false" @click="showInfo(scope.row)">
 | 
			
		||||
                            详情</el-link>
 | 
			
		||||
                        <el-divider direction="vertical" border-style="dashed" />
 | 
			
		||||
                        <el-link class="ml5" type="primary" plain size="small" :underline="false"
 | 
			
		||||
                            @click="onShowSqlExec(scope.row)">
 | 
			
		||||
                            SQL执行记录</el-link>
 | 
			
		||||
            <template #queryRight>
 | 
			
		||||
                <el-button v-auth="perms.saveDb" type="primary" icon="plus" @click="editDb(false)">添加</el-button>
 | 
			
		||||
                <el-button v-auth="perms.delDb" :disabled="selectionData.length < 1" @click="deleteDb()" type="danger" icon="delete">删除</el-button>
 | 
			
		||||
            </template>
 | 
			
		||||
 | 
			
		||||
            <template #tagPath="{ data }">
 | 
			
		||||
                <tag-info :tag-path="data.tagPath" />
 | 
			
		||||
                <span class="ml5">
 | 
			
		||||
                    {{ data.tagPath }}
 | 
			
		||||
                </span>
 | 
			
		||||
            </template>
 | 
			
		||||
 | 
			
		||||
            <template #database="{ data }">
 | 
			
		||||
                <el-popover placement="right" trigger="click" :width="300">
 | 
			
		||||
                    <template #reference>
 | 
			
		||||
                        <el-link type="primary" :underline="false" plain @click="selectDb(data.dbs)">查看 </el-link>
 | 
			
		||||
                    </template>
 | 
			
		||||
                </el-table-column>
 | 
			
		||||
            </el-table>
 | 
			
		||||
            <el-row style="margin-top: 20px" type="flex" justify="end">
 | 
			
		||||
                <el-pagination style="text-align: right" @current-change="handlePageChange" :total="total"
 | 
			
		||||
                    layout="prev, pager, next, total, jumper" v-model:current-page="query.pageNum"
 | 
			
		||||
                    :page-size="query.pageSize"></el-pagination>
 | 
			
		||||
            </el-row>
 | 
			
		||||
        </el-card>
 | 
			
		||||
                    <el-input v-model="filterDb.param" @keyup="filterSchema" class="w-50 m-2" placeholder="搜索" size="small">
 | 
			
		||||
                        <template #prefix>
 | 
			
		||||
                            <el-icon class="el-input__icon">
 | 
			
		||||
                                <search-icon />
 | 
			
		||||
                            </el-icon>
 | 
			
		||||
                        </template>
 | 
			
		||||
                    </el-input>
 | 
			
		||||
                    <div
 | 
			
		||||
                        class="el-tag--plain el-tag--success"
 | 
			
		||||
                        v-for="db in filterDb.list"
 | 
			
		||||
                        :key="db"
 | 
			
		||||
                        style="border: 1px var(--color-success-light-3) solid; margin-top: 3px; border-radius: 5px; padding: 2px; position: relative"
 | 
			
		||||
                    >
 | 
			
		||||
                        <el-link type="success" plain size="small" :underline="false">{{ db }}</el-link>
 | 
			
		||||
                        <el-link type="primary" plain size="small" :underline="false" @click="showTableInfo(data, db)" style="position: absolute; right: 4px"
 | 
			
		||||
                            >操作
 | 
			
		||||
                        </el-link>
 | 
			
		||||
                    </div>
 | 
			
		||||
                </el-popover>
 | 
			
		||||
            </template>
 | 
			
		||||
 | 
			
		||||
            <template #more="{ data }">
 | 
			
		||||
                <el-button @click="showInfo(data)" link>详情</el-button>
 | 
			
		||||
 | 
			
		||||
                <el-button class="ml5" type="primary" @click="onShowSqlExec(data)" link>SQL执行记录</el-button>
 | 
			
		||||
            </template>
 | 
			
		||||
 | 
			
		||||
            <template #action="{ data }">
 | 
			
		||||
                <el-button v-if="actionBtns[perms.saveDb]" @click="editDb(data)" type="primary" link>编辑</el-button>
 | 
			
		||||
            </template>
 | 
			
		||||
        </page-table>
 | 
			
		||||
 | 
			
		||||
        <el-dialog width="80%" :title="`${db} 表信息`" :before-close="closeTableInfo" v-model="tableInfoDialog.visible">
 | 
			
		||||
            <el-row class="mb10">
 | 
			
		||||
@@ -96,13 +83,10 @@
 | 
			
		||||
                    </el-form-item>
 | 
			
		||||
 | 
			
		||||
                    <el-form-item label="导出表: ">
 | 
			
		||||
                        <el-table @selection-change="handleDumpTableSelectionChange" max-height="300" size="small"
 | 
			
		||||
                            :data="tableInfoDialog.infos">
 | 
			
		||||
                        <el-table @selection-change="handleDumpTableSelectionChange" max-height="300" size="small" :data="tableInfoDialog.infos">
 | 
			
		||||
                            <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="tableComment" 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>
 | 
			
		||||
                    </el-form-item>
 | 
			
		||||
 | 
			
		||||
@@ -114,30 +98,40 @@
 | 
			
		||||
 | 
			
		||||
                <el-button type="primary" size="small" @click="openEditTable(false)">创建表</el-button>
 | 
			
		||||
            </el-row>
 | 
			
		||||
            <el-table v-loading="tableInfoDialog.loading" border stripe :data="filterTableInfos" size="small"
 | 
			
		||||
                max-height="680">
 | 
			
		||||
            <el-table v-loading="tableInfoDialog.loading" border stripe :data="filterTableInfos" size="small" max-height="680">
 | 
			
		||||
                <el-table-column property="tableName" label="表名" min-width="150" show-overflow-tooltip>
 | 
			
		||||
                    <template #header>
 | 
			
		||||
                        <el-input v-model="tableInfoDialog.tableNameSearch" size="small" placeholder="表名: 输入可过滤"
 | 
			
		||||
                            clearable />
 | 
			
		||||
                        <el-input v-model="tableInfoDialog.tableNameSearch" size="small" placeholder="表名: 输入可过滤" clearable />
 | 
			
		||||
                    </template>
 | 
			
		||||
                </el-table-column>
 | 
			
		||||
                <el-table-column property="tableComment" label="备注" min-width="150" show-overflow-tooltip>
 | 
			
		||||
                    <template #header>
 | 
			
		||||
                        <el-input v-model="tableInfoDialog.tableCommentSearch" size="small" placeholder="备注: 输入可过滤"
 | 
			
		||||
                            clearable />
 | 
			
		||||
                        <el-input v-model="tableInfoDialog.tableCommentSearch" size="small" placeholder="备注: 输入可过滤" clearable />
 | 
			
		||||
                    </template>
 | 
			
		||||
                </el-table-column>
 | 
			
		||||
                <el-table-column prop="tableRows" label="Rows" min-width="70" sortable
 | 
			
		||||
                    :sort-method="(a: any, b: any) => parseInt(a.tableRows) - parseInt(b.tableRows)"></el-table-column>
 | 
			
		||||
                <el-table-column property="dataLength" label="数据大小" sortable
 | 
			
		||||
                    :sort-method="(a: any, b: any) => parseInt(a.dataLength) - parseInt(b.dataLength)">
 | 
			
		||||
                <el-table-column
 | 
			
		||||
                    prop="tableRows"
 | 
			
		||||
                    label="Rows"
 | 
			
		||||
                    min-width="70"
 | 
			
		||||
                    sortable
 | 
			
		||||
                    :sort-method="(a: any, b: any) => parseInt(a.tableRows) - parseInt(b.tableRows)"
 | 
			
		||||
                ></el-table-column>
 | 
			
		||||
                <el-table-column
 | 
			
		||||
                    property="dataLength"
 | 
			
		||||
                    label="数据大小"
 | 
			
		||||
                    sortable
 | 
			
		||||
                    :sort-method="(a: any, b: any) => parseInt(a.dataLength) - parseInt(b.dataLength)"
 | 
			
		||||
                >
 | 
			
		||||
                    <template #default="scope">
 | 
			
		||||
                        {{ formatByteSize(scope.row.dataLength) }}
 | 
			
		||||
                    </template>
 | 
			
		||||
                </el-table-column>
 | 
			
		||||
                <el-table-column property="indexLength" label="索引大小" sortable
 | 
			
		||||
                    :sort-method="(a: any, b: any) => parseInt(a.indexLength) - parseInt(b.indexLength)">
 | 
			
		||||
                <el-table-column
 | 
			
		||||
                    property="indexLength"
 | 
			
		||||
                    label="索引大小"
 | 
			
		||||
                    sortable
 | 
			
		||||
                    :sort-method="(a: any, b: any) => parseInt(a.indexLength) - parseInt(b.indexLength)"
 | 
			
		||||
                >
 | 
			
		||||
                    <template #default="scope">
 | 
			
		||||
                        {{ formatByteSize(scope.row.indexLength) }}
 | 
			
		||||
                    </template>
 | 
			
		||||
@@ -147,9 +141,13 @@
 | 
			
		||||
                    <template #default="scope">
 | 
			
		||||
                        <el-link @click.prevent="showColumns(scope.row)" type="primary">字段</el-link>
 | 
			
		||||
                        <el-link class="ml5" @click.prevent="showTableIndex(scope.row)" type="success">索引</el-link>
 | 
			
		||||
                        <el-link class="ml5"
 | 
			
		||||
                        <el-link
 | 
			
		||||
                            class="ml5"
 | 
			
		||||
                            v-if="tableCreateDialog.enableEditTypes.indexOf(tableCreateDialog.type) > -1"
 | 
			
		||||
                            @click.prevent="openEditTable(scope.row)" type="warning">编辑表</el-link>
 | 
			
		||||
                            @click.prevent="openEditTable(scope.row)"
 | 
			
		||||
                            type="warning"
 | 
			
		||||
                            >编辑表</el-link
 | 
			
		||||
                        >
 | 
			
		||||
                        <el-link class="ml5" @click.prevent="showCreateDdl(scope.row)" type="info">DDL</el-link>
 | 
			
		||||
                    </template>
 | 
			
		||||
                </el-table-column>
 | 
			
		||||
@@ -161,65 +159,48 @@
 | 
			
		||||
            </el-table>
 | 
			
		||||
        </el-dialog>
 | 
			
		||||
 | 
			
		||||
        <el-dialog width="90%" :title="`${sqlExecLogDialog.title} - SQL执行记录`" :before-close="onBeforeCloseSqlExecDialog"
 | 
			
		||||
            v-model="sqlExecLogDialog.visible">
 | 
			
		||||
            <div class="toolbar">
 | 
			
		||||
                <el-select v-model="sqlExecLogDialog.query.db" placeholder="请选择数据库" filterable clearable>
 | 
			
		||||
                    <el-option v-for="item in sqlExecLogDialog.dbs" :key="item" :label="`${item}`" :value="item">
 | 
			
		||||
                    </el-option>
 | 
			
		||||
                </el-select>
 | 
			
		||||
                <el-input v-model="sqlExecLogDialog.query.table" placeholder="请输入表名" clearable class="ml5"
 | 
			
		||||
                    style="width: 180px" />
 | 
			
		||||
                <el-select v-model="sqlExecLogDialog.query.type" placeholder="请选择操作类型" clearable class="ml5">
 | 
			
		||||
                    <el-option v-for="item in enums.DbSqlExecTypeEnum as any" :key="item.value" :label="item.label"
 | 
			
		||||
                        :value="item.value"> </el-option>
 | 
			
		||||
                </el-select>
 | 
			
		||||
                <el-button class="ml5" @click="searchSqlExecLog" type="success" icon="search"></el-button>
 | 
			
		||||
            </div>
 | 
			
		||||
            <el-table border stripe :data="sqlExecLogDialog.data" size="small">
 | 
			
		||||
                <el-table-column prop="db" label="数据库" min-width="60" show-overflow-tooltip> </el-table-column>
 | 
			
		||||
                <el-table-column prop="table" label="表" min-width="60" show-overflow-tooltip> </el-table-column>
 | 
			
		||||
                <el-table-column prop="type" label="类型" width="85" show-overflow-tooltip>
 | 
			
		||||
                    <template #default="scope">
 | 
			
		||||
                        <el-tag v-if="scope.row.type == enums.DbSqlExecTypeEnum['UPDATE'].value" color="#E4F5EB"
 | 
			
		||||
                            size="small">UPDATE</el-tag>
 | 
			
		||||
                        <el-tag v-if="scope.row.type == enums.DbSqlExecTypeEnum['DELETE'].value" color="#F9E2AE"
 | 
			
		||||
                            size="small">DELETE</el-tag>
 | 
			
		||||
                        <el-tag v-if="scope.row.type == enums.DbSqlExecTypeEnum['INSERT'].value" color="#A8DEE0"
 | 
			
		||||
                            size="small">INSERT</el-tag>
 | 
			
		||||
                        <el-tag v-if="scope.row.type == enums.DbSqlExecTypeEnum['QUERY'].value" color="#A8DEE0"
 | 
			
		||||
                            size="small">QUERY</el-tag>
 | 
			
		||||
                    </template>
 | 
			
		||||
                </el-table-column>
 | 
			
		||||
                <el-table-column prop="sql" label="SQL" min-width="230" show-overflow-tooltip> </el-table-column>
 | 
			
		||||
                <el-table-column prop="oldValue" label="原值" min-width="150" show-overflow-tooltip> </el-table-column>
 | 
			
		||||
                <el-table-column prop="creator" label="执行人" min-width="60" show-overflow-tooltip> </el-table-column>
 | 
			
		||||
                <el-table-column prop="createTime" label="执行时间" show-overflow-tooltip>
 | 
			
		||||
                    <template #default="scope">
 | 
			
		||||
                        {{ dateFormat(scope.row.createTime) }}
 | 
			
		||||
                    </template>
 | 
			
		||||
                </el-table-column>
 | 
			
		||||
                <el-table-column prop="remark" label="备注" min-width="60" show-overflow-tooltip> </el-table-column>
 | 
			
		||||
                <el-table-column label="操作" min-width="50" fixed="right">
 | 
			
		||||
                    <template #default="scope">
 | 
			
		||||
                        <el-link
 | 
			
		||||
                            v-if="scope.row.type == enums.DbSqlExecTypeEnum['UPDATE'].value || scope.row.type == enums.DbSqlExecTypeEnum['DELETE'].value"
 | 
			
		||||
                            type="primary" plain size="small" :underline="false" @click="onShowRollbackSql(scope.row)">
 | 
			
		||||
                            还原SQL</el-link>
 | 
			
		||||
                    </template>
 | 
			
		||||
                </el-table-column>
 | 
			
		||||
            </el-table>
 | 
			
		||||
            <el-row style="margin-top: 20px" type="flex" justify="end">
 | 
			
		||||
                <el-pagination style="text-align: right" @current-change="handleSqlExecPageChange"
 | 
			
		||||
                    :total="sqlExecLogDialog.total" layout="prev, pager, next, total, jumper"
 | 
			
		||||
                    v-model:current-page="sqlExecLogDialog.query.pageNum" :page-size="sqlExecLogDialog.query.pageSize">
 | 
			
		||||
                </el-pagination>
 | 
			
		||||
            </el-row>
 | 
			
		||||
        <el-dialog
 | 
			
		||||
            width="90%"
 | 
			
		||||
            :title="`${sqlExecLogDialog.title} - SQL执行记录`"
 | 
			
		||||
            :before-close="onBeforeCloseSqlExecDialog"
 | 
			
		||||
            :close-on-click-modal="false"
 | 
			
		||||
            v-model="sqlExecLogDialog.visible"
 | 
			
		||||
        >
 | 
			
		||||
            <page-table
 | 
			
		||||
                height="100%"
 | 
			
		||||
                ref="sqlExecDialogPageTableRef"
 | 
			
		||||
                :query="sqlExecLogDialog.queryConfig"
 | 
			
		||||
                v-model:query-form="sqlExecLogDialog.query"
 | 
			
		||||
                :data="sqlExecLogDialog.data"
 | 
			
		||||
                :columns="sqlExecLogDialog.columns"
 | 
			
		||||
                :total="sqlExecLogDialog.total"
 | 
			
		||||
                v-model:page-size="sqlExecLogDialog.query.pageSize"
 | 
			
		||||
                v-model:page-num="sqlExecLogDialog.query.pageNum"
 | 
			
		||||
                @pageChange="searchSqlExecLog()"
 | 
			
		||||
            >
 | 
			
		||||
                <template #dbSelect>
 | 
			
		||||
                    <el-select v-model="sqlExecLogDialog.query.db" placeholder="请选择数据库" style="width: 200px" filterable clearable>
 | 
			
		||||
                        <el-option v-for="item in sqlExecLogDialog.dbs" :key="item" :label="`${item}`" :value="item"> </el-option>
 | 
			
		||||
                    </el-select>
 | 
			
		||||
                </template>
 | 
			
		||||
 | 
			
		||||
                <template #action="{ data }">
 | 
			
		||||
                    <el-link
 | 
			
		||||
                        v-if="data.type == DbSqlExecTypeEnum.Update.value || data.type == DbSqlExecTypeEnum.Delete.value"
 | 
			
		||||
                        type="primary"
 | 
			
		||||
                        plain
 | 
			
		||||
                        size="small"
 | 
			
		||||
                        :underline="false"
 | 
			
		||||
                        @click="onShowRollbackSql(data)"
 | 
			
		||||
                    >
 | 
			
		||||
                        还原SQL</el-link
 | 
			
		||||
                    >
 | 
			
		||||
                </template>
 | 
			
		||||
            </page-table>
 | 
			
		||||
        </el-dialog>
 | 
			
		||||
 | 
			
		||||
        <el-dialog width="55%" :title="`还原SQL`" v-model="rollbackSqlDialog.visible">
 | 
			
		||||
            <el-input type="textarea" :autosize="{ minRows: 15, maxRows: 30 }" v-model="rollbackSqlDialog.sql"
 | 
			
		||||
                size="small"> </el-input>
 | 
			
		||||
            <el-input type="textarea" :autosize="{ minRows: 15, maxRows: 30 }" v-model="rollbackSqlDialog.sql" size="small"> </el-input>
 | 
			
		||||
        </el-dialog>
 | 
			
		||||
 | 
			
		||||
        <el-dialog width="40%" :title="`${chooseTableName} 字段信息`" v-model="columnDialog.visible">
 | 
			
		||||
@@ -237,14 +218,12 @@
 | 
			
		||||
                <el-table-column prop="columnName" label="列名" min-width="120" show-overflow-tooltip> </el-table-column>
 | 
			
		||||
                <el-table-column prop="seqInIndex" label="列序列号" show-overflow-tooltip> </el-table-column>
 | 
			
		||||
                <el-table-column prop="indexType" label="类型"> </el-table-column>
 | 
			
		||||
                <el-table-column prop="indexComment" label="备注" min-width="130" show-overflow-tooltip>
 | 
			
		||||
                </el-table-column>
 | 
			
		||||
                <el-table-column prop="indexComment" label="备注" min-width="130" show-overflow-tooltip> </el-table-column>
 | 
			
		||||
            </el-table>
 | 
			
		||||
        </el-dialog>
 | 
			
		||||
 | 
			
		||||
        <el-dialog width="55%" :title="`${chooseTableName} Create-DDL`" v-model="ddlDialog.visible">
 | 
			
		||||
            <el-input disabled type="textarea" :autosize="{ minRows: 15, maxRows: 30 }" v-model="ddlDialog.ddl"
 | 
			
		||||
                size="small"> </el-input>
 | 
			
		||||
            <el-input disabled type="textarea" :autosize="{ minRows: 15, maxRows: 30 }" v-model="ddlDialog.ddl" size="small"> </el-input>
 | 
			
		||||
        </el-dialog>
 | 
			
		||||
 | 
			
		||||
        <el-dialog v-model="infoDialog.visible">
 | 
			
		||||
@@ -264,60 +243,83 @@
 | 
			
		||||
                <el-descriptions-item :span="3" label="备注">{{ infoDialog.data.remark }}</el-descriptions-item>
 | 
			
		||||
                <el-descriptions-item :span="3" label="数据库">{{ infoDialog.data.database }}</el-descriptions-item>
 | 
			
		||||
 | 
			
		||||
                <el-descriptions-item :span="3" label="SSH隧道">{{ infoDialog.data.sshTunnelMachineId > 0 ? '是' : '否' }}
 | 
			
		||||
                </el-descriptions-item>
 | 
			
		||||
                <el-descriptions-item :span="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="创建时间">{{ 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="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="valChange" :title="dbEditDialog.title" v-model:visible="dbEditDialog.visible"
 | 
			
		||||
            v-model:db="dbEditDialog.data"></db-edit>
 | 
			
		||||
        <create-table :title="tableCreateDialog.title" :active-name="tableCreateDialog.activeName" :dbId="dbId" :db="db"
 | 
			
		||||
            :data="tableCreateDialog.data" v-model:visible="tableCreateDialog.visible" @submit-sql="onSubmitSql">
 | 
			
		||||
        <db-edit @val-change="valChange" :title="dbEditDialog.title" v-model:visible="dbEditDialog.visible" v-model:db="dbEditDialog.data"></db-edit>
 | 
			
		||||
        <create-table
 | 
			
		||||
            :title="tableCreateDialog.title"
 | 
			
		||||
            :active-name="tableCreateDialog.activeName"
 | 
			
		||||
            :dbId="dbId"
 | 
			
		||||
            :db="db"
 | 
			
		||||
            :data="tableCreateDialog.data"
 | 
			
		||||
            v-model:visible="tableCreateDialog.visible"
 | 
			
		||||
            @submit-sql="onSubmitSql"
 | 
			
		||||
        >
 | 
			
		||||
        </create-table>
 | 
			
		||||
    </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script lang='ts' setup>
 | 
			
		||||
import { toRefs, reactive, computed, onMounted, defineAsyncComponent } from 'vue';
 | 
			
		||||
<script lang="ts" setup>
 | 
			
		||||
import { ref, toRefs, reactive, computed, onMounted, defineAsyncComponent } from 'vue';
 | 
			
		||||
import { ElMessage, ElMessageBox } from 'element-plus';
 | 
			
		||||
import { formatByteSize } from '@/common/utils/format';
 | 
			
		||||
import { dbApi } from './api';
 | 
			
		||||
import enums from './enums';
 | 
			
		||||
import { DbSqlExecTypeEnum } from './enums';
 | 
			
		||||
import SqlExecBox from './component/SqlExecBox';
 | 
			
		||||
import config from '@/common/config';
 | 
			
		||||
import { getSession } from '@/common/utils/storage';
 | 
			
		||||
import { isTrue } from '@/common/assert';
 | 
			
		||||
import { Search as SearchIcon } from '@element-plus/icons-vue'
 | 
			
		||||
import { tagApi } from '../tag/api';
 | 
			
		||||
import { Search as SearchIcon } from '@element-plus/icons-vue';
 | 
			
		||||
import { dateFormat } from '@/common/utils/date';
 | 
			
		||||
import TagInfo from '../component/TagInfo.vue';
 | 
			
		||||
import PageTable from '@/components/pagetable/PageTable.vue';
 | 
			
		||||
import { TableColumn, TableQuery } from '@/components/pagetable';
 | 
			
		||||
import { hasPerms } from '@/components/auth/auth';
 | 
			
		||||
 | 
			
		||||
const DbEdit = defineAsyncComponent(() => import('./DbEdit.vue'));
 | 
			
		||||
const CreateTable = defineAsyncComponent(() => import('./CreateTable.vue'));
 | 
			
		||||
 | 
			
		||||
const permissions = {
 | 
			
		||||
const perms = {
 | 
			
		||||
    saveDb: 'db:save',
 | 
			
		||||
    delDb: 'db:del',
 | 
			
		||||
}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const queryConfig = [TableQuery.slot('tagPath', '标签', 'tagPathSelect')];
 | 
			
		||||
 | 
			
		||||
const columns = ref([
 | 
			
		||||
    TableColumn.new('tagPath', '标签路径').isSlot().setAddWidth(20),
 | 
			
		||||
    TableColumn.new('name', '名称'),
 | 
			
		||||
    TableColumn.new('host', 'host:port').setFormatFunc((data: any, _prop: string) => `${data.host}:${data.port}`),
 | 
			
		||||
    TableColumn.new('type', '类型'),
 | 
			
		||||
    TableColumn.new('database', '数据库').isSlot().setMinWidth(70),
 | 
			
		||||
    TableColumn.new('username', '用户名'),
 | 
			
		||||
    TableColumn.new('remark', '备注'),
 | 
			
		||||
    TableColumn.new('more', '更多').isSlot().setMinWidth(165).fixedRight(),
 | 
			
		||||
]);
 | 
			
		||||
 | 
			
		||||
// 该用户拥有的的操作列按钮权限
 | 
			
		||||
const actionBtns = hasPerms([perms.saveDb]);
 | 
			
		||||
const actionColumn = TableColumn.new('action', '操作').isSlot().setMinWidth(65).fixedRight().alignCenter();
 | 
			
		||||
 | 
			
		||||
const pageTableRef: any = ref(null);
 | 
			
		||||
 | 
			
		||||
const state = reactive({
 | 
			
		||||
    row: {},
 | 
			
		||||
    dbId: 0,
 | 
			
		||||
    db: '',
 | 
			
		||||
    tags: [],
 | 
			
		||||
    chooseId: null as any,
 | 
			
		||||
    /**
 | 
			
		||||
     * 选中的数据
 | 
			
		||||
     */
 | 
			
		||||
    chooseData: null,
 | 
			
		||||
    selectionData: [],
 | 
			
		||||
    /**
 | 
			
		||||
     * 查询条件
 | 
			
		||||
     */
 | 
			
		||||
@@ -341,6 +343,22 @@ const state = reactive({
 | 
			
		||||
    },
 | 
			
		||||
    // sql执行记录弹框
 | 
			
		||||
    sqlExecLogDialog: {
 | 
			
		||||
        queryConfig: [
 | 
			
		||||
            TableQuery.slot('db', '数据库', 'dbSelect'),
 | 
			
		||||
            TableQuery.text('table', '表名'),
 | 
			
		||||
            TableQuery.select('type', '操作类型').setOptions(Object.values(DbSqlExecTypeEnum)),
 | 
			
		||||
        ],
 | 
			
		||||
        columns: [
 | 
			
		||||
            TableColumn.new('db', '数据库'),
 | 
			
		||||
            TableColumn.new('table', '表'),
 | 
			
		||||
            TableColumn.new('type', '类型').typeTag(DbSqlExecTypeEnum).setAddWidth(10),
 | 
			
		||||
            TableColumn.new('creator', '执行人'),
 | 
			
		||||
            TableColumn.new('sql', 'SQL').canBeautify(),
 | 
			
		||||
            TableColumn.new('oldValue', '原值').canBeautify(),
 | 
			
		||||
            TableColumn.new('createTime', '执行时间').isTime(),
 | 
			
		||||
            TableColumn.new('remark', '备注'),
 | 
			
		||||
            TableColumn.new('action', '操作').isSlot().setMinWidth(100).fixedRight().alignCenter(),
 | 
			
		||||
        ],
 | 
			
		||||
        title: '',
 | 
			
		||||
        visible: false,
 | 
			
		||||
        data: [],
 | 
			
		||||
@@ -352,7 +370,7 @@ const state = reactive({
 | 
			
		||||
            table: '',
 | 
			
		||||
            type: null,
 | 
			
		||||
            pageNum: 1,
 | 
			
		||||
            pageSize: 12,
 | 
			
		||||
            pageSize: 10,
 | 
			
		||||
        },
 | 
			
		||||
    },
 | 
			
		||||
    rollbackSqlDialog: {
 | 
			
		||||
@@ -390,7 +408,8 @@ const state = reactive({
 | 
			
		||||
        activeName: '1',
 | 
			
		||||
        type: '',
 | 
			
		||||
        enableEditTypes: ['mysql'], // 支持"编辑表"的数据库类型
 | 
			
		||||
        data: {  // 修改表时,传递修改数据
 | 
			
		||||
        data: {
 | 
			
		||||
            // 修改表时,传递修改数据
 | 
			
		||||
            edit: false,
 | 
			
		||||
            row: {},
 | 
			
		||||
            indexs: [],
 | 
			
		||||
@@ -401,14 +420,14 @@ const state = reactive({
 | 
			
		||||
        param: '',
 | 
			
		||||
        cache: [],
 | 
			
		||||
        list: [],
 | 
			
		||||
    }
 | 
			
		||||
    },
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const {
 | 
			
		||||
    dbId,
 | 
			
		||||
    db,
 | 
			
		||||
    tags,
 | 
			
		||||
    chooseId,
 | 
			
		||||
    selectionData,
 | 
			
		||||
    query,
 | 
			
		||||
    datas,
 | 
			
		||||
    total,
 | 
			
		||||
@@ -425,10 +444,12 @@ const {
 | 
			
		||||
    dbEditDialog,
 | 
			
		||||
    tableCreateDialog,
 | 
			
		||||
    filterDb,
 | 
			
		||||
} = toRefs(state)
 | 
			
		||||
 | 
			
		||||
} = toRefs(state);
 | 
			
		||||
 | 
			
		||||
onMounted(async () => {
 | 
			
		||||
    if (Object.keys(actionBtns).length > 0) {
 | 
			
		||||
        columns.value.push(actionColumn);
 | 
			
		||||
    }
 | 
			
		||||
    search();
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
@@ -452,69 +473,57 @@ const filterTableInfos = computed(() => {
 | 
			
		||||
    });
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const choose = (item: any) => {
 | 
			
		||||
    if (!item) {
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
    state.chooseId = item.id;
 | 
			
		||||
    state.chooseData = item;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const search = async () => {
 | 
			
		||||
    let res: any = await dbApi.dbs.request(state.query);
 | 
			
		||||
    // 切割数据库
 | 
			
		||||
    res.list.forEach((e: any) => {
 | 
			
		||||
        e.popoverSelectDbVisible = false;
 | 
			
		||||
        e.dbs = e.database.split(' ');
 | 
			
		||||
    });
 | 
			
		||||
    state.datas = res.list;
 | 
			
		||||
    state.total = res.total;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const handlePageChange = (curPage: number) => {
 | 
			
		||||
    state.query.pageNum = curPage;
 | 
			
		||||
    search();
 | 
			
		||||
    try {
 | 
			
		||||
        pageTableRef.value.loading(true);
 | 
			
		||||
        let res: any = await dbApi.dbs.request(state.query);
 | 
			
		||||
        // 切割数据库
 | 
			
		||||
        res.list?.forEach((e: any) => {
 | 
			
		||||
            e.popoverSelectDbVisible = false;
 | 
			
		||||
            e.dbs = e.database.split(' ');
 | 
			
		||||
        });
 | 
			
		||||
        state.datas = res.list;
 | 
			
		||||
        state.total = res.total;
 | 
			
		||||
    } finally {
 | 
			
		||||
        pageTableRef.value.loading(false);
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const showInfo = (info: any) => {
 | 
			
		||||
    state.infoDialog.data = info;
 | 
			
		||||
    state.infoDialog.visible = true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const getTags = async () => {
 | 
			
		||||
    state.tags = await tagApi.getAccountTags.request(null);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const editDb = async (isAdd = false) => {
 | 
			
		||||
    if (isAdd) {
 | 
			
		||||
const getTags = async () => {
 | 
			
		||||
    state.tags = await dbApi.dbTags.request(null);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const editDb = async (data: any) => {
 | 
			
		||||
    if (!data) {
 | 
			
		||||
        state.dbEditDialog.data = null;
 | 
			
		||||
        state.dbEditDialog.title = '新增数据库资源';
 | 
			
		||||
    } else {
 | 
			
		||||
        state.dbEditDialog.data = state.chooseData;
 | 
			
		||||
        state.dbEditDialog.data = data;
 | 
			
		||||
        state.dbEditDialog.title = '修改数据库资源';
 | 
			
		||||
    }
 | 
			
		||||
    state.dbEditDialog.visible = true;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const valChange = () => {
 | 
			
		||||
    state.chooseData = null;
 | 
			
		||||
    state.chooseId = null;
 | 
			
		||||
    search();
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const deleteDb = async (id: number) => {
 | 
			
		||||
const deleteDb = async () => {
 | 
			
		||||
    try {
 | 
			
		||||
        await ElMessageBox.confirm(`确定删除该库?`, '提示', {
 | 
			
		||||
        await ElMessageBox.confirm(`确定删除【${state.selectionData.map((x: any) => x.name).join(', ')}】库?`, '提示', {
 | 
			
		||||
            confirmButtonText: '确定',
 | 
			
		||||
            cancelButtonText: '取消',
 | 
			
		||||
            type: 'warning',
 | 
			
		||||
        });
 | 
			
		||||
        await dbApi.deleteDb.request({ id });
 | 
			
		||||
        await dbApi.deleteDb.request({ id: state.selectionData.map((x: any) => x.id).join(',') });
 | 
			
		||||
        ElMessage.success('删除成功');
 | 
			
		||||
        state.chooseData = null;
 | 
			
		||||
        state.chooseId = null;
 | 
			
		||||
        search();
 | 
			
		||||
    } catch (err) { }
 | 
			
		||||
    } catch (err) {}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const onShowSqlExec = async (row: any) => {
 | 
			
		||||
@@ -543,11 +552,6 @@ const searchSqlExecLog = async () => {
 | 
			
		||||
    state.sqlExecLogDialog.total = res.total;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const handleSqlExecPageChange = (curPage: number) => {
 | 
			
		||||
    state.sqlExecLogDialog.query.pageNum = curPage;
 | 
			
		||||
    searchSqlExecLog();
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 选择导出数据库表
 | 
			
		||||
 */
 | 
			
		||||
@@ -563,9 +567,9 @@ const dump = (db: string) => {
 | 
			
		||||
    const a = document.createElement('a');
 | 
			
		||||
    a.setAttribute(
 | 
			
		||||
        'href',
 | 
			
		||||
        `${config.baseApiUrl}/dbs/${state.dbId}/dump?db=${db}&type=${state.dumpInfo.type}&tables=${state.dumpInfo.tables.join(
 | 
			
		||||
            ','
 | 
			
		||||
        )}&token=${getSession('token')}`
 | 
			
		||||
        `${config.baseApiUrl}/dbs/${state.dbId}/dump?db=${db}&type=${state.dumpInfo.type}&tables=${state.dumpInfo.tables.join(',')}&token=${getSession(
 | 
			
		||||
            'token'
 | 
			
		||||
        )}`
 | 
			
		||||
    );
 | 
			
		||||
    a.click();
 | 
			
		||||
    state.showDumpInfo = false;
 | 
			
		||||
@@ -577,7 +581,7 @@ const onShowRollbackSql = async (sqlExecLog: any) => {
 | 
			
		||||
    const oldValue = JSON.parse(sqlExecLog.oldValue);
 | 
			
		||||
 | 
			
		||||
    const rollbackSqls = [];
 | 
			
		||||
    if (sqlExecLog.type == enums.DbSqlExecTypeEnum['UPDATE'].value) {
 | 
			
		||||
    if (sqlExecLog.type == DbSqlExecTypeEnum['UPDATE'].value) {
 | 
			
		||||
        for (let ov of oldValue) {
 | 
			
		||||
            const setItems = [];
 | 
			
		||||
            for (let key in ov) {
 | 
			
		||||
@@ -588,7 +592,7 @@ const onShowRollbackSql = async (sqlExecLog: any) => {
 | 
			
		||||
            }
 | 
			
		||||
            rollbackSqls.push(`UPDATE ${sqlExecLog.table} SET ${setItems.join(', ')} WHERE ${primaryKey} = ${wrapValue(ov[primaryKey])};`);
 | 
			
		||||
        }
 | 
			
		||||
    } else if (sqlExecLog.type == enums.DbSqlExecTypeEnum['DELETE'].value) {
 | 
			
		||||
    } else if (sqlExecLog.type == DbSqlExecTypeEnum['DELETE'].value) {
 | 
			
		||||
        const columnNames = columns.map((c: any) => c.columnName);
 | 
			
		||||
        for (let ov of oldValue) {
 | 
			
		||||
            const values = [];
 | 
			
		||||
@@ -609,7 +613,7 @@ const getPrimaryKey = (columns: any) => {
 | 
			
		||||
        return col.columnName;
 | 
			
		||||
    }
 | 
			
		||||
    return columns[0].columnName;
 | 
			
		||||
}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 包装值,如果值类型为number则直接返回,其他则需要使用''包装
 | 
			
		||||
@@ -626,7 +630,7 @@ const showTableInfo = async (row: any, db: string) => {
 | 
			
		||||
    state.tableInfoDialog.visible = true;
 | 
			
		||||
    try {
 | 
			
		||||
        state.tableInfoDialog.infos = await dbApi.tableInfos.request({ id: row.id, db });
 | 
			
		||||
        state.tableCreateDialog.type = row.type
 | 
			
		||||
        state.tableCreateDialog.type = row.type;
 | 
			
		||||
        state.dbId = row.id;
 | 
			
		||||
        state.row = row;
 | 
			
		||||
        state.db = db;
 | 
			
		||||
@@ -638,9 +642,9 @@ const showTableInfo = async (row: any, db: string) => {
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const onSubmitSql = async (row: { tableName: string }) => {
 | 
			
		||||
    await openEditTable(row)
 | 
			
		||||
    await openEditTable(row);
 | 
			
		||||
    state.tableInfoDialog.infos = await dbApi.tableInfos.request({ id: state.dbId, db: state.db });
 | 
			
		||||
}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const closeTableInfo = () => {
 | 
			
		||||
    state.showDumpInfo = false;
 | 
			
		||||
@@ -651,7 +655,7 @@ const closeTableInfo = () => {
 | 
			
		||||
const showColumns = async (row: any) => {
 | 
			
		||||
    state.chooseTableName = row.tableName;
 | 
			
		||||
    state.columnDialog.columns = await dbApi.columnMetadata.request({
 | 
			
		||||
        id: state.chooseId,
 | 
			
		||||
        id: state.dbId,
 | 
			
		||||
        db: state.db,
 | 
			
		||||
        tableName: row.tableName,
 | 
			
		||||
    });
 | 
			
		||||
@@ -662,7 +666,7 @@ const showColumns = async (row: any) => {
 | 
			
		||||
const showTableIndex = async (row: any) => {
 | 
			
		||||
    state.chooseTableName = row.tableName;
 | 
			
		||||
    state.indexDialog.indexs = await dbApi.tableIndex.request({
 | 
			
		||||
        id: state.chooseId,
 | 
			
		||||
        id: state.dbId,
 | 
			
		||||
        db: state.db,
 | 
			
		||||
        tableName: row.tableName,
 | 
			
		||||
    });
 | 
			
		||||
@@ -673,7 +677,7 @@ const showTableIndex = async (row: any) => {
 | 
			
		||||
const showCreateDdl = async (row: any) => {
 | 
			
		||||
    state.chooseTableName = row.tableName;
 | 
			
		||||
    const res = await dbApi.tableDdl.request({
 | 
			
		||||
        id: state.chooseId,
 | 
			
		||||
        id: state.dbId,
 | 
			
		||||
        db: state.db,
 | 
			
		||||
        tableName: row.tableName,
 | 
			
		||||
    });
 | 
			
		||||
@@ -694,58 +698,57 @@ const dropTable = async (row: any) => {
 | 
			
		||||
        });
 | 
			
		||||
        SqlExecBox({
 | 
			
		||||
            sql: `DROP TABLE ${tableName}`,
 | 
			
		||||
            dbId: state.chooseId,
 | 
			
		||||
            dbId: state.dbId,
 | 
			
		||||
            db: state.db,
 | 
			
		||||
            runSuccessCallback: async () => {
 | 
			
		||||
                state.tableInfoDialog.infos = await dbApi.tableInfos.request({ id: state.chooseId, db: state.db });
 | 
			
		||||
                state.tableInfoDialog.infos = await dbApi.tableInfos.request({ id: state.dbId, db: state.db });
 | 
			
		||||
            },
 | 
			
		||||
        });
 | 
			
		||||
    } catch (err) { }
 | 
			
		||||
    } catch (err) {}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// 点击查看时初始化数据
 | 
			
		||||
const selectDb = (row: any) => {
 | 
			
		||||
    state.filterDb.param = ''
 | 
			
		||||
    state.filterDb.param = '';
 | 
			
		||||
    state.filterDb.cache = row;
 | 
			
		||||
    state.filterDb.list = row;
 | 
			
		||||
}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// 输入字符过滤schema
 | 
			
		||||
const filterSchema = () => {
 | 
			
		||||
    if (state.filterDb.param) {
 | 
			
		||||
        state.filterDb.list = state.filterDb.cache.filter((a) => { return String(a).toLowerCase().indexOf(state.filterDb.param) > -1 })
 | 
			
		||||
        state.filterDb.list = state.filterDb.cache.filter((a) => {
 | 
			
		||||
            return String(a).toLowerCase().indexOf(state.filterDb.param) > -1;
 | 
			
		||||
        });
 | 
			
		||||
    } else {
 | 
			
		||||
        state.filterDb.list = state.filterDb.cache;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// 打开编辑表
 | 
			
		||||
const openEditTable = async (row: any) => {
 | 
			
		||||
 | 
			
		||||
    state.tableCreateDialog.visible = true
 | 
			
		||||
    state.tableCreateDialog.activeName = '1'
 | 
			
		||||
    state.tableCreateDialog.visible = true;
 | 
			
		||||
    state.tableCreateDialog.activeName = '1';
 | 
			
		||||
 | 
			
		||||
    if (row === false) {
 | 
			
		||||
        state.tableCreateDialog.data = { edit: false, row: {}, indexs: [], columns: [] }
 | 
			
		||||
        state.tableCreateDialog.title = '创建表'
 | 
			
		||||
        state.tableCreateDialog.data = { edit: false, row: {}, indexs: [], columns: [] };
 | 
			
		||||
        state.tableCreateDialog.title = '创建表';
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (row.tableName) {
 | 
			
		||||
        state.tableCreateDialog.title = '修改表'
 | 
			
		||||
        state.tableCreateDialog.title = '修改表';
 | 
			
		||||
        let indexs = await dbApi.tableIndex.request({
 | 
			
		||||
            id: state.chooseId,
 | 
			
		||||
            id: state.dbId,
 | 
			
		||||
            db: state.db,
 | 
			
		||||
            tableName: row.tableName,
 | 
			
		||||
        });
 | 
			
		||||
        let columns = await dbApi.columnMetadata.request({
 | 
			
		||||
            id: state.chooseId,
 | 
			
		||||
            id: state.dbId,
 | 
			
		||||
            db: state.db,
 | 
			
		||||
            tableName: row.tableName,
 | 
			
		||||
        });
 | 
			
		||||
        state.tableCreateDialog.data = { edit: true, row, indexs, columns }
 | 
			
		||||
        state.tableCreateDialog.data = { edit: true, row, indexs, columns };
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
};
 | 
			
		||||
</script>
 | 
			
		||||
<style lang="scss">
 | 
			
		||||
 | 
			
		||||
</style>
 | 
			
		||||
<style lang="scss"></style>
 | 
			
		||||
 
 | 
			
		||||
@@ -2,9 +2,9 @@
 | 
			
		||||
    <div>
 | 
			
		||||
        <el-row>
 | 
			
		||||
            <el-col :span="4">
 | 
			
		||||
                <el-button type="primary" icon="plus"
 | 
			
		||||
                    @click="addQueryTab({ id: nowDbInst.id, dbs: nowDbInst.databases }, state.db)"
 | 
			
		||||
                    size="small">新建查询</el-button>
 | 
			
		||||
                <el-button type="primary" icon="plus" @click="addQueryTab({ id: nowDbInst.id, dbs: nowDbInst.databases }, state.db)" size="small"
 | 
			
		||||
                    >新建查询</el-button
 | 
			
		||||
                >
 | 
			
		||||
            </el-col>
 | 
			
		||||
            <el-col :span="20" v-if="state.db">
 | 
			
		||||
                <el-descriptions :column="4" size="small" border style="height: 10px">
 | 
			
		||||
@@ -24,29 +24,29 @@
 | 
			
		||||
        </el-row>
 | 
			
		||||
        <el-row type="flex">
 | 
			
		||||
            <el-col :span="4" style="border-left: 1px solid #eee; margin-top: 10px">
 | 
			
		||||
                <tag-tree ref="tagTreeRef" @node-click="nodeClick" :load="loadNode"
 | 
			
		||||
                    :load-contextmenu-items="getContextmenuItems" @current-contextmenu-click="onCurrentContextmenuClick"
 | 
			
		||||
                    :height="state.tagTreeHeight">
 | 
			
		||||
                <tag-tree
 | 
			
		||||
                    ref="tagTreeRef"
 | 
			
		||||
                    @node-click="nodeClick"
 | 
			
		||||
                    :load="loadNode"
 | 
			
		||||
                    :load-contextmenu-items="getContextmenuItems"
 | 
			
		||||
                    @current-contextmenu-click="onCurrentContextmenuClick"
 | 
			
		||||
                    :height="state.tagTreeHeight"
 | 
			
		||||
                >
 | 
			
		||||
                    <template #prefix="{ data }">
 | 
			
		||||
                        <span v-if="data.type == NodeType.DbInst">
 | 
			
		||||
                            <el-popover placement="right-start" title="数据库实例信息" trigger="hover" :width="210">
 | 
			
		||||
                                <template #reference>
 | 
			
		||||
                                    <SvgIcon v-if="data.params.type === 'mysql'" name="iconfont icon-op-mysql" :size="18" />
 | 
			
		||||
                                    <SvgIcon v-if="data.params.type === 'postgres'" name="iconfont icon-op-postgres"
 | 
			
		||||
                                        :size="18" />
 | 
			
		||||
                                    <SvgIcon v-if="data.params.type === 'postgres'" name="iconfont icon-op-postgres" :size="18" />
 | 
			
		||||
 | 
			
		||||
                                    <SvgIcon name="InfoFilled" v-else />
 | 
			
		||||
                                </template>
 | 
			
		||||
                                <template #default>
 | 
			
		||||
                                    <el-form class="instances-pop-form" label-width="55px" :size="'small'">
 | 
			
		||||
                                        <el-form-item label="类型:">{{ data.params.type }}</el-form-item>
 | 
			
		||||
                                        <el-form-item label="链接:">{{ data.params.host }}:{{
 | 
			
		||||
                                            data.params.port
 | 
			
		||||
                                        }}</el-form-item>
 | 
			
		||||
                                        <el-form-item label="链接:">{{ data.params.host }}:{{ data.params.port }}</el-form-item>
 | 
			
		||||
                                        <el-form-item label="用户:">{{ data.params.username }}</el-form-item>
 | 
			
		||||
                                        <el-form-item v-if="data.params.remark" label="备注:">{{
 | 
			
		||||
                                            data.params.remark
 | 
			
		||||
                                        }}</el-form-item>
 | 
			
		||||
                                        <el-form-item v-if="data.params.remark" label="备注:">{{ data.params.remark }}</el-form-item>
 | 
			
		||||
                                    </el-form>
 | 
			
		||||
                                </template>
 | 
			
		||||
                            </el-popover>
 | 
			
		||||
@@ -56,28 +56,32 @@
 | 
			
		||||
 | 
			
		||||
                        <SvgIcon name="Calendar" v-if="data.type == NodeType.TableMenu" color="#409eff" />
 | 
			
		||||
 | 
			
		||||
                        <el-tooltip v-if="data.type == NodeType.Table" effect="customized"
 | 
			
		||||
                            :content="data.params.tableComment" placement="top-end">
 | 
			
		||||
                        <el-tooltip v-if="data.type == NodeType.Table" effect="customized" :content="data.params.tableComment" placement="top-end">
 | 
			
		||||
                            <SvgIcon name="Calendar" color="#409eff" />
 | 
			
		||||
                        </el-tooltip>
 | 
			
		||||
 | 
			
		||||
                        <SvgIcon name="Files" v-if="data.type == NodeType.SqlMenu || data.type == NodeType.Sql"
 | 
			
		||||
                            color="#f56c6c" />
 | 
			
		||||
                        <SvgIcon name="Files" v-if="data.type == NodeType.SqlMenu || data.type == NodeType.Sql" color="#f56c6c" />
 | 
			
		||||
                    </template>
 | 
			
		||||
                </tag-tree>
 | 
			
		||||
            </el-col>
 | 
			
		||||
            <el-col :span="20">
 | 
			
		||||
                <el-container id="data-exec" style="border-left: 1px solid #eee; margin-top: 10px">
 | 
			
		||||
                    <el-tabs @tab-remove="onRemoveTab" @tab-change="onTabChange" style="width: 100%"
 | 
			
		||||
                        v-model="state.activeName">
 | 
			
		||||
                    <el-tabs @tab-remove="onRemoveTab" @tab-change="onTabChange" style="width: 100%" v-model="state.activeName">
 | 
			
		||||
                        <el-tab-pane closable v-for="dt in state.tabs.values()" :key="dt.key" :label="dt.key" :name="dt.key">
 | 
			
		||||
                            <table-data
 | 
			
		||||
                                v-if="dt.type === TabType.TableData"
 | 
			
		||||
                                @gen-insert-sql="onGenerateInsertSql"
 | 
			
		||||
                                :data="dt"
 | 
			
		||||
                                :table-height="state.dataTabsTableHeight"
 | 
			
		||||
                            ></table-data>
 | 
			
		||||
 | 
			
		||||
                        <el-tab-pane closable v-for="dt in state.tabs.values()" :key="dt.key" :label="dt.key"
 | 
			
		||||
                            :name="dt.key">
 | 
			
		||||
                            <table-data v-if="dt.type === TabType.TableData" @gen-insert-sql="onGenerateInsertSql"
 | 
			
		||||
                                :data="dt" :table-height="state.dataTabsTableHeight"></table-data>
 | 
			
		||||
 | 
			
		||||
                            <query v-else @save-sql-success="reloadSqls" @delete-sql-success="deleteSqlScript(dt)"
 | 
			
		||||
                                :data="dt" :editor-height="state.editorHeight">
 | 
			
		||||
                            <query
 | 
			
		||||
                                v-else
 | 
			
		||||
                                @save-sql-success="reloadSqls"
 | 
			
		||||
                                @delete-sql-success="deleteSqlScript(dt)"
 | 
			
		||||
                                :data="dt"
 | 
			
		||||
                                :editor-height="state.editorHeight"
 | 
			
		||||
                            >
 | 
			
		||||
                            </query>
 | 
			
		||||
                        </el-tab-pane>
 | 
			
		||||
                    </el-tabs>
 | 
			
		||||
@@ -85,8 +89,7 @@
 | 
			
		||||
            </el-col>
 | 
			
		||||
        </el-row>
 | 
			
		||||
 | 
			
		||||
        <el-dialog @close="state.genSqlDialog.visible = false" v-model="state.genSqlDialog.visible" title="SQL"
 | 
			
		||||
            width="1000px">
 | 
			
		||||
        <el-dialog @close="state.genSqlDialog.visible = false" v-model="state.genSqlDialog.visible" title="SQL" width="1000px">
 | 
			
		||||
            <el-input v-model="state.genSqlDialog.sql" type="textarea" rows="20" />
 | 
			
		||||
        </el-dialog>
 | 
			
		||||
    </div>
 | 
			
		||||
@@ -100,7 +103,7 @@ import { language as sqlLanguage } from 'monaco-editor/esm/vs/basic-languages/my
 | 
			
		||||
import * as monaco from 'monaco-editor';
 | 
			
		||||
import { editor, languages, Position } from 'monaco-editor';
 | 
			
		||||
 | 
			
		||||
import { DbInst, TabInfo, TabType } from './db'
 | 
			
		||||
import { DbInst, TabInfo, TabType } from './db';
 | 
			
		||||
import { TagTreeNode } from '../component/tag';
 | 
			
		||||
import TagTree from '../component/TagTree.vue';
 | 
			
		||||
import { dbApi } from './api';
 | 
			
		||||
@@ -112,18 +115,18 @@ const TableData = defineAsyncComponent(() => import('./component/tab/TableData.v
 | 
			
		||||
 * 树节点类型
 | 
			
		||||
 */
 | 
			
		||||
class NodeType {
 | 
			
		||||
    static DbInst = 1
 | 
			
		||||
    static Db = 2
 | 
			
		||||
    static DbInst = 1;
 | 
			
		||||
    static Db = 2;
 | 
			
		||||
    static TableMenu = 3;
 | 
			
		||||
    static SqlMenu = 4;
 | 
			
		||||
    static Table = 5;
 | 
			
		||||
    static Sql = 6;
 | 
			
		||||
}
 | 
			
		||||
class ContextmenuClickId {
 | 
			
		||||
    static ReloadTable = 0
 | 
			
		||||
    static ReloadTable = 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const tagTreeRef: any = ref(null)
 | 
			
		||||
const tagTreeRef: any = ref(null);
 | 
			
		||||
 | 
			
		||||
const tabs: Map<string, TabInfo> = new Map();
 | 
			
		||||
const state = reactive({
 | 
			
		||||
@@ -144,12 +147,10 @@ const state = reactive({
 | 
			
		||||
    },
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const {
 | 
			
		||||
    nowDbInst,
 | 
			
		||||
} = toRefs(state);
 | 
			
		||||
const { nowDbInst } = toRefs(state);
 | 
			
		||||
 | 
			
		||||
onMounted(() => {
 | 
			
		||||
    self.completionItemProvider?.dispose()
 | 
			
		||||
    self.completionItemProvider?.dispose();
 | 
			
		||||
    setHeight();
 | 
			
		||||
    // 监听浏览器窗口大小变化,更新对应组件高度
 | 
			
		||||
    window.onresize = () => setHeight();
 | 
			
		||||
@@ -165,20 +166,20 @@ const setHeight = () => {
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
* instmap; tagPaht -> info[]
 | 
			
		||||
*/
 | 
			
		||||
 * instmap; tagPaht -> info[]
 | 
			
		||||
 */
 | 
			
		||||
const instMap: Map<string, any[]> = new Map();
 | 
			
		||||
 | 
			
		||||
const getInsts = async () => {
 | 
			
		||||
    const res = await dbApi.dbs.request({ pageNum: 1, pageSize: 1000, })
 | 
			
		||||
    if (!res.total) return
 | 
			
		||||
    const res = await dbApi.dbs.request({ pageNum: 1, pageSize: 1000 });
 | 
			
		||||
    if (!res.total) return;
 | 
			
		||||
    for (const db of res.list) {
 | 
			
		||||
        const tagPath = db.tagPath;
 | 
			
		||||
        let dbInsts = instMap.get(tagPath) || [];
 | 
			
		||||
        dbInsts.push(db);
 | 
			
		||||
        instMap.set(tagPath, dbInsts?.sort());
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 加载树节点
 | 
			
		||||
@@ -203,7 +204,7 @@ const loadNode = async (node: any) => {
 | 
			
		||||
 | 
			
		||||
    // 点击tagPath -> 加载数据库实例信息列表
 | 
			
		||||
    if (nodeType === TagTreeNode.TagPath) {
 | 
			
		||||
        const dbInfos = instMap.get(data.key)
 | 
			
		||||
        const dbInfos = instMap.get(data.key);
 | 
			
		||||
        return dbInfos?.map((x: any) => {
 | 
			
		||||
            return new TagTreeNode(`${data.key}.${x.id}`, x.name, NodeType.DbInst).withParams(x);
 | 
			
		||||
        });
 | 
			
		||||
@@ -219,15 +220,17 @@ const loadNode = async (node: any) => {
 | 
			
		||||
                name: params.name,
 | 
			
		||||
                type: params.type,
 | 
			
		||||
                dbs: dbs,
 | 
			
		||||
                db: x
 | 
			
		||||
            })
 | 
			
		||||
        })
 | 
			
		||||
                db: x,
 | 
			
		||||
            });
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // 点击数据库 -> 加载 表&Sql 菜单
 | 
			
		||||
    if (nodeType === NodeType.Db) {
 | 
			
		||||
        return [new TagTreeNode(`${params.id}.${params.db}.table-menu`, '表', NodeType.TableMenu).withParams(params),
 | 
			
		||||
        new TagTreeNode(getSqlMenuNodeKey(params.id, params.db), 'SQL', NodeType.SqlMenu).withParams(params)];
 | 
			
		||||
        return [
 | 
			
		||||
            new TagTreeNode(`${params.id}.${params.db}.table-menu`, '表', NodeType.TableMenu).withParams(params),
 | 
			
		||||
            new TagTreeNode(getSqlMenuNodeKey(params.id, params.db), 'SQL', NodeType.SqlMenu).withParams(params),
 | 
			
		||||
        ];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // 点击表菜单 -> 加载表列表
 | 
			
		||||
@@ -262,30 +265,28 @@ const nodeClick = async (data: any) => {
 | 
			
		||||
    if (dataType === NodeType.Sql) {
 | 
			
		||||
        await addQueryTab({ id: params.id, nodeKey: nodeKey, dbs: params.dbs }, params.db, params.sqlName);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const getContextmenuItems = (data: any) => {
 | 
			
		||||
    const dataType = data.type;
 | 
			
		||||
    if (dataType === NodeType.TableMenu) {
 | 
			
		||||
        return [
 | 
			
		||||
            { contextMenuClickId: ContextmenuClickId.ReloadTable, txt: '刷新', icon: 'RefreshRight' }
 | 
			
		||||
        ]
 | 
			
		||||
        return [{ contextMenuClickId: ContextmenuClickId.ReloadTable, txt: '刷新', icon: 'RefreshRight' }];
 | 
			
		||||
    }
 | 
			
		||||
    return [];
 | 
			
		||||
}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// 当前右击菜单点击事件
 | 
			
		||||
const onCurrentContextmenuClick = (clickData: any) => {
 | 
			
		||||
    const clickId = clickData.id;
 | 
			
		||||
    if (clickId == ContextmenuClickId.ReloadTable) {
 | 
			
		||||
        reloadTables(clickData.item.key)
 | 
			
		||||
        reloadTables(clickData.item.key);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const getTables = async (params: any) => {
 | 
			
		||||
    const { id, db } = params;
 | 
			
		||||
    let tables = await DbInst.getInst(id).loadTables(db, state.reloadStatus);
 | 
			
		||||
    state.reloadStatus = false
 | 
			
		||||
    state.reloadStatus = false;
 | 
			
		||||
    return tables.map((x: any) => {
 | 
			
		||||
        return new TagTreeNode(`${id}.${db}.${x.tableName}`, x.tableName, NodeType.Table).withIsLeaf(true).withParams({
 | 
			
		||||
            id,
 | 
			
		||||
@@ -293,8 +294,8 @@ const getTables = async (params: any) => {
 | 
			
		||||
            tableName: x.tableName,
 | 
			
		||||
            tableComment: x.tableComment,
 | 
			
		||||
        });
 | 
			
		||||
    })
 | 
			
		||||
}
 | 
			
		||||
    });
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 加载用户保存的sql脚本
 | 
			
		||||
@@ -303,7 +304,7 @@ const getTables = async (params: any) => {
 | 
			
		||||
 * @param schema
 | 
			
		||||
 */
 | 
			
		||||
const loadSqls = async (id: any, db: string, dbs: any) => {
 | 
			
		||||
    const sqls = await dbApi.getSqlNames.request({ id: id, db: db, })
 | 
			
		||||
    const sqls = await dbApi.getSqlNames.request({ id: id, db: db });
 | 
			
		||||
    return sqls.map((x: any) => {
 | 
			
		||||
        return new TagTreeNode(`${id}.${db}.${x.name}`, x.name, NodeType.Sql).withIsLeaf(true).withParams({
 | 
			
		||||
            id,
 | 
			
		||||
@@ -312,17 +313,17 @@ const loadSqls = async (id: any, db: string, dbs: any) => {
 | 
			
		||||
            sqlName: x.name,
 | 
			
		||||
        });
 | 
			
		||||
    });
 | 
			
		||||
}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// 选择数据库
 | 
			
		||||
const changeSchema = (inst: any, schema: string) => {
 | 
			
		||||
    state.nowDbInst = DbInst.getOrNewInst(inst);
 | 
			
		||||
    state.db = schema;
 | 
			
		||||
}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// 加载选中的表数据,即新增表数据操作tab
 | 
			
		||||
const loadTableData = async (inst: any, schema: string, tableName: string) => {
 | 
			
		||||
    changeSchema(inst, schema)
 | 
			
		||||
    changeSchema(inst, schema);
 | 
			
		||||
    if (tableName == '') {
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
@@ -341,16 +342,16 @@ const loadTableData = async (inst: any, schema: string, tableName: string) => {
 | 
			
		||||
    tab.db = schema;
 | 
			
		||||
    tab.type = TabType.TableData;
 | 
			
		||||
    tab.params = {
 | 
			
		||||
        table: tableName
 | 
			
		||||
    }
 | 
			
		||||
    state.tabs.set(label, tab)
 | 
			
		||||
}
 | 
			
		||||
        table: tableName,
 | 
			
		||||
    };
 | 
			
		||||
    state.tabs.set(label, tab);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// 新建查询panel
 | 
			
		||||
const addQueryTab = async (inst: any, db: string, sqlName: string = '') => {
 | 
			
		||||
    if (!db || !inst.id) {
 | 
			
		||||
        ElMessage.warning('请选择数据库实例及对应的schema')
 | 
			
		||||
        return
 | 
			
		||||
        ElMessage.warning('请选择数据库实例及对应的schema');
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const dbId = inst.id;
 | 
			
		||||
@@ -364,7 +365,7 @@ const addQueryTab = async (inst: any, db: string, sqlName: string = '') => {
 | 
			
		||||
            if (v.type == TabType.Query && !v.params.sqlName) {
 | 
			
		||||
                count++;
 | 
			
		||||
            }
 | 
			
		||||
        })
 | 
			
		||||
        });
 | 
			
		||||
        label = `新查询${count}:${dbId}:${db}`;
 | 
			
		||||
    }
 | 
			
		||||
    state.activeName = label;
 | 
			
		||||
@@ -381,16 +382,16 @@ const addQueryTab = async (inst: any, db: string, sqlName: string = '') => {
 | 
			
		||||
    tab.params = {
 | 
			
		||||
        sqlName: sqlName,
 | 
			
		||||
        dbs: inst.dbs,
 | 
			
		||||
    }
 | 
			
		||||
    state.tabs.set(label, tab)
 | 
			
		||||
    };
 | 
			
		||||
    state.tabs.set(label, tab);
 | 
			
		||||
    registerSqlCompletionItemProvider();
 | 
			
		||||
}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const onRemoveTab = (targetName: string) => {
 | 
			
		||||
    let activeName = state.activeName;
 | 
			
		||||
    const tabNames = [...state.tabs.keys()]
 | 
			
		||||
    const tabNames = [...state.tabs.keys()];
 | 
			
		||||
    for (let i = 0; i < tabNames.length; i++) {
 | 
			
		||||
        const tabName = tabNames[i]
 | 
			
		||||
        const tabName = tabNames[i];
 | 
			
		||||
        if (tabName !== targetName) {
 | 
			
		||||
            continue;
 | 
			
		||||
        }
 | 
			
		||||
@@ -414,7 +415,7 @@ const onTabChange = () => {
 | 
			
		||||
    const nowTab = state.tabs.get(state.activeName);
 | 
			
		||||
    state.nowDbInst = DbInst.getInst(nowTab?.dbId);
 | 
			
		||||
    state.db = nowTab?.db as string;
 | 
			
		||||
}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const onGenerateInsertSql = async (sql: string) => {
 | 
			
		||||
    state.genSqlDialog.sql = sql;
 | 
			
		||||
@@ -423,245 +424,256 @@ const onGenerateInsertSql = async (sql: string) => {
 | 
			
		||||
 | 
			
		||||
const reloadSqls = (dbId: number, db: string) => {
 | 
			
		||||
    tagTreeRef.value.reloadNode(getSqlMenuNodeKey(dbId, db));
 | 
			
		||||
}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const deleteSqlScript = (ti: TabInfo) => {
 | 
			
		||||
    reloadSqls(ti.dbId, ti.db);
 | 
			
		||||
    onRemoveTab(ti.key);
 | 
			
		||||
}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const getSqlMenuNodeKey = (dbId: number, db: string) => {
 | 
			
		||||
    return `${dbId}.${db}.sql-menu`
 | 
			
		||||
}
 | 
			
		||||
    return `${dbId}.${db}.sql-menu`;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const reloadTables = (nodeKey: string) => {
 | 
			
		||||
    state.reloadStatus = true
 | 
			
		||||
    state.reloadStatus = true;
 | 
			
		||||
    tagTreeRef.value.reloadNode(nodeKey);
 | 
			
		||||
}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const registerSqlCompletionItemProvider = () => {
 | 
			
		||||
    // 参考 https://microsoft.github.io/monaco-editor/playground.html#extending-language-services-completion-provider-example
 | 
			
		||||
    self.completionItemProvider = self.completionItemProvider || monaco.languages.registerCompletionItemProvider('sql', {
 | 
			
		||||
        triggerCharacters: ['.', ' '],
 | 
			
		||||
        provideCompletionItems: async (model: editor.ITextModel, position: Position): Promise<languages.CompletionList | null | undefined> => {
 | 
			
		||||
            let word = model.getWordUntilPosition(position);
 | 
			
		||||
            const nowTab = state.tabs.get(state.activeName);
 | 
			
		||||
            if (!nowTab) {
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
            const { db, dbId } = nowTab;
 | 
			
		||||
            const dbInst = DbInst.getInst(dbId);
 | 
			
		||||
            const { lineNumber, column } = position
 | 
			
		||||
            const { startColumn, endColumn } = word
 | 
			
		||||
    self.completionItemProvider =
 | 
			
		||||
        self.completionItemProvider ||
 | 
			
		||||
        monaco.languages.registerCompletionItemProvider('sql', {
 | 
			
		||||
            triggerCharacters: ['.', ' '],
 | 
			
		||||
            provideCompletionItems: async (model: editor.ITextModel, position: Position): Promise<languages.CompletionList | null | undefined> => {
 | 
			
		||||
                let word = model.getWordUntilPosition(position);
 | 
			
		||||
                const nowTab = state.tabs.get(state.activeName);
 | 
			
		||||
                if (!nowTab) {
 | 
			
		||||
                    return;
 | 
			
		||||
                }
 | 
			
		||||
                const { db, dbId } = nowTab;
 | 
			
		||||
                const dbInst = DbInst.getInst(dbId);
 | 
			
		||||
                const { lineNumber, column } = position;
 | 
			
		||||
                const { startColumn, endColumn } = word;
 | 
			
		||||
 | 
			
		||||
            // 当前行文本
 | 
			
		||||
            let lineContent = model.getLineContent(lineNumber);
 | 
			
		||||
            // 注释行不需要代码提示
 | 
			
		||||
            if (lineContent.startsWith('--')) {
 | 
			
		||||
                return { suggestions: [] }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            let range = {
 | 
			
		||||
                startLineNumber: lineNumber,
 | 
			
		||||
                endLineNumber: lineNumber,
 | 
			
		||||
                startColumn,
 | 
			
		||||
                endColumn,
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
            //  光标前文本
 | 
			
		||||
            const textBeforePointer = model.getValueInRange({
 | 
			
		||||
                startLineNumber: lineNumber,
 | 
			
		||||
                startColumn: 0,
 | 
			
		||||
                endLineNumber: lineNumber,
 | 
			
		||||
                endColumn: column
 | 
			
		||||
            })
 | 
			
		||||
            const textBeforePointerMulti = model.getValueInRange({
 | 
			
		||||
                startLineNumber: 1,
 | 
			
		||||
                startColumn: 0,
 | 
			
		||||
                endLineNumber: lineNumber,
 | 
			
		||||
                endColumn: column
 | 
			
		||||
            })
 | 
			
		||||
            // 光标后文本
 | 
			
		||||
            const textAfterPointerMulti = model.getValueInRange({
 | 
			
		||||
                startLineNumber: lineNumber,
 | 
			
		||||
                startColumn: column,
 | 
			
		||||
                endLineNumber: model.getLineCount(),
 | 
			
		||||
                endColumn: model.getLineMaxColumn(model.getLineCount())
 | 
			
		||||
            })
 | 
			
		||||
            // // const nextTokens = textAfterPointer.trim().split(/\s+/)
 | 
			
		||||
            // // const nextToken = nextTokens[0].toLowerCase()
 | 
			
		||||
            const tokens = textBeforePointer.trim().split(/\s+/)
 | 
			
		||||
            const lastToken = tokens[tokens.length - 1].toLowerCase()
 | 
			
		||||
            const secondToken = tokens.length > 2 && tokens[tokens.length - 2].toLowerCase() || ''
 | 
			
		||||
 | 
			
		||||
            const dbs = nowTab.params?.dbs?.split(' ') || [];
 | 
			
		||||
            // console.log("光标前文本:=>" + textBeforePointerMulti)
 | 
			
		||||
            // console.log("最后输入的:=>" + lastToken)
 | 
			
		||||
 | 
			
		||||
            let suggestions: languages.CompletionItem[] = []
 | 
			
		||||
            const tables = await dbInst.loadTables(db);
 | 
			
		||||
 | 
			
		||||
            async function hintTableColumns(tableName: any, db: any) {
 | 
			
		||||
                let dbHits = await dbInst.loadDbHints(db)
 | 
			
		||||
                let columns = dbHits[tableName]
 | 
			
		||||
                let suggestions: languages.CompletionItem[] = []
 | 
			
		||||
                columns?.forEach((a: string, index: any) => {
 | 
			
		||||
                    // 字段数据格式  字段名 字段注释,  如: create_time  [datetime][创建时间]
 | 
			
		||||
                    const nameAndComment = a.split("  ")
 | 
			
		||||
                    const fieldName = nameAndComment[0]
 | 
			
		||||
                    suggestions.push({
 | 
			
		||||
                        label: {
 | 
			
		||||
                            label: a,
 | 
			
		||||
                            description: 'column'
 | 
			
		||||
                        },
 | 
			
		||||
                        kind: monaco.languages.CompletionItemKind.Property,
 | 
			
		||||
                        detail: '', // 不显示detail, 否则选中时备注等会被遮挡
 | 
			
		||||
                        insertText: fieldName + ' ', // create_time
 | 
			
		||||
                        range,
 | 
			
		||||
                        sortText: 100 + index + '' // 使用表字段声明顺序排序,排序需为字符串类型
 | 
			
		||||
                    });
 | 
			
		||||
                })
 | 
			
		||||
                return suggestions
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (lastToken.indexOf('.') > -1 || secondToken.indexOf('.') > -1) {
 | 
			
		||||
                // 如果是.触发代码提示,则进行【 库.表名联想 】 或 【 表别名.表字段联想 】
 | 
			
		||||
                let str = lastToken.substring(0, lastToken.lastIndexOf('.'))
 | 
			
		||||
                if (lastToken.trim().startsWith('.')) {
 | 
			
		||||
                    str = secondToken
 | 
			
		||||
                // 当前行文本
 | 
			
		||||
                let lineContent = model.getLineContent(lineNumber);
 | 
			
		||||
                // 注释行不需要代码提示
 | 
			
		||||
                if (lineContent.startsWith('--')) {
 | 
			
		||||
                    return { suggestions: [] };
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                // 库.表名联想
 | 
			
		||||
                if (dbs && dbs.filter((a: any) => a === str)?.length > 0) {
 | 
			
		||||
                    let tables = await dbInst.loadTables(str)
 | 
			
		||||
                    let suggestions: languages.CompletionItem[] = []
 | 
			
		||||
                    for (let item of tables) {
 | 
			
		||||
                        const { tableName, tableComment } = item
 | 
			
		||||
                let range = {
 | 
			
		||||
                    startLineNumber: lineNumber,
 | 
			
		||||
                    endLineNumber: lineNumber,
 | 
			
		||||
                    startColumn,
 | 
			
		||||
                    endColumn,
 | 
			
		||||
                };
 | 
			
		||||
 | 
			
		||||
                //  光标前文本
 | 
			
		||||
                const textBeforePointer = model.getValueInRange({
 | 
			
		||||
                    startLineNumber: lineNumber,
 | 
			
		||||
                    startColumn: 0,
 | 
			
		||||
                    endLineNumber: lineNumber,
 | 
			
		||||
                    endColumn: column,
 | 
			
		||||
                });
 | 
			
		||||
                const textBeforePointerMulti = model.getValueInRange({
 | 
			
		||||
                    startLineNumber: 1,
 | 
			
		||||
                    startColumn: 0,
 | 
			
		||||
                    endLineNumber: lineNumber,
 | 
			
		||||
                    endColumn: column,
 | 
			
		||||
                });
 | 
			
		||||
                // 光标后文本
 | 
			
		||||
                const textAfterPointerMulti = model.getValueInRange({
 | 
			
		||||
                    startLineNumber: lineNumber,
 | 
			
		||||
                    startColumn: column,
 | 
			
		||||
                    endLineNumber: model.getLineCount(),
 | 
			
		||||
                    endColumn: model.getLineMaxColumn(model.getLineCount()),
 | 
			
		||||
                });
 | 
			
		||||
                // // const nextTokens = textAfterPointer.trim().split(/\s+/)
 | 
			
		||||
                // // const nextToken = nextTokens[0].toLowerCase()
 | 
			
		||||
                const tokens = textBeforePointer.trim().split(/\s+/);
 | 
			
		||||
                let lastToken = tokens[tokens.length - 1].toLowerCase();
 | 
			
		||||
                const secondToken = (tokens.length > 2 && tokens[tokens.length - 2].toLowerCase()) || '';
 | 
			
		||||
 | 
			
		||||
                // const dbs = nowTab.params?.dbs?.split(' ') || [];
 | 
			
		||||
                const dbs = (nowTab.params && nowTab.params.dbs && nowTab.params.dbs.split(' ')) || [];
 | 
			
		||||
                // console.log("光标前文本:=>" + textBeforePointerMulti)
 | 
			
		||||
                // console.log("最后输入的:=>" + lastToken)
 | 
			
		||||
 | 
			
		||||
                let suggestions: languages.CompletionItem[] = [];
 | 
			
		||||
                const tables = await dbInst.loadTables(db);
 | 
			
		||||
 | 
			
		||||
                async function hintTableColumns(tableName: any, db: any) {
 | 
			
		||||
                    let dbHits = await dbInst.loadDbHints(db);
 | 
			
		||||
                    let columns = dbHits[tableName];
 | 
			
		||||
                    let suggestions: languages.CompletionItem[] = [];
 | 
			
		||||
                    columns?.forEach((a: string, index: any) => {
 | 
			
		||||
                        // 字段数据格式  字段名 字段注释,  如: create_time  [datetime][创建时间]
 | 
			
		||||
                        const nameAndComment = a.split('  ');
 | 
			
		||||
                        const fieldName = nameAndComment[0];
 | 
			
		||||
                        suggestions.push({
 | 
			
		||||
                            label: {
 | 
			
		||||
                                label: tableName + (tableComment ? ' - ' + tableComment : ''),
 | 
			
		||||
                                description: 'table'
 | 
			
		||||
                                label: a,
 | 
			
		||||
                                description: 'column',
 | 
			
		||||
                            },
 | 
			
		||||
                            kind: monaco.languages.CompletionItemKind.File,
 | 
			
		||||
                            insertText: tableName,
 | 
			
		||||
                            range
 | 
			
		||||
                            kind: monaco.languages.CompletionItemKind.Property,
 | 
			
		||||
                            detail: '', // 不显示detail, 否则选中时备注等会被遮挡
 | 
			
		||||
                            insertText: fieldName, // create_time
 | 
			
		||||
                            range,
 | 
			
		||||
                            sortText: 100 + index + '', // 使用表字段声明顺序排序,排序需为字符串类型
 | 
			
		||||
                        });
 | 
			
		||||
                    }
 | 
			
		||||
                    return { suggestions }
 | 
			
		||||
                    });
 | 
			
		||||
                    return suggestions;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                let sql = textBeforePointerMulti.split(';')[textBeforePointerMulti.split(';').length - 1] + textAfterPointerMulti.split(';')[0];
 | 
			
		||||
                // 表别名.表字段联想
 | 
			
		||||
                let tableInfo = getTableByAlias(sql, db, str)
 | 
			
		||||
                if (tableInfo.tableName) {
 | 
			
		||||
                    let tableName = tableInfo.tableName
 | 
			
		||||
                    let db = tableInfo.dbName;
 | 
			
		||||
                    // 取出表名并提示
 | 
			
		||||
                    let suggestions = await hintTableColumns(tableName, db);
 | 
			
		||||
                    if (suggestions.length > 0) {
 | 
			
		||||
                if (lastToken.indexOf('.') > -1 || secondToken.indexOf('.') > -1) {
 | 
			
		||||
                    // 如果是.触发代码提示,则进行【 库.表名联想 】 或 【 表别名.表字段联想 】
 | 
			
		||||
                    let str = lastToken.substring(0, lastToken.lastIndexOf('.'));
 | 
			
		||||
                    if (lastToken.trim().startsWith('.')) {
 | 
			
		||||
                        str = secondToken;
 | 
			
		||||
                    }
 | 
			
		||||
                    // 如果字符串粘连起了如:'a.creator,a.',需要重新取出别名
 | 
			
		||||
                    let aliasArr = lastToken.split(',');
 | 
			
		||||
                    if (aliasArr.length > 1) {
 | 
			
		||||
                        lastToken = aliasArr[aliasArr.length - 1];
 | 
			
		||||
                        str = lastToken.substring(0, lastToken.lastIndexOf('.'));
 | 
			
		||||
                        if (lastToken.trim().startsWith('.')) {
 | 
			
		||||
                            str = secondToken;
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                    // 库.表名联想
 | 
			
		||||
                    if (dbs && dbs.filter((a: any) => a === str)?.length > 0) {
 | 
			
		||||
                        let tables = await dbInst.loadTables(str);
 | 
			
		||||
                        let suggestions: languages.CompletionItem[] = [];
 | 
			
		||||
                        for (let item of tables) {
 | 
			
		||||
                            const { tableName, tableComment } = item;
 | 
			
		||||
                            suggestions.push({
 | 
			
		||||
                                label: {
 | 
			
		||||
                                    label: tableName + (tableComment ? ' - ' + tableComment : ''),
 | 
			
		||||
                                    description: 'table',
 | 
			
		||||
                                },
 | 
			
		||||
                                kind: monaco.languages.CompletionItemKind.File,
 | 
			
		||||
                                insertText: tableName,
 | 
			
		||||
                                range,
 | 
			
		||||
                            });
 | 
			
		||||
                        }
 | 
			
		||||
                        return { suggestions };
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                return { suggestions: [] }
 | 
			
		||||
            } else {
 | 
			
		||||
                // 如果sql里含有表名,则提示表字段
 | 
			
		||||
                let mat = textBeforePointerMulti.match(/from\n*\s+\n*(\w+)\n*\s+\n*/i)
 | 
			
		||||
                if (mat && mat.length > 1) {
 | 
			
		||||
                    let tableName = mat[1]
 | 
			
		||||
                    // 取出表名并提示
 | 
			
		||||
                    let addSuggestions = await hintTableColumns(tableName, db);
 | 
			
		||||
                    if (addSuggestions.length > 0) {
 | 
			
		||||
                        suggestions = suggestions.concat(addSuggestions)
 | 
			
		||||
 | 
			
		||||
                    let sql = textBeforePointerMulti.split(';')[textBeforePointerMulti.split(';').length - 1] + textAfterPointerMulti.split(';')[0];
 | 
			
		||||
                    // 表别名.表字段联想
 | 
			
		||||
                    let tableInfo = getTableByAlias(sql, db, str);
 | 
			
		||||
                    if (tableInfo.tableName) {
 | 
			
		||||
                        let tableName = tableInfo.tableName;
 | 
			
		||||
                        let db = tableInfo.dbName;
 | 
			
		||||
                        // 取出表名并提示
 | 
			
		||||
                        let suggestions = await hintTableColumns(tableName, db);
 | 
			
		||||
                        if (suggestions.length > 0) {
 | 
			
		||||
                            return { suggestions };
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                    return { suggestions: [] };
 | 
			
		||||
                } else {
 | 
			
		||||
                    // 如果sql里含有表名,则提示表字段
 | 
			
		||||
                    let mat = textBeforePointerMulti.match(/[from|update]\n*\s+\n*(\w+)\n*\s+\n*/i);
 | 
			
		||||
                    if (mat && mat.length > 1) {
 | 
			
		||||
                        let tableName = mat[1];
 | 
			
		||||
                        // 取出表名并提示
 | 
			
		||||
                        let addSuggestions = await hintTableColumns(tableName, db);
 | 
			
		||||
                        if (addSuggestions.length > 0) {
 | 
			
		||||
                            suggestions = suggestions.concat(addSuggestions);
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // 表名联想
 | 
			
		||||
            tables.forEach((tableMeta: any) => {
 | 
			
		||||
                const { tableName, tableComment } = tableMeta;
 | 
			
		||||
                suggestions.push({
 | 
			
		||||
                    label: {
 | 
			
		||||
                        label: tableName + ' - ' + tableComment,
 | 
			
		||||
                        description: 'table'
 | 
			
		||||
                    },
 | 
			
		||||
                    kind: monaco.languages.CompletionItemKind.File,
 | 
			
		||||
                    detail: tableComment,
 | 
			
		||||
                    insertText: tableName + ' ',
 | 
			
		||||
                    range
 | 
			
		||||
                });
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            // mysql关键字
 | 
			
		||||
            sqlLanguage.keywords.forEach((item: any) => {
 | 
			
		||||
                suggestions.push({
 | 
			
		||||
                    label: {
 | 
			
		||||
                        label: item,
 | 
			
		||||
                        description: 'keyword'
 | 
			
		||||
                    },
 | 
			
		||||
                    kind: monaco.languages.CompletionItemKind.Keyword,
 | 
			
		||||
                    insertText: item,
 | 
			
		||||
                    range
 | 
			
		||||
                });
 | 
			
		||||
            })
 | 
			
		||||
            // 操作符
 | 
			
		||||
            sqlLanguage.operators.forEach((item: any) => {
 | 
			
		||||
                suggestions.push({
 | 
			
		||||
                    label: {
 | 
			
		||||
                        label: item,
 | 
			
		||||
                        description: 'opt'
 | 
			
		||||
                    },
 | 
			
		||||
                    kind: monaco.languages.CompletionItemKind.Operator,
 | 
			
		||||
                    insertText: item,
 | 
			
		||||
                    range
 | 
			
		||||
                });
 | 
			
		||||
            })
 | 
			
		||||
            // 内置函数
 | 
			
		||||
            sqlLanguage.builtinFunctions.forEach((item: any) => {
 | 
			
		||||
                suggestions.push({
 | 
			
		||||
                    label: {
 | 
			
		||||
                        label: item,
 | 
			
		||||
                        description: 'func'
 | 
			
		||||
                    },
 | 
			
		||||
                    kind: monaco.languages.CompletionItemKind.Function,
 | 
			
		||||
                    insertText: item,
 | 
			
		||||
                    range
 | 
			
		||||
                });
 | 
			
		||||
            })
 | 
			
		||||
            // 内置变量
 | 
			
		||||
            sqlLanguage.builtinVariables.forEach((item: string) => {
 | 
			
		||||
                suggestions.push({
 | 
			
		||||
                    label: {
 | 
			
		||||
                        label: item,
 | 
			
		||||
                        description: 'var'
 | 
			
		||||
                    },
 | 
			
		||||
                    kind: monaco.languages.CompletionItemKind.Variable,
 | 
			
		||||
                    insertText: item,
 | 
			
		||||
                    range
 | 
			
		||||
                });
 | 
			
		||||
            })
 | 
			
		||||
 | 
			
		||||
            // 库名提示
 | 
			
		||||
            if (dbs && dbs.length > 0) {
 | 
			
		||||
                dbs.forEach((a: any) => {
 | 
			
		||||
                // 表名联想
 | 
			
		||||
                tables.forEach((tableMeta: any) => {
 | 
			
		||||
                    const { tableName, tableComment } = tableMeta;
 | 
			
		||||
                    suggestions.push({
 | 
			
		||||
                        label: {
 | 
			
		||||
                            label: a,
 | 
			
		||||
                            description: 'schema'
 | 
			
		||||
                            label: tableName + ' - ' + tableComment,
 | 
			
		||||
                            description: 'table',
 | 
			
		||||
                        },
 | 
			
		||||
                        kind: monaco.languages.CompletionItemKind.Folder,
 | 
			
		||||
                        insertText: a,
 | 
			
		||||
                        range
 | 
			
		||||
                        kind: monaco.languages.CompletionItemKind.File,
 | 
			
		||||
                        detail: tableComment,
 | 
			
		||||
                        insertText: tableName + ' ',
 | 
			
		||||
                        range,
 | 
			
		||||
                    });
 | 
			
		||||
                })
 | 
			
		||||
            }
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
            // 默认提示
 | 
			
		||||
            return {
 | 
			
		||||
                suggestions: suggestions
 | 
			
		||||
            };
 | 
			
		||||
        },
 | 
			
		||||
    });
 | 
			
		||||
}
 | 
			
		||||
                // mysql关键字
 | 
			
		||||
                sqlLanguage.keywords.forEach((item: any) => {
 | 
			
		||||
                    suggestions.push({
 | 
			
		||||
                        label: {
 | 
			
		||||
                            label: item,
 | 
			
		||||
                            description: 'keyword',
 | 
			
		||||
                        },
 | 
			
		||||
                        kind: monaco.languages.CompletionItemKind.Keyword,
 | 
			
		||||
                        insertText: item,
 | 
			
		||||
                        range,
 | 
			
		||||
                    });
 | 
			
		||||
                });
 | 
			
		||||
                // 操作符
 | 
			
		||||
                sqlLanguage.operators.forEach((item: any) => {
 | 
			
		||||
                    suggestions.push({
 | 
			
		||||
                        label: {
 | 
			
		||||
                            label: item,
 | 
			
		||||
                            description: 'opt',
 | 
			
		||||
                        },
 | 
			
		||||
                        kind: monaco.languages.CompletionItemKind.Operator,
 | 
			
		||||
                        insertText: item,
 | 
			
		||||
                        range,
 | 
			
		||||
                    });
 | 
			
		||||
                });
 | 
			
		||||
                // 内置函数
 | 
			
		||||
                sqlLanguage.builtinFunctions.forEach((item: any) => {
 | 
			
		||||
                    suggestions.push({
 | 
			
		||||
                        label: {
 | 
			
		||||
                            label: item,
 | 
			
		||||
                            description: 'func',
 | 
			
		||||
                        },
 | 
			
		||||
                        kind: monaco.languages.CompletionItemKind.Function,
 | 
			
		||||
                        insertText: item,
 | 
			
		||||
                        range,
 | 
			
		||||
                    });
 | 
			
		||||
                });
 | 
			
		||||
                // 内置变量
 | 
			
		||||
                sqlLanguage.builtinVariables.forEach((item: string) => {
 | 
			
		||||
                    suggestions.push({
 | 
			
		||||
                        label: {
 | 
			
		||||
                            label: item,
 | 
			
		||||
                            description: 'var',
 | 
			
		||||
                        },
 | 
			
		||||
                        kind: monaco.languages.CompletionItemKind.Variable,
 | 
			
		||||
                        insertText: item,
 | 
			
		||||
                        range,
 | 
			
		||||
                    });
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
                // 库名提示
 | 
			
		||||
                if (dbs && dbs.length > 0) {
 | 
			
		||||
                    dbs.forEach((a: any) => {
 | 
			
		||||
                        suggestions.push({
 | 
			
		||||
                            label: {
 | 
			
		||||
                                label: a,
 | 
			
		||||
                                description: 'schema',
 | 
			
		||||
                            },
 | 
			
		||||
                            kind: monaco.languages.CompletionItemKind.Folder,
 | 
			
		||||
                            insertText: a,
 | 
			
		||||
                            range,
 | 
			
		||||
                        });
 | 
			
		||||
                    });
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                // 默认提示
 | 
			
		||||
                return {
 | 
			
		||||
                    suggestions: suggestions,
 | 
			
		||||
                };
 | 
			
		||||
            },
 | 
			
		||||
        });
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 根据别名获取sql里的表名
 | 
			
		||||
@@ -669,8 +681,7 @@ const registerSqlCompletionItemProvider = () => {
 | 
			
		||||
 * @param db 默认数据库
 | 
			
		||||
 * @param alias 别名
 | 
			
		||||
 */
 | 
			
		||||
const getTableByAlias = (sql: string, db: string, alias: string): { dbName: string, tableName: string } => {
 | 
			
		||||
 | 
			
		||||
const getTableByAlias = (sql: string, db: string, alias: string): { dbName: string; tableName: string } => {
 | 
			
		||||
    // 表别名:表名
 | 
			
		||||
    let result = {};
 | 
			
		||||
    let defName = '';
 | 
			
		||||
@@ -686,30 +697,32 @@ where l.name='kevin' and exsits(select 1 from pharmacywestpas pw where p.outvisi
 | 
			
		||||
unit all
 | 
			
		||||
select * from invisit v where`.match(/(join|from)\s+(\w*-?\w*\.?\w+)\s*(as)?\s*(\w*)/gi)
 | 
			
		||||
     */
 | 
			
		||||
    let match = sql.match(/(join|from)\n*\s+\n*(\w*-?\w*\.?\w+)\s*(as)?\s*(\w*)\n*/gi)
 | 
			
		||||
    let match = sql.match(/(join|from)\n*\s+\n*(\w*-?\w*\.?\w+)\s*(as)?\s*(\w*)\n*/gi);
 | 
			
		||||
    if (match && match.length > 0) {
 | 
			
		||||
        match.forEach(a => {
 | 
			
		||||
        match.forEach((a) => {
 | 
			
		||||
            // 去掉前缀,取出
 | 
			
		||||
            let t = a.substring(5, a.length)
 | 
			
		||||
            let t = a
 | 
			
		||||
                .substring(5, a.length)
 | 
			
		||||
                .replaceAll(/\s+/g, ' ')
 | 
			
		||||
                .replaceAll(/\s+as\s+/gi, ' ')
 | 
			
		||||
                .replaceAll(/\r\n/g, ' ').trim()
 | 
			
		||||
                .replaceAll(/\r\n/g, ' ')
 | 
			
		||||
                .trim()
 | 
			
		||||
                .split(/\s+/);
 | 
			
		||||
            let withDb = t[0].split('.');
 | 
			
		||||
            // 表名是 db名.表名
 | 
			
		||||
            let tName = withDb.length > 1 ? withDb[1] : withDb[0]
 | 
			
		||||
            let dbName = withDb.length > 1 ? withDb[0] : (db || '')
 | 
			
		||||
            let tName = withDb.length > 1 ? withDb[1] : withDb[0];
 | 
			
		||||
            let dbName = withDb.length > 1 ? withDb[0] : db || '';
 | 
			
		||||
            if (t.length == 2) {
 | 
			
		||||
                // 表别名:表名
 | 
			
		||||
                result[t[1]] = { tableName: tName, dbName }
 | 
			
		||||
                result[t[1]] = { tableName: tName, dbName };
 | 
			
		||||
            } else {
 | 
			
		||||
                // 只有表名无别名 取第一个无别名的表为默认表
 | 
			
		||||
                !defName && (defResult = { tableName: tName, dbName: db })
 | 
			
		||||
                !defName && (defResult = { tableName: tName, dbName: db });
 | 
			
		||||
            }
 | 
			
		||||
        })
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
    return result[alias] || defResult
 | 
			
		||||
}
 | 
			
		||||
    return result[alias] || defResult;
 | 
			
		||||
};
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style lang="scss">
 | 
			
		||||
@@ -753,7 +766,7 @@ select * from invisit v where`.match(/(join|from)\s+(\w*-?\w*\.?\w+)\s*(as)?\s*(
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.update_field_active {
 | 
			
		||||
    background-color: var(--el-color-success)
 | 
			
		||||
    background-color: var(--el-color-success);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.instances-pop-form {
 | 
			
		||||
 
 | 
			
		||||
@@ -2,14 +2,12 @@
 | 
			
		||||
    <div>
 | 
			
		||||
        <el-dialog :title="`${title} 详情`" v-model="dialogVisible" :before-close="cancel" width="90%">
 | 
			
		||||
            <el-table @cell-click="cellClick" :data="data.res">
 | 
			
		||||
                <el-table-column :width="200" :prop="item" :label="item" v-for="item in data.colNames" :key="item">
 | 
			
		||||
                </el-table-column>
 | 
			
		||||
                <el-table-column :width="200" :prop="item" :label="item" v-for="item in data.colNames" :key="item"> </el-table-column>
 | 
			
		||||
            </el-table>
 | 
			
		||||
        </el-dialog>
 | 
			
		||||
    </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
<script lang="ts" setup>
 | 
			
		||||
import { watch, toRefs, reactive } from 'vue';
 | 
			
		||||
 | 
			
		||||
@@ -23,10 +21,10 @@ const props = defineProps({
 | 
			
		||||
    data: {
 | 
			
		||||
        type: Object,
 | 
			
		||||
    },
 | 
			
		||||
})
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
//定义事件
 | 
			
		||||
const emit = defineEmits(['update:visible'])
 | 
			
		||||
const emit = defineEmits(['update:visible']);
 | 
			
		||||
 | 
			
		||||
const state = reactive({
 | 
			
		||||
    dialogVisible: false,
 | 
			
		||||
@@ -36,10 +34,7 @@ const state = reactive({
 | 
			
		||||
    },
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const {
 | 
			
		||||
    dialogVisible,
 | 
			
		||||
    data,
 | 
			
		||||
} = toRefs(state)
 | 
			
		||||
const { dialogVisible, data } = toRefs(state);
 | 
			
		||||
 | 
			
		||||
watch(props, async (newValue: any) => {
 | 
			
		||||
    state.dialogVisible = newValue.visible;
 | 
			
		||||
@@ -66,6 +61,4 @@ const cellClick = (row: any, column: any, cell: any) => {
 | 
			
		||||
const cancel = () => {
 | 
			
		||||
    emit('update:visible', false);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -2,27 +2,28 @@ import Api from '@/common/Api';
 | 
			
		||||
 | 
			
		||||
export const dbApi = {
 | 
			
		||||
    // 获取权限列表
 | 
			
		||||
    dbs: Api.newGet("/dbs"),
 | 
			
		||||
    saveDb: Api.newPost("/dbs"),
 | 
			
		||||
    getAllDatabase: Api.newPost("/dbs/databases"),
 | 
			
		||||
    getDbPwd: Api.newGet("/dbs/{id}/pwd"),
 | 
			
		||||
    deleteDb: Api.newDelete("/dbs/{id}"),
 | 
			
		||||
    dumpDb: Api.newPost("/dbs/{id}/dump"),
 | 
			
		||||
    tableInfos: Api.newGet("/dbs/{id}/t-infos"),
 | 
			
		||||
    tableIndex: Api.newGet("/dbs/{id}/t-index"),
 | 
			
		||||
    tableDdl: Api.newGet("/dbs/{id}/t-create-ddl"),
 | 
			
		||||
    tableMetadata: Api.newGet("/dbs/{id}/t-metadata"),
 | 
			
		||||
    columnMetadata: Api.newGet("/dbs/{id}/c-metadata"),
 | 
			
		||||
    dbs: Api.newGet('/dbs'),
 | 
			
		||||
    dbTags: Api.newGet('/dbs/tags'),
 | 
			
		||||
    saveDb: Api.newPost('/dbs'),
 | 
			
		||||
    getAllDatabase: Api.newPost('/dbs/databases'),
 | 
			
		||||
    getDbPwd: Api.newGet('/dbs/{id}/pwd'),
 | 
			
		||||
    deleteDb: Api.newDelete('/dbs/{id}'),
 | 
			
		||||
    dumpDb: Api.newPost('/dbs/{id}/dump'),
 | 
			
		||||
    tableInfos: Api.newGet('/dbs/{id}/t-infos'),
 | 
			
		||||
    tableIndex: Api.newGet('/dbs/{id}/t-index'),
 | 
			
		||||
    tableDdl: Api.newGet('/dbs/{id}/t-create-ddl'),
 | 
			
		||||
    tableMetadata: Api.newGet('/dbs/{id}/t-metadata'),
 | 
			
		||||
    columnMetadata: Api.newGet('/dbs/{id}/c-metadata'),
 | 
			
		||||
    // 获取表即列提示
 | 
			
		||||
    hintTables: Api.newGet("/dbs/{id}/hint-tables"),
 | 
			
		||||
    sqlExec: Api.newPost("/dbs/{id}/exec-sql"),
 | 
			
		||||
    hintTables: Api.newGet('/dbs/{id}/hint-tables'),
 | 
			
		||||
    sqlExec: Api.newPost('/dbs/{id}/exec-sql'),
 | 
			
		||||
    // 保存sql
 | 
			
		||||
    saveSql: Api.newPost("/dbs/{id}/sql"),
 | 
			
		||||
    saveSql: Api.newPost('/dbs/{id}/sql'),
 | 
			
		||||
    // 获取保存的sql
 | 
			
		||||
    getSql: Api.newGet("/dbs/{id}/sql"),
 | 
			
		||||
    getSql: Api.newGet('/dbs/{id}/sql'),
 | 
			
		||||
    // 获取保存的sql names
 | 
			
		||||
    getSqlNames: Api.newGet("/dbs/{id}/sql-names"),
 | 
			
		||||
    deleteDbSql: Api.newDelete("/dbs/{id}/sql"),
 | 
			
		||||
    getSqlNames: Api.newGet('/dbs/{id}/sql-names'),
 | 
			
		||||
    deleteDbSql: Api.newDelete('/dbs/{id}/sql'),
 | 
			
		||||
    // 获取数据库sql执行记录
 | 
			
		||||
    getSqlExecs: Api.newGet("/dbs/{dbId}/sql-execs"),
 | 
			
		||||
}
 | 
			
		||||
    getSqlExecs: Api.newGet('/dbs/{dbId}/sql-execs'),
 | 
			
		||||
};
 | 
			
		||||
 
 | 
			
		||||
@@ -1,20 +1,42 @@
 | 
			
		||||
<template>
 | 
			
		||||
    <div>
 | 
			
		||||
        <el-table @cell-dblclick="(row: any, column: any, cell: any, event: any) => cellClick(row, column, cell)"
 | 
			
		||||
            @sort-change="(sort: any) => onTableSortChange(sort)" @selection-change="onDataSelectionChange"
 | 
			
		||||
            :data="datas" size="small" :max-height="tableHeight" v-loading="loading" element-loading-text="查询中..."
 | 
			
		||||
            :empty-text="emptyText" stripe border class="mt5">
 | 
			
		||||
        <el-table
 | 
			
		||||
            @cell-dblclick="(row: any, column: any, cell: any, event: any) => cellClick(row, column, cell)"
 | 
			
		||||
            @sort-change="(sort: any) => onTableSortChange(sort)"
 | 
			
		||||
            @selection-change="onDataSelectionChange"
 | 
			
		||||
            :data="datas"
 | 
			
		||||
            size="small"
 | 
			
		||||
            :max-height="tableHeight"
 | 
			
		||||
            v-loading="loading"
 | 
			
		||||
            element-loading-text="查询中..."
 | 
			
		||||
            :empty-text="emptyText"
 | 
			
		||||
            highlight-current-row
 | 
			
		||||
            stripe
 | 
			
		||||
            border
 | 
			
		||||
            class="mt5"
 | 
			
		||||
        >
 | 
			
		||||
            <el-table-column v-if="datas.length > 0 && table" type="selection" width="35" />
 | 
			
		||||
            <el-table-column min-width="100" :width="DbInst.flexColumnWidth(item, datas)" align="center"
 | 
			
		||||
                v-for="item in columnNames" :key="item" :prop="item" :label="item" show-overflow-tooltip
 | 
			
		||||
                :sortable="sortable">
 | 
			
		||||
                <template #header v-if="showColumnTip">
 | 
			
		||||
                    <el-tooltip raw-content placement="top" effect="customized">
 | 
			
		||||
                        <template #content> {{ getColumnTip(item) }} </template>
 | 
			
		||||
                        {{ item }}
 | 
			
		||||
                    </el-tooltip>
 | 
			
		||||
                </template>
 | 
			
		||||
            </el-table-column>
 | 
			
		||||
 | 
			
		||||
            <template v-for="(item, index) in columns">
 | 
			
		||||
                <el-table-column
 | 
			
		||||
                    min-width="100"
 | 
			
		||||
                    :width="DbInst.flexColumnWidth(item.columnName, datas)"
 | 
			
		||||
                    align="center"
 | 
			
		||||
                    v-if="item.show"
 | 
			
		||||
                    :key="index"
 | 
			
		||||
                    :prop="item.columnName"
 | 
			
		||||
                    :label="item.columnName"
 | 
			
		||||
                    show-overflow-tooltip
 | 
			
		||||
                    :sortable="sortable"
 | 
			
		||||
                >
 | 
			
		||||
                    <template #header v-if="showColumnTip">
 | 
			
		||||
                        <el-tooltip raw-content placement="top" effect="customized">
 | 
			
		||||
                            <template #content> {{ getColumnTip(item) }} </template>
 | 
			
		||||
                            {{ item.columnName }}
 | 
			
		||||
                        </el-tooltip>
 | 
			
		||||
                    </template>
 | 
			
		||||
                </el-table-column>
 | 
			
		||||
            </template>
 | 
			
		||||
        </el-table>
 | 
			
		||||
    </div>
 | 
			
		||||
</template>
 | 
			
		||||
@@ -45,8 +67,8 @@ const props = defineProps({
 | 
			
		||||
    data: {
 | 
			
		||||
        type: Array,
 | 
			
		||||
    },
 | 
			
		||||
    columnNames: {
 | 
			
		||||
        type: Array,
 | 
			
		||||
    columns: {
 | 
			
		||||
        type: Array<any>,
 | 
			
		||||
    },
 | 
			
		||||
    sortable: {
 | 
			
		||||
        type: [String, Boolean],
 | 
			
		||||
@@ -76,7 +98,6 @@ const state = reactive({
 | 
			
		||||
    db: '',  // 数据库名
 | 
			
		||||
    table: '', // 当前的表名
 | 
			
		||||
    datas: [],
 | 
			
		||||
    columnNames: [],
 | 
			
		||||
    columns: [],
 | 
			
		||||
    sortable: false,
 | 
			
		||||
    loading: false,
 | 
			
		||||
@@ -92,7 +113,6 @@ const {
 | 
			
		||||
    datas,
 | 
			
		||||
    sortable,
 | 
			
		||||
    loading,
 | 
			
		||||
    columnNames,
 | 
			
		||||
    showColumnTip,
 | 
			
		||||
} = toRefs(state);
 | 
			
		||||
 | 
			
		||||
@@ -114,24 +134,16 @@ const setState = (props: any) => {
 | 
			
		||||
    state.tableHeight = props.height;
 | 
			
		||||
    state.sortable = props.sortable;
 | 
			
		||||
    state.loading = props.loading;
 | 
			
		||||
    state.columnNames = props.columnNames;
 | 
			
		||||
    state.columns = props.columns;
 | 
			
		||||
    state.showColumnTip = props.showColumnTip;
 | 
			
		||||
    state.emptyText = props.emptyText;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const getColumnTip = (columnName: string) => {
 | 
			
		||||
    // 优先从 table map中获取
 | 
			
		||||
    let columns = getNowDb().getColumns(state.table);
 | 
			
		||||
    if (!columns) {
 | 
			
		||||
        return '';
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const column = columns.find((c: any) => c.columnName == columnName);
 | 
			
		||||
const getColumnTip = (column: any) => {
 | 
			
		||||
    const comment = column.columnComment;
 | 
			
		||||
    return `${column.columnType} ${comment ? ' |  ' + comment : ''}`;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 表排序字段变更
 | 
			
		||||
 */
 | 
			
		||||
@@ -178,7 +190,7 @@ const cellClick = (row: any, column: any, cell: any) => {
 | 
			
		||||
                const updateColumn = await dbInst.loadTableColumn(state.db, state.table, property);
 | 
			
		||||
                const newField = {
 | 
			
		||||
                    div, row,
 | 
			
		||||
                    fieldName: column.rawColumnKey,
 | 
			
		||||
                    fieldName: property,
 | 
			
		||||
                    fieldType: updateColumn.columnType,
 | 
			
		||||
                    oldValue: text,
 | 
			
		||||
                    newValue: input.value
 | 
			
		||||
@@ -209,10 +221,10 @@ const cellClick = (row: any, column: any, cell: any) => {
 | 
			
		||||
                let fields = primaryKeyFields[0].fields
 | 
			
		||||
 | 
			
		||||
                const fieldsParam = fields.filter((a) => {
 | 
			
		||||
                    if (a.fieldName === column.rawColumnKey) {
 | 
			
		||||
                    if (a.fieldName === column.property) {
 | 
			
		||||
                        a.newValue = input.value
 | 
			
		||||
                    }
 | 
			
		||||
                    return a.fieldName === column.rawColumnKey
 | 
			
		||||
                    return a.fieldName === column.property
 | 
			
		||||
                })
 | 
			
		||||
 | 
			
		||||
                const field = fieldsParam.length > 0 && fieldsParam[0] || {} as FieldsMeta
 | 
			
		||||
@@ -222,7 +234,7 @@ const cellClick = (row: any, column: any, cell: any) => {
 | 
			
		||||
                    let delIndex: number[] = [];
 | 
			
		||||
                    currentUpdatedFields.forEach((a, i) => {
 | 
			
		||||
                        if (a.primaryKey === primaryKeyValue) {
 | 
			
		||||
                            a.fields = a.fields && a.fields.length > 0 ? a.fields.filter(f => f.fieldName !== column.rawColumnKey) : [];
 | 
			
		||||
                            a.fields = a.fields && a.fields.length > 0 ? a.fields.filter(f => f.fieldName !== column.property) : [];
 | 
			
		||||
                            a.fields.length <= 0 && delIndex.push(i)
 | 
			
		||||
                        }
 | 
			
		||||
                    });
 | 
			
		||||
@@ -269,7 +281,7 @@ const submitUpdateFields = () => {
 | 
			
		||||
        a.fields.forEach(f => {
 | 
			
		||||
            sql += ` ${f.fieldName} = ${DbInst.wrapColumnValue(f.fieldType, f.newValue)},`
 | 
			
		||||
            // 如果修改的字段是主键
 | 
			
		||||
            if(f.fieldName === primaryKeyName){
 | 
			
		||||
            if (f.fieldName === primaryKeyName) {
 | 
			
		||||
                primaryKey = f.oldValue
 | 
			
		||||
            }
 | 
			
		||||
            divs.push(f.div)
 | 
			
		||||
@@ -305,10 +317,6 @@ const changeUpdatedField = () => {
 | 
			
		||||
    emits('changeUpdatedField', state.updatedFields);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const getNowDb = () => {
 | 
			
		||||
    return DbInst.getInst(state.dbId).getDb(state.db);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const getNowDbInst = () => {
 | 
			
		||||
    return DbInst.getInst(state.dbId);
 | 
			
		||||
}
 | 
			
		||||
@@ -317,11 +325,10 @@ defineExpose({
 | 
			
		||||
    submitUpdateFields,
 | 
			
		||||
    cancelUpdateFields
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style lang="scss">
 | 
			
		||||
.update_field_active {
 | 
			
		||||
    background-color: var(--el-color-success)
 | 
			
		||||
    background-color: var(--el-color-success);
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
 
 | 
			
		||||
@@ -1,44 +1,44 @@
 | 
			
		||||
import { h, render, VNode } from 'vue'
 | 
			
		||||
import SqlExecDialog from './SqlExecDialog.vue'
 | 
			
		||||
import { h, render, VNode } from 'vue';
 | 
			
		||||
import SqlExecDialog from './SqlExecDialog.vue';
 | 
			
		||||
 | 
			
		||||
export type SqlExecProps = {
 | 
			
		||||
    sql: string
 | 
			
		||||
    dbId: number,
 | 
			
		||||
    db: string,
 | 
			
		||||
    runSuccessCallback?: Function,
 | 
			
		||||
    cancelCallback?: Function
 | 
			
		||||
}
 | 
			
		||||
    sql: string;
 | 
			
		||||
    dbId: number;
 | 
			
		||||
    db: string;
 | 
			
		||||
    runSuccessCallback?: Function;
 | 
			
		||||
    cancelCallback?: Function;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const boxId = 'sql-exec-id'
 | 
			
		||||
const boxId = 'sql-exec-id';
 | 
			
		||||
 | 
			
		||||
const renderBox = (): VNode => {
 | 
			
		||||
    const props: SqlExecProps = {
 | 
			
		||||
        sql: '',
 | 
			
		||||
        dbId: 0,
 | 
			
		||||
    } as any
 | 
			
		||||
    const container = document.createElement('div')
 | 
			
		||||
    container.id = boxId
 | 
			
		||||
    } as any;
 | 
			
		||||
    const container = document.createElement('div');
 | 
			
		||||
    container.id = boxId;
 | 
			
		||||
    // 创建 虚拟dom
 | 
			
		||||
    const boxVNode = h(SqlExecDialog, props)
 | 
			
		||||
    const boxVNode = h(SqlExecDialog, props);
 | 
			
		||||
    // 将虚拟dom渲染到 container dom 上
 | 
			
		||||
    render(boxVNode, container)
 | 
			
		||||
    render(boxVNode, container);
 | 
			
		||||
    // 最后将 container 追加到 body 上
 | 
			
		||||
    document.body.appendChild(container)
 | 
			
		||||
    document.body.appendChild(container);
 | 
			
		||||
 | 
			
		||||
    return boxVNode
 | 
			
		||||
}
 | 
			
		||||
    return boxVNode;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
let boxInstance: any
 | 
			
		||||
let boxInstance: any;
 | 
			
		||||
 | 
			
		||||
const SqlExecBox = (props: SqlExecProps): void => {
 | 
			
		||||
    if (boxInstance) {
 | 
			
		||||
        const boxVue = boxInstance.component
 | 
			
		||||
        const boxVue = boxInstance.component;
 | 
			
		||||
        // 调用open方法显示弹框,注意不能使用boxVue.ctx来调用组件函数(build打包后ctx会获取不到)
 | 
			
		||||
        boxVue.exposed.open(props);
 | 
			
		||||
    } else {
 | 
			
		||||
        boxInstance = renderBox()
 | 
			
		||||
        SqlExecBox(props)
 | 
			
		||||
        boxInstance = renderBox();
 | 
			
		||||
        SqlExecBox(props);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export default SqlExecBox;
 | 
			
		||||
 
 | 
			
		||||
@@ -36,7 +36,7 @@ const props = defineProps({
 | 
			
		||||
    sql: {
 | 
			
		||||
        type: String,
 | 
			
		||||
    },
 | 
			
		||||
})
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const remarkInputRef = ref<InputInstance>();
 | 
			
		||||
const state = reactive({
 | 
			
		||||
@@ -48,12 +48,7 @@ const state = reactive({
 | 
			
		||||
    btnLoading: false,
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const {
 | 
			
		||||
    dialogVisible,
 | 
			
		||||
    sqlValue,
 | 
			
		||||
    remark,
 | 
			
		||||
    btnLoading
 | 
			
		||||
} = toRefs(state)
 | 
			
		||||
const { dialogVisible, sqlValue, remark, btnLoading } = toRefs(state);
 | 
			
		||||
 | 
			
		||||
state.sqlValue = props.sql as any;
 | 
			
		||||
let runSuccessCallback: any;
 | 
			
		||||
@@ -81,7 +76,7 @@ const runSql = async () => {
 | 
			
		||||
        for (let re of res.res) {
 | 
			
		||||
            if (re.result !== 'success') {
 | 
			
		||||
                ElMessage.error(`${re.sql} \n执行失败: ${re.result}`);
 | 
			
		||||
                throw new Error(re.result)
 | 
			
		||||
                throw new Error(re.result);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
@@ -129,7 +124,7 @@ const open = (props: SqlExecProps) => {
 | 
			
		||||
    });
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
defineExpose({ open })
 | 
			
		||||
defineExpose({ open });
 | 
			
		||||
</script>
 | 
			
		||||
<style lang="scss">
 | 
			
		||||
.codesql {
 | 
			
		||||
 
 | 
			
		||||
@@ -3,25 +3,30 @@
 | 
			
		||||
        <div>
 | 
			
		||||
            <div class="toolbar">
 | 
			
		||||
                <div class="fl">
 | 
			
		||||
                    <el-link @click="onRunSql()" :underline="false" class="ml15" icon="VideoPlay">
 | 
			
		||||
                    </el-link>
 | 
			
		||||
                    <el-link @click="onRunSql()" :underline="false" class="ml15" icon="VideoPlay"> </el-link>
 | 
			
		||||
                    <el-divider direction="vertical" border-style="dashed" />
 | 
			
		||||
 | 
			
		||||
                    <el-tooltip class="box-item" effect="dark" content="format sql" placement="top">
 | 
			
		||||
                        <el-link @click="formatSql()" type="primary" :underline="false" icon="MagicStick">
 | 
			
		||||
                        </el-link>
 | 
			
		||||
                        <el-link @click="formatSql()" type="primary" :underline="false" icon="MagicStick"> </el-link>
 | 
			
		||||
                    </el-tooltip>
 | 
			
		||||
                    <el-divider direction="vertical" border-style="dashed" />
 | 
			
		||||
 | 
			
		||||
                    <el-tooltip class="box-item" effect="dark" content="commit" placement="top">
 | 
			
		||||
                        <el-link @click="onCommit()" type="success" :underline="false" icon="CircleCheck">
 | 
			
		||||
                        </el-link>
 | 
			
		||||
                        <el-link @click="onCommit()" type="success" :underline="false" icon="CircleCheck"> </el-link>
 | 
			
		||||
                    </el-tooltip>
 | 
			
		||||
                    <el-divider direction="vertical" border-style="dashed" />
 | 
			
		||||
 | 
			
		||||
                    <el-upload class="sql-file-exec" :before-upload="beforeUpload" :on-success="execSqlFileSuccess"
 | 
			
		||||
                        :headers="{ Authorization: token }" :action="getUploadSqlFileUrl()" :show-file-list="false"
 | 
			
		||||
                        name="file" multiple :limit="100">
 | 
			
		||||
                    <el-upload
 | 
			
		||||
                        class="sql-file-exec"
 | 
			
		||||
                        :before-upload="beforeUpload"
 | 
			
		||||
                        :on-success="execSqlFileSuccess"
 | 
			
		||||
                        :headers="{ Authorization: token }"
 | 
			
		||||
                        :action="getUploadSqlFileUrl()"
 | 
			
		||||
                        :show-file-list="false"
 | 
			
		||||
                        name="file"
 | 
			
		||||
                        multiple
 | 
			
		||||
                        :limit="100"
 | 
			
		||||
                    >
 | 
			
		||||
                        <el-tooltip class="box-item" effect="dark" content="SQL脚本执行" placement="top">
 | 
			
		||||
                            <el-link type="success" :underline="false" icon="Document"></el-link>
 | 
			
		||||
                        </el-tooltip>
 | 
			
		||||
@@ -29,17 +34,14 @@
 | 
			
		||||
                </div>
 | 
			
		||||
 | 
			
		||||
                <div style="float: right" class="fl">
 | 
			
		||||
                    <el-button @click="saveSql()" type="primary" icon="document-add" plain size="small">保存SQL
 | 
			
		||||
                    </el-button>
 | 
			
		||||
                    <el-button v-if="sqlName" @click="deleteSql()" type="danger" icon="delete" plain size="small">删除SQL
 | 
			
		||||
                    </el-button>
 | 
			
		||||
                    <el-button @click="saveSql()" type="primary" icon="document-add" plain size="small">保存SQL </el-button>
 | 
			
		||||
                    <el-button v-if="sqlName" @click="deleteSql()" type="danger" icon="delete" plain size="small">删除SQL </el-button>
 | 
			
		||||
                </div>
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
 | 
			
		||||
        <div class="mt5 sqlEditor">
 | 
			
		||||
            <div :id="'MonacoTextarea-' + ti.key" :style="{ height: editorHeight }">
 | 
			
		||||
            </div>
 | 
			
		||||
            <div :id="'MonacoTextarea-' + ti.key" :style="{ height: editorHeight }"></div>
 | 
			
		||||
        </div>
 | 
			
		||||
 | 
			
		||||
        <div class="editor-move-resize" @mousedown="onDragSetHeight">
 | 
			
		||||
@@ -50,31 +52,35 @@
 | 
			
		||||
 | 
			
		||||
        <div class="mt5">
 | 
			
		||||
            <el-row>
 | 
			
		||||
                <el-link v-if="table" @click="onDeleteData()" class="ml5" type="danger" icon="delete"
 | 
			
		||||
                    :underline="false"></el-link>
 | 
			
		||||
                <el-link v-if="table" @click="onDeleteData()" class="ml5" type="danger" icon="delete" :underline="false"></el-link>
 | 
			
		||||
 | 
			
		||||
                <span v-if="execRes.data.length > 0">
 | 
			
		||||
                    <el-divider direction="vertical" border-style="dashed" />
 | 
			
		||||
                    <el-link type="success" :underline="false" @click="exportData"><span
 | 
			
		||||
                            style="font-size: 12px">导出</span></el-link>
 | 
			
		||||
                    <el-link type="success" :underline="false" @click="exportData"><span style="font-size: 12px">导出</span></el-link>
 | 
			
		||||
                </span>
 | 
			
		||||
                <span v-if="hasUpdatedFileds">
 | 
			
		||||
                    <el-divider direction="vertical" border-style="dashed" />
 | 
			
		||||
                    <el-link type="success" :underline="false" @click="submitUpdateFields()"><span
 | 
			
		||||
                            style="font-size: 12px">提交</span></el-link>
 | 
			
		||||
                    <el-link type="success" :underline="false" @click="submitUpdateFields()"><span style="font-size: 12px">提交</span></el-link>
 | 
			
		||||
                </span>
 | 
			
		||||
                <span v-if="hasUpdatedFileds">
 | 
			
		||||
                    <el-divider direction="vertical" border-style="dashed" />
 | 
			
		||||
                    <el-link type="warning" :underline="false" @click="cancelUpdateFields"><span
 | 
			
		||||
                            style="font-size: 12px">取消</span></el-link>
 | 
			
		||||
                    <el-link type="warning" :underline="false" @click="cancelUpdateFields"><span style="font-size: 12px">取消</span></el-link>
 | 
			
		||||
                </span>
 | 
			
		||||
            </el-row>
 | 
			
		||||
            <db-table ref="dbTableRef" :db-id="state.ti.dbId" :db="state.ti.db" :data="execRes.data" :table="state.table"
 | 
			
		||||
                :column-names="execRes.tableColumn" :loading="loading" :height="tableDataHeight"
 | 
			
		||||
                empty-text="tips: select *开头的单表查询或点击表名默认查询的数据,可双击数据在线修改" @selection-change="onDataSelectionChange"
 | 
			
		||||
                @change-updated-field="changeUpdatedField"></db-table>
 | 
			
		||||
            <db-table
 | 
			
		||||
                ref="dbTableRef"
 | 
			
		||||
                :db-id="state.ti.dbId"
 | 
			
		||||
                :db="state.ti.db"
 | 
			
		||||
                :data="execRes.data"
 | 
			
		||||
                :table="state.table"
 | 
			
		||||
                :columns="execRes.tableColumn"
 | 
			
		||||
                :loading="loading"
 | 
			
		||||
                :height="tableDataHeight"
 | 
			
		||||
                empty-text="tips: select *开头的单表查询或点击表名默认查询的数据,可双击数据在线修改"
 | 
			
		||||
                @selection-change="onDataSelectionChange"
 | 
			
		||||
                @change-updated-field="changeUpdatedField"
 | 
			
		||||
            ></db-table>
 | 
			
		||||
        </div>
 | 
			
		||||
 | 
			
		||||
    </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
@@ -94,18 +100,18 @@ import { editor } from 'monaco-editor';
 | 
			
		||||
// 主题仓库 https://github.com/brijeshb42/monaco-themes
 | 
			
		||||
// 主题例子 https://editor.bitwiser.in/
 | 
			
		||||
import SolarizedLight from 'monaco-themes/themes/Solarized-light.json';
 | 
			
		||||
import DbTable from '../DbTable.vue'
 | 
			
		||||
import DbTable from '../DbTable.vue';
 | 
			
		||||
import { TabInfo } from '../../db';
 | 
			
		||||
import { exportCsv } from '@/common/utils/export';
 | 
			
		||||
import { dateStrFormat } from '@/common/utils/date';
 | 
			
		||||
import { dbApi } from '../../api';
 | 
			
		||||
 | 
			
		||||
const emits = defineEmits(['saveSqlSuccess', 'deleteSqlSuccess'])
 | 
			
		||||
const emits = defineEmits(['saveSqlSuccess', 'deleteSqlSuccess']);
 | 
			
		||||
 | 
			
		||||
const props = defineProps({
 | 
			
		||||
    data: {
 | 
			
		||||
        type: TabInfo,
 | 
			
		||||
        required: true
 | 
			
		||||
        required: true,
 | 
			
		||||
    },
 | 
			
		||||
    // sql脚本名,若有则去加载该sql内容
 | 
			
		||||
    sqlName: {
 | 
			
		||||
@@ -114,9 +120,9 @@ const props = defineProps({
 | 
			
		||||
    },
 | 
			
		||||
    editorHeight: {
 | 
			
		||||
        type: String,
 | 
			
		||||
        default: '600'
 | 
			
		||||
    }
 | 
			
		||||
})
 | 
			
		||||
        default: '600',
 | 
			
		||||
    },
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const { themeConfig } = storeToRefs(useThemeConfig());
 | 
			
		||||
const token = getSession('token');
 | 
			
		||||
@@ -134,7 +140,7 @@ const state = reactive({
 | 
			
		||||
    loading: false, // 是否在加载数据
 | 
			
		||||
    execRes: {
 | 
			
		||||
        data: [],
 | 
			
		||||
        tableColumn: []
 | 
			
		||||
        tableColumn: [],
 | 
			
		||||
    },
 | 
			
		||||
    selectionDatas: [] as any,
 | 
			
		||||
    editorHeight: '500',
 | 
			
		||||
@@ -142,20 +148,14 @@ const state = reactive({
 | 
			
		||||
    hasUpdatedFileds: false,
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const {
 | 
			
		||||
    tableDataHeight,
 | 
			
		||||
    editorHeight,
 | 
			
		||||
    ti,
 | 
			
		||||
    execRes,
 | 
			
		||||
    table,
 | 
			
		||||
    sqlName,
 | 
			
		||||
    loading,
 | 
			
		||||
    hasUpdatedFileds,
 | 
			
		||||
} = toRefs(state);
 | 
			
		||||
const { tableDataHeight, editorHeight, ti, execRes, table, sqlName, loading, hasUpdatedFileds } = toRefs(state);
 | 
			
		||||
 | 
			
		||||
watch(() => props.editorHeight, (newValue: any) => {
 | 
			
		||||
    state.editorHeight = newValue;
 | 
			
		||||
});
 | 
			
		||||
watch(
 | 
			
		||||
    () => props.editorHeight,
 | 
			
		||||
    (newValue: any) => {
 | 
			
		||||
        state.editorHeight = newValue;
 | 
			
		||||
    }
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
onMounted(async () => {
 | 
			
		||||
    console.log('in query mounted');
 | 
			
		||||
@@ -170,19 +170,19 @@ onMounted(async () => {
 | 
			
		||||
        state.sql = res.sql;
 | 
			
		||||
    }
 | 
			
		||||
    nextTick(() => {
 | 
			
		||||
        setTimeout(() => initMonacoEditor(), 50)
 | 
			
		||||
    })
 | 
			
		||||
        setTimeout(() => initMonacoEditor(), 50);
 | 
			
		||||
    });
 | 
			
		||||
    await state.ti.getNowDbInst().loadDbHints(state.ti.db);
 | 
			
		||||
})
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
self.MonacoEnvironment = {
 | 
			
		||||
    getWorker() {
 | 
			
		||||
        return new EditorWorker();
 | 
			
		||||
    }
 | 
			
		||||
    },
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const initMonacoEditor = () => {
 | 
			
		||||
    let monacoTextarea = document.getElementById('MonacoTextarea-' + state.ti.key) as HTMLElement
 | 
			
		||||
    let monacoTextarea = document.getElementById('MonacoTextarea-' + state.ti.key) as HTMLElement;
 | 
			
		||||
    // options参数参考 https://microsoft.github.io/monaco-editor/api/interfaces/monaco.editor.IStandaloneEditorConstructionOptions.html#language
 | 
			
		||||
    // 初始化一些主题
 | 
			
		||||
    monaco.editor.defineTheme('SolarizedLight', SolarizedLight);
 | 
			
		||||
@@ -194,7 +194,7 @@ const initMonacoEditor = () => {
 | 
			
		||||
        roundedSelection: false, // 禁用选择文本背景的圆角
 | 
			
		||||
        matchBrackets: 'near',
 | 
			
		||||
        linkedEditing: true,
 | 
			
		||||
        cursorBlinking: 'smooth',// 光标闪烁样式
 | 
			
		||||
        cursorBlinking: 'smooth', // 光标闪烁样式
 | 
			
		||||
        mouseWheelZoom: true, // 在按住Ctrl键的同时使用鼠标滚轮时,在编辑器中缩放字体
 | 
			
		||||
        overviewRulerBorder: false, // 不要滚动条的边框
 | 
			
		||||
        tabSize: 2, // tab 缩进长度
 | 
			
		||||
@@ -219,7 +219,7 @@ const initMonacoEditor = () => {
 | 
			
		||||
        keybindingContext: undefined,
 | 
			
		||||
        keybindings: [
 | 
			
		||||
            // chord
 | 
			
		||||
            monaco.KeyMod.chord(monaco.KeyMod.CtrlCmd | monaco.KeyCode.KeyR, 0)
 | 
			
		||||
            monaco.KeyMod.chord(monaco.KeyMod.CtrlCmd | monaco.KeyCode.KeyR, 0),
 | 
			
		||||
        ],
 | 
			
		||||
        contextMenuGroupId: 'navigation',
 | 
			
		||||
        contextMenuOrder: 1.5,
 | 
			
		||||
@@ -229,9 +229,9 @@ const initMonacoEditor = () => {
 | 
			
		||||
            try {
 | 
			
		||||
                await onRunSql();
 | 
			
		||||
            } catch (e: any) {
 | 
			
		||||
                e.message && ElMessage.error(e.message)
 | 
			
		||||
                e.message && ElMessage.error(e.message);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        },
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    // 注册快捷键:ctrl + shift + f 格式化sql
 | 
			
		||||
@@ -246,7 +246,7 @@ const initMonacoEditor = () => {
 | 
			
		||||
        keybindingContext: undefined,
 | 
			
		||||
        keybindings: [
 | 
			
		||||
            // chord
 | 
			
		||||
            monaco.KeyMod.chord(monaco.KeyMod.CtrlCmd | monaco.KeyMod.Shift | monaco.KeyCode.KeyF, 0)
 | 
			
		||||
            monaco.KeyMod.chord(monaco.KeyMod.CtrlCmd | monaco.KeyMod.Shift | monaco.KeyCode.KeyF, 0),
 | 
			
		||||
        ],
 | 
			
		||||
        contextMenuGroupId: 'navigation',
 | 
			
		||||
        contextMenuOrder: 2,
 | 
			
		||||
@@ -256,9 +256,9 @@ const initMonacoEditor = () => {
 | 
			
		||||
            try {
 | 
			
		||||
                await formatSql();
 | 
			
		||||
            } catch (e: any) {
 | 
			
		||||
                e.message && ElMessage.error(e.message)
 | 
			
		||||
                e.message && ElMessage.error(e.message);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        },
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    // 动态设置主题
 | 
			
		||||
@@ -272,18 +272,18 @@ const initMonacoEditor = () => {
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 拖拽改变sql编辑区和查询结果区高度
 | 
			
		||||
*/
 | 
			
		||||
 */
 | 
			
		||||
const onDragSetHeight = () => {
 | 
			
		||||
    document.onmousemove = (e) => {
 | 
			
		||||
        e.preventDefault();
 | 
			
		||||
        //得到鼠标拖动的宽高距离:取绝对值
 | 
			
		||||
        state.editorHeight = `${document.getElementById('MonacoTextarea-' + state.ti.key)!.offsetHeight + e.movementY}px`
 | 
			
		||||
        state.tableDataHeight -= e.movementY
 | 
			
		||||
    }
 | 
			
		||||
        state.editorHeight = `${document.getElementById('MonacoTextarea-' + state.ti.key)!.offsetHeight + e.movementY}px`;
 | 
			
		||||
        state.tableDataHeight -= e.movementY;
 | 
			
		||||
    };
 | 
			
		||||
    document.onmouseup = () => {
 | 
			
		||||
        document.onmousemove = null;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
    };
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 执行sql
 | 
			
		||||
@@ -324,11 +324,17 @@ const onRunSql = async () => {
 | 
			
		||||
 | 
			
		||||
        const colAndData: any = await state.ti.getNowDbInst().runSql(state.ti.db, sql, execRemark);
 | 
			
		||||
        if (!colAndData.res || colAndData.res.length === 0) {
 | 
			
		||||
            ElMessage.warning('未查询到结果集')
 | 
			
		||||
            ElMessage.warning('未查询到结果集');
 | 
			
		||||
        }
 | 
			
		||||
        state.execRes.data = colAndData.res;
 | 
			
		||||
        state.execRes.tableColumn = colAndData.colNames;
 | 
			
		||||
        cancelUpdateFields()
 | 
			
		||||
        // 兼容表格字段配置
 | 
			
		||||
        state.execRes.tableColumn = colAndData.colNames.map((x: any) => {
 | 
			
		||||
            return {
 | 
			
		||||
                columnName: x,
 | 
			
		||||
                show: true,
 | 
			
		||||
            };
 | 
			
		||||
        });
 | 
			
		||||
        cancelUpdateFields();
 | 
			
		||||
    } catch (e: any) {
 | 
			
		||||
        state.execRes.data = [];
 | 
			
		||||
        state.execRes.tableColumn = [];
 | 
			
		||||
@@ -364,15 +370,15 @@ const getSql = () => {
 | 
			
		||||
        return res;
 | 
			
		||||
    }
 | 
			
		||||
    // 选择选中的sql
 | 
			
		||||
    let selection = monacoEditor.getSelection()
 | 
			
		||||
    let selection = monacoEditor.getSelection();
 | 
			
		||||
    if (selection) {
 | 
			
		||||
        res = monacoEditor.getModel()?.getValueInRange(selection)
 | 
			
		||||
        res = monacoEditor.getModel()?.getValueInRange(selection);
 | 
			
		||||
    }
 | 
			
		||||
    // 整个编辑器的sql
 | 
			
		||||
    if (!res) {
 | 
			
		||||
        return monacoEditor.getModel()?.getValue()
 | 
			
		||||
        return monacoEditor.getModel()?.getValue();
 | 
			
		||||
    }
 | 
			
		||||
    return res
 | 
			
		||||
    return res;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const saveSql = async () => {
 | 
			
		||||
@@ -386,8 +392,7 @@ const saveSql = async () => {
 | 
			
		||||
            const input = await ElMessageBox.prompt('请输入SQL脚本名', 'SQL名', {
 | 
			
		||||
                confirmButtonText: '确定',
 | 
			
		||||
                cancelButtonText: '取消',
 | 
			
		||||
                inputPattern:
 | 
			
		||||
                    /\w+/,
 | 
			
		||||
                inputPattern: /\w+/,
 | 
			
		||||
                inputErrorMessage: '请输入SQL脚本名',
 | 
			
		||||
            });
 | 
			
		||||
            sqlName = input.value;
 | 
			
		||||
@@ -405,7 +410,7 @@ const saveSql = async () => {
 | 
			
		||||
 | 
			
		||||
const deleteSql = async () => {
 | 
			
		||||
    const sqlName = state.sqlName;
 | 
			
		||||
    notBlank(sqlName, "该sql内容未保存");
 | 
			
		||||
    notBlank(sqlName, '该sql内容未保存');
 | 
			
		||||
    const { dbId, db } = state.ti;
 | 
			
		||||
    try {
 | 
			
		||||
        await ElMessageBox.confirm(`确定删除【${sqlName}】该SQL内容?`, '提示', {
 | 
			
		||||
@@ -416,21 +421,21 @@ const deleteSql = async () => {
 | 
			
		||||
        await dbApi.deleteDbSql.request({ id: dbId, db: db, name: sqlName });
 | 
			
		||||
        ElMessage.success('删除成功');
 | 
			
		||||
        emits('deleteSqlSuccess', dbId, db);
 | 
			
		||||
    } catch (err) { }
 | 
			
		||||
    } catch (err) {}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 格式化sql
 | 
			
		||||
 */
 | 
			
		||||
const formatSql = () => {
 | 
			
		||||
    let selection = monacoEditor.getSelection()
 | 
			
		||||
    let selection = monacoEditor.getSelection();
 | 
			
		||||
    if (!selection) {
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
    let sql = monacoEditor.getModel()?.getValueInRange(selection)
 | 
			
		||||
    let sql = monacoEditor.getModel()?.getValueInRange(selection);
 | 
			
		||||
    // 有选中sql则格式化并替换选中sql, 否则格式化编辑器所有内容
 | 
			
		||||
    if (sql) {
 | 
			
		||||
        replaceSelection(sqlFormatter(sql), selection)
 | 
			
		||||
        replaceSelection(sqlFormatter(sql), selection);
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
    monacoEditor.getModel()?.setValue(sqlFormatter(monacoEditor.getValue()));
 | 
			
		||||
@@ -456,29 +461,29 @@ const replaceSelection = (str: string, selection: any) => {
 | 
			
		||||
        model.setValue(str);
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
    const { startLineNumber, endLineNumber, startColumn, endColumn } = selection
 | 
			
		||||
    const { startLineNumber, endLineNumber, startColumn, endColumn } = selection;
 | 
			
		||||
 | 
			
		||||
    const textBeforeSelection = model.getValueInRange({
 | 
			
		||||
        startLineNumber: 1,
 | 
			
		||||
        startColumn: 0,
 | 
			
		||||
        endLineNumber: startLineNumber,
 | 
			
		||||
        endColumn: startColumn,
 | 
			
		||||
    })
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    const textAfterSelection = model.getValueInRange({
 | 
			
		||||
        startLineNumber: endLineNumber,
 | 
			
		||||
        startColumn: endColumn,
 | 
			
		||||
        endLineNumber: model.getLineCount(),
 | 
			
		||||
        endColumn: model.getLineMaxColumn(model.getLineCount()),
 | 
			
		||||
    })
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    monacoEditor.setValue(textBeforeSelection + str + textAfterSelection)
 | 
			
		||||
    monacoEditor.focus()
 | 
			
		||||
    monacoEditor.setValue(textBeforeSelection + str + textAfterSelection);
 | 
			
		||||
    monacoEditor.focus();
 | 
			
		||||
    monacoEditor.setPosition({
 | 
			
		||||
        lineNumber: startLineNumber,
 | 
			
		||||
        column: 0,
 | 
			
		||||
    })
 | 
			
		||||
}
 | 
			
		||||
    });
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 导出当前页数据
 | 
			
		||||
@@ -486,7 +491,11 @@ const replaceSelection = (str: string, selection: any) => {
 | 
			
		||||
const exportData = () => {
 | 
			
		||||
    const dataList = state.execRes.data as any;
 | 
			
		||||
    isTrue(dataList.length > 0, '没有数据可导出');
 | 
			
		||||
    exportCsv(`数据查询导出-${dateStrFormat('yyyyMMddHHmm', new Date().toString())}`, state.execRes.tableColumn, dataList)
 | 
			
		||||
    exportCsv(
 | 
			
		||||
        `数据查询导出-${dateStrFormat('yyyyMMddHHmm', new Date().toString())}`,
 | 
			
		||||
        state.execRes.tableColumn.map((x: any) => x.columnName),
 | 
			
		||||
        dataList
 | 
			
		||||
    );
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const beforeUpload = (file: File) => {
 | 
			
		||||
@@ -505,7 +514,6 @@ const getUploadSqlFileUrl = () => {
 | 
			
		||||
    return `${config.baseApiUrl}/dbs/${state.ti.dbId}/exec-sql-file?db=${state.ti.db}`;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
const onDataSelectionChange = (datas: []) => {
 | 
			
		||||
    state.selectionDatas = datas;
 | 
			
		||||
};
 | 
			
		||||
@@ -513,7 +521,7 @@ const onDataSelectionChange = (datas: []) => {
 | 
			
		||||
const changeUpdatedField = (updatedFields: []) => {
 | 
			
		||||
    // 如果存在要更新字段,则显示提交和取消按钮
 | 
			
		||||
    state.hasUpdatedFileds = updatedFields && updatedFields.length > 0;
 | 
			
		||||
}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 执行删除数据事件
 | 
			
		||||
@@ -522,7 +530,7 @@ const onDeleteData = async () => {
 | 
			
		||||
    const deleteDatas = state.selectionDatas;
 | 
			
		||||
    isTrue(deleteDatas && deleteDatas.length > 0, '请先选择要删除的数据');
 | 
			
		||||
    const { db } = state.ti;
 | 
			
		||||
    const dbInst = state.ti.getNowDbInst()
 | 
			
		||||
    const dbInst = state.ti.getNowDbInst();
 | 
			
		||||
    const primaryKey = await dbInst.loadTableColumn(db, state.table);
 | 
			
		||||
    const primaryKeyColumnName = primaryKey.columnName;
 | 
			
		||||
    dbInst.promptExeSql(db, dbInst.genDeleteByPrimaryKeysSql(db, state.table, deleteDatas), null, () => {
 | 
			
		||||
@@ -535,12 +543,11 @@ const onDeleteData = async () => {
 | 
			
		||||
 | 
			
		||||
const submitUpdateFields = () => {
 | 
			
		||||
    dbTableRef.value.submitUpdateFields();
 | 
			
		||||
}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const cancelUpdateFields = () => {
 | 
			
		||||
    dbTableRef.value.cancelUpdateFields();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
};
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style lang="scss">
 | 
			
		||||
@@ -560,9 +567,8 @@ const cancelUpdateFields = () => {
 | 
			
		||||
    border: 1px solid #ccc;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
.update_field_active {
 | 
			
		||||
    background-color: var(--el-color-success)
 | 
			
		||||
    background-color: var(--el-color-success);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.editor-move-resize {
 | 
			
		||||
 
 | 
			
		||||
@@ -2,8 +2,29 @@
 | 
			
		||||
    <div>
 | 
			
		||||
        <el-row>
 | 
			
		||||
            <el-col :span="8">
 | 
			
		||||
                <el-link @click="onRefresh()" icon="refresh" :underline="false" class="ml5">
 | 
			
		||||
                </el-link>
 | 
			
		||||
                <el-link @click="onRefresh()" icon="refresh" :underline="false" class="ml5"> </el-link>
 | 
			
		||||
                <el-divider direction="vertical" border-style="dashed" />
 | 
			
		||||
 | 
			
		||||
                <el-popover
 | 
			
		||||
                    popper-style="max-height: 550px; overflow: auto; max-width: 450px"
 | 
			
		||||
                    placement="bottom"
 | 
			
		||||
                    width="auto"
 | 
			
		||||
                    title="表格字段配置"
 | 
			
		||||
                    trigger="click"
 | 
			
		||||
                >
 | 
			
		||||
                    <div v-for="(item, index) in columns" :key="index">
 | 
			
		||||
                        <el-checkbox
 | 
			
		||||
                            v-model="item.show"
 | 
			
		||||
                            :label="`${!item.columnComment ? item.columnName : item.columnName + ' [' + item.columnComment + ']'}`"
 | 
			
		||||
                            :true-label="true"
 | 
			
		||||
                            :false-label="false"
 | 
			
		||||
                            size="small"
 | 
			
		||||
                        />
 | 
			
		||||
                    </div>
 | 
			
		||||
                    <template #reference>
 | 
			
		||||
                        <el-link icon="Operation" size="small" :underline="false"></el-link>
 | 
			
		||||
                    </template>
 | 
			
		||||
                </el-popover>
 | 
			
		||||
                <el-divider direction="vertical" border-style="dashed" />
 | 
			
		||||
 | 
			
		||||
                <el-link @click="onShowAddDataDialog()" type="primary" icon="plus" :underline="false"></el-link>
 | 
			
		||||
@@ -13,8 +34,7 @@
 | 
			
		||||
                <el-divider direction="vertical" border-style="dashed" />
 | 
			
		||||
 | 
			
		||||
                <el-tooltip class="box-item" effect="dark" content="commit" placement="top">
 | 
			
		||||
                    <el-link @click="onCommit()" type="success" icon="CircleCheck" :underline="false">
 | 
			
		||||
                    </el-link>
 | 
			
		||||
                    <el-link @click="onCommit()" type="success" icon="CircleCheck" :underline="false"> </el-link>
 | 
			
		||||
                </el-tooltip>
 | 
			
		||||
                <el-divider direction="vertical" border-style="dashed" />
 | 
			
		||||
 | 
			
		||||
@@ -37,22 +57,31 @@
 | 
			
		||||
                </el-tooltip>
 | 
			
		||||
            </el-col>
 | 
			
		||||
            <el-col :span="16">
 | 
			
		||||
                <el-input v-model="condition" placeholder="若需条件过滤,可选择列并点击对应的字段并输入需要过滤的内容点击查询按钮即可" clearable
 | 
			
		||||
                    @clear="selectData" size="small" style="width: 100%">
 | 
			
		||||
                <el-input
 | 
			
		||||
                    v-model="condition"
 | 
			
		||||
                    placeholder="若需条件过滤,可选择列并点击对应的字段并输入需要过滤的内容点击查询按钮即可"
 | 
			
		||||
                    clearable
 | 
			
		||||
                    @clear="selectData"
 | 
			
		||||
                    size="small"
 | 
			
		||||
                    style="width: 100%"
 | 
			
		||||
                >
 | 
			
		||||
                    <template #prepend>
 | 
			
		||||
                        <el-popover trigger="click" :width="320" placement="right">
 | 
			
		||||
                            <template #reference>
 | 
			
		||||
                                <el-link type="success" :underline="false">选择列</el-link>
 | 
			
		||||
                            </template>
 | 
			
		||||
                            <el-table :data="columns" max-height="500" size="small" @row-click="
 | 
			
		||||
                                (...event: any) => {
 | 
			
		||||
                                    onConditionRowClick(event);
 | 
			
		||||
                                }
 | 
			
		||||
                            " style="cursor: pointer">
 | 
			
		||||
                                <el-table-column property="columnName" label="列名" show-overflow-tooltip>
 | 
			
		||||
                                </el-table-column>
 | 
			
		||||
                                <el-table-column property="columnComment" label="备注" show-overflow-tooltip>
 | 
			
		||||
                                </el-table-column>
 | 
			
		||||
                            <el-table
 | 
			
		||||
                                :data="columns"
 | 
			
		||||
                                max-height="500"
 | 
			
		||||
                                size="small"
 | 
			
		||||
                                @row-click="(...event: any) => {
 | 
			
		||||
                                onConditionRowClick(event);
 | 
			
		||||
                            }
 | 
			
		||||
                                "
 | 
			
		||||
                                style="cursor: pointer"
 | 
			
		||||
                            >
 | 
			
		||||
                                <el-table-column property="columnName" label="列名" show-overflow-tooltip> </el-table-column>
 | 
			
		||||
                                <el-table-column property="columnComment" label="备注" show-overflow-tooltip> </el-table-column>
 | 
			
		||||
                            </el-table>
 | 
			
		||||
                        </el-popover>
 | 
			
		||||
                    </template>
 | 
			
		||||
@@ -64,16 +93,34 @@
 | 
			
		||||
            </el-col>
 | 
			
		||||
        </el-row>
 | 
			
		||||
 | 
			
		||||
        <db-table ref="dbTableRef" :db-id="state.ti.dbId" :db="state.ti.db" :data="datas" :table="state.table"
 | 
			
		||||
            :column-names="columnNames" :loading="loading" :height="tableHeight" :show-column-tip="true"
 | 
			
		||||
            :sortable="'custom'" @sort-change="(sort: any) => onTableSortChange(sort)"
 | 
			
		||||
            @selection-change="onDataSelectionChange" @change-updated-field="changeUpdatedField"></db-table>
 | 
			
		||||
        <db-table
 | 
			
		||||
            ref="dbTableRef"
 | 
			
		||||
            :db-id="state.ti.dbId"
 | 
			
		||||
            :db="state.ti.db"
 | 
			
		||||
            :data="datas"
 | 
			
		||||
            :table="state.table"
 | 
			
		||||
            :columns="columns"
 | 
			
		||||
            :loading="loading"
 | 
			
		||||
            :height="tableHeight"
 | 
			
		||||
            :show-column-tip="true"
 | 
			
		||||
            :sortable="'custom'"
 | 
			
		||||
            @sort-change="(sort: any) => onTableSortChange(sort)"
 | 
			
		||||
            @selection-change="onDataSelectionChange"
 | 
			
		||||
            @change-updated-field="changeUpdatedField"
 | 
			
		||||
        ></db-table>
 | 
			
		||||
 | 
			
		||||
        <el-row type="flex" class="mt5" justify="center">
 | 
			
		||||
            <el-pagination small :total="count" @current-change="pageChange()" layout="prev, pager, next, total, jumper"
 | 
			
		||||
                v-model:current-page="pageNum" :page-size="DbInst.DefaultLimit"></el-pagination>
 | 
			
		||||
            <el-pagination
 | 
			
		||||
                small
 | 
			
		||||
                :total="count"
 | 
			
		||||
                @current-change="pageChange()"
 | 
			
		||||
                layout="prev, pager, next, total, jumper"
 | 
			
		||||
                v-model:current-page="pageNum"
 | 
			
		||||
                :page-size="DbInst.DefaultLimit"
 | 
			
		||||
            ></el-pagination>
 | 
			
		||||
        </el-row>
 | 
			
		||||
        <div style=" font-size: 12px; padding: 0 10px; color: #606266"><span>{{ state.sql }}</span>
 | 
			
		||||
        <div style="font-size: 12px; padding: 0 10px; color: #606266">
 | 
			
		||||
            <span>{{ state.sql }}</span>
 | 
			
		||||
        </div>
 | 
			
		||||
 | 
			
		||||
        <el-dialog v-model="conditionDialog.visible" :title="conditionDialog.title" width="420px">
 | 
			
		||||
@@ -101,15 +148,22 @@
 | 
			
		||||
        </el-dialog>
 | 
			
		||||
 | 
			
		||||
        <el-dialog v-model="addDataDialog.visible" :title="addDataDialog.title" :destroy-on-close="true" width="600px">
 | 
			
		||||
            <el-form ref="dataForm" :model="addDataDialog.data" label-width="160px" size="small">
 | 
			
		||||
                <el-form-item v-for="column in columns" class="w100" :prop="column.columnName" :label="column.columnName"
 | 
			
		||||
                    :required="column.nullable != 'YES' && column.columnKey != 'PRI'">
 | 
			
		||||
                    <el-input-number v-if="DbInst.isNumber(column.columnType)"
 | 
			
		||||
            <el-form ref="dataForm" :model="addDataDialog.data" label-width="auto" size="small">
 | 
			
		||||
                <el-form-item
 | 
			
		||||
                    v-for="column in columns"
 | 
			
		||||
                    class="w100"
 | 
			
		||||
                    :prop="column.columnName"
 | 
			
		||||
                    :label="column.columnName"
 | 
			
		||||
                    :required="column.nullable != 'YES' && column.columnKey != 'PRI'"
 | 
			
		||||
                >
 | 
			
		||||
                    <el-input-number
 | 
			
		||||
                        v-if="DbInst.isNumber(column.columnType)"
 | 
			
		||||
                        v-model="addDataDialog.data[`${column.columnName}`]"
 | 
			
		||||
                        :placeholder="`${column.columnType}  ${column.columnComment}`" class="w100" />
 | 
			
		||||
                        :placeholder="`${column.columnType}  ${column.columnComment}`"
 | 
			
		||||
                        class="w100"
 | 
			
		||||
                    />
 | 
			
		||||
 | 
			
		||||
                    <el-input v-else v-model="addDataDialog.data[`${column.columnName}`]"
 | 
			
		||||
                        :placeholder="`${column.columnType}  ${column.columnComment}`" />
 | 
			
		||||
                    <el-input v-else v-model="addDataDialog.data[`${column.columnName}`]" :placeholder="`${column.columnType}  ${column.columnComment}`" />
 | 
			
		||||
                </el-form-item>
 | 
			
		||||
            </el-form>
 | 
			
		||||
            <template #footer>
 | 
			
		||||
@@ -130,21 +184,21 @@ import { ElMessage } from 'element-plus';
 | 
			
		||||
import { DbInst, TabInfo } from '../../db';
 | 
			
		||||
import { exportCsv } from '@/common/utils/export';
 | 
			
		||||
import { dateStrFormat } from '@/common/utils/date';
 | 
			
		||||
import DbTable from '../DbTable.vue'
 | 
			
		||||
import DbTable from '../DbTable.vue';
 | 
			
		||||
 | 
			
		||||
const emits = defineEmits(['genInsertSql'])
 | 
			
		||||
const emits = defineEmits(['genInsertSql']);
 | 
			
		||||
const dataForm: any = ref(null);
 | 
			
		||||
 | 
			
		||||
const props = defineProps({
 | 
			
		||||
    data: {
 | 
			
		||||
        type: TabInfo,
 | 
			
		||||
        required: true
 | 
			
		||||
        required: true,
 | 
			
		||||
    },
 | 
			
		||||
    tableHeight: {
 | 
			
		||||
        type: [String],
 | 
			
		||||
        default: '600'
 | 
			
		||||
    }
 | 
			
		||||
})
 | 
			
		||||
        default: '600',
 | 
			
		||||
    },
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const dbTableRef = ref(null) as Ref;
 | 
			
		||||
 | 
			
		||||
@@ -156,7 +210,6 @@ const state = reactive({
 | 
			
		||||
    orderBy: '',
 | 
			
		||||
    condition: '', // 当前条件框的条件
 | 
			
		||||
    loading: false, // 是否在加载数据
 | 
			
		||||
    columnNames: [],
 | 
			
		||||
    columns: [] as any,
 | 
			
		||||
    pageNum: 1,
 | 
			
		||||
    count: 0,
 | 
			
		||||
@@ -168,7 +221,7 @@ const state = reactive({
 | 
			
		||||
        dataTab: null,
 | 
			
		||||
        visible: false,
 | 
			
		||||
        condition: '=',
 | 
			
		||||
        value: null
 | 
			
		||||
        value: null,
 | 
			
		||||
    },
 | 
			
		||||
    addDataDialog: {
 | 
			
		||||
        data: {},
 | 
			
		||||
@@ -180,46 +233,40 @@ const state = reactive({
 | 
			
		||||
    hasUpdatedFileds: false,
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const {
 | 
			
		||||
    datas,
 | 
			
		||||
    condition,
 | 
			
		||||
    loading,
 | 
			
		||||
    columns,
 | 
			
		||||
    columnNames,
 | 
			
		||||
    pageNum,
 | 
			
		||||
    count,
 | 
			
		||||
    hasUpdatedFileds,
 | 
			
		||||
    conditionDialog,
 | 
			
		||||
    addDataDialog,
 | 
			
		||||
} = toRefs(state);
 | 
			
		||||
const { datas, condition, loading, columns, pageNum, count, hasUpdatedFileds, conditionDialog, addDataDialog } = toRefs(state);
 | 
			
		||||
 | 
			
		||||
watch(() => props.tableHeight, (newValue: any) => {
 | 
			
		||||
    state.tableHeight = newValue;
 | 
			
		||||
});
 | 
			
		||||
watch(
 | 
			
		||||
    () => props.tableHeight,
 | 
			
		||||
    (newValue: any) => {
 | 
			
		||||
        state.tableHeight = newValue;
 | 
			
		||||
    }
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
onMounted(async () => {
 | 
			
		||||
    console.log('in table data mounted');
 | 
			
		||||
    state.ti = props.data;
 | 
			
		||||
    state.tableHeight = props.tableHeight;
 | 
			
		||||
    state.table = state.ti.params.table;
 | 
			
		||||
    notBlank(state.table, "TableData组件params.table信息不能为空")
 | 
			
		||||
    notBlank(state.table, 'TableData组件params.table信息不能为空');
 | 
			
		||||
 | 
			
		||||
    const columns = await state.ti.getNowDbInst().loadColumns(state.ti.db, state.table);
 | 
			
		||||
    columns.forEach((x: any) => {
 | 
			
		||||
        x.show = true;
 | 
			
		||||
    });
 | 
			
		||||
    state.columns = columns;
 | 
			
		||||
    state.columnNames = columns.map((t: any) => t.columnName);
 | 
			
		||||
    await onRefresh();
 | 
			
		||||
})
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const onRefresh = async () => {
 | 
			
		||||
    // 查询条件置空
 | 
			
		||||
    state.condition = '';
 | 
			
		||||
    state.pageNum = 1;
 | 
			
		||||
    await selectData();
 | 
			
		||||
}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
   * 数据tab修改页数
 | 
			
		||||
   */
 | 
			
		||||
 * 数据tab修改页数
 | 
			
		||||
 */
 | 
			
		||||
const pageChange = async () => {
 | 
			
		||||
    await selectData();
 | 
			
		||||
};
 | 
			
		||||
@@ -245,7 +292,7 @@ const selectData = async () => {
 | 
			
		||||
    } finally {
 | 
			
		||||
        state.loading = false;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 导出当前页数据
 | 
			
		||||
@@ -253,10 +300,15 @@ const selectData = async () => {
 | 
			
		||||
const exportData = () => {
 | 
			
		||||
    const dataList = state.datas as any;
 | 
			
		||||
    isTrue(dataList.length > 0, '没有数据可导出');
 | 
			
		||||
    exportCsv(`数据导出-${state.table}-${dateStrFormat('yyyyMMddHHmm', new Date().toString())}`, state.columnNames, dataList)
 | 
			
		||||
    let columnNames = [];
 | 
			
		||||
    for (let column of state.columns) {
 | 
			
		||||
        if (column.show) {
 | 
			
		||||
            columnNames.push(column.columnName);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    exportCsv(`数据导出-${state.table}-${dateStrFormat('yyyyMMddHHmm', new Date().toString())}`, columnNames, dataList);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 条件查询,点击列信息后显示输入对应的值
 | 
			
		||||
 */
 | 
			
		||||
@@ -302,7 +354,7 @@ const onSelectByCondition = async () => {
 | 
			
		||||
    notEmpty(state.condition, '条件不能为空');
 | 
			
		||||
    state.pageNum = 1;
 | 
			
		||||
    await selectData();
 | 
			
		||||
}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 表排序字段变更
 | 
			
		||||
@@ -323,7 +375,7 @@ const onDataSelectionChange = (datas: []) => {
 | 
			
		||||
const changeUpdatedField = (updatedFields: []) => {
 | 
			
		||||
    // 如果存在要更新字段,则显示提交和取消按钮
 | 
			
		||||
    state.hasUpdatedFileds = updatedFields && updatedFields.length > 0;
 | 
			
		||||
}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 执行删除数据事件
 | 
			
		||||
@@ -332,7 +384,7 @@ const onDeleteData = async () => {
 | 
			
		||||
    const deleteDatas = state.selectionDatas;
 | 
			
		||||
    isTrue(deleteDatas && deleteDatas.length > 0, '请先选择要删除的数据');
 | 
			
		||||
    const { db } = state.ti;
 | 
			
		||||
    const dbInst = state.ti.getNowDbInst()
 | 
			
		||||
    const dbInst = state.ti.getNowDbInst();
 | 
			
		||||
    dbInst.promptExeSql(db, dbInst.genDeleteByPrimaryKeysSql(db, state.table, deleteDatas), null, () => {
 | 
			
		||||
        onRefresh();
 | 
			
		||||
    });
 | 
			
		||||
@@ -345,21 +397,21 @@ const onGenerateInsertSql = async () => {
 | 
			
		||||
 | 
			
		||||
const submitUpdateFields = () => {
 | 
			
		||||
    dbTableRef.value.submitUpdateFields();
 | 
			
		||||
}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const cancelUpdateFields = () => {
 | 
			
		||||
    dbTableRef.value.cancelUpdateFields();
 | 
			
		||||
}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const onShowAddDataDialog = async () => {
 | 
			
		||||
    state.addDataDialog.title = `添加'${state.table}'表数据`
 | 
			
		||||
    state.addDataDialog.title = `添加'${state.table}'表数据`;
 | 
			
		||||
    state.addDataDialog.visible = true;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const closeAddDataDialog = () => {
 | 
			
		||||
    state.addDataDialog.visible = false;
 | 
			
		||||
    state.addDataDialog.data = {};
 | 
			
		||||
}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// 添加新数据行
 | 
			
		||||
const addRow = async () => {
 | 
			
		||||
@@ -369,9 +421,9 @@ const addRow = async () => {
 | 
			
		||||
            // key: 字段名,value: 字段名提示
 | 
			
		||||
            let obj: any = {};
 | 
			
		||||
            for (let item of state.columns) {
 | 
			
		||||
                const value = data[item.columnName]
 | 
			
		||||
                const value = data[item.columnName];
 | 
			
		||||
                if (!value) {
 | 
			
		||||
                    continue
 | 
			
		||||
                    continue;
 | 
			
		||||
                }
 | 
			
		||||
                obj[`${item.columnName}`] = DbInst.wrapValueByType(value);
 | 
			
		||||
            }
 | 
			
		||||
@@ -387,13 +439,11 @@ const addRow = async () => {
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style lang="scss">
 | 
			
		||||
.update_field_active {
 | 
			
		||||
    background-color: var(--el-color-success)
 | 
			
		||||
    background-color: var(--el-color-success);
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,6 @@
 | 
			
		||||
/* eslint-disable no-unused-vars */
 | 
			
		||||
import { dbApi } from './api';
 | 
			
		||||
import { getTextWidth } from '@/common/utils/string';
 | 
			
		||||
import SqlExecBox from './component/SqlExecBox';
 | 
			
		||||
 | 
			
		||||
const dbInstCache: Map<number, DbInst> = new Map();
 | 
			
		||||
@@ -8,30 +9,30 @@ export class DbInst {
 | 
			
		||||
    /**
 | 
			
		||||
     * 标签路径
 | 
			
		||||
     */
 | 
			
		||||
    tagPath: string
 | 
			
		||||
    tagPath: string;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 实例id
 | 
			
		||||
     */
 | 
			
		||||
    id: number
 | 
			
		||||
    id: number;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 实例名
 | 
			
		||||
     */
 | 
			
		||||
    name: string
 | 
			
		||||
    name: string;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 数据库类型, mysql postgres
 | 
			
		||||
     */
 | 
			
		||||
    type: string
 | 
			
		||||
    type: string;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * schema -> db
 | 
			
		||||
     */
 | 
			
		||||
    dbs: Map<string, Db> = new Map()
 | 
			
		||||
    dbs: Map<string, Db> = new Map();
 | 
			
		||||
 | 
			
		||||
    /** 数据库schema,多个用空格隔开 */
 | 
			
		||||
    databases: string
 | 
			
		||||
    databases: string;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 默认查询分页数量
 | 
			
		||||
@@ -45,9 +46,9 @@ export class DbInst {
 | 
			
		||||
     */
 | 
			
		||||
    getDb(dbName: string) {
 | 
			
		||||
        if (!dbName) {
 | 
			
		||||
            throw new Error('dbName不能为空')
 | 
			
		||||
            throw new Error('dbName不能为空');
 | 
			
		||||
        }
 | 
			
		||||
        let db = this.dbs.get(dbName)
 | 
			
		||||
        let db = this.dbs.get(dbName);
 | 
			
		||||
        if (db) {
 | 
			
		||||
            return db;
 | 
			
		||||
        }
 | 
			
		||||
@@ -120,17 +121,17 @@ export class DbInst {
 | 
			
		||||
            return db.tableHints;
 | 
			
		||||
        }
 | 
			
		||||
        console.log(`load db-hits -> dbName: ${dbName}`);
 | 
			
		||||
        const hits = await dbApi.hintTables.request({ id: this.id, db: db.name, })
 | 
			
		||||
        const hits = await dbApi.hintTables.request({ id: this.id, db: db.name });
 | 
			
		||||
        db.tableHints = hits;
 | 
			
		||||
        return hits;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
    * 执行sql
 | 
			
		||||
    *
 | 
			
		||||
    * @param sql sql
 | 
			
		||||
    * @param remark 执行备注
 | 
			
		||||
    */
 | 
			
		||||
     * 执行sql
 | 
			
		||||
     *
 | 
			
		||||
     * @param sql sql
 | 
			
		||||
     * @param remark 执行备注
 | 
			
		||||
     */
 | 
			
		||||
    async runSql(dbName: string, sql: string, remark: string = '') {
 | 
			
		||||
        return await dbApi.sqlExec.request({
 | 
			
		||||
            id: this.id,
 | 
			
		||||
@@ -174,7 +175,7 @@ export class DbInst {
 | 
			
		||||
            }
 | 
			
		||||
            sqls.push(`INSERT INTO ${table} (${colNames.join(', ')}) VALUES(${values.join(', ')})`);
 | 
			
		||||
        }
 | 
			
		||||
        return sqls.join(';\n') + ';'
 | 
			
		||||
        return sqls.join(';\n') + ';';
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
@@ -190,11 +191,13 @@ export class DbInst {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /*
 | 
			
		||||
    * 弹框提示是否执行sql
 | 
			
		||||
    */
 | 
			
		||||
     * 弹框提示是否执行sql
 | 
			
		||||
     */
 | 
			
		||||
    promptExeSql = (db: string, sql: string, cancelFunc: any = null, successFunc: any = null) => {
 | 
			
		||||
        SqlExecBox({
 | 
			
		||||
            sql, dbId: this.id, db,
 | 
			
		||||
            sql,
 | 
			
		||||
            dbId: this.id,
 | 
			
		||||
            db,
 | 
			
		||||
            runSuccessCallback: successFunc,
 | 
			
		||||
            cancelCallback: cancelFunc,
 | 
			
		||||
        });
 | 
			
		||||
@@ -207,7 +210,7 @@ export class DbInst {
 | 
			
		||||
     */
 | 
			
		||||
    static getOrNewInst(inst: any) {
 | 
			
		||||
        if (!inst) {
 | 
			
		||||
            throw new Error('inst不能为空')
 | 
			
		||||
            throw new Error('inst不能为空');
 | 
			
		||||
        }
 | 
			
		||||
        let dbInst = dbInstCache.get(inst.id);
 | 
			
		||||
        if (dbInst) {
 | 
			
		||||
@@ -226,11 +229,11 @@ export class DbInst {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
    * 获取数据库实例id,若不存在,则新建一个并缓存
 | 
			
		||||
    * @param dbId 数据库实例id
 | 
			
		||||
    * @param dbType 第一次获取时为必传项,即第一次创建时
 | 
			
		||||
    * @returns 数据库实例
 | 
			
		||||
    */
 | 
			
		||||
     * 获取数据库实例id,若不存在,则新建一个并缓存
 | 
			
		||||
     * @param dbId 数据库实例id
 | 
			
		||||
     * @param dbType 第一次获取时为必传项,即第一次创建时
 | 
			
		||||
     * @returns 数据库实例
 | 
			
		||||
     */
 | 
			
		||||
    static getInst(dbId?: number): DbInst {
 | 
			
		||||
        if (!dbId) {
 | 
			
		||||
            throw new Error('dbId不能为空');
 | 
			
		||||
@@ -250,11 +253,11 @@ export class DbInst {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
    * 获取count sql
 | 
			
		||||
    * @param table 表名
 | 
			
		||||
    * @param condition 条件
 | 
			
		||||
    * @returns count sql
 | 
			
		||||
    */
 | 
			
		||||
     * 获取count sql
 | 
			
		||||
     * @param table 表名
 | 
			
		||||
     * @param condition 条件
 | 
			
		||||
     * @returns count sql
 | 
			
		||||
     */
 | 
			
		||||
    static getDefaultCountSql = (table: string, condition?: string) => {
 | 
			
		||||
        return `SELECT COUNT(*) count FROM ${table} ${condition ? 'WHERE ' + condition : ''}`;
 | 
			
		||||
    };
 | 
			
		||||
@@ -275,14 +278,14 @@ export class DbInst {
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
    * 根据字段类型包装字段值,如为字符串等则添加‘’,数字类型则直接返回即可
 | 
			
		||||
    */
 | 
			
		||||
     * 根据字段类型包装字段值,如为字符串等则添加‘’,数字类型则直接返回即可
 | 
			
		||||
     */
 | 
			
		||||
    static wrapColumnValue(columnType: string, value: any) {
 | 
			
		||||
        if (this.isNumber(columnType)) {
 | 
			
		||||
            return value;
 | 
			
		||||
        }
 | 
			
		||||
        return `'${value}'`;
 | 
			
		||||
    };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 判断字段类型是否为数字类型
 | 
			
		||||
@@ -291,7 +294,7 @@ export class DbInst {
 | 
			
		||||
     */
 | 
			
		||||
    static isNumber(columnType: string) {
 | 
			
		||||
        return columnType.match(/int|double|float|nubmer|decimal|byte|bit/gi);
 | 
			
		||||
    };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     *
 | 
			
		||||
@@ -300,81 +303,37 @@ export class DbInst {
 | 
			
		||||
     * @param flag 标志
 | 
			
		||||
     * @returns 列宽度
 | 
			
		||||
     */
 | 
			
		||||
    static flexColumnWidth = (str: any, tableData: any, flag = 'equal') => {
 | 
			
		||||
        // str为该列的字段名(传字符串);tableData为该表格的数据源(传变量);
 | 
			
		||||
        // flag为可选值,可不传该参数,传参时可选'max'或'equal',默认为'max'
 | 
			
		||||
        // flag为'max'则设置列宽适配该列中最长的内容,flag为'equal'则设置列宽适配该列中第一行内容的长度。
 | 
			
		||||
        str = str + '';
 | 
			
		||||
        let columnContent = '';
 | 
			
		||||
        if (!tableData || !tableData.length || tableData.length === 0 || tableData === undefined) {
 | 
			
		||||
    static flexColumnWidth = (prop: any, tableData: any) => {
 | 
			
		||||
        if (!prop || !prop.length || prop.length === 0 || prop === undefined) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        if (!str || !str.length || str.length === 0 || str === undefined) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        if (flag === 'equal') {
 | 
			
		||||
            // 获取该列中第一个不为空的数据(内容)
 | 
			
		||||
            for (let i = 0; i < tableData.length; i++) {
 | 
			
		||||
                // 转为字符串后比较
 | 
			
		||||
                if ((tableData[i][str] + '').length > 0) {
 | 
			
		||||
                    columnContent = tableData[i][str] + '';
 | 
			
		||||
                    break;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        } else {
 | 
			
		||||
            // 获取该列中最长的数据(内容)
 | 
			
		||||
            let index = 0;
 | 
			
		||||
            for (let i = 0; i < tableData.length; i++) {
 | 
			
		||||
                if (tableData[i][str] === null) {
 | 
			
		||||
                    return;
 | 
			
		||||
                }
 | 
			
		||||
                const now_temp = tableData[i][str] + '';
 | 
			
		||||
                const max_temp = tableData[index][str] + '';
 | 
			
		||||
                if (now_temp.length > max_temp.length) {
 | 
			
		||||
                    index = i;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            columnContent = tableData[index][str] + '';
 | 
			
		||||
        }
 | 
			
		||||
        const contentWidth: number = DbInst.getContentWidth(columnContent);
 | 
			
		||||
        // 获取列名称的长度 加上排序图标长度
 | 
			
		||||
        const columnWidth: number = DbInst.getContentWidth(str) + 43;
 | 
			
		||||
        const flexWidth: number = contentWidth > columnWidth ? contentWidth : columnWidth;
 | 
			
		||||
        return flexWidth + 'px';
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 获取内容所需要占用的宽度
 | 
			
		||||
    */
 | 
			
		||||
    static getContentWidth = (content: any): number => {
 | 
			
		||||
        // 以下分配的单位长度可根据实际需求进行调整
 | 
			
		||||
        let flexWidth = 0;
 | 
			
		||||
        for (const char of content) {
 | 
			
		||||
            if (flexWidth > 500) {
 | 
			
		||||
                break;
 | 
			
		||||
            }
 | 
			
		||||
            if ((char >= '0' && char <= '9') || (char >= 'a' && char <= 'z')) {
 | 
			
		||||
                // 如果是小写字母、数字字符,分配8个单位宽度
 | 
			
		||||
                flexWidth += 8.5;
 | 
			
		||||
        // 获取列名称的长度 加上排序图标长度
 | 
			
		||||
        const columnWidth: number = getTextWidth(prop) + 40;
 | 
			
		||||
        // prop为该列的字段名(传字符串);tableData为该表格的数据源(传变量);
 | 
			
		||||
        if (!tableData || !tableData.length || tableData.length === 0 || tableData === undefined) {
 | 
			
		||||
            return columnWidth;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // 获取该列中最长的数据(内容)
 | 
			
		||||
        let maxWidthText = '';
 | 
			
		||||
        let maxWidthValue;
 | 
			
		||||
        // 获取该列中最长的数据(内容)
 | 
			
		||||
        for (let i = 0; i < tableData.length; i++) {
 | 
			
		||||
            let nowValue = tableData[i][prop];
 | 
			
		||||
            if (!nowValue) {
 | 
			
		||||
                continue;
 | 
			
		||||
            }
 | 
			
		||||
            if (char >= 'A' && char <= 'Z') {
 | 
			
		||||
                flexWidth += 9;
 | 
			
		||||
                continue;
 | 
			
		||||
            }
 | 
			
		||||
            if (char >= '\u4e00' && char <= '\u9fa5') {
 | 
			
		||||
                // 如果是中文字符,为字符分配16个单位宽度
 | 
			
		||||
                flexWidth += 16;
 | 
			
		||||
            } else {
 | 
			
		||||
                // 其他种类字符,为字符分配9个单位宽度
 | 
			
		||||
                flexWidth += 8;
 | 
			
		||||
            // 转为字符串比较长度
 | 
			
		||||
            let nowText = nowValue + '';
 | 
			
		||||
            if (nowText.length > maxWidthText.length) {
 | 
			
		||||
                maxWidthText = nowText;
 | 
			
		||||
                maxWidthValue = nowValue;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        if (flexWidth > 500) {
 | 
			
		||||
            // 设置最大宽度
 | 
			
		||||
            flexWidth = 500;
 | 
			
		||||
        }
 | 
			
		||||
        return flexWidth;
 | 
			
		||||
        const contentWidth: number = getTextWidth(maxWidthText) + 15;
 | 
			
		||||
        const flexWidth: number = contentWidth > columnWidth ? contentWidth : columnWidth;
 | 
			
		||||
        return flexWidth > 500 ? 500 : flexWidth;
 | 
			
		||||
    };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -382,10 +341,10 @@ export class DbInst {
 | 
			
		||||
 * 数据库实例信息
 | 
			
		||||
 */
 | 
			
		||||
class Db {
 | 
			
		||||
    name: string  // 库名
 | 
			
		||||
    tables: []   // 数据库实例表信息
 | 
			
		||||
    columnsMap: Map<string, any> = new Map  // table -> columns
 | 
			
		||||
    tableHints: any = null // 提示词
 | 
			
		||||
    name: string; // 库名
 | 
			
		||||
    tables: []; // 数据库实例表信息
 | 
			
		||||
    columnsMap: Map<string, any> = new Map(); // table -> columns
 | 
			
		||||
    tableHints: any = null; // 提示词
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 获取指定表列信息(前提需要dbInst.loadColumns)
 | 
			
		||||
@@ -396,10 +355,10 @@ class Db {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
    * 获取指定表中的指定列名信息,若列名为空则默认返回主键
 | 
			
		||||
    * @param table 表名
 | 
			
		||||
    * @param columnName 列名
 | 
			
		||||
    */
 | 
			
		||||
     * 获取指定表中的指定列名信息,若列名为空则默认返回主键
 | 
			
		||||
     * @param table 表名
 | 
			
		||||
     * @param columnName 列名
 | 
			
		||||
     */
 | 
			
		||||
    getColumn(table: string, columnName: string = '') {
 | 
			
		||||
        const cols = this.getColumns(table);
 | 
			
		||||
        if (!columnName) {
 | 
			
		||||
@@ -426,32 +385,32 @@ export class TabInfo {
 | 
			
		||||
    /**
 | 
			
		||||
     * tab唯一key。与label、name都一致
 | 
			
		||||
     */
 | 
			
		||||
    key: string
 | 
			
		||||
    key: string;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 菜单树节点key
 | 
			
		||||
     */
 | 
			
		||||
    treeNodeKey: string
 | 
			
		||||
    treeNodeKey: string;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 数据库实例id
 | 
			
		||||
     */
 | 
			
		||||
    dbId: number
 | 
			
		||||
    dbId: number;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 库名
 | 
			
		||||
     */
 | 
			
		||||
    db: string = ''
 | 
			
		||||
    db: string = '';
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * tab 类型
 | 
			
		||||
     */
 | 
			
		||||
    type: TabType
 | 
			
		||||
    type: TabType;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * tab需要的其他信息
 | 
			
		||||
     */
 | 
			
		||||
    params: any
 | 
			
		||||
    params: any;
 | 
			
		||||
 | 
			
		||||
    getNowDbInst() {
 | 
			
		||||
        return DbInst.getInst(this.dbId);
 | 
			
		||||
@@ -465,26 +424,26 @@ export class TabInfo {
 | 
			
		||||
/** 修改表字段所需数据 */
 | 
			
		||||
export type UpdateFieldsMeta = {
 | 
			
		||||
    // 主键值
 | 
			
		||||
    primaryKey: string
 | 
			
		||||
    primaryKey: string;
 | 
			
		||||
    // 主键名
 | 
			
		||||
    primaryKeyName: string
 | 
			
		||||
    primaryKeyName: string;
 | 
			
		||||
    // 主键类型
 | 
			
		||||
    primaryKeyType: string
 | 
			
		||||
    primaryKeyType: string;
 | 
			
		||||
    // 新值
 | 
			
		||||
    fields: FieldsMeta[]
 | 
			
		||||
}
 | 
			
		||||
    fields: FieldsMeta[];
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export type FieldsMeta = {
 | 
			
		||||
    // 字段所在div
 | 
			
		||||
    div: HTMLElement
 | 
			
		||||
    div: HTMLElement;
 | 
			
		||||
    // 字段名
 | 
			
		||||
    fieldName: string
 | 
			
		||||
    fieldName: string;
 | 
			
		||||
    // 字段所在的表格行数据
 | 
			
		||||
    row: any
 | 
			
		||||
    row: any;
 | 
			
		||||
    // 字段类型
 | 
			
		||||
    fieldType: string
 | 
			
		||||
    fieldType: string;
 | 
			
		||||
    // 原值
 | 
			
		||||
    oldValue: string
 | 
			
		||||
    oldValue: string;
 | 
			
		||||
    // 新值
 | 
			
		||||
    newValue: string
 | 
			
		||||
}
 | 
			
		||||
    newValue: string;
 | 
			
		||||
};
 | 
			
		||||
 
 | 
			
		||||
@@ -1,12 +1,10 @@
 | 
			
		||||
import { Enum } from '@/common/Enum'
 | 
			
		||||
import { EnumValue } from '@/common/Enum';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 枚举类
 | 
			
		||||
 */
 | 
			
		||||
export default {
 | 
			
		||||
    // 数据库sql执行类型
 | 
			
		||||
    DbSqlExecTypeEnum: new Enum().add('UPDATE', 'UPDATE', 1)
 | 
			
		||||
        .add('DELETE', 'DELETE', 2)
 | 
			
		||||
        .add('INSERT', 'INSERT', 3)
 | 
			
		||||
        .add('QUERY', 'QUERY', 4),
 | 
			
		||||
}
 | 
			
		||||
// 数据库sql执行类型
 | 
			
		||||
export const DbSqlExecTypeEnum = {
 | 
			
		||||
    Update: EnumValue.of(1, 'UPDATE').setTagColor('#E4F5EB'),
 | 
			
		||||
    Delete: EnumValue.of(2, 'DELETE').setTagColor('#F9E2AE'),
 | 
			
		||||
    Insert: EnumValue.of(3, 'INSERT').setTagColor('#A8DEE0'),
 | 
			
		||||
    Query: EnumValue.of(4, 'QUERY').setTagColor('#A8DEE0'),
 | 
			
		||||
    Other: EnumValue.of(-1, 'OTHER').setTagColor('#F9E2AE'),
 | 
			
		||||
};
 | 
			
		||||
 
 | 
			
		||||
@@ -1 +1 @@
 | 
			
		||||
export { default } from './SqlExec.vue';
 | 
			
		||||
export { default } from './SqlExec.vue';
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,99 @@
 | 
			
		||||
export const TYPE_LIST = ['bigint', 'binary', 'blob', 'char', 'datetime', 'date', 'decimal', 'double', 'enum', 'float', 'int', 'json', 'longblob', 'longtext', 'mediumblob', 'mediumtext', 'set', 'smallint', 'text', 'time', 'timestamp', 'tinyint', 'varbinary', 'varchar']
 | 
			
		||||
export const TYPE_LIST = [
 | 
			
		||||
    'bigint',
 | 
			
		||||
    'binary',
 | 
			
		||||
    'blob',
 | 
			
		||||
    'char',
 | 
			
		||||
    'datetime',
 | 
			
		||||
    'date',
 | 
			
		||||
    'decimal',
 | 
			
		||||
    'double',
 | 
			
		||||
    'enum',
 | 
			
		||||
    'float',
 | 
			
		||||
    'int',
 | 
			
		||||
    'json',
 | 
			
		||||
    'longblob',
 | 
			
		||||
    'longtext',
 | 
			
		||||
    'mediumblob',
 | 
			
		||||
    'mediumtext',
 | 
			
		||||
    'set',
 | 
			
		||||
    'smallint',
 | 
			
		||||
    'text',
 | 
			
		||||
    'time',
 | 
			
		||||
    'timestamp',
 | 
			
		||||
    'tinyint',
 | 
			
		||||
    'varbinary',
 | 
			
		||||
    'varchar',
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
export const CHARACTER_SET_NAME_LIST = ['armscii8', 'ascii', 'big5', 'binary', 'cp1250', 'cp1251', 'cp1256', 'cp1257', 'cp850', 'cp852', 'cp866', 'cp932', 'dec8', 'eucjpms', 'euckr', 'gb18030', 'gb2312', 'gbk', 'geostd8', 'greek', 'hebrew', 'hp8', 'keybcs2', 'koi8r', 'koi8u', 'latin1', 'latin2', 'latin5', 'latin7', 'macce', 'macroman', 'sjis', 'swe7', 'tis620', 'ucs2', 'ujis', 'utf16', 'utf16le', 'utf32', 'utf8', 'utf8mb4']
 | 
			
		||||
export const CHARACTER_SET_NAME_LIST = [
 | 
			
		||||
    'armscii8',
 | 
			
		||||
    'ascii',
 | 
			
		||||
    'big5',
 | 
			
		||||
    'binary',
 | 
			
		||||
    'cp1250',
 | 
			
		||||
    'cp1251',
 | 
			
		||||
    'cp1256',
 | 
			
		||||
    'cp1257',
 | 
			
		||||
    'cp850',
 | 
			
		||||
    'cp852',
 | 
			
		||||
    'cp866',
 | 
			
		||||
    'cp932',
 | 
			
		||||
    'dec8',
 | 
			
		||||
    'eucjpms',
 | 
			
		||||
    'euckr',
 | 
			
		||||
    'gb18030',
 | 
			
		||||
    'gb2312',
 | 
			
		||||
    'gbk',
 | 
			
		||||
    'geostd8',
 | 
			
		||||
    'greek',
 | 
			
		||||
    'hebrew',
 | 
			
		||||
    'hp8',
 | 
			
		||||
    'keybcs2',
 | 
			
		||||
    'koi8r',
 | 
			
		||||
    'koi8u',
 | 
			
		||||
    'latin1',
 | 
			
		||||
    'latin2',
 | 
			
		||||
    'latin5',
 | 
			
		||||
    'latin7',
 | 
			
		||||
    'macce',
 | 
			
		||||
    'macroman',
 | 
			
		||||
    'sjis',
 | 
			
		||||
    'swe7',
 | 
			
		||||
    'tis620',
 | 
			
		||||
    'ucs2',
 | 
			
		||||
    'ujis',
 | 
			
		||||
    'utf16',
 | 
			
		||||
    'utf16le',
 | 
			
		||||
    'utf32',
 | 
			
		||||
    'utf8',
 | 
			
		||||
    'utf8mb4',
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
export const COLLATION_SUFFIX_LIST = ['unicode_ci', 'bin', 'croatian_ci', 'czech_ci', 'danish_ci', 'esperanto_ci', 'estonian_ci', 'general_ci', 'german2_ci', 'hungarian_ci', 'icelandic_ci', 'latvian_ci', 'lithuanian_ci', 'persian_ci', 'polish_ci', 'roman_ci', 'romanian_ci', 'sinhala_ci', 'slovak_ci', 'slovenian_ci', 'spanish2_ci', 'spanish_ci', 'swedish_ci', 'turkish_ci', 'unicode_520_ci', 'vietnamese_ci']
 | 
			
		||||
export const COLLATION_SUFFIX_LIST = [
 | 
			
		||||
    'unicode_ci',
 | 
			
		||||
    'bin',
 | 
			
		||||
    'croatian_ci',
 | 
			
		||||
    'czech_ci',
 | 
			
		||||
    'danish_ci',
 | 
			
		||||
    'esperanto_ci',
 | 
			
		||||
    'estonian_ci',
 | 
			
		||||
    'general_ci',
 | 
			
		||||
    'german2_ci',
 | 
			
		||||
    'hungarian_ci',
 | 
			
		||||
    'icelandic_ci',
 | 
			
		||||
    'latvian_ci',
 | 
			
		||||
    'lithuanian_ci',
 | 
			
		||||
    'persian_ci',
 | 
			
		||||
    'polish_ci',
 | 
			
		||||
    'roman_ci',
 | 
			
		||||
    'romanian_ci',
 | 
			
		||||
    'sinhala_ci',
 | 
			
		||||
    'slovak_ci',
 | 
			
		||||
    'slovenian_ci',
 | 
			
		||||
    'spanish2_ci',
 | 
			
		||||
    'spanish_ci',
 | 
			
		||||
    'swedish_ci',
 | 
			
		||||
    'turkish_ci',
 | 
			
		||||
    'unicode_520_ci',
 | 
			
		||||
    'vietnamese_ci',
 | 
			
		||||
];
 | 
			
		||||
 
 | 
			
		||||
@@ -1,98 +1,109 @@
 | 
			
		||||
<template>
 | 
			
		||||
    <div class="file-manage">
 | 
			
		||||
        <el-dialog :title="title" v-model="dialogVisible" :show-close="true" :before-close="handleClose" width="800px">
 | 
			
		||||
        <el-dialog :title="title" v-model="dialogVisible" :show-close="true" :before-close="handleClose" width="50%">
 | 
			
		||||
            <div class="toolbar">
 | 
			
		||||
                <div style="float: right">
 | 
			
		||||
                    <el-button v-auth="'machine:file:add'" type="primary" @click="add" icon="plus" size="small" plain>添加
 | 
			
		||||
                    </el-button>
 | 
			
		||||
                    <el-button v-auth="'machine:file:add'" type="primary" @click="add" icon="plus" plain>添加 </el-button>
 | 
			
		||||
                </div>
 | 
			
		||||
            </div>
 | 
			
		||||
            <el-table :data="fileTable" stripe style="width: 100%">
 | 
			
		||||
                <el-table-column prop="name" label="名称" width>
 | 
			
		||||
            <el-table :data="fileTable" stripe style="width: 100%" v-loading="loading">
 | 
			
		||||
                <el-table-column prop="name" label="名称" min-width="70px">
 | 
			
		||||
                    <template #default="scope">
 | 
			
		||||
                        <el-input v-model="scope.row.name" size="small" :disabled="scope.row.id != null" clearable>
 | 
			
		||||
                        </el-input>
 | 
			
		||||
                        <el-input v-model="scope.row.name" :disabled="scope.row.id != null" clearable> </el-input>
 | 
			
		||||
                    </template>
 | 
			
		||||
                </el-table-column>
 | 
			
		||||
                <el-table-column prop="name" label="类型" min-width="50px">
 | 
			
		||||
                <el-table-column prop="name" label="类型" width="130px">
 | 
			
		||||
                    <template #default="scope">
 | 
			
		||||
                        <el-select :disabled="scope.row.id != null" size="small" v-model="scope.row.type"
 | 
			
		||||
                            style="width: 100px" placeholder="请选择">
 | 
			
		||||
                            <el-option v-for="item in enums.FileTypeEnum as any" :key="item.value" :label="item.label"
 | 
			
		||||
                                :value="item.value"></el-option>
 | 
			
		||||
                        <el-select :disabled="scope.row.id != null" v-model="scope.row.type" style="width: 100px" placeholder="请选择">
 | 
			
		||||
                            <el-option v-for="item in FileTypeEnum as any" :key="item.value" :label="item.label" :value="item.value"></el-option>
 | 
			
		||||
                        </el-select>
 | 
			
		||||
                    </template>
 | 
			
		||||
                </el-table-column>
 | 
			
		||||
                <el-table-column prop="path" label="路径" width>
 | 
			
		||||
                <el-table-column prop="path" label="路径" min-width="150px" show-overflow-tooltip>
 | 
			
		||||
                    <template #default="scope">
 | 
			
		||||
                        <el-input v-model="scope.row.path" :disabled="scope.row.id != null" size="small" clearable>
 | 
			
		||||
                        </el-input>
 | 
			
		||||
                        <el-input v-model="scope.row.path" :disabled="scope.row.id != null" clearable> </el-input>
 | 
			
		||||
                    </template>
 | 
			
		||||
                </el-table-column>
 | 
			
		||||
                <el-table-column label="操作" width>
 | 
			
		||||
                <el-table-column label="操作" min-wdith="180px">
 | 
			
		||||
                    <template #default="scope">
 | 
			
		||||
                        <el-button v-if="scope.row.id == null" @click="addFiles(scope.row)" type="success"
 | 
			
		||||
                            icon="success-filled" size="small" plain>确定</el-button>
 | 
			
		||||
                        <el-button v-if="scope.row.id != null" @click="getConf(scope.row)" type="primary" icon="tickets"
 | 
			
		||||
                            size="small" plain>查看</el-button>
 | 
			
		||||
                        <el-button v-auth="'machine:file:del'" type="danger" @click="deleteRow(scope.$index, scope.row)"
 | 
			
		||||
                            icon="delete" size="small" 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-auth="'machine:file:del'" type="danger" @click="deleteRow(scope.$index, scope.row)" icon="delete" plain></el-button>
 | 
			
		||||
                    </template>
 | 
			
		||||
                </el-table-column>
 | 
			
		||||
            </el-table>
 | 
			
		||||
            <el-row style="margin-top: 10px" type="flex" justify="end">
 | 
			
		||||
                <el-pagination small style="text-align: center" :total="total" layout="prev, pager, next, total, jumper"
 | 
			
		||||
                    v-model:current-page="query.pageNum" :page-size="query.pageSize" @current-change="handlePageChange">
 | 
			
		||||
                <el-pagination
 | 
			
		||||
                    style="text-align: center"
 | 
			
		||||
                    :total="total"
 | 
			
		||||
                    layout="prev, pager, next, total, jumper"
 | 
			
		||||
                    v-model:current-page="query.pageNum"
 | 
			
		||||
                    :page-size="query.pageSize"
 | 
			
		||||
                    @current-change="handlePageChange"
 | 
			
		||||
                >
 | 
			
		||||
                </el-pagination>
 | 
			
		||||
            </el-row>
 | 
			
		||||
        </el-dialog>
 | 
			
		||||
 | 
			
		||||
        <el-dialog :title="tree.title" v-model="tree.visible" :close-on-click-modal="false" width="70%">
 | 
			
		||||
            <el-progress v-if="uploadProgressShow" style="width: 90%; margin-left: 20px" :text-inside="true"
 | 
			
		||||
                :stroke-width="20" :percentage="progressNum" />
 | 
			
		||||
            <div style="height: 45vh; overflow: auto">
 | 
			
		||||
                <el-tree v-if="tree.visible" ref="fileTree" :highlight-current="true" :load="loadNode"
 | 
			
		||||
                    :props="treeProps" lazy node-key="id" :expand-on-click-node="true">
 | 
			
		||||
            <el-progress v-if="uploadProgressShow" style="width: 90%; margin-left: 20px" :text-inside="true" :stroke-width="20" :percentage="progressNum" />
 | 
			
		||||
            <div style="height: 55vh; overflow: auto">
 | 
			
		||||
                <el-tree
 | 
			
		||||
                    v-if="tree.visible"
 | 
			
		||||
                    ref="fileTree"
 | 
			
		||||
                    :highlight-current="true"
 | 
			
		||||
                    :load="loadNode"
 | 
			
		||||
                    :props="treeProps"
 | 
			
		||||
                    lazy
 | 
			
		||||
                    node-key="id"
 | 
			
		||||
                    :expand-on-click-node="false"
 | 
			
		||||
                >
 | 
			
		||||
                    <template #default="{ node, data }">
 | 
			
		||||
                        <span class="custom-tree-node">
 | 
			
		||||
                            <el-dropdown size="small" @visible-change="getFilePath(data, $event)" trigger="contextmenu">
 | 
			
		||||
                                <span class="el-dropdown-link">
 | 
			
		||||
                                    <span v-if="data.type == 'd' && !node.expanded">
 | 
			
		||||
                                        <SvgIcon name="folder" />
 | 
			
		||||
                                        <SvgIcon :size="15" name="folder" />
 | 
			
		||||
                                    </span>
 | 
			
		||||
                                    <span v-if="data.type == 'd' && node.expanded">
 | 
			
		||||
                                        <SvgIcon name="folder-opened" />
 | 
			
		||||
                                        <SvgIcon :size="15" name="folder-opened" />
 | 
			
		||||
                                    </span>
 | 
			
		||||
                                    <span v-if="data.type == '-'">
 | 
			
		||||
                                        <SvgIcon name="document" />
 | 
			
		||||
                                        <SvgIcon :size="15" name="document" />
 | 
			
		||||
                                    </span>
 | 
			
		||||
 | 
			
		||||
                                    <span>
 | 
			
		||||
                                    <span class="ml5" style="font-weight: bold">
 | 
			
		||||
                                        {{ node.label }}
 | 
			
		||||
                                    </span>
 | 
			
		||||
                                </span>
 | 
			
		||||
 | 
			
		||||
                                <template #dropdown>
 | 
			
		||||
                                    <el-dropdown-menu>
 | 
			
		||||
                                        <el-dropdown-item @click="getFileContent(tree.folder.id, data.path)"
 | 
			
		||||
                                            v-if="data.type == '-' && data.size < 1 * 1024 * 1024">
 | 
			
		||||
                                        <el-dropdown-item
 | 
			
		||||
                                            @click="getFileContent(tree.folder.id, data.path)"
 | 
			
		||||
                                            v-if="data.type == '-' && data.size < 1 * 1024 * 1024"
 | 
			
		||||
                                        >
 | 
			
		||||
                                            <el-link type="info" icon="view" :underline="false">查看</el-link>
 | 
			
		||||
                                        </el-dropdown-item>
 | 
			
		||||
 | 
			
		||||
                                        <span v-auth="'machine:file:write'">
 | 
			
		||||
                                            <el-dropdown-item @click="showCreateFileDialog(node)"
 | 
			
		||||
                                                v-if="data.type == 'd'">
 | 
			
		||||
                                                <el-link type="primary" icon="document" :underline="false"
 | 
			
		||||
                                                    style="margin-left: 2px">新建</el-link>
 | 
			
		||||
                                            <el-dropdown-item @click="showCreateFileDialog(node)" v-if="data.type == 'd'">
 | 
			
		||||
                                                <el-link type="primary" icon="document" :underline="false" style="margin-left: 2px">新建</el-link>
 | 
			
		||||
                                            </el-dropdown-item>
 | 
			
		||||
                                        </span>
 | 
			
		||||
 | 
			
		||||
                                        <span v-auth="'machine:file:upload'">
 | 
			
		||||
                                            <el-dropdown-item v-if="data.type == 'd'">
 | 
			
		||||
                                                <el-upload :before-upload="beforeUpload" :on-success="uploadSuccess"
 | 
			
		||||
                                                    action="" :http-request="getUploadFile" :headers="{ token }"
 | 
			
		||||
                                                    :show-file-list="false" name="file"
 | 
			
		||||
                                                    style="display: inline-block; margin-left: 2px">
 | 
			
		||||
                                                <el-upload
 | 
			
		||||
                                                    :before-upload="beforeUpload"
 | 
			
		||||
                                                    :on-success="uploadSuccess"
 | 
			
		||||
                                                    action=""
 | 
			
		||||
                                                    :http-request="getUploadFile"
 | 
			
		||||
                                                    :headers="{ token }"
 | 
			
		||||
                                                    :show-file-list="false"
 | 
			
		||||
                                                    name="file"
 | 
			
		||||
                                                    style="display: inline-block; margin-left: 2px"
 | 
			
		||||
                                                >
 | 
			
		||||
                                                    <el-link icon="upload" :underline="false">上传</el-link>
 | 
			
		||||
                                                </el-upload>
 | 
			
		||||
                                            </el-dropdown-item>
 | 
			
		||||
@@ -100,25 +111,33 @@
 | 
			
		||||
 | 
			
		||||
                                        <span v-auth="'machine:file:write'">
 | 
			
		||||
                                            <el-dropdown-item @click="downloadFile(node, data)" v-if="data.type == '-'">
 | 
			
		||||
                                                <el-link type="primary" icon="download" :underline="false"
 | 
			
		||||
                                                    style="margin-left: 2px">下载</el-link>
 | 
			
		||||
                                                <el-link type="primary" icon="download" :underline="false" style="margin-left: 2px">下载</el-link>
 | 
			
		||||
                                            </el-dropdown-item>
 | 
			
		||||
                                        </span>
 | 
			
		||||
 | 
			
		||||
                                        <span v-auth="'machine:file:rm'">
 | 
			
		||||
                                            <el-dropdown-item @click="deleteFile(node, data)" v-if="!dontOperate(data)">
 | 
			
		||||
                                                <el-link type="danger" icon="delete" :underline="false"
 | 
			
		||||
                                                    style="margin-left: 2px">删除</el-link>
 | 
			
		||||
                                                <el-link type="danger" icon="delete" :underline="false" style="margin-left: 2px">删除</el-link>
 | 
			
		||||
                                            </el-dropdown-item>
 | 
			
		||||
                                        </span>
 | 
			
		||||
                                    </el-dropdown-menu>
 | 
			
		||||
                                </template>
 | 
			
		||||
                            </el-dropdown>
 | 
			
		||||
                            <span style="display: inline-block" class="ml15">
 | 
			
		||||
                                <span style="color: #67c23a" v-if="data.type == '-'">[{{ formatFileSize(data.size)
 | 
			
		||||
                                }}]</span>
 | 
			
		||||
                                <span v-if="data.mode" style="color: #67c23a"> [{{ data.mode }} {{ data.modTime
 | 
			
		||||
                                }}]</span>
 | 
			
		||||
                                <span style="color: #67c23a; font-weight: bold" v-if="data.type == '-'"> [{{ formatFileSize(data.size) }}] </span>
 | 
			
		||||
                                <span style="color: #67c23a; font-weight: bold" v-if="data.type == 'd' && data.dirSize"> [{{ data.dirSize }}] </span>
 | 
			
		||||
                                <span style="color: #67c23a; font-weight: bold" v-if="data.type == 'd' && !data.dirSize">
 | 
			
		||||
                                    [<el-button @click="getDirSize(data)" type="primary" link :loading="data.loadingDirSize">size</el-button>]
 | 
			
		||||
                                </span>
 | 
			
		||||
 | 
			
		||||
                                <el-popover placement="top-start" :title="`${data.path}-文件详情`" :width="520" trigger="click" @show="showFileStat(data)">
 | 
			
		||||
                                    <template #reference>
 | 
			
		||||
                                        <span style="color: #67c23a; font-weight: bold">
 | 
			
		||||
                                            [<el-button @click="showFileStat(data)" type="primary" link :loading="data.loadingStat">stat</el-button>]
 | 
			
		||||
                                        </span>
 | 
			
		||||
                                    </template>
 | 
			
		||||
                                    <el-input :input-style="{ color: 'black' }" disabled autosize v-model="data.stat" type="textarea" />
 | 
			
		||||
                                </el-popover>
 | 
			
		||||
                            </span>
 | 
			
		||||
                        </span>
 | 
			
		||||
                    </template>
 | 
			
		||||
@@ -126,8 +145,15 @@
 | 
			
		||||
            </div>
 | 
			
		||||
        </el-dialog>
 | 
			
		||||
 | 
			
		||||
        <el-dialog :destroy-on-close="true" title="新建文件" v-model="createFileDialog.visible"
 | 
			
		||||
            :before-close="closeCreateFileDialog" :close-on-click-modal="false" top="5vh" width="400px">
 | 
			
		||||
        <el-dialog
 | 
			
		||||
            :destroy-on-close="true"
 | 
			
		||||
            title="新建文件"
 | 
			
		||||
            v-model="createFileDialog.visible"
 | 
			
		||||
            :before-close="closeCreateFileDialog"
 | 
			
		||||
            :close-on-click-modal="false"
 | 
			
		||||
            top="5vh"
 | 
			
		||||
            width="400px"
 | 
			
		||||
        >
 | 
			
		||||
            <div>
 | 
			
		||||
                <el-form-item prop="name" label="名称:">
 | 
			
		||||
                    <el-input v-model.trim="createFileDialog.name" placeholder="请输入名称" auto-complete="off"></el-input>
 | 
			
		||||
@@ -148,8 +174,14 @@
 | 
			
		||||
            </template>
 | 
			
		||||
        </el-dialog>
 | 
			
		||||
 | 
			
		||||
        <el-dialog :destroy-on-close="true" :title="fileContent.dialogTitle" v-model="fileContent.contentVisible" :close-on-click-modal="false"
 | 
			
		||||
            top="5vh" width="70%">
 | 
			
		||||
        <el-dialog
 | 
			
		||||
            :destroy-on-close="true"
 | 
			
		||||
            :title="fileContent.dialogTitle"
 | 
			
		||||
            v-model="fileContent.contentVisible"
 | 
			
		||||
            :close-on-click-modal="false"
 | 
			
		||||
            top="5vh"
 | 
			
		||||
            width="70%"
 | 
			
		||||
        >
 | 
			
		||||
            <div>
 | 
			
		||||
                <monaco-editor :can-change-mode="true" v-model="fileContent.content" :language="fileContent.type" />
 | 
			
		||||
            </div>
 | 
			
		||||
@@ -171,7 +203,7 @@ import { machineApi } from './api';
 | 
			
		||||
 | 
			
		||||
import MonacoEditor from '@/components/monaco/MonacoEditor.vue';
 | 
			
		||||
import { getSession } from '@/common/utils/storage';
 | 
			
		||||
import enums from './enums';
 | 
			
		||||
import { FileTypeEnum } from './enums';
 | 
			
		||||
import config from '@/common/config';
 | 
			
		||||
import { isTrue } from '@/common/assert';
 | 
			
		||||
 | 
			
		||||
@@ -179,15 +211,15 @@ const props = defineProps({
 | 
			
		||||
    visible: { type: Boolean },
 | 
			
		||||
    machineId: { type: Number },
 | 
			
		||||
    title: { type: String },
 | 
			
		||||
})
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const emit = defineEmits(['update:visible', 'cancel', 'update:machineId'])
 | 
			
		||||
const emit = defineEmits(['update:visible', 'cancel', 'update:machineId']);
 | 
			
		||||
 | 
			
		||||
const treeProps = {
 | 
			
		||||
    label: 'name',
 | 
			
		||||
    children: 'zones',
 | 
			
		||||
    isLeaf: 'leaf',
 | 
			
		||||
}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const addFile = machineApi.addConf;
 | 
			
		||||
const delFile = machineApi.delConf;
 | 
			
		||||
@@ -206,6 +238,7 @@ const state = reactive({
 | 
			
		||||
        pageNum: 1,
 | 
			
		||||
        pageSize: 8,
 | 
			
		||||
    },
 | 
			
		||||
    loading: false,
 | 
			
		||||
    form: {
 | 
			
		||||
        id: null,
 | 
			
		||||
        type: null,
 | 
			
		||||
@@ -248,30 +281,25 @@ const state = reactive({
 | 
			
		||||
    file: null as any,
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const {
 | 
			
		||||
    dialogVisible,
 | 
			
		||||
    query,
 | 
			
		||||
    total,
 | 
			
		||||
    fileTable,
 | 
			
		||||
    fileContent,
 | 
			
		||||
    tree,
 | 
			
		||||
    progressNum,
 | 
			
		||||
    uploadProgressShow,
 | 
			
		||||
    createFileDialog,
 | 
			
		||||
} = toRefs(state)
 | 
			
		||||
const { dialogVisible, loading, query, total, fileTable, fileContent, tree, progressNum, uploadProgressShow, createFileDialog } = toRefs(state);
 | 
			
		||||
 | 
			
		||||
watch(props, async (newValue) => {
 | 
			
		||||
    state.dialogVisible = newValue.visible;
 | 
			
		||||
    if (newValue.machineId && newValue.visible) {
 | 
			
		||||
        await getFiles();
 | 
			
		||||
    }
 | 
			
		||||
    state.dialogVisible = newValue.visible;
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const getFiles = async () => {
 | 
			
		||||
    state.query.id = props.machineId as any;
 | 
			
		||||
    const res = await files.request(state.query);
 | 
			
		||||
    state.fileTable = res.list;
 | 
			
		||||
    state.total = res.total;
 | 
			
		||||
    try {
 | 
			
		||||
        state.loading = true;
 | 
			
		||||
        state.query.id = props.machineId as any;
 | 
			
		||||
        const res = await files.request(state.query);
 | 
			
		||||
        state.fileTable = res.list || [];
 | 
			
		||||
        state.total = res.total;
 | 
			
		||||
    } finally {
 | 
			
		||||
        state.loading = false;
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const handlePageChange = (curPage: number) => {
 | 
			
		||||
@@ -438,6 +466,37 @@ const loadNode = async (node: any, resolve: any) => {
 | 
			
		||||
    return resolve(res);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const getDirSize = async (data: any) => {
 | 
			
		||||
    try {
 | 
			
		||||
        data.loadingDirSize = true;
 | 
			
		||||
        const res = await machineApi.dirSize.request({
 | 
			
		||||
            machineId: props.machineId,
 | 
			
		||||
            fileId: state.tree.folder.id,
 | 
			
		||||
            path: data.path,
 | 
			
		||||
        });
 | 
			
		||||
        data.dirSize = res;
 | 
			
		||||
    } finally {
 | 
			
		||||
        data.loadingDirSize = false;
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const showFileStat = async (data: any) => {
 | 
			
		||||
    try {
 | 
			
		||||
        if (data.stat) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        data.loadingStat = true;
 | 
			
		||||
        const res = await machineApi.fileStat.request({
 | 
			
		||||
            machineId: props.machineId,
 | 
			
		||||
            fileId: state.tree.folder.id,
 | 
			
		||||
            path: data.path,
 | 
			
		||||
        });
 | 
			
		||||
        data.stat = res;
 | 
			
		||||
    } finally {
 | 
			
		||||
        data.loadingStat = false;
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const showCreateFileDialog = (node: any) => {
 | 
			
		||||
    isTrue(node.expanded, '请先点击展开该节点后再创建');
 | 
			
		||||
    state.createFileDialog.node = node;
 | 
			
		||||
@@ -492,10 +551,7 @@ const deleteFile = (node: any, data: any) => {
 | 
			
		||||
 | 
			
		||||
const downloadFile = (node: any, data: any) => {
 | 
			
		||||
    const a = document.createElement('a');
 | 
			
		||||
    a.setAttribute(
 | 
			
		||||
        'href',
 | 
			
		||||
        `${config.baseApiUrl}/machines/${props.machineId}/files/${state.tree.folder.id}/read?type=1&path=${data.path}&token=${token}`
 | 
			
		||||
    );
 | 
			
		||||
    a.setAttribute('href', `${config.baseApiUrl}/machines/${props.machineId}/files/${state.tree.folder.id}/read?type=1&path=${data.path}&token=${token}`);
 | 
			
		||||
    a.click();
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@@ -547,24 +603,7 @@ const getFilePath = (data: object, visible: boolean) => {
 | 
			
		||||
};
 | 
			
		||||
const dontOperate = (data: any) => {
 | 
			
		||||
    const path = data.path;
 | 
			
		||||
    const ls = [
 | 
			
		||||
        '/',
 | 
			
		||||
        '//',
 | 
			
		||||
        '/usr',
 | 
			
		||||
        '/usr/',
 | 
			
		||||
        '/usr/bin',
 | 
			
		||||
        '/opt',
 | 
			
		||||
        '/run',
 | 
			
		||||
        '/etc',
 | 
			
		||||
        '/proc',
 | 
			
		||||
        '/var',
 | 
			
		||||
        '/mnt',
 | 
			
		||||
        '/boot',
 | 
			
		||||
        '/dev',
 | 
			
		||||
        '/home',
 | 
			
		||||
        '/media',
 | 
			
		||||
        '/root',
 | 
			
		||||
    ];
 | 
			
		||||
    const ls = ['/', '//', '/usr', '/usr/', '/usr/bin', '/opt', '/run', '/etc', '/proc', '/var', '/mnt', '/boot', '/dev', '/home', '/media', '/root'];
 | 
			
		||||
    return ls.indexOf(path) != -1;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@@ -589,6 +628,4 @@ const formatFileSize = (size: any) => {
 | 
			
		||||
    return '-';
 | 
			
		||||
};
 | 
			
		||||
</script>
 | 
			
		||||
<style lang="scss">
 | 
			
		||||
 | 
			
		||||
</style>
 | 
			
		||||
<style lang="scss"></style>
 | 
			
		||||
 
 | 
			
		||||
@@ -1,24 +1,18 @@
 | 
			
		||||
<template>
 | 
			
		||||
    <div>
 | 
			
		||||
        <el-dialog :title="title" v-model="dialogVisible" :close-on-click-modal="false" :destroy-on-close="true"
 | 
			
		||||
            :before-close="cancel" width="650px">
 | 
			
		||||
            <el-form :model="form" ref="machineForm" :rules="rules" label-width="85px">
 | 
			
		||||
        <el-dialog :title="title" v-model="dialogVisible" :close-on-click-modal="false" :destroy-on-close="true" :before-close="cancel" width="650px">
 | 
			
		||||
            <el-form :model="form" ref="machineForm" :rules="rules" label-width="auto">
 | 
			
		||||
                <el-tabs v-model="tabActiveName">
 | 
			
		||||
                    <el-tab-pane label="基础信息" name="basic">
 | 
			
		||||
                        <el-form-item prop="tagId" label="标签:" required :rules="{
 | 
			
		||||
                            required: true,
 | 
			
		||||
                            message: '请选择标签',
 | 
			
		||||
                            trigger: ['change', 'blur'],
 | 
			
		||||
                        }">
 | 
			
		||||
                            <tag-select v-model:tag-id="form.tagId" v-model:tag-path="form.tagPath" style="width: 100%" />
 | 
			
		||||
                        <el-form-item prop="tagId" label="标签:">
 | 
			
		||||
                            <tag-select v-model="form.tagId" v-model:tag-path="form.tagPath" style="width: 100%" />
 | 
			
		||||
                        </el-form-item>
 | 
			
		||||
                        <el-form-item prop="name" label="名称:" required>
 | 
			
		||||
                            <el-input v-model.trim="form.name" placeholder="请输入机器别名" auto-complete="off"></el-input>
 | 
			
		||||
                        </el-form-item>
 | 
			
		||||
                        <el-form-item prop="ip" label="ip:" required>
 | 
			
		||||
                            <el-col :span="18">
 | 
			
		||||
                                <el-input :disabled="form.id" v-model.trim="form.ip" placeholder="主机ip" auto-complete="off">
 | 
			
		||||
                                </el-input>
 | 
			
		||||
                                <el-input :disabled="form.id" v-model.trim="form.ip" placeholder="主机ip" auto-complete="off"> </el-input>
 | 
			
		||||
                            </el-col>
 | 
			
		||||
                            <el-col style="text-align: center" :span="1">:</el-col>
 | 
			
		||||
                            <el-col :span="5">
 | 
			
		||||
@@ -27,20 +21,17 @@
 | 
			
		||||
                        </el-form-item>
 | 
			
		||||
 | 
			
		||||
                        <el-form-item prop="username" label="用户名:">
 | 
			
		||||
                            <el-input v-model.trim="form.username" placeholder="请输授权用户名" autocomplete="new-password">
 | 
			
		||||
                            </el-input>
 | 
			
		||||
                            <el-input v-model.trim="form.username" placeholder="请输授权用户名" autocomplete="new-password"> </el-input>
 | 
			
		||||
                        </el-form-item>
 | 
			
		||||
 | 
			
		||||
                        <el-form-item label="认证方式:">
 | 
			
		||||
                            <el-select @change="changeAuthMethod" style="width: 100%" v-model="state.authType"
 | 
			
		||||
                                placeholder="请选认证方式">
 | 
			
		||||
                            <el-select @change="changeAuthMethod" style="width: 100%" v-model="state.authType" placeholder="请选认证方式">
 | 
			
		||||
                                <el-option key="1" label="密码" :value="1"> </el-option>
 | 
			
		||||
                                <el-option key="2" label="授权凭证" :value="2"> </el-option>
 | 
			
		||||
                            </el-select>
 | 
			
		||||
                        </el-form-item>
 | 
			
		||||
                        <el-form-item v-if="state.authType == 1" prop="password" label="密码:">
 | 
			
		||||
                            <el-input type="password" show-password v-model.trim="form.password" placeholder="请输入密码"
 | 
			
		||||
                                autocomplete="new-password">
 | 
			
		||||
                            <el-input type="password" show-password v-model.trim="form.password" placeholder="请输入密码" autocomplete="new-password">
 | 
			
		||||
                            </el-input>
 | 
			
		||||
                        </el-form-item>
 | 
			
		||||
 | 
			
		||||
@@ -82,7 +73,7 @@ import { machineApi } from './api';
 | 
			
		||||
import { ElMessage } from 'element-plus';
 | 
			
		||||
import TagSelect from '../component/TagSelect.vue';
 | 
			
		||||
import SshTunnelSelect from '../component/SshTunnelSelect.vue';
 | 
			
		||||
import AuthCertSelect from './authcert/AuthCertSelect.vue'
 | 
			
		||||
import AuthCertSelect from './authcert/AuthCertSelect.vue';
 | 
			
		||||
 | 
			
		||||
const props = defineProps({
 | 
			
		||||
    visible: {
 | 
			
		||||
@@ -94,12 +85,19 @@ const props = defineProps({
 | 
			
		||||
    title: {
 | 
			
		||||
        type: String,
 | 
			
		||||
    },
 | 
			
		||||
})
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
//定义事件
 | 
			
		||||
const emit = defineEmits(['update:visible', 'cancel', 'val-change'])
 | 
			
		||||
const emit = defineEmits(['update:visible', 'cancel', 'val-change']);
 | 
			
		||||
 | 
			
		||||
const rules = {
 | 
			
		||||
    tagId: [
 | 
			
		||||
        {
 | 
			
		||||
            required: true,
 | 
			
		||||
            message: '请选择标签',
 | 
			
		||||
            trigger: ['blur', 'change'],
 | 
			
		||||
        },
 | 
			
		||||
    ],
 | 
			
		||||
    name: [
 | 
			
		||||
        {
 | 
			
		||||
            required: true,
 | 
			
		||||
@@ -135,7 +133,7 @@ const rules = {
 | 
			
		||||
            trigger: ['change', 'blur'],
 | 
			
		||||
        },
 | 
			
		||||
    ],
 | 
			
		||||
}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const machineForm: any = ref(null);
 | 
			
		||||
const authCertSelectRef: any = ref(null);
 | 
			
		||||
@@ -165,13 +163,7 @@ const state = reactive({
 | 
			
		||||
    btnLoading: false,
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const {
 | 
			
		||||
    dialogVisible,
 | 
			
		||||
    tabActiveName,
 | 
			
		||||
    form,
 | 
			
		||||
    testConnBtnLoading,
 | 
			
		||||
    btnLoading,
 | 
			
		||||
} = toRefs(state)
 | 
			
		||||
const { dialogVisible, tabActiveName, form, testConnBtnLoading, btnLoading } = toRefs(state);
 | 
			
		||||
 | 
			
		||||
watch(props, async (newValue: any) => {
 | 
			
		||||
    state.dialogVisible = newValue.visible;
 | 
			
		||||
@@ -182,7 +174,7 @@ watch(props, async (newValue: any) => {
 | 
			
		||||
    if (newValue.machine) {
 | 
			
		||||
        state.form = { ...newValue.machine };
 | 
			
		||||
        // 如果凭证类型为公共的,则表示使用授权凭证认证
 | 
			
		||||
        const authCertId = (state.form as any).authCertId
 | 
			
		||||
        const authCertId = (state.form as any).authCertId;
 | 
			
		||||
        if (authCertId > 0) {
 | 
			
		||||
            state.authType = 2;
 | 
			
		||||
        } else {
 | 
			
		||||
@@ -202,7 +194,7 @@ const changeAuthMethod = (val: any) => {
 | 
			
		||||
            state.form.password = '';
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const testConn = async () => {
 | 
			
		||||
    machineForm.value.validate(async (valid: boolean) => {
 | 
			
		||||
@@ -223,7 +215,7 @@ const testConn = async () => {
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
    });
 | 
			
		||||
}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const btnOk = async () => {
 | 
			
		||||
    machineForm.value.validate(async (valid: boolean) => {
 | 
			
		||||
@@ -250,16 +242,15 @@ const btnOk = async () => {
 | 
			
		||||
 | 
			
		||||
const getReqForm = () => {
 | 
			
		||||
    const reqForm: any = { ...state.form };
 | 
			
		||||
    debugger
 | 
			
		||||
    // 如果为密码认证,则置空授权凭证id
 | 
			
		||||
    if (state.authType == 1) {
 | 
			
		||||
        reqForm.authCertId = -1;
 | 
			
		||||
    }
 | 
			
		||||
    if (!state.form.sshTunnelMachineId || state.form.sshTunnelMachineId <= 0) {
 | 
			
		||||
        reqForm.sshTunnelMachineId = -1
 | 
			
		||||
        reqForm.sshTunnelMachineId = -1;
 | 
			
		||||
    }
 | 
			
		||||
    return reqForm
 | 
			
		||||
}
 | 
			
		||||
    return reqForm;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const cancel = () => {
 | 
			
		||||
    emit('update:visible', false);
 | 
			
		||||
 
 | 
			
		||||
@@ -1,127 +1,102 @@
 | 
			
		||||
<template>
 | 
			
		||||
    <div>
 | 
			
		||||
        <el-card>
 | 
			
		||||
            <div>
 | 
			
		||||
                <el-button v-auth="'machine:add'" type="primary" icon="plus" @click="openFormDialog(false)" plain>添加
 | 
			
		||||
                </el-button>
 | 
			
		||||
                <el-button v-auth="'machine:update'" type="primary" icon="edit" :disabled="!currentId"
 | 
			
		||||
                    @click="openFormDialog(currentData)" plain>编辑</el-button>
 | 
			
		||||
                <el-button v-auth="'machine:del'" :disabled="!currentId" @click="deleteMachine(currentId)" type="danger"
 | 
			
		||||
                    icon="delete">删除</el-button>
 | 
			
		||||
                <div style="float: right">
 | 
			
		||||
                    <el-select @focus="getTags" v-model="params.tagPath" placeholder="请选择标签" @clear="search" filterable
 | 
			
		||||
                        clearable>
 | 
			
		||||
                        <el-option v-for="item in tags" :key="item" :label="item" :value="item"> </el-option>
 | 
			
		||||
                    </el-select>
 | 
			
		||||
                    <el-input class="ml5" placeholder="请输入名称" style="width: 150px" v-model="params.name" @clear="search"
 | 
			
		||||
                        plain clearable></el-input>
 | 
			
		||||
                    <el-input class="ml5" placeholder="请输入ip" style="width: 150px" v-model="params.ip" @clear="search" plain
 | 
			
		||||
                        clearable></el-input>
 | 
			
		||||
                    <el-button class="ml5" @click="search" type="success" icon="search"></el-button>
 | 
			
		||||
                </div>
 | 
			
		||||
            </div>
 | 
			
		||||
        <page-table
 | 
			
		||||
            ref="pageTableRef"
 | 
			
		||||
            :query="queryConfig"
 | 
			
		||||
            v-model:query-form="params"
 | 
			
		||||
            :show-selection="true"
 | 
			
		||||
            v-model:selection-data="state.selectionData"
 | 
			
		||||
            :data="data.list"
 | 
			
		||||
            :columns="columns"
 | 
			
		||||
            :total="data.total"
 | 
			
		||||
            v-model:page-size="params.pageSize"
 | 
			
		||||
            v-model:page-num="params.pageNum"
 | 
			
		||||
            @pageChange="search()"
 | 
			
		||||
        >
 | 
			
		||||
            <template #tagPathSelect>
 | 
			
		||||
                <el-select @focus="getTags" v-model="params.tagPath" placeholder="请选择标签" @clear="search" filterable clearable style="width: 200px">
 | 
			
		||||
                    <el-option v-for="item in tags" :key="item" :label="item" :value="item"> </el-option>
 | 
			
		||||
                </el-select>
 | 
			
		||||
            </template>
 | 
			
		||||
 | 
			
		||||
            <el-table :data="data.list" stripe style="width: 100%" @current-change="choose">
 | 
			
		||||
                <el-table-column label="选择" width="55px">
 | 
			
		||||
                    <template #default="scope">
 | 
			
		||||
                        <el-radio v-model="currentId" :label="scope.row.id">
 | 
			
		||||
                            <i></i>
 | 
			
		||||
                        </el-radio>
 | 
			
		||||
            <template #queryRight>
 | 
			
		||||
                <el-button v-auth="perms.addMachine" type="primary" icon="plus" @click="openFormDialog(false)" plain>添加 </el-button>
 | 
			
		||||
                <el-button v-auth="perms.delMachine" :disabled="selectionData.length < 1" @click="deleteMachine()" type="danger" icon="delete">删除</el-button>
 | 
			
		||||
            </template>
 | 
			
		||||
 | 
			
		||||
            <template #tagPath="{ data }">
 | 
			
		||||
                <tag-info :tag-path="data.tagPath" />
 | 
			
		||||
                <span class="ml5">
 | 
			
		||||
                    {{ data.tagPath }}
 | 
			
		||||
                </span>
 | 
			
		||||
            </template>
 | 
			
		||||
 | 
			
		||||
            <template #ipPort="{ data }">
 | 
			
		||||
                <el-link :disabled="data.status == -1" @click="showMachineStats(data)" type="primary" :underline="false">
 | 
			
		||||
                    {{ `${data.ip}:${data.port}` }}
 | 
			
		||||
                </el-link>
 | 
			
		||||
            </template>
 | 
			
		||||
 | 
			
		||||
            <template #status="{ data }">
 | 
			
		||||
                <el-switch
 | 
			
		||||
                    v-auth:disabled="'machine:update'"
 | 
			
		||||
                    :width="52"
 | 
			
		||||
                    v-model="data.status"
 | 
			
		||||
                    :active-value="1"
 | 
			
		||||
                    :inactive-value="-1"
 | 
			
		||||
                    inline-prompt
 | 
			
		||||
                    active-text="启用"
 | 
			
		||||
                    inactive-text="停用"
 | 
			
		||||
                    style="--el-switch-on-color: #13ce66; --el-switch-off-color: #ff4949"
 | 
			
		||||
                    @change="changeStatus(data)"
 | 
			
		||||
                ></el-switch>
 | 
			
		||||
            </template>
 | 
			
		||||
 | 
			
		||||
            <template #action="{ data }">
 | 
			
		||||
                <span v-auth="'machine:terminal'">
 | 
			
		||||
                    <el-button :disabled="data.status == -1" type="primary" @click="showTerminal(data)" link>终端</el-button>
 | 
			
		||||
                    <el-divider direction="vertical" border-style="dashed" />
 | 
			
		||||
                </span>
 | 
			
		||||
 | 
			
		||||
                <span v-auth="'machine:file'">
 | 
			
		||||
                    <el-button type="success" :disabled="data.status == -1" @click="showFileManage(data)" link>文件</el-button>
 | 
			
		||||
                    <el-divider direction="vertical" border-style="dashed" />
 | 
			
		||||
                </span>
 | 
			
		||||
 | 
			
		||||
                <el-button :disabled="data.status == -1" type="warning" @click="serviceManager(data)" link>脚本</el-button>
 | 
			
		||||
                <el-divider direction="vertical" border-style="dashed" />
 | 
			
		||||
 | 
			
		||||
                <el-dropdown @command="handleCommand">
 | 
			
		||||
                    <span class="el-dropdown-link-machine-list">
 | 
			
		||||
                        更多
 | 
			
		||||
                        <el-icon class="el-icon--right">
 | 
			
		||||
                            <arrow-down />
 | 
			
		||||
                        </el-icon>
 | 
			
		||||
                    </span>
 | 
			
		||||
                    <template #dropdown>
 | 
			
		||||
                        <el-dropdown-menu>
 | 
			
		||||
                            <el-dropdown-item :command="{ type: 'detail', data }"> 详情 </el-dropdown-item>
 | 
			
		||||
 | 
			
		||||
                            <el-dropdown-item :command="{ type: 'edit', data }" v-if="actionBtns[perms.updateMachine]"> 编辑 </el-dropdown-item>
 | 
			
		||||
 | 
			
		||||
                            <el-dropdown-item :command="{ type: 'process', data }" :disabled="data.status == -1"> 进程 </el-dropdown-item>
 | 
			
		||||
 | 
			
		||||
                            <el-dropdown-item :command="{ type: 'terminalRec', data }" v-if="actionBtns[perms.updateMachine] && data.enableRecorder == 1">
 | 
			
		||||
                                终端回放
 | 
			
		||||
                            </el-dropdown-item>
 | 
			
		||||
 | 
			
		||||
                            <el-dropdown-item
 | 
			
		||||
                                :command="{ type: 'closeCli', data }"
 | 
			
		||||
                                v-if="actionBtns[perms.closeCli]"
 | 
			
		||||
                                :disabled="!data.hasCli || data.status == -1"
 | 
			
		||||
                            >
 | 
			
		||||
                                关闭连接
 | 
			
		||||
                            </el-dropdown-item>
 | 
			
		||||
                        </el-dropdown-menu>
 | 
			
		||||
                    </template>
 | 
			
		||||
                </el-table-column>
 | 
			
		||||
                <el-table-column prop="tagPath" label="标签路径" min-width="150" show-overflow-tooltip>
 | 
			
		||||
                    <template #default="scope">
 | 
			
		||||
                        <tag-info :tag-path="scope.row.tagPath" />
 | 
			
		||||
                        <span class="ml5">
 | 
			
		||||
                            {{ scope.row.tagPath }}
 | 
			
		||||
                        </span>
 | 
			
		||||
                    </template>
 | 
			
		||||
                </el-table-column>
 | 
			
		||||
                <el-table-column prop="name" label="名称" min-width="140" show-overflow-tooltip></el-table-column>
 | 
			
		||||
 | 
			
		||||
                <el-table-column prop="ip" label="ip:port" min-width="150">
 | 
			
		||||
                    <template #default="scope">
 | 
			
		||||
                        <el-link :disabled="scope.row.status == -1" @click="showMachineStats(scope.row)" type="primary"
 | 
			
		||||
                            :underline="false">
 | 
			
		||||
                            {{ `${scope.row.ip}:${scope.row.port}` }}
 | 
			
		||||
                        </el-link>
 | 
			
		||||
                    </template>
 | 
			
		||||
                </el-table-column>
 | 
			
		||||
 | 
			
		||||
                <el-table-column prop="username" label="用户名" min-width="100">
 | 
			
		||||
                </el-table-column>
 | 
			
		||||
 | 
			
		||||
                <el-table-column prop="status" label="状态" min-width="80">
 | 
			
		||||
                    <template #default="scope">
 | 
			
		||||
                        <el-switch v-auth:disabled="'machine:update'" :width="52" v-model="scope.row.status"
 | 
			
		||||
                            :active-value="1" :inactive-value="-1" inline-prompt active-text="启用" inactive-text="停用"
 | 
			
		||||
                            style="--el-switch-on-color: #13ce66; --el-switch-off-color: #ff4949"
 | 
			
		||||
                            @change="changeStatus(scope.row)"></el-switch>
 | 
			
		||||
                    </template>
 | 
			
		||||
                </el-table-column>
 | 
			
		||||
 | 
			
		||||
                <el-table-column prop="remark" label="备注" min-width="250" show-overflow-tooltip></el-table-column>
 | 
			
		||||
 | 
			
		||||
                <el-table-column label="操作" min-width="235" fixed="right">
 | 
			
		||||
                    <template #default="scope">
 | 
			
		||||
                        <span v-auth="'machine:terminal'">
 | 
			
		||||
                            <el-link :disabled="scope.row.status == -1" type="primary" @click="showTerminal(scope.row)"
 | 
			
		||||
                                plain size="small" :underline="false">终端</el-link>
 | 
			
		||||
                            <el-divider direction="vertical" border-style="dashed" />
 | 
			
		||||
                        </span>
 | 
			
		||||
 | 
			
		||||
                        <span v-auth="'machine:file'">
 | 
			
		||||
                            <el-link type="success" :disabled="scope.row.status == -1" @click="showFileManage(scope.row)"
 | 
			
		||||
                                plain size="small" :underline="false">文件</el-link>
 | 
			
		||||
                            <el-divider direction="vertical" border-style="dashed" />
 | 
			
		||||
                        </span>
 | 
			
		||||
 | 
			
		||||
                        <el-link :disabled="scope.row.status == -1" type="warning" @click="serviceManager(scope.row)" plain
 | 
			
		||||
                            size="small" :underline="false">脚本</el-link>
 | 
			
		||||
                        <el-divider direction="vertical" border-style="dashed" />
 | 
			
		||||
 | 
			
		||||
                        <el-dropdown>
 | 
			
		||||
                            <span class="el-dropdown-link-machine-list">
 | 
			
		||||
                                更多
 | 
			
		||||
                                <el-icon class="el-icon--right">
 | 
			
		||||
                                    <arrow-down />
 | 
			
		||||
                                </el-icon>
 | 
			
		||||
                            </span>
 | 
			
		||||
                            <template #dropdown>
 | 
			
		||||
                                <el-dropdown-menu>
 | 
			
		||||
                                    <el-dropdown-item>
 | 
			
		||||
                                        <el-link @click="showInfo(scope.row)" plain :underline="false" size="small">详情
 | 
			
		||||
                                        </el-link>
 | 
			
		||||
                                    </el-dropdown-item>
 | 
			
		||||
 | 
			
		||||
                                    <el-dropdown-item>
 | 
			
		||||
                                        <el-link @click="showProcess(scope.row)" :disabled="scope.row.status == -1" plain
 | 
			
		||||
                                            :underline="false" size="small">进程</el-link>
 | 
			
		||||
                                    </el-dropdown-item>
 | 
			
		||||
 | 
			
		||||
                                    <el-dropdown-item v-if="scope.row.enableRecorder == 1">
 | 
			
		||||
                                        <el-link v-auth="'machine:update'" @click="showRec(scope.row)" plain
 | 
			
		||||
                                            :underline="false" size="small">终端回放</el-link>
 | 
			
		||||
                                    </el-dropdown-item>
 | 
			
		||||
 | 
			
		||||
                                    <el-dropdown-item>
 | 
			
		||||
                                        <el-link v-auth="'machine:close-cli'"
 | 
			
		||||
                                            :disabled="!scope.row.hasCli || scope.row.status == -1" type="danger"
 | 
			
		||||
                                            @click="closeCli(scope.row)" plain size="small" :underline="false">关闭连接
 | 
			
		||||
                                        </el-link>
 | 
			
		||||
                                    </el-dropdown-item>
 | 
			
		||||
                                </el-dropdown-menu>
 | 
			
		||||
                            </template>
 | 
			
		||||
                        </el-dropdown>
 | 
			
		||||
                    </template>
 | 
			
		||||
                </el-table-column>
 | 
			
		||||
            </el-table>
 | 
			
		||||
            <el-row style="margin-top: 20px" type="flex" justify="end">
 | 
			
		||||
                <el-pagination style="text-align: right" :total="data.total" layout="prev, pager, next, total, jumper"
 | 
			
		||||
                    v-model:current-page="params.pageNum" :page-size="params.pageSize"
 | 
			
		||||
                    @current-change="handlePageChange"></el-pagination>
 | 
			
		||||
            </el-row>
 | 
			
		||||
        </el-card>
 | 
			
		||||
                </el-dropdown>
 | 
			
		||||
            </template>
 | 
			
		||||
        </page-table>
 | 
			
		||||
 | 
			
		||||
        <el-dialog v-model="infoDialog.visible">
 | 
			
		||||
            <el-descriptions title="详情" :column="3" border>
 | 
			
		||||
@@ -140,50 +115,46 @@
 | 
			
		||||
 | 
			
		||||
                <el-descriptions-item :span="3" label="备注">{{ infoDialog.data.remark }}</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="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="2" label="创建时间">{{ dateFormat(infoDialog.data.createTime) }}
 | 
			
		||||
                </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="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>
 | 
			
		||||
 | 
			
		||||
        <machine-edit :title="machineEditDialog.title" v-model:visible="machineEditDialog.visible"
 | 
			
		||||
            v-model:machine="machineEditDialog.data" @valChange="submitSuccess"></machine-edit>
 | 
			
		||||
        <machine-edit
 | 
			
		||||
            :title="machineEditDialog.title"
 | 
			
		||||
            v-model:visible="machineEditDialog.visible"
 | 
			
		||||
            v-model:machine="machineEditDialog.data"
 | 
			
		||||
            @valChange="submitSuccess"
 | 
			
		||||
        ></machine-edit>
 | 
			
		||||
 | 
			
		||||
        <process-list v-model:visible="processDialog.visible" v-model:machineId="processDialog.machineId" />
 | 
			
		||||
 | 
			
		||||
        <script-manage :title="serviceDialog.title" v-model:visible="serviceDialog.visible"
 | 
			
		||||
            v-model:machineId="serviceDialog.machineId" />
 | 
			
		||||
        <script-manage :title="serviceDialog.title" v-model:visible="serviceDialog.visible" v-model:machineId="serviceDialog.machineId" />
 | 
			
		||||
 | 
			
		||||
        <file-manage :title="fileDialog.title" v-model:visible="fileDialog.visible"
 | 
			
		||||
            v-model:machineId="fileDialog.machineId" />
 | 
			
		||||
        <file-manage :title="fileDialog.title" v-model:visible="fileDialog.visible" v-model:machineId="fileDialog.machineId" />
 | 
			
		||||
 | 
			
		||||
        <machine-stats v-model:visible="machineStatsDialog.visible" :machineId="machineStatsDialog.machineId"
 | 
			
		||||
            :title="machineStatsDialog.title"></machine-stats>
 | 
			
		||||
        <machine-stats v-model:visible="machineStatsDialog.visible" :machineId="machineStatsDialog.machineId" :title="machineStatsDialog.title"></machine-stats>
 | 
			
		||||
 | 
			
		||||
        <machine-rec v-model:visible="machineRecDialog.visible" :machineId="machineRecDialog.machineId"
 | 
			
		||||
            :title="machineRecDialog.title"></machine-rec>
 | 
			
		||||
        <machine-rec v-model:visible="machineRecDialog.visible" :machineId="machineRecDialog.machineId" :title="machineRecDialog.title"></machine-rec>
 | 
			
		||||
    </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script lang="ts" setup>
 | 
			
		||||
import { toRefs, reactive, onMounted, defineAsyncComponent } from 'vue';
 | 
			
		||||
import { ref, toRefs, reactive, onMounted, defineAsyncComponent } from 'vue';
 | 
			
		||||
import { useRouter } from 'vue-router';
 | 
			
		||||
import { ElMessage, ElMessageBox } from 'element-plus';
 | 
			
		||||
import { machineApi } from './api';
 | 
			
		||||
import { tagApi } from '../tag/api';
 | 
			
		||||
import { dateFormat } from '@/common/utils/date';
 | 
			
		||||
import TagInfo from '../component/TagInfo.vue';
 | 
			
		||||
import PageTable from '@/components/pagetable/PageTable.vue';
 | 
			
		||||
import { TableColumn, TableQuery } from '@/components/pagetable';
 | 
			
		||||
import { hasPerms } from '@/components/auth/auth';
 | 
			
		||||
 | 
			
		||||
// 组件
 | 
			
		||||
const MachineEdit = defineAsyncComponent(() => import('./MachineEdit.vue'));
 | 
			
		||||
@@ -194,6 +165,30 @@ const MachineRec = defineAsyncComponent(() => import('./MachineRec.vue'));
 | 
			
		||||
const ProcessList = defineAsyncComponent(() => import('./ProcessList.vue'));
 | 
			
		||||
 | 
			
		||||
const router = useRouter();
 | 
			
		||||
const pageTableRef: any = ref(null);
 | 
			
		||||
 | 
			
		||||
const perms = {
 | 
			
		||||
    addMachine: 'machine:add',
 | 
			
		||||
    updateMachine: 'machine:update',
 | 
			
		||||
    delMachine: 'machine:del',
 | 
			
		||||
    terminal: 'machine:terminal',
 | 
			
		||||
    closeCli: 'machine:close-cli',
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const queryConfig = [TableQuery.slot('tagPath', '标签', 'tagPathSelect'), TableQuery.text('ip', 'IP'), TableQuery.text('name', '名称')];
 | 
			
		||||
 | 
			
		||||
const columns = ref([
 | 
			
		||||
    TableColumn.new('tagPath', '标签路径').isSlot().setAddWidth(20),
 | 
			
		||||
    TableColumn.new('name', '名称'),
 | 
			
		||||
    TableColumn.new('ipPort', 'ip:port').isSlot().setAddWidth(35),
 | 
			
		||||
    TableColumn.new('username', '用户名'),
 | 
			
		||||
    TableColumn.new('status', '状态').isSlot().setMinWidth(85),
 | 
			
		||||
    TableColumn.new('remark', '备注'),
 | 
			
		||||
    TableColumn.new('action', '操作').isSlot().setMinWidth(238).fixedRight().alignCenter(),
 | 
			
		||||
]);
 | 
			
		||||
// 该用户拥有的的操作列按钮权限,使用v-if进行判断,v-auth对el-dropdown-item无效
 | 
			
		||||
const actionBtns = hasPerms([perms.updateMachine, perms.closeCli]);
 | 
			
		||||
 | 
			
		||||
const state = reactive({
 | 
			
		||||
    tags: [] as any,
 | 
			
		||||
    params: {
 | 
			
		||||
@@ -212,9 +207,8 @@ const state = reactive({
 | 
			
		||||
        visible: false,
 | 
			
		||||
        data: null as any,
 | 
			
		||||
    },
 | 
			
		||||
    // 当前选中数据id
 | 
			
		||||
    currentId: 0,
 | 
			
		||||
    currentData: null,
 | 
			
		||||
    // 当前选中数据
 | 
			
		||||
    selectionData: [],
 | 
			
		||||
    serviceDialog: {
 | 
			
		||||
        visible: false,
 | 
			
		||||
        machineId: 0,
 | 
			
		||||
@@ -247,31 +241,38 @@ const state = reactive({
 | 
			
		||||
    },
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const {
 | 
			
		||||
    tags,
 | 
			
		||||
    params,
 | 
			
		||||
    data,
 | 
			
		||||
    infoDialog,
 | 
			
		||||
    currentId,
 | 
			
		||||
    currentData,
 | 
			
		||||
    serviceDialog,
 | 
			
		||||
    processDialog,
 | 
			
		||||
    fileDialog,
 | 
			
		||||
    machineStatsDialog,
 | 
			
		||||
    machineEditDialog,
 | 
			
		||||
    machineRecDialog,
 | 
			
		||||
} = toRefs(state)
 | 
			
		||||
const { tags, params, data, infoDialog, selectionData, serviceDialog, processDialog, fileDialog, machineStatsDialog, machineEditDialog, machineRecDialog } =
 | 
			
		||||
    toRefs(state);
 | 
			
		||||
 | 
			
		||||
onMounted(async () => {
 | 
			
		||||
    search();
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const choose = (item: any) => {
 | 
			
		||||
    if (!item) {
 | 
			
		||||
        return;
 | 
			
		||||
const handleCommand = (commond: any) => {
 | 
			
		||||
    const data = commond.data;
 | 
			
		||||
    const type = commond.type;
 | 
			
		||||
    switch (type) {
 | 
			
		||||
        case 'detail': {
 | 
			
		||||
            showInfo(data);
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        case 'edit': {
 | 
			
		||||
            openFormDialog(data);
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        case 'process': {
 | 
			
		||||
            showProcess(data);
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        case 'terminalRec': {
 | 
			
		||||
            showRec(data);
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        case 'closeCli': {
 | 
			
		||||
            closeCli(data);
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    state.currentId = item.id;
 | 
			
		||||
    state.currentData = item;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const showTerminal = (row: any) => {
 | 
			
		||||
@@ -297,13 +298,13 @@ const closeCli = async (row: any) => {
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const getTags = async () => {
 | 
			
		||||
    state.tags = await tagApi.getAccountTags.request(null);
 | 
			
		||||
    state.tags = await machineApi.tagList.request(null);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const openFormDialog = async (machine: any) => {
 | 
			
		||||
    let dialogTitle;
 | 
			
		||||
    if (machine) {
 | 
			
		||||
        state.machineEditDialog.data = state.currentData as any;
 | 
			
		||||
        state.machineEditDialog.data = machine;
 | 
			
		||||
        dialogTitle = '编辑机器';
 | 
			
		||||
    } else {
 | 
			
		||||
        state.machineEditDialog.data = null;
 | 
			
		||||
@@ -314,19 +315,21 @@ const openFormDialog = async (machine: any) => {
 | 
			
		||||
    state.machineEditDialog.visible = true;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const deleteMachine = async (id: number) => {
 | 
			
		||||
const deleteMachine = async () => {
 | 
			
		||||
    try {
 | 
			
		||||
        await ElMessageBox.confirm(`确定删除该机器信息? 该操作将同时删除脚本及文件配置信息`, '提示', {
 | 
			
		||||
            confirmButtonText: '确定',
 | 
			
		||||
            cancelButtonText: '取消',
 | 
			
		||||
            type: 'warning',
 | 
			
		||||
        });
 | 
			
		||||
        await machineApi.del.request({ id });
 | 
			
		||||
        await ElMessageBox.confirm(
 | 
			
		||||
            `确定删除【${state.selectionData.map((x: any) => x.name).join(', ')}】机器信息? 该操作将同时删除脚本及文件配置信息`,
 | 
			
		||||
            '提示',
 | 
			
		||||
            {
 | 
			
		||||
                confirmButtonText: '确定',
 | 
			
		||||
                cancelButtonText: '取消',
 | 
			
		||||
                type: 'warning',
 | 
			
		||||
            }
 | 
			
		||||
        );
 | 
			
		||||
        await machineApi.del.request({ id: state.selectionData.map((x: any) => x.id).join(',') });
 | 
			
		||||
        ElMessage.success('操作成功');
 | 
			
		||||
        state.currentId = 0;
 | 
			
		||||
        state.currentData = null;
 | 
			
		||||
        search();
 | 
			
		||||
    } catch (err) { }
 | 
			
		||||
    } catch (err) {}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const serviceManager = (row: any) => {
 | 
			
		||||
@@ -339,6 +342,9 @@ const serviceManager = (row: any) => {
 | 
			
		||||
 * 调整机器状态
 | 
			
		||||
 */
 | 
			
		||||
const changeStatus = async (row: any) => {
 | 
			
		||||
    if (!row.id) {
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
    await machineApi.changeStatus.request({ id: row.id, status: row.status });
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@@ -352,31 +358,29 @@ const showMachineStats = async (machine: any) => {
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const submitSuccess = () => {
 | 
			
		||||
    state.currentId = 0;
 | 
			
		||||
    state.currentData = null;
 | 
			
		||||
    search();
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const showFileManage = (currentData: any) => {
 | 
			
		||||
const showFileManage = (selectionData: any) => {
 | 
			
		||||
    state.fileDialog.visible = true;
 | 
			
		||||
    state.fileDialog.machineId = currentData.id;
 | 
			
		||||
    state.fileDialog.title = `${currentData.name} => ${currentData.ip}`;
 | 
			
		||||
    state.fileDialog.machineId = selectionData.id;
 | 
			
		||||
    state.fileDialog.title = `${selectionData.name} => ${selectionData.ip}`;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const search = async () => {
 | 
			
		||||
    const res = await machineApi.list.request(state.params);
 | 
			
		||||
    state.data = res;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const handlePageChange = (curPage: number) => {
 | 
			
		||||
    state.params.pageNum = curPage;
 | 
			
		||||
    search();
 | 
			
		||||
    try {
 | 
			
		||||
        pageTableRef.value.loading(true);
 | 
			
		||||
        const res = await machineApi.list.request(state.params);
 | 
			
		||||
        state.data = res;
 | 
			
		||||
    } finally {
 | 
			
		||||
        pageTableRef.value.loading(false);
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const showInfo = (info: any) => {
 | 
			
		||||
    state.infoDialog.data = info;
 | 
			
		||||
    state.infoDialog.visible = true;
 | 
			
		||||
}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const showProcess = (row: any) => {
 | 
			
		||||
    state.processDialog.machineId = row.id;
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,6 @@
 | 
			
		||||
<template>
 | 
			
		||||
    <div id="terminalRecDialog">
 | 
			
		||||
        <el-dialog :title="title" v-model="dialogVisible" :before-close="handleClose" :close-on-click-modal="false"
 | 
			
		||||
            :destroy-on-close="true" width="70%">
 | 
			
		||||
        <el-dialog :title="title" v-model="dialogVisible" :before-close="handleClose" :close-on-click-modal="false" :destroy-on-close="true" width="70%">
 | 
			
		||||
            <div class="toolbar">
 | 
			
		||||
                <el-select @change="getUsers" v-model="operateDate" placeholder="操作日期" filterable>
 | 
			
		||||
                    <el-option v-for="item in operateDates" :key="item" :label="item" :value="item"> </el-option>
 | 
			
		||||
@@ -30,9 +29,9 @@ const props = defineProps({
 | 
			
		||||
    visible: { type: Boolean },
 | 
			
		||||
    machineId: { type: Number },
 | 
			
		||||
    title: { type: String },
 | 
			
		||||
})
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const emit = defineEmits(['update:visible', 'cancel', 'update:machineId'])
 | 
			
		||||
const emit = defineEmits(['update:visible', 'cancel', 'update:machineId']);
 | 
			
		||||
 | 
			
		||||
const playerRef = ref(null);
 | 
			
		||||
const state = reactive({
 | 
			
		||||
@@ -47,16 +46,7 @@ const state = reactive({
 | 
			
		||||
    rec: '',
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const {
 | 
			
		||||
    dialogVisible,
 | 
			
		||||
    title,
 | 
			
		||||
    operateDates,
 | 
			
		||||
    operateDate,
 | 
			
		||||
    users,
 | 
			
		||||
    recs,
 | 
			
		||||
    user,
 | 
			
		||||
    rec,
 | 
			
		||||
} = toRefs(state)
 | 
			
		||||
const { dialogVisible, title, operateDates, operateDate, users, recs, user, rec } = toRefs(state);
 | 
			
		||||
 | 
			
		||||
watch(props, async (newValue: any) => {
 | 
			
		||||
    const visible = newValue.visible;
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,6 @@
 | 
			
		||||
<template>
 | 
			
		||||
    <div>
 | 
			
		||||
        <el-dialog :title="title" v-model="dialogVisible" :close-on-click-modal="true" :destroy-on-close="true"
 | 
			
		||||
            :before-close="cancel" width="1050px">
 | 
			
		||||
        <el-dialog :title="title" v-model="dialogVisible" :close-on-click-modal="true" :destroy-on-close="true" :before-close="cancel" width="1050px">
 | 
			
		||||
            <el-row :gutter="20">
 | 
			
		||||
                <el-col :lg="12" :md="12">
 | 
			
		||||
                    <el-descriptions size="small" title="基础信息" :column="2" border>
 | 
			
		||||
@@ -20,8 +19,7 @@
 | 
			
		||||
                        <el-descriptions-item label="运行中任务">
 | 
			
		||||
                            {{ stats.RunningProcs }}
 | 
			
		||||
                        </el-descriptions-item>
 | 
			
		||||
                        <el-descriptions-item label="负载"> {{ stats.Load1 }} {{ stats.Load5 }} {{ stats.Load10 }}
 | 
			
		||||
                        </el-descriptions-item>
 | 
			
		||||
                        <el-descriptions-item label="负载"> {{ stats.Load1 }} {{ stats.Load5 }} {{ stats.Load10 }} </el-descriptions-item>
 | 
			
		||||
                    </el-descriptions>
 | 
			
		||||
                </el-col>
 | 
			
		||||
 | 
			
		||||
@@ -38,8 +36,7 @@
 | 
			
		||||
                <el-col :lg="8" :md="8">
 | 
			
		||||
                    <span style="font-size: 16px; font-weight: 700">磁盘</span>
 | 
			
		||||
                    <el-table :data="stats.FSInfos" stripe max-height="250" style="width: 100%" border>
 | 
			
		||||
                        <el-table-column prop="MountPoint" label="挂载点" min-width="100" show-overflow-tooltip>
 | 
			
		||||
                        </el-table-column>
 | 
			
		||||
                        <el-table-column prop="MountPoint" label="挂载点" min-width="100" show-overflow-tooltip> </el-table-column>
 | 
			
		||||
                        <el-table-column prop="Used" label="可使用" min-width="70" show-overflow-tooltip>
 | 
			
		||||
                            <template #default="scope">
 | 
			
		||||
                                {{ formatByteSize(scope.row.Free) }}
 | 
			
		||||
@@ -57,10 +54,8 @@
 | 
			
		||||
                    <span style="font-size: 16px; font-weight: 700">网卡</span>
 | 
			
		||||
                    <el-table :data="netInter" stripe max-height="250" style="width: 100%" border>
 | 
			
		||||
                        <el-table-column prop="name" label="网卡" min-width="120" show-overflow-tooltip></el-table-column>
 | 
			
		||||
                        <el-table-column prop="IPv4" label="IPv4" min-width="130" show-overflow-tooltip>
 | 
			
		||||
                        </el-table-column>
 | 
			
		||||
                        <el-table-column prop="IPv6" label="IPv6" min-width="130" show-overflow-tooltip>
 | 
			
		||||
                        </el-table-column>
 | 
			
		||||
                        <el-table-column prop="IPv4" label="IPv4" min-width="130" show-overflow-tooltip> </el-table-column>
 | 
			
		||||
                        <el-table-column prop="IPv6" label="IPv6" min-width="130" show-overflow-tooltip> </el-table-column>
 | 
			
		||||
                        <el-table-column prop="Rx" label="接收(rx)" min-width="110" show-overflow-tooltip>
 | 
			
		||||
                            <template #default="scope">
 | 
			
		||||
                                {{ formatByteSize(scope.row.Rx) }}
 | 
			
		||||
@@ -78,9 +73,9 @@
 | 
			
		||||
    </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script lang="ts"  setup>
 | 
			
		||||
<script lang="ts" setup>
 | 
			
		||||
import { toRefs, reactive, watch, ref, nextTick } from 'vue';
 | 
			
		||||
import useEcharts from '@/common/echarts/useEcharts.ts';
 | 
			
		||||
import useEcharts from '@/common/echarts/useEcharts';
 | 
			
		||||
import tdTheme from '@/common/echarts/theme.json';
 | 
			
		||||
import { formatByteSize } from '@/common/utils/format';
 | 
			
		||||
import { machineApi } from './api';
 | 
			
		||||
@@ -98,9 +93,9 @@ const props = defineProps({
 | 
			
		||||
    title: {
 | 
			
		||||
        type: String,
 | 
			
		||||
    },
 | 
			
		||||
})
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const emit = defineEmits(['update:visible', 'cancel', 'update:machineId'])
 | 
			
		||||
const emit = defineEmits(['update:visible', 'cancel', 'update:machineId']);
 | 
			
		||||
 | 
			
		||||
const cpuRef: any = ref();
 | 
			
		||||
const memRef: any = ref();
 | 
			
		||||
@@ -114,13 +109,9 @@ const state = reactive({
 | 
			
		||||
    netInter: [] as any,
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const {
 | 
			
		||||
    dialogVisible,
 | 
			
		||||
    stats,
 | 
			
		||||
    netInter,
 | 
			
		||||
} = toRefs(state)
 | 
			
		||||
const { dialogVisible, stats, netInter } = toRefs(state);
 | 
			
		||||
 | 
			
		||||
let charts = [] as any
 | 
			
		||||
let charts = [] as any;
 | 
			
		||||
 | 
			
		||||
watch(props, async (newValue: any) => {
 | 
			
		||||
    const visible = newValue.visible;
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,6 @@
 | 
			
		||||
<template>
 | 
			
		||||
    <div class="file-manage">
 | 
			
		||||
        <el-dialog title="进程信息" v-model="dialogVisible" :destroy-on-close="true" :show-close="true"
 | 
			
		||||
            :before-close="handleClose" width="65%">
 | 
			
		||||
        <el-dialog title="进程信息" v-model="dialogVisible" :destroy-on-close="true" :show-close="true" :before-close="handleClose" width="65%">
 | 
			
		||||
            <div class="toolbar">
 | 
			
		||||
                <el-row>
 | 
			
		||||
                    <el-col :span="4">
 | 
			
		||||
@@ -22,8 +21,7 @@
 | 
			
		||||
                        </el-select>
 | 
			
		||||
                    </el-col>
 | 
			
		||||
                    <el-col :span="6">
 | 
			
		||||
                        <el-button class="ml5" @click="getProcess" type="primary" icon="tickets" size="small" plain>刷新
 | 
			
		||||
                        </el-button>
 | 
			
		||||
                        <el-button class="ml5" @click="getProcess" type="primary" icon="tickets" size="small" plain>刷新 </el-button>
 | 
			
		||||
                    </el-col>
 | 
			
		||||
                </el-row>
 | 
			
		||||
            </div>
 | 
			
		||||
@@ -83,15 +81,13 @@
 | 
			
		||||
                        </el-tooltip>
 | 
			
		||||
                    </template>
 | 
			
		||||
                </el-table-column>
 | 
			
		||||
                <el-table-column prop="command" label="command" :min-width="120" show-overflow-tooltip>
 | 
			
		||||
                </el-table-column>
 | 
			
		||||
                <el-table-column prop="command" label="command" :min-width="120" show-overflow-tooltip> </el-table-column>
 | 
			
		||||
 | 
			
		||||
                <el-table-column label="操作">
 | 
			
		||||
                    <template #default="scope">
 | 
			
		||||
                        <el-popconfirm title="确定终止该进程?" @confirm="confirmKillProcess(scope.row.pid)">
 | 
			
		||||
                            <template #reference>
 | 
			
		||||
                                <el-button v-auth="'machine:killprocess'" type="danger" icon="delete" size="small"
 | 
			
		||||
                                    plain>终止</el-button>
 | 
			
		||||
                                <el-button v-auth="'machine:killprocess'" type="danger" icon="delete" size="small" plain>终止</el-button>
 | 
			
		||||
                            </template>
 | 
			
		||||
                        </el-popconfirm>
 | 
			
		||||
                        <!-- <el-button @click="addFiles(scope.row)" type="danger" icon="delete" size="small" plain>终止</el-button> -->
 | 
			
		||||
@@ -111,9 +107,9 @@ const props = defineProps({
 | 
			
		||||
    visible: { type: Boolean },
 | 
			
		||||
    machineId: { type: Number },
 | 
			
		||||
    title: { type: String },
 | 
			
		||||
})
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const emit = defineEmits(['update:visible', 'cancel', 'update:machineId'])
 | 
			
		||||
const emit = defineEmits(['update:visible', 'cancel', 'update:machineId']);
 | 
			
		||||
 | 
			
		||||
const state = reactive({
 | 
			
		||||
    dialogVisible: false,
 | 
			
		||||
@@ -126,12 +122,7 @@ const state = reactive({
 | 
			
		||||
    processList: [],
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const {
 | 
			
		||||
    dialogVisible,
 | 
			
		||||
    params,
 | 
			
		||||
    processList,
 | 
			
		||||
} = toRefs(state)
 | 
			
		||||
 | 
			
		||||
const { dialogVisible, params, processList } = toRefs(state);
 | 
			
		||||
 | 
			
		||||
watch(props, (newValue) => {
 | 
			
		||||
    if (props.machineId) {
 | 
			
		||||
 
 | 
			
		||||
@@ -1,8 +1,15 @@
 | 
			
		||||
<template>
 | 
			
		||||
    <div class="mock-data-dialog">
 | 
			
		||||
        <el-dialog :title="title" v-model="dialogVisible" :close-on-click-modal="false" :before-close="cancel"
 | 
			
		||||
            :show-close="true" :destroy-on-close="true" width="900px">
 | 
			
		||||
            <el-form :model="form" ref="scriptForm" label-width="50px" size="small">
 | 
			
		||||
        <el-dialog
 | 
			
		||||
            :title="title"
 | 
			
		||||
            v-model="dialogVisible"
 | 
			
		||||
            :close-on-click-modal="false"
 | 
			
		||||
            :before-close="cancel"
 | 
			
		||||
            :show-close="true"
 | 
			
		||||
            :destroy-on-close="true"
 | 
			
		||||
            width="900px"
 | 
			
		||||
        >
 | 
			
		||||
            <el-form :model="form" ref="scriptForm" label-width="auto">
 | 
			
		||||
                <el-form-item prop="method" label="名称">
 | 
			
		||||
                    <el-input v-model="form.name" placeholder="请输入名称"></el-input>
 | 
			
		||||
                </el-form-item>
 | 
			
		||||
@@ -13,13 +20,12 @@
 | 
			
		||||
 | 
			
		||||
                <el-form-item prop="type" label="类型">
 | 
			
		||||
                    <el-select v-model="form.type" default-first-option style="width: 100%" placeholder="请选择类型">
 | 
			
		||||
                        <el-option v-for="item in enums.scriptTypeEnum as any" :key="item.value" :label="item.label"
 | 
			
		||||
                            :value="item.value"></el-option>
 | 
			
		||||
                        <el-option v-for="item in ScriptResultEnum" :key="item.value" :label="item.label" :value="item.value"></el-option>
 | 
			
		||||
                    </el-select>
 | 
			
		||||
                </el-form-item>
 | 
			
		||||
 | 
			
		||||
                <el-row style="margin-left: 30px; margin-bottom: 5px">
 | 
			
		||||
                    <el-button @click="onAddParam" size="small" type="success">新增占位符参数</el-button>
 | 
			
		||||
                    <el-button @click="onAddParam" type="success">新增占位符参数</el-button>
 | 
			
		||||
                </el-row>
 | 
			
		||||
                <el-form-item :key="param" v-for="(param, index) in params" prop="params" :label="`参数${index + 1}`">
 | 
			
		||||
                    <el-row>
 | 
			
		||||
@@ -48,7 +54,7 @@
 | 
			
		||||
                            <el-divider direction="vertical" border-style="dashed" />
 | 
			
		||||
                        </span>
 | 
			
		||||
                        <el-col :span="2">
 | 
			
		||||
                            <el-button @click="onDeleteParam(index)" size="small" type="danger">删除</el-button>
 | 
			
		||||
                            <el-button @click="onDeleteParam(index)" type="danger">删除</el-button>
 | 
			
		||||
                        </el-col>
 | 
			
		||||
                    </el-row>
 | 
			
		||||
                </el-form-item>
 | 
			
		||||
@@ -59,8 +65,7 @@
 | 
			
		||||
            <template #footer>
 | 
			
		||||
                <div class="dialog-footer">
 | 
			
		||||
                    <el-button @click="cancel()" :disabled="submitDisabled">关 闭</el-button>
 | 
			
		||||
                    <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>
 | 
			
		||||
            </template>
 | 
			
		||||
        </el-dialog>
 | 
			
		||||
@@ -71,7 +76,7 @@
 | 
			
		||||
import { ref, toRefs, reactive, watch } from 'vue';
 | 
			
		||||
import { ElMessage } from 'element-plus';
 | 
			
		||||
import { machineApi } from './api';
 | 
			
		||||
import enums from './enums';
 | 
			
		||||
import { ScriptResultEnum } from './enums';
 | 
			
		||||
import { notEmpty } from '@/common/assert';
 | 
			
		||||
import MonacoEditor from '@/components/monaco/MonacoEditor.vue';
 | 
			
		||||
 | 
			
		||||
@@ -91,9 +96,9 @@ const props = defineProps({
 | 
			
		||||
    isCommon: {
 | 
			
		||||
        type: Boolean,
 | 
			
		||||
    },
 | 
			
		||||
})
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const emit = defineEmits(['update:visible', 'cancel', 'submitSuccess'])
 | 
			
		||||
const emit = defineEmits(['update:visible', 'cancel', 'submitSuccess']);
 | 
			
		||||
 | 
			
		||||
const { isCommon, machineId } = toRefs(props);
 | 
			
		||||
const scriptForm: any = ref(null);
 | 
			
		||||
@@ -114,13 +119,7 @@ const state = reactive({
 | 
			
		||||
    btnLoading: false,
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const {
 | 
			
		||||
    dialogVisible,
 | 
			
		||||
    submitDisabled,
 | 
			
		||||
    params,
 | 
			
		||||
    form,
 | 
			
		||||
    btnLoading,
 | 
			
		||||
} = toRefs(state)
 | 
			
		||||
const { dialogVisible, submitDisabled, params, form, btnLoading } = toRefs(state);
 | 
			
		||||
 | 
			
		||||
watch(props, (newValue: any) => {
 | 
			
		||||
    state.dialogVisible = newValue.visible;
 | 
			
		||||
 
 | 
			
		||||
@@ -1,73 +1,76 @@
 | 
			
		||||
<template>
 | 
			
		||||
    <div class="file-manage">
 | 
			
		||||
        <el-dialog :title="title" v-model="dialogVisible" :destroy-on-close="true" :show-close="true"
 | 
			
		||||
            :before-close="handleClose" width="60%">
 | 
			
		||||
            <div class="toolbar">
 | 
			
		||||
                <div style="float: left">
 | 
			
		||||
                    <el-select v-model="type" @change="getScripts" size="small" placeholder="请选择">
 | 
			
		||||
                        <el-option :key="0" label="私有" :value="0"> </el-option>
 | 
			
		||||
                        <el-option :key="1" label="公共" :value="1"> </el-option>
 | 
			
		||||
                    </el-select>
 | 
			
		||||
                </div>
 | 
			
		||||
                <div style="float: right">
 | 
			
		||||
                    <el-button @click="editScript(currentData)" :disabled="currentId == null" type="primary"
 | 
			
		||||
                        icon="tickets" size="small" plain>查看</el-button>
 | 
			
		||||
                    <el-button v-auth="'machine:script:save'" type="primary" @click="editScript(null)" icon="plus"
 | 
			
		||||
                        size="small" plain>添加</el-button>
 | 
			
		||||
                    <el-button v-auth="'machine:script:del'" :disabled="currentId == null" type="danger"
 | 
			
		||||
                        @click="deleteRow(currentData)" icon="delete" size="small" plain>删除</el-button>
 | 
			
		||||
                </div>
 | 
			
		||||
            </div>
 | 
			
		||||
        <el-dialog
 | 
			
		||||
            @open="getScripts()"
 | 
			
		||||
            :title="title"
 | 
			
		||||
            v-model="dialogVisible"
 | 
			
		||||
            :destroy-on-close="true"
 | 
			
		||||
            :show-close="true"
 | 
			
		||||
            :before-close="handleClose"
 | 
			
		||||
            width="55%"
 | 
			
		||||
        >
 | 
			
		||||
            <page-table
 | 
			
		||||
                ref="pageTableRef"
 | 
			
		||||
                :query="queryConfig"
 | 
			
		||||
                v-model:query-form="query"
 | 
			
		||||
                :data="scriptTable"
 | 
			
		||||
                :columns="columns"
 | 
			
		||||
                :total="total"
 | 
			
		||||
                v-model:page-size="query.pageSize"
 | 
			
		||||
                v-model:page-num="query.pageNum"
 | 
			
		||||
                @pageChange="getScripts()"
 | 
			
		||||
                :show-selection="true"
 | 
			
		||||
                v-model:selection-data="selectionData"
 | 
			
		||||
            >
 | 
			
		||||
                <template #queryRight>
 | 
			
		||||
                    <el-button v-auth="'machine:script:save'" type="primary" @click="editScript(null)" icon="plus" plain>添加</el-button>
 | 
			
		||||
                    <el-button
 | 
			
		||||
                        v-auth="'machine:script:del'"
 | 
			
		||||
                        :disabled="selectionData.length < 1"
 | 
			
		||||
                        type="danger"
 | 
			
		||||
                        @click="deleteRow(selectionData)"
 | 
			
		||||
                        icon="delete"
 | 
			
		||||
                        plain
 | 
			
		||||
                        >删除</el-button
 | 
			
		||||
                    >
 | 
			
		||||
                </template>
 | 
			
		||||
 | 
			
		||||
            <el-table :data="scriptTable" @current-change="choose" stripe border size="small" style="width: 100%">
 | 
			
		||||
                <el-table-column label="选择" width="55px">
 | 
			
		||||
                    <template #default="scope">
 | 
			
		||||
                        <el-radio v-model="currentId" :label="scope.row.id">
 | 
			
		||||
                            <i></i>
 | 
			
		||||
                        </el-radio>
 | 
			
		||||
                    </template>
 | 
			
		||||
                </el-table-column>
 | 
			
		||||
                <el-table-column prop="name" label="名称" :min-width="70"> </el-table-column>
 | 
			
		||||
                <el-table-column prop="description" label="描述" :min-width="100" show-overflow-tooltip></el-table-column>
 | 
			
		||||
                <el-table-column prop="name" label="类型" :min-width="50">
 | 
			
		||||
                    <template #default="scope">
 | 
			
		||||
                        {{ enums.scriptTypeEnum.getLabelByValue(scope.row.type) }}
 | 
			
		||||
                    </template>
 | 
			
		||||
                </el-table-column>
 | 
			
		||||
                <el-table-column label="操作">
 | 
			
		||||
                    <template #default="scope">
 | 
			
		||||
                        <el-button v-if="scope.row.id == null" type="success" icon="el-icon-success" size="small" plain>
 | 
			
		||||
                            确定</el-button>
 | 
			
		||||
                <template #action="{ data }">
 | 
			
		||||
                    <el-button v-auth="'machine:script:run'" v-if="data.id != null" @click="runScript(data)" type="primary" icon="video-play" link
 | 
			
		||||
                        >执行
 | 
			
		||||
                    </el-button>
 | 
			
		||||
 | 
			
		||||
                        <el-button v-auth="'machine:script:run'" v-if="scope.row.id != null"
 | 
			
		||||
                            @click="runScript(scope.row)" type="primary" icon="video-play" size="small" plain>执行
 | 
			
		||||
                        </el-button>
 | 
			
		||||
                    </template>
 | 
			
		||||
                </el-table-column>
 | 
			
		||||
            </el-table>
 | 
			
		||||
            <el-row style="margin-top: 10px" type="flex" justify="end">
 | 
			
		||||
                <el-pagination small style="text-align: center" :total="total" layout="prev, pager, next, total, jumper"
 | 
			
		||||
                    v-model:current-page="query.pageNum" :page-size="query.pageSize" @current-change="handlePageChange">
 | 
			
		||||
                </el-pagination>
 | 
			
		||||
            </el-row>
 | 
			
		||||
                    <el-button @click="editScript(data)" type="primary" icon="tickets" link>查看 </el-button>
 | 
			
		||||
                </template>
 | 
			
		||||
            </page-table>
 | 
			
		||||
        </el-dialog>
 | 
			
		||||
 | 
			
		||||
        <el-dialog title="脚本参数" v-model="scriptParamsDialog.visible" width="400px">
 | 
			
		||||
            <el-form ref="paramsForm" :model="scriptParamsDialog.params" label-width="70px" size="small">
 | 
			
		||||
                <el-form-item v-for="item in scriptParamsDialog.paramsFormItem as any" :key="item.name"
 | 
			
		||||
                    :prop="item.model" :label="item.name" required>
 | 
			
		||||
                    <el-input v-if="!item.options" v-model="scriptParamsDialog.params[item.model]"
 | 
			
		||||
                        :placeholder="item.placeholder" autocomplete="off" clearable></el-input>
 | 
			
		||||
                    <el-select v-else v-model="scriptParamsDialog.params[item.model]" :placeholder="item.placeholder"
 | 
			
		||||
                        filterable autocomplete="off" clearable style="width: 100%">
 | 
			
		||||
                        <el-option v-for="option in item.options.split(',')" :key="option" :label="option"
 | 
			
		||||
                            :value="option" />
 | 
			
		||||
            <el-form ref="paramsForm" :model="scriptParamsDialog.params" label-width="auto">
 | 
			
		||||
                <el-form-item v-for="item in scriptParamsDialog.paramsFormItem as any" :key="item.name" :prop="item.model" :label="item.name" required>
 | 
			
		||||
                    <el-input
 | 
			
		||||
                        v-if="!item.options"
 | 
			
		||||
                        v-model="scriptParamsDialog.params[item.model]"
 | 
			
		||||
                        :placeholder="item.placeholder"
 | 
			
		||||
                        autocomplete="off"
 | 
			
		||||
                        clearable
 | 
			
		||||
                    ></el-input>
 | 
			
		||||
                    <el-select
 | 
			
		||||
                        v-else
 | 
			
		||||
                        v-model="scriptParamsDialog.params[item.model]"
 | 
			
		||||
                        :placeholder="item.placeholder"
 | 
			
		||||
                        filterable
 | 
			
		||||
                        autocomplete="off"
 | 
			
		||||
                        clearable
 | 
			
		||||
                        style="width: 100%"
 | 
			
		||||
                    >
 | 
			
		||||
                        <el-option v-for="option in item.options.split(',')" :key="option" :label="option" :value="option" />
 | 
			
		||||
                    </el-select>
 | 
			
		||||
                </el-form-item>
 | 
			
		||||
            </el-form>
 | 
			
		||||
            <template #footer>
 | 
			
		||||
                <span class="dialog-footer">
 | 
			
		||||
                    <el-button type="primary" @click="hasParamsRun(currentData)" size="small">确 定</el-button>
 | 
			
		||||
                    <el-button type="primary" @click="hasParamsRun()">确 定</el-button>
 | 
			
		||||
                </span>
 | 
			
		||||
            </template>
 | 
			
		||||
        </el-dialog>
 | 
			
		||||
@@ -78,14 +81,26 @@
 | 
			
		||||
            </div>
 | 
			
		||||
        </el-dialog>
 | 
			
		||||
 | 
			
		||||
        <el-dialog v-if="terminalDialog.visible" title="终端" v-model="terminalDialog.visible" width="80%"
 | 
			
		||||
            :close-on-click-modal="false" :modal="false" @close="closeTermnial">
 | 
			
		||||
            <ssh-terminal ref="terminal" :cmd="terminalDialog.cmd" :machineId="terminalDialog.machineId"
 | 
			
		||||
                height="560px" />
 | 
			
		||||
        <el-dialog
 | 
			
		||||
            v-if="terminalDialog.visible"
 | 
			
		||||
            title="终端"
 | 
			
		||||
            v-model="terminalDialog.visible"
 | 
			
		||||
            width="80%"
 | 
			
		||||
            :close-on-click-modal="false"
 | 
			
		||||
            :modal="false"
 | 
			
		||||
            @close="closeTermnial"
 | 
			
		||||
        >
 | 
			
		||||
            <ssh-terminal ref="terminal" :cmd="terminalDialog.cmd" :machineId="terminalDialog.machineId" height="560px" />
 | 
			
		||||
        </el-dialog>
 | 
			
		||||
 | 
			
		||||
        <script-edit v-model:visible="editDialog.visible" v-model:data="editDialog.data" :title="editDialog.title"
 | 
			
		||||
            v-model:machineId="editDialog.machineId" :isCommon="type == 1" @submitSuccess="submitSuccess" />
 | 
			
		||||
        <script-edit
 | 
			
		||||
            v-model:visible="editDialog.visible"
 | 
			
		||||
            v-model:data="editDialog.data"
 | 
			
		||||
            :title="editDialog.title"
 | 
			
		||||
            v-model:machineId="editDialog.machineId"
 | 
			
		||||
            :isCommon="state.query.type == ScriptTypeEnum.Public.value"
 | 
			
		||||
            @submitSuccess="submitSuccess"
 | 
			
		||||
        />
 | 
			
		||||
    </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
@@ -94,27 +109,37 @@ import { ref, toRefs, reactive, watch } from 'vue';
 | 
			
		||||
import { ElMessage, ElMessageBox } from 'element-plus';
 | 
			
		||||
import SshTerminal from './SshTerminal.vue';
 | 
			
		||||
import { machineApi } from './api';
 | 
			
		||||
import enums from './enums';
 | 
			
		||||
import { ScriptResultEnum, ScriptTypeEnum } from './enums';
 | 
			
		||||
import ScriptEdit from './ScriptEdit.vue';
 | 
			
		||||
import PageTable from '@/components/pagetable/PageTable.vue';
 | 
			
		||||
import { TableColumn, TableQuery } from '@/components/pagetable';
 | 
			
		||||
 | 
			
		||||
const props = defineProps({
 | 
			
		||||
    visible: { type: Boolean },
 | 
			
		||||
    machineId: { type: Number },
 | 
			
		||||
    title: { type: String },
 | 
			
		||||
})
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const emit = defineEmits(['update:visible', 'cancel', 'update:machineId'])
 | 
			
		||||
const emit = defineEmits(['update:visible', 'cancel', 'update:machineId']);
 | 
			
		||||
 | 
			
		||||
const paramsForm: any = ref(null);
 | 
			
		||||
const pageTableRef: any = ref(null);
 | 
			
		||||
 | 
			
		||||
const state = reactive({
 | 
			
		||||
    dialogVisible: false,
 | 
			
		||||
    type: 0,
 | 
			
		||||
    currentId: null,
 | 
			
		||||
    currentData: null,
 | 
			
		||||
    selectionData: [],
 | 
			
		||||
    queryConfig: [TableQuery.select('type', '类型').setOptions(Object.values(ScriptTypeEnum))],
 | 
			
		||||
    columns: [
 | 
			
		||||
        TableColumn.new('name', '名称'),
 | 
			
		||||
        TableColumn.new('description', '描述'),
 | 
			
		||||
        TableColumn.new('type', '类型').isEnum(ScriptResultEnum),
 | 
			
		||||
        TableColumn.new('action', '操作').isSlot().setMinWidth(130).alignCenter(),
 | 
			
		||||
    ],
 | 
			
		||||
    query: {
 | 
			
		||||
        machineId: 0 as any,
 | 
			
		||||
        type: ScriptTypeEnum.Private.value,
 | 
			
		||||
        pageNum: 1,
 | 
			
		||||
        pageSize: 8,
 | 
			
		||||
        pageSize: 6,
 | 
			
		||||
    },
 | 
			
		||||
    editDialog: {
 | 
			
		||||
        visible: false,
 | 
			
		||||
@@ -125,6 +150,7 @@ const state = reactive({
 | 
			
		||||
    total: 0,
 | 
			
		||||
    scriptTable: [],
 | 
			
		||||
    scriptParamsDialog: {
 | 
			
		||||
        script: null,
 | 
			
		||||
        visible: false,
 | 
			
		||||
        params: {},
 | 
			
		||||
        paramsFormItem: [],
 | 
			
		||||
@@ -140,39 +166,24 @@ const state = reactive({
 | 
			
		||||
    },
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const {
 | 
			
		||||
    dialogVisible,
 | 
			
		||||
    type,
 | 
			
		||||
    currentId,
 | 
			
		||||
    currentData,
 | 
			
		||||
    query,
 | 
			
		||||
    editDialog,
 | 
			
		||||
    total,
 | 
			
		||||
    scriptTable,
 | 
			
		||||
    scriptParamsDialog,
 | 
			
		||||
    resultDialog,
 | 
			
		||||
    terminalDialog,
 | 
			
		||||
} = toRefs(state)
 | 
			
		||||
const { dialogVisible, queryConfig, columns, selectionData, query, editDialog, total, scriptTable, scriptParamsDialog, resultDialog, terminalDialog } =
 | 
			
		||||
    toRefs(state);
 | 
			
		||||
 | 
			
		||||
watch(props, async (newValue) => {
 | 
			
		||||
    if (props.machineId && newValue.visible) {
 | 
			
		||||
        await getScripts();
 | 
			
		||||
    }
 | 
			
		||||
    state.dialogVisible = newValue.visible;
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const getScripts = async () => {
 | 
			
		||||
    state.currentId = null;
 | 
			
		||||
    state.currentData = null;
 | 
			
		||||
    state.query.machineId = state.type == 0 ? props.machineId : 9999999;
 | 
			
		||||
    const res = await machineApi.scripts.request(state.query);
 | 
			
		||||
    state.scriptTable = res.list;
 | 
			
		||||
    state.total = res.total;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const handlePageChange = (curPage: number) => {
 | 
			
		||||
    state.query.pageNum = curPage;
 | 
			
		||||
    getScripts();
 | 
			
		||||
    try {
 | 
			
		||||
        // 通过open事件才开获取到pageTableRef值
 | 
			
		||||
        pageTableRef.value.loading(true);
 | 
			
		||||
        state.query.machineId = state.query.type == ScriptTypeEnum.Private.value ? props.machineId : 9999999;
 | 
			
		||||
        const res = await machineApi.scripts.request(state.query);
 | 
			
		||||
        state.scriptTable = res.list;
 | 
			
		||||
        state.total = res.total;
 | 
			
		||||
    } finally {
 | 
			
		||||
        pageTableRef.value.loading(false);
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const runScript = async (script: any) => {
 | 
			
		||||
@@ -181,6 +192,7 @@ const runScript = async (script: any) => {
 | 
			
		||||
        state.scriptParamsDialog.paramsFormItem = JSON.parse(script.params);
 | 
			
		||||
        if (state.scriptParamsDialog.paramsFormItem && state.scriptParamsDialog.paramsFormItem.length > 0) {
 | 
			
		||||
            state.scriptParamsDialog.visible = true;
 | 
			
		||||
            state.scriptParamsDialog.script = script;
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
@@ -189,14 +201,15 @@ const runScript = async (script: any) => {
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// 有参数的脚本执行函数
 | 
			
		||||
const hasParamsRun = async (script: any) => {
 | 
			
		||||
const hasParamsRun = async () => {
 | 
			
		||||
    // 如果脚本参数弹窗显示,则校验参数表单数据通过后执行
 | 
			
		||||
    if (state.scriptParamsDialog.visible) {
 | 
			
		||||
        paramsForm.value.validate((valid: any) => {
 | 
			
		||||
            if (valid) {
 | 
			
		||||
                run(script);
 | 
			
		||||
                run(state.scriptParamsDialog.script);
 | 
			
		||||
                state.scriptParamsDialog.params = {};
 | 
			
		||||
                state.scriptParamsDialog.visible = false;
 | 
			
		||||
                state.scriptParamsDialog.script = null;
 | 
			
		||||
                paramsForm.value.resetFields();
 | 
			
		||||
            } else {
 | 
			
		||||
                return false;
 | 
			
		||||
@@ -206,13 +219,13 @@ const hasParamsRun = async (script: any) => {
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const run = async (script: any) => {
 | 
			
		||||
    const noResult = script.type == enums.scriptTypeEnum['NO_RESULT'].value;
 | 
			
		||||
    const noResult = script.type == ScriptResultEnum.NoResult.value;
 | 
			
		||||
    // 如果脚本类型为有结果类型,则显示结果信息
 | 
			
		||||
    if (script.type == enums.scriptTypeEnum['RESULT'].value || noResult) {
 | 
			
		||||
    if (script.type == ScriptResultEnum.Result.value || noResult) {
 | 
			
		||||
        const res = await machineApi.runScript.request({
 | 
			
		||||
            machineId: props.machineId,
 | 
			
		||||
            scriptId: script.id,
 | 
			
		||||
            params: state.scriptParamsDialog.params,
 | 
			
		||||
            params: JSON.stringify(state.scriptParamsDialog.params),
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        if (noResult) {
 | 
			
		||||
@@ -224,7 +237,7 @@ const run = async (script: any) => {
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (script.type == enums.scriptTypeEnum['REAL_TIME'].value) {
 | 
			
		||||
    if (script.type == ScriptResultEnum.RealTime.value) {
 | 
			
		||||
        script = script.script;
 | 
			
		||||
        if (state.scriptParamsDialog.params) {
 | 
			
		||||
            script = templateResolve(script, state.scriptParamsDialog.params);
 | 
			
		||||
@@ -255,17 +268,6 @@ const closeTermnial = () => {
 | 
			
		||||
    state.terminalDialog.machineId = 0;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 选择数据
 | 
			
		||||
 */
 | 
			
		||||
const choose = (item: any) => {
 | 
			
		||||
    if (!item) {
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
    state.currentId = item.id;
 | 
			
		||||
    state.currentData = item;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const editScript = (data: any) => {
 | 
			
		||||
    state.editDialog.machineId = props.machineId as any;
 | 
			
		||||
    state.editDialog.data = data;
 | 
			
		||||
@@ -281,8 +283,8 @@ const submitSuccess = () => {
 | 
			
		||||
    getScripts();
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const deleteRow = (row: any) => {
 | 
			
		||||
    ElMessageBox.confirm(`此操作将删除 [${row.name}], 是否继续?`, '提示', {
 | 
			
		||||
const deleteRow = (rows: any) => {
 | 
			
		||||
    ElMessageBox.confirm(`此操作将删除【${rows.map((x: any) => x.name).join(', ')}】脚本信息, 是否继续?`, '提示', {
 | 
			
		||||
        confirmButtonText: '确定',
 | 
			
		||||
        cancelButtonText: '取消',
 | 
			
		||||
        type: 'warning',
 | 
			
		||||
@@ -290,7 +292,7 @@ const deleteRow = (row: any) => {
 | 
			
		||||
        machineApi.deleteScript
 | 
			
		||||
            .request({
 | 
			
		||||
                machineId: props.machineId,
 | 
			
		||||
                scriptId: row.id,
 | 
			
		||||
                scriptId: rows.map((x: any) => x.id).join(','),
 | 
			
		||||
            })
 | 
			
		||||
            .then(() => {
 | 
			
		||||
                getScripts();
 | 
			
		||||
@@ -306,9 +308,9 @@ const handleClose = () => {
 | 
			
		||||
    emit('update:visible', false);
 | 
			
		||||
    emit('update:machineId', null);
 | 
			
		||||
    emit('cancel');
 | 
			
		||||
    state.query.type = ScriptTypeEnum.Private.value;
 | 
			
		||||
    state.scriptTable = [];
 | 
			
		||||
    state.scriptParamsDialog.paramsFormItem = [];
 | 
			
		||||
};
 | 
			
		||||
</script>
 | 
			
		||||
<style lang="sass">
 | 
			
		||||
</style>
 | 
			
		||||
<style lang="sass"></style>
 | 
			
		||||
 
 | 
			
		||||
@@ -16,7 +16,7 @@ const props = defineProps({
 | 
			
		||||
    machineId: { type: Number },
 | 
			
		||||
    cmd: { type: String },
 | 
			
		||||
    height: { type: [String, Number] },
 | 
			
		||||
})
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const { themeConfig } = storeToRefs(useThemeConfig());
 | 
			
		||||
const state = reactive({
 | 
			
		||||
@@ -42,7 +42,6 @@ onBeforeUnmount(() => {
 | 
			
		||||
    closeAll();
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
function initXterm() {
 | 
			
		||||
    const term: any = new Terminal({
 | 
			
		||||
        fontSize: themeConfig.value.terminalFontSize || 15,
 | 
			
		||||
@@ -104,8 +103,7 @@ function initXterm() {
 | 
			
		||||
let pingInterval: any;
 | 
			
		||||
function initSocket() {
 | 
			
		||||
    state.socket = new WebSocket(
 | 
			
		||||
        `${config.baseWsUrl}/machines/${props.machineId}/terminal?token=${getSession('token')}&cols=${state.term.cols}&rows=${state.term.rows
 | 
			
		||||
        }`
 | 
			
		||||
        `${config.baseWsUrl}/machines/${props.machineId}/terminal?token=${getSession('token')}&cols=${state.term.cols}&rows=${state.term.rows}`
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    // 监听socket连接
 | 
			
		||||
@@ -175,7 +173,7 @@ function closeAll() {
 | 
			
		||||
<style lang="scss">
 | 
			
		||||
#xterm {
 | 
			
		||||
    .xterm-viewport {
 | 
			
		||||
        overflow-y: hidden
 | 
			
		||||
        overflow-y: hidden;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
 
 | 
			
		||||
@@ -15,16 +15,11 @@ const state = reactive({
 | 
			
		||||
    height: 0,
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const {
 | 
			
		||||
    machineId,
 | 
			
		||||
    height,
 | 
			
		||||
} = toRefs(state)
 | 
			
		||||
const { machineId, height } = toRefs(state);
 | 
			
		||||
 | 
			
		||||
onMounted(() => {
 | 
			
		||||
    state.height = window.innerHeight + 5;
 | 
			
		||||
    state.machineId = Number.parseInt(route.query.id as string);
 | 
			
		||||
});
 | 
			
		||||
</script>
 | 
			
		||||
<style lang="scss">
 | 
			
		||||
 | 
			
		||||
</style>
 | 
			
		||||
<style lang="scss"></style>
 | 
			
		||||
 
 | 
			
		||||
@@ -2,45 +2,57 @@ import Api from '@/common/Api';
 | 
			
		||||
 | 
			
		||||
export const machineApi = {
 | 
			
		||||
    // 获取权限列表
 | 
			
		||||
    list: Api.newGet("/machines"),
 | 
			
		||||
    getMachinePwd: Api.newGet("/machines/{id}/pwd"),
 | 
			
		||||
    info: Api.newGet("/machines/{id}/sysinfo"),
 | 
			
		||||
    stats: Api.newGet("/machines/{id}/stats"),
 | 
			
		||||
    process: Api.newGet("/machines/{id}/process"),
 | 
			
		||||
    list: Api.newGet('/machines'),
 | 
			
		||||
    tagList: Api.newGet('/machines/tags'),
 | 
			
		||||
    getMachinePwd: Api.newGet('/machines/{id}/pwd'),
 | 
			
		||||
    info: Api.newGet('/machines/{id}/sysinfo'),
 | 
			
		||||
    stats: Api.newGet('/machines/{id}/stats'),
 | 
			
		||||
    process: Api.newGet('/machines/{id}/process'),
 | 
			
		||||
    // 终止进程
 | 
			
		||||
    killProcess: Api.newDelete("/machines/{id}/process"),
 | 
			
		||||
    closeCli: Api.newDelete("/machines/{id}/close-cli"),
 | 
			
		||||
    testConn: Api.newPost("/machines/test-conn"),
 | 
			
		||||
    killProcess: Api.newDelete('/machines/{id}/process'),
 | 
			
		||||
    closeCli: Api.newDelete('/machines/{id}/close-cli'),
 | 
			
		||||
    testConn: Api.newPost('/machines/test-conn'),
 | 
			
		||||
    // 保存按钮
 | 
			
		||||
    saveMachine: Api.newPost("/machines"),
 | 
			
		||||
    saveMachine: Api.newPost('/machines'),
 | 
			
		||||
    // 调整状态
 | 
			
		||||
    changeStatus: Api.newPut("/machines/{id}/{status}"),
 | 
			
		||||
    changeStatus: Api.newPut('/machines/{id}/{status}'),
 | 
			
		||||
    // 删除机器
 | 
			
		||||
    del: Api.newDelete("/machines/{id}"),
 | 
			
		||||
    scripts: Api.newGet("/machines/{machineId}/scripts"),
 | 
			
		||||
    runScript: Api.newGet("/machines/{machineId}/scripts/{scriptId}/run"),
 | 
			
		||||
    saveScript: Api.newPost("/machines/{machineId}/scripts"),
 | 
			
		||||
    deleteScript: Api.newDelete("/machines/{machineId}/scripts/{scriptId}"),
 | 
			
		||||
    del: Api.newDelete('/machines/{id}'),
 | 
			
		||||
    scripts: Api.newGet('/machines/{machineId}/scripts'),
 | 
			
		||||
    runScript: Api.newGet('/machines/{machineId}/scripts/{scriptId}/run'),
 | 
			
		||||
    saveScript: Api.newPost('/machines/{machineId}/scripts'),
 | 
			
		||||
    deleteScript: Api.newDelete('/machines/{machineId}/scripts/{scriptId}'),
 | 
			
		||||
    // 获取配置文件列表
 | 
			
		||||
    files: Api.newGet("/machines/{id}/files"),
 | 
			
		||||
    lsFile: Api.newGet("/machines/{machineId}/files/{fileId}/read-dir"),
 | 
			
		||||
    rmFile: Api.newDelete("/machines/{machineId}/files/{fileId}/remove"),
 | 
			
		||||
    uploadFile: Api.newPost("/machines/{machineId}/files/{fileId}/upload?token={token}"),
 | 
			
		||||
    fileContent: Api.newGet("/machines/{machineId}/files/{fileId}/read"),
 | 
			
		||||
    createFile: Api.newPost("/machines/{machineId}/files/{id}/create-file"),
 | 
			
		||||
    files: Api.newGet('/machines/{id}/files'),
 | 
			
		||||
    lsFile: Api.newGet('/machines/{machineId}/files/{fileId}/read-dir'),
 | 
			
		||||
    dirSize: Api.newGet('/machines/{machineId}/files/{fileId}/dir-size'),
 | 
			
		||||
    fileStat: Api.newGet('/machines/{machineId}/files/{fileId}/file-stat'),
 | 
			
		||||
    rmFile: Api.newDelete('/machines/{machineId}/files/{fileId}/remove'),
 | 
			
		||||
    uploadFile: Api.newPost('/machines/{machineId}/files/{fileId}/upload?token={token}'),
 | 
			
		||||
    fileContent: Api.newGet('/machines/{machineId}/files/{fileId}/read'),
 | 
			
		||||
    createFile: Api.newPost('/machines/{machineId}/files/{id}/create-file'),
 | 
			
		||||
    // 修改文件内容
 | 
			
		||||
    updateFileContent: Api.newPost("/machines/{machineId}/files/{id}/write"),
 | 
			
		||||
    updateFileContent: Api.newPost('/machines/{machineId}/files/{id}/write'),
 | 
			
		||||
    // 添加文件or目录
 | 
			
		||||
    addConf: Api.newPost("/machines/{machineId}/files"),
 | 
			
		||||
    addConf: Api.newPost('/machines/{machineId}/files'),
 | 
			
		||||
    // 删除配置的文件or目录
 | 
			
		||||
    delConf: Api.newDelete("/machines/{machineId}/files/{id}"),
 | 
			
		||||
    terminal: Api.newGet("/api/machines/{id}/terminal"),
 | 
			
		||||
    recDirNames: Api.newGet("/machines/rec/names")
 | 
			
		||||
}
 | 
			
		||||
    delConf: Api.newDelete('/machines/{machineId}/files/{id}'),
 | 
			
		||||
    terminal: Api.newGet('/api/machines/{id}/terminal'),
 | 
			
		||||
    recDirNames: Api.newGet('/machines/rec/names'),
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const authCertApi = {
 | 
			
		||||
    baseList : Api.newGet("/sys/authcerts/base"),
 | 
			
		||||
    list: Api.newGet("/sys/authcerts"),
 | 
			
		||||
    save: Api.newPost("/sys/authcerts"),
 | 
			
		||||
    delete: Api.newDelete("/sys/authcerts/{id}"),
 | 
			
		||||
}
 | 
			
		||||
    baseList: Api.newGet('/sys/authcerts/base'),
 | 
			
		||||
    list: Api.newGet('/sys/authcerts'),
 | 
			
		||||
    save: Api.newPost('/sys/authcerts'),
 | 
			
		||||
    delete: Api.newDelete('/sys/authcerts/{id}'),
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const cronJobApi = {
 | 
			
		||||
    list: Api.newGet('/machine-cronjobs'),
 | 
			
		||||
    relateMachineIds: Api.newGet('/machine-cronjobs/machine-ids'),
 | 
			
		||||
    relateCronJobIds: Api.newGet('/machine-cronjobs/cronjob-ids'),
 | 
			
		||||
    save: Api.newPost('/machine-cronjobs'),
 | 
			
		||||
    delete: Api.newDelete('/machine-cronjobs/{id}'),
 | 
			
		||||
    execList: Api.newGet('/machine-cronjobs/execs'),
 | 
			
		||||
};
 | 
			
		||||
 
 | 
			
		||||
@@ -1,8 +1,7 @@
 | 
			
		||||
<template>
 | 
			
		||||
    <div>
 | 
			
		||||
        <el-dialog :title="title" v-model="dvisible" :show-close="false" :before-close="cancel" width="500px"
 | 
			
		||||
            :destroy-on-close="true">
 | 
			
		||||
            <el-form ref="acForm" :rules="rules" :model="form" label-width="90px">
 | 
			
		||||
        <el-dialog :title="title" v-model="dvisible" :show-close="false" :before-close="cancel" width="500px" :destroy-on-close="true">
 | 
			
		||||
            <el-form ref="acForm" :rules="rules" :model="form" label-width="auto">
 | 
			
		||||
                <el-form-item prop="name" label="名称:" required>
 | 
			
		||||
                    <el-input v-model="form.name"></el-input>
 | 
			
		||||
                </el-form-item>
 | 
			
		||||
@@ -13,17 +12,14 @@
 | 
			
		||||
                    </el-select>
 | 
			
		||||
                </el-form-item>
 | 
			
		||||
                <el-form-item v-if="form.authMethod == 1" prop="password" label="密码:">
 | 
			
		||||
                    <el-input type="password" show-password clearable v-model.trim="form.password" placeholder="请输入密码"
 | 
			
		||||
                        autocomplete="new-password">
 | 
			
		||||
                    <el-input type="password" show-password clearable v-model.trim="form.password" placeholder="请输入密码" autocomplete="new-password">
 | 
			
		||||
                    </el-input>
 | 
			
		||||
                </el-form-item>
 | 
			
		||||
                <el-form-item v-if="form.authMethod == 2" prop="password" label="秘钥:">
 | 
			
		||||
                    <el-input type="textarea" :rows="5" v-model="form.password" placeholder="请将私钥文件内容拷贝至此">
 | 
			
		||||
                    </el-input>
 | 
			
		||||
                    <el-input type="textarea" :rows="5" v-model="form.password" placeholder="请将私钥文件内容拷贝至此"> </el-input>
 | 
			
		||||
                </el-form-item>
 | 
			
		||||
                <el-form-item v-if="form.authMethod == 2" prop="passphrase" label="秘钥密码:">
 | 
			
		||||
                    <el-input type="password" v-model="form.passphrase">
 | 
			
		||||
                    </el-input>
 | 
			
		||||
                    <el-input type="password" v-model="form.passphrase"> </el-input>
 | 
			
		||||
                </el-form-item>
 | 
			
		||||
 | 
			
		||||
                <el-form-item label="备注:">
 | 
			
		||||
@@ -54,10 +50,10 @@ const props = defineProps({
 | 
			
		||||
    title: {
 | 
			
		||||
        type: String,
 | 
			
		||||
    },
 | 
			
		||||
})
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
//定义事件
 | 
			
		||||
const emit = defineEmits(['update:visible', 'cancel', 'val-change'])
 | 
			
		||||
const emit = defineEmits(['update:visible', 'cancel', 'val-change']);
 | 
			
		||||
 | 
			
		||||
const acForm: any = ref(null);
 | 
			
		||||
 | 
			
		||||
@@ -68,8 +64,8 @@ const rules = {
 | 
			
		||||
            message: '授权凭证名称不能为空',
 | 
			
		||||
            trigger: ['change', 'blur'],
 | 
			
		||||
        },
 | 
			
		||||
    ]
 | 
			
		||||
}
 | 
			
		||||
    ],
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const state = reactive({
 | 
			
		||||
    dvisible: false,
 | 
			
		||||
@@ -85,11 +81,7 @@ const state = reactive({
 | 
			
		||||
    btnLoading: false,
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const {
 | 
			
		||||
    dvisible,
 | 
			
		||||
    form,
 | 
			
		||||
    btnLoading,
 | 
			
		||||
} = toRefs(state)
 | 
			
		||||
const { dvisible, form, btnLoading } = toRefs(state);
 | 
			
		||||
 | 
			
		||||
watch(props, (newValue: any) => {
 | 
			
		||||
    state.dvisible = newValue.visible;
 | 
			
		||||
 
 | 
			
		||||
@@ -1,59 +1,28 @@
 | 
			
		||||
<template>
 | 
			
		||||
    <div class="role-list">
 | 
			
		||||
        <el-card>
 | 
			
		||||
            <div>
 | 
			
		||||
    <div>
 | 
			
		||||
        <page-table
 | 
			
		||||
            :query="state.queryConfig"
 | 
			
		||||
            v-model:query-form="query"
 | 
			
		||||
            :show-selection="true"
 | 
			
		||||
            v-model:selection-data="selectionData"
 | 
			
		||||
            :data="authcerts"
 | 
			
		||||
            :columns="state.columns"
 | 
			
		||||
            :total="total"
 | 
			
		||||
            v-model:page-size="query.pageSize"
 | 
			
		||||
            v-model:page-num="query.pageNum"
 | 
			
		||||
            @pageChange="search()"
 | 
			
		||||
        >
 | 
			
		||||
            <template #queryRight>
 | 
			
		||||
                <el-button type="primary" icon="plus" @click="edit(false)">添加</el-button>
 | 
			
		||||
                <el-button :disabled="chooseId == null" @click="edit(chooseData)" type="primary" icon="edit">编辑
 | 
			
		||||
                </el-button>
 | 
			
		||||
                <el-button :disabled="chooseId == null" @click="deleteAc(chooseData)" type="danger" icon="delete">删除
 | 
			
		||||
                </el-button>
 | 
			
		||||
                <el-button :disabled="selectionData.length < 1" @click="deleteAc(selectionData)" type="danger" icon="delete">删除 </el-button>
 | 
			
		||||
            </template>
 | 
			
		||||
 | 
			
		||||
                <div style="float: right">
 | 
			
		||||
                    <el-input class="ml5" placeholder="请输入凭证名称" style="width: 200px" v-model="query.name" @clear="search"
 | 
			
		||||
                        plain clearable></el-input>
 | 
			
		||||
                    <el-button class="ml5" @click="search" type="success" icon="search"></el-button>
 | 
			
		||||
                </div>
 | 
			
		||||
            </div>
 | 
			
		||||
            <template #action="{ data }">
 | 
			
		||||
                <el-button @click="edit(data)" type="primary" link>编辑 </el-button>
 | 
			
		||||
            </template>
 | 
			
		||||
        </page-table>
 | 
			
		||||
 | 
			
		||||
            <el-table :data="authcerts" @current-change="choose" ref="table" style="width: 100%">
 | 
			
		||||
                <el-table-column label="选择" width="55px">
 | 
			
		||||
                    <template #default="scope">
 | 
			
		||||
                        <el-radio v-model="chooseId" :label="scope.row.id">
 | 
			
		||||
                            <i></i>
 | 
			
		||||
                        </el-radio>
 | 
			
		||||
                    </template>
 | 
			
		||||
                </el-table-column>
 | 
			
		||||
                <el-table-column prop="name" label="名称" min-width="60px" show-overflow-tooltip></el-table-column>
 | 
			
		||||
                <el-table-column prop="authMethod" label="认证方式" min-width="50px">
 | 
			
		||||
                    <template #default="scope">
 | 
			
		||||
                        <el-tag v-if="scope.row.authMethod == 1" type="success" size="small">密码</el-tag>
 | 
			
		||||
                        <el-tag v-if="scope.row.authMethod == 2" size="small">密钥</el-tag>
 | 
			
		||||
                    </template>
 | 
			
		||||
                </el-table-column>
 | 
			
		||||
                <el-table-column prop="remark" label="备注" min-width="100px" show-overflow-tooltip>
 | 
			
		||||
                </el-table-column>
 | 
			
		||||
                <el-table-column prop="creator" label="创建人" min-width="60px"></el-table-column>
 | 
			
		||||
                <el-table-column prop="createTime" label="创建时间" min-width="100px">
 | 
			
		||||
                    <template #default="scope">
 | 
			
		||||
                        {{ dateFormat(scope.row.createTime) }}
 | 
			
		||||
                    </template>
 | 
			
		||||
                </el-table-column>
 | 
			
		||||
                <el-table-column prop="modifier" label="修改者" min-width="60px" show-overflow-tooltip></el-table-column>
 | 
			
		||||
                <el-table-column prop="updateTime" label="更新时间" min-width="100px">
 | 
			
		||||
                    <template #default="scope">
 | 
			
		||||
                        {{ dateFormat(scope.row.updateTime) }}
 | 
			
		||||
                    </template>
 | 
			
		||||
                </el-table-column>
 | 
			
		||||
            </el-table>
 | 
			
		||||
            <el-row style="margin-top: 20px" type="flex" justify="end">
 | 
			
		||||
                <el-pagination style="text-align: right" @current-change="handlePageChange" :total="total"
 | 
			
		||||
                    layout="prev, pager, next, total, jumper" v-model:current-page="query.pageNum"
 | 
			
		||||
                    :page-size="query.pageSize"></el-pagination>
 | 
			
		||||
            </el-row>
 | 
			
		||||
        </el-card>
 | 
			
		||||
 | 
			
		||||
        <auth-cert-edit :title="editor.title" v-model:visible="editor.visible" :data="editor.authcert"
 | 
			
		||||
            @val-change="editChange" />
 | 
			
		||||
        <auth-cert-edit :title="editor.title" v-model:visible="editor.visible" :data="editor.authcert" @val-change="editChange" />
 | 
			
		||||
    </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
@@ -62,19 +31,30 @@ import { toRefs, reactive, onMounted } from 'vue';
 | 
			
		||||
import AuthCertEdit from './AuthCertEdit.vue';
 | 
			
		||||
import { authCertApi } from '../api';
 | 
			
		||||
import { ElMessage, ElMessageBox } from 'element-plus';
 | 
			
		||||
import { dateFormat } from '@/common/utils/date';
 | 
			
		||||
import PageTable from '@/components/pagetable/PageTable.vue';
 | 
			
		||||
import { TableColumn, TableQuery } from '@/components/pagetable';
 | 
			
		||||
import { AuthMethodEnum } from '../enums';
 | 
			
		||||
 | 
			
		||||
const state = reactive({
 | 
			
		||||
    query: {
 | 
			
		||||
        pageNum: 1,
 | 
			
		||||
        pageSize: 10,
 | 
			
		||||
        name: null,
 | 
			
		||||
        type: null,
 | 
			
		||||
    },
 | 
			
		||||
    queryConfig: [TableQuery.text('name', '凭证名称')],
 | 
			
		||||
    columns: [
 | 
			
		||||
        TableColumn.new('name', '名称'),
 | 
			
		||||
        TableColumn.new('authMethod', '认证方式').typeTag(AuthMethodEnum),
 | 
			
		||||
        TableColumn.new('remark', '备注'),
 | 
			
		||||
        TableColumn.new('creator', '创建人'),
 | 
			
		||||
        TableColumn.new('createTime', '创建时间').isTime(),
 | 
			
		||||
        TableColumn.new('creator', '修改者'),
 | 
			
		||||
        TableColumn.new('createTime', '修改时间').isTime(),
 | 
			
		||||
        TableColumn.new('action', '操作').isSlot().fixedRight().setMinWidth(65).alignCenter(),
 | 
			
		||||
    ],
 | 
			
		||||
    total: 0,
 | 
			
		||||
    authcerts: [],
 | 
			
		||||
    chooseId: null,
 | 
			
		||||
    chooseData: null,
 | 
			
		||||
    selectionData: [],
 | 
			
		||||
    paramsDialog: {
 | 
			
		||||
        visible: false,
 | 
			
		||||
        config: null as any,
 | 
			
		||||
@@ -88,14 +68,7 @@ const state = reactive({
 | 
			
		||||
    },
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const {
 | 
			
		||||
    query,
 | 
			
		||||
    total,
 | 
			
		||||
    authcerts,
 | 
			
		||||
    chooseId,
 | 
			
		||||
    chooseData,
 | 
			
		||||
    editor,
 | 
			
		||||
} = toRefs(state)
 | 
			
		||||
const { query, total, authcerts, selectionData, editor } = toRefs(state);
 | 
			
		||||
 | 
			
		||||
onMounted(() => {
 | 
			
		||||
    search();
 | 
			
		||||
@@ -107,23 +80,8 @@ const search = async () => {
 | 
			
		||||
    state.total = res.total;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const handlePageChange = (curPage: number) => {
 | 
			
		||||
    state.query.pageNum = curPage;
 | 
			
		||||
    search();
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const choose = (item: any) => {
 | 
			
		||||
    if (!item) {
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
    state.chooseId = item.id;
 | 
			
		||||
    state.chooseData = item;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const editChange = () => {
 | 
			
		||||
    ElMessage.success('保存成功');
 | 
			
		||||
    state.chooseId = null;
 | 
			
		||||
    state.chooseData = null;
 | 
			
		||||
    search();
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@@ -139,18 +97,15 @@ const edit = (data: any) => {
 | 
			
		||||
 | 
			
		||||
const deleteAc = async (data: any) => {
 | 
			
		||||
    try {
 | 
			
		||||
        await ElMessageBox.confirm(`确定删除该授权凭证?`, '提示', {
 | 
			
		||||
        await ElMessageBox.confirm(`确定删除该【${data.map((x: any) => x.name).join(', ')}授权凭证?`, '提示', {
 | 
			
		||||
            confirmButtonText: '确定',
 | 
			
		||||
            cancelButtonText: '取消',
 | 
			
		||||
            type: 'warning',
 | 
			
		||||
        });
 | 
			
		||||
        await authCertApi.delete.request({ id: data.id });
 | 
			
		||||
        await authCertApi.delete.request({ id: data.map((x: any) => x.id).join(',') });
 | 
			
		||||
        ElMessage.success('删除成功');
 | 
			
		||||
        state.chooseData = null;
 | 
			
		||||
        state.chooseId = null;
 | 
			
		||||
        search();
 | 
			
		||||
    } catch (err) { }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
    } catch (err) {}
 | 
			
		||||
};
 | 
			
		||||
</script>
 | 
			
		||||
<style lang="scss"></style>
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,6 @@
 | 
			
		||||
<template>
 | 
			
		||||
    <div style="width: 100%">
 | 
			
		||||
        <el-select @change="changeValue" v-model="id" filterable placeholder="请选择授权凭证,可前往[机器管理->授权凭证]添加"
 | 
			
		||||
            style="width: 100%">
 | 
			
		||||
        <el-select @change="changeValue" v-model="id" filterable placeholder="请选择授权凭证,可前往[机器管理->授权凭证]添加" style="width: 100%">
 | 
			
		||||
            <el-option v-for="ac in acs" :key="ac.id" :value="ac.id" :label="ac.name">
 | 
			
		||||
                <el-tag v-if="ac.authMethod == 1" type="success" size="small">密码</el-tag>
 | 
			
		||||
                <el-tag v-if="ac.authMethod == 2" size="small">密钥</el-tag>
 | 
			
		||||
@@ -21,43 +20,38 @@ import { reactive, toRefs, onMounted } from 'vue';
 | 
			
		||||
import { authCertApi } from '../api';
 | 
			
		||||
 | 
			
		||||
//定义事件
 | 
			
		||||
const emit = defineEmits(['update:modelValue', 'change'])
 | 
			
		||||
 | 
			
		||||
const emit = defineEmits(['update:modelValue', 'change']);
 | 
			
		||||
 | 
			
		||||
const props = defineProps({
 | 
			
		||||
    modelValue: {
 | 
			
		||||
        type: [Number],
 | 
			
		||||
        required: true,
 | 
			
		||||
    },
 | 
			
		||||
})
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const state = reactive({
 | 
			
		||||
    acs: [] as any,
 | 
			
		||||
    id: null as any,
 | 
			
		||||
})
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const {
 | 
			
		||||
    acs,
 | 
			
		||||
    id,
 | 
			
		||||
} = toRefs(state)
 | 
			
		||||
const { acs, id } = toRefs(state);
 | 
			
		||||
 | 
			
		||||
onMounted(async () => {
 | 
			
		||||
    await getAcs();
 | 
			
		||||
    if (props.modelValue) {
 | 
			
		||||
        state.id = props.modelValue;
 | 
			
		||||
    }
 | 
			
		||||
})
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const changeValue = (val: any) => {
 | 
			
		||||
    emit('update:modelValue', val);
 | 
			
		||||
    emit('change', val);
 | 
			
		||||
}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const getAcs = async () => {
 | 
			
		||||
    const acs = await authCertApi.baseList.request({ pageSize: 100, type: 2 })
 | 
			
		||||
    const acs = await authCertApi.baseList.request({ pageSize: 100, type: 2 });
 | 
			
		||||
    state.acs = acs.list;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
};
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style lang="scss"></style>
 | 
			
		||||
<style lang="scss"></style>
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										189
									
								
								mayfly_go_web/src/views/ops/machine/cronjob/CronJobEdit.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										189
									
								
								mayfly_go_web/src/views/ops/machine/cronjob/CronJobEdit.vue
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,189 @@
 | 
			
		||||
<template>
 | 
			
		||||
    <div class="mock-data-dialog">
 | 
			
		||||
        <el-dialog
 | 
			
		||||
            :title="title"
 | 
			
		||||
            v-model="dialogVisible"
 | 
			
		||||
            :close-on-click-modal="false"
 | 
			
		||||
            :before-close="cancel"
 | 
			
		||||
            :show-close="true"
 | 
			
		||||
            :destroy-on-close="true"
 | 
			
		||||
            width="900px"
 | 
			
		||||
        >
 | 
			
		||||
            <el-form :model="form" ref="formRef" :rules="rules" label-width="auto">
 | 
			
		||||
                <el-form-item prop="name" label="名称">
 | 
			
		||||
                    <el-input v-model="form.name" placeholder="请输入名称"></el-input>
 | 
			
		||||
                </el-form-item>
 | 
			
		||||
 | 
			
		||||
                <el-form-item prop="cron" label="cron表达式">
 | 
			
		||||
                    <el-input v-model="form.cron" placeholder="请输入cron表达式"></el-input>
 | 
			
		||||
                </el-form-item>
 | 
			
		||||
 | 
			
		||||
                <el-form-item prop="status" label="状态">
 | 
			
		||||
                    <el-select v-model="form.status" default-first-option style="width: 100%" placeholder="请选择状态">
 | 
			
		||||
                        <el-option v-for="item in CronJobStatusEnum" :key="item.value" :label="item.label" :value="item.value"></el-option>
 | 
			
		||||
                    </el-select>
 | 
			
		||||
                </el-form-item>
 | 
			
		||||
 | 
			
		||||
                <el-form-item prop="saveExecResType" label="记录类型">
 | 
			
		||||
                    <el-select v-model="form.saveExecResType" default-first-option style="width: 100%" placeholder="请选择记录类型">
 | 
			
		||||
                        <el-option v-for="item in CronJobSaveExecResTypeEnum" :key="item.value" :label="item.label" :value="item.value"></el-option>
 | 
			
		||||
                    </el-select>
 | 
			
		||||
                </el-form-item>
 | 
			
		||||
 | 
			
		||||
                <el-form-item prop="remark" label="备注">
 | 
			
		||||
                    <el-input v-model="form.remark" placeholder="请输入备注"></el-input>
 | 
			
		||||
                </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>
 | 
			
		||||
                    <monaco-editor style="width: 100%" v-model="form.script" language="shell" height="300px"
 | 
			
		||||
                /></el-form-item>
 | 
			
		||||
            </el-form>
 | 
			
		||||
 | 
			
		||||
            <template #footer>
 | 
			
		||||
                <div class="dialog-footer">
 | 
			
		||||
                    <el-button @click="cancel()" :disabled="submitDisabled">关 闭</el-button>
 | 
			
		||||
                    <el-button v-auth="'machine:script:save'" type="primary" :loading="btnLoading" @click="btnOk" :disabled="submitDisabled">保 存</el-button>
 | 
			
		||||
                </div>
 | 
			
		||||
            </template>
 | 
			
		||||
        </el-dialog>
 | 
			
		||||
    </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script lang="ts" setup>
 | 
			
		||||
import { ref, toRefs, reactive, watch, onMounted } from 'vue';
 | 
			
		||||
import { ElMessage } from 'element-plus';
 | 
			
		||||
import { cronJobApi, machineApi } from '../api';
 | 
			
		||||
import { CronJobStatusEnum, CronJobSaveExecResTypeEnum } from '../enums';
 | 
			
		||||
import { notEmpty } from '@/common/assert';
 | 
			
		||||
import MonacoEditor from '@/components/monaco/MonacoEditor.vue';
 | 
			
		||||
 | 
			
		||||
const props = defineProps({
 | 
			
		||||
    visible: {
 | 
			
		||||
        type: Boolean,
 | 
			
		||||
    },
 | 
			
		||||
    data: {
 | 
			
		||||
        type: Object,
 | 
			
		||||
    },
 | 
			
		||||
    title: {
 | 
			
		||||
        type: String,
 | 
			
		||||
    },
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const emit = defineEmits(['update:visible', 'cancel', 'submitSuccess']);
 | 
			
		||||
 | 
			
		||||
const formRef: any = ref(null);
 | 
			
		||||
const rules = {
 | 
			
		||||
    name: [
 | 
			
		||||
        {
 | 
			
		||||
            required: true,
 | 
			
		||||
            message: '请输入名称',
 | 
			
		||||
            trigger: ['change', 'blur'],
 | 
			
		||||
        },
 | 
			
		||||
    ],
 | 
			
		||||
    cron: [
 | 
			
		||||
        {
 | 
			
		||||
            required: true,
 | 
			
		||||
            message: '请输入cron表达式',
 | 
			
		||||
            trigger: ['change', 'blur'],
 | 
			
		||||
        },
 | 
			
		||||
    ],
 | 
			
		||||
    status: [
 | 
			
		||||
        {
 | 
			
		||||
            required: true,
 | 
			
		||||
            message: '请选择状态',
 | 
			
		||||
            trigger: ['change', 'blur'],
 | 
			
		||||
        },
 | 
			
		||||
    ],
 | 
			
		||||
    saveExecResType: [
 | 
			
		||||
        {
 | 
			
		||||
            required: true,
 | 
			
		||||
            message: '请选择执行记录类型',
 | 
			
		||||
            trigger: ['change', 'blur'],
 | 
			
		||||
        },
 | 
			
		||||
    ],
 | 
			
		||||
    script: [
 | 
			
		||||
        {
 | 
			
		||||
            required: true,
 | 
			
		||||
            message: '请输入执行脚本',
 | 
			
		||||
            trigger: ['change', 'blur'],
 | 
			
		||||
        },
 | 
			
		||||
    ],
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const state = reactive({
 | 
			
		||||
    dialogVisible: false,
 | 
			
		||||
    submitDisabled: false,
 | 
			
		||||
    chooseMachines: [],
 | 
			
		||||
    form: {
 | 
			
		||||
        id: null,
 | 
			
		||||
        name: '',
 | 
			
		||||
        cron: '',
 | 
			
		||||
        machineIds: [],
 | 
			
		||||
        remark: '',
 | 
			
		||||
        script: '',
 | 
			
		||||
        status: 1,
 | 
			
		||||
        saveExecResType: -1,
 | 
			
		||||
    },
 | 
			
		||||
    machines: [] as any,
 | 
			
		||||
    btnLoading: false,
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const { dialogVisible, submitDisabled, form, btnLoading } = toRefs(state);
 | 
			
		||||
 | 
			
		||||
onMounted(async () => {
 | 
			
		||||
    const res = await machineApi.list.request({ pageNum: 1, pageSize: 100 });
 | 
			
		||||
    state.machines = res.list;
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
watch(props, async (newValue: any) => {
 | 
			
		||||
    state.dialogVisible = newValue.visible;
 | 
			
		||||
    if (!newValue.visible) {
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
    if (newValue.data) {
 | 
			
		||||
        state.form = { ...newValue.data };
 | 
			
		||||
        state.form.machineIds = await cronJobApi.relateMachineIds.request({ cronJobId: state.form.id });
 | 
			
		||||
    } else {
 | 
			
		||||
        state.form = {} as any;
 | 
			
		||||
        state.chooseMachines = [];
 | 
			
		||||
    }
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const btnOk = () => {
 | 
			
		||||
    formRef.value.validate((valid: any) => {
 | 
			
		||||
        if (valid) {
 | 
			
		||||
            notEmpty(state.form.name, '名称不能为空');
 | 
			
		||||
            notEmpty(state.form.script, '脚本内容不能为空');
 | 
			
		||||
            cronJobApi.save.request(state.form).then(
 | 
			
		||||
                () => {
 | 
			
		||||
                    ElMessage.success('保存成功');
 | 
			
		||||
                    emit('submitSuccess');
 | 
			
		||||
                    state.submitDisabled = false;
 | 
			
		||||
                    cancel();
 | 
			
		||||
                },
 | 
			
		||||
                () => {
 | 
			
		||||
                    state.submitDisabled = false;
 | 
			
		||||
                }
 | 
			
		||||
            );
 | 
			
		||||
        } else {
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
    });
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const cancel = () => {
 | 
			
		||||
    emit('update:visible', false);
 | 
			
		||||
    emit('cancel');
 | 
			
		||||
};
 | 
			
		||||
</script>
 | 
			
		||||
<style lang="scss"></style>
 | 
			
		||||
							
								
								
									
										166
									
								
								mayfly_go_web/src/views/ops/machine/cronjob/CronJobExecList.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										166
									
								
								mayfly_go_web/src/views/ops/machine/cronjob/CronJobExecList.vue
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,166 @@
 | 
			
		||||
<template>
 | 
			
		||||
    <div>
 | 
			
		||||
        <el-dialog
 | 
			
		||||
            :title="title"
 | 
			
		||||
            v-model="dialogVisible"
 | 
			
		||||
            :close-on-click-modal="false"
 | 
			
		||||
            :before-close="cancel"
 | 
			
		||||
            :show-close="true"
 | 
			
		||||
            :destroy-on-close="true"
 | 
			
		||||
            width="65%"
 | 
			
		||||
        >
 | 
			
		||||
            <page-table
 | 
			
		||||
                ref="pageTableRef"
 | 
			
		||||
                :query="queryConfig"
 | 
			
		||||
                v-model:query-form="params"
 | 
			
		||||
                :data="data.list"
 | 
			
		||||
                :columns="columns"
 | 
			
		||||
                :total="data.total"
 | 
			
		||||
                v-model:page-size="params.pageSize"
 | 
			
		||||
                v-model:page-num="params.pageNum"
 | 
			
		||||
                @pageChange="search()"
 | 
			
		||||
            >
 | 
			
		||||
                <template #machineSelect>
 | 
			
		||||
                    <el-select v-model="params.machineId" filterable placeholder="选择机器查询" style="width: 200px" clearable>
 | 
			
		||||
                        <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>
 | 
			
		||||
            </page-table>
 | 
			
		||||
        </el-dialog>
 | 
			
		||||
    </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script lang="ts" setup>
 | 
			
		||||
import { watch, ref, toRefs, reactive } from 'vue';
 | 
			
		||||
import { cronJobApi, machineApi } from '../api';
 | 
			
		||||
import PageTable from '@/components/pagetable/PageTable.vue';
 | 
			
		||||
import { TableColumn, TableQuery } from '@/components/pagetable';
 | 
			
		||||
import { CronJobExecStatusEnum } from '../enums';
 | 
			
		||||
 | 
			
		||||
const props = defineProps({
 | 
			
		||||
    visible: {
 | 
			
		||||
        type: Boolean,
 | 
			
		||||
    },
 | 
			
		||||
    data: {
 | 
			
		||||
        type: Object,
 | 
			
		||||
    },
 | 
			
		||||
    title: {
 | 
			
		||||
        type: String,
 | 
			
		||||
    },
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const emit = defineEmits(['update:visible', 'update:data', 'cancel']);
 | 
			
		||||
 | 
			
		||||
const queryConfig = [
 | 
			
		||||
    TableQuery.slot('machineSelect', '机器', 'machineSelect'),
 | 
			
		||||
    TableQuery.select('status', '状态').setOptions(Object.values(CronJobExecStatusEnum)),
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
const columns = ref([
 | 
			
		||||
    TableColumn.new('machineIp', '机器IP').setMinWidth(120),
 | 
			
		||||
    TableColumn.new('machineName', '机器名称').setMinWidth(100),
 | 
			
		||||
    TableColumn.new('status', '状态').typeTag(CronJobExecStatusEnum).setMinWidth(70),
 | 
			
		||||
    TableColumn.new('res', '执行结果').setMinWidth(250),
 | 
			
		||||
    TableColumn.new('execTime', '执行时间').isTime().setMinWidth(150),
 | 
			
		||||
]);
 | 
			
		||||
 | 
			
		||||
const state = reactive({
 | 
			
		||||
    dialogVisible: false,
 | 
			
		||||
    tags: [] as any,
 | 
			
		||||
    params: {
 | 
			
		||||
        pageNum: 1,
 | 
			
		||||
        pageSize: 10,
 | 
			
		||||
        cronJobId: 0,
 | 
			
		||||
        status: null,
 | 
			
		||||
        machineId: null,
 | 
			
		||||
    },
 | 
			
		||||
    // 列表数据
 | 
			
		||||
    data: {
 | 
			
		||||
        list: [],
 | 
			
		||||
        total: 10,
 | 
			
		||||
    },
 | 
			
		||||
    machines: [],
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const machineMap: Map<number, any> = new Map();
 | 
			
		||||
 | 
			
		||||
const { dialogVisible, params, data } = toRefs(state);
 | 
			
		||||
 | 
			
		||||
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 res = await cronJobApi.execList.request(state.params);
 | 
			
		||||
    if (!res.list) {
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // 填充机器信息
 | 
			
		||||
    for (let x of res.list) {
 | 
			
		||||
        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;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    state.data = res;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const cancel = () => {
 | 
			
		||||
    emit('update:visible', false);
 | 
			
		||||
    setTimeout(() => {
 | 
			
		||||
        initData();
 | 
			
		||||
    }, 500);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const initData = () => {
 | 
			
		||||
    state.data.list = [];
 | 
			
		||||
    state.data.total = 0;
 | 
			
		||||
    state.params.pageNum = 1;
 | 
			
		||||
    state.params.machineId = null;
 | 
			
		||||
    state.params.status = null;
 | 
			
		||||
};
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style>
 | 
			
		||||
.el-dialog__body {
 | 
			
		||||
    padding: 2px 2px;
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
							
								
								
									
										150
									
								
								mayfly_go_web/src/views/ops/machine/cronjob/CronJobList.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										150
									
								
								mayfly_go_web/src/views/ops/machine/cronjob/CronJobList.vue
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,150 @@
 | 
			
		||||
<template>
 | 
			
		||||
    <div>
 | 
			
		||||
        <page-table
 | 
			
		||||
            ref="pageTableRef"
 | 
			
		||||
            :query="queryConfig"
 | 
			
		||||
            v-model:query-form="params"
 | 
			
		||||
            :show-selection="true"
 | 
			
		||||
            v-model:selection-data="state.selectionData"
 | 
			
		||||
            :data="data.list"
 | 
			
		||||
            :columns="columns"
 | 
			
		||||
            :total="data.total"
 | 
			
		||||
            v-model:page-size="params.pageSize"
 | 
			
		||||
            v-model:page-num="params.pageNum"
 | 
			
		||||
            @pageChange="search()"
 | 
			
		||||
        >
 | 
			
		||||
            <template #queryRight>
 | 
			
		||||
                <el-button v-auth="perms.saveCronJob" type="primary" icon="plus" @click="openFormDialog(false)" plain>添加 </el-button>
 | 
			
		||||
                <el-button v-auth="perms.delCronJob" :disabled="selectionData.length < 1" @click="deleteCronJob()" type="danger" icon="delete">删除</el-button>
 | 
			
		||||
            </template>
 | 
			
		||||
 | 
			
		||||
            <template #running="{ data }">
 | 
			
		||||
                <el-tag v-if="data.running" type="success" effect="plain">运行中</el-tag>
 | 
			
		||||
                <el-tag v-else type="danger" effect="plain">未运行</el-tag>
 | 
			
		||||
            </template>
 | 
			
		||||
 | 
			
		||||
            <template #action="{ data }">
 | 
			
		||||
                <el-button v-auth="perms.saveCronJob" type="primary" @click="openFormDialog(data)" link>编辑</el-button>
 | 
			
		||||
                <el-button type="primary" @click="showExec(data)" link>执行记录</el-button>
 | 
			
		||||
            </template>
 | 
			
		||||
        </page-table>
 | 
			
		||||
 | 
			
		||||
        <CronJobEdit v-model:visible="cronJobEdit.visible" v-model:data="cronJobEdit.data" :title="cronJobEdit.title" @submit-success="search" />
 | 
			
		||||
        <CronJobExecList v-model:visible="execDialog.visible" :data="execDialog.data" />
 | 
			
		||||
    </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script lang="ts" setup>
 | 
			
		||||
import { ref, toRefs, reactive, onMounted, defineAsyncComponent } from 'vue';
 | 
			
		||||
import { ElMessage, ElMessageBox } from 'element-plus';
 | 
			
		||||
import { cronJobApi } from '../api';
 | 
			
		||||
import PageTable from '@/components/pagetable/PageTable.vue';
 | 
			
		||||
import { TableColumn, TableQuery } from '@/components/pagetable';
 | 
			
		||||
import { CronJobStatusEnum, CronJobSaveExecResTypeEnum } from '../enums';
 | 
			
		||||
 | 
			
		||||
const CronJobEdit = defineAsyncComponent(() => import('./CronJobEdit.vue'));
 | 
			
		||||
const CronJobExecList = defineAsyncComponent(() => import('./CronJobExecList.vue'));
 | 
			
		||||
 | 
			
		||||
const pageTableRef: any = ref(null);
 | 
			
		||||
 | 
			
		||||
const perms = {
 | 
			
		||||
    saveCronJob: 'machine:cronjob:save',
 | 
			
		||||
    delCronJob: 'machine:cronjob:del',
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const queryConfig = [TableQuery.text('name', '名称'), TableQuery.select('status', '状态').setOptions(Object.values(CronJobStatusEnum))];
 | 
			
		||||
 | 
			
		||||
const columns = ref([
 | 
			
		||||
    TableColumn.new('key', 'key'),
 | 
			
		||||
    TableColumn.new('name', '名称'),
 | 
			
		||||
    TableColumn.new('cron', 'cron'),
 | 
			
		||||
    TableColumn.new('script', '脚本').canBeautify(),
 | 
			
		||||
    TableColumn.new('status', '状态').typeTag(CronJobStatusEnum),
 | 
			
		||||
    TableColumn.new('running', '运行状态').isSlot(),
 | 
			
		||||
    TableColumn.new('saveExecResType', '记录类型').typeTag(CronJobSaveExecResTypeEnum),
 | 
			
		||||
    TableColumn.new('remark', '备注'),
 | 
			
		||||
    TableColumn.new('action', '操作').isSlot().setMinWidth(180).fixedRight().alignCenter(),
 | 
			
		||||
]);
 | 
			
		||||
 | 
			
		||||
const state = reactive({
 | 
			
		||||
    params: {
 | 
			
		||||
        pageNum: 1,
 | 
			
		||||
        pageSize: 10,
 | 
			
		||||
        ip: null,
 | 
			
		||||
        name: null,
 | 
			
		||||
    },
 | 
			
		||||
    // 列表数据
 | 
			
		||||
    data: {
 | 
			
		||||
        list: [],
 | 
			
		||||
        total: 10,
 | 
			
		||||
    },
 | 
			
		||||
    selectionData: [],
 | 
			
		||||
    execDialog: {
 | 
			
		||||
        visible: false,
 | 
			
		||||
        total: 0,
 | 
			
		||||
        data: [] as any,
 | 
			
		||||
    },
 | 
			
		||||
    cronJobEdit: {
 | 
			
		||||
        visible: false,
 | 
			
		||||
        data: null as any,
 | 
			
		||||
        title: '新增机器',
 | 
			
		||||
    },
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const { selectionData, params, data, execDialog, cronJobEdit } = toRefs(state);
 | 
			
		||||
 | 
			
		||||
onMounted(async () => {
 | 
			
		||||
    search();
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const openFormDialog = async (data: any) => {
 | 
			
		||||
    let dialogTitle;
 | 
			
		||||
    if (data) {
 | 
			
		||||
        state.cronJobEdit.data = data;
 | 
			
		||||
        dialogTitle = '编辑计划任务';
 | 
			
		||||
    } else {
 | 
			
		||||
        state.cronJobEdit.data = null;
 | 
			
		||||
        dialogTitle = '添加计划任务';
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    state.cronJobEdit.title = dialogTitle;
 | 
			
		||||
    state.cronJobEdit.visible = true;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const deleteCronJob = async () => {
 | 
			
		||||
    try {
 | 
			
		||||
        await ElMessageBox.confirm(`确定删除【${state.selectionData.map((x: any) => x.name).join(', ')}】计划任务信息? 该操作将同时删除执行记录`, '提示', {
 | 
			
		||||
            confirmButtonText: '确定',
 | 
			
		||||
            cancelButtonText: '取消',
 | 
			
		||||
            type: 'warning',
 | 
			
		||||
        });
 | 
			
		||||
        await cronJobApi.delete.request({ id: state.selectionData.map((x: any) => x.id).join(',') });
 | 
			
		||||
        ElMessage.success('操作成功');
 | 
			
		||||
        search();
 | 
			
		||||
    } catch (err) {}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 显示计划任务执行记录
 | 
			
		||||
 */
 | 
			
		||||
const showExec = async (data: any) => {
 | 
			
		||||
    state.execDialog.data = data;
 | 
			
		||||
    state.execDialog.visible = true;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const search = async () => {
 | 
			
		||||
    try {
 | 
			
		||||
        pageTableRef.value.loading(true);
 | 
			
		||||
        const res = await cronJobApi.list.request(state.params);
 | 
			
		||||
        state.data = res;
 | 
			
		||||
    } finally {
 | 
			
		||||
        pageTableRef.value.loading(false);
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style>
 | 
			
		||||
.el-dialog__body {
 | 
			
		||||
    padding: 2px 2px;
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
@@ -1,8 +1,45 @@
 | 
			
		||||
import { Enum } from '@/common/Enum';
 | 
			
		||||
import { EnumValue } from '@/common/Enum';
 | 
			
		||||
 | 
			
		||||
export default {
 | 
			
		||||
    // uri请求方法
 | 
			
		||||
    scriptTypeEnum: new Enum().add('RESULT', '有结果', 1).add('NO_RESULT', '无结果', 2).add('REAL_TIME', '实时交互', 3),
 | 
			
		||||
    // 文件类型枚举
 | 
			
		||||
    FileTypeEnum: new Enum().add('DIRECTORY', '目录', 1).add('FILE', '文件', 2),
 | 
			
		||||
}
 | 
			
		||||
// 脚本执行结果类型
 | 
			
		||||
export const ScriptResultEnum = {
 | 
			
		||||
    Result: EnumValue.of(1, '有结果'),
 | 
			
		||||
    NoResult: EnumValue.of(2, '无结果'),
 | 
			
		||||
    RealTime: EnumValue.of(3, '实时交互'),
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// 脚本类型
 | 
			
		||||
export const ScriptTypeEnum = {
 | 
			
		||||
    Private: EnumValue.of(1, '私有'),
 | 
			
		||||
    Public: EnumValue.of(2, '公共'),
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// 文件类型枚举
 | 
			
		||||
export const FileTypeEnum = {
 | 
			
		||||
    Directory: EnumValue.of(1, '目录'),
 | 
			
		||||
    File: EnumValue.of(2, '文件'),
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// 授权凭证认证方式枚举
 | 
			
		||||
export const AuthMethodEnum = {
 | 
			
		||||
    Password: EnumValue.of(1, '密码').tagTypeSuccess(),
 | 
			
		||||
    PrivateKey: EnumValue.of(2, '秘钥'),
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// 计划任务状态
 | 
			
		||||
export const CronJobStatusEnum = {
 | 
			
		||||
    Enable: EnumValue.of(1, '启用').tagTypeSuccess(),
 | 
			
		||||
    Disable: EnumValue.of(-1, '禁用').tagTypeDanger(),
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// 计划任务保存执行结果类型
 | 
			
		||||
export const CronJobSaveExecResTypeEnum = {
 | 
			
		||||
    No: EnumValue.of(-1, '不记录').tagTypeDanger(),
 | 
			
		||||
    OnError: EnumValue.of(1, '错误时记录').tagTypeWarning(),
 | 
			
		||||
    Yes: EnumValue.of(2, '记录').tagTypeSuccess(),
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// 计划任务执行记录状态
 | 
			
		||||
export const CronJobExecStatusEnum = {
 | 
			
		||||
    Error: EnumValue.of(-1, '错误').tagTypeDanger(),
 | 
			
		||||
    Success: EnumValue.of(1, '成功').tagTypeSuccess(),
 | 
			
		||||
};
 | 
			
		||||
 
 | 
			
		||||
@@ -1 +1 @@
 | 
			
		||||
export { default } from './MachineList.vue';
 | 
			
		||||
export { default } from './MachineList.vue';
 | 
			
		||||
 
 | 
			
		||||
@@ -10,11 +10,9 @@
 | 
			
		||||
                                    <SvgIcon name="iconfont icon-op-mongo" :size="18" />
 | 
			
		||||
                                </template>
 | 
			
		||||
                                <template #default>
 | 
			
		||||
                                    <el-form class="instances-pop-form" label-width="50px" :size="'small'">
 | 
			
		||||
                                        <el-form class="instances-pop-form" label-width="55px" :size="'small'">
 | 
			
		||||
                                            <el-form-item label="名称:">{{ data.params.name }}</el-form-item>
 | 
			
		||||
                                            <el-form-item label="链接:">{{ data.params.uri }}</el-form-item>
 | 
			
		||||
                                        </el-form>
 | 
			
		||||
                                    <el-form class="instances-pop-form" label-width="auto" :size="'small'">
 | 
			
		||||
                                        <el-form-item label="名称:">{{ data.params.name }}</el-form-item>
 | 
			
		||||
                                        <el-form-item label="链接:">{{ data.params.uri }}</el-form-item>
 | 
			
		||||
                                    </el-form>
 | 
			
		||||
                                </template>
 | 
			
		||||
                            </el-popover>
 | 
			
		||||
@@ -22,16 +20,13 @@
 | 
			
		||||
 | 
			
		||||
                        <SvgIcon v-if="data.type == NodeType.Dbs" name="Coin" color="#67c23a" />
 | 
			
		||||
 | 
			
		||||
                        <SvgIcon v-if="data.type == NodeType.Coll || data.type == NodeType.CollMenu" name="Document"
 | 
			
		||||
                            class="color-primary" />
 | 
			
		||||
                        <SvgIcon v-if="data.type == NodeType.Coll || data.type == NodeType.CollMenu" name="Document" class="color-primary" />
 | 
			
		||||
                    </template>
 | 
			
		||||
 | 
			
		||||
                    <template #label="{ data }">
 | 
			
		||||
                        <span v-if="data.type == NodeType.Dbs">
 | 
			
		||||
                            {{ data.params.dbName }}
 | 
			
		||||
                            <span style="color: #8492a6;font-size: 13px">
 | 
			
		||||
                                [{{ formatByteSize(data.params.size) }}]
 | 
			
		||||
                            </span>
 | 
			
		||||
                            <span style="color: #8492a6; font-size: 13px"> [{{ formatByteSize(data.params.size) }}] </span>
 | 
			
		||||
                        </span>
 | 
			
		||||
 | 
			
		||||
                        <span v-else>{{ data.label }}</span>
 | 
			
		||||
@@ -46,17 +41,17 @@
 | 
			
		||||
                            <el-row>
 | 
			
		||||
                                <el-col :span="2">
 | 
			
		||||
                                    <div>
 | 
			
		||||
                                        <el-link @click="findCommand(state.activeName)" icon="refresh" :underline="false"
 | 
			
		||||
                                            class="">
 | 
			
		||||
                                        </el-link>
 | 
			
		||||
                                        <el-link @click="onEditDoc(null)" class="ml5" type="primary" icon="plus"
 | 
			
		||||
                                            :underline="false">
 | 
			
		||||
                                        </el-link>
 | 
			
		||||
                                        <el-link @click="findCommand(state.activeName)" icon="refresh" :underline="false" class=""> </el-link>
 | 
			
		||||
                                        <el-link @click="onEditDoc(null)" class="ml5" type="primary" icon="plus" :underline="false"> </el-link>
 | 
			
		||||
                                    </div>
 | 
			
		||||
                                </el-col>
 | 
			
		||||
                                <el-col :span="22">
 | 
			
		||||
                                    <el-input ref="findParamInputRef" v-model="dt.findParamStr" placeholder="点击输入相应查询条件"
 | 
			
		||||
                                        @focus="showFindDialog(dt.key)">
 | 
			
		||||
                                    <el-input
 | 
			
		||||
                                        ref="findParamInputRef"
 | 
			
		||||
                                        v-model="dt.findParamStr"
 | 
			
		||||
                                        placeholder="点击输入相应查询条件"
 | 
			
		||||
                                        @focus="showFindDialog(dt.key)"
 | 
			
		||||
                                    >
 | 
			
		||||
                                        <template #prepend>查询参数</template>
 | 
			
		||||
                                    </el-input>
 | 
			
		||||
                                </el-col>
 | 
			
		||||
@@ -67,8 +62,7 @@
 | 
			
		||||
                                        <el-input type="textarea" v-model="item.value" :rows="10" />
 | 
			
		||||
                                        <div style="padding: 3px; float: right" class="mr5 mongo-doc-btns">
 | 
			
		||||
                                            <div>
 | 
			
		||||
                                                <el-link @click="onEditDoc(item)" :underline="false" type="success"
 | 
			
		||||
                                                    icon="MagicStick"></el-link>
 | 
			
		||||
                                                <el-link @click="onEditDoc(item)" :underline="false" type="success" icon="MagicStick"></el-link>
 | 
			
		||||
 | 
			
		||||
                                                <!-- <el-divider direction="vertical" border-style="dashed" /> -->
 | 
			
		||||
 | 
			
		||||
@@ -79,8 +73,7 @@
 | 
			
		||||
 | 
			
		||||
                                                <el-popconfirm @confirm="onDeleteDoc(item.value)" title="确定删除该文档?">
 | 
			
		||||
                                                    <template #reference>
 | 
			
		||||
                                                        <el-link :underline="false" type="danger" icon="DocumentDelete">
 | 
			
		||||
                                                        </el-link>
 | 
			
		||||
                                                        <el-link :underline="false" type="danger" icon="DocumentDelete"> </el-link>
 | 
			
		||||
                                                    </template>
 | 
			
		||||
                                                </el-popconfirm>
 | 
			
		||||
                                            </div>
 | 
			
		||||
@@ -92,18 +85,15 @@
 | 
			
		||||
                    </el-tabs>
 | 
			
		||||
                </el-container>
 | 
			
		||||
            </el-col>
 | 
			
		||||
 | 
			
		||||
        </el-row>
 | 
			
		||||
 | 
			
		||||
        <el-dialog width="600px" title="find参数" v-model="findDialog.visible">
 | 
			
		||||
            <el-form label-width="70px">
 | 
			
		||||
            <el-form label-width="auto">
 | 
			
		||||
                <el-form-item label="filter">
 | 
			
		||||
                    <monaco-editor style="width: 100%;" height="150px" ref="monacoEditorRef"
 | 
			
		||||
                        v-model="findDialog.findParam.filter" language="json" />
 | 
			
		||||
                    <monaco-editor style="width: 100%" height="150px" ref="monacoEditorRef" v-model="findDialog.findParam.filter" language="json" />
 | 
			
		||||
                </el-form-item>
 | 
			
		||||
                <el-form-item label="sort">
 | 
			
		||||
                    <el-input v-model="findDialog.findParam.sort" type="textarea" :rows="3" clearable
 | 
			
		||||
                        auto-complete="off"></el-input>
 | 
			
		||||
                    <el-input v-model="findDialog.findParam.sort" type="textarea" :rows="3" clearable auto-complete="off"></el-input>
 | 
			
		||||
                </el-form-item>
 | 
			
		||||
                <el-form-item label="limit">
 | 
			
		||||
                    <el-input v-model.number="findDialog.findParam.limit" type="number" auto-complete="off"></el-input>
 | 
			
		||||
@@ -120,8 +110,12 @@
 | 
			
		||||
            </template>
 | 
			
		||||
        </el-dialog>
 | 
			
		||||
 | 
			
		||||
        <el-dialog width="60%" :title="`${state.docEditDialog.isAdd ? '新增' : '修改'}'${state.activeName}'集合文档`"
 | 
			
		||||
            v-model="docEditDialog.visible" :close-on-click-modal="false">
 | 
			
		||||
        <el-dialog
 | 
			
		||||
            width="60%"
 | 
			
		||||
            :title="`${state.docEditDialog.isAdd ? '新增' : '修改'}'${state.activeName}'集合文档`"
 | 
			
		||||
            v-model="docEditDialog.visible"
 | 
			
		||||
            :close-on-click-modal="false"
 | 
			
		||||
        >
 | 
			
		||||
            <monaco-editor v-model="docEditDialog.doc" language="json" />
 | 
			
		||||
            <template #footer>
 | 
			
		||||
                <div>
 | 
			
		||||
@@ -151,10 +145,10 @@ const MonacoEditor = defineAsyncComponent(() => import('@/components/monaco/Mona
 | 
			
		||||
 * 树节点类型
 | 
			
		||||
 */
 | 
			
		||||
class NodeType {
 | 
			
		||||
    static Mongo = 1
 | 
			
		||||
    static Dbs = 2
 | 
			
		||||
    static CollMenu = 3
 | 
			
		||||
    static Coll = 4
 | 
			
		||||
    static Mongo = 1;
 | 
			
		||||
    static Dbs = 2;
 | 
			
		||||
    static CollMenu = 3;
 | 
			
		||||
    static Coll = 4;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const findParamInputRef: any = ref(null);
 | 
			
		||||
@@ -189,11 +183,7 @@ const state = reactive({
 | 
			
		||||
    },
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const {
 | 
			
		||||
    dataHeight,
 | 
			
		||||
    findDialog,
 | 
			
		||||
    docEditDialog,
 | 
			
		||||
} = toRefs(state)
 | 
			
		||||
const { dataHeight, findDialog, docEditDialog } = toRefs(state);
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * instmap;  tagPaht -> mongo info[]
 | 
			
		||||
@@ -201,15 +191,15 @@ const {
 | 
			
		||||
const instMap: Map<string, any[]> = new Map();
 | 
			
		||||
 | 
			
		||||
const getInsts = async () => {
 | 
			
		||||
    const res = await mongoApi.mongoList.request({ pageNum: 1, pageSize: 1000, });
 | 
			
		||||
    if (!res.total) return
 | 
			
		||||
    const res = await mongoApi.mongoList.request({ pageNum: 1, pageSize: 1000 });
 | 
			
		||||
    if (!res.total) return;
 | 
			
		||||
    for (const mongoInfo of res.list) {
 | 
			
		||||
        const tagPath = mongoInfo.tagPath;
 | 
			
		||||
        let mongoInsts = instMap.get(tagPath) || [];
 | 
			
		||||
        mongoInsts.push(mongoInfo);
 | 
			
		||||
        instMap.set(tagPath, mongoInsts);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 加载文件树节点
 | 
			
		||||
@@ -234,7 +224,7 @@ const loadNode = async (node: any) => {
 | 
			
		||||
 | 
			
		||||
    // 点击标签 -> 显示mongo信息列表
 | 
			
		||||
    if (nodeType === TagTreeNode.TagPath) {
 | 
			
		||||
        const mongoInfos = instMap.get(data.key)
 | 
			
		||||
        const mongoInfos = instMap.get(data.key);
 | 
			
		||||
        return mongoInfos?.map((x: any) => {
 | 
			
		||||
            return new TagTreeNode(`${data.key}.${x.id}`, x.name, NodeType.Mongo).withParams(x);
 | 
			
		||||
        });
 | 
			
		||||
@@ -252,7 +242,7 @@ const loadNode = async (node: any) => {
 | 
			
		||||
 | 
			
		||||
    // 点击数据库集合节点 -> 加载集合列表
 | 
			
		||||
    if (nodeType === NodeType.CollMenu) {
 | 
			
		||||
        return await getCollections(params.id, params.dbName)
 | 
			
		||||
        return await getCollections(params.id, params.dbName);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return [];
 | 
			
		||||
@@ -270,9 +260,9 @@ const getDatabases = async (inst: any) => {
 | 
			
		||||
            id: inst.id,
 | 
			
		||||
            dbName,
 | 
			
		||||
            size: x.SizeOnDisk,
 | 
			
		||||
        })
 | 
			
		||||
    })
 | 
			
		||||
}
 | 
			
		||||
        });
 | 
			
		||||
    });
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 获取集合列表信息
 | 
			
		||||
@@ -287,7 +277,7 @@ const getCollections = async (id: any, database: string) => {
 | 
			
		||||
            collection: x,
 | 
			
		||||
        });
 | 
			
		||||
    });
 | 
			
		||||
}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const nodeClick = (data: any) => {
 | 
			
		||||
    // 点击集合
 | 
			
		||||
@@ -295,7 +285,7 @@ const nodeClick = (data: any) => {
 | 
			
		||||
        const { id, database, collection } = data.params;
 | 
			
		||||
        changeCollection(id, database, collection);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const changeCollection = (id: any, schema: string, collection: string) => {
 | 
			
		||||
    const label = `${id}:\`${schema}\`.${collection}`;
 | 
			
		||||
@@ -407,7 +397,7 @@ const onEditDoc = async (item: any) => {
 | 
			
		||||
    state.docEditDialog.isAdd = false;
 | 
			
		||||
    state.docEditDialog.doc = item.value;
 | 
			
		||||
    state.docEditDialog.visible = true;
 | 
			
		||||
}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const onSaveDoc = async () => {
 | 
			
		||||
    if (state.docEditDialog.isAdd) {
 | 
			
		||||
@@ -490,9 +480,8 @@ const removeDataTab = (targetName: string) => {
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const getNowDataTab = () => {
 | 
			
		||||
    return state.dataTabs[state.activeName]
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
    return state.dataTabs[state.activeName];
 | 
			
		||||
};
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style lang="scss">
 | 
			
		||||
 
 | 
			
		||||
@@ -1,20 +1,24 @@
 | 
			
		||||
<template>
 | 
			
		||||
    <div>
 | 
			
		||||
        <el-dialog :title="title" v-model="dialogVisible" :before-close="cancel" :close-on-click-modal="false" width="38%"
 | 
			
		||||
            :destroy-on-close="true">
 | 
			
		||||
        <el-dialog :title="title" v-model="dialogVisible" :before-close="cancel" :close-on-click-modal="false" width="38%" :destroy-on-close="true">
 | 
			
		||||
            <el-form :model="form" ref="mongoForm" :rules="rules" label-width="85px">
 | 
			
		||||
                <el-tabs v-model="tabActiveName">
 | 
			
		||||
                    <el-tab-pane label="基础信息" name="basic">
 | 
			
		||||
                        <el-form-item prop="tagId" label="标签:" required>
 | 
			
		||||
                            <tag-select v-model:tag-id="form.tagId" v-model:tag-path="form.tagPath" style="width: 100%" />
 | 
			
		||||
                            <tag-select v-model="form.tagId" v-model:tag-path="form.tagPath" style="width: 100%" />
 | 
			
		||||
                        </el-form-item>
 | 
			
		||||
 | 
			
		||||
                        <el-form-item prop="name" label="名称" required>
 | 
			
		||||
                            <el-input v-model.trim="form.name" placeholder="请输入名称" auto-complete="off"></el-input>
 | 
			
		||||
                        </el-form-item>
 | 
			
		||||
                        <el-form-item prop="uri" label="uri" required>
 | 
			
		||||
                            <el-input type="textarea" :rows="2" v-model.trim="form.uri"
 | 
			
		||||
                                placeholder="形如 mongodb://username:password@host1:port1" auto-complete="off"></el-input>
 | 
			
		||||
                            <el-input
 | 
			
		||||
                                type="textarea"
 | 
			
		||||
                                :rows="2"
 | 
			
		||||
                                v-model.trim="form.uri"
 | 
			
		||||
                                placeholder="形如 mongodb://username:password@host1:port1"
 | 
			
		||||
                                auto-complete="off"
 | 
			
		||||
                            ></el-input>
 | 
			
		||||
                        </el-form-item>
 | 
			
		||||
                    </el-tab-pane>
 | 
			
		||||
 | 
			
		||||
@@ -53,10 +57,10 @@ const props = defineProps({
 | 
			
		||||
    title: {
 | 
			
		||||
        type: String,
 | 
			
		||||
    },
 | 
			
		||||
})
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
//定义事件
 | 
			
		||||
const emit = defineEmits(['update:visible', 'cancel', 'val-change'])
 | 
			
		||||
const emit = defineEmits(['update:visible', 'cancel', 'val-change']);
 | 
			
		||||
 | 
			
		||||
const rules = {
 | 
			
		||||
    tagId: [
 | 
			
		||||
@@ -80,7 +84,7 @@ const rules = {
 | 
			
		||||
            trigger: ['change', 'blur'],
 | 
			
		||||
        },
 | 
			
		||||
    ],
 | 
			
		||||
}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const mongoForm: any = ref(null);
 | 
			
		||||
const state = reactive({
 | 
			
		||||
@@ -97,12 +101,7 @@ const state = reactive({
 | 
			
		||||
    btnLoading: false,
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const {
 | 
			
		||||
    dialogVisible,
 | 
			
		||||
    tabActiveName,
 | 
			
		||||
    form,
 | 
			
		||||
    btnLoading,
 | 
			
		||||
} = toRefs(state)
 | 
			
		||||
const { dialogVisible, tabActiveName, form, btnLoading } = toRefs(state);
 | 
			
		||||
 | 
			
		||||
watch(props, async (newValue: any) => {
 | 
			
		||||
    state.dialogVisible = newValue.visible;
 | 
			
		||||
@@ -122,7 +121,7 @@ const btnOk = async () => {
 | 
			
		||||
        if (valid) {
 | 
			
		||||
            const reqForm = { ...state.form };
 | 
			
		||||
            if (!state.form.sshTunnelMachineId || state.form.sshTunnelMachineId <= 0) {
 | 
			
		||||
                reqForm.sshTunnelMachineId = -1
 | 
			
		||||
                reqForm.sshTunnelMachineId = -1;
 | 
			
		||||
            }
 | 
			
		||||
            // reqForm.uri = await RsaEncrypt(reqForm.uri);
 | 
			
		||||
            mongoApi.saveMongo.request(reqForm).then(() => {
 | 
			
		||||
 
 | 
			
		||||
@@ -1,59 +1,42 @@
 | 
			
		||||
<template>
 | 
			
		||||
    <div>
 | 
			
		||||
        <el-card>
 | 
			
		||||
            <el-button type="primary" icon="plus" @click="editMongo(true)" plain>添加</el-button>
 | 
			
		||||
            <el-button type="primary" icon="edit" :disabled="currentId == null" @click="editMongo(false)" plain>编辑
 | 
			
		||||
            </el-button>
 | 
			
		||||
            <el-button type="danger" icon="delete" :disabled="currentId == null" @click="deleteMongo" plain>删除
 | 
			
		||||
            </el-button>
 | 
			
		||||
            <div style="float: right">
 | 
			
		||||
                <el-select @focus="getTags" v-model="query.tagPath" placeholder="请选择标签" filterable clearable>
 | 
			
		||||
        <page-table
 | 
			
		||||
            ref="pageTableRef"
 | 
			
		||||
            :query="queryConfig"
 | 
			
		||||
            v-model:query-form="query"
 | 
			
		||||
            :show-selection="true"
 | 
			
		||||
            v-model:selection-data="selectionData"
 | 
			
		||||
            :data="list"
 | 
			
		||||
            :columns="columns"
 | 
			
		||||
            :total="total"
 | 
			
		||||
            v-model:page-size="query.pageSize"
 | 
			
		||||
            v-model:page-num="query.pageNum"
 | 
			
		||||
            @pageChange="search()"
 | 
			
		||||
        >
 | 
			
		||||
            <template #tagPathSelect>
 | 
			
		||||
                <el-select @focus="getTags" v-model="query.tagPath" placeholder="请选择标签" @clear="search" filterable clearable style="width: 200px">
 | 
			
		||||
                    <el-option v-for="item in tags" :key="item" :label="item" :value="item"> </el-option>
 | 
			
		||||
                </el-select>
 | 
			
		||||
                <el-button class="ml5" @click="search" type="success" icon="search"></el-button>
 | 
			
		||||
            </div>
 | 
			
		||||
            <el-table :data="list" style="width: 100%" @current-change="choose" stripe>
 | 
			
		||||
                <el-table-column label="选择" width="60px">
 | 
			
		||||
                    <template #default="scope">
 | 
			
		||||
                        <el-radio v-model="currentId" :label="scope.row.id">
 | 
			
		||||
                            <i></i>
 | 
			
		||||
                        </el-radio>
 | 
			
		||||
                    </template>
 | 
			
		||||
                </el-table-column>
 | 
			
		||||
                <el-table-column prop="tagPath" label="标签路径" min-width="150" show-overflow-tooltip>
 | 
			
		||||
                    <template #default="scope">
 | 
			
		||||
                        <tag-info :tag-path="scope.row.tagPath" />
 | 
			
		||||
                        <span class="ml5">
 | 
			
		||||
                            {{ scope.row.tagPath }}
 | 
			
		||||
                        </span>
 | 
			
		||||
                    </template>
 | 
			
		||||
                </el-table-column>
 | 
			
		||||
                <el-table-column prop="name" label="名称" width></el-table-column>
 | 
			
		||||
                <el-table-column prop="uri" label="连接uri" min-width="150" show-overflow-tooltip>
 | 
			
		||||
                    <template #default="scope">
 | 
			
		||||
                        {{ scope.row.uri.split('@')[1] }}
 | 
			
		||||
                    </template>
 | 
			
		||||
                </el-table-column>
 | 
			
		||||
                <el-table-column prop="createTime" label="创建时间" min-width="150">
 | 
			
		||||
                    <template #default="scope">
 | 
			
		||||
                        {{ dateFormat(scope.row.createTime) }}
 | 
			
		||||
                    </template>
 | 
			
		||||
                </el-table-column>
 | 
			
		||||
                <el-table-column prop="creator" label="创建人"></el-table-column>
 | 
			
		||||
            </template>
 | 
			
		||||
 | 
			
		||||
                <el-table-column label="操作" width>
 | 
			
		||||
                    <template #default="scope">
 | 
			
		||||
                        <el-link type="primary" @click="showDatabases(scope.row.id)" plain size="small"
 | 
			
		||||
                            :underline="false">数据库</el-link>
 | 
			
		||||
                    </template>
 | 
			
		||||
                </el-table-column>
 | 
			
		||||
            </el-table>
 | 
			
		||||
            <el-row style="margin-top: 20px" type="flex" justify="end">
 | 
			
		||||
                <el-pagination style="text-align: right" @current-change="handlePageChange" :total="total"
 | 
			
		||||
                    layout="prev, pager, next, total, jumper" v-model:current-page="query.pageNum"
 | 
			
		||||
                    :page-size="query.pageSize"></el-pagination>
 | 
			
		||||
            </el-row>
 | 
			
		||||
        </el-card>
 | 
			
		||||
            <template #queryRight>
 | 
			
		||||
                <el-button type="primary" icon="plus" @click="editMongo(true)" plain>添加</el-button>
 | 
			
		||||
                <el-button type="danger" icon="delete" :disabled="selectionData.length < 1" @click="deleteMongo" plain>删除 </el-button>
 | 
			
		||||
            </template>
 | 
			
		||||
 | 
			
		||||
            <template #tagPath="{ data }">
 | 
			
		||||
                <tag-info :tag-path="data.tagPath" />
 | 
			
		||||
                <span class="ml5">
 | 
			
		||||
                    {{ data.tagPath }}
 | 
			
		||||
                </span>
 | 
			
		||||
            </template>
 | 
			
		||||
 | 
			
		||||
            <template #action="{ data }">
 | 
			
		||||
                <el-button @click="showDatabases(data.id)" link>数据库</el-button>
 | 
			
		||||
 | 
			
		||||
                <el-button type="primary" @click="editMongo(data)" link>编辑</el-button>
 | 
			
		||||
            </template>
 | 
			
		||||
        </page-table>
 | 
			
		||||
 | 
			
		||||
        <el-dialog width="800px" :title="databaseDialog.title" v-model="databaseDialog.visible">
 | 
			
		||||
            <el-table :data="databaseDialog.data" size="small">
 | 
			
		||||
@@ -67,17 +50,14 @@
 | 
			
		||||
 | 
			
		||||
                <el-table-column min-width="150" label="操作">
 | 
			
		||||
                    <template #default="scope">
 | 
			
		||||
                        <el-link type="success" @click="showDatabaseStats(scope.row.Name)" plain size="small"
 | 
			
		||||
                            :underline="false">stats</el-link>
 | 
			
		||||
                        <el-link type="success" @click="showDatabaseStats(scope.row.Name)" plain size="small" :underline="false">stats</el-link>
 | 
			
		||||
                        <el-divider direction="vertical" border-style="dashed" />
 | 
			
		||||
                        <el-link type="primary" @click="showCollections(scope.row.Name)" plain size="small"
 | 
			
		||||
                            :underline="false">集合</el-link>
 | 
			
		||||
                        <el-link type="primary" @click="showCollections(scope.row.Name)" plain size="small" :underline="false">集合</el-link>
 | 
			
		||||
                    </template>
 | 
			
		||||
                </el-table-column>
 | 
			
		||||
            </el-table>
 | 
			
		||||
 | 
			
		||||
            <el-dialog width="700px" :title="databaseDialog.statsDialog.title"
 | 
			
		||||
                v-model="databaseDialog.statsDialog.visible">
 | 
			
		||||
            <el-dialog width="700px" :title="databaseDialog.statsDialog.title" v-model="databaseDialog.statsDialog.visible">
 | 
			
		||||
                <el-descriptions title="库状态信息" :column="3" border size="small">
 | 
			
		||||
                    <el-descriptions-item label="db" label-align="right" align="center">
 | 
			
		||||
                        {{ databaseDialog.statsDialog.data.db }}
 | 
			
		||||
@@ -126,8 +106,7 @@
 | 
			
		||||
                <el-table-column prop="name" label="名称" show-overflow-tooltip> </el-table-column>
 | 
			
		||||
                <el-table-column min-width="80" label="操作">
 | 
			
		||||
                    <template #default="scope">
 | 
			
		||||
                        <el-link type="success" @click="showCollectionStats(scope.row.name)" plain size="small"
 | 
			
		||||
                            :underline="false">stats</el-link>
 | 
			
		||||
                        <el-link type="success" @click="showCollectionStats(scope.row.name)" plain size="small" :underline="false">stats</el-link>
 | 
			
		||||
                        <el-divider direction="vertical" border-style="dashed" />
 | 
			
		||||
                        <el-popconfirm @confirm="onDeleteCollection(scope.row.name)" title="确定删除该集合?">
 | 
			
		||||
                            <template #reference>
 | 
			
		||||
@@ -138,8 +117,7 @@
 | 
			
		||||
                </el-table-column>
 | 
			
		||||
            </el-table>
 | 
			
		||||
 | 
			
		||||
            <el-dialog width="700px" :title="collectionsDialog.statsDialog.title"
 | 
			
		||||
                v-model="collectionsDialog.statsDialog.visible">
 | 
			
		||||
            <el-dialog width="700px" :title="collectionsDialog.statsDialog.title" v-model="collectionsDialog.statsDialog.visible">
 | 
			
		||||
                <el-descriptions title="集合状态信息" :column="3" border size="small">
 | 
			
		||||
                    <el-descriptions-item label="ns" label-align="right" :span="2" align="center">
 | 
			
		||||
                        {{ collectionsDialog.statsDialog.data.ns }}
 | 
			
		||||
@@ -171,7 +149,7 @@
 | 
			
		||||
        </el-dialog>
 | 
			
		||||
 | 
			
		||||
        <el-dialog width="400px" title="新建集合" v-model="createCollectionDialog.visible" :destroy-on-close="true">
 | 
			
		||||
            <el-form :model="createCollectionDialog.form" label-width="70px">
 | 
			
		||||
            <el-form :model="createCollectionDialog.form" label-width="auto">
 | 
			
		||||
                <el-form-item prop="name" label="集合名" required>
 | 
			
		||||
                    <el-input v-model="createCollectionDialog.form.name" clearable></el-input>
 | 
			
		||||
                </el-form-item>
 | 
			
		||||
@@ -187,20 +165,36 @@
 | 
			
		||||
            </template>
 | 
			
		||||
        </el-dialog>
 | 
			
		||||
 | 
			
		||||
        <mongo-edit @val-change="valChange" :title="mongoEditDialog.title" v-model:visible="mongoEditDialog.visible"
 | 
			
		||||
            v-model:mongo="mongoEditDialog.data"></mongo-edit>
 | 
			
		||||
        <mongo-edit
 | 
			
		||||
            @val-change="valChange"
 | 
			
		||||
            :title="mongoEditDialog.title"
 | 
			
		||||
            v-model:visible="mongoEditDialog.visible"
 | 
			
		||||
            v-model:mongo="mongoEditDialog.data"
 | 
			
		||||
        ></mongo-edit>
 | 
			
		||||
    </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script lang="ts" setup>
 | 
			
		||||
import { mongoApi } from './api';
 | 
			
		||||
import { toRefs, reactive, onMounted } from 'vue';
 | 
			
		||||
import { ref, toRefs, reactive, onMounted } from 'vue';
 | 
			
		||||
import { ElMessage, ElMessageBox } from 'element-plus';
 | 
			
		||||
import { tagApi } from '../tag/api';
 | 
			
		||||
import MongoEdit from './MongoEdit.vue';
 | 
			
		||||
import { formatByteSize } from '@/common/utils/format';
 | 
			
		||||
import { dateFormat } from '@/common/utils/date';
 | 
			
		||||
import TagInfo from '../component/TagInfo.vue';
 | 
			
		||||
import PageTable from '@/components/pagetable/PageTable.vue';
 | 
			
		||||
import { TableColumn, TableQuery } from '@/components/pagetable';
 | 
			
		||||
 | 
			
		||||
const pageTableRef: any = ref(null);
 | 
			
		||||
 | 
			
		||||
const queryConfig = [TableQuery.slot('tagPath', '标签', 'tagPathSelect')];
 | 
			
		||||
const columns = ref([
 | 
			
		||||
    TableColumn.new('tagPath', '标签路径').isSlot().setAddWidth(20),
 | 
			
		||||
    TableColumn.new('name', '名称'),
 | 
			
		||||
    TableColumn.new('uri', '连接uri'),
 | 
			
		||||
    TableColumn.new('createTime', '创建时间').isTime(),
 | 
			
		||||
    TableColumn.new('creator', '创建人'),
 | 
			
		||||
    TableColumn.new('action', '操作').isSlot().setMinWidth(100).fixedRight().alignCenter(),
 | 
			
		||||
]);
 | 
			
		||||
 | 
			
		||||
const state = reactive({
 | 
			
		||||
    tags: [],
 | 
			
		||||
@@ -210,8 +204,7 @@ const state = reactive({
 | 
			
		||||
    },
 | 
			
		||||
    list: [],
 | 
			
		||||
    total: 0,
 | 
			
		||||
    currentId: null,
 | 
			
		||||
    currentData: null as any,
 | 
			
		||||
    selectionData: [],
 | 
			
		||||
    query: {
 | 
			
		||||
        pageNum: 1,
 | 
			
		||||
        pageSize: 10,
 | 
			
		||||
@@ -251,38 +244,15 @@ const state = reactive({
 | 
			
		||||
    },
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const {
 | 
			
		||||
    tags,
 | 
			
		||||
    list,
 | 
			
		||||
    total,
 | 
			
		||||
    currentId,
 | 
			
		||||
    query,
 | 
			
		||||
    mongoEditDialog,
 | 
			
		||||
    databaseDialog,
 | 
			
		||||
    collectionsDialog,
 | 
			
		||||
    createCollectionDialog,
 | 
			
		||||
} = toRefs(state)
 | 
			
		||||
const { tags, list, total, selectionData, query, mongoEditDialog, databaseDialog, collectionsDialog, createCollectionDialog } = toRefs(state);
 | 
			
		||||
 | 
			
		||||
onMounted(async () => {
 | 
			
		||||
    search();
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const handlePageChange = (curPage: number) => {
 | 
			
		||||
    state.query.pageNum = curPage;
 | 
			
		||||
    search();
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const choose = (item: any) => {
 | 
			
		||||
    if (!item) {
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
    state.currentId = item.id;
 | 
			
		||||
    state.currentData = item;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const showDatabases = async (id: number) => {
 | 
			
		||||
    // state.query.tagPath = row.tagPath
 | 
			
		||||
    state.dbOps.dbId = id
 | 
			
		||||
    state.dbOps.dbId = id;
 | 
			
		||||
 | 
			
		||||
    state.databaseDialog.data = (await mongoApi.databases.request({ id })).Databases;
 | 
			
		||||
    state.databaseDialog.title = `数据库列表`;
 | 
			
		||||
@@ -291,7 +261,7 @@ const showDatabases = async (id: number) => {
 | 
			
		||||
 | 
			
		||||
const showDatabaseStats = async (dbName: string) => {
 | 
			
		||||
    state.databaseDialog.statsDialog.data = await mongoApi.runCommand.request({
 | 
			
		||||
        id: state.currentId,
 | 
			
		||||
        id: state.dbOps.dbId,
 | 
			
		||||
        database: dbName,
 | 
			
		||||
        command: {
 | 
			
		||||
            dbStats: 1,
 | 
			
		||||
@@ -310,7 +280,7 @@ const showCollections = async (database: string) => {
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const setCollections = async (database: string) => {
 | 
			
		||||
    const res = await mongoApi.collections.request({ id: state.currentId, database });
 | 
			
		||||
    const res = await mongoApi.collections.request({ id: state.dbOps.dbId, database });
 | 
			
		||||
    const collections = [] as any;
 | 
			
		||||
    for (let r of res) {
 | 
			
		||||
        collections.push({ name: r });
 | 
			
		||||
@@ -323,7 +293,7 @@ const setCollections = async (database: string) => {
 | 
			
		||||
 */
 | 
			
		||||
const showCollectionStats = async (collection: string) => {
 | 
			
		||||
    state.collectionsDialog.statsDialog.data = await mongoApi.runCommand.request({
 | 
			
		||||
        id: state.currentId,
 | 
			
		||||
        id: state.dbOps.dbId,
 | 
			
		||||
        database: state.collectionsDialog.database,
 | 
			
		||||
        command: {
 | 
			
		||||
            collStats: collection,
 | 
			
		||||
@@ -338,7 +308,7 @@ const showCollectionStats = async (collection: string) => {
 | 
			
		||||
 */
 | 
			
		||||
const onDeleteCollection = async (collection: string) => {
 | 
			
		||||
    await mongoApi.runCommand.request({
 | 
			
		||||
        id: state.currentId,
 | 
			
		||||
        id: state.dbOps.dbId,
 | 
			
		||||
        database: state.collectionsDialog.database,
 | 
			
		||||
        command: {
 | 
			
		||||
            drop: collection,
 | 
			
		||||
@@ -355,7 +325,7 @@ const showCreateCollectionDialog = () => {
 | 
			
		||||
const onCreateCollection = async () => {
 | 
			
		||||
    const form = state.createCollectionDialog.form;
 | 
			
		||||
    await mongoApi.runCommand.request({
 | 
			
		||||
        id: state.currentId,
 | 
			
		||||
        id: state.dbOps.dbId,
 | 
			
		||||
        database: state.collectionsDialog.database,
 | 
			
		||||
        command: {
 | 
			
		||||
            create: form.name,
 | 
			
		||||
@@ -369,48 +339,46 @@ const onCreateCollection = async () => {
 | 
			
		||||
 | 
			
		||||
const deleteMongo = async () => {
 | 
			
		||||
    try {
 | 
			
		||||
        await ElMessageBox.confirm(`确定删除该mongo?`, '提示', {
 | 
			
		||||
        await ElMessageBox.confirm(`确定删除【${state.selectionData.map((x: any) => x.name).join(', ')}】mongo信息?`, '提示', {
 | 
			
		||||
            confirmButtonText: '确定',
 | 
			
		||||
            cancelButtonText: '取消',
 | 
			
		||||
            type: 'warning',
 | 
			
		||||
        });
 | 
			
		||||
        await mongoApi.deleteMongo.request({ id: state.currentId });
 | 
			
		||||
        await mongoApi.deleteMongo.request({ id: state.selectionData.map((x: any) => x.id).join(',') });
 | 
			
		||||
        ElMessage.success('删除成功');
 | 
			
		||||
        state.currentData = null;
 | 
			
		||||
        state.currentId = null;
 | 
			
		||||
        search();
 | 
			
		||||
    } catch (err) { }
 | 
			
		||||
    } catch (err) {}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const search = async () => {
 | 
			
		||||
    const res = await mongoApi.mongoList.request(state.query);
 | 
			
		||||
    state.list = res.list;
 | 
			
		||||
    state.total = res.total;
 | 
			
		||||
    try {
 | 
			
		||||
        pageTableRef.value.loading(true);
 | 
			
		||||
        const res = await mongoApi.mongoList.request(state.query);
 | 
			
		||||
        state.list = res.list;
 | 
			
		||||
        state.total = res.total;
 | 
			
		||||
    } finally {
 | 
			
		||||
        pageTableRef.value.loading(false);
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const getTags = async () => {
 | 
			
		||||
    state.tags = await tagApi.getAccountTags.request(null);
 | 
			
		||||
    state.tags = await mongoApi.mongoTags.request(null);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const editMongo = async (isAdd = false) => {
 | 
			
		||||
    if (isAdd) {
 | 
			
		||||
const editMongo = async (data: any) => {
 | 
			
		||||
    if (!data) {
 | 
			
		||||
        state.mongoEditDialog.data = null;
 | 
			
		||||
        state.mongoEditDialog.title = '新增mongo';
 | 
			
		||||
    } else {
 | 
			
		||||
        state.mongoEditDialog.data = state.currentData;
 | 
			
		||||
        state.mongoEditDialog.data = data;
 | 
			
		||||
        state.mongoEditDialog.title = '修改mongo';
 | 
			
		||||
    }
 | 
			
		||||
    state.mongoEditDialog.visible = true;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const valChange = () => {
 | 
			
		||||
    state.currentId = null;
 | 
			
		||||
    state.currentData = null;
 | 
			
		||||
    search();
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style>
 | 
			
		||||
 | 
			
		||||
</style>
 | 
			
		||||
<style></style>
 | 
			
		||||
 
 | 
			
		||||
@@ -1,14 +1,15 @@
 | 
			
		||||
import Api from '@/common/Api';
 | 
			
		||||
 | 
			
		||||
export const mongoApi = {
 | 
			
		||||
    mongoList : Api.newGet("/mongos"),
 | 
			
		||||
    saveMongo : Api.newPost("/mongos"),
 | 
			
		||||
    deleteMongo : Api.newDelete("/mongos/{id}"),
 | 
			
		||||
    databases: Api.newGet("/mongos/{id}/databases"),
 | 
			
		||||
    collections: Api.newGet("/mongos/{id}/collections"),
 | 
			
		||||
    runCommand: Api.newPost("/mongos/{id}/run-command"),
 | 
			
		||||
    findCommand: Api.newPost("/mongos/{id}/command/find"),
 | 
			
		||||
    updateByIdCommand: Api.newPost("/mongos/{id}/command/update-by-id"),
 | 
			
		||||
    deleteByIdCommand: Api.newPost("/mongos/{id}/command/delete-by-id"),
 | 
			
		||||
    insertCommand: Api.newPost("/mongos/{id}/command/insert"),
 | 
			
		||||
}
 | 
			
		||||
    mongoList: Api.newGet('/mongos'),
 | 
			
		||||
    mongoTags: Api.newGet('/mongos/tags'),
 | 
			
		||||
    saveMongo: Api.newPost('/mongos'),
 | 
			
		||||
    deleteMongo: Api.newDelete('/mongos/{id}'),
 | 
			
		||||
    databases: Api.newGet('/mongos/{id}/databases'),
 | 
			
		||||
    collections: Api.newGet('/mongos/{id}/collections'),
 | 
			
		||||
    runCommand: Api.newPost('/mongos/{id}/run-command'),
 | 
			
		||||
    findCommand: Api.newPost('/mongos/{id}/command/find'),
 | 
			
		||||
    updateByIdCommand: Api.newPost('/mongos/{id}/command/update-by-id'),
 | 
			
		||||
    deleteByIdCommand: Api.newPost('/mongos/{id}/command/delete-by-id'),
 | 
			
		||||
    insertCommand: Api.newPost('/mongos/{id}/command/insert'),
 | 
			
		||||
};
 | 
			
		||||
 
 | 
			
		||||
@@ -12,13 +12,11 @@
 | 
			
		||||
                                            <SvgIcon name="iconfont icon-op-redis" :size="18" />
 | 
			
		||||
                                        </template>
 | 
			
		||||
                                        <template #default>
 | 
			
		||||
                                            <el-form class="instances-pop-form" label-width="50px" :size="'small'">
 | 
			
		||||
                                            <el-form class="instances-pop-form" label-width="auto" :size="'small'">
 | 
			
		||||
                                                <el-form-item label="名称:">{{ data.params.name }}</el-form-item>
 | 
			
		||||
                                                <el-form-item label="模式:">{{ data.params.mode }}</el-form-item>
 | 
			
		||||
                                                <el-form-item label="链接:">{{ data.params.host }}</el-form-item>
 | 
			
		||||
                                                <el-form-item label="备注:">{{
 | 
			
		||||
                                                    data.params.remark
 | 
			
		||||
                                                }}</el-form-item>
 | 
			
		||||
                                                <el-form-item label="备注:">{{ data.params.remark }}</el-form-item>
 | 
			
		||||
                                            </el-form>
 | 
			
		||||
                                        </template>
 | 
			
		||||
                                    </el-popover>
 | 
			
		||||
@@ -31,32 +29,37 @@
 | 
			
		||||
                </el-row>
 | 
			
		||||
            </el-col>
 | 
			
		||||
 | 
			
		||||
            <el-col :span="20" style="border-left: 1px solid var(--el-card-border-color);">
 | 
			
		||||
            <el-col :span="20" style="border-left: 1px solid var(--el-card-border-color)">
 | 
			
		||||
                <div class="mt10 ml5">
 | 
			
		||||
                    <el-col>
 | 
			
		||||
                        <el-form class="search-form" label-position="right" :inline="true" label-width="60px">
 | 
			
		||||
                            <el-form-item label="key" label-width="40px">
 | 
			
		||||
                                <el-input placeholder="match 支持*模糊key" style="width: 250px" v-model="scanParam.match"
 | 
			
		||||
                                    @clear="clear()" clearable></el-input>
 | 
			
		||||
                        <el-form class="search-form" label-position="right" :inline="true" label-width="auto">
 | 
			
		||||
                            <el-form-item label="key" label-width="auto">
 | 
			
		||||
                                <el-input placeholder="match 支持*模糊key" style="width: 250px" v-model="scanParam.match" @clear="clear()" clearable></el-input>
 | 
			
		||||
                            </el-form-item>
 | 
			
		||||
                            <el-form-item label="count" label-width="40px">
 | 
			
		||||
                                <el-input placeholder="count" style="width: 70px" v-model.number="scanParam.count">
 | 
			
		||||
                                </el-input>
 | 
			
		||||
                            <el-form-item label="count" label-width="auto">
 | 
			
		||||
                                <el-input placeholder="count" style="width: 70px" v-model.number="scanParam.count"> </el-input>
 | 
			
		||||
                            </el-form-item>
 | 
			
		||||
                            <el-form-item>
 | 
			
		||||
                                <el-button @click="searchKey()" type="success" icon="search" plain></el-button>
 | 
			
		||||
                                <el-button @click="scan()" icon="bottom" plain>scan</el-button>
 | 
			
		||||
                                <el-button @click="showNewKeyDialog" type="primary" icon="plus" plain
 | 
			
		||||
                                    v-auth="'redis:data:save'"></el-button>
 | 
			
		||||
                                <el-button @click="flushDb" type="danger" plain v-auth="'redis:data:save'">flush</el-button>
 | 
			
		||||
                                <el-button :disabled="!scanParam.id || !scanParam.db" @click="searchKey()" type="success" icon="search" plain></el-button>
 | 
			
		||||
                                <el-button :disabled="!scanParam.id || !scanParam.db" @click="scan()" icon="bottom" plain>scan</el-button>
 | 
			
		||||
                                <el-button
 | 
			
		||||
                                    :disabled="!scanParam.id || !scanParam.db"
 | 
			
		||||
                                    @click="showNewKeyDialog"
 | 
			
		||||
                                    type="primary"
 | 
			
		||||
                                    icon="plus"
 | 
			
		||||
                                    plain
 | 
			
		||||
                                    v-auth="'redis:data:save'"
 | 
			
		||||
                                ></el-button>
 | 
			
		||||
                                <el-button :disabled="!scanParam.id || !scanParam.db" @click="flushDb" type="danger" plain v-auth="'redis:data:save'"
 | 
			
		||||
                                    >flush</el-button
 | 
			
		||||
                                >
 | 
			
		||||
                            </el-form-item>
 | 
			
		||||
                            <div style="float: right">
 | 
			
		||||
                                <span>keys: {{ state.dbsize }}</span>
 | 
			
		||||
                            </div>
 | 
			
		||||
                        </el-form>
 | 
			
		||||
                    </el-col>
 | 
			
		||||
                    <el-table v-loading="state.loading" :data="state.keys" :height="tableHeight" stripe
 | 
			
		||||
                        :highlight-current-row="true" style="cursor: pointer">
 | 
			
		||||
                    <el-table v-loading="state.loading" :data="state.keys" :height="tableHeight" stripe :highlight-current-row="true" style="cursor: pointer">
 | 
			
		||||
                        <el-table-column show-overflow-tooltip prop="key" label="key"></el-table-column>
 | 
			
		||||
                        <el-table-column prop="type" label="type" width="80">
 | 
			
		||||
                            <template #default="scope">
 | 
			
		||||
@@ -70,11 +73,9 @@
 | 
			
		||||
                        </el-table-column>
 | 
			
		||||
                        <el-table-column label="操作">
 | 
			
		||||
                            <template #default="scope">
 | 
			
		||||
                                <el-button @click="showKeyDetail(scope.row)" type="success" icon="search" plain
 | 
			
		||||
                                    size="small">查看
 | 
			
		||||
                                </el-button>
 | 
			
		||||
                                <el-button v-auth="'redis:data:del'" @click="del(scope.row.key)" type="danger" icon="delete"
 | 
			
		||||
                                    plain size="small">删除
 | 
			
		||||
                                <el-button @click="showKeyDetail(scope.row)" type="success" icon="search" plain size="small">查看 </el-button>
 | 
			
		||||
                                <el-button v-auth="'redis:data:del'" @click="del(scope.row.key)" type="danger" icon="delete" plain size="small"
 | 
			
		||||
                                    >删除
 | 
			
		||||
                                </el-button>
 | 
			
		||||
                            </template>
 | 
			
		||||
                        </el-table-column>
 | 
			
		||||
@@ -85,21 +86,17 @@
 | 
			
		||||
 | 
			
		||||
        <div style="text-align: center; margin-top: 10px"></div>
 | 
			
		||||
 | 
			
		||||
        <el-dialog title="Key详情" v-model="keyDetailDialog.visible" width="800px" :destroy-on-close="true"
 | 
			
		||||
            :close-on-click-modal="false">
 | 
			
		||||
            <key-detail :redisId="scanParam.id" :db="scanParam.db" :key-info="keyDetailDialog.keyInfo"
 | 
			
		||||
                @change-key="searchKey()" />
 | 
			
		||||
        <el-dialog title="Key详情" v-model="keyDetailDialog.visible" width="800px" :destroy-on-close="true" :close-on-click-modal="false">
 | 
			
		||||
            <key-detail :redisId="scanParam.id" :db="scanParam.db" :key-info="keyDetailDialog.keyInfo" @change-key="searchKey()" />
 | 
			
		||||
        </el-dialog>
 | 
			
		||||
 | 
			
		||||
        <el-dialog title="新增Key" v-model="newKeyDialog.visible" width="500px" :destroy-on-close="true"
 | 
			
		||||
            :close-on-click-modal="false">
 | 
			
		||||
            <el-form ref="keyForm" label-width="50px">
 | 
			
		||||
        <el-dialog title="新增Key" v-model="newKeyDialog.visible" width="500px" :destroy-on-close="true" :close-on-click-modal="false">
 | 
			
		||||
            <el-form ref="keyForm" label-width="auto">
 | 
			
		||||
                <el-form-item prop="key" label="键名">
 | 
			
		||||
                    <el-input v-model.trim="keyDetailDialog.keyInfo.key" placeholder="请输入键名"></el-input>
 | 
			
		||||
                </el-form-item>
 | 
			
		||||
                <el-form-item prop="type" label="类型">
 | 
			
		||||
                    <el-select v-model="keyDetailDialog.keyInfo.type" default-first-option style="width: 100%"
 | 
			
		||||
                        placeholder="请选择类型">
 | 
			
		||||
                    <el-select v-model="keyDetailDialog.keyInfo.type" default-first-option style="width: 100%" placeholder="请选择类型">
 | 
			
		||||
                        <el-option key="string" label="string" value="string"></el-option>
 | 
			
		||||
                        <el-option key="hash" label="hash" value="hash"></el-option>
 | 
			
		||||
                        <el-option key="set" label="set" value="set"></el-option>
 | 
			
		||||
@@ -133,8 +130,8 @@ const KeyDetail = defineAsyncComponent(() => import('./KeyDetail.vue'));
 | 
			
		||||
 * 树节点类型
 | 
			
		||||
 */
 | 
			
		||||
class NodeType {
 | 
			
		||||
    static Redis = 1
 | 
			
		||||
    static Db = 2
 | 
			
		||||
    static Redis = 1;
 | 
			
		||||
    static Db = 2;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const state = reactive({
 | 
			
		||||
@@ -149,7 +146,7 @@ const state = reactive({
 | 
			
		||||
    scanParam: {
 | 
			
		||||
        id: null as any,
 | 
			
		||||
        mode: '',
 | 
			
		||||
        db: 0,
 | 
			
		||||
        db: null as any,
 | 
			
		||||
        match: null,
 | 
			
		||||
        count: 10,
 | 
			
		||||
        cursor: {},
 | 
			
		||||
@@ -169,21 +166,15 @@ const state = reactive({
 | 
			
		||||
    dbsize: 0,
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const {
 | 
			
		||||
    tableHeight,
 | 
			
		||||
    scanParam,
 | 
			
		||||
    keyDetailDialog,
 | 
			
		||||
    newKeyDialog,
 | 
			
		||||
} = toRefs(state)
 | 
			
		||||
 | 
			
		||||
const { tableHeight, scanParam, keyDetailDialog, newKeyDialog } = toRefs(state);
 | 
			
		||||
 | 
			
		||||
onMounted(async () => {
 | 
			
		||||
    setHeight();
 | 
			
		||||
})
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const setHeight = () => {
 | 
			
		||||
    state.tableHeight = window.innerHeight - 159;
 | 
			
		||||
}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * instmap;  tagPaht -> redis info[]
 | 
			
		||||
@@ -191,15 +182,15 @@ const setHeight = () => {
 | 
			
		||||
const instMap: Map<string, any[]> = new Map();
 | 
			
		||||
 | 
			
		||||
const getInsts = async () => {
 | 
			
		||||
    const res = await redisApi.redisList.request({});
 | 
			
		||||
    if (!res.total) return
 | 
			
		||||
    const res = await redisApi.redisList.request({ pageNum: 1, pageSize: 1000 });
 | 
			
		||||
    if (!res.total) return;
 | 
			
		||||
    for (const redisInfo of res.list) {
 | 
			
		||||
        const tagPath = redisInfo.tagPath;
 | 
			
		||||
        let redisInsts = instMap.get(tagPath) || [];
 | 
			
		||||
        redisInsts.push(redisInfo);
 | 
			
		||||
        instMap.set(tagPath, redisInsts);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 加载文件树节点
 | 
			
		||||
@@ -221,7 +212,7 @@ const loadNode = async (node: any) => {
 | 
			
		||||
    const data = node.data;
 | 
			
		||||
    // 点击tagPath -> 加载数据库信息列表
 | 
			
		||||
    if (data.type === TagTreeNode.TagPath) {
 | 
			
		||||
        const redisInfos = instMap.get(data.key)
 | 
			
		||||
        const redisInfos = instMap.get(data.key);
 | 
			
		||||
        return redisInfos?.map((x: any) => {
 | 
			
		||||
            return new TagTreeNode(`${data.key}.${x.id}`, x.name, NodeType.Redis).withParams(x);
 | 
			
		||||
        });
 | 
			
		||||
@@ -243,7 +234,7 @@ const nodeClick = (data: any) => {
 | 
			
		||||
        state.scanParam.db = data.params.db;
 | 
			
		||||
        scan();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 获取所有库信息
 | 
			
		||||
@@ -256,27 +247,27 @@ const getDbs = async (redisInfo: any) => {
 | 
			
		||||
            db: x,
 | 
			
		||||
            name: `db${x}`,
 | 
			
		||||
            keys: 0,
 | 
			
		||||
        })
 | 
			
		||||
    })
 | 
			
		||||
        });
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    if (redisInfo.mode == 'cluster') {
 | 
			
		||||
        return dbs;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const res = await redisApi.redisInfo.request({ id: redisInfo.id, host: redisInfo.host, section: "Keyspace" });
 | 
			
		||||
    const res = await redisApi.redisInfo.request({ id: redisInfo.id, host: redisInfo.host, section: 'Keyspace' });
 | 
			
		||||
    for (let db in res.Keyspace) {
 | 
			
		||||
        for (let d of dbs) {
 | 
			
		||||
            if (db == d.params.name) {
 | 
			
		||||
                d.params.keys = res.Keyspace[db]?.split(',')[0]?.split('=')[1] || 0
 | 
			
		||||
                d.params.keys = res.Keyspace[db]?.split(',')[0]?.split('=')[1] || 0;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    // 替换label
 | 
			
		||||
    dbs.forEach((e: any) => {
 | 
			
		||||
        e.label = `${e.params.name} [${e.params.keys}]`
 | 
			
		||||
        e.label = `${e.params.name} [${e.params.keys}]`;
 | 
			
		||||
    });
 | 
			
		||||
    return dbs;
 | 
			
		||||
}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const scan = async () => {
 | 
			
		||||
    isTrue(state.scanParam.id != null, '请先选择redis');
 | 
			
		||||
@@ -284,7 +275,7 @@ const scan = async () => {
 | 
			
		||||
 | 
			
		||||
    const match: string = state.scanParam.match || '';
 | 
			
		||||
    if (!match) {
 | 
			
		||||
        isTrue(state.scanParam.count <= 100, "key搜索条件为空时, count不能大于100")
 | 
			
		||||
        isTrue(state.scanParam.count <= 100, 'key搜索条件为空时, count不能大于100');
 | 
			
		||||
    } else if (match.indexOf('*') != -1) {
 | 
			
		||||
        const dbsize = state.dbsize;
 | 
			
		||||
        // 如果为模糊搜索,并且搜索的key模式大于指定字符数,则将count设大点scan
 | 
			
		||||
@@ -295,10 +286,10 @@ const scan = async () => {
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const scanParam = { ...state.scanParam }
 | 
			
		||||
    const scanParam = { ...state.scanParam };
 | 
			
		||||
    // 集群模式count设小点,因为后端会从所有master节点scan一遍然后合并结果,默认假设redis集群有3个master
 | 
			
		||||
    if (scanParam.mode == 'cluster') {
 | 
			
		||||
        scanParam.count = Math.floor(state.scanParam.count / 3)
 | 
			
		||||
        scanParam.count = Math.floor(state.scanParam.count / 3);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    state.loading = true;
 | 
			
		||||
@@ -341,39 +332,41 @@ const showKeyDetail = async (row: any) => {
 | 
			
		||||
 | 
			
		||||
const showNewKeyDialog = () => {
 | 
			
		||||
    notNull(state.scanParam.id, '请先选择redis');
 | 
			
		||||
    notNull(state.scanParam.db, "请选择要操作的库")
 | 
			
		||||
    notNull(state.scanParam.db, '请选择要操作的库');
 | 
			
		||||
    resetKeyDetailInfo();
 | 
			
		||||
    state.newKeyDialog.visible = true;
 | 
			
		||||
}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const flushDb = () => {
 | 
			
		||||
    ElMessageBox.confirm(`确定清空[${state.scanParam.db}]库的所有key?`, '提示', {
 | 
			
		||||
        confirmButtonText: '确定',
 | 
			
		||||
        cancelButtonText: '取消',
 | 
			
		||||
        type: 'warning',
 | 
			
		||||
    }).then(() => {
 | 
			
		||||
        redisApi.flushDb
 | 
			
		||||
            .request({
 | 
			
		||||
                id: state.scanParam.id,
 | 
			
		||||
                db: state.scanParam.db,
 | 
			
		||||
            })
 | 
			
		||||
            .then(() => {
 | 
			
		||||
                ElMessage.success('清除成功!');
 | 
			
		||||
                searchKey();
 | 
			
		||||
            });
 | 
			
		||||
    }).catch(() => { });
 | 
			
		||||
}
 | 
			
		||||
    })
 | 
			
		||||
        .then(() => {
 | 
			
		||||
            redisApi.flushDb
 | 
			
		||||
                .request({
 | 
			
		||||
                    id: state.scanParam.id,
 | 
			
		||||
                    db: state.scanParam.db,
 | 
			
		||||
                })
 | 
			
		||||
                .then(() => {
 | 
			
		||||
                    ElMessage.success('清除成功!');
 | 
			
		||||
                    searchKey();
 | 
			
		||||
                });
 | 
			
		||||
        })
 | 
			
		||||
        .catch(() => {});
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const cancelNewKey = () => {
 | 
			
		||||
    resetKeyDetailInfo();
 | 
			
		||||
    state.newKeyDialog.visible = false;
 | 
			
		||||
}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const newKey = async () => {
 | 
			
		||||
    const keyInfo = state.keyDetailDialog.keyInfo
 | 
			
		||||
    const keyType = keyInfo.type
 | 
			
		||||
    const keyInfo = state.keyDetailDialog.keyInfo;
 | 
			
		||||
    const keyType = keyInfo.type;
 | 
			
		||||
    const key = keyInfo.key;
 | 
			
		||||
    notBlank(key, "键名不能为空");
 | 
			
		||||
    notBlank(key, '键名不能为空');
 | 
			
		||||
 | 
			
		||||
    if (keyType == 'string') {
 | 
			
		||||
        await redisApi.setString.request({
 | 
			
		||||
@@ -381,36 +374,38 @@ const newKey = async () => {
 | 
			
		||||
            db: state.scanParam.db,
 | 
			
		||||
            key: key,
 | 
			
		||||
            value: '',
 | 
			
		||||
        })
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
    state.newKeyDialog.visible = false;
 | 
			
		||||
    state.keyDetailDialog.visible = true;
 | 
			
		||||
    searchKey();
 | 
			
		||||
}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const resetKeyDetailInfo = () => {
 | 
			
		||||
    state.keyDetailDialog.keyInfo.key = '';
 | 
			
		||||
    state.keyDetailDialog.keyInfo.type = 'string';
 | 
			
		||||
    state.keyDetailDialog.keyInfo.timed = -1;
 | 
			
		||||
}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const del = (key: string) => {
 | 
			
		||||
    ElMessageBox.confirm(`确定删除[ ${key} ] 该key?`, '提示', {
 | 
			
		||||
        confirmButtonText: '确定',
 | 
			
		||||
        cancelButtonText: '取消',
 | 
			
		||||
        type: 'warning',
 | 
			
		||||
    }).then(() => {
 | 
			
		||||
        redisApi.delKey
 | 
			
		||||
            .request({
 | 
			
		||||
                key,
 | 
			
		||||
                id: state.scanParam.id,
 | 
			
		||||
                db: state.scanParam.db,
 | 
			
		||||
            })
 | 
			
		||||
            .then(() => {
 | 
			
		||||
                ElMessage.success('删除成功!');
 | 
			
		||||
                searchKey();
 | 
			
		||||
            });
 | 
			
		||||
    }).catch(() => { });
 | 
			
		||||
    })
 | 
			
		||||
        .then(() => {
 | 
			
		||||
            redisApi.delKey
 | 
			
		||||
                .request({
 | 
			
		||||
                    key,
 | 
			
		||||
                    id: state.scanParam.id,
 | 
			
		||||
                    db: state.scanParam.db,
 | 
			
		||||
                })
 | 
			
		||||
                .then(() => {
 | 
			
		||||
                    ElMessage.success('删除成功!');
 | 
			
		||||
                    searchKey();
 | 
			
		||||
                });
 | 
			
		||||
        })
 | 
			
		||||
        .catch(() => {});
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const ttlConveter = (ttl: any) => {
 | 
			
		||||
 
 | 
			
		||||
@@ -1,17 +1,15 @@
 | 
			
		||||
<template>
 | 
			
		||||
    <div class="format-viewer-container">
 | 
			
		||||
        <div class="mb5 fr">
 | 
			
		||||
            <el-select v-model="selectedView" class='format-selector' size='mini' placeholder='Text'>
 | 
			
		||||
            <el-select v-model="selectedView" class="format-selector" size="mini" placeholder="Text">
 | 
			
		||||
                <template #prefix>
 | 
			
		||||
                    <SvgIcon name="view" />
 | 
			
		||||
                </template>
 | 
			
		||||
                <el-option v-for="item of Object.keys(viewers)" :key="item" :label="item" :value="item">
 | 
			
		||||
                </el-option>
 | 
			
		||||
                <el-option v-for="item of Object.keys(viewers)" :key="item" :label="item" :value="item"> </el-option>
 | 
			
		||||
            </el-select>
 | 
			
		||||
        </div>
 | 
			
		||||
 | 
			
		||||
        <component ref='viewerRef' :is='components[viewerComponent]' :content='state.content' :name="selectedView">
 | 
			
		||||
        </component>
 | 
			
		||||
        <component ref="viewerRef" :is="components[viewerComponent]" :content="state.content" :name="selectedView"> </component>
 | 
			
		||||
    </div>
 | 
			
		||||
</template>
 | 
			
		||||
<script lang="ts" setup>
 | 
			
		||||
@@ -23,12 +21,13 @@ const props = defineProps({
 | 
			
		||||
    content: {
 | 
			
		||||
        type: String,
 | 
			
		||||
    },
 | 
			
		||||
})
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const components = shallowReactive({
 | 
			
		||||
    ViewerText, ViewerJson
 | 
			
		||||
})
 | 
			
		||||
const viewerRef: any = ref(null)
 | 
			
		||||
    ViewerText,
 | 
			
		||||
    ViewerJson,
 | 
			
		||||
});
 | 
			
		||||
const viewerRef: any = ref(null);
 | 
			
		||||
 | 
			
		||||
const state = reactive({
 | 
			
		||||
    content: '',
 | 
			
		||||
@@ -36,18 +35,16 @@ const state = reactive({
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const viewers = {
 | 
			
		||||
    "Text": {
 | 
			
		||||
    Text: {
 | 
			
		||||
        value: 'ViewerText',
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    "Json": {
 | 
			
		||||
    Json: {
 | 
			
		||||
        value: 'ViewerJson',
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
    },
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const {
 | 
			
		||||
    selectedView,
 | 
			
		||||
} = toRefs(state)
 | 
			
		||||
const { selectedView } = toRefs(state);
 | 
			
		||||
 | 
			
		||||
const viewerComponent = computed(() => {
 | 
			
		||||
    return viewers[state.selectedView].value;
 | 
			
		||||
@@ -62,15 +59,15 @@ watch(
 | 
			
		||||
 | 
			
		||||
onMounted(() => {
 | 
			
		||||
    state.content = props.content as any;
 | 
			
		||||
})
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const getContent = () => {
 | 
			
		||||
    return viewerRef.value.getContent();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
defineExpose({ getContent })
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
defineExpose({ getContent });
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style lang="scss">
 | 
			
		||||
.format-selector {
 | 
			
		||||
    width: 130px;
 | 
			
		||||
@@ -85,7 +82,7 @@ defineExpose({ getContent })
 | 
			
		||||
    border: 1px solid #dcdfe6;
 | 
			
		||||
    padding: 5px 10px;
 | 
			
		||||
    border-radius: 4px;
 | 
			
		||||
    clear: both
 | 
			
		||||
    clear: both;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.format-viewer-container .formater-binary-tag {
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,6 @@
 | 
			
		||||
<template>
 | 
			
		||||
    <div>
 | 
			
		||||
        <el-dialog :title="title" v-model="dialogVisible" :show-close="true" width="1000px" @close="close()">
 | 
			
		||||
 | 
			
		||||
            <el-row :gutter="20">
 | 
			
		||||
                <el-col :lg="16" :md="16">
 | 
			
		||||
                    <el-descriptions class="redis-info info-server" title="Redis服务器信息" :column="3" size="small" border>
 | 
			
		||||
@@ -31,10 +30,8 @@
 | 
			
		||||
 | 
			
		||||
                <el-col :lg="12" :md="12">
 | 
			
		||||
                    <el-descriptions class="redis-info info-client" title="客户端连接" :column="3" size="small" border>
 | 
			
		||||
                        <el-descriptions-item label="已连接客户端数">{{ info.Clients.connected_clients
 | 
			
		||||
                        }}</el-descriptions-item>
 | 
			
		||||
                        <el-descriptions-item label="正在等待阻塞命令客户端数">{{ info.Clients.blocked_clients
 | 
			
		||||
                        }}</el-descriptions-item>
 | 
			
		||||
                        <el-descriptions-item label="已连接客户端数">{{ info.Clients.connected_clients }}</el-descriptions-item>
 | 
			
		||||
                        <el-descriptions-item label="正在等待阻塞命令客户端数">{{ info.Clients.blocked_clients }}</el-descriptions-item>
 | 
			
		||||
                    </el-descriptions>
 | 
			
		||||
                </el-col>
 | 
			
		||||
            </el-row>
 | 
			
		||||
@@ -50,14 +47,10 @@
 | 
			
		||||
                <el-col :lg="24" :md="24">
 | 
			
		||||
                    <span style="font-size: 14px; font-weight: 700">键值统计</span>
 | 
			
		||||
                    <el-table :data="Keyspace" stripe max-height="250" style="width: 100%" border>
 | 
			
		||||
                        <el-table-column prop="db" label="数据库" min-width="100" show-overflow-tooltip>
 | 
			
		||||
                        </el-table-column>
 | 
			
		||||
                        <el-table-column prop="keys" label="keys" min-width="70" show-overflow-tooltip>
 | 
			
		||||
                        </el-table-column>
 | 
			
		||||
                        <el-table-column prop="expires" label="expires" min-width="70" show-overflow-tooltip>
 | 
			
		||||
                        </el-table-column>
 | 
			
		||||
                        <el-table-column prop="avg_ttl" label="avg_ttl" min-width="70" show-overflow-tooltip>
 | 
			
		||||
                        </el-table-column>
 | 
			
		||||
                        <el-table-column prop="db" label="数据库" min-width="100" show-overflow-tooltip> </el-table-column>
 | 
			
		||||
                        <el-table-column prop="keys" label="keys" min-width="70" show-overflow-tooltip> </el-table-column>
 | 
			
		||||
                        <el-table-column prop="expires" label="expires" min-width="70" show-overflow-tooltip> </el-table-column>
 | 
			
		||||
                        <el-table-column prop="avg_ttl" label="avg_ttl" min-width="70" show-overflow-tooltip> </el-table-column>
 | 
			
		||||
                    </el-table>
 | 
			
		||||
                </el-col>
 | 
			
		||||
            </el-row>
 | 
			
		||||
@@ -71,12 +64,9 @@
 | 
			
		||||
            </el-descriptions>
 | 
			
		||||
 | 
			
		||||
            <el-descriptions class="redis-info info-persistence" title="持久化" :column="3" size="small" border>
 | 
			
		||||
                <el-descriptions-item label="是否启用aof">{{ info.Persistence?.aof_enabled || false
 | 
			
		||||
                }}</el-descriptions-item>
 | 
			
		||||
                <el-descriptions-item label="是否正在载入持久化文件">{{ info.Persistence?.loading || false
 | 
			
		||||
                }}</el-descriptions-item>
 | 
			
		||||
                <el-descriptions-item label="是否启用aof">{{ info.Persistence?.aof_enabled || false }}</el-descriptions-item>
 | 
			
		||||
                <el-descriptions-item label="是否正在载入持久化文件">{{ info.Persistence?.loading || false }}</el-descriptions-item>
 | 
			
		||||
            </el-descriptions>
 | 
			
		||||
 | 
			
		||||
        </el-dialog>
 | 
			
		||||
    </div>
 | 
			
		||||
</template>
 | 
			
		||||
@@ -97,24 +87,20 @@ const props = defineProps({
 | 
			
		||||
    info: {
 | 
			
		||||
        type: [Boolean, Object],
 | 
			
		||||
    },
 | 
			
		||||
})
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const emit = defineEmits(['update:visible', 'close'])
 | 
			
		||||
const emit = defineEmits(['update:visible', 'close']);
 | 
			
		||||
 | 
			
		||||
const state = reactive({
 | 
			
		||||
    dialogVisible: false,
 | 
			
		||||
    memInfo: {} as any,
 | 
			
		||||
    Keyspace: [] as any[],
 | 
			
		||||
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
let memChart: any = null;
 | 
			
		||||
let memRef = ref(null);
 | 
			
		||||
 | 
			
		||||
const {
 | 
			
		||||
    dialogVisible,
 | 
			
		||||
    Keyspace,
 | 
			
		||||
} = toRefs(state)
 | 
			
		||||
const { dialogVisible, Keyspace } = toRefs(state);
 | 
			
		||||
 | 
			
		||||
watch(
 | 
			
		||||
    () => props.visible,
 | 
			
		||||
@@ -132,15 +118,15 @@ watch(
 | 
			
		||||
        if (info['Keyspace']) {
 | 
			
		||||
            let arr = [];
 | 
			
		||||
            for (let k in info['Keyspace']) {
 | 
			
		||||
                let data = { db: k }
 | 
			
		||||
                let d = info['Keyspace'][k].split(',')
 | 
			
		||||
                let data = { db: k };
 | 
			
		||||
                let d = info['Keyspace'][k].split(',');
 | 
			
		||||
                for (let f of d) {
 | 
			
		||||
                    let v = f.split('=')
 | 
			
		||||
                    data[v[0]] = v[1]
 | 
			
		||||
                    let v = f.split('=');
 | 
			
		||||
                    data[v[0]] = v[1];
 | 
			
		||||
                }
 | 
			
		||||
                arr.push(data)
 | 
			
		||||
                arr.push(data);
 | 
			
		||||
            }
 | 
			
		||||
            state.Keyspace = arr
 | 
			
		||||
            state.Keyspace = arr;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
);
 | 
			
		||||
@@ -149,10 +135,10 @@ const initCharts = () => {
 | 
			
		||||
    nextTick(() => {
 | 
			
		||||
        initMemStats();
 | 
			
		||||
    });
 | 
			
		||||
}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const initMemStats = () => {
 | 
			
		||||
    let maxMem = state.memInfo.maxmemory === '0' ? state.memInfo.total_system_memory : state.memInfo.maxmemory
 | 
			
		||||
    let maxMem = state.memInfo.maxmemory === '0' ? state.memInfo.total_system_memory : state.memInfo.maxmemory;
 | 
			
		||||
    const data = [
 | 
			
		||||
        { name: '可用内存:', value: maxMem - state.memInfo.used_memory },
 | 
			
		||||
        {
 | 
			
		||||
@@ -205,7 +191,7 @@ const initMemStats = () => {
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
    memChart = useEcharts(memRef.value, tdTheme, option);
 | 
			
		||||
}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const close = () => {
 | 
			
		||||
    emit('update:visible', false);
 | 
			
		||||
 
 | 
			
		||||
@@ -2,20 +2,26 @@
 | 
			
		||||
    <div>
 | 
			
		||||
        <el-container direction="vertical" class="key-tab-container">
 | 
			
		||||
            <!-- key info -->
 | 
			
		||||
            <key-header ref="keyHeader" :redis-id="redisId" :db="db" :key-info="keyInfo" @refresh-content="refreshContent"
 | 
			
		||||
                @change-key="changeKey" class="key-header-info">
 | 
			
		||||
            <key-header
 | 
			
		||||
                ref="keyHeader"
 | 
			
		||||
                :redis-id="redisId"
 | 
			
		||||
                :db="db"
 | 
			
		||||
                :key-info="keyInfo"
 | 
			
		||||
                @refresh-content="refreshContent"
 | 
			
		||||
                @change-key="changeKey"
 | 
			
		||||
                class="key-header-info"
 | 
			
		||||
            >
 | 
			
		||||
            </key-header>
 | 
			
		||||
 | 
			
		||||
            <!-- key content -->
 | 
			
		||||
            <component ref="keyValueRef" :is="components[componentName]" :redis-id="redisId" :db="db" :key-info="keyInfo">
 | 
			
		||||
            </component>
 | 
			
		||||
            <component ref="keyValueRef" :is="components[componentName]" :redis-id="redisId" :db="db" :key-info="keyInfo"> </component>
 | 
			
		||||
        </el-container>
 | 
			
		||||
    </div>
 | 
			
		||||
</template>
 | 
			
		||||
<script lang="ts" setup>
 | 
			
		||||
import { defineAsyncComponent, ref, shallowReactive, reactive, computed, toRefs } from 'vue';
 | 
			
		||||
import { ElMessage } from 'element-plus';
 | 
			
		||||
import KeyHeader from './KeyHeader.vue'
 | 
			
		||||
import KeyHeader from './KeyHeader.vue';
 | 
			
		||||
 | 
			
		||||
const KeyValueString = defineAsyncComponent(() => import('./KeyValueString.vue'));
 | 
			
		||||
const KeyValueHash = defineAsyncComponent(() => import('./KeyValueHash.vue'));
 | 
			
		||||
@@ -24,24 +30,28 @@ const KeyValueList = defineAsyncComponent(() => import('./KeyValueList.vue'));
 | 
			
		||||
const KeyValueZset = defineAsyncComponent(() => import('./KeyValueZset.vue'));
 | 
			
		||||
 | 
			
		||||
const components = shallowReactive({
 | 
			
		||||
    KeyValueString, KeyValueHash, KeyValueSet, KeyValueList, KeyValueZset
 | 
			
		||||
})
 | 
			
		||||
    KeyValueString,
 | 
			
		||||
    KeyValueHash,
 | 
			
		||||
    KeyValueSet,
 | 
			
		||||
    KeyValueList,
 | 
			
		||||
    KeyValueZset,
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const keyValueRef = ref(null) as any
 | 
			
		||||
const keyValueRef = ref(null) as any;
 | 
			
		||||
 | 
			
		||||
const props = defineProps({
 | 
			
		||||
    redisId: {
 | 
			
		||||
        type: Number
 | 
			
		||||
        type: Number,
 | 
			
		||||
    },
 | 
			
		||||
    db: {
 | 
			
		||||
        type: Number
 | 
			
		||||
        type: Number,
 | 
			
		||||
    },
 | 
			
		||||
    keyInfo: {
 | 
			
		||||
        type: [Object],
 | 
			
		||||
    },
 | 
			
		||||
})
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const emit = defineEmits(['update:visible', 'changeKey', 'valChange'])
 | 
			
		||||
const emit = defineEmits(['update:visible', 'changeKey', 'valChange']);
 | 
			
		||||
 | 
			
		||||
const state = reactive({
 | 
			
		||||
    redisId: 0,
 | 
			
		||||
@@ -56,25 +66,23 @@ const componentMap = {
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const componentName = computed(() => {
 | 
			
		||||
    const component = componentMap[props.keyInfo?.type]
 | 
			
		||||
    const component = componentMap[props.keyInfo?.type];
 | 
			
		||||
    if (!component) {
 | 
			
		||||
        ElMessage.error("暂不支持该类型")
 | 
			
		||||
        return ''
 | 
			
		||||
        ElMessage.error('暂不支持该类型');
 | 
			
		||||
        return '';
 | 
			
		||||
    }
 | 
			
		||||
    return component;
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const refreshContent = () => {
 | 
			
		||||
    keyValueRef.value?.initData();
 | 
			
		||||
}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const changeKey = () => {
 | 
			
		||||
    emit('changeKey');
 | 
			
		||||
}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const {
 | 
			
		||||
 | 
			
		||||
} = toRefs(state)
 | 
			
		||||
const {} = toRefs(state);
 | 
			
		||||
 | 
			
		||||
// watch(
 | 
			
		||||
//     () => props.keyInfo,
 | 
			
		||||
 
 | 
			
		||||
@@ -8,10 +8,8 @@
 | 
			
		||||
                </template>
 | 
			
		||||
 | 
			
		||||
                <template #suffix>
 | 
			
		||||
                    <SvgIcon v-auth="'redis:data:save'" @click="renameKey" title="点击重命名" name="check"
 | 
			
		||||
                        class="cursor-pointer" />
 | 
			
		||||
                    <SvgIcon v-auth="'redis:data:save'" @click="renameKey" title="点击重命名" name="check" class="cursor-pointer" />
 | 
			
		||||
                </template>
 | 
			
		||||
 | 
			
		||||
            </el-input>
 | 
			
		||||
        </div>
 | 
			
		||||
 | 
			
		||||
@@ -30,9 +28,8 @@
 | 
			
		||||
        </div>
 | 
			
		||||
 | 
			
		||||
        <!-- del & refresh btn -->
 | 
			
		||||
        <div class='key-header-item key-header-btn-con'>
 | 
			
		||||
            <el-button slot="reference" ref='refreshBtn' type="success" @click="refreshKey" icon="refresh"
 | 
			
		||||
                title="刷新"></el-button>
 | 
			
		||||
        <div class="key-header-item key-header-btn-con">
 | 
			
		||||
            <el-button slot="reference" ref="refreshBtn" type="success" @click="refreshKey" icon="refresh" title="刷新"></el-button>
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
</template>
 | 
			
		||||
@@ -43,17 +40,17 @@ import { ElMessage, ElMessageBox } from 'element-plus';
 | 
			
		||||
 | 
			
		||||
const props = defineProps({
 | 
			
		||||
    redisId: {
 | 
			
		||||
        type: Number
 | 
			
		||||
        type: Number,
 | 
			
		||||
    },
 | 
			
		||||
    db: {
 | 
			
		||||
        type: Number
 | 
			
		||||
        type: Number,
 | 
			
		||||
    },
 | 
			
		||||
    keyInfo: {
 | 
			
		||||
        type: [Object],
 | 
			
		||||
    },
 | 
			
		||||
})
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const emit = defineEmits(['refreshContent', 'changeKey', 'valChange'])
 | 
			
		||||
const emit = defineEmits(['refreshContent', 'changeKey', 'valChange']);
 | 
			
		||||
 | 
			
		||||
const state = reactive({
 | 
			
		||||
    redisId: 0,
 | 
			
		||||
@@ -66,19 +63,19 @@ const state = reactive({
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
onMounted(() => {
 | 
			
		||||
    state.keyInfo = props.keyInfo
 | 
			
		||||
    state.oldKey = props.keyInfo?.key
 | 
			
		||||
})
 | 
			
		||||
    state.keyInfo = props.keyInfo;
 | 
			
		||||
    state.oldKey = props.keyInfo?.key;
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const refreshKey = async () => {
 | 
			
		||||
    const ttl = await redisApi.keyTtl.request({
 | 
			
		||||
        id: props.redisId,
 | 
			
		||||
        db: props.db,
 | 
			
		||||
        key: state.oldKey,
 | 
			
		||||
    })
 | 
			
		||||
    });
 | 
			
		||||
    state.keyInfo.timed = ttl;
 | 
			
		||||
    emit('refreshContent');
 | 
			
		||||
}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const renameKey = async () => {
 | 
			
		||||
    if (!state.oldKey || state.keyInfo.key == state.oldKey) {
 | 
			
		||||
@@ -88,11 +85,11 @@ const renameKey = async () => {
 | 
			
		||||
        id: props.redisId,
 | 
			
		||||
        db: props.db,
 | 
			
		||||
        newKey: state.keyInfo.key,
 | 
			
		||||
        key: state.oldKey
 | 
			
		||||
        key: state.oldKey,
 | 
			
		||||
    });
 | 
			
		||||
    ElMessage.success("设置成功")
 | 
			
		||||
    ElMessage.success('设置成功');
 | 
			
		||||
    emit('changeKey');
 | 
			
		||||
}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const ttlKey = async () => {
 | 
			
		||||
    if (!state.oldKey) {
 | 
			
		||||
@@ -101,32 +98,28 @@ const ttlKey = async () => {
 | 
			
		||||
    // ttl <= 0,则持久化该key
 | 
			
		||||
    if (state.keyInfo.timed <= 0) {
 | 
			
		||||
        try {
 | 
			
		||||
            await ElMessageBox.confirm(
 | 
			
		||||
                '确定持久化该key?',
 | 
			
		||||
                'Warning',
 | 
			
		||||
                {
 | 
			
		||||
                    confirmButtonText: '确认',
 | 
			
		||||
                    cancelButtonText: '取消',
 | 
			
		||||
                    type: 'warning',
 | 
			
		||||
                }
 | 
			
		||||
            )
 | 
			
		||||
            await ElMessageBox.confirm('确定持久化该key?', 'Warning', {
 | 
			
		||||
                confirmButtonText: '确认',
 | 
			
		||||
                cancelButtonText: '取消',
 | 
			
		||||
                type: 'warning',
 | 
			
		||||
            });
 | 
			
		||||
        } catch (err) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        await persistKey();
 | 
			
		||||
        state.keyInfo.timed = -1;
 | 
			
		||||
        return
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    await redisApi.expireKey.request({
 | 
			
		||||
        id: props.redisId,
 | 
			
		||||
        db: props.db,
 | 
			
		||||
        key: state.keyInfo.key,
 | 
			
		||||
        seconds: state.keyInfo.timed
 | 
			
		||||
        seconds: state.keyInfo.timed,
 | 
			
		||||
    });
 | 
			
		||||
    ElMessage.success("设置成功")
 | 
			
		||||
    ElMessage.success('设置成功');
 | 
			
		||||
    emit('changeKey');
 | 
			
		||||
}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const persistKey = async () => {
 | 
			
		||||
    await redisApi.persistKey.request({
 | 
			
		||||
@@ -134,14 +127,11 @@ const persistKey = async () => {
 | 
			
		||||
        db: props.db,
 | 
			
		||||
        key: state.keyInfo.key,
 | 
			
		||||
    });
 | 
			
		||||
    ElMessage.success("设置成功")
 | 
			
		||||
    ElMessage.success('设置成功');
 | 
			
		||||
    emit('changeKey');
 | 
			
		||||
}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const {
 | 
			
		||||
    keyInfo,
 | 
			
		||||
    oldKey,
 | 
			
		||||
} = toRefs(state)
 | 
			
		||||
const { keyInfo, oldKey } = toRefs(state);
 | 
			
		||||
 | 
			
		||||
// watch(
 | 
			
		||||
//     () => props.keyInfo,
 | 
			
		||||
@@ -189,7 +179,7 @@ const {
 | 
			
		||||
    appearance: none;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.key-header-item.key-header-btn-con .el-button+.el-button {
 | 
			
		||||
.key-header-item.key-header-btn-con .el-button + .el-button {
 | 
			
		||||
    margin-left: 4px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,40 +1,37 @@
 | 
			
		||||
<template>
 | 
			
		||||
    <div>
 | 
			
		||||
        <el-button @click="showEditDialog(null)" icon="plus" size="small" plain type="primary" class="mb10">添加新行</el-button>
 | 
			
		||||
        <el-table size="small" border :data="hashValues" min-height=300 stripe>
 | 
			
		||||
            <el-table-column type="index" :label="'ID (Total: ' + total + ')'" sortable width="100">
 | 
			
		||||
            </el-table-column>
 | 
			
		||||
            <el-table-column resizable sortable prop="field" label="field" show-overflow-tooltip min-width="100">
 | 
			
		||||
            </el-table-column>
 | 
			
		||||
            <el-table-column resizable sortable prop="value" label="value" show-overflow-tooltip min-width="200">
 | 
			
		||||
            </el-table-column>
 | 
			
		||||
        <el-table size="small" border :data="hashValues" height="450" min-height="300" stripe>
 | 
			
		||||
            <el-table-column type="index" :label="'ID (Total: ' + total + ')'" sortable width="100"> </el-table-column>
 | 
			
		||||
            <el-table-column resizable sortable prop="field" label="field" show-overflow-tooltip min-width="100"> </el-table-column>
 | 
			
		||||
            <el-table-column resizable sortable prop="value" label="value" show-overflow-tooltip min-width="200"> </el-table-column>
 | 
			
		||||
            <el-table-column label="操作">
 | 
			
		||||
                <template #header>
 | 
			
		||||
                    <el-input class="key-detail-filter-value" v-model="state.filterValue" @keyup.enter='hscan(true, true)'
 | 
			
		||||
                        placeholder="输入关键词回车搜索" clearable size="small" />
 | 
			
		||||
                    <el-input
 | 
			
		||||
                        class="key-detail-filter-value"
 | 
			
		||||
                        v-model="state.filterValue"
 | 
			
		||||
                        @keyup.enter="hscan(true, true)"
 | 
			
		||||
                        placeholder="输入关键词回车搜索"
 | 
			
		||||
                        clearable
 | 
			
		||||
                        size="small"
 | 
			
		||||
                    />
 | 
			
		||||
                </template>
 | 
			
		||||
                <template #default="scope">
 | 
			
		||||
                    <el-link @click="showEditDialog(scope.row)" :underline="false" type="primary" icon="edit"
 | 
			
		||||
                        plain></el-link>
 | 
			
		||||
                    <el-link @click="showEditDialog(scope.row)" :underline="false" type="primary" icon="edit" plain></el-link>
 | 
			
		||||
                    <el-popconfirm title="确定删除?" @confirm="hdel(scope.row.field, scope.$index)">
 | 
			
		||||
                        <template #reference>
 | 
			
		||||
                            <el-link v-auth="'redis:data:del'" :underline="false" type="danger" icon="delete" size="small"
 | 
			
		||||
                                plain class="ml5"></el-link>
 | 
			
		||||
                            <el-link v-auth="'redis:data:del'" :underline="false" type="danger" icon="delete" size="small" plain class="ml5"></el-link>
 | 
			
		||||
                        </template>
 | 
			
		||||
                    </el-popconfirm>
 | 
			
		||||
 | 
			
		||||
                </template>
 | 
			
		||||
            </el-table-column>
 | 
			
		||||
        </el-table>
 | 
			
		||||
        <!-- load more content -->
 | 
			
		||||
        <div class='content-more-container'>
 | 
			
		||||
            <el-button size='small' @click='hscan()' :disabled='loadMoreDisable' class='content-more-btn'>
 | 
			
		||||
                加载更多
 | 
			
		||||
            </el-button>
 | 
			
		||||
        <div class="content-more-container">
 | 
			
		||||
            <el-button size="small" @click="hscan()" :disabled="loadMoreDisable" class="content-more-btn"> 加载更多 </el-button>
 | 
			
		||||
        </div>
 | 
			
		||||
 | 
			
		||||
        <el-dialog title="添加新行" v-model="editDialog.visible" width="600px" :destroy-on-close="true"
 | 
			
		||||
            :close-on-click-modal="false">
 | 
			
		||||
        <el-dialog title="添加新行" v-model="editDialog.visible" width="600px" :destroy-on-close="true" :close-on-click-modal="false">
 | 
			
		||||
            <el-form>
 | 
			
		||||
                <el-form-item>
 | 
			
		||||
                    <el-input v-model="editDialog.field" placeholder="field" />
 | 
			
		||||
@@ -64,17 +61,17 @@ const props = defineProps({
 | 
			
		||||
    redisId: {
 | 
			
		||||
        type: [Number],
 | 
			
		||||
        require: true,
 | 
			
		||||
        default: 0
 | 
			
		||||
        default: 0,
 | 
			
		||||
    },
 | 
			
		||||
    db: {
 | 
			
		||||
        type: [Number],
 | 
			
		||||
        require: true,
 | 
			
		||||
        default: 0
 | 
			
		||||
        default: 0,
 | 
			
		||||
    },
 | 
			
		||||
    keyInfo: {
 | 
			
		||||
        type: [Object],
 | 
			
		||||
    },
 | 
			
		||||
})
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const formatViewerRef = ref(null) as any;
 | 
			
		||||
 | 
			
		||||
@@ -98,29 +95,23 @@ const state = reactive({
 | 
			
		||||
    },
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const {
 | 
			
		||||
    hashValues,
 | 
			
		||||
    total,
 | 
			
		||||
    loadMoreDisable,
 | 
			
		||||
    editDialog,
 | 
			
		||||
} = toRefs(state)
 | 
			
		||||
 | 
			
		||||
const { hashValues, total, loadMoreDisable, editDialog } = toRefs(state);
 | 
			
		||||
 | 
			
		||||
onMounted(() => {
 | 
			
		||||
    state.redisId = props.redisId;
 | 
			
		||||
    state.db = props.db;
 | 
			
		||||
    state.key = props.keyInfo?.key;
 | 
			
		||||
    initData();
 | 
			
		||||
})
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const initData = () => {
 | 
			
		||||
    state.filterValue = '';
 | 
			
		||||
    hscan(true, true);
 | 
			
		||||
}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const getScanMatch = () => {
 | 
			
		||||
    return state.filterValue ? `*${state.filterValue}*` : '*';
 | 
			
		||||
}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const hscan = async (resetTableData = false, resetCursor = false) => {
 | 
			
		||||
    if (resetCursor) {
 | 
			
		||||
@@ -130,10 +121,10 @@ const hscan = async (resetTableData = false, resetCursor = false) => {
 | 
			
		||||
    const scanRes = await redisApi.hscan.request({
 | 
			
		||||
        ...getBaseReqParam(),
 | 
			
		||||
        match: getScanMatch(),
 | 
			
		||||
        ...state.scanParam
 | 
			
		||||
        ...state.scanParam,
 | 
			
		||||
    });
 | 
			
		||||
    state.scanParam.cursor = scanRes.cursor;
 | 
			
		||||
    state.loadMoreDisable = scanRes.cursor == 0
 | 
			
		||||
    state.loadMoreDisable = scanRes.cursor == 0;
 | 
			
		||||
    state.total = scanRes.keySize;
 | 
			
		||||
 | 
			
		||||
    const keys = scanRes.keys;
 | 
			
		||||
@@ -147,7 +138,7 @@ const hscan = async (resetTableData = false, resetCursor = false) => {
 | 
			
		||||
    if (resetTableData) {
 | 
			
		||||
        state.hashValues = hashValue;
 | 
			
		||||
    } else {
 | 
			
		||||
        state.hashValues.push(...hashValue)
 | 
			
		||||
        state.hashValues.push(...hashValue);
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@@ -158,7 +149,7 @@ const hdel = async (field: any, index: any) => {
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    ElMessage.success('删除成功');
 | 
			
		||||
    state.hashValues.splice(index, 1)
 | 
			
		||||
    state.hashValues.splice(index, 1);
 | 
			
		||||
    state.total--;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@@ -167,16 +158,16 @@ const showEditDialog = (row: any) => {
 | 
			
		||||
    state.editDialog.field = row ? row.field : '';
 | 
			
		||||
    state.editDialog.value = row ? row.value : '';
 | 
			
		||||
    state.editDialog.visible = true;
 | 
			
		||||
}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const confirmEditData = async () => {
 | 
			
		||||
    const param = getBaseReqParam();
 | 
			
		||||
 | 
			
		||||
    const field = state.editDialog.field;
 | 
			
		||||
    notBlank(field, "field不能为空");
 | 
			
		||||
    notBlank(field, 'field不能为空');
 | 
			
		||||
 | 
			
		||||
    // 存在数据行,则说明为修改,则要先删除旧数据后新增
 | 
			
		||||
    const dataRow = state.editDialog.dataRow
 | 
			
		||||
    const dataRow = state.editDialog.dataRow;
 | 
			
		||||
    if (dataRow) {
 | 
			
		||||
        await redisApi.hdel.request({
 | 
			
		||||
            ...param,
 | 
			
		||||
@@ -185,7 +176,7 @@ const confirmEditData = async () => {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // 获取hash value内容并新增
 | 
			
		||||
    const value = formatViewerRef.value.getContent()
 | 
			
		||||
    const value = formatViewerRef.value.getContent();
 | 
			
		||||
    const res = await redisApi.hset.request({
 | 
			
		||||
        ...param,
 | 
			
		||||
        value: [
 | 
			
		||||
@@ -196,7 +187,7 @@ const confirmEditData = async () => {
 | 
			
		||||
        ],
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    ElMessage.success("保存成功");
 | 
			
		||||
    ElMessage.success('保存成功');
 | 
			
		||||
    if (dataRow) {
 | 
			
		||||
        state.editDialog.dataRow.value = value;
 | 
			
		||||
        state.editDialog.dataRow.field = field;
 | 
			
		||||
@@ -211,18 +202,17 @@ const confirmEditData = async () => {
 | 
			
		||||
    }
 | 
			
		||||
    state.editDialog.visible = false;
 | 
			
		||||
    state.editDialog.dataRow = null;
 | 
			
		||||
}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const getBaseReqParam = () => {
 | 
			
		||||
    return {
 | 
			
		||||
        id: state.redisId,
 | 
			
		||||
        db: state.db,
 | 
			
		||||
        key: state.key
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
defineExpose({ initData })
 | 
			
		||||
        key: state.key,
 | 
			
		||||
    };
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
defineExpose({ initData });
 | 
			
		||||
</script>
 | 
			
		||||
<style lang="scss">
 | 
			
		||||
#string-value-text {
 | 
			
		||||
 
 | 
			
		||||
@@ -1,34 +1,26 @@
 | 
			
		||||
<template>
 | 
			
		||||
    <div>
 | 
			
		||||
        <el-button @click="showEditDialog(null)" icon="plus" size="small" plain type="primary" class="mb10">添加新行</el-button>
 | 
			
		||||
        <el-table size="small" border :data="values" min-height=300 stripe>
 | 
			
		||||
            <el-table-column type="index" :label="'ID (Total: ' + total + ')'" sortable width="100">
 | 
			
		||||
            </el-table-column>
 | 
			
		||||
            <el-table-column resizable sortable prop="value" label="value" show-overflow-tooltip min-width="200">
 | 
			
		||||
            </el-table-column>
 | 
			
		||||
        <el-table size="small" border :data="values" height="450" min-height="300" stripe>
 | 
			
		||||
            <el-table-column type="index" :label="'ID (Total: ' + total + ')'" sortable width="100"> </el-table-column>
 | 
			
		||||
            <el-table-column resizable sortable prop="value" label="value" show-overflow-tooltip min-width="200"> </el-table-column>
 | 
			
		||||
            <el-table-column label="操作">
 | 
			
		||||
                <template #default="scope">
 | 
			
		||||
                    <el-link @click="showEditDialog(scope.row)" :underline="false" type="primary" icon="edit"
 | 
			
		||||
                        plain></el-link>
 | 
			
		||||
                    <el-link @click="showEditDialog(scope.row)" :underline="false" type="primary" icon="edit" plain></el-link>
 | 
			
		||||
                    <el-popconfirm title="确定删除?" @confirm="lrem(scope.row, scope.$index)">
 | 
			
		||||
                        <template #reference>
 | 
			
		||||
                            <el-link v-auth="'redis:data:del'" :underline="false" type="danger" icon="delete" size="small"
 | 
			
		||||
                                plain class="ml5"></el-link>
 | 
			
		||||
                            <el-link v-auth="'redis:data:del'" :underline="false" type="danger" icon="delete" size="small" plain class="ml5"></el-link>
 | 
			
		||||
                        </template>
 | 
			
		||||
                    </el-popconfirm>
 | 
			
		||||
 | 
			
		||||
                </template>
 | 
			
		||||
            </el-table-column>
 | 
			
		||||
        </el-table>
 | 
			
		||||
        <!-- load more content -->
 | 
			
		||||
        <div class='content-more-container'>
 | 
			
		||||
            <el-button size='small' @click='getListValue(false)' :disabled='loadMoreDisable' class='content-more-btn'>
 | 
			
		||||
                加载更多
 | 
			
		||||
            </el-button>
 | 
			
		||||
        <div class="content-more-container">
 | 
			
		||||
            <el-button size="small" @click="getListValue(false)" :disabled="loadMoreDisable" class="content-more-btn"> 加载更多 </el-button>
 | 
			
		||||
        </div>
 | 
			
		||||
 | 
			
		||||
        <el-dialog title="添加新行" v-model="editDialog.visible" width="600px" :destroy-on-close="true"
 | 
			
		||||
            :close-on-click-modal="false">
 | 
			
		||||
        <el-dialog title="添加新行" v-model="editDialog.visible" width="600px" :destroy-on-close="true" :close-on-click-modal="false">
 | 
			
		||||
            <el-form>
 | 
			
		||||
                <el-form-item>
 | 
			
		||||
                    <format-viewer class="w100" ref="formatViewerRef" :content="editDialog.content"></format-viewer>
 | 
			
		||||
@@ -42,7 +34,6 @@
 | 
			
		||||
                </div>
 | 
			
		||||
            </template>
 | 
			
		||||
        </el-dialog>
 | 
			
		||||
 | 
			
		||||
    </div>
 | 
			
		||||
</template>
 | 
			
		||||
<script lang="ts" setup>
 | 
			
		||||
@@ -60,12 +51,12 @@ const props = defineProps({
 | 
			
		||||
    db: {
 | 
			
		||||
        type: [Number],
 | 
			
		||||
        require: true,
 | 
			
		||||
        default: 0
 | 
			
		||||
        default: 0,
 | 
			
		||||
    },
 | 
			
		||||
    keyInfo: {
 | 
			
		||||
        type: [Object],
 | 
			
		||||
    },
 | 
			
		||||
})
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const formatViewerRef = ref(null) as any;
 | 
			
		||||
 | 
			
		||||
@@ -82,27 +73,22 @@ const state = reactive({
 | 
			
		||||
        visible: false,
 | 
			
		||||
        content: '',
 | 
			
		||||
        dataRow: null as any,
 | 
			
		||||
    }
 | 
			
		||||
    },
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const {
 | 
			
		||||
    total,
 | 
			
		||||
    values,
 | 
			
		||||
    loadMoreDisable,
 | 
			
		||||
    editDialog,
 | 
			
		||||
} = toRefs(state)
 | 
			
		||||
const { total, values, loadMoreDisable, editDialog } = toRefs(state);
 | 
			
		||||
 | 
			
		||||
onMounted(() => {
 | 
			
		||||
    state.redisId = props.redisId;
 | 
			
		||||
    state.db = props.db;
 | 
			
		||||
    state.key = props.keyInfo?.key;
 | 
			
		||||
    initData();
 | 
			
		||||
})
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const initData = () => {
 | 
			
		||||
    state.pageNum = 1;
 | 
			
		||||
    getListValue(true);
 | 
			
		||||
}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const getListValue = async (resetTableData = false) => {
 | 
			
		||||
    const pageNum = state.pageNum;
 | 
			
		||||
@@ -122,10 +108,10 @@ const getListValue = async (resetTableData = false) => {
 | 
			
		||||
    if (resetTableData) {
 | 
			
		||||
        state.values = datas;
 | 
			
		||||
    } else {
 | 
			
		||||
        state.values.push(...datas)
 | 
			
		||||
        state.values.push(...datas);
 | 
			
		||||
    }
 | 
			
		||||
    state.pageNum++;
 | 
			
		||||
    state.loadMoreDisable = state.values.length === state.total
 | 
			
		||||
    state.loadMoreDisable = state.values.length === state.total;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const lset = async (row: any, rowIndex: number) => {
 | 
			
		||||
@@ -141,29 +127,29 @@ const showEditDialog = (row: any) => {
 | 
			
		||||
    state.editDialog.dataRow = row;
 | 
			
		||||
    state.editDialog.content = row ? row.value : '';
 | 
			
		||||
    state.editDialog.visible = true;
 | 
			
		||||
}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const confirmEditData = async () => {
 | 
			
		||||
    const param = getBaseReqParam();
 | 
			
		||||
 | 
			
		||||
    // 存在数据行,则说明为修改,则要先删除旧数据后新增
 | 
			
		||||
    const dataRow = state.editDialog.dataRow
 | 
			
		||||
    const dataRow = state.editDialog.dataRow;
 | 
			
		||||
    if (dataRow) {
 | 
			
		||||
        await redisApi.lrem.request({
 | 
			
		||||
            member: state.editDialog.dataRow.value,
 | 
			
		||||
            count: 1,
 | 
			
		||||
            ...param
 | 
			
		||||
            ...param,
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // 获取list member内容并新增
 | 
			
		||||
    const member = formatViewerRef.value.getContent()
 | 
			
		||||
    const member = formatViewerRef.value.getContent();
 | 
			
		||||
    await redisApi.saveListValue.request({
 | 
			
		||||
        value: [member],
 | 
			
		||||
        ...param
 | 
			
		||||
        ...param,
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    ElMessage.success("保存成功");
 | 
			
		||||
    ElMessage.success('保存成功');
 | 
			
		||||
    if (dataRow) {
 | 
			
		||||
        state.editDialog.dataRow.value = member;
 | 
			
		||||
    } else {
 | 
			
		||||
@@ -172,28 +158,27 @@ const confirmEditData = async () => {
 | 
			
		||||
    }
 | 
			
		||||
    state.editDialog.visible = false;
 | 
			
		||||
    state.editDialog.dataRow = null;
 | 
			
		||||
}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const lrem = async (row: any, index: any) => {
 | 
			
		||||
    await redisApi.lrem.request({
 | 
			
		||||
        ...getBaseReqParam(),
 | 
			
		||||
        member: row.value,
 | 
			
		||||
        count: 1,
 | 
			
		||||
    })
 | 
			
		||||
    ElMessage.success("删除成功");
 | 
			
		||||
    state.values.splice(index, 1)
 | 
			
		||||
    });
 | 
			
		||||
    ElMessage.success('删除成功');
 | 
			
		||||
    state.values.splice(index, 1);
 | 
			
		||||
    state.total--;
 | 
			
		||||
}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const getBaseReqParam = () => {
 | 
			
		||||
    return {
 | 
			
		||||
        id: state.redisId,
 | 
			
		||||
        db: state.db,
 | 
			
		||||
        key: state.key
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
defineExpose({ initData })
 | 
			
		||||
        key: state.key,
 | 
			
		||||
    };
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
defineExpose({ initData });
 | 
			
		||||
</script>
 | 
			
		||||
<style lang="scss"></style>
 | 
			
		||||
 
 | 
			
		||||
@@ -1,38 +1,36 @@
 | 
			
		||||
<template>
 | 
			
		||||
    <div>
 | 
			
		||||
        <el-button @click="showEditDialog(null)" icon="plus" size="small" plain type="primary" class="mb10">添加新行</el-button>
 | 
			
		||||
        <el-table size="small" border :data="setDatas" min-height=300 stripe>
 | 
			
		||||
            <el-table-column type="index" :label="'ID (Total: ' + total + ')'" sortable width="100">
 | 
			
		||||
            </el-table-column>
 | 
			
		||||
            <el-table-column resizable sortable prop="value" label="value" show-overflow-tooltip min-width="200">
 | 
			
		||||
            </el-table-column>
 | 
			
		||||
        <el-table size="small" border :data="setDatas" height="450" min-height="300" stripe>
 | 
			
		||||
            <el-table-column type="index" :label="'ID (Total: ' + total + ')'" sortable width="100"> </el-table-column>
 | 
			
		||||
            <el-table-column resizable sortable prop="value" label="value" show-overflow-tooltip min-width="200"> </el-table-column>
 | 
			
		||||
            <el-table-column label="操作">
 | 
			
		||||
                <template #header>
 | 
			
		||||
                    <el-input class="key-detail-filter-value" v-model="state.filterValue"
 | 
			
		||||
                        @keyup.enter='sscanData(true, true)' placeholder="输入关键词回车搜索" clearable size="small" />
 | 
			
		||||
                    <el-input
 | 
			
		||||
                        class="key-detail-filter-value"
 | 
			
		||||
                        v-model="state.filterValue"
 | 
			
		||||
                        @keyup.enter="sscanData(true, true)"
 | 
			
		||||
                        placeholder="输入关键词回车搜索"
 | 
			
		||||
                        clearable
 | 
			
		||||
                        size="small"
 | 
			
		||||
                    />
 | 
			
		||||
                </template>
 | 
			
		||||
                <template #default="scope">
 | 
			
		||||
                    <el-link @click="showEditDialog(scope.row)" :underline="false" type="primary" icon="edit"
 | 
			
		||||
                        plain></el-link>
 | 
			
		||||
                    <el-link @click="showEditDialog(scope.row)" :underline="false" type="primary" icon="edit" plain></el-link>
 | 
			
		||||
                    <el-popconfirm title="确定删除?" @confirm="srem(scope.row, scope.$index)">
 | 
			
		||||
                        <template #reference>
 | 
			
		||||
                            <el-link v-auth="'redis:data:del'" :underline="false" type="danger" icon="delete" size="small"
 | 
			
		||||
                                plain class="ml5"></el-link>
 | 
			
		||||
                            <el-link v-auth="'redis:data:del'" :underline="false" type="danger" icon="delete" size="small" plain class="ml5"></el-link>
 | 
			
		||||
                        </template>
 | 
			
		||||
                    </el-popconfirm>
 | 
			
		||||
 | 
			
		||||
                </template>
 | 
			
		||||
            </el-table-column>
 | 
			
		||||
        </el-table>
 | 
			
		||||
        <!-- load more content -->
 | 
			
		||||
        <div class='content-more-container'>
 | 
			
		||||
            <el-button size='small' @click='sscanData(false)' :disabled='loadMoreDisable' class='content-more-btn'>
 | 
			
		||||
                加载更多
 | 
			
		||||
            </el-button>
 | 
			
		||||
        <div class="content-more-container">
 | 
			
		||||
            <el-button size="small" @click="sscanData(false)" :disabled="loadMoreDisable" class="content-more-btn"> 加载更多 </el-button>
 | 
			
		||||
        </div>
 | 
			
		||||
 | 
			
		||||
        <el-dialog title="添加新行" v-model="editDialog.visible" width="600px" :destroy-on-close="true"
 | 
			
		||||
            :close-on-click-modal="false">
 | 
			
		||||
        <el-dialog title="添加新行" v-model="editDialog.visible" width="600px" :destroy-on-close="true" :close-on-click-modal="false">
 | 
			
		||||
            <el-form>
 | 
			
		||||
                <el-form-item>
 | 
			
		||||
                    <format-viewer class="w100" ref="formatViewerRef" :content="editDialog.content"></format-viewer>
 | 
			
		||||
@@ -46,7 +44,6 @@
 | 
			
		||||
                </div>
 | 
			
		||||
            </template>
 | 
			
		||||
        </el-dialog>
 | 
			
		||||
 | 
			
		||||
    </div>
 | 
			
		||||
</template>
 | 
			
		||||
<script lang="ts" setup>
 | 
			
		||||
@@ -64,12 +61,12 @@ const props = defineProps({
 | 
			
		||||
    db: {
 | 
			
		||||
        type: [Number],
 | 
			
		||||
        require: true,
 | 
			
		||||
        default: 0
 | 
			
		||||
        default: 0,
 | 
			
		||||
    },
 | 
			
		||||
    keyInfo: {
 | 
			
		||||
        type: [Object],
 | 
			
		||||
    },
 | 
			
		||||
})
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const formatViewerRef = ref(null) as any;
 | 
			
		||||
 | 
			
		||||
@@ -91,32 +88,27 @@ const state = reactive({
 | 
			
		||||
        visible: false,
 | 
			
		||||
        content: '',
 | 
			
		||||
        dataRow: null as any,
 | 
			
		||||
    }
 | 
			
		||||
    },
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const {
 | 
			
		||||
    total,
 | 
			
		||||
    setDatas,
 | 
			
		||||
    loadMoreDisable,
 | 
			
		||||
    editDialog,
 | 
			
		||||
} = toRefs(state)
 | 
			
		||||
const { total, setDatas, loadMoreDisable, editDialog } = toRefs(state);
 | 
			
		||||
 | 
			
		||||
onMounted(() => {
 | 
			
		||||
    state.redisId = props.redisId;
 | 
			
		||||
    state.db = props.db;
 | 
			
		||||
    state.key = props.keyInfo?.key;
 | 
			
		||||
    initData();
 | 
			
		||||
})
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const initData = () => {
 | 
			
		||||
    state.filterValue = '';
 | 
			
		||||
    sscanData(true, true);
 | 
			
		||||
    getTotal();
 | 
			
		||||
}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const getScanMatch = () => {
 | 
			
		||||
    return state.filterValue ? `*${state.filterValue}*` : '*';
 | 
			
		||||
}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const sscanData = async (resetDatas = true, resetCursor = false) => {
 | 
			
		||||
    if (resetCursor) {
 | 
			
		||||
@@ -125,7 +117,7 @@ const sscanData = async (resetDatas = true, resetCursor = false) => {
 | 
			
		||||
    const res = await redisApi.sscan.request({
 | 
			
		||||
        ...getBaseReqParam(),
 | 
			
		||||
        match: getScanMatch(),
 | 
			
		||||
        ...state.scanParam
 | 
			
		||||
        ...state.scanParam,
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    if (resetDatas) {
 | 
			
		||||
@@ -134,44 +126,44 @@ const sscanData = async (resetDatas = true, resetCursor = false) => {
 | 
			
		||||
    res.keys.forEach((x: any) => {
 | 
			
		||||
        state.setDatas.push({
 | 
			
		||||
            value: x,
 | 
			
		||||
        })
 | 
			
		||||
    })
 | 
			
		||||
        });
 | 
			
		||||
    });
 | 
			
		||||
    state.scanParam.cursor = res.cursor;
 | 
			
		||||
    state.loadMoreDisable = res.cursor == 0
 | 
			
		||||
    state.loadMoreDisable = res.cursor == 0;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const getTotal = () => {
 | 
			
		||||
    redisApi.scard.request(getBaseReqParam()).then((res) => {
 | 
			
		||||
        state.total = res;
 | 
			
		||||
    });
 | 
			
		||||
}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const showEditDialog = (row: any) => {
 | 
			
		||||
    state.editDialog.dataRow = row;
 | 
			
		||||
    state.editDialog.content = row ? row.value : '';
 | 
			
		||||
    state.editDialog.visible = true;
 | 
			
		||||
}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const confirmEditData = async () => {
 | 
			
		||||
    const param = getBaseReqParam();
 | 
			
		||||
 | 
			
		||||
    // 存在数据行,则说明为修改,则要先删除旧数据后新增
 | 
			
		||||
    const dataRow = state.editDialog.dataRow
 | 
			
		||||
    const dataRow = state.editDialog.dataRow;
 | 
			
		||||
    if (dataRow) {
 | 
			
		||||
        await redisApi.srem.request({
 | 
			
		||||
            member: state.editDialog.dataRow.value,
 | 
			
		||||
            ...param
 | 
			
		||||
            ...param,
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // 获取set member内容并新增
 | 
			
		||||
    const member = formatViewerRef.value.getContent()
 | 
			
		||||
    const member = formatViewerRef.value.getContent();
 | 
			
		||||
    await redisApi.sadd.request({
 | 
			
		||||
        member,
 | 
			
		||||
        ...param
 | 
			
		||||
        ...param,
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    ElMessage.success("保存成功");
 | 
			
		||||
    ElMessage.success('保存成功');
 | 
			
		||||
    if (dataRow) {
 | 
			
		||||
        state.editDialog.dataRow.value = member;
 | 
			
		||||
    } else {
 | 
			
		||||
@@ -180,26 +172,26 @@ const confirmEditData = async () => {
 | 
			
		||||
    }
 | 
			
		||||
    state.editDialog.visible = false;
 | 
			
		||||
    state.editDialog.dataRow = null;
 | 
			
		||||
}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const srem = async (row: any, index: any) => {
 | 
			
		||||
    await redisApi.srem.request({
 | 
			
		||||
        ...getBaseReqParam(),
 | 
			
		||||
        member: row.value,
 | 
			
		||||
    })
 | 
			
		||||
    ElMessage.success("删除成功");
 | 
			
		||||
    state.setDatas.splice(index, 1)
 | 
			
		||||
    });
 | 
			
		||||
    ElMessage.success('删除成功');
 | 
			
		||||
    state.setDatas.splice(index, 1);
 | 
			
		||||
    state.total--;
 | 
			
		||||
}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const getBaseReqParam = () => {
 | 
			
		||||
    return {
 | 
			
		||||
        id: state.redisId,
 | 
			
		||||
        db: state.db,
 | 
			
		||||
        key: state.key
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
        key: state.key,
 | 
			
		||||
    };
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
defineExpose({ initData })
 | 
			
		||||
defineExpose({ initData });
 | 
			
		||||
</script>
 | 
			
		||||
<style lang="scss"></style>
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,6 @@
 | 
			
		||||
<template>
 | 
			
		||||
    <div>
 | 
			
		||||
        <el-form class='key-content-string' label-width="85px">
 | 
			
		||||
        <el-form class="key-content-string" label-width="auto">
 | 
			
		||||
            <div>
 | 
			
		||||
                <format-viewer ref="formatViewerRef" :content="string.value"></format-viewer>
 | 
			
		||||
            </div>
 | 
			
		||||
@@ -31,9 +31,9 @@ const props = defineProps({
 | 
			
		||||
    keyInfo: {
 | 
			
		||||
        type: [Object],
 | 
			
		||||
    },
 | 
			
		||||
})
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const formatViewerRef = ref(null) as any
 | 
			
		||||
const formatViewerRef = ref(null) as any;
 | 
			
		||||
 | 
			
		||||
const state = reactive({
 | 
			
		||||
    redisId: 0,
 | 
			
		||||
@@ -50,20 +50,18 @@ const state = reactive({
 | 
			
		||||
    },
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const {
 | 
			
		||||
    string,
 | 
			
		||||
} = toRefs(state)
 | 
			
		||||
const { string } = toRefs(state);
 | 
			
		||||
 | 
			
		||||
onMounted(() => {
 | 
			
		||||
    state.redisId = props.redisId
 | 
			
		||||
    state.db = props.db
 | 
			
		||||
    state.redisId = props.redisId;
 | 
			
		||||
    state.db = props.db;
 | 
			
		||||
    state.key = props.keyInfo?.key;
 | 
			
		||||
    initData();
 | 
			
		||||
})
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const initData = () => {
 | 
			
		||||
    getStringValue();
 | 
			
		||||
}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const getStringValue = async () => {
 | 
			
		||||
    if (state.key) {
 | 
			
		||||
@@ -86,12 +84,11 @@ const getBaseReqParam = () => {
 | 
			
		||||
    return {
 | 
			
		||||
        id: state.redisId,
 | 
			
		||||
        db: state.db,
 | 
			
		||||
        key: state.key
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
defineExpose({ initData })
 | 
			
		||||
        key: state.key,
 | 
			
		||||
    };
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
defineExpose({ initData });
 | 
			
		||||
</script>
 | 
			
		||||
<style lang="scss">
 | 
			
		||||
.key-content-string .format-viewer-container {
 | 
			
		||||
 
 | 
			
		||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user