mirror of
				https://gitee.com/dromara/mayfly-go
				synced 2025-11-04 08:20:25 +08:00 
			
		
		
		
	refactor: sql取消执行逻辑调整、前端使用vueuse重构部分代码
This commit is contained in:
		@@ -10,6 +10,7 @@
 | 
			
		||||
  },
 | 
			
		||||
  "dependencies": {
 | 
			
		||||
    "@element-plus/icons-vue": "^2.1.0",
 | 
			
		||||
    "@vueuse/core": "^10.7.0",
 | 
			
		||||
    "asciinema-player": "^3.6.2",
 | 
			
		||||
    "axios": "^1.6.2",
 | 
			
		||||
    "clipboard": "^2.0.11",
 | 
			
		||||
@@ -21,7 +22,7 @@
 | 
			
		||||
    "jsencrypt": "^3.3.2",
 | 
			
		||||
    "lodash": "^4.17.21",
 | 
			
		||||
    "mitt": "^3.0.1",
 | 
			
		||||
    "monaco-editor": "^0.44.0",
 | 
			
		||||
    "monaco-editor": "^0.45.0",
 | 
			
		||||
    "monaco-sql-languages": "^0.11.0",
 | 
			
		||||
    "monaco-themes": "^0.4.4",
 | 
			
		||||
    "nprogress": "^0.2.0",
 | 
			
		||||
@@ -32,7 +33,7 @@
 | 
			
		||||
    "splitpanes": "^3.1.5",
 | 
			
		||||
    "sql-formatter": "^14.0.0",
 | 
			
		||||
    "uuid": "^9.0.1",
 | 
			
		||||
    "vue": "^3.3.10",
 | 
			
		||||
    "vue": "^3.3.11",
 | 
			
		||||
    "vue-router": "^4.2.5",
 | 
			
		||||
    "xterm": "^5.3.0",
 | 
			
		||||
    "xterm-addon-fit": "^0.8.0",
 | 
			
		||||
@@ -54,7 +55,7 @@
 | 
			
		||||
    "prettier": "^3.1.0",
 | 
			
		||||
    "sass": "^1.69.0",
 | 
			
		||||
    "typescript": "^5.3.2",
 | 
			
		||||
    "vite": "^5.0.5",
 | 
			
		||||
    "vite": "^5.0.7",
 | 
			
		||||
    "vue-eslint-parser": "^9.3.2"
 | 
			
		||||
  },
 | 
			
		||||
  "browserslist": [
 | 
			
		||||
 
 | 
			
		||||
@@ -28,6 +28,7 @@ import Setings from '@/layout/navBars/breadcrumb/setings.vue';
 | 
			
		||||
import mittBus from '@/common/utils/mitt';
 | 
			
		||||
import { getThemeConfig } from './common/utils/storage';
 | 
			
		||||
import { useWatermark } from '@/common/sysconfig';
 | 
			
		||||
import { useIntervalFn } from '@vueuse/core';
 | 
			
		||||
 | 
			
		||||
const setingsRef = ref();
 | 
			
		||||
const route = useRoute();
 | 
			
		||||
@@ -40,12 +41,6 @@ const openSetingsDrawer = () => {
 | 
			
		||||
    setingsRef.value.openDrawer();
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const prefers = matchMedia('(prefers-color-scheme: dark)');
 | 
			
		||||
const switchDarkFollowOS = () => {
 | 
			
		||||
    // 跟随系统主题
 | 
			
		||||
    themeConfigStores.switchDark(prefers.matches);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// 页面加载时
 | 
			
		||||
onMounted(() => {
 | 
			
		||||
    nextTick(() => {
 | 
			
		||||
@@ -60,7 +55,6 @@ onMounted(() => {
 | 
			
		||||
            themeConfigStores.setThemeConfig({ themeConfig: tc });
 | 
			
		||||
            document.documentElement.style.cssText = getLocal('themeConfigStyle');
 | 
			
		||||
        }
 | 
			
		||||
        switchDarkFollowOS();
 | 
			
		||||
 | 
			
		||||
        // 是否开启水印
 | 
			
		||||
        useWatermark().then((res) => {
 | 
			
		||||
@@ -77,36 +71,35 @@ watch(
 | 
			
		||||
            setTimeout(() => {
 | 
			
		||||
                setWatermarkContent();
 | 
			
		||||
                refreshWatermarkTime();
 | 
			
		||||
                resume();
 | 
			
		||||
            }, 500);
 | 
			
		||||
        } else {
 | 
			
		||||
            pause();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
// 刷新水印时间
 | 
			
		||||
const { pause, resume } = useIntervalFn(() => {
 | 
			
		||||
    if (!themeConfig.value.isWatermark) {
 | 
			
		||||
        pause();
 | 
			
		||||
    }
 | 
			
		||||
    refreshWatermarkTime();
 | 
			
		||||
}, 60000);
 | 
			
		||||
 | 
			
		||||
const setWatermarkContent = () => {
 | 
			
		||||
    themeConfigStores.setWatermarkUser();
 | 
			
		||||
    themeConfigStores.setWatermarkNowTime();
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
let refreshWatermarkTimeInterval: any = null;
 | 
			
		||||
/**
 | 
			
		||||
 * 刷新水印时间
 | 
			
		||||
 */
 | 
			
		||||
const refreshWatermarkTime = () => {
 | 
			
		||||
    if (refreshWatermarkTimeInterval) {
 | 
			
		||||
        clearInterval(refreshWatermarkTimeInterval);
 | 
			
		||||
    }
 | 
			
		||||
    refreshWatermarkTimeInterval = setInterval(() => {
 | 
			
		||||
        if (themeConfig.value.isWatermark) {
 | 
			
		||||
    themeConfigStores.setWatermarkNowTime();
 | 
			
		||||
        } else {
 | 
			
		||||
            clearInterval(refreshWatermarkTimeInterval);
 | 
			
		||||
        }
 | 
			
		||||
    }, 60000);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// 页面销毁时,关闭监听布局配置
 | 
			
		||||
onUnmounted(() => {
 | 
			
		||||
    clearInterval(refreshWatermarkTimeInterval);
 | 
			
		||||
    mittBus.off('openSetingsDrawer', () => {});
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -49,32 +49,27 @@ class Api {
 | 
			
		||||
     * 请求对应的该api
 | 
			
		||||
     * @param {Object} param 请求该api的参数
 | 
			
		||||
     */
 | 
			
		||||
    request(param: any = null, options: any = null, headers: any = null): Promise<any> {
 | 
			
		||||
    request(param: any = null, options: any = {}): Promise<any> {
 | 
			
		||||
        if (this.beforeHandler) {
 | 
			
		||||
            this.beforeHandler(param);
 | 
			
		||||
        }
 | 
			
		||||
        return request.request(this.method, this.url, param, headers, options);
 | 
			
		||||
        return request.request(this.method, this.url, param, options);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 请求对应的该api
 | 
			
		||||
     * 允许取消的请求, 使用Api.cancelReq(key) 取消请求
 | 
			
		||||
     * @param key 用于取消该key关联的请求
 | 
			
		||||
     * @param {Object} param 请求该api的参数
 | 
			
		||||
     */
 | 
			
		||||
    requestCanCancel(key: string, param: any = null, options: any = null, headers: any = null): Promise<any> {
 | 
			
		||||
    allowCancelReq(key: string, param: any = null, options: RequestInit = {}): Promise<any> {
 | 
			
		||||
        let controller = Api.abortControllers.get(key);
 | 
			
		||||
        if (!controller) {
 | 
			
		||||
            controller = new AbortController();
 | 
			
		||||
            Api.abortControllers.set(key, controller);
 | 
			
		||||
        }
 | 
			
		||||
        if (options) {
 | 
			
		||||
        options.signal = controller.signal;
 | 
			
		||||
        } else {
 | 
			
		||||
            options = {
 | 
			
		||||
                signal: controller.signal,
 | 
			
		||||
            };
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return this.request(param, options, headers);
 | 
			
		||||
        return this.request(param, options);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**    静态方法     **/
 | 
			
		||||
 
 | 
			
		||||
@@ -1,11 +1,20 @@
 | 
			
		||||
import router from '../router';
 | 
			
		||||
import Axios from 'axios';
 | 
			
		||||
import config from './config';
 | 
			
		||||
import { getClientId, getToken } from './utils/storage';
 | 
			
		||||
import { templateResolve } from './utils/string';
 | 
			
		||||
import { ElMessage } from 'element-plus';
 | 
			
		||||
import axios from 'axios';
 | 
			
		||||
 | 
			
		||||
export default {
 | 
			
		||||
    request,
 | 
			
		||||
    fetchReq,
 | 
			
		||||
    get,
 | 
			
		||||
    post,
 | 
			
		||||
    put,
 | 
			
		||||
    del,
 | 
			
		||||
    getApiUrl,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export interface Result {
 | 
			
		||||
    /**
 | 
			
		||||
     * 响应码
 | 
			
		||||
@@ -30,6 +39,7 @@ enum ResultEnum {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const baseUrl: string = config.baseApiUrl;
 | 
			
		||||
// const baseUrl: string = 'http://localhost:18888/api';
 | 
			
		||||
// const baseWsUrl: string = config.baseWsUrl;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
@@ -42,13 +52,13 @@ function notifyErrorMsg(msg: string) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// create an axios instance
 | 
			
		||||
const service = Axios.create({
 | 
			
		||||
const axiosInst = axios.create({
 | 
			
		||||
    baseURL: baseUrl, // url = base url + request url
 | 
			
		||||
    timeout: 60000, // request timeout
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
// request interceptor
 | 
			
		||||
service.interceptors.request.use(
 | 
			
		||||
axiosInst.interceptors.request.use(
 | 
			
		||||
    (config: any) => {
 | 
			
		||||
        // do something before request is sent
 | 
			
		||||
        const token = getToken();
 | 
			
		||||
@@ -65,21 +75,8 @@ service.interceptors.request.use(
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
// response interceptor
 | 
			
		||||
service.interceptors.response.use(
 | 
			
		||||
    (response) => {
 | 
			
		||||
        // 获取请求返回结果
 | 
			
		||||
        const res: Result = response.data;
 | 
			
		||||
        if (res.code === ResultEnum.SUCCESS) {
 | 
			
		||||
            return res.data;
 | 
			
		||||
        }
 | 
			
		||||
        // 如果提示没有权限,则移除token,使其重新登录
 | 
			
		||||
        if (res.code === ResultEnum.NO_PERMISSION) {
 | 
			
		||||
            router.push({
 | 
			
		||||
                path: '/401',
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
        return Promise.reject(res);
 | 
			
		||||
    },
 | 
			
		||||
axiosInst.interceptors.response.use(
 | 
			
		||||
    (response) => response,
 | 
			
		||||
    (e: any) => {
 | 
			
		||||
        const rejectPromise = Promise.reject(e);
 | 
			
		||||
 | 
			
		||||
@@ -125,35 +122,37 @@ service.interceptors.response.use(
 | 
			
		||||
 * @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不能为空');
 | 
			
		||||
function request(method: string, url: string, params: any = null, options: any = {}): Promise<any> {
 | 
			
		||||
    if (!url) {
 | 
			
		||||
        throw new Error('请求url不能为空');
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // 简单判断该url是否是restful风格
 | 
			
		||||
    if (url.indexOf('{') != -1) {
 | 
			
		||||
        url = templateResolve(url, params);
 | 
			
		||||
    }
 | 
			
		||||
    const query: any = {
 | 
			
		||||
 | 
			
		||||
    const req: any = {
 | 
			
		||||
        method,
 | 
			
		||||
        url: url,
 | 
			
		||||
        url,
 | 
			
		||||
        ...options,
 | 
			
		||||
    };
 | 
			
		||||
    if (headers) {
 | 
			
		||||
        query.headers = headers;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // post和put使用json格式传参
 | 
			
		||||
    if (method === 'post' || method === 'put') {
 | 
			
		||||
        query.data = params;
 | 
			
		||||
        req.data = params;
 | 
			
		||||
    } else {
 | 
			
		||||
        query.params = params;
 | 
			
		||||
        req.params = params;
 | 
			
		||||
    }
 | 
			
		||||
    return service
 | 
			
		||||
        .request(query)
 | 
			
		||||
        .then((res) => res)
 | 
			
		||||
 | 
			
		||||
    return axiosInst
 | 
			
		||||
        .request(req)
 | 
			
		||||
        .then((response) => {
 | 
			
		||||
            // 获取请求返回结果
 | 
			
		||||
            const result: Result = response.data;
 | 
			
		||||
            return parseResult(result);
 | 
			
		||||
        })
 | 
			
		||||
        .catch((e) => {
 | 
			
		||||
            // 如果返回的code不为成功,则会返回对应的错误msg,则直接统一通知即可。忽略登录超时或没有权限的提示(直接跳转至401页面)
 | 
			
		||||
            if (e.msg && e?.code != ResultEnum.NO_PERMISSION) {
 | 
			
		||||
                notifyErrorMsg(e.msg);
 | 
			
		||||
            }
 | 
			
		||||
            return Promise.reject(e);
 | 
			
		||||
        });
 | 
			
		||||
}
 | 
			
		||||
@@ -165,20 +164,20 @@ function request(method: string, url: string, params: any = null, headers: any =
 | 
			
		||||
 * @param {Object} url   uri
 | 
			
		||||
 * @param {Object} params 参数
 | 
			
		||||
 */
 | 
			
		||||
function get(url: string, params: any = null, headers: any = null, options: any = null): Promise<any> {
 | 
			
		||||
    return request('get', url, params, headers, options);
 | 
			
		||||
function get(url: string, params: any = null, options: any = {}): Promise<any> {
 | 
			
		||||
    return request('get', url, params, options);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function post(url: string, params: any = null, headers: any = null, options: any = null): Promise<any> {
 | 
			
		||||
    return request('post', url, params, headers, options);
 | 
			
		||||
function post(url: string, params: any = null, options: any = {}): Promise<any> {
 | 
			
		||||
    return request('post', url, params, options);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function put(url: string, params: any = null, headers: any = null, options: any = null): Promise<any> {
 | 
			
		||||
    return request('put', url, params, headers, options);
 | 
			
		||||
function put(url: string, params: any = null, options: any = {}): Promise<any> {
 | 
			
		||||
    return request('put', url, params, options);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function del(url: string, params: any = null, headers: any = null, options: any = null): Promise<any> {
 | 
			
		||||
    return request('delete', url, params, headers, options);
 | 
			
		||||
function del(url: string, params: any = null, options: any = {}): Promise<any> {
 | 
			
		||||
    return request('delete', url, params, options);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function getApiUrl(url: string) {
 | 
			
		||||
@@ -191,11 +190,80 @@ export function joinClientParams(): string {
 | 
			
		||||
    return `token=${getToken()}&clientId=${getClientId()}`;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default {
 | 
			
		||||
    request,
 | 
			
		||||
    get,
 | 
			
		||||
    post,
 | 
			
		||||
    put,
 | 
			
		||||
    del,
 | 
			
		||||
    getApiUrl,
 | 
			
		||||
};
 | 
			
		||||
async function fetchReq(method: string, url: string, params: any = null, options: RequestInit = {}): Promise<any> {
 | 
			
		||||
    options.method = method;
 | 
			
		||||
 | 
			
		||||
    if (params) {
 | 
			
		||||
        // post和put使用json格式传参
 | 
			
		||||
        if (method === 'post' || method === 'put') {
 | 
			
		||||
            options.body = JSON.stringify(params);
 | 
			
		||||
        } else {
 | 
			
		||||
            const searchParam = new URLSearchParams();
 | 
			
		||||
            Object.keys(params).forEach((key) => {
 | 
			
		||||
                const val = params[key];
 | 
			
		||||
                if (val) {
 | 
			
		||||
                    searchParam.append(key, val);
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
            url = `${url}?${searchParam.toString()}`;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Part 1: Add headers and attach auth token
 | 
			
		||||
    const headers = new Headers(options.headers || {});
 | 
			
		||||
 | 
			
		||||
    const token = getToken();
 | 
			
		||||
    if (token) {
 | 
			
		||||
        headers.set('Authorization', token);
 | 
			
		||||
        headers.set('ClientId', getClientId());
 | 
			
		||||
    }
 | 
			
		||||
    options.headers = headers;
 | 
			
		||||
 | 
			
		||||
    try {
 | 
			
		||||
        const res: Response = await fetch(`${baseUrl}${url}`, options);
 | 
			
		||||
        if (!res.ok) {
 | 
			
		||||
            throw new Error(`请求响应错误: 状态码=${res.status}`);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        const jsonRes = await res.json();
 | 
			
		||||
        // 获取请求返回结果
 | 
			
		||||
        const result: Result = jsonRes;
 | 
			
		||||
        return parseResult(result);
 | 
			
		||||
    } catch (e: any) {
 | 
			
		||||
        const rejectPromise = Promise.reject(e);
 | 
			
		||||
 | 
			
		||||
        if (e?.name == 'AbortError') {
 | 
			
		||||
            console.log('请求已取消');
 | 
			
		||||
            return rejectPromise;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (e.message) {
 | 
			
		||||
            notifyErrorMsg(e.message);
 | 
			
		||||
            return rejectPromise;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        notifyErrorMsg('网络请求错误');
 | 
			
		||||
        console.error(e);
 | 
			
		||||
        return rejectPromise;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function parseResult(result: Result) {
 | 
			
		||||
    if (result.code === ResultEnum.SUCCESS) {
 | 
			
		||||
        return result.data;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // 如果提示没有权限,则移除token,使其重新登录
 | 
			
		||||
    if (result.code === ResultEnum.NO_PERMISSION) {
 | 
			
		||||
        router.push({
 | 
			
		||||
            path: '/401',
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // 如果返回的code不为成功,则会返回对应的错误msg,则直接统一通知即可。忽略登录超时或没有权限的提示(直接跳转至401页面)
 | 
			
		||||
    if (result.msg && result?.code != ResultEnum.NO_PERMISSION) {
 | 
			
		||||
        notifyErrorMsg(result.msg);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return Promise.reject(result);
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -2,8 +2,8 @@ import openApi from './openApi';
 | 
			
		||||
 | 
			
		||||
// 登录是否使用验证码配置key
 | 
			
		||||
const AccountLoginSecurity = 'AccountLoginSecurity';
 | 
			
		||||
const UseLoginCaptchaConfigKey = 'UseLoginCaptcha';
 | 
			
		||||
const UseWatermarkConfigKey = 'UseWatermark';
 | 
			
		||||
const MachineConfig = 'MachineConfig';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 获取系统配置值
 | 
			
		||||
@@ -43,15 +43,6 @@ export async function getAccountLoginSecurity(): Promise<any> {
 | 
			
		||||
    return jsonValue;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 是否使用登录验证码
 | 
			
		||||
 *
 | 
			
		||||
 * @returns
 | 
			
		||||
 */
 | 
			
		||||
export async function useLoginCaptcha(): Promise<boolean> {
 | 
			
		||||
    return await getBoolConfigValue(UseLoginCaptchaConfigKey, true);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 是否启用水印信息配置
 | 
			
		||||
 *
 | 
			
		||||
@@ -75,13 +66,6 @@ export async function useWatermark(): Promise<any> {
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function convertBool(value: string, defaultValue: boolean) {
 | 
			
		||||
    if (!value) {
 | 
			
		||||
        return defaultValue;
 | 
			
		||||
    }
 | 
			
		||||
    return value == '1' || value == 'true';
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 获取LDAP登录配置
 | 
			
		||||
 *
 | 
			
		||||
@@ -91,3 +75,32 @@ export async function getLdapEnabled(): Promise<any> {
 | 
			
		||||
    const value = await openApi.getLdapEnabled();
 | 
			
		||||
    return convertBool(value, false);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 是否启用水印信息配置
 | 
			
		||||
 *
 | 
			
		||||
 * @returns
 | 
			
		||||
 */
 | 
			
		||||
export async function getMachineConfig(): Promise<any> {
 | 
			
		||||
    const value = await getConfigValue(MachineConfig);
 | 
			
		||||
    const defaultValue = {
 | 
			
		||||
        // 默认1gb
 | 
			
		||||
        uploadMaxFileSize: '1GB',
 | 
			
		||||
    };
 | 
			
		||||
    if (!value) {
 | 
			
		||||
        return defaultValue;
 | 
			
		||||
    }
 | 
			
		||||
    try {
 | 
			
		||||
        const jsonValue = JSON.parse(value);
 | 
			
		||||
        return jsonValue;
 | 
			
		||||
    } catch (e) {
 | 
			
		||||
        return defaultValue;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function convertBool(value: string, defaultValue: boolean) {
 | 
			
		||||
    if (!value) {
 | 
			
		||||
        return defaultValue;
 | 
			
		||||
    }
 | 
			
		||||
    return value == '1' || value == 'true';
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,17 +0,0 @@
 | 
			
		||||
import { ref } from 'vue';
 | 
			
		||||
 | 
			
		||||
const vw = ref(document.documentElement.clientWidth);
 | 
			
		||||
const vh = ref(document.documentElement.clientHeight);
 | 
			
		||||
 | 
			
		||||
window.addEventListener('resize', () => {
 | 
			
		||||
    vw.value = document.documentElement.clientWidth;
 | 
			
		||||
    vh.value = document.documentElement.clientHeight;
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 获取视图宽高
 | 
			
		||||
 * @returns 视图宽高
 | 
			
		||||
 */
 | 
			
		||||
export function useViewport() {
 | 
			
		||||
    return { vw, vh };
 | 
			
		||||
}
 | 
			
		||||
@@ -15,6 +15,37 @@ export function formatByteSize(size: number, fixed = 2) {
 | 
			
		||||
    return parseFloat((size / Math.pow(base, exponent)).toFixed(fixed)) + units[exponent];
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 容量转为对应的字节大小,如 1KB转为 1024
 | 
			
		||||
 * @param sizeString  1kb 1gb等
 | 
			
		||||
 * @returns
 | 
			
		||||
 */
 | 
			
		||||
export function convertToBytes(sizeStr: string) {
 | 
			
		||||
    sizeStr = sizeStr.trim();
 | 
			
		||||
    const unit = sizeStr.slice(-2);
 | 
			
		||||
 | 
			
		||||
    const valueStr = sizeStr.slice(0, -2);
 | 
			
		||||
    const value = parseInt(valueStr, 10);
 | 
			
		||||
 | 
			
		||||
    let bytes = 0;
 | 
			
		||||
 | 
			
		||||
    switch (unit.toUpperCase()) {
 | 
			
		||||
        case 'KB':
 | 
			
		||||
            bytes = value * 1024;
 | 
			
		||||
            break;
 | 
			
		||||
        case 'MB':
 | 
			
		||||
            bytes = value * 1024 * 1024;
 | 
			
		||||
            break;
 | 
			
		||||
        case 'GB':
 | 
			
		||||
            bytes = value * 1024 * 1024 * 1024;
 | 
			
		||||
            break;
 | 
			
		||||
        default:
 | 
			
		||||
            throw new Error('Invalid size unit');
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return bytes;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 格式化json字符串
 | 
			
		||||
 * @param txt  json字符串
 | 
			
		||||
 
 | 
			
		||||
@@ -34,8 +34,8 @@
 | 
			
		||||
<script setup lang="ts" name="layoutTagsViewContextmenu">
 | 
			
		||||
import { computed, reactive, onMounted, onUnmounted, watch } from 'vue';
 | 
			
		||||
import { ContextmenuItem } from './index';
 | 
			
		||||
import { useViewport } from '@/common/use';
 | 
			
		||||
import SvgIcon from '@/components/svgIcon/index.vue';
 | 
			
		||||
import { useWindowSize } from '@vueuse/core';
 | 
			
		||||
 | 
			
		||||
// 定义父组件传过来的值
 | 
			
		||||
const props = defineProps({
 | 
			
		||||
@@ -57,7 +57,7 @@ const props = defineProps({
 | 
			
		||||
// 定义子组件向父组件传值/事件
 | 
			
		||||
const emit = defineEmits(['currentContextmenuClick']);
 | 
			
		||||
 | 
			
		||||
const { vw, vh } = useViewport();
 | 
			
		||||
const { width: vw, height: vh } = useWindowSize();
 | 
			
		||||
 | 
			
		||||
// 定义变量内容
 | 
			
		||||
const state = reactive({
 | 
			
		||||
 
 | 
			
		||||
@@ -1,212 +1,43 @@
 | 
			
		||||
<template>
 | 
			
		||||
    <div class="dynamic-form">
 | 
			
		||||
		<el-form
 | 
			
		||||
			:model="form"
 | 
			
		||||
			ref="dynamicForm"
 | 
			
		||||
			:label-width="formInfo.labelWidth ? formInfo.labelWidth : '100px'"
 | 
			
		||||
			:size="formInfo.size ? formInfo.size : 'small'"
 | 
			
		||||
		>
 | 
			
		||||
			<el-row v-for="fr in formInfo.formRows" :key="fr.key">
 | 
			
		||||
				<el-col v-for="item in fr" :key="item.key" :span="item.span ? item.span : 24 / fr.length">
 | 
			
		||||
					<el-form-item :prop="item.name" :label="item.label" :label-width="item.labelWidth" :required="item.required" :rules="item.rules">
 | 
			
		||||
						<!-- input输入框 -->
 | 
			
		||||
						<el-input
 | 
			
		||||
							v-if="item.type === 'input'"
 | 
			
		||||
							v-model.trim="form[item.name]"
 | 
			
		||||
							:placeholder="item.placeholder"
 | 
			
		||||
							:type="item.inputType"
 | 
			
		||||
							clearable
 | 
			
		||||
        <el-form v-bind="$attrs" ref="formRef" :model="formData" label-width="auto">
 | 
			
		||||
            <el-form-item v-for="item in formItems as any" :key="item.name" :prop="item.model" :label="item.name" required>
 | 
			
		||||
                <el-input v-if="!item.options" v-model="formData[item.model]" :placeholder="item.placeholder" autocomplete="off" clearable></el-input>
 | 
			
		||||
 | 
			
		||||
							@change="item.change ? item.change(form) : ''"
 | 
			
		||||
						></el-input>
 | 
			
		||||
 | 
			
		||||
						<!-- 普通文本信息(可用于不可修改字段等) -->
 | 
			
		||||
						<span v-else-if="item.type === 'text'">{{ form[item.name] }}</span>
 | 
			
		||||
 | 
			
		||||
						<!-- select选择框 -->
 | 
			
		||||
						<!-- optionProps.label: 指定option中的label为options对象的某个属性值,默认就是label字段 -->
 | 
			
		||||
						<!-- optionProps.value: 指定option中的value为options对象的某个属性值,默认就是value字段 -->
 | 
			
		||||
						<el-select
 | 
			
		||||
							v-else-if="item.type === 'select'"
 | 
			
		||||
							v-model.trim="form[item.name]"
 | 
			
		||||
							:placeholder="item.placeholder"
 | 
			
		||||
							:filterable="item.filterable"
 | 
			
		||||
							:remote="item.remote"
 | 
			
		||||
							:remote-method="item.remoteMethod"
 | 
			
		||||
							@focus="item.focus ? item.focus(form) : ''"
 | 
			
		||||
							clearable
 | 
			
		||||
							:disabled="item.updateDisabled && form.id != null"
 | 
			
		||||
							style="width: 100%"
 | 
			
		||||
						>
 | 
			
		||||
							<el-option
 | 
			
		||||
								v-for="i in item.options"
 | 
			
		||||
								:key="i.key"
 | 
			
		||||
								:label="i[item.optionProps ? item.optionProps.label || 'label' : 'label']"
 | 
			
		||||
								:value="i[item.optionProps ? item.optionProps.value || 'value' : 'value']"
 | 
			
		||||
							></el-option>
 | 
			
		||||
                <el-select v-else v-model="formData[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-col>
 | 
			
		||||
			</el-row>
 | 
			
		||||
 | 
			
		||||
			<el-row type="flex" justify="center">
 | 
			
		||||
				<slot name="btns" :submitDisabled="submitDisabled" :data="form" :submit="submit">
 | 
			
		||||
					<el-button @click="reset" size="small">重 置</el-button>
 | 
			
		||||
					<el-button type="primary" @click="submit" size="small">保 存</el-button>
 | 
			
		||||
				</slot>
 | 
			
		||||
			</el-row>
 | 
			
		||||
        </el-form>
 | 
			
		||||
    </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script lang="ts">
 | 
			
		||||
import { watch, ref, toRefs, reactive, onMounted, defineComponent } from 'vue';
 | 
			
		||||
import { ElMessage } from 'element-plus';
 | 
			
		||||
<script lang="ts" setup>
 | 
			
		||||
import { useVModel } from '@vueuse/core';
 | 
			
		||||
import { ref } from 'vue';
 | 
			
		||||
 | 
			
		||||
export default defineComponent({
 | 
			
		||||
	name: 'DynamicForm',
 | 
			
		||||
 | 
			
		||||
	props: {
 | 
			
		||||
		formInfo: { type: Object },
 | 
			
		||||
		formData: { type: [Object, Boolean] },
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	setup(props: any, context) {
 | 
			
		||||
		const dynamicForm: any = ref();
 | 
			
		||||
		const state = reactive({
 | 
			
		||||
			form: {},
 | 
			
		||||
			submitDisabled: false,
 | 
			
		||||
		});
 | 
			
		||||
 | 
			
		||||
		watch(props.formData, (newValue, oldValue) => {
 | 
			
		||||
			if (props.formData) {
 | 
			
		||||
				state.form = { ...props.formData };
 | 
			
		||||
			}
 | 
			
		||||
		});
 | 
			
		||||
 | 
			
		||||
		const submit = () => {
 | 
			
		||||
			dynamicForm.value.validate((valid: boolean) => {
 | 
			
		||||
				if (valid) {
 | 
			
		||||
					// 提交的表单数据
 | 
			
		||||
					const subform = { ...state.form };
 | 
			
		||||
					const operation = state.form['id'] ? props.formInfo['updateApi'] : props.formInfo['createApi'];
 | 
			
		||||
					if (operation) {
 | 
			
		||||
						state.submitDisabled = true;
 | 
			
		||||
						operation.request(state.form).then(
 | 
			
		||||
							(res: any) => {
 | 
			
		||||
								ElMessage.success('保存成功');
 | 
			
		||||
								context.emit('submitSuccess', subform);
 | 
			
		||||
								state.submitDisabled = false;
 | 
			
		||||
								// this.cancel()
 | 
			
		||||
							},
 | 
			
		||||
							(e: any) => {
 | 
			
		||||
								state.submitDisabled = false;
 | 
			
		||||
							}
 | 
			
		||||
						);
 | 
			
		||||
					} else {
 | 
			
		||||
						ElMessage.error('表单未设置对应的提交权限');
 | 
			
		||||
					}
 | 
			
		||||
				} else {
 | 
			
		||||
					return false;
 | 
			
		||||
				}
 | 
			
		||||
			});
 | 
			
		||||
		};
 | 
			
		||||
 | 
			
		||||
		const reset = () => {
 | 
			
		||||
			context.emit('reset');
 | 
			
		||||
			resetFieldsAndData();
 | 
			
		||||
		};
 | 
			
		||||
 | 
			
		||||
		/**
 | 
			
		||||
		 * 重置表单以及表单数据
 | 
			
		||||
		 */
 | 
			
		||||
		const resetFieldsAndData = () => {
 | 
			
		||||
			// 对整个表单进行重置,将所有字段值重置为初始值并移除校验结果
 | 
			
		||||
			const df: any = dynamicForm;
 | 
			
		||||
			df.resetFields();
 | 
			
		||||
			// 重置表单数据
 | 
			
		||||
			state.form = {};
 | 
			
		||||
		};
 | 
			
		||||
 | 
			
		||||
		return {
 | 
			
		||||
			...toRefs(state),
 | 
			
		||||
			dynamicForm,
 | 
			
		||||
			submit,
 | 
			
		||||
			reset,
 | 
			
		||||
			resetFieldsAndData,
 | 
			
		||||
		};
 | 
			
		||||
	},
 | 
			
		||||
const props = defineProps({
 | 
			
		||||
    formItems: { type: Array },
 | 
			
		||||
    modelValue: { type: Object },
 | 
			
		||||
});
 | 
			
		||||
// @Component({
 | 
			
		||||
//   name: 'DynamicForm'
 | 
			
		||||
// })
 | 
			
		||||
// export default class DynamicForm extends Vue {
 | 
			
		||||
//   @Prop()
 | 
			
		||||
//   formInfo: object
 | 
			
		||||
//   @Prop()
 | 
			
		||||
//   formData: [object,boolean]|undefined
 | 
			
		||||
 | 
			
		||||
//   form = {}
 | 
			
		||||
//   submitDisabled = false
 | 
			
		||||
const emit = defineEmits(['update:modelValue']);
 | 
			
		||||
 | 
			
		||||
//   @Watch('formData', { deep: true })
 | 
			
		||||
//   onRoleChange() {
 | 
			
		||||
//     if (this.formData) {
 | 
			
		||||
//       this.form = { ...this.formData }
 | 
			
		||||
//     }
 | 
			
		||||
//   }
 | 
			
		||||
const formRef: any = ref();
 | 
			
		||||
 | 
			
		||||
//   submit() {
 | 
			
		||||
//     const dynamicForm: any = this.$refs['dynamicForm']
 | 
			
		||||
//     dynamicForm.validate((valid: boolean) => {
 | 
			
		||||
//       if (valid) {
 | 
			
		||||
//         // 提交的表单数据
 | 
			
		||||
//         const subform = { ...this.form }
 | 
			
		||||
//         const operation = this.form['id']
 | 
			
		||||
//           ? this.formInfo['updateApi']
 | 
			
		||||
//           : this.formInfo['createApi']
 | 
			
		||||
//         if (operation) {
 | 
			
		||||
//           this.submitDisabled = true
 | 
			
		||||
//           operation.request(this.form).then(
 | 
			
		||||
//             (res: any) => {
 | 
			
		||||
//               ElMessage.success('保存成功')
 | 
			
		||||
//               this.$emit('submitSuccess', subform)
 | 
			
		||||
//               this.submitDisabled = false
 | 
			
		||||
//               // this.cancel()
 | 
			
		||||
//             },
 | 
			
		||||
//             (e: any) => {
 | 
			
		||||
//               this.submitDisabled = false
 | 
			
		||||
//             }
 | 
			
		||||
//           )
 | 
			
		||||
//         } else {
 | 
			
		||||
//           ElMessage.error('表单未设置对应的提交权限')
 | 
			
		||||
//         }
 | 
			
		||||
//       } else {
 | 
			
		||||
//         return false
 | 
			
		||||
//       }
 | 
			
		||||
//     })
 | 
			
		||||
//   }
 | 
			
		||||
const formData: any = useVModel(props, 'modelValue', emit);
 | 
			
		||||
 | 
			
		||||
//   reset() {
 | 
			
		||||
//     this.$emit('reset')
 | 
			
		||||
//     this.resetFieldsAndData()
 | 
			
		||||
//   }
 | 
			
		||||
const validate = async (func: any) => {
 | 
			
		||||
    await formRef.value.validate(func);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
//   /**
 | 
			
		||||
//    * 重置表单以及表单数据
 | 
			
		||||
//    */
 | 
			
		||||
//   resetFieldsAndData() {
 | 
			
		||||
//     // 对整个表单进行重置,将所有字段值重置为初始值并移除校验结果
 | 
			
		||||
//     const df: any = this.$refs['dynamicForm']
 | 
			
		||||
//     df.resetFields()
 | 
			
		||||
//     // 重置表单数据
 | 
			
		||||
//     this.form = {}
 | 
			
		||||
//   }
 | 
			
		||||
const resetFields = () => {
 | 
			
		||||
    formRef.value.resetFields();
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
//   mounted() {
 | 
			
		||||
//     // 组件可能还没有初始化,第一次初始化的时候无法watch对象
 | 
			
		||||
//     this.form = { ...this.formData }
 | 
			
		||||
//   }
 | 
			
		||||
 | 
			
		||||
// }
 | 
			
		||||
defineExpose({
 | 
			
		||||
    validate,
 | 
			
		||||
    resetFields,
 | 
			
		||||
});
 | 
			
		||||
</script>
 | 
			
		||||
<style lang="scss"></style>
 | 
			
		||||
 
 | 
			
		||||
@@ -1,60 +1,55 @@
 | 
			
		||||
<template>
 | 
			
		||||
    <div class="form-dialog">
 | 
			
		||||
		<el-dialog :title="title" v-model="visible" :width="dialogWidth ? dialogWidth : '500px'">
 | 
			
		||||
			<dynamic-form ref="df" :form-info="formInfo" :form-data="formData" @submitSuccess="submitSuccess">
 | 
			
		||||
				<template #btns="props">
 | 
			
		||||
        <el-dialog @close="close" v-bind="$attrs" :title="title" v-model="dialogVisible" :width="width">
 | 
			
		||||
            <dynamic-form ref="df" :form-items="formItems" v-model="formData" />
 | 
			
		||||
 | 
			
		||||
            <template #footer>
 | 
			
		||||
                <span>
 | 
			
		||||
                    <slot name="btns">
 | 
			
		||||
						<el-button :disabled="props.submitDisabled" type="primary" @click="props.submit" size="small">保 存</el-button>
 | 
			
		||||
						<el-button :disabled="props.submitDisabled" @click="close()" size="small">取 消</el-button>
 | 
			
		||||
                        <el-button @click="dialogVisible = false">取 消</el-button>
 | 
			
		||||
                        <el-button type="primary" @click="confirm">确 定</el-button>
 | 
			
		||||
                    </slot>
 | 
			
		||||
                </span>
 | 
			
		||||
            </template>
 | 
			
		||||
			</dynamic-form>
 | 
			
		||||
        </el-dialog>
 | 
			
		||||
    </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script lang="ts">
 | 
			
		||||
import { watch, ref, toRefs, reactive, onMounted, defineComponent } from 'vue';
 | 
			
		||||
<script lang="ts" setup>
 | 
			
		||||
import { ref } from 'vue';
 | 
			
		||||
import DynamicForm from './DynamicForm.vue';
 | 
			
		||||
export default defineComponent({
 | 
			
		||||
	name: 'DynamicFormDialog',
 | 
			
		||||
	components: {
 | 
			
		||||
		DynamicForm,
 | 
			
		||||
	},
 | 
			
		||||
import { useVModel } from '@vueuse/core';
 | 
			
		||||
 | 
			
		||||
	props: {
 | 
			
		||||
		visible: { type: Boolean },
 | 
			
		||||
		dialogWidth: { type: String },
 | 
			
		||||
const emit = defineEmits(['update:visible', 'update:modelValue', 'close', 'confirm']);
 | 
			
		||||
 | 
			
		||||
const props = defineProps({
 | 
			
		||||
    title: { type: String },
 | 
			
		||||
		formInfo: { type: Object },
 | 
			
		||||
		formData: { type: [Object, Boolean] },
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	setup(props: any, context) {
 | 
			
		||||
		const df: any = ref();
 | 
			
		||||
 | 
			
		||||
		const close = () => {
 | 
			
		||||
			// 更新父组件visible prop对应的值为false
 | 
			
		||||
			context.emit('update:visible', false);
 | 
			
		||||
			// 关闭窗口,则将表单数据置为null
 | 
			
		||||
			context.emit('update:formData', null);
 | 
			
		||||
			context.emit('close');
 | 
			
		||||
			// 取消动态表单的校验以及form数据
 | 
			
		||||
			setTimeout(() => {
 | 
			
		||||
				df.resetFieldsAndData();
 | 
			
		||||
			}, 200);
 | 
			
		||||
		};
 | 
			
		||||
 | 
			
		||||
		const submitSuccess = (form: any) => {
 | 
			
		||||
			context.emit('submitSuccess', form);
 | 
			
		||||
			close();
 | 
			
		||||
		};
 | 
			
		||||
 | 
			
		||||
		return {
 | 
			
		||||
			df,
 | 
			
		||||
			close,
 | 
			
		||||
			submitSuccess,
 | 
			
		||||
		};
 | 
			
		||||
	},
 | 
			
		||||
    visible: { type: Boolean },
 | 
			
		||||
    width: { type: [String, Number], default: '500px' },
 | 
			
		||||
    formItems: { type: Array },
 | 
			
		||||
    modelValue: { type: Object },
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const df: any = ref();
 | 
			
		||||
 | 
			
		||||
const formData: any = useVModel(props, 'modelValue', emit);
 | 
			
		||||
const dialogVisible: any = useVModel(props, 'visible', emit);
 | 
			
		||||
 | 
			
		||||
const close = () => {
 | 
			
		||||
    emit('close');
 | 
			
		||||
    // 取消动态表单的校验
 | 
			
		||||
    setTimeout(() => {
 | 
			
		||||
        formData.value = {};
 | 
			
		||||
        df.value.resetFields();
 | 
			
		||||
    }, 200);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const confirm = () => {
 | 
			
		||||
    df.value.validate((valid: any) => {
 | 
			
		||||
        if (!valid) {
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
        emit('confirm', formData.value);
 | 
			
		||||
    });
 | 
			
		||||
};
 | 
			
		||||
</script>
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,60 @@
 | 
			
		||||
<template>
 | 
			
		||||
    <div class="dynamic-form-edit w100">
 | 
			
		||||
        <el-table :data="formItems" stripe class="w100" empty-text="暂无表单项">
 | 
			
		||||
            <el-table-column prop="name" label="model" min-width="100px">
 | 
			
		||||
                <template #header>
 | 
			
		||||
                    <el-button class="ml0" type="primary" circle size="small" icon="Plus" @click="addItem()"> </el-button>
 | 
			
		||||
                    <span class="ml10">model</span>
 | 
			
		||||
                </template>
 | 
			
		||||
                <template #default="scope">
 | 
			
		||||
                    <el-input v-model="scope.row['model']" placeholder="字段model" clearable> </el-input>
 | 
			
		||||
                </template>
 | 
			
		||||
            </el-table-column>
 | 
			
		||||
 | 
			
		||||
            <el-table-column prop="name" label="label" min-width="100px">
 | 
			
		||||
                <template #default="scope">
 | 
			
		||||
                    <el-input v-model="scope.row['name']" placeholder="字段title" clearable> </el-input>
 | 
			
		||||
                </template>
 | 
			
		||||
            </el-table-column>
 | 
			
		||||
 | 
			
		||||
            <el-table-column prop="placeholder" label="字段说明" min-width="140px">
 | 
			
		||||
                <template #default="scope">
 | 
			
		||||
                    <el-input v-model="scope.row['placeholder']" placeholder="字段说明" clearable> </el-input>
 | 
			
		||||
                </template>
 | 
			
		||||
            </el-table-column>
 | 
			
		||||
 | 
			
		||||
            <el-table-column prop="options" label="可选值" min-width="140px">
 | 
			
		||||
                <template #default="scope">
 | 
			
		||||
                    <el-input v-model="scope.row['options']" placeholder="可选值 ,分割" clearable> </el-input>
 | 
			
		||||
                </template>
 | 
			
		||||
            </el-table-column>
 | 
			
		||||
 | 
			
		||||
            <el-table-column label="操作" wdith="20px">
 | 
			
		||||
                <template #default="scope">
 | 
			
		||||
                    <el-button type="danger" @click="deleteItem(scope.$index)" icon="delete" plain></el-button>
 | 
			
		||||
                </template>
 | 
			
		||||
            </el-table-column>
 | 
			
		||||
        </el-table>
 | 
			
		||||
    </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script lang="ts" setup>
 | 
			
		||||
import { useVModel } from '@vueuse/core';
 | 
			
		||||
 | 
			
		||||
const props = defineProps({
 | 
			
		||||
    modelValue: { type: Array },
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const emit = defineEmits(['update:modelValue']);
 | 
			
		||||
 | 
			
		||||
const formItems: any = useVModel(props, 'modelValue', emit);
 | 
			
		||||
 | 
			
		||||
const addItem = () => {
 | 
			
		||||
    formItems.value.push({});
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const deleteItem = (index: any) => {
 | 
			
		||||
    formItems.value.splice(index, 1);
 | 
			
		||||
};
 | 
			
		||||
</script>
 | 
			
		||||
<style lang="scss"></style>
 | 
			
		||||
@@ -1,2 +1,3 @@
 | 
			
		||||
export { default as DynamicForm } from './DynamicForm.vue';
 | 
			
		||||
export { default as DynamicFormDialog } from './DynamicFormDialog.vue';
 | 
			
		||||
export { default as DynamicFormEdit } from './DynamicFormEdit.vue';
 | 
			
		||||
 
 | 
			
		||||
@@ -2,8 +2,8 @@
 | 
			
		||||
    <div class="layout-navbars-breadcrumb-user" :style="{ flex: layoutUserFlexNum }">
 | 
			
		||||
        <div class="layout-navbars-breadcrumb-user-icon">
 | 
			
		||||
            <el-switch
 | 
			
		||||
                @change="switchDark(state.isDark)"
 | 
			
		||||
                v-model="state.isDark"
 | 
			
		||||
                @change="switchDark()"
 | 
			
		||||
                v-model="isDark"
 | 
			
		||||
                active-action-icon="Moon"
 | 
			
		||||
                inactive-action-icon="Sunny"
 | 
			
		||||
                style="--el-switch-off-color: #c4c9c4; --el-switch-on-color: #2c2c2c"
 | 
			
		||||
@@ -75,7 +75,7 @@
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script setup lang="ts" name="layoutBreadcrumbUser">
 | 
			
		||||
import { ref, computed, reactive, onMounted } from 'vue';
 | 
			
		||||
import { ref, computed, reactive, onMounted, watch } from 'vue';
 | 
			
		||||
import { useRouter } from 'vue-router';
 | 
			
		||||
import { ElMessageBox, ElMessage } from 'element-plus';
 | 
			
		||||
import screenfull from 'screenfull';
 | 
			
		||||
@@ -83,17 +83,17 @@ import { resetRoute } from '@/router/index';
 | 
			
		||||
import { storeToRefs } from 'pinia';
 | 
			
		||||
import { useUserInfo } from '@/store/userInfo';
 | 
			
		||||
import { useThemeConfig } from '@/store/themeConfig';
 | 
			
		||||
import { clearSession, removeLocal } from '@/common/utils/storage';
 | 
			
		||||
import { clearSession } from '@/common/utils/storage';
 | 
			
		||||
import UserNews from '@/layout/navBars/breadcrumb/userNews.vue';
 | 
			
		||||
import SearchMenu from '@/layout/navBars/breadcrumb/search.vue';
 | 
			
		||||
import mittBus from '@/common/utils/mitt';
 | 
			
		||||
import openApi from '@/common/openApi';
 | 
			
		||||
import { saveThemeConfig, getThemeConfig } from '@/common/utils/storage';
 | 
			
		||||
import { useDark, usePreferredDark } from '@vueuse/core';
 | 
			
		||||
 | 
			
		||||
const router = useRouter();
 | 
			
		||||
const searchRef = ref();
 | 
			
		||||
const state = reactive({
 | 
			
		||||
    isDark: false,
 | 
			
		||||
    isScreenfull: false,
 | 
			
		||||
    isShowUserNewsPopover: false,
 | 
			
		||||
    disabledI18n: 'zh-cn',
 | 
			
		||||
@@ -165,8 +165,21 @@ const onHandleCommandClick = (path: string) => {
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const switchDark = (isDark: boolean) => {
 | 
			
		||||
    themeConfigStore.switchDark(isDark);
 | 
			
		||||
const isDark = useDark();
 | 
			
		||||
const preDark = usePreferredDark();
 | 
			
		||||
 | 
			
		||||
watch(preDark, (newValue) => {
 | 
			
		||||
    isDark.value = newValue;
 | 
			
		||||
    switchDark();
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const switchDark = () => {
 | 
			
		||||
    themeConfig.value.isDark = isDark.value;
 | 
			
		||||
    if (isDark.value) {
 | 
			
		||||
        themeConfig.value.editorTheme = 'vs-dark';
 | 
			
		||||
    } else {
 | 
			
		||||
        themeConfig.value.editorTheme = 'vs';
 | 
			
		||||
    }
 | 
			
		||||
    saveThemeConfig(themeConfig.value);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@@ -176,14 +189,14 @@ const onSearchClick = () => {
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// 组件大小改变
 | 
			
		||||
const onComponentSizeChange = (size: string) => {
 | 
			
		||||
    removeLocal('themeConfig');
 | 
			
		||||
    themeConfig.value.globalComponentSize = size;
 | 
			
		||||
    saveThemeConfig(themeConfig.value);
 | 
			
		||||
    // proxy.$ELEMENT.size = size;
 | 
			
		||||
    initComponentSize();
 | 
			
		||||
    window.location.reload();
 | 
			
		||||
};
 | 
			
		||||
// const onComponentSizeChange = (size: string) => {
 | 
			
		||||
//     removeLocal('themeConfig');
 | 
			
		||||
//     themeConfig.value.globalComponentSize = size;
 | 
			
		||||
//     saveThemeConfig(themeConfig.value);
 | 
			
		||||
//     // proxy.$ELEMENT.size = size;
 | 
			
		||||
//     initComponentSize();
 | 
			
		||||
//     window.location.reload();
 | 
			
		||||
// };
 | 
			
		||||
 | 
			
		||||
// 初始化全局组件大小
 | 
			
		||||
const initComponentSize = () => {
 | 
			
		||||
@@ -208,7 +221,7 @@ onMounted(() => {
 | 
			
		||||
    const themeConfig = getThemeConfig();
 | 
			
		||||
    if (themeConfig) {
 | 
			
		||||
        initComponentSize();
 | 
			
		||||
        state.isDark = themeConfig.isDark;
 | 
			
		||||
        isDark.value = themeConfig.isDark;
 | 
			
		||||
    }
 | 
			
		||||
});
 | 
			
		||||
</script>
 | 
			
		||||
 
 | 
			
		||||
@@ -146,18 +146,6 @@ export const useThemeConfig = defineStore('themeConfig', {
 | 
			
		||||
        setThemeConfig(data: ThemeConfigState) {
 | 
			
		||||
            this.themeConfig = data.themeConfig;
 | 
			
		||||
        },
 | 
			
		||||
        // 切换暗模式
 | 
			
		||||
        switchDark(isDark: boolean) {
 | 
			
		||||
            this.themeConfig.isDark = isDark;
 | 
			
		||||
            const body = document.documentElement as HTMLElement;
 | 
			
		||||
            if (isDark) {
 | 
			
		||||
                body.setAttribute('class', 'dark');
 | 
			
		||||
                this.themeConfig.editorTheme = 'vs-dark';
 | 
			
		||||
            } else {
 | 
			
		||||
                body.setAttribute('class', '');
 | 
			
		||||
                this.themeConfig.editorTheme = 'vs';
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        // 设置水印配置信息
 | 
			
		||||
        setWatermarkConfig(useWatermarkConfig: any) {
 | 
			
		||||
            this.themeConfig.watermarkText = [];
 | 
			
		||||
 
 | 
			
		||||
@@ -46,8 +46,8 @@ import { onMounted, reactive, ref, watch, toRefs, onUnmounted } from 'vue';
 | 
			
		||||
import { NodeType, TagTreeNode } from './tag';
 | 
			
		||||
import TagInfo from './TagInfo.vue';
 | 
			
		||||
import { Contextmenu } from '@/components/contextmenu';
 | 
			
		||||
import { useViewport } from '@/common/use';
 | 
			
		||||
import { tagApi } from '../tag/api';
 | 
			
		||||
import { useWindowSize } from '@vueuse/core';
 | 
			
		||||
 | 
			
		||||
const props = defineProps({
 | 
			
		||||
    resourceType: {
 | 
			
		||||
@@ -78,7 +78,7 @@ const emit = defineEmits(['nodeClick', 'currentContextmenuClick']);
 | 
			
		||||
const treeRef: any = ref(null);
 | 
			
		||||
const contextmenuRef = ref();
 | 
			
		||||
 | 
			
		||||
const { vh } = useViewport();
 | 
			
		||||
const { height: vh } = useWindowSize();
 | 
			
		||||
 | 
			
		||||
const state = reactive({
 | 
			
		||||
    height: 600 as any,
 | 
			
		||||
 
 | 
			
		||||
@@ -45,14 +45,20 @@
 | 
			
		||||
                <el-button v-auth="perms.delDb" :disabled="selectionData.length < 1" @click="deleteDb()" type="danger" icon="delete">删除</el-button>
 | 
			
		||||
            </template>
 | 
			
		||||
 | 
			
		||||
            <template #tagPath="{ data }">
 | 
			
		||||
                <resource-tag :resource-code="data.code" :resource-type="TagResourceTypeEnum.Db.value" />
 | 
			
		||||
            <template #type="{ data }">
 | 
			
		||||
                <el-tooltip :content="data.type" placement="top">
 | 
			
		||||
                    <SvgIcon :name="getDbDialect(data.type).getInfo().icon" :size="20" />
 | 
			
		||||
                </el-tooltip>
 | 
			
		||||
            </template>
 | 
			
		||||
 | 
			
		||||
            <template #host="{ data }">
 | 
			
		||||
                {{ `${data.host}:${data.port}` }}
 | 
			
		||||
            </template>
 | 
			
		||||
 | 
			
		||||
            <template #tagPath="{ data }">
 | 
			
		||||
                <resource-tag :resource-code="data.code" :resource-type="TagResourceTypeEnum.Db.value" />
 | 
			
		||||
            </template>
 | 
			
		||||
 | 
			
		||||
            <template #action="{ data }">
 | 
			
		||||
                <span v-if="actionBtns[perms.saveDb]">
 | 
			
		||||
                    <el-button type="primary" @click="editDb(data)" link>编辑</el-button>
 | 
			
		||||
@@ -173,6 +179,7 @@ import { DbType } from './dialect';
 | 
			
		||||
import { tagApi } from '../tag/api';
 | 
			
		||||
import { TagResourceTypeEnum } from '@/common/commonEnum';
 | 
			
		||||
import { useRoute } from 'vue-router';
 | 
			
		||||
import { getDbDialect } from './dialect/index';
 | 
			
		||||
 | 
			
		||||
const DbEdit = defineAsyncComponent(() => import('./DbEdit.vue'));
 | 
			
		||||
 | 
			
		||||
@@ -186,7 +193,7 @@ const queryConfig = [TableQuery.slot('tagPath', '标签', 'tagPathSelect'), Tabl
 | 
			
		||||
 | 
			
		||||
const columns = ref([
 | 
			
		||||
    TableColumn.new('instanceName', '实例名'),
 | 
			
		||||
    TableColumn.new('type', '类型'),
 | 
			
		||||
    TableColumn.new('type', '类型').isSlot().setAddWidth(-15).alignCenter(),
 | 
			
		||||
    TableColumn.new('host', 'ip:port').isSlot().setAddWidth(40),
 | 
			
		||||
    TableColumn.new('username', 'username'),
 | 
			
		||||
    TableColumn.new('name', '名称'),
 | 
			
		||||
 
 | 
			
		||||
@@ -20,6 +20,12 @@
 | 
			
		||||
                >
 | 
			
		||||
            </template>
 | 
			
		||||
 | 
			
		||||
            <template #type="{ data }">
 | 
			
		||||
                <el-tooltip :content="data.type" placement="top">
 | 
			
		||||
                    <SvgIcon :name="getDbDialect(data.type).getInfo().icon" :size="20" />
 | 
			
		||||
                </el-tooltip>
 | 
			
		||||
            </template>
 | 
			
		||||
 | 
			
		||||
            <template #action="{ data }">
 | 
			
		||||
                <el-button @click="showInfo(data)" link>详情</el-button>
 | 
			
		||||
                <el-button v-if="actionBtns[perms.saveInstance]" @click="editInstance(data)" type="primary" link>编辑</el-button>
 | 
			
		||||
@@ -66,6 +72,8 @@ import { dateFormat } from '@/common/utils/date';
 | 
			
		||||
import PageTable from '@/components/pagetable/PageTable.vue';
 | 
			
		||||
import { TableColumn, TableQuery } from '@/components/pagetable';
 | 
			
		||||
import { hasPerms } from '@/components/auth/auth';
 | 
			
		||||
import SvgIcon from '@/components/svgIcon/index.vue';
 | 
			
		||||
import { getDbDialect } from './dialect';
 | 
			
		||||
 | 
			
		||||
const InstanceEdit = defineAsyncComponent(() => import('./InstanceEdit.vue'));
 | 
			
		||||
 | 
			
		||||
@@ -78,7 +86,7 @@ const queryConfig = [TableQuery.text('name', '名称')];
 | 
			
		||||
 | 
			
		||||
const columns = ref([
 | 
			
		||||
    TableColumn.new('name', '名称'),
 | 
			
		||||
    TableColumn.new('type', '类型'),
 | 
			
		||||
    TableColumn.new('type', '类型').isSlot().setAddWidth(-15).alignCenter(),
 | 
			
		||||
    TableColumn.new('host', 'host:port').setFormatFunc((data: any) => `${data.host}:${data.port}`),
 | 
			
		||||
    TableColumn.new('username', '用户名'),
 | 
			
		||||
    TableColumn.new('params', '连接参数'),
 | 
			
		||||
 
 | 
			
		||||
@@ -21,7 +21,6 @@ export const dbApi = {
 | 
			
		||||
            param.sql = Base64.encode(param.sql);
 | 
			
		||||
        }
 | 
			
		||||
    }),
 | 
			
		||||
    sqlExecCancel: Api.newPost('/dbs/{id}/exec-sql/cancel/{execId}'),
 | 
			
		||||
    // 保存sql
 | 
			
		||||
    saveSql: Api.newPost('/dbs/{id}/sql'),
 | 
			
		||||
    // 获取保存的sql
 | 
			
		||||
 
 | 
			
		||||
@@ -33,7 +33,7 @@
 | 
			
		||||
                    </el-upload>
 | 
			
		||||
                </div>
 | 
			
		||||
 | 
			
		||||
                <div style="float: right" class="fl">
 | 
			
		||||
                <div class="fr">
 | 
			
		||||
                    <el-button @click="saveSql()" type="primary" icon="document-add" plain size="small">保存SQL</el-button>
 | 
			
		||||
                </div>
 | 
			
		||||
            </div>
 | 
			
		||||
@@ -128,7 +128,7 @@
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script lang="ts" setup>
 | 
			
		||||
import { h, nextTick, onMounted, reactive, toRefs, ref } from 'vue';
 | 
			
		||||
import { h, nextTick, onMounted, reactive, toRefs, ref, onBeforeUnmount } from 'vue';
 | 
			
		||||
import { getToken } from '@/common/utils/storage';
 | 
			
		||||
import { notBlank } from '@/common/assert';
 | 
			
		||||
import { format as sqlFormatter } from 'sql-formatter';
 | 
			
		||||
@@ -150,8 +150,8 @@ import { ElNotification } from 'element-plus';
 | 
			
		||||
import syssocket from '@/common/syssocket';
 | 
			
		||||
import SvgIcon from '@/components/svgIcon/index.vue';
 | 
			
		||||
import { getDbDialect } from '../../dialect';
 | 
			
		||||
import { randomUuid } from '@/common/utils/string';
 | 
			
		||||
import { Splitpanes, Pane } from 'splitpanes';
 | 
			
		||||
import Api from '@/common/Api';
 | 
			
		||||
 | 
			
		||||
const emits = defineEmits(['saveSqlSuccess']);
 | 
			
		||||
 | 
			
		||||
@@ -252,6 +252,12 @@ onMounted(async () => {
 | 
			
		||||
    await getNowDbInst().loadDbHints(props.dbName);
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
onBeforeUnmount(() => {
 | 
			
		||||
    state.execResTabs.forEach((x: ExecResTab) => {
 | 
			
		||||
        Api.removeAbortKey(x.loadingKey);
 | 
			
		||||
    });
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const onRemoveTab = (targetId: number) => {
 | 
			
		||||
    let activeTab = state.activeTab;
 | 
			
		||||
    const tabs = [...state.execResTabs];
 | 
			
		||||
@@ -347,7 +353,8 @@ const onRunSql = async (newTab = false) => {
 | 
			
		||||
        execRes.errorMsg = '';
 | 
			
		||||
        execRes.sql = '';
 | 
			
		||||
 | 
			
		||||
        const loadingKey = randomUuid();
 | 
			
		||||
        // 用于取消执行
 | 
			
		||||
        const loadingKey = Api.genAbortKey(execRes.loadingKey);
 | 
			
		||||
        execRes.loadingKey = loadingKey;
 | 
			
		||||
 | 
			
		||||
        const colAndData: any = await getNowDbInst().runSql(props.dbName, sql, execRemark, loadingKey);
 | 
			
		||||
@@ -397,7 +404,7 @@ const onRunSql = async (newTab = false) => {
 | 
			
		||||
const getSql = () => {
 | 
			
		||||
    let res = '' as string | undefined;
 | 
			
		||||
    // 编辑器还没初始化
 | 
			
		||||
    if (!monacoEditor?.getModel) {
 | 
			
		||||
    if (!monacoEditor?.getModel()) {
 | 
			
		||||
        return res;
 | 
			
		||||
    }
 | 
			
		||||
    // 选择选中的sql
 | 
			
		||||
@@ -405,6 +412,7 @@ const getSql = () => {
 | 
			
		||||
    if (selection) {
 | 
			
		||||
        res = monacoEditor.getModel()?.getValueInRange(selection);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // 整个编辑器的sql
 | 
			
		||||
    if (!res) {
 | 
			
		||||
        return monacoEditor.getModel()?.getValue();
 | 
			
		||||
 
 | 
			
		||||
@@ -133,7 +133,8 @@ import { ContextmenuItem, Contextmenu } from '@/components/contextmenu';
 | 
			
		||||
import SvgIcon from '@/components/svgIcon/index.vue';
 | 
			
		||||
import { exportCsv, exportFile } from '@/common/utils/export';
 | 
			
		||||
import { dateStrFormat } from '@/common/utils/date';
 | 
			
		||||
import { dbApi } from '../../api';
 | 
			
		||||
import Api from '@/common/Api';
 | 
			
		||||
import { useIntervalFn } from '@vueuse/core';
 | 
			
		||||
 | 
			
		||||
const emits = defineEmits(['dataDelete', 'sortChange', 'deleteData', 'selectionChange', 'changeUpdatedField']);
 | 
			
		||||
 | 
			
		||||
@@ -285,7 +286,9 @@ const selectionRowsMap: Map<number, any> = new Map();
 | 
			
		||||
const cellUpdateMap: Map<number, UpdatedRow> = new Map();
 | 
			
		||||
 | 
			
		||||
// 数据加载时间计时器
 | 
			
		||||
let execTimeInterval: any = null;
 | 
			
		||||
const { pause, resume } = useIntervalFn(() => {
 | 
			
		||||
    state.execTime += 0.1;
 | 
			
		||||
}, 100);
 | 
			
		||||
 | 
			
		||||
const state = reactive({
 | 
			
		||||
    dbId: 0, // 当前选中操作的数据库实例
 | 
			
		||||
@@ -429,22 +432,17 @@ const setTableColumns = (columns: any) => {
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const startLoading = () => {
 | 
			
		||||
    if (execTimeInterval) {
 | 
			
		||||
        endLoading();
 | 
			
		||||
    }
 | 
			
		||||
    execTimeInterval = setInterval(() => {
 | 
			
		||||
        state.execTime += 0.1; // 每秒递增执行时间
 | 
			
		||||
    }, 100);
 | 
			
		||||
    state.execTime = 0;
 | 
			
		||||
    resume();
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const endLoading = () => {
 | 
			
		||||
    state.execTime = 0;
 | 
			
		||||
    clearInterval(execTimeInterval);
 | 
			
		||||
    pause();
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const cancelLoading = async () => {
 | 
			
		||||
    if (props.loadingKey) {
 | 
			
		||||
        await dbApi.sqlExecCancel.request({ id: state.dbId, execId: props.loadingKey });
 | 
			
		||||
        Api.cancelReq(props.loadingKey);
 | 
			
		||||
        endLoading();
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
 
 | 
			
		||||
@@ -197,9 +197,17 @@ export class DbInst {
 | 
			
		||||
     * @param remark 执行备注
 | 
			
		||||
     */
 | 
			
		||||
    async runSql(dbName: string, sql: string, remark: string = '', key: string = '') {
 | 
			
		||||
        if (key) {
 | 
			
		||||
            return await dbApi.sqlExec.allowCancelReq(key, {
 | 
			
		||||
                id: this.id,
 | 
			
		||||
                db: dbName,
 | 
			
		||||
                sql: sql.trim(),
 | 
			
		||||
                remark,
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return await dbApi.sqlExec.request({
 | 
			
		||||
            id: this.id,
 | 
			
		||||
            execId: key,
 | 
			
		||||
            db: dbName,
 | 
			
		||||
            sql: sql.trim(),
 | 
			
		||||
            remark,
 | 
			
		||||
 
 | 
			
		||||
@@ -24,39 +24,17 @@
 | 
			
		||||
                    </el-select>
 | 
			
		||||
                </el-form-item>
 | 
			
		||||
 | 
			
		||||
                <el-row style="margin-left: 30px; margin-bottom: 5px">
 | 
			
		||||
                    <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>
 | 
			
		||||
                        <el-col :span="5">
 | 
			
		||||
                            <el-input v-model.trim="param.model" placeholder="内容中用{{.model}}替换"></el-input>
 | 
			
		||||
                        </el-col>
 | 
			
		||||
                        <span :span="1">
 | 
			
		||||
                            <el-divider direction="vertical" border-style="dashed" />
 | 
			
		||||
                        </span>
 | 
			
		||||
                        <el-col :span="4">
 | 
			
		||||
                            <el-input v-model.trim="param.name" placeholder="字段名"></el-input>
 | 
			
		||||
                        </el-col>
 | 
			
		||||
                        <span :span="1">
 | 
			
		||||
                            <el-divider direction="vertical" border-style="dashed" />
 | 
			
		||||
                        </span>
 | 
			
		||||
                        <el-col :span="4">
 | 
			
		||||
                            <el-input v-model="param.placeholder" placeholder="字段说明"></el-input>
 | 
			
		||||
                        </el-col>
 | 
			
		||||
                        <span :span="1">
 | 
			
		||||
                            <el-divider direction="vertical" border-style="dashed" />
 | 
			
		||||
                        </span>
 | 
			
		||||
                        <el-col :span="4">
 | 
			
		||||
                            <el-input v-model="param.options" placeholder="可选值 ,分割"></el-input>
 | 
			
		||||
                        </el-col>
 | 
			
		||||
                        <span :span="1">
 | 
			
		||||
                            <el-divider direction="vertical" border-style="dashed" />
 | 
			
		||||
                        </span>
 | 
			
		||||
                        <el-col :span="2">
 | 
			
		||||
                            <el-button @click="onDeleteParam(index)" type="danger">删除</el-button>
 | 
			
		||||
                        </el-col>
 | 
			
		||||
                    </el-row>
 | 
			
		||||
                <el-form-item class="w100">
 | 
			
		||||
                    <template #label>
 | 
			
		||||
                        <el-tooltip placement="top">
 | 
			
		||||
                            <template #content>
 | 
			
		||||
                                <span v-pre>1. 脚本内容中可使用{{.model}}作为占位符 </span>
 | 
			
		||||
                                <br />2. 执行脚本时可输入对应表单内容对占位符进行替换后执行
 | 
			
		||||
                            </template>
 | 
			
		||||
                            <span> 参数<SvgIcon name="question-filled" /> </span>
 | 
			
		||||
                        </el-tooltip>
 | 
			
		||||
                    </template>
 | 
			
		||||
                    <dynamic-form-edit v-model="params" />
 | 
			
		||||
                </el-form-item>
 | 
			
		||||
 | 
			
		||||
                <el-form-item required prop="script" class="100w">
 | 
			
		||||
@@ -82,6 +60,8 @@ import { ElMessage } from 'element-plus';
 | 
			
		||||
import { machineApi } from './api';
 | 
			
		||||
import { ScriptResultEnum } from './enums';
 | 
			
		||||
import MonacoEditor from '@/components/monaco/MonacoEditor.vue';
 | 
			
		||||
import { DynamicFormEdit } from '@/components/dynamic-form';
 | 
			
		||||
import SvgIcon from '@/components/svgIcon/index.vue';
 | 
			
		||||
 | 
			
		||||
const props = defineProps({
 | 
			
		||||
    visible: {
 | 
			
		||||
@@ -171,14 +151,6 @@ watch(props, (newValue: any) => {
 | 
			
		||||
    }
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const onAddParam = () => {
 | 
			
		||||
    state.params.push({ name: '', model: '', placeholder: '' });
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const onDeleteParam = (idx: number) => {
 | 
			
		||||
    state.params.splice(idx, 1);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const btnOk = () => {
 | 
			
		||||
    state.form.machineId = isCommon.value ? 9999999 : (machineId?.value as any);
 | 
			
		||||
    scriptForm.value.validate((valid: any) => {
 | 
			
		||||
 
 | 
			
		||||
@@ -45,35 +45,16 @@
 | 
			
		||||
            </page-table>
 | 
			
		||||
        </el-dialog>
 | 
			
		||||
 | 
			
		||||
        <el-dialog title="脚本参数" v-model="scriptParamsDialog.visible" width="400px">
 | 
			
		||||
            <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%"
 | 
			
		||||
        <dynamic-form-dialog
 | 
			
		||||
            title="脚本参数"
 | 
			
		||||
            width="400px"
 | 
			
		||||
            v-model:visible="scriptParamsDialog.visible"
 | 
			
		||||
            ref="paramsForm"
 | 
			
		||||
            :form-items="scriptParamsDialog.paramsFormItem"
 | 
			
		||||
            v-model="scriptParamsDialog.params"
 | 
			
		||||
            @confirm="hasParamsRun"
 | 
			
		||||
        >
 | 
			
		||||
                        <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()">确 定</el-button>
 | 
			
		||||
                </span>
 | 
			
		||||
            </template>
 | 
			
		||||
        </el-dialog>
 | 
			
		||||
        </dynamic-form-dialog>
 | 
			
		||||
 | 
			
		||||
        <el-dialog title="执行结果" v-model="resultDialog.visible" width="50%">
 | 
			
		||||
            <div style="white-space: pre-line; padding: 10px; color: #000000">
 | 
			
		||||
@@ -115,6 +96,7 @@ import { ScriptResultEnum, ScriptTypeEnum } from './enums';
 | 
			
		||||
import ScriptEdit from './ScriptEdit.vue';
 | 
			
		||||
import PageTable from '@/components/pagetable/PageTable.vue';
 | 
			
		||||
import { TableColumn, TableQuery } from '@/components/pagetable';
 | 
			
		||||
import { DynamicFormDialog } from '@/components/dynamic-form';
 | 
			
		||||
 | 
			
		||||
const props = defineProps({
 | 
			
		||||
    visible: { type: Boolean },
 | 
			
		||||
@@ -204,20 +186,9 @@ const runScript = async (script: any) => {
 | 
			
		||||
 | 
			
		||||
// 有参数的脚本执行函数
 | 
			
		||||
const hasParamsRun = async () => {
 | 
			
		||||
    // 如果脚本参数弹窗显示,则校验参数表单数据通过后执行
 | 
			
		||||
    if (state.scriptParamsDialog.visible) {
 | 
			
		||||
        paramsForm.value.validate((valid: any) => {
 | 
			
		||||
            if (valid) {
 | 
			
		||||
                run(state.scriptParamsDialog.script);
 | 
			
		||||
                state.scriptParamsDialog.params = {};
 | 
			
		||||
    await run(state.scriptParamsDialog.script);
 | 
			
		||||
    state.scriptParamsDialog.visible = false;
 | 
			
		||||
    state.scriptParamsDialog.script = null;
 | 
			
		||||
                paramsForm.value.resetFields();
 | 
			
		||||
            } else {
 | 
			
		||||
                return false;
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const run = async (script: any) => {
 | 
			
		||||
 
 | 
			
		||||
@@ -49,7 +49,7 @@
 | 
			
		||||
                                                    :before-upload="beforeUpload"
 | 
			
		||||
                                                    :on-success="uploadSuccess"
 | 
			
		||||
                                                    action=""
 | 
			
		||||
                                                    :http-request="getUploadFile"
 | 
			
		||||
                                                    :http-request="uploadFile"
 | 
			
		||||
                                                    :headers="{ token }"
 | 
			
		||||
                                                    :show-file-list="false"
 | 
			
		||||
                                                    name="file"
 | 
			
		||||
@@ -68,7 +68,7 @@
 | 
			
		||||
                                                        ref="folderUploadRef"
 | 
			
		||||
                                                        webkitdirectory
 | 
			
		||||
                                                        directory
 | 
			
		||||
                                                        @change="getFolder"
 | 
			
		||||
                                                        @change="uploadFolder"
 | 
			
		||||
                                                        style="display: none"
 | 
			
		||||
                                                    />
 | 
			
		||||
                                                </div>
 | 
			
		||||
@@ -173,7 +173,7 @@
 | 
			
		||||
 | 
			
		||||
                <el-table-column prop="size" label="大小" width="100" sortable>
 | 
			
		||||
                    <template #default="scope">
 | 
			
		||||
                        <span style="color: #67c23a; font-weight: bold" v-if="scope.row.type == '-'"> {{ formatFileSize(scope.row.size) }} </span>
 | 
			
		||||
                        <span style="color: #67c23a; font-weight: bold" v-if="scope.row.type == '-'"> {{ formatByteSize(scope.row.size) }} </span>
 | 
			
		||||
                        <span style="color: #67c23a; font-weight: bold" v-if="scope.row.type == 'd' && scope.row.dirSize"> {{ scope.row.dirSize }} </span>
 | 
			
		||||
                        <span style="color: #67c23a; font-weight: bold" v-if="scope.row.type == 'd' && !scope.row.dirSize">
 | 
			
		||||
                            <el-button @click="getDirSize(scope.row)" type="primary" link :loading="scope.row.loadingDirSize">计算</el-button>
 | 
			
		||||
@@ -280,6 +280,8 @@ import { isTrue } from '@/common/assert';
 | 
			
		||||
import MachineFileContent from './MachineFileContent.vue';
 | 
			
		||||
import { notBlank } from '@/common/assert';
 | 
			
		||||
import { getToken } from '@/common/utils/storage';
 | 
			
		||||
import { formatByteSize, convertToBytes } from '@/common/utils/format';
 | 
			
		||||
import { getMachineConfig } from '@/common/sysconfig';
 | 
			
		||||
 | 
			
		||||
const props = defineProps({
 | 
			
		||||
    machineId: { type: Number },
 | 
			
		||||
@@ -326,14 +328,15 @@ const state = reactive({
 | 
			
		||||
        type: folderType,
 | 
			
		||||
        data: null as any,
 | 
			
		||||
    },
 | 
			
		||||
    file: null as any,
 | 
			
		||||
    machineConfig: { uploadMaxFileSize: '1GB' },
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const { basePath, nowPath, loading, fileNameFilter, progressNum, uploadProgressShow, fileContent, createFileDialog } = toRefs(state);
 | 
			
		||||
 | 
			
		||||
onMounted(() => {
 | 
			
		||||
onMounted(async () => {
 | 
			
		||||
    state.basePath = props.path;
 | 
			
		||||
    setFiles(props.path);
 | 
			
		||||
    state.machineConfig = await getMachineConfig();
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const filterFiles = computed(() =>
 | 
			
		||||
@@ -616,16 +619,24 @@ function addFinderToList() {
 | 
			
		||||
    folderUploadRef.value.click();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function getFolder(e: any) {
 | 
			
		||||
function uploadFolder(e: any) {
 | 
			
		||||
    //e.target.files为文件夹里面的文件
 | 
			
		||||
    // 把文件夹数据放到formData里面,下面的files和paths字段根据接口来定
 | 
			
		||||
    var form = new FormData();
 | 
			
		||||
    form.append('basePath', state.nowPath);
 | 
			
		||||
 | 
			
		||||
    let totalFileSize = 0;
 | 
			
		||||
    for (let file of e.target.files) {
 | 
			
		||||
        totalFileSize += file.size;
 | 
			
		||||
        form.append('files', file);
 | 
			
		||||
        form.append('paths', file.webkitRelativePath);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    try {
 | 
			
		||||
        if (!checkUploadFileSize(totalFileSize)) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // 上传操作
 | 
			
		||||
        machineApi.uploadFile
 | 
			
		||||
            .request(form, {
 | 
			
		||||
@@ -660,7 +671,7 @@ const onUploadProgress = (progressEvent: any) => {
 | 
			
		||||
    state.progressNum = complete;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const getUploadFile = (content: any) => {
 | 
			
		||||
const uploadFile = (content: any) => {
 | 
			
		||||
    const params = new FormData();
 | 
			
		||||
    const path = state.nowPath;
 | 
			
		||||
    params.append('file', content.file);
 | 
			
		||||
@@ -695,7 +706,16 @@ const uploadSuccess = (res: any) => {
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const beforeUpload = (file: File) => {
 | 
			
		||||
    state.file = file;
 | 
			
		||||
    return checkUploadFileSize(file.size);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const checkUploadFileSize = (fileSize: number) => {
 | 
			
		||||
    const bytes = convertToBytes(state.machineConfig.uploadMaxFileSize);
 | 
			
		||||
    if (fileSize > bytes) {
 | 
			
		||||
        ElMessage.error(`上传的文件超过系统配置的[${state.machineConfig.uploadMaxFileSize}]`);
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
    return true;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const dontOperate = (data: any) => {
 | 
			
		||||
@@ -704,27 +724,6 @@ const dontOperate = (data: any) => {
 | 
			
		||||
    return ls.indexOf(path) != -1;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 格式化文件大小
 | 
			
		||||
 * @param {*} value
 | 
			
		||||
 */
 | 
			
		||||
const formatFileSize = (size: any) => {
 | 
			
		||||
    const value = Number(size);
 | 
			
		||||
    if (size && !isNaN(value)) {
 | 
			
		||||
        const units = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB', 'BB'];
 | 
			
		||||
        let index = 0;
 | 
			
		||||
        let k = value;
 | 
			
		||||
        if (value >= 1024) {
 | 
			
		||||
            while (k > 1024) {
 | 
			
		||||
                k = k / 1024;
 | 
			
		||||
                index++;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        return `${k.toFixed(2)}${units[index]}`;
 | 
			
		||||
    }
 | 
			
		||||
    return '-';
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
defineExpose({ showFileContent });
 | 
			
		||||
</script>
 | 
			
		||||
<style lang="scss">
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,6 @@
 | 
			
		||||
<template>
 | 
			
		||||
    <div>
 | 
			
		||||
        <el-dialog :title="title" v-model="dvisible" :show-close="false" :before-close="cancel" width="750px" :destroy-on-close="true">
 | 
			
		||||
        <el-dialog :title="title" v-model="dvisible" :show-close="false" :before-close="cancel" width="900px" :destroy-on-close="true">
 | 
			
		||||
            <el-form ref="configForm" :model="form" label-width="auto">
 | 
			
		||||
                <el-form-item prop="name" label="配置项" required>
 | 
			
		||||
                    <el-input v-model="form.name"></el-input>
 | 
			
		||||
@@ -22,43 +22,10 @@
 | 
			
		||||
                    </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-row>
 | 
			
		||||
                <el-form-item :key="param" v-for="(param, index) in params" prop="params" :label="`参数${index + 1}`">
 | 
			
		||||
                    <el-row>
 | 
			
		||||
                        <el-col :span="5">
 | 
			
		||||
                            <el-input v-model="param.model" placeholder="model"></el-input>
 | 
			
		||||
                        </el-col>
 | 
			
		||||
                        <span :span="1">
 | 
			
		||||
                            <el-divider direction="vertical" border-style="dashed" />
 | 
			
		||||
                        </span>
 | 
			
		||||
                        <el-col :span="4">
 | 
			
		||||
                            <el-input v-model="param.name" placeholder="字段名"></el-input>
 | 
			
		||||
                        </el-col>
 | 
			
		||||
                        <span :span="1">
 | 
			
		||||
                            <el-divider direction="vertical" border-style="dashed" />
 | 
			
		||||
                        </span>
 | 
			
		||||
                        <el-col :span="4">
 | 
			
		||||
                            <el-input v-model="param.placeholder" placeholder="字段说明"></el-input>
 | 
			
		||||
                        </el-col>
 | 
			
		||||
                        <span :span="1">
 | 
			
		||||
                            <el-divider direction="vertical" border-style="dashed" />
 | 
			
		||||
                        </span>
 | 
			
		||||
                        <el-col :span="4">
 | 
			
		||||
                            <el-input v-model="param.options" placeholder="可选值 ,分割"></el-input>
 | 
			
		||||
                        </el-col>
 | 
			
		||||
                        <span :span="1">
 | 
			
		||||
                            <el-divider direction="vertical" border-style="dashed" />
 | 
			
		||||
                        </span>
 | 
			
		||||
                        <el-col :span="2">
 | 
			
		||||
                            <el-button @click="onDeleteParam(index)" size="small" type="danger">删除</el-button>
 | 
			
		||||
                        </el-col>
 | 
			
		||||
                    </el-row>
 | 
			
		||||
                <el-form-item label="配置项" class="w100">
 | 
			
		||||
                    <dynamic-form-edit v-model="params" />
 | 
			
		||||
                </el-form-item>
 | 
			
		||||
                <!-- <el-form-item prop="value" label="配置值:" required>
 | 
			
		||||
                    <el-input v-model="form.value"></el-input>
 | 
			
		||||
                </el-form-item> -->
 | 
			
		||||
 | 
			
		||||
                <el-form-item label="备注">
 | 
			
		||||
                    <el-input v-model="form.remark" type="textarea" :rows="2"></el-input>
 | 
			
		||||
                </el-form-item>
 | 
			
		||||
@@ -76,6 +43,7 @@
 | 
			
		||||
<script lang="ts" setup>
 | 
			
		||||
import { ref, toRefs, reactive, watch } from 'vue';
 | 
			
		||||
import { configApi, accountApi } from '../api';
 | 
			
		||||
import { DynamicFormEdit } from '@/components/dynamic-form';
 | 
			
		||||
 | 
			
		||||
const props = defineProps({
 | 
			
		||||
    visible: {
 | 
			
		||||
@@ -139,14 +107,6 @@ watch(props, (newValue: any) => {
 | 
			
		||||
    }
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const onAddParam = () => {
 | 
			
		||||
    state.params.push({ name: '', model: '', placeholder: '' });
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const onDeleteParam = (idx: number) => {
 | 
			
		||||
    state.params.splice(idx, 1);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const cancel = () => {
 | 
			
		||||
    // 更新父组件visible prop对应的值为false
 | 
			
		||||
    emit('update:visible', false);
 | 
			
		||||
 
 | 
			
		||||
@@ -25,41 +25,20 @@
 | 
			
		||||
            </template>
 | 
			
		||||
        </page-table>
 | 
			
		||||
 | 
			
		||||
        <el-dialog :before-close="closeSetConfigDialog" title="配置项设置" v-model="paramsDialog.visible" width="600px">
 | 
			
		||||
            <el-form v-if="paramsDialog.paramsFormItem.length > 0" ref="paramsFormRef" :model="paramsDialog.params" label-width="auto">
 | 
			
		||||
                <el-form-item v-for="item in paramsDialog.paramsFormItem" :key="item.name" :prop="item.model" :label="item.name" required>
 | 
			
		||||
                    <el-input
 | 
			
		||||
                        v-if="!item.options && !item.type"
 | 
			
		||||
                        v-model="paramsDialog.params[item.model]"
 | 
			
		||||
                        :placeholder="item.placeholder"
 | 
			
		||||
                        autocomplete="off"
 | 
			
		||||
                        clearable
 | 
			
		||||
                    ></el-input>
 | 
			
		||||
                    <el-checkbox
 | 
			
		||||
                        v-else-if="item.type == 'checkbox'"
 | 
			
		||||
                        v-model="paramsDialog.params[item.model]"
 | 
			
		||||
                        autocomplete="off"
 | 
			
		||||
                        :label="item.placeholder"
 | 
			
		||||
                        clearable
 | 
			
		||||
        <el-dialog @close="closeSetConfigDialog" title="配置项设置" v-model="paramsDialog.visible" width="600px">
 | 
			
		||||
            <dynamic-form
 | 
			
		||||
                ref="paramsFormRef"
 | 
			
		||||
                v-if="paramsDialog.paramsFormItem.length > 0"
 | 
			
		||||
                :form-items="paramsDialog.paramsFormItem"
 | 
			
		||||
                v-model="paramsDialog.params"
 | 
			
		||||
            />
 | 
			
		||||
                    <el-select
 | 
			
		||||
                        v-else
 | 
			
		||||
                        v-model="paramsDialog.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>
 | 
			
		||||
 | 
			
		||||
            <el-form v-else ref="paramsFormRef" label-width="auto">
 | 
			
		||||
                <el-form-item label="配置值" required>
 | 
			
		||||
                    <el-input v-model="paramsDialog.params" :placeholder="paramsDialog.config.remark" autocomplete="off" clearable></el-input>
 | 
			
		||||
                </el-form-item>
 | 
			
		||||
            </el-form>
 | 
			
		||||
 | 
			
		||||
            <template #footer>
 | 
			
		||||
                <span class="dialog-footer">
 | 
			
		||||
                    <el-button @click="closeSetConfigDialog()">取 消</el-button>
 | 
			
		||||
@@ -80,6 +59,7 @@ import { ElMessage } from 'element-plus';
 | 
			
		||||
import PageTable from '@/components/pagetable/PageTable.vue';
 | 
			
		||||
import { TableColumn } from '@/components/pagetable';
 | 
			
		||||
import { hasPerms } from '@/components/auth/auth';
 | 
			
		||||
import { DynamicForm } from '@/components/dynamic-form';
 | 
			
		||||
 | 
			
		||||
const perms = {
 | 
			
		||||
    saveConfig: 'config:save',
 | 
			
		||||
@@ -163,7 +143,7 @@ const closeSetConfigDialog = () => {
 | 
			
		||||
const setConfig = async () => {
 | 
			
		||||
    let paramsValue = state.paramsDialog.params;
 | 
			
		||||
    if (state.paramsDialog.paramsFormItem.length > 0) {
 | 
			
		||||
        await paramsFormRef.value.validate(async (valid: boolean) => {
 | 
			
		||||
        await paramsFormRef.value.validate((valid: boolean) => {
 | 
			
		||||
            if (!valid) {
 | 
			
		||||
                paramsValue = null as any;
 | 
			
		||||
                return false;
 | 
			
		||||
 
 | 
			
		||||
@@ -345,6 +345,11 @@
 | 
			
		||||
  resolved "https://registry.npmmirror.com/@types/web-bluetooth/-/web-bluetooth-0.0.15.tgz"
 | 
			
		||||
  integrity sha512-w7hEHXnPMEZ+4nGKl/KDRVpxkwYxYExuHOYXyzIzCDzEZ9ZCGMAewulr9IqJu2LR4N37fcnb1XVeuZ09qgOxhA==
 | 
			
		||||
 | 
			
		||||
"@types/web-bluetooth@^0.0.20":
 | 
			
		||||
  version "0.0.20"
 | 
			
		||||
  resolved "https://registry.npmmirror.com/@types/web-bluetooth/-/web-bluetooth-0.0.20.tgz#f066abfcd1cbe66267cdbbf0de010d8a41b41597"
 | 
			
		||||
  integrity sha512-g9gZnnXVq7gM7v3tJCWV/qw7w+KeOlSHAhgF9RytFyifW6AF61hdT2ucrYhPq9hLs5JIryeupHV3qGk95dH9ow==
 | 
			
		||||
 | 
			
		||||
"@typescript-eslint/eslint-plugin@^6.7.4":
 | 
			
		||||
  version "6.7.4"
 | 
			
		||||
  resolved "https://registry.npmmirror.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.7.4.tgz#057338df21b6062c2f2fc5999fbea8af9973ac6d"
 | 
			
		||||
@@ -445,6 +450,16 @@
 | 
			
		||||
    estree-walker "^2.0.2"
 | 
			
		||||
    source-map-js "^1.0.2"
 | 
			
		||||
 | 
			
		||||
"@vue/compiler-core@3.3.11":
 | 
			
		||||
  version "3.3.11"
 | 
			
		||||
  resolved "https://registry.npmmirror.com/@vue/compiler-core/-/compiler-core-3.3.11.tgz#9fa26f8c81b9b34365f94ce1ed4d0e6e6f94a2ac"
 | 
			
		||||
  integrity sha512-h97/TGWBilnLuRaj58sxNrsUU66fwdRKLOLQ9N/5iNDfp+DZhYH9Obhe0bXxhedl8fjAgpRANpiZfbgWyruQ0w==
 | 
			
		||||
  dependencies:
 | 
			
		||||
    "@babel/parser" "^7.23.5"
 | 
			
		||||
    "@vue/shared" "3.3.11"
 | 
			
		||||
    estree-walker "^2.0.2"
 | 
			
		||||
    source-map-js "^1.0.2"
 | 
			
		||||
 | 
			
		||||
"@vue/compiler-dom@3.3.10":
 | 
			
		||||
  version "3.3.10"
 | 
			
		||||
  resolved "https://registry.npmmirror.com/@vue/compiler-dom/-/compiler-dom-3.3.10.tgz#183811252be6aff4ac923f783124bb1590301907"
 | 
			
		||||
@@ -453,7 +468,31 @@
 | 
			
		||||
    "@vue/compiler-core" "3.3.10"
 | 
			
		||||
    "@vue/shared" "3.3.10"
 | 
			
		||||
 | 
			
		||||
"@vue/compiler-sfc@3.3.10", "@vue/compiler-sfc@^3.3.10":
 | 
			
		||||
"@vue/compiler-dom@3.3.11":
 | 
			
		||||
  version "3.3.11"
 | 
			
		||||
  resolved "https://registry.npmmirror.com/@vue/compiler-dom/-/compiler-dom-3.3.11.tgz#36a76ea3a296d41bad133a6912cb0a847d969e4f"
 | 
			
		||||
  integrity sha512-zoAiUIqSKqAJ81WhfPXYmFGwDRuO+loqLxvXmfUdR5fOitPoUiIeFI9cTTyv9MU5O1+ZZglJVTusWzy+wfk5hw==
 | 
			
		||||
  dependencies:
 | 
			
		||||
    "@vue/compiler-core" "3.3.11"
 | 
			
		||||
    "@vue/shared" "3.3.11"
 | 
			
		||||
 | 
			
		||||
"@vue/compiler-sfc@3.3.11":
 | 
			
		||||
  version "3.3.11"
 | 
			
		||||
  resolved "https://registry.npmmirror.com/@vue/compiler-sfc/-/compiler-sfc-3.3.11.tgz#acfae240c875d067e0e2c9a4e2d910074408c73b"
 | 
			
		||||
  integrity sha512-U4iqPlHO0KQeK1mrsxCN0vZzw43/lL8POxgpzcJweopmqtoYy9nljJzWDIQS3EfjiYhfdtdk9Gtgz7MRXnz3GA==
 | 
			
		||||
  dependencies:
 | 
			
		||||
    "@babel/parser" "^7.23.5"
 | 
			
		||||
    "@vue/compiler-core" "3.3.11"
 | 
			
		||||
    "@vue/compiler-dom" "3.3.11"
 | 
			
		||||
    "@vue/compiler-ssr" "3.3.11"
 | 
			
		||||
    "@vue/reactivity-transform" "3.3.11"
 | 
			
		||||
    "@vue/shared" "3.3.11"
 | 
			
		||||
    estree-walker "^2.0.2"
 | 
			
		||||
    magic-string "^0.30.5"
 | 
			
		||||
    postcss "^8.4.32"
 | 
			
		||||
    source-map-js "^1.0.2"
 | 
			
		||||
 | 
			
		||||
"@vue/compiler-sfc@^3.3.10":
 | 
			
		||||
  version "3.3.10"
 | 
			
		||||
  resolved "https://registry.npmmirror.com/@vue/compiler-sfc/-/compiler-sfc-3.3.10.tgz#8eb97d42f276089ec58fd0565ef3a813bceeaa87"
 | 
			
		||||
  integrity sha512-xpcTe7Rw7QefOTRFFTlcfzozccvjM40dT45JtrE3onGm/jBLZ0JhpKu3jkV7rbDFLeeagR/5RlJ2Y9SvyS0lAg==
 | 
			
		||||
@@ -477,6 +516,14 @@
 | 
			
		||||
    "@vue/compiler-dom" "3.3.10"
 | 
			
		||||
    "@vue/shared" "3.3.10"
 | 
			
		||||
 | 
			
		||||
"@vue/compiler-ssr@3.3.11":
 | 
			
		||||
  version "3.3.11"
 | 
			
		||||
  resolved "https://registry.npmmirror.com/@vue/compiler-ssr/-/compiler-ssr-3.3.11.tgz#598942a73b64f2bd3f95908b104a7fbb55fc41a2"
 | 
			
		||||
  integrity sha512-Zd66ZwMvndxRTgVPdo+muV4Rv9n9DwQ4SSgWWKWkPFebHQfVYRrVjeygmmDmPewsHyznCNvJ2P2d6iOOhdv8Qg==
 | 
			
		||||
  dependencies:
 | 
			
		||||
    "@vue/compiler-dom" "3.3.11"
 | 
			
		||||
    "@vue/shared" "3.3.11"
 | 
			
		||||
 | 
			
		||||
"@vue/devtools-api@^6.5.0":
 | 
			
		||||
  version "6.5.0"
 | 
			
		||||
  resolved "https://registry.npmmirror.com/@vue/devtools-api/-/devtools-api-6.5.0.tgz#98b99425edee70b4c992692628fa1ea2c1e57d07"
 | 
			
		||||
@@ -493,43 +540,69 @@
 | 
			
		||||
    estree-walker "^2.0.2"
 | 
			
		||||
    magic-string "^0.30.5"
 | 
			
		||||
 | 
			
		||||
"@vue/reactivity@3.3.10":
 | 
			
		||||
  version "3.3.10"
 | 
			
		||||
  resolved "https://registry.npmmirror.com/@vue/reactivity/-/reactivity-3.3.10.tgz#78fe3da319276d9e6d0f072037532928c472a287"
 | 
			
		||||
  integrity sha512-H5Z7rOY/JLO+e5a6/FEXaQ1TMuOvY4LDVgT+/+HKubEAgs9qeeZ+NhADSeEtrNQeiKLDuzeKc8v0CUFpB6Pqgw==
 | 
			
		||||
"@vue/reactivity-transform@3.3.11":
 | 
			
		||||
  version "3.3.11"
 | 
			
		||||
  resolved "https://registry.npmmirror.com/@vue/reactivity-transform/-/reactivity-transform-3.3.11.tgz#2bd486f4eff60c8724309925618891e722fcfadc"
 | 
			
		||||
  integrity sha512-fPGjH0wqJo68A0wQ1k158utDq/cRyZNlFoxGwNScE28aUFOKFEnCBsvyD8jHn+0kd0UKVpuGuaZEQ6r9FJRqCg==
 | 
			
		||||
  dependencies:
 | 
			
		||||
    "@vue/shared" "3.3.10"
 | 
			
		||||
    "@babel/parser" "^7.23.5"
 | 
			
		||||
    "@vue/compiler-core" "3.3.11"
 | 
			
		||||
    "@vue/shared" "3.3.11"
 | 
			
		||||
    estree-walker "^2.0.2"
 | 
			
		||||
    magic-string "^0.30.5"
 | 
			
		||||
 | 
			
		||||
"@vue/runtime-core@3.3.10":
 | 
			
		||||
  version "3.3.10"
 | 
			
		||||
  resolved "https://registry.npmmirror.com/@vue/runtime-core/-/runtime-core-3.3.10.tgz#d7b78c5c0500b856cf9447ef81d4a1b1438fd5bb"
 | 
			
		||||
  integrity sha512-DZ0v31oTN4YHX9JEU5VW1LoIVgFovWgIVb30bWn9DG9a7oA415idcwsRNNajqTx8HQJyOaWfRKoyuP2P2TYIag==
 | 
			
		||||
"@vue/reactivity@3.3.11":
 | 
			
		||||
  version "3.3.11"
 | 
			
		||||
  resolved "https://registry.npmmirror.com/@vue/reactivity/-/reactivity-3.3.11.tgz#91f8e6c9ac60a595a5278c836b197628fd947a0d"
 | 
			
		||||
  integrity sha512-D5tcw091f0nuu+hXq5XANofD0OXnBmaRqMYl5B3fCR+mX+cXJIGNw/VNawBqkjLNWETrFW0i+xH9NvDbTPVh7g==
 | 
			
		||||
  dependencies:
 | 
			
		||||
    "@vue/reactivity" "3.3.10"
 | 
			
		||||
    "@vue/shared" "3.3.10"
 | 
			
		||||
    "@vue/shared" "3.3.11"
 | 
			
		||||
 | 
			
		||||
"@vue/runtime-dom@3.3.10":
 | 
			
		||||
  version "3.3.10"
 | 
			
		||||
  resolved "https://registry.npmmirror.com/@vue/runtime-dom/-/runtime-dom-3.3.10.tgz#130dfffb8fee8051671aaf80c5104d2020544950"
 | 
			
		||||
  integrity sha512-c/jKb3ny05KJcYk0j1m7Wbhrxq7mZYr06GhKykDMNRRR9S+/dGT8KpHuNQjv3/8U4JshfkAk6TpecPD3B21Ijw==
 | 
			
		||||
"@vue/runtime-core@3.3.11":
 | 
			
		||||
  version "3.3.11"
 | 
			
		||||
  resolved "https://registry.npmmirror.com/@vue/runtime-core/-/runtime-core-3.3.11.tgz#63defba57bc54c1dac68a95b56c2633b1419193d"
 | 
			
		||||
  integrity sha512-g9ztHGwEbS5RyWaOpXuyIVFTschclnwhqEbdy5AwGhYOgc7m/q3NFwr50MirZwTTzX55JY8pSkeib9BX04NIpw==
 | 
			
		||||
  dependencies:
 | 
			
		||||
    "@vue/runtime-core" "3.3.10"
 | 
			
		||||
    "@vue/shared" "3.3.10"
 | 
			
		||||
    "@vue/reactivity" "3.3.11"
 | 
			
		||||
    "@vue/shared" "3.3.11"
 | 
			
		||||
 | 
			
		||||
"@vue/runtime-dom@3.3.11":
 | 
			
		||||
  version "3.3.11"
 | 
			
		||||
  resolved "https://registry.npmmirror.com/@vue/runtime-dom/-/runtime-dom-3.3.11.tgz#1146d8d280b0fec4d2e18c4a4c8f8121d0cecc09"
 | 
			
		||||
  integrity sha512-OlhtV1PVpbgk+I2zl+Y5rQtDNcCDs12rsRg71XwaA2/Rbllw6mBLMi57VOn8G0AjOJ4Mdb4k56V37+g8ukShpQ==
 | 
			
		||||
  dependencies:
 | 
			
		||||
    "@vue/runtime-core" "3.3.11"
 | 
			
		||||
    "@vue/shared" "3.3.11"
 | 
			
		||||
    csstype "^3.1.2"
 | 
			
		||||
 | 
			
		||||
"@vue/server-renderer@3.3.10":
 | 
			
		||||
  version "3.3.10"
 | 
			
		||||
  resolved "https://registry.npmmirror.com/@vue/server-renderer/-/server-renderer-3.3.10.tgz#f23d151f0e5021ebdc730052d9934c9178486742"
 | 
			
		||||
  integrity sha512-0i6ww3sBV3SKlF3YTjSVqKQ74xialMbjVYGy7cOTi7Imd8ediE7t72SK3qnvhrTAhOvlQhq6Bk6nFPdXxe0sAg==
 | 
			
		||||
"@vue/server-renderer@3.3.11":
 | 
			
		||||
  version "3.3.11"
 | 
			
		||||
  resolved "https://registry.npmmirror.com/@vue/server-renderer/-/server-renderer-3.3.11.tgz#409aed8031a125791e2143552975ecd1958ad601"
 | 
			
		||||
  integrity sha512-AIWk0VwwxCAm4wqtJyxBylRTXSy1wCLOKbWxHaHiu14wjsNYtiRCSgVuqEPVuDpErOlRdNnuRgipQfXRLjLN5A==
 | 
			
		||||
  dependencies:
 | 
			
		||||
    "@vue/compiler-ssr" "3.3.10"
 | 
			
		||||
    "@vue/shared" "3.3.10"
 | 
			
		||||
    "@vue/compiler-ssr" "3.3.11"
 | 
			
		||||
    "@vue/shared" "3.3.11"
 | 
			
		||||
 | 
			
		||||
"@vue/shared@3.3.10":
 | 
			
		||||
  version "3.3.10"
 | 
			
		||||
  resolved "https://registry.npmmirror.com/@vue/shared/-/shared-3.3.10.tgz#1583a8d85a957d8b819078c465d2a11db7914b2f"
 | 
			
		||||
  integrity sha512-2y3Y2J1a3RhFa0WisHvACJR2ncvWiVHcP8t0Inxo+NKz+8RKO4ZV8eZgCxRgQoA6ITfV12L4E6POOL9HOU5nqw==
 | 
			
		||||
 | 
			
		||||
"@vue/shared@3.3.11":
 | 
			
		||||
  version "3.3.11"
 | 
			
		||||
  resolved "https://registry.npmmirror.com/@vue/shared/-/shared-3.3.11.tgz#f6a038e15237edefcc90dbfe7edb806dd355c7bd"
 | 
			
		||||
  integrity sha512-u2G8ZQ9IhMWTMXaWqZycnK4UthG1fA238CD+DP4Dm4WJi5hdUKKLg0RMRaRpDPNMdkTwIDkp7WtD0Rd9BH9fLw==
 | 
			
		||||
 | 
			
		||||
"@vueuse/core@^10.7.0":
 | 
			
		||||
  version "10.7.0"
 | 
			
		||||
  resolved "https://registry.npmmirror.com/@vueuse/core/-/core-10.7.0.tgz#34f2f02f179dc0dcffc2be70d6b1233e011404b9"
 | 
			
		||||
  integrity sha512-4EUDESCHtwu44ZWK3Gc/hZUVhVo/ysvdtwocB5vcauSV4B7NiGY5972WnsojB3vRNdxvAt7kzJWE2h9h7C9d5w==
 | 
			
		||||
  dependencies:
 | 
			
		||||
    "@types/web-bluetooth" "^0.0.20"
 | 
			
		||||
    "@vueuse/metadata" "10.7.0"
 | 
			
		||||
    "@vueuse/shared" "10.7.0"
 | 
			
		||||
    vue-demi ">=0.14.6"
 | 
			
		||||
 | 
			
		||||
"@vueuse/core@^9.1.0":
 | 
			
		||||
  version "9.2.0"
 | 
			
		||||
  resolved "https://registry.npmmirror.com/@vueuse/core/-/core-9.2.0.tgz"
 | 
			
		||||
@@ -540,11 +613,23 @@
 | 
			
		||||
    "@vueuse/shared" "9.2.0"
 | 
			
		||||
    vue-demi "*"
 | 
			
		||||
 | 
			
		||||
"@vueuse/metadata@10.7.0":
 | 
			
		||||
  version "10.7.0"
 | 
			
		||||
  resolved "https://registry.npmmirror.com/@vueuse/metadata/-/metadata-10.7.0.tgz#7b05e6cfd376aa9bb339a81e16a89c12f3e88c03"
 | 
			
		||||
  integrity sha512-GlaH7tKP2iBCZ3bHNZ6b0cl9g0CJK8lttkBNUX156gWvNYhTKEtbweWLm9rxCPIiwzYcr/5xML6T8ZUEt+DkvA==
 | 
			
		||||
 | 
			
		||||
"@vueuse/metadata@9.2.0":
 | 
			
		||||
  version "9.2.0"
 | 
			
		||||
  resolved "https://registry.npmmirror.com/@vueuse/metadata/-/metadata-9.2.0.tgz"
 | 
			
		||||
  integrity sha512-exN4KE6iquxDCdt72BgEhb3tlOpECtD61AUdXnUqBTIUCl70x1Ar/QXo3bYcvxmdMS2/peQyfeTzBjRTpvL5xw==
 | 
			
		||||
 | 
			
		||||
"@vueuse/shared@10.7.0":
 | 
			
		||||
  version "10.7.0"
 | 
			
		||||
  resolved "https://registry.npmmirror.com/@vueuse/shared/-/shared-10.7.0.tgz#21e425cc5ede421e0cda38ac59a0beee6da86b1b"
 | 
			
		||||
  integrity sha512-kc00uV6CiaTdc3i1CDC4a3lBxzaBE9AgYNtFN87B5OOscqeWElj/uza8qVDmk7/U8JbqoONLbtqiLJ5LGRuqlw==
 | 
			
		||||
  dependencies:
 | 
			
		||||
    vue-demi ">=0.14.6"
 | 
			
		||||
 | 
			
		||||
"@vueuse/shared@9.2.0":
 | 
			
		||||
  version "9.2.0"
 | 
			
		||||
  resolved "https://registry.npmmirror.com/@vueuse/shared/-/shared-9.2.0.tgz"
 | 
			
		||||
@@ -1419,10 +1504,10 @@ mitt@^3.0.1:
 | 
			
		||||
  resolved "https://registry.npmmirror.com/mitt/-/mitt-3.0.1.tgz#ea36cf0cc30403601ae074c8f77b7092cdab36d1"
 | 
			
		||||
  integrity sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==
 | 
			
		||||
 | 
			
		||||
monaco-editor@^0.44.0:
 | 
			
		||||
  version "0.44.0"
 | 
			
		||||
  resolved "https://registry.npmmirror.com/monaco-editor/-/monaco-editor-0.44.0.tgz#3c0fe3655923bbf7dd647057302070b5095b6c59"
 | 
			
		||||
  integrity sha512-5SmjNStN6bSuSE5WPT2ZV+iYn1/yI9sd4Igtk23ChvqB7kDk9lZbB9F5frsuvpB+2njdIeGGFf2G4gbE6rCC9Q==
 | 
			
		||||
monaco-editor@^0.45.0:
 | 
			
		||||
  version "0.45.0"
 | 
			
		||||
  resolved "https://registry.npmmirror.com/monaco-editor/-/monaco-editor-0.45.0.tgz#6939123a6254aea9fea2d647697f846306dd4448"
 | 
			
		||||
  integrity sha512-mjv1G1ZzfEE3k9HZN0dQ2olMdwIfaeAAjFiwNprLfYNRSz7ctv9XuCT7gPtBGrMUeV1/iZzYKj17Khu1hxoHOA==
 | 
			
		||||
 | 
			
		||||
monaco-sql-languages@^0.11.0:
 | 
			
		||||
  version "0.11.0"
 | 
			
		||||
@@ -1859,10 +1944,10 @@ uuid@^9.0.1:
 | 
			
		||||
  resolved "https://registry.npmmirror.com/uuid/-/uuid-9.0.1.tgz#e188d4c8853cc722220392c424cd637f32293f30"
 | 
			
		||||
  integrity sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==
 | 
			
		||||
 | 
			
		||||
vite@^5.0.5:
 | 
			
		||||
  version "5.0.5"
 | 
			
		||||
  resolved "https://registry.npmmirror.com/vite/-/vite-5.0.5.tgz#3eebe3698e3b32cea36350f58879258fec858a3c"
 | 
			
		||||
  integrity sha512-OekeWqR9Ls56f3zd4CaxzbbS11gqYkEiBtnWFFgYR2WV8oPJRRKq0mpskYy/XaoCL3L7VINDhqqOMNDiYdGvGg==
 | 
			
		||||
vite@^5.0.7:
 | 
			
		||||
  version "5.0.7"
 | 
			
		||||
  resolved "https://registry.npmmirror.com/vite/-/vite-5.0.7.tgz#ad081d735f6769f76b556818500bdafb72c3fe93"
 | 
			
		||||
  integrity sha512-B4T4rJCDPihrQo2B+h1MbeGL/k/GMAHzhQ8S0LjQ142s6/+l3hHTT095ORvsshj4QCkoWu3Xtmob5mazvakaOw==
 | 
			
		||||
  dependencies:
 | 
			
		||||
    esbuild "^0.19.3"
 | 
			
		||||
    postcss "^8.4.32"
 | 
			
		||||
@@ -1880,6 +1965,11 @@ vue-demi@>=0.14.5:
 | 
			
		||||
  resolved "https://registry.npmmirror.com/vue-demi/-/vue-demi-0.14.5.tgz#676d0463d1a1266d5ab5cba932e043d8f5f2fbd9"
 | 
			
		||||
  integrity sha512-o9NUVpl/YlsGJ7t+xuqJKx8EBGf1quRhCiT6D/J0pfwmk9zUwYkC7yrF4SZCe6fETvSM3UNL2edcbYrSyc4QHA==
 | 
			
		||||
 | 
			
		||||
vue-demi@>=0.14.6:
 | 
			
		||||
  version "0.14.6"
 | 
			
		||||
  resolved "https://registry.npmmirror.com/vue-demi/-/vue-demi-0.14.6.tgz#dc706582851dc1cdc17a0054f4fec2eb6df74c92"
 | 
			
		||||
  integrity sha512-8QA7wrYSHKaYgUxDA5ZC24w+eHm3sYCbp0EzcDwKqN3p6HqtTCGR/GVsPyZW92unff4UlcSh++lmqDWN3ZIq4w==
 | 
			
		||||
 | 
			
		||||
vue-eslint-parser@^9.3.1:
 | 
			
		||||
  version "9.3.1"
 | 
			
		||||
  resolved "https://registry.npmmirror.com/vue-eslint-parser/-/vue-eslint-parser-9.3.1.tgz#429955e041ae5371df5f9e37ebc29ba046496182"
 | 
			
		||||
@@ -1913,16 +2003,16 @@ vue-router@^4.2.5:
 | 
			
		||||
  dependencies:
 | 
			
		||||
    "@vue/devtools-api" "^6.5.0"
 | 
			
		||||
 | 
			
		||||
vue@^3.3.10:
 | 
			
		||||
  version "3.3.10"
 | 
			
		||||
  resolved "https://registry.npmmirror.com/vue/-/vue-3.3.10.tgz#6e19c1982ee655a14babe1610288b90005f02ab1"
 | 
			
		||||
  integrity sha512-zg6SIXZdTBwiqCw/1p+m04VyHjLfwtjwz8N57sPaBhEex31ND0RYECVOC1YrRwMRmxFf5T1dabl6SGUbMKKuVw==
 | 
			
		||||
vue@^3.3.11:
 | 
			
		||||
  version "3.3.11"
 | 
			
		||||
  resolved "https://registry.npmmirror.com/vue/-/vue-3.3.11.tgz#898d97025f73cdb5fc4e3ae3fd07a54615232140"
 | 
			
		||||
  integrity sha512-d4oBctG92CRO1cQfVBZp6WJAs0n8AK4Xf5fNjQCBeKCvMI1efGQ5E3Alt1slFJS9fZuPcFoiAiqFvQlv1X7t/w==
 | 
			
		||||
  dependencies:
 | 
			
		||||
    "@vue/compiler-dom" "3.3.10"
 | 
			
		||||
    "@vue/compiler-sfc" "3.3.10"
 | 
			
		||||
    "@vue/runtime-dom" "3.3.10"
 | 
			
		||||
    "@vue/server-renderer" "3.3.10"
 | 
			
		||||
    "@vue/shared" "3.3.10"
 | 
			
		||||
    "@vue/compiler-dom" "3.3.11"
 | 
			
		||||
    "@vue/compiler-sfc" "3.3.11"
 | 
			
		||||
    "@vue/runtime-dom" "3.3.11"
 | 
			
		||||
    "@vue/server-renderer" "3.3.11"
 | 
			
		||||
    "@vue/shared" "3.3.11"
 | 
			
		||||
 | 
			
		||||
which@^2.0.1:
 | 
			
		||||
  version "2.0.2"
 | 
			
		||||
 
 | 
			
		||||
@@ -25,7 +25,6 @@ import (
 | 
			
		||||
	"mayfly-go/pkg/ws"
 | 
			
		||||
	"strconv"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"sync"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/gin-gonic/gin"
 | 
			
		||||
@@ -84,9 +83,6 @@ func (d *Db) DeleteDb(rc *req.Ctx) {
 | 
			
		||||
 | 
			
		||||
/**  数据库操作相关、执行sql等   ***/
 | 
			
		||||
 | 
			
		||||
// 取消执行sql函数map; key -> execId ; value -> cancelFunc
 | 
			
		||||
var cancelExecSqlMap = sync.Map{}
 | 
			
		||||
 | 
			
		||||
func (d *Db) ExecSql(rc *req.Ctx) {
 | 
			
		||||
	g := rc.GinCtx
 | 
			
		||||
	form := &form.DbSqlExecForm{}
 | 
			
		||||
@@ -112,14 +108,9 @@ func (d *Db) ExecSql(rc *req.Ctx) {
 | 
			
		||||
		DbConn: dbConn,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ctx := rc.MetaCtx
 | 
			
		||||
	// 如果存在执行id,则保存取消函数,用于后续可能的取消操作
 | 
			
		||||
	if form.ExecId != "" {
 | 
			
		||||
		cancelCtx, cancel := context.WithTimeout(rc.MetaCtx, 55*time.Second)
 | 
			
		||||
		ctx = cancelCtx
 | 
			
		||||
		cancelExecSqlMap.Store(form.ExecId, cancel)
 | 
			
		||||
		defer cancelExecSqlMap.Delete(form.ExecId)
 | 
			
		||||
	}
 | 
			
		||||
	// 比前端超时时间稍微快一点,可以提示到前端
 | 
			
		||||
	ctx, cancel := context.WithTimeout(rc.MetaCtx, 58*time.Second)
 | 
			
		||||
	defer cancel()
 | 
			
		||||
 | 
			
		||||
	sqls, err := sqlparser.SplitStatementToPieces(sql, sqlparser.WithDialect(dbConn.Info.Type.Dialect()))
 | 
			
		||||
	biz.ErrIsNil(err, "SQL解析错误,请检查您的执行SQL")
 | 
			
		||||
@@ -150,14 +141,6 @@ func (d *Db) ExecSql(rc *req.Ctx) {
 | 
			
		||||
	rc.ResData = colAndRes
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (d *Db) CancelExecSql(rc *req.Ctx) {
 | 
			
		||||
	execId := ginx.PathParam(rc.GinCtx, "execId")
 | 
			
		||||
	if cancelFunc, ok := cancelExecSqlMap.LoadAndDelete(execId); ok {
 | 
			
		||||
		rc.ReqParam = execId
 | 
			
		||||
		cancelFunc.(context.CancelFunc)()
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// progressCategory sql文件执行进度消息类型
 | 
			
		||||
const progressCategory = "execSqlFileProgress"
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -35,8 +35,6 @@ func InitDbRouter(router *gin.RouterGroup) {
 | 
			
		||||
 | 
			
		||||
		req.NewPost(":dbId/exec-sql", d.ExecSql).Log(req.NewLog("db-执行Sql")),
 | 
			
		||||
 | 
			
		||||
		req.NewPost(":dbId/exec-sql/cancel/:execId", d.CancelExecSql).Log(req.NewLog("db-取消执行Sql")),
 | 
			
		||||
 | 
			
		||||
		req.NewPost(":dbId/exec-sql-file", d.ExecSqlFile).Log(req.NewLogSave("db-执行Sql文件")),
 | 
			
		||||
 | 
			
		||||
		req.NewGet(":dbId/dump", d.DumpSql).Log(req.NewLogSave("db-导出sql文件")).NoRes(),
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user