Compare commits
	
		
			17 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					a4d3a4627a | ||
| 
						 | 
					77ae6e3bab | ||
| 
						 | 
					e0f1f40ba0 | ||
| 
						 | 
					d300f604f1 | ||
| 
						 | 
					2c2c0ff40b | ||
| 
						 | 
					b4ddbbd38f | ||
| 
						 | 
					7544288451 | ||
| 
						 | 
					41443dccc0 | ||
| 
						 | 
					22e218fc5f | ||
| 
						 | 
					4da0d1abaa | ||
| 
						 | 
					6563b53436 | ||
| 
						 | 
					fac71a4794 | ||
| 
						 | 
					92dff6fdc3 | ||
| 
						 | 
					a1eca3d691 | ||
| 
						 | 
					6681dc1057 | ||
| 
						 | 
					829a68feaa | ||
| 
						 | 
					72677e270d | 
@@ -13,7 +13,7 @@
 | 
			
		||||
    <img src="https://img.shields.io/docker/pulls/mayflygo/mayfly-go.svg?label=docker%20pulls&color=fac858" alt="docker pulls"/>
 | 
			
		||||
  </a>
 | 
			
		||||
  <a href="https://github.com/golang/go" target="_blank">
 | 
			
		||||
    <img src="https://img.shields.io/badge/Golang-1.20%2B-yellow.svg" alt="golang"/>
 | 
			
		||||
    <img src="https://img.shields.io/badge/Golang-1.21%2B-yellow.svg" alt="golang"/>
 | 
			
		||||
  </a>
 | 
			
		||||
  <a href="https://cn.vuejs.org" target="_blank">
 | 
			
		||||
    <img src="https://img.shields.io/badge/Vue-3.x-green.svg" alt="vue">
 | 
			
		||||
@@ -22,7 +22,7 @@
 | 
			
		||||
 | 
			
		||||
### 介绍
 | 
			
		||||
 | 
			
		||||
web 版 **linux(终端[终端回放] 文件 脚本 进程)、数据库(mysql postgres)、redis(单机 哨兵 集群)、mongo 统一管理操作平台**
 | 
			
		||||
web 版 **linux(终端[终端回放] 文件 脚本 进程 计划任务)、数据库(mysql postgres)、redis(单机 哨兵 集群)、mongo 统一管理操作平台**
 | 
			
		||||
 | 
			
		||||
### 开发语言与主要框架
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -74,12 +74,13 @@ function build() {
 | 
			
		||||
    # fi
 | 
			
		||||
 | 
			
		||||
    if [ "${copyDocScript}" == "1" ] ; then
 | 
			
		||||
        echo_green "拷贝脚本等资源文件[config.yml.example、mayfly-go.sql、readme.txt、startup.sh、shutdown.sh]"
 | 
			
		||||
        echo_green "拷贝脚本等资源文件[config.yml.example、mayfly-go.sql、mayfly-go.sqlite、readme.txt、startup.sh、shutdown.sh]"
 | 
			
		||||
        cp ${server_folder}/config.yml.example ${toFolder}
 | 
			
		||||
        cp ${server_folder}/mayfly-go.sql ${toFolder}
 | 
			
		||||
        cp ${server_folder}/readme.txt ${toFolder}
 | 
			
		||||
        cp ${server_folder}/startup.sh ${toFolder}
 | 
			
		||||
        cp ${server_folder}/shutdown.sh ${toFolder}
 | 
			
		||||
        cp ${server_folder}/resources/script/startup.sh ${toFolder}
 | 
			
		||||
        cp ${server_folder}/resources/script/shutdown.sh ${toFolder}
 | 
			
		||||
        cp ${server_folder}/resources/script/sql/mayfly-go.sql ${toFolder}
 | 
			
		||||
        cp ${server_folder}/resources/data/mayfly-go.sqlite ${toFolder}
 | 
			
		||||
    fi
 | 
			
		||||
 | 
			
		||||
    echo_yellow ">>>>>>>>>>>>>>>>>>>${os}-${arch}打包构建完成<<<<<<<<<<<<<<<<<<<<\n"
 | 
			
		||||
 
 | 
			
		||||
@@ -2,4 +2,8 @@
 | 
			
		||||
ENV = 'development'
 | 
			
		||||
 | 
			
		||||
# 本地环境接口地址
 | 
			
		||||
VITE_API_URL = '/api'
 | 
			
		||||
VITE_API_URL = '/api'
 | 
			
		||||
 | 
			
		||||
# 路由模式
 | 
			
		||||
# Optional: hash | history
 | 
			
		||||
VITE_ROUTER_MODE = hash
 | 
			
		||||
@@ -2,4 +2,8 @@
 | 
			
		||||
ENV = 'production'
 | 
			
		||||
 | 
			
		||||
# 线上环境接口地址
 | 
			
		||||
VITE_API_URL = '/api'
 | 
			
		||||
VITE_API_URL = '/api'
 | 
			
		||||
 | 
			
		||||
# 路由模式
 | 
			
		||||
# Optional: hash | history
 | 
			
		||||
VITE_ROUTER_MODE = hash
 | 
			
		||||
@@ -1,76 +1,76 @@
 | 
			
		||||
module.exports = {
 | 
			
		||||
	root: true,
 | 
			
		||||
	env: {
 | 
			
		||||
		browser: true,
 | 
			
		||||
		es2021: true,
 | 
			
		||||
		node: true,
 | 
			
		||||
	},
 | 
			
		||||
	parser: 'vue-eslint-parser',
 | 
			
		||||
	parserOptions: {
 | 
			
		||||
		ecmaVersion: 12,
 | 
			
		||||
		parser: '@typescript-eslint/parser',
 | 
			
		||||
		sourceType: 'module',
 | 
			
		||||
	},
 | 
			
		||||
	extends: ['plugin:vue/vue3-essential', 'plugin:vue/essential', 'eslint:recommended'],
 | 
			
		||||
	plugins: ['vue', '@typescript-eslint'],
 | 
			
		||||
	overrides: [
 | 
			
		||||
		{
 | 
			
		||||
			files: ['*.ts', '*.tsx', '*.vue'],
 | 
			
		||||
			rules: {
 | 
			
		||||
				'no-undef': 'off',
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
	],
 | 
			
		||||
	rules: {
 | 
			
		||||
		// http://eslint.cn/docs/rules/
 | 
			
		||||
		// https://eslint.vuejs.org/rules/
 | 
			
		||||
		// https://typescript-eslint.io/rules/no-unused-vars/
 | 
			
		||||
		'@typescript-eslint/ban-ts-ignore': 'off',
 | 
			
		||||
		'@typescript-eslint/explicit-function-return-type': 'off',
 | 
			
		||||
		'@typescript-eslint/no-explicit-any': 'off',
 | 
			
		||||
		'@typescript-eslint/no-var-requires': 'off',
 | 
			
		||||
		'@typescript-eslint/no-empty-function': 'off',
 | 
			
		||||
		'@typescript-eslint/no-use-before-define': 'off',
 | 
			
		||||
		'@typescript-eslint/ban-ts-comment': 'off',
 | 
			
		||||
		'@typescript-eslint/ban-types': 'off',
 | 
			
		||||
		'@typescript-eslint/no-non-null-assertion': 'off',
 | 
			
		||||
		'@typescript-eslint/explicit-module-boundary-types': 'off',
 | 
			
		||||
		'@typescript-eslint/no-redeclare': 'error',
 | 
			
		||||
		'@typescript-eslint/no-non-null-asserted-optional-chain': 'off',
 | 
			
		||||
		'@typescript-eslint/no-unused-vars': [2],
 | 
			
		||||
		'vue/custom-event-name-casing': 'off',
 | 
			
		||||
		'vue/attributes-order': 'off',
 | 
			
		||||
		'vue/one-component-per-file': 'off',
 | 
			
		||||
		'vue/html-closing-bracket-newline': 'off',
 | 
			
		||||
		'vue/max-attributes-per-line': 'off',
 | 
			
		||||
		'vue/multiline-html-element-content-newline': 'off',
 | 
			
		||||
		'vue/singleline-html-element-content-newline': 'off',
 | 
			
		||||
		'vue/attribute-hyphenation': 'off',
 | 
			
		||||
		'vue/html-self-closing': 'off',
 | 
			
		||||
		'vue/no-multiple-template-root': 'off',
 | 
			
		||||
		'vue/require-default-prop': 'off',
 | 
			
		||||
		'vue/no-v-model-argument': 'off',
 | 
			
		||||
		'vue/no-arrow-functions-in-watch': 'off',
 | 
			
		||||
		'vue/no-template-key': 'off',
 | 
			
		||||
		'vue/no-v-html': 'off',
 | 
			
		||||
		'vue/comment-directive': 'off',
 | 
			
		||||
		'vue/no-parsing-error': 'off',
 | 
			
		||||
		'vue/no-deprecated-v-on-native-modifier': 'off',
 | 
			
		||||
		'vue/multi-word-component-names': 'off',
 | 
			
		||||
		'no-useless-escape': 'off',
 | 
			
		||||
		'no-sparse-arrays': 'off',
 | 
			
		||||
		'no-prototype-builtins': 'off',
 | 
			
		||||
		'no-constant-condition': 'off',
 | 
			
		||||
		'no-use-before-define': 'off',
 | 
			
		||||
		'no-restricted-globals': 'off',
 | 
			
		||||
		'no-restricted-syntax': 'off',
 | 
			
		||||
		'generator-star-spacing': 'off',
 | 
			
		||||
		'no-unreachable': 'off',
 | 
			
		||||
		'no-multiple-template-root': 'off',
 | 
			
		||||
		'no-unused-vars': 'error',
 | 
			
		||||
		'no-v-model-argument': 'off',
 | 
			
		||||
		'no-case-declarations': 'off',
 | 
			
		||||
		'no-console': 'error',
 | 
			
		||||
		'no-redeclare': 'off',
 | 
			
		||||
	},
 | 
			
		||||
};
 | 
			
		||||
    root: true,
 | 
			
		||||
    env: {
 | 
			
		||||
        browser: true,
 | 
			
		||||
        es2021: true,
 | 
			
		||||
        node: true,
 | 
			
		||||
    },
 | 
			
		||||
    parser: 'vue-eslint-parser',
 | 
			
		||||
    parserOptions: {
 | 
			
		||||
        ecmaVersion: 12,
 | 
			
		||||
        parser: '@typescript-eslint/parser',
 | 
			
		||||
        sourceType: 'module',
 | 
			
		||||
    },
 | 
			
		||||
    extends: ['plugin:vue/vue3-essential', 'plugin:vue/essential', 'eslint:recommended'],
 | 
			
		||||
    plugins: ['vue', '@typescript-eslint'],
 | 
			
		||||
    overrides: [
 | 
			
		||||
        {
 | 
			
		||||
            files: ['*.ts', '*.tsx', '*.vue'],
 | 
			
		||||
            rules: {
 | 
			
		||||
                'no-undef': 'off',
 | 
			
		||||
            },
 | 
			
		||||
        },
 | 
			
		||||
    ],
 | 
			
		||||
    rules: {
 | 
			
		||||
        // http://eslint.cn/docs/rules/
 | 
			
		||||
        // https://eslint.vuejs.org/rules/
 | 
			
		||||
        // https://typescript-eslint.io/rules/no-unused-vars/
 | 
			
		||||
        '@typescript-eslint/ban-ts-ignore': 'off',
 | 
			
		||||
        '@typescript-eslint/explicit-function-return-type': 'off',
 | 
			
		||||
        '@typescript-eslint/no-explicit-any': 'off',
 | 
			
		||||
        '@typescript-eslint/no-var-requires': 'off',
 | 
			
		||||
        '@typescript-eslint/no-empty-function': 'off',
 | 
			
		||||
        '@typescript-eslint/no-use-before-define': 'off',
 | 
			
		||||
        '@typescript-eslint/ban-ts-comment': 'off',
 | 
			
		||||
        '@typescript-eslint/ban-types': 'off',
 | 
			
		||||
        '@typescript-eslint/no-non-null-assertion': 'off',
 | 
			
		||||
        '@typescript-eslint/explicit-module-boundary-types': 'off',
 | 
			
		||||
        '@typescript-eslint/no-redeclare': 'error',
 | 
			
		||||
        '@typescript-eslint/no-non-null-asserted-optional-chain': 'off',
 | 
			
		||||
        '@typescript-eslint/no-unused-vars': [2],
 | 
			
		||||
        'vue/custom-event-name-casing': 'off',
 | 
			
		||||
        'vue/attributes-order': 'off',
 | 
			
		||||
        'vue/one-component-per-file': 'off',
 | 
			
		||||
        'vue/html-closing-bracket-newline': 'off',
 | 
			
		||||
        'vue/max-attributes-per-line': 'off',
 | 
			
		||||
        'vue/multiline-html-element-content-newline': 'off',
 | 
			
		||||
        'vue/singleline-html-element-content-newline': 'off',
 | 
			
		||||
        'vue/attribute-hyphenation': 'off',
 | 
			
		||||
        'vue/html-self-closing': 'off',
 | 
			
		||||
        'vue/no-multiple-template-root': 'off',
 | 
			
		||||
        'vue/require-default-prop': 'off',
 | 
			
		||||
        'vue/no-v-model-argument': 'off',
 | 
			
		||||
        'vue/no-arrow-functions-in-watch': 'off',
 | 
			
		||||
        'vue/no-template-key': 'off',
 | 
			
		||||
        'vue/no-v-html': 'off',
 | 
			
		||||
        'vue/comment-directive': 'off',
 | 
			
		||||
        'vue/no-parsing-error': 'off',
 | 
			
		||||
        'vue/no-deprecated-v-on-native-modifier': 'off',
 | 
			
		||||
        'vue/multi-word-component-names': 'off',
 | 
			
		||||
        'no-useless-escape': 'off',
 | 
			
		||||
        'no-sparse-arrays': 'off',
 | 
			
		||||
        'no-prototype-builtins': 'off',
 | 
			
		||||
        'no-constant-condition': 'off',
 | 
			
		||||
        'no-use-before-define': 'off',
 | 
			
		||||
        'no-restricted-globals': 'off',
 | 
			
		||||
        'no-restricted-syntax': 'off',
 | 
			
		||||
        'generator-star-spacing': 'off',
 | 
			
		||||
        'no-unreachable': 'off',
 | 
			
		||||
        'no-multiple-template-root': 'off',
 | 
			
		||||
        'no-unused-vars': 'error',
 | 
			
		||||
        'no-v-model-argument': 'off',
 | 
			
		||||
        'no-case-declarations': 'off',
 | 
			
		||||
        // 'no-console': 'error',
 | 
			
		||||
        'no-redeclare': 'off',
 | 
			
		||||
    },
 | 
			
		||||
};
 | 
			
		||||
 
 | 
			
		||||
@@ -10,27 +10,27 @@
 | 
			
		||||
  },
 | 
			
		||||
  "dependencies": {
 | 
			
		||||
    "@element-plus/icons-vue": "^2.1.0",
 | 
			
		||||
    "asciinema-player": "^3.5.0",
 | 
			
		||||
    "asciinema-player": "^3.6.2",
 | 
			
		||||
    "axios": "^1.5.0",
 | 
			
		||||
    "countup.js": "^2.7.0",
 | 
			
		||||
    "cropperjs": "^1.5.11",
 | 
			
		||||
    "echarts": "^5.4.0",
 | 
			
		||||
    "element-plus": "^2.3.12",
 | 
			
		||||
    "element-plus": "^2.4.0",
 | 
			
		||||
    "jsencrypt": "^3.3.1",
 | 
			
		||||
    "lodash": "^4.17.21",
 | 
			
		||||
    "mitt": "^3.0.1",
 | 
			
		||||
    "monaco-editor": "^0.43.0",
 | 
			
		||||
    "monaco-editor": "^0.44.0",
 | 
			
		||||
    "monaco-sql-languages": "^0.11.0",
 | 
			
		||||
    "monaco-themes": "^0.4.4",
 | 
			
		||||
    "nprogress": "^0.2.0",
 | 
			
		||||
    "pinia": "^2.1.6",
 | 
			
		||||
    "pinia": "^2.1.7",
 | 
			
		||||
    "qrcode.vue": "^3.4.0",
 | 
			
		||||
    "screenfull": "^6.0.2",
 | 
			
		||||
    "sortablejs": "^1.13.0",
 | 
			
		||||
    "sortablejs": "^1.15.0",
 | 
			
		||||
    "sql-formatter": "^12.1.2",
 | 
			
		||||
    "vue": "^3.3.4",
 | 
			
		||||
    "vue-clipboard3": "^1.0.1",
 | 
			
		||||
    "vue-router": "^4.2.4",
 | 
			
		||||
    "vue-router": "^4.2.5",
 | 
			
		||||
    "xterm": "^5.3.0",
 | 
			
		||||
    "xterm-addon-fit": "^0.8.0",
 | 
			
		||||
    "xterm-addon-search": "^0.13.0",
 | 
			
		||||
@@ -40,20 +40,19 @@
 | 
			
		||||
    "@types/lodash": "^4.14.178",
 | 
			
		||||
    "@types/node": "^15.6.0",
 | 
			
		||||
    "@types/nprogress": "^0.2.0",
 | 
			
		||||
    "@types/sortablejs": "^1.10.6",
 | 
			
		||||
    "@typescript-eslint/eslint-plugin": "^4.23.0",
 | 
			
		||||
    "@typescript-eslint/parser": "^4.23.0",
 | 
			
		||||
    "@vitejs/plugin-vue": "^4.0.0",
 | 
			
		||||
    "@vue/compiler-sfc": "^3.0.11",
 | 
			
		||||
    "dotenv": "^10.0.0",
 | 
			
		||||
    "@types/sortablejs": "^1.15.3",
 | 
			
		||||
    "@typescript-eslint/eslint-plugin": "^6.7.4",
 | 
			
		||||
    "@typescript-eslint/parser": "^6.7.4",
 | 
			
		||||
    "@vitejs/plugin-vue": "^4.4.0",
 | 
			
		||||
    "@vue/compiler-sfc": "^3.3.4",
 | 
			
		||||
    "dotenv": "^16.3.1",
 | 
			
		||||
    "eslint": "^8.35.0",
 | 
			
		||||
    "eslint-plugin-vue": "^8.2.0",
 | 
			
		||||
    "prettier": "^2.3.0",
 | 
			
		||||
    "sass": "^1.62.0",
 | 
			
		||||
    "sass-loader": "^13.2.0",
 | 
			
		||||
    "sass": "^1.69.0",
 | 
			
		||||
    "typescript": "^5.0.2",
 | 
			
		||||
    "vite": "^4.4.9",
 | 
			
		||||
    "vue-eslint-parser": "^9.1.1"
 | 
			
		||||
    "vite": "^4.4.11",
 | 
			
		||||
    "vue-eslint-parser": "^9.3.1"
 | 
			
		||||
  },
 | 
			
		||||
  "browserslist": [
 | 
			
		||||
    "> 1%",
 | 
			
		||||
 
 | 
			
		||||
@@ -1,20 +1,33 @@
 | 
			
		||||
<template>
 | 
			
		||||
    <router-view v-show="themeConfig.lockScreenTime !== 0" />
 | 
			
		||||
    <LockScreen v-if="themeConfig.isLockScreen" />
 | 
			
		||||
    <Setings ref="setingsRef" v-show="themeConfig.lockScreenTime !== 0" />
 | 
			
		||||
    <div class="h100">
 | 
			
		||||
        <el-watermark
 | 
			
		||||
            :zIndex="10000000"
 | 
			
		||||
            :width="210"
 | 
			
		||||
            v-if="themeConfig.isWatermark"
 | 
			
		||||
            :font="{ color: 'rgba(180, 180, 180, 0.5)' }"
 | 
			
		||||
            :content="themeConfig.watermarkText"
 | 
			
		||||
            class="h100"
 | 
			
		||||
        >
 | 
			
		||||
            <router-view v-show="themeConfig.lockScreenTime !== 0" />
 | 
			
		||||
        </el-watermark>
 | 
			
		||||
        <router-view v-if="!themeConfig.isWatermark" v-show="themeConfig.lockScreenTime !== 0" />
 | 
			
		||||
 | 
			
		||||
        <LockScreen v-if="themeConfig.isLockScreen" />
 | 
			
		||||
        <Setings ref="setingsRef" v-show="themeConfig.lockScreenTime !== 0" />
 | 
			
		||||
    </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script setup lang="ts" name="app">
 | 
			
		||||
import { ref, onBeforeMount, onMounted, onUnmounted, nextTick, watch } from 'vue';
 | 
			
		||||
import { ref, onMounted, onUnmounted, nextTick, watch } from 'vue';
 | 
			
		||||
import { useRoute } from 'vue-router';
 | 
			
		||||
// import { useTagsViewRoutes } from '@/store/tagsViewRoutes';
 | 
			
		||||
import { storeToRefs } from 'pinia';
 | 
			
		||||
import { useThemeConfig } from '@/store/themeConfig';
 | 
			
		||||
import { getLocal } from '@/common/utils/storage';
 | 
			
		||||
import LockScreen from '@/views/layout/lockScreen/index.vue';
 | 
			
		||||
import Setings from '@/views/layout/navBars/breadcrumb/setings.vue';
 | 
			
		||||
import Watermark from '@/common/utils/wartermark';
 | 
			
		||||
import LockScreen from '@/layout/lockScreen/index.vue';
 | 
			
		||||
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';
 | 
			
		||||
 | 
			
		||||
const setingsRef = ref();
 | 
			
		||||
const route = useRoute();
 | 
			
		||||
@@ -27,14 +40,6 @@ const openSetingsDrawer = () => {
 | 
			
		||||
    setingsRef.value.openDrawer();
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// 设置初始化,防止刷新时恢复默认
 | 
			
		||||
onBeforeMount(() => {
 | 
			
		||||
    // 设置批量第三方 icon 图标
 | 
			
		||||
    // setIntroduction.cssCdn();
 | 
			
		||||
    // // 设置批量第三方 js
 | 
			
		||||
    // setIntroduction.jsCdn();
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
// 页面加载时
 | 
			
		||||
onMounted(() => {
 | 
			
		||||
    nextTick(() => {
 | 
			
		||||
@@ -42,16 +47,61 @@ onMounted(() => {
 | 
			
		||||
        mittBus.on('openSetingsDrawer', () => {
 | 
			
		||||
            openSetingsDrawer();
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        // 获取缓存中的布局配置
 | 
			
		||||
        if (getLocal('themeConfig')) {
 | 
			
		||||
            themeConfigStores.setThemeConfig({ themeConfig: getLocal('themeConfig') });
 | 
			
		||||
        const tc = getThemeConfig();
 | 
			
		||||
        if (tc) {
 | 
			
		||||
            themeConfigStores.setThemeConfig({ themeConfig: tc });
 | 
			
		||||
            document.documentElement.style.cssText = getLocal('themeConfigStyle');
 | 
			
		||||
 | 
			
		||||
            themeConfigStores.switchDark(tc.isDark);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // 是否开启水印
 | 
			
		||||
        useWatermark().then((res) => {
 | 
			
		||||
            themeConfigStores.setWatermarkConfig(res);
 | 
			
		||||
        });
 | 
			
		||||
    });
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
// 监听 themeConfig isWartermark配置文件的变化
 | 
			
		||||
watch(
 | 
			
		||||
    () => themeConfig.value.isWatermark,
 | 
			
		||||
    (val) => {
 | 
			
		||||
        if (val) {
 | 
			
		||||
            setTimeout(() => {
 | 
			
		||||
                setWatermarkContent();
 | 
			
		||||
                refreshWatermarkTime();
 | 
			
		||||
            }, 500);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
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);
 | 
			
		||||
        }
 | 
			
		||||
    }, 10000);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// 页面销毁时,关闭监听布局配置
 | 
			
		||||
onUnmounted(() => {
 | 
			
		||||
    clearInterval(refreshWatermarkTimeInterval);
 | 
			
		||||
    mittBus.off('openSetingsDrawer', () => {});
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
@@ -60,8 +110,6 @@ watch(
 | 
			
		||||
    () => route.path,
 | 
			
		||||
    () => {
 | 
			
		||||
        nextTick(() => {
 | 
			
		||||
            // 路由变化更新水印
 | 
			
		||||
            Watermark.use();
 | 
			
		||||
            document.title = `${route.meta.title} - ${themeConfig.value.globalTitle}` || themeConfig.value.globalTitle;
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
| 
		 Before Width: | Height: | Size: 39 KiB  | 
							
								
								
									
										1
									
								
								mayfly_go_web/src/assets/image/401.svg
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 14 KiB  | 
| 
		 Before Width: | Height: | Size: 36 KiB  | 
							
								
								
									
										1
									
								
								mayfly_go_web/src/assets/image/404.svg
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 13 KiB  | 
| 
		 Before Width: | Height: | Size: 458 KiB  | 
							
								
								
									
										1
									
								
								mayfly_go_web/src/assets/image/login-bg-main.svg
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 13 KiB  | 
							
								
								
									
										19
									
								
								mayfly_go_web/src/assets/image/login-bg-split.svg
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 8.4 KiB  | 
@@ -11,7 +11,7 @@ const config = {
 | 
			
		||||
    baseWsUrl: `${(window as any).globalConfig.BaseWsUrl || `${location.protocol == 'https:' ? 'wss:' : 'ws:'}//${getBaseApiUrl()}`}/api`,
 | 
			
		||||
 | 
			
		||||
    // 系统版本
 | 
			
		||||
    version: 'v1.5.2',
 | 
			
		||||
    version: 'v1.5.3',
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export default config;
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,7 @@
 | 
			
		||||
import router from '../router';
 | 
			
		||||
import Axios from 'axios';
 | 
			
		||||
import config from './config';
 | 
			
		||||
import { getSession } from './utils/storage';
 | 
			
		||||
import { getToken } from './utils/storage';
 | 
			
		||||
import { templateResolve } from './utils/string';
 | 
			
		||||
import { ElMessage } from 'element-plus';
 | 
			
		||||
 | 
			
		||||
@@ -29,7 +29,7 @@ enum ResultEnum {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const baseUrl: string = config.baseApiUrl;
 | 
			
		||||
const baseWsUrl: string = config.baseWsUrl;
 | 
			
		||||
// const baseWsUrl: string = config.baseWsUrl;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 通知错误消息
 | 
			
		||||
@@ -43,14 +43,14 @@ function notifyErrorMsg(msg: string) {
 | 
			
		||||
// create an axios instance
 | 
			
		||||
const service = Axios.create({
 | 
			
		||||
    baseURL: baseUrl, // url = base url + request url
 | 
			
		||||
    timeout: 20000, // request timeout
 | 
			
		||||
    timeout: 60000, // request timeout
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
// request interceptor
 | 
			
		||||
service.interceptors.request.use(
 | 
			
		||||
    (config: any) => {
 | 
			
		||||
        // do something before request is sent
 | 
			
		||||
        const token = getSession('token');
 | 
			
		||||
        const token = getToken();
 | 
			
		||||
        if (token) {
 | 
			
		||||
            // 设置token
 | 
			
		||||
            config.headers['Authorization'] = token;
 | 
			
		||||
@@ -143,8 +143,8 @@ function request(method: string, url: string, params: any = null, headers: any =
 | 
			
		||||
        .request(query)
 | 
			
		||||
        .then((res) => res)
 | 
			
		||||
        .catch((e) => {
 | 
			
		||||
            // 如果返回的code不为成功,则会返回对应的错误msg,则直接统一通知即可
 | 
			
		||||
            if (e.msg) {
 | 
			
		||||
            // 如果返回的code不为成功,则会返回对应的错误msg,则直接统一通知即可。忽略登录超时或没有权限的提示(直接跳转至401页面)
 | 
			
		||||
            if (e.msg && e?.code != ResultEnum.NO_PERMISSION) {
 | 
			
		||||
                notifyErrorMsg(e.msg);
 | 
			
		||||
            }
 | 
			
		||||
            return Promise.reject(e);
 | 
			
		||||
@@ -176,7 +176,7 @@ function del(url: string, params: any = null, headers: any = null, options: any
 | 
			
		||||
 | 
			
		||||
function getApiUrl(url: string) {
 | 
			
		||||
    // 只是返回api地址而不做请求,用在上传组件之类的
 | 
			
		||||
    return baseUrl + url + '?token=' + getSession('token');
 | 
			
		||||
    return baseUrl + url + '?token=' + getToken();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default {
 | 
			
		||||
 
 | 
			
		||||
@@ -1,43 +1,77 @@
 | 
			
		||||
import Config from './config';
 | 
			
		||||
import { ElNotification } from 'element-plus';
 | 
			
		||||
import { ElNotification, NotificationHandle } from 'element-plus';
 | 
			
		||||
import SocketBuilder from './SocketBuilder';
 | 
			
		||||
import { getSession } from '@/common/utils/storage';
 | 
			
		||||
import { getToken } from '@/common/utils/storage';
 | 
			
		||||
import { createVNode, reactive } from "vue";
 | 
			
		||||
import { buildProgressProps } from "@/components/progress-notify/progress-notify";
 | 
			
		||||
import ProgressNotify from '/src/components/progress-notify/progress-notify.vue';
 | 
			
		||||
 | 
			
		||||
export default {
 | 
			
		||||
    /**
 | 
			
		||||
     * 全局系统消息websocket
 | 
			
		||||
     */
 | 
			
		||||
    sysMsgSocket() {
 | 
			
		||||
        const token = getSession('token');
 | 
			
		||||
        const token = getToken();
 | 
			
		||||
        if (!token) {
 | 
			
		||||
            return null;
 | 
			
		||||
        }
 | 
			
		||||
        const messageTypes = {
 | 
			
		||||
            0: "error",
 | 
			
		||||
            1: "success",
 | 
			
		||||
            2: "info",
 | 
			
		||||
        }
 | 
			
		||||
        const notifyMap: Map<Number, any> = new Map()
 | 
			
		||||
 | 
			
		||||
        return SocketBuilder.builder(`${Config.baseWsUrl}/sysmsg?token=${token}`)
 | 
			
		||||
            .message((event: { data: string }) => {
 | 
			
		||||
                const message = JSON.parse(event.data);
 | 
			
		||||
                let mtype: string;
 | 
			
		||||
                switch (message.type) {
 | 
			
		||||
                    case 0:
 | 
			
		||||
                        mtype = 'error';
 | 
			
		||||
                        break;
 | 
			
		||||
                    case 2:
 | 
			
		||||
                        mtype = 'info';
 | 
			
		||||
                        break;
 | 
			
		||||
                    case 1:
 | 
			
		||||
                        mtype = 'success';
 | 
			
		||||
                const type = messageTypes[message.type]
 | 
			
		||||
                switch (message.category) {
 | 
			
		||||
                    case "execSqlFileProgress":
 | 
			
		||||
                        const content = JSON.parse(message.msg)
 | 
			
		||||
                        const id = content.id
 | 
			
		||||
                        let progress = notifyMap.get(id)
 | 
			
		||||
                        if (content.terminated) {
 | 
			
		||||
                            if (progress != undefined) {
 | 
			
		||||
                                progress.notification?.close()
 | 
			
		||||
                                notifyMap.delete(id)
 | 
			
		||||
                                progress = undefined
 | 
			
		||||
                            }
 | 
			
		||||
                            return
 | 
			
		||||
                        }
 | 
			
		||||
                        if (progress == undefined) {
 | 
			
		||||
                            progress = {
 | 
			
		||||
                                props: reactive(buildProgressProps()),
 | 
			
		||||
                                notification: undefined,
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
                        progress.props.progress.sqlFileName = content.sqlFileName
 | 
			
		||||
                        progress.props.progress.executedStatements = content.executedStatements
 | 
			
		||||
                        if (!notifyMap.has(id)) {
 | 
			
		||||
                            const vNodeMessage = createVNode(
 | 
			
		||||
                                ProgressNotify,
 | 
			
		||||
                                progress.props,
 | 
			
		||||
                                null,
 | 
			
		||||
                            )
 | 
			
		||||
                            progress.notification = ElNotification({
 | 
			
		||||
                                duration: 0,
 | 
			
		||||
                                title: message.title,
 | 
			
		||||
                                message: vNodeMessage,
 | 
			
		||||
                                type: type,
 | 
			
		||||
                                showClose: false,
 | 
			
		||||
                            });
 | 
			
		||||
                            notifyMap.set(id, progress)
 | 
			
		||||
                        }
 | 
			
		||||
                        break;
 | 
			
		||||
                    default:
 | 
			
		||||
                        mtype = 'info';
 | 
			
		||||
                        ElNotification({
 | 
			
		||||
                            duration: 0,
 | 
			
		||||
                            title: message.title,
 | 
			
		||||
                            message: message.msg,
 | 
			
		||||
                            type: type,
 | 
			
		||||
                        });
 | 
			
		||||
                        break;
 | 
			
		||||
                }
 | 
			
		||||
                if (mtype == undefined) {
 | 
			
		||||
                    return;
 | 
			
		||||
                }
 | 
			
		||||
                ElNotification({
 | 
			
		||||
                    duration: 0,
 | 
			
		||||
                    title: message.title,
 | 
			
		||||
                    message: message.msg,
 | 
			
		||||
                    type: mtype as any,
 | 
			
		||||
                });
 | 
			
		||||
            })
 | 
			
		||||
            .open((event: any) => console.log(event))
 | 
			
		||||
            .build();
 | 
			
		||||
 
 | 
			
		||||
@@ -3,7 +3,7 @@ import openApi from './openApi';
 | 
			
		||||
// 登录是否使用验证码配置key
 | 
			
		||||
const AccountLoginSecurity = 'AccountLoginSecurity';
 | 
			
		||||
const UseLoginCaptchaConfigKey = 'UseLoginCaptcha';
 | 
			
		||||
const UseWartermarkConfigKey = 'UseWartermark';
 | 
			
		||||
const UseWatermarkConfigKey = 'UseWatermark';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 获取系统配置值
 | 
			
		||||
@@ -53,12 +53,21 @@ export async function useLoginCaptcha(): Promise<boolean> {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 是否启用水印
 | 
			
		||||
 * 是否启用水印信息配置
 | 
			
		||||
 *
 | 
			
		||||
 * @returns
 | 
			
		||||
 */
 | 
			
		||||
export async function useWartermark(): Promise<boolean> {
 | 
			
		||||
    return await getBoolConfigValue(UseWartermarkConfigKey, true);
 | 
			
		||||
export async function useWatermark(): Promise<any> {
 | 
			
		||||
    const value = await getConfigValue(UseWatermarkConfigKey);
 | 
			
		||||
    if (!value) {
 | 
			
		||||
        return {
 | 
			
		||||
            isUse: true,
 | 
			
		||||
        };
 | 
			
		||||
    }
 | 
			
		||||
    const jsonValue = JSON.parse(value);
 | 
			
		||||
    // 将字符串转为bool
 | 
			
		||||
    jsonValue.isUse = convertBool(jsonValue.isUse, true);
 | 
			
		||||
    return jsonValue;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function convertBool(value: string, defaultValue: boolean) {
 | 
			
		||||
@@ -77,4 +86,3 @@ export async function getLdapEnabled(): Promise<any> {
 | 
			
		||||
    const value = await openApi.getLdapEnabled();
 | 
			
		||||
    return convertBool(value, false);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,46 +1,42 @@
 | 
			
		||||
import { nextTick } from 'vue';
 | 
			
		||||
import loadingCss from '@/theme/loading.scss?inline';
 | 
			
		||||
import '@/theme/loading.scss';
 | 
			
		||||
 | 
			
		||||
// 定义方法
 | 
			
		||||
/**
 | 
			
		||||
 * 页面全局 Loading
 | 
			
		||||
 * @method start 创建 loading
 | 
			
		||||
 * @method done 移除 loading
 | 
			
		||||
 */
 | 
			
		||||
export const NextLoading = {
 | 
			
		||||
    // 载入 css
 | 
			
		||||
    setCss: () => {
 | 
			
		||||
        let link = document.createElement('link');
 | 
			
		||||
        link.rel = 'stylesheet';
 | 
			
		||||
        link.href = loadingCss;
 | 
			
		||||
        link.crossOrigin = 'anonymous';
 | 
			
		||||
        document.getElementsByTagName('head')[0].appendChild(link);
 | 
			
		||||
    },
 | 
			
		||||
    // 创建 loading
 | 
			
		||||
    start: () => {
 | 
			
		||||
        const bodys: any = document.body;
 | 
			
		||||
        const div = document.createElement('div');
 | 
			
		||||
        const bodys: Element = document.body;
 | 
			
		||||
        const div = <HTMLElement>document.createElement('div');
 | 
			
		||||
        div.setAttribute('class', 'loading-next');
 | 
			
		||||
        const htmls = `
 | 
			
		||||
			<div class="loading-next-box">
 | 
			
		||||
			<div class="loading-next-box-warp">
 | 
			
		||||
				<div class="loading-next-box-item"></div>
 | 
			
		||||
				<div class="loading-next-box-item"></div>
 | 
			
		||||
				<div class="loading-next-box-item"></div>
 | 
			
		||||
				<div class="loading-next-box-item"></div>
 | 
			
		||||
				<div class="loading-next-box-item"></div>
 | 
			
		||||
				<div class="loading-next-box-item"></div>
 | 
			
		||||
				<div class="loading-next-box-item"></div>
 | 
			
		||||
				<div class="loading-next-box-item"></div>
 | 
			
		||||
				<div class="loading-next-box-item"></div>
 | 
			
		||||
				<div class="loading-next-box-warp">
 | 
			
		||||
					<div class="loading-next-box-item"></div>
 | 
			
		||||
					<div class="loading-next-box-item"></div>
 | 
			
		||||
					<div class="loading-next-box-item"></div>
 | 
			
		||||
					<div class="loading-next-box-item"></div>
 | 
			
		||||
					<div class="loading-next-box-item"></div>
 | 
			
		||||
					<div class="loading-next-box-item"></div>
 | 
			
		||||
					<div class="loading-next-box-item"></div>
 | 
			
		||||
					<div class="loading-next-box-item"></div>
 | 
			
		||||
					<div class="loading-next-box-item"></div>
 | 
			
		||||
				</div>
 | 
			
		||||
			</div>
 | 
			
		||||
		</div>
 | 
			
		||||
		`;
 | 
			
		||||
        div.innerHTML = htmls;
 | 
			
		||||
        bodys.insertBefore(div, bodys.childNodes[0]);
 | 
			
		||||
    },
 | 
			
		||||
    // 移除 loading
 | 
			
		||||
    done: () => {
 | 
			
		||||
    done: (time: number = 1000) => {
 | 
			
		||||
        nextTick(() => {
 | 
			
		||||
            setTimeout(() => {
 | 
			
		||||
                const el = document.querySelector('.loading-next');
 | 
			
		||||
                el && el.parentNode?.removeChild(el);
 | 
			
		||||
            }, 1000);
 | 
			
		||||
                const el = <HTMLElement>document.querySelector('.loading-next');
 | 
			
		||||
                el?.parentNode?.removeChild(el);
 | 
			
		||||
            }, time);
 | 
			
		||||
        });
 | 
			
		||||
    },
 | 
			
		||||
};
 | 
			
		||||
 
 | 
			
		||||
@@ -1,17 +1,70 @@
 | 
			
		||||
const TokenKey = 'token';
 | 
			
		||||
const UserKey = 'user';
 | 
			
		||||
const TagViewsKey = 'tagViews';
 | 
			
		||||
 | 
			
		||||
// 获取请求token
 | 
			
		||||
export function getToken(): string {
 | 
			
		||||
    return getLocal(TokenKey);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 保存用户访问token
 | 
			
		||||
export function saveToken(token: string) {
 | 
			
		||||
    setLocal(TokenKey, token);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 获取登录用户基础信息
 | 
			
		||||
export function getUser() {
 | 
			
		||||
    return getLocal(UserKey);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 保存用户信息
 | 
			
		||||
export function saveUser(userinfo: any) {
 | 
			
		||||
    setLocal(UserKey, userinfo);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function saveThemeConfig(themeConfig: any) {
 | 
			
		||||
    setLocal('themeConfig', themeConfig);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function getThemeConfig() {
 | 
			
		||||
    return getLocal('themeConfig');
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 清除用户相关的用户信息
 | 
			
		||||
export function clearUser() {
 | 
			
		||||
    removeLocal(TokenKey);
 | 
			
		||||
    removeLocal(UserKey);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function getTagViews() {
 | 
			
		||||
    return getSession(TagViewsKey);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function setTagViews(tagViews: Array<object>) {
 | 
			
		||||
    setSession(TagViewsKey, tagViews);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function removeTagViews() {
 | 
			
		||||
    removeSession(TagViewsKey);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 1. localStorage
 | 
			
		||||
// 设置永久缓存
 | 
			
		||||
export function setLocal(key: string, val: any) {
 | 
			
		||||
    window.localStorage.setItem(key, JSON.stringify(val));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 获取永久缓存
 | 
			
		||||
export function getLocal(key: string) {
 | 
			
		||||
    let json: any = window.localStorage.getItem(key);
 | 
			
		||||
    return JSON.parse(json);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 移除永久缓存
 | 
			
		||||
export function removeLocal(key: string) {
 | 
			
		||||
    window.localStorage.removeItem(key);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 移除全部永久缓存
 | 
			
		||||
export function clearLocal() {
 | 
			
		||||
    window.localStorage.clear();
 | 
			
		||||
@@ -22,33 +75,20 @@ export function clearLocal() {
 | 
			
		||||
export function setSession(key: string, val: any) {
 | 
			
		||||
    window.sessionStorage.setItem(key, JSON.stringify(val));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 获取临时缓存
 | 
			
		||||
export function getSession(key: string) {
 | 
			
		||||
    let json: any = window.sessionStorage.getItem(key);
 | 
			
		||||
    return JSON.parse(json);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 移除临时缓存
 | 
			
		||||
export function removeSession(key: string) {
 | 
			
		||||
    window.sessionStorage.removeItem(key);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 移除全部临时缓存
 | 
			
		||||
export function clearSession() {
 | 
			
		||||
    clearUser();
 | 
			
		||||
    window.sessionStorage.clear();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function getUserInfo4Session() {
 | 
			
		||||
    return getSession('userInfo');
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function setUserInfo2Session(userinfo: any) {
 | 
			
		||||
    setSession('userInfo', userinfo);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 获取是否开启水印
 | 
			
		||||
export function getUseWatermark4Session() {
 | 
			
		||||
    return getSession('useWatermark');
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function setUseWatermark2Session(useWatermark: boolean) {
 | 
			
		||||
    setSession('useWatermark', useWatermark);
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										13
									
								
								mayfly_go_web/src/common/utils/url.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,13 @@
 | 
			
		||||
const mode = import.meta.env.VITE_ROUTER_MODE;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @description 获取不同路由模式所对应的 url
 | 
			
		||||
 * @returns {String}
 | 
			
		||||
 */
 | 
			
		||||
export function getNowUrl() {
 | 
			
		||||
    const url = {
 | 
			
		||||
        hash: location.hash.substring(1),
 | 
			
		||||
        history: location.pathname + location.search,
 | 
			
		||||
    };
 | 
			
		||||
    return url[mode];
 | 
			
		||||
}
 | 
			
		||||
@@ -1,65 +0,0 @@
 | 
			
		||||
import { getUseWatermark4Session, getUserInfo4Session } from '@/common/utils/storage';
 | 
			
		||||
import { dateFormat2 } from '@/common/utils/date';
 | 
			
		||||
 | 
			
		||||
// 页面添加水印效果
 | 
			
		||||
const setWatermark = (str: any) => {
 | 
			
		||||
    const id = '1.23452384164.123412416';
 | 
			
		||||
    if (document.getElementById(id) !== null) document.body.removeChild(document.getElementById(id) as any);
 | 
			
		||||
    const can = document.createElement('canvas');
 | 
			
		||||
    can.width = 400;
 | 
			
		||||
    can.height = 250;
 | 
			
		||||
    const cans: any = can.getContext('2d');
 | 
			
		||||
    cans.rotate((-20 * Math.PI) / 180);
 | 
			
		||||
    cans.font = '14px Vedana';
 | 
			
		||||
    cans.fillStyle = 'rgba(200, 200, 200, 0.35)';
 | 
			
		||||
    cans.textAlign = 'left';
 | 
			
		||||
    cans.textBaseline = 'Middle';
 | 
			
		||||
    // cans.fillText('mayfly go', can.width / 4, can.height )
 | 
			
		||||
    cans.fillText(str, can.width / 8, can.height / 2);
 | 
			
		||||
 | 
			
		||||
    const div = document.createElement('div');
 | 
			
		||||
    div.id = id;
 | 
			
		||||
    div.style.pointerEvents = 'none';
 | 
			
		||||
    div.style.top = '30px';
 | 
			
		||||
    div.style.left = '0px';
 | 
			
		||||
    div.style.position = 'fixed';
 | 
			
		||||
    div.style.zIndex = '10000000';
 | 
			
		||||
    div.style.width = document.documentElement.clientWidth + 'px';
 | 
			
		||||
    div.style.height = document.documentElement.clientHeight + 'px';
 | 
			
		||||
    div.style.background = `url(${can.toDataURL('image/png')}) left top repeat`;
 | 
			
		||||
    document.body.appendChild(div);
 | 
			
		||||
    return id;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
function set(str: any) {
 | 
			
		||||
    let id = setWatermark(str);
 | 
			
		||||
    if (document.getElementById(id) === null) id = setWatermark(str);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function del() {
 | 
			
		||||
    let id = '1.23452384164.123412416';
 | 
			
		||||
    if (document.getElementById(id) !== null) document.body.removeChild(document.getElementById(id) as any);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const watermark = {
 | 
			
		||||
    use: () => {
 | 
			
		||||
        setTimeout(() => {
 | 
			
		||||
            const userinfo = getUserInfo4Session();
 | 
			
		||||
            if (userinfo && getUseWatermark4Session()) {
 | 
			
		||||
                set(`${userinfo.username} ${dateFormat2('yyyy-MM-dd HH:mm:ss', new Date())}`);
 | 
			
		||||
            } else {
 | 
			
		||||
                del();
 | 
			
		||||
            }
 | 
			
		||||
        }, 1500);
 | 
			
		||||
    },
 | 
			
		||||
    // 设置水印
 | 
			
		||||
    set: (str: any) => {
 | 
			
		||||
        set(str);
 | 
			
		||||
    },
 | 
			
		||||
    // 删除水印
 | 
			
		||||
    del: () => {
 | 
			
		||||
        del();
 | 
			
		||||
    },
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export default watermark;
 | 
			
		||||
@@ -9,7 +9,6 @@
 | 
			
		||||
 | 
			
		||||
<script lang="ts" setup>
 | 
			
		||||
import { ref, watch, toRefs, reactive, onMounted, onBeforeUnmount } from 'vue';
 | 
			
		||||
// import * as monaco from 'monaco-editor';
 | 
			
		||||
import * as monaco from 'monaco-editor/esm/vs/editor/editor.api';
 | 
			
		||||
// 相关语言
 | 
			
		||||
import 'monaco-editor/esm/vs/basic-languages/shell/shell.contribution.js';
 | 
			
		||||
@@ -155,7 +154,22 @@ const options = {
 | 
			
		||||
    },
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const monacoTextarea: any = ref();
 | 
			
		||||
 | 
			
		||||
let monacoEditorIns: editor.IStandaloneCodeEditor = null as any;
 | 
			
		||||
let completionItemProvider: any = null;
 | 
			
		||||
 | 
			
		||||
self.MonacoEnvironment = {
 | 
			
		||||
    getWorker(_: any, label: string) {
 | 
			
		||||
        if (label === 'json') {
 | 
			
		||||
            return new JsonWorker();
 | 
			
		||||
        }
 | 
			
		||||
        return new EditorWorker();
 | 
			
		||||
    },
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const state = reactive({
 | 
			
		||||
    editorHeight: '500px',
 | 
			
		||||
    languageMode: 'shell',
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
@@ -173,6 +187,7 @@ onBeforeUnmount(() => {
 | 
			
		||||
        monacoEditorIns.dispose();
 | 
			
		||||
    }
 | 
			
		||||
    if (completionItemProvider) {
 | 
			
		||||
        console.log('unmount=> dispose completion item provider');
 | 
			
		||||
        completionItemProvider.dispose();
 | 
			
		||||
    }
 | 
			
		||||
});
 | 
			
		||||
@@ -203,20 +218,6 @@ watch(
 | 
			
		||||
    }
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
const monacoTextarea: any = ref(null);
 | 
			
		||||
 | 
			
		||||
let monacoEditorIns: editor.IStandaloneCodeEditor = null as any;
 | 
			
		||||
let completionItemProvider: any = null;
 | 
			
		||||
 | 
			
		||||
self.MonacoEnvironment = {
 | 
			
		||||
    getWorker(_: any, label: string) {
 | 
			
		||||
        if (label === 'json') {
 | 
			
		||||
            return new JsonWorker();
 | 
			
		||||
        }
 | 
			
		||||
        return new EditorWorker();
 | 
			
		||||
    },
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const initMonacoEditorIns = () => {
 | 
			
		||||
    console.log('初始化monaco编辑器');
 | 
			
		||||
    // options参数参考 https://microsoft.github.io/monaco-editor/api/interfaces/monaco.editor.IStandaloneEditorConstructionOptions.html#language
 | 
			
		||||
@@ -260,6 +261,7 @@ const setEditorValue = (value: any) => {
 | 
			
		||||
 */
 | 
			
		||||
const registerCompletionItemProvider = () => {
 | 
			
		||||
    if (completionItemProvider) {
 | 
			
		||||
        console.log('exist competion item provider, dispose now');
 | 
			
		||||
        completionItemProvider.dispose();
 | 
			
		||||
    }
 | 
			
		||||
    if (state.languageMode == 'shell') {
 | 
			
		||||
@@ -299,7 +301,11 @@ const format = () => {
 | 
			
		||||
    monacoEditorIns.trigger('', 'editor.action.formatDocument', '');
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
defineExpose({ format });
 | 
			
		||||
const getEditor = () => {
 | 
			
		||||
    return monacoEditorIns;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
defineExpose({ getEditor, format });
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style lang="scss">
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,25 @@
 | 
			
		||||
import * as monaco from 'monaco-editor/esm/vs/editor/editor.api';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * key: language, value: CompletionItemProvider
 | 
			
		||||
 */
 | 
			
		||||
const completionItemProviders: Map<string, any> = new Map();
 | 
			
		||||
 | 
			
		||||
export function registerCompletionItemProvider(language: string, completionItemProvider: any, replace: boolean = true) {
 | 
			
		||||
    const exist = completionItemProviders.get(language);
 | 
			
		||||
    if (exist) {
 | 
			
		||||
        if (!replace) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        exist.dispose();
 | 
			
		||||
    }
 | 
			
		||||
    completionItemProviders.set(language, monaco.languages.registerCompletionItemProvider(language, completionItemProvider));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function dispposeCompletionItemProvider(language: string) {
 | 
			
		||||
    const exist = completionItemProviders.get(language);
 | 
			
		||||
    if (exist) {
 | 
			
		||||
        exist.dispose();
 | 
			
		||||
        completionItemProviders.delete(language);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,12 @@
 | 
			
		||||
export const buildProgressProps = (): any => {
 | 
			
		||||
    return {
 | 
			
		||||
        progress: {
 | 
			
		||||
            sqlFileName: {
 | 
			
		||||
                type: String,
 | 
			
		||||
            },
 | 
			
		||||
            executedStatements: {
 | 
			
		||||
                type: Number,
 | 
			
		||||
            },
 | 
			
		||||
        },
 | 
			
		||||
    };
 | 
			
		||||
};
 | 
			
		||||
@@ -0,0 +1,34 @@
 | 
			
		||||
<template>
 | 
			
		||||
    <el-descriptions border size="small" :title="`${progress.sqlFileName}`">
 | 
			
		||||
        <el-descriptions-item label="时间">{{ state.elapsedTime }}</el-descriptions-item>
 | 
			
		||||
        <el-descriptions-item label="已处理">{{ progress.executedStatements }}</el-descriptions-item>
 | 
			
		||||
    </el-descriptions>
 | 
			
		||||
</template>
 | 
			
		||||
<script lang="ts" setup>
 | 
			
		||||
import { onMounted, onUnmounted, reactive } from 'vue';
 | 
			
		||||
import { formatTime } from 'element-plus/es/components/countdown/src/utils';
 | 
			
		||||
import { buildProgressProps } from './progress-notify';
 | 
			
		||||
 | 
			
		||||
const props = defineProps(buildProgressProps());
 | 
			
		||||
 | 
			
		||||
const state = reactive({
 | 
			
		||||
    elapsedTime: '00:00:00',
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
let timer: any = undefined;
 | 
			
		||||
const startTime = Date.now();
 | 
			
		||||
 | 
			
		||||
onMounted(async () => {
 | 
			
		||||
    timer = setInterval(() => {
 | 
			
		||||
        const elapsed = Date.now() - startTime;
 | 
			
		||||
        state.elapsedTime = formatTime(elapsed, 'HH:mm:ss');
 | 
			
		||||
    }, 1000);
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
onUnmounted(async () => {
 | 
			
		||||
    if (timer != undefined) {
 | 
			
		||||
        clearInterval(timer); // 在Vue实例销毁前,清除我们的定时器
 | 
			
		||||
        timer = undefined;
 | 
			
		||||
    }
 | 
			
		||||
});
 | 
			
		||||
</script>
 | 
			
		||||
@@ -52,13 +52,13 @@
 | 
			
		||||
                                title="最小化"
 | 
			
		||||
                            />
 | 
			
		||||
 | 
			
		||||
                            <!-- <SvgIcon name="FullScreen" @click="handlerFullScreen(openTerminal)" :size="20" class="pointer-icon mr10" title="全屏|退出全屏" /> -->
 | 
			
		||||
                            <SvgIcon name="FullScreen" @click="handlerFullScreen(openTerminal)" :size="20" class="pointer-icon mr10" title="全屏|退出全屏" />
 | 
			
		||||
 | 
			
		||||
                            <SvgIcon name="Close" class="pointer-icon" @click="close(openTerminal.terminalId)" title="关闭" :size="20" />
 | 
			
		||||
                        </div>
 | 
			
		||||
                    </div>
 | 
			
		||||
                </template>
 | 
			
		||||
                <div class="terminal-wrapper" style="height: calc(100vh - 215px)">
 | 
			
		||||
                <div class="terminal-wrapper" :style="{ height: `calc(100vh - ${openTerminal.fullscreen ? '47px' : '200px'})` }">
 | 
			
		||||
                    <TerminalBody
 | 
			
		||||
                        @status-change="terminalStatusChange(openTerminal.terminalId, $event)"
 | 
			
		||||
                        :ref="(el) => setTerminalRef(el, openTerminal.terminalId)"
 | 
			
		||||
@@ -230,6 +230,16 @@ function maximize(terminalId: any) {
 | 
			
		||||
    }, 250);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const handlerFullScreen = (terminal: any) => {
 | 
			
		||||
    terminal.fullscreen = !terminal.fullscreen;
 | 
			
		||||
    const terminalRef = openTerminalRefs[terminal.terminalId];
 | 
			
		||||
    // fit
 | 
			
		||||
    setTimeout(() => {
 | 
			
		||||
        terminalRef?.fitTerminal();
 | 
			
		||||
        terminalRef?.focus();
 | 
			
		||||
    }, 250);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const closeMinimizeTerminal = (terminalId: any) => {
 | 
			
		||||
    delete state.minimizeTerminals[terminalId];
 | 
			
		||||
    close(terminalId);
 | 
			
		||||
@@ -249,9 +259,11 @@ defineExpose({
 | 
			
		||||
        padding: 10px;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // .terminal-dialog {
 | 
			
		||||
    //     height: calc(100vh - 200px) !important;
 | 
			
		||||
    // }
 | 
			
		||||
    // 取消body最大高度,否则全屏有问题
 | 
			
		||||
    .el-dialog__body {
 | 
			
		||||
        max-height: 100% !important;
 | 
			
		||||
        overflow: hidden !important;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .el-overlay .el-overlay-dialog .el-dialog .el-dialog__body {
 | 
			
		||||
        padding: 0px !important;
 | 
			
		||||
 
 | 
			
		||||
@@ -21,8 +21,8 @@ import pinia from '@/store/index';
 | 
			
		||||
import { storeToRefs } from 'pinia';
 | 
			
		||||
import { useThemeConfig } from '@/store/themeConfig';
 | 
			
		||||
import { useRoutesList } from '@/store/routesList';
 | 
			
		||||
import Logo from '@/views/layout/logo/index.vue';
 | 
			
		||||
import Vertical from '@/views/layout/navMenu/vertical.vue';
 | 
			
		||||
import Logo from '@/layout/logo/index.vue';
 | 
			
		||||
import Vertical from '@/layout/navMenu/vertical.vue';
 | 
			
		||||
import mittBus from '@/common/utils/mitt';
 | 
			
		||||
 | 
			
		||||
const { proxy } = getCurrentInstance() as any;
 | 
			
		||||
@@ -38,8 +38,7 @@ const state: any = reactive({
 | 
			
		||||
// 设置菜单展开/收起时的宽度
 | 
			
		||||
const setCollapseWidth = computed(() => {
 | 
			
		||||
    let { layout, isCollapse, menuBar } = themeConfig.value;
 | 
			
		||||
    let asideBrColor =
 | 
			
		||||
        menuBar === '#FFFFFF' || menuBar === '#FFF' || menuBar === '#fff' || menuBar === '#ffffff' ? 'layout-el-aside-br-color' : '';
 | 
			
		||||
    let asideBrColor = menuBar === '#FFFFFF' || menuBar === '#FFF' || menuBar === '#fff' || menuBar === '#ffffff' ? 'layout-el-aside-br-color' : '';
 | 
			
		||||
    if (layout === 'columns') {
 | 
			
		||||
        // 分栏布局,菜单收起时宽度给 1px
 | 
			
		||||
        if (isCollapse) {
 | 
			
		||||
@@ -6,7 +6,7 @@
 | 
			
		||||
 | 
			
		||||
<script lang="ts">
 | 
			
		||||
import { computed } from 'vue';
 | 
			
		||||
import NavBarsIndex from '@/views/layout/navBars/index.vue';
 | 
			
		||||
import NavBarsIndex from '@/layout/navBars/index.vue';
 | 
			
		||||
import { useThemeConfig } from '@/store/themeConfig';
 | 
			
		||||
export default {
 | 
			
		||||
    name: 'layoutHeader',
 | 
			
		||||
@@ -1,16 +1,25 @@
 | 
			
		||||
<template>
 | 
			
		||||
    <el-main class="layout-main">
 | 
			
		||||
        <el-scrollbar class="layout-scrollbar" ref="layoutScrollbarRef"
 | 
			
		||||
        <el-scrollbar
 | 
			
		||||
            class="layout-scrollbar"
 | 
			
		||||
            ref="layoutScrollbarRef"
 | 
			
		||||
            v-show="!state.currentRouteMeta.link && state.currentRouteMeta.linkType != 1"
 | 
			
		||||
            :style="{ minHeight: `calc(100vh - ${state.headerHeight}` }">
 | 
			
		||||
            :style="{ minHeight: `calc(100vh - ${state.headerHeight}` }"
 | 
			
		||||
        >
 | 
			
		||||
            <LayoutParentView />
 | 
			
		||||
            <Footer v-if="themeConfig.isFooter" />
 | 
			
		||||
        </el-scrollbar>
 | 
			
		||||
        <Link :style="{ height: `calc(100vh - ${state.headerHeight}` }" :meta="state.currentRouteMeta"
 | 
			
		||||
            v-if="state.currentRouteMeta.link && state.currentRouteMeta.linkType == 2" />
 | 
			
		||||
        <Iframes :style="{ height: `calc(100vh - ${state.headerHeight}` }" :meta="state.currentRouteMeta"
 | 
			
		||||
        <Link
 | 
			
		||||
            :style="{ height: `calc(100vh - ${state.headerHeight}` }"
 | 
			
		||||
            :meta="state.currentRouteMeta"
 | 
			
		||||
            v-if="state.currentRouteMeta.link && state.currentRouteMeta.linkType == 2"
 | 
			
		||||
        />
 | 
			
		||||
        <Iframes
 | 
			
		||||
            :style="{ height: `calc(100vh - ${state.headerHeight}` }"
 | 
			
		||||
            :meta="state.currentRouteMeta"
 | 
			
		||||
            v-if="state.currentRouteMeta.link && state.currentRouteMeta.linkType == 1 && state.isShowLink"
 | 
			
		||||
            @getCurrentRouteMeta="onGetCurrentRouteMeta" />
 | 
			
		||||
            @getCurrentRouteMeta="onGetCurrentRouteMeta"
 | 
			
		||||
        />
 | 
			
		||||
    </el-main>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
@@ -19,10 +28,10 @@ import { reactive, getCurrentInstance, watch, onBeforeMount } from 'vue';
 | 
			
		||||
import { useRoute } from 'vue-router';
 | 
			
		||||
import { storeToRefs } from 'pinia';
 | 
			
		||||
import { useThemeConfig } from '@/store/themeConfig';
 | 
			
		||||
import LayoutParentView from '@/views/layout/routerView/parent.vue';
 | 
			
		||||
import Footer from '@/views/layout/footer/index.vue';
 | 
			
		||||
import Link from '@/views/layout/routerView/link.vue';
 | 
			
		||||
import Iframes from '@/views/layout/routerView/iframes.vue';
 | 
			
		||||
import LayoutParentView from '@/layout/routerView/parent.vue';
 | 
			
		||||
import Footer from '@/layout/footer/index.vue';
 | 
			
		||||
import Link from '@/layout/routerView/link.vue';
 | 
			
		||||
import Iframes from '@/layout/routerView/iframes.vue';
 | 
			
		||||
 | 
			
		||||
const { proxy } = getCurrentInstance() as any;
 | 
			
		||||
const { themeConfig } = storeToRefs(useThemeConfig());
 | 
			
		||||
@@ -10,10 +10,10 @@ import { onBeforeMount, onUnmounted } from 'vue';
 | 
			
		||||
import { getLocal, setLocal } from '@/common/utils/storage';
 | 
			
		||||
import { storeToRefs } from 'pinia';
 | 
			
		||||
import { useThemeConfig } from '@/store/themeConfig';
 | 
			
		||||
import Defaults from '@/views/layout/main/defaults.vue';
 | 
			
		||||
import Classic from '@/views/layout/main/classic.vue';
 | 
			
		||||
import Transverse from '@/views/layout/main/transverse.vue';
 | 
			
		||||
import Columns from '@/views/layout/main/columns.vue';
 | 
			
		||||
import Defaults from '@/layout/main/defaults.vue';
 | 
			
		||||
import Classic from '@/layout/main/classic.vue';
 | 
			
		||||
import Transverse from '@/layout/main/transverse.vue';
 | 
			
		||||
import Columns from '@/layout/main/columns.vue';
 | 
			
		||||
import mittBus from '@/common/utils/mitt';
 | 
			
		||||
 | 
			
		||||
const { themeConfig } = storeToRefs(useThemeConfig());
 | 
			
		||||
@@ -213,7 +213,7 @@ onUnmounted(() => {
 | 
			
		||||
}
 | 
			
		||||
.layout-lock-screen-img {
 | 
			
		||||
    @extend .layout-lock-screen-fixed;
 | 
			
		||||
    background: url('@/assets/image/bg-login.png') no-repeat;
 | 
			
		||||
    background: url('@/assets/image/login-bg-main.svg') no-repeat;
 | 
			
		||||
    background-size: 100% 100%;
 | 
			
		||||
    z-index: 9999991;
 | 
			
		||||
}
 | 
			
		||||
@@ -15,10 +15,10 @@
 | 
			
		||||
<script lang="ts" setup name="layoutClassic">
 | 
			
		||||
import { storeToRefs } from 'pinia';
 | 
			
		||||
import { useThemeConfig } from '@/store/themeConfig';
 | 
			
		||||
import Aside from '@/views/layout/component/aside.vue';
 | 
			
		||||
import Header from '@/views/layout/component/header.vue';
 | 
			
		||||
import Main from '@/views/layout/component/main.vue';
 | 
			
		||||
import TagsView from '@/views/layout/navBars/tagsView/tagsView.vue';
 | 
			
		||||
import Aside from '@/layout/component/aside.vue';
 | 
			
		||||
import Header from '@/layout/component/header.vue';
 | 
			
		||||
import Main from '@/layout/component/main.vue';
 | 
			
		||||
import TagsView from '@/layout/navBars/tagsView/tagsView.vue';
 | 
			
		||||
 | 
			
		||||
const { themeConfig } = storeToRefs(useThemeConfig());
 | 
			
		||||
</script>
 | 
			
		||||
@@ -17,10 +17,10 @@
 | 
			
		||||
 | 
			
		||||
<script lang="ts">
 | 
			
		||||
import { computed } from 'vue';
 | 
			
		||||
import Aside from '@/views/layout/component/aside.vue';
 | 
			
		||||
import Header from '@/views/layout/component/header.vue';
 | 
			
		||||
import Main from '@/views/layout/component/main.vue';
 | 
			
		||||
import ColumnsAside from '@/views/layout/component/columnsAside.vue';
 | 
			
		||||
import Aside from '@/layout/component/aside.vue';
 | 
			
		||||
import Header from '@/layout/component/header.vue';
 | 
			
		||||
import Main from '@/layout/component/main.vue';
 | 
			
		||||
import ColumnsAside from '@/layout/component/columnsAside.vue';
 | 
			
		||||
import { useThemeConfig } from '@/store/themeConfig';
 | 
			
		||||
export default {
 | 
			
		||||
    name: 'layoutColumns',
 | 
			
		||||
@@ -15,9 +15,9 @@
 | 
			
		||||
<script lang="ts">
 | 
			
		||||
import { computed, getCurrentInstance, watch } from 'vue';
 | 
			
		||||
import { useRoute } from 'vue-router';
 | 
			
		||||
import Aside from '@/views/layout/component/aside.vue';
 | 
			
		||||
import Header from '@/views/layout/component/header.vue';
 | 
			
		||||
import Main from '@/views/layout/component/main.vue';
 | 
			
		||||
import Aside from '@/layout/component/aside.vue';
 | 
			
		||||
import Header from '@/layout/component/header.vue';
 | 
			
		||||
import Main from '@/layout/component/main.vue';
 | 
			
		||||
import { useThemeConfig } from '@/store/themeConfig';
 | 
			
		||||
export default {
 | 
			
		||||
    name: 'layoutDefaults',
 | 
			
		||||
@@ -7,8 +7,8 @@
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script lang="ts">
 | 
			
		||||
import Header from '@/views/layout/component/header.vue';
 | 
			
		||||
import Main from '@/views/layout/component/main.vue';
 | 
			
		||||
import Header from '@/layout/component/header.vue';
 | 
			
		||||
import Main from '@/layout/component/main.vue';
 | 
			
		||||
export default {
 | 
			
		||||
    name: 'layoutTransverse',
 | 
			
		||||
    components: { Header, Main },
 | 
			
		||||
@@ -14,10 +14,10 @@ import pinia from '@/store/index';
 | 
			
		||||
import { storeToRefs } from 'pinia';
 | 
			
		||||
import { useThemeConfig } from '@/store/themeConfig';
 | 
			
		||||
import { useRoutesList } from '@/store/routesList';
 | 
			
		||||
import Breadcrumb from '@/views/layout/navBars/breadcrumb/breadcrumb.vue';
 | 
			
		||||
import User from '@/views/layout/navBars/breadcrumb/user.vue';
 | 
			
		||||
import Logo from '@/views/layout/logo/index.vue';
 | 
			
		||||
import Horizontal from '@/views/layout/navMenu/horizontal.vue';
 | 
			
		||||
import Breadcrumb from '@/layout/navBars/breadcrumb/breadcrumb.vue';
 | 
			
		||||
import User from '@/layout/navBars/breadcrumb/user.vue';
 | 
			
		||||
import Logo from '@/layout/logo/index.vue';
 | 
			
		||||
import Horizontal from '@/layout/navMenu/horizontal.vue';
 | 
			
		||||
import mittBus from '@/common/utils/mitt';
 | 
			
		||||
 | 
			
		||||
const { themeConfig } = storeToRefs(useThemeConfig());
 | 
			
		||||
@@ -374,7 +374,7 @@
 | 
			
		||||
                        </div>
 | 
			
		||||
                    </div>
 | 
			
		||||
                </div>
 | 
			
		||||
                <div class="copy-config">
 | 
			
		||||
                <!-- <div class="copy-config">
 | 
			
		||||
                    <el-alert title="点击下方按钮,复制布局配置去 /src/store/modules/themeConfig.ts中修改" type="warning" :closable="false"> </el-alert>
 | 
			
		||||
                    <el-button
 | 
			
		||||
                        size="small"
 | 
			
		||||
@@ -385,7 +385,7 @@
 | 
			
		||||
                        @click="onCopyConfigClick($event.target)"
 | 
			
		||||
                        >一键复制配置
 | 
			
		||||
                    </el-button>
 | 
			
		||||
                </div>
 | 
			
		||||
                </div> -->
 | 
			
		||||
            </el-scrollbar>
 | 
			
		||||
        </el-drawer>
 | 
			
		||||
    </div>
 | 
			
		||||
@@ -56,7 +56,7 @@
 | 
			
		||||
                <crop />
 | 
			
		||||
            </el-icon>
 | 
			
		||||
        </div>
 | 
			
		||||
        <el-dropdown :show-timeout="70" :hide-timeout="50" @command="onHandleCommandClick">
 | 
			
		||||
        <el-dropdown trigger="click" :show-timeout="70" :hide-timeout="50" @command="onHandleCommandClick">
 | 
			
		||||
            <span class="layout-navbars-breadcrumb-user-link" style="cursor: pointer">
 | 
			
		||||
                <img :src="userInfo.photo" class="layout-navbars-breadcrumb-user-link-photo mr5" />
 | 
			
		||||
                {{ userInfo.name || userInfo.username }}
 | 
			
		||||
@@ -75,7 +75,7 @@
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script setup lang="ts" name="layoutBreadcrumbUser">
 | 
			
		||||
import { ref, computed, reactive, onMounted, nextTick } from 'vue';
 | 
			
		||||
import { ref, computed, reactive, onMounted } from 'vue';
 | 
			
		||||
import { useRouter } from 'vue-router';
 | 
			
		||||
import { ElMessageBox, ElMessage } from 'element-plus';
 | 
			
		||||
import screenfull from 'screenfull';
 | 
			
		||||
@@ -83,11 +83,12 @@ import { resetRoute } from '@/router/index';
 | 
			
		||||
import { storeToRefs } from 'pinia';
 | 
			
		||||
import { useUserInfo } from '@/store/userInfo';
 | 
			
		||||
import { useThemeConfig } from '@/store/themeConfig';
 | 
			
		||||
import { clearSession, setLocal, getLocal, removeLocal } from '@/common/utils/storage';
 | 
			
		||||
import UserNews from '@/views/layout/navBars/breadcrumb/userNews.vue';
 | 
			
		||||
import SearchMenu from '@/views/layout/navBars/breadcrumb/search.vue';
 | 
			
		||||
import { clearSession, removeLocal } 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';
 | 
			
		||||
 | 
			
		||||
const router = useRouter();
 | 
			
		||||
const searchRef = ref();
 | 
			
		||||
@@ -99,7 +100,8 @@ const state = reactive({
 | 
			
		||||
    disabledSize: '',
 | 
			
		||||
});
 | 
			
		||||
const { userInfo } = storeToRefs(useUserInfo());
 | 
			
		||||
const { themeConfig } = storeToRefs(useThemeConfig());
 | 
			
		||||
const themeConfigStore = useThemeConfig();
 | 
			
		||||
const { themeConfig } = storeToRefs(themeConfigStore);
 | 
			
		||||
 | 
			
		||||
// 设置分割样式
 | 
			
		||||
const layoutUserFlexNum = computed(() => {
 | 
			
		||||
@@ -164,16 +166,8 @@ const onHandleCommandClick = (path: string) => {
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const switchDark = (isDark: boolean) => {
 | 
			
		||||
    themeConfig.value.isDark = isDark;
 | 
			
		||||
    setLocal('themeConfig', themeConfig.value);
 | 
			
		||||
    const body = document.documentElement as HTMLElement;
 | 
			
		||||
    if (isDark) {
 | 
			
		||||
        body.setAttribute('class', 'dark');
 | 
			
		||||
        themeConfig.value.editorTheme = 'vs-dark';
 | 
			
		||||
    } else {
 | 
			
		||||
        body.setAttribute('class', '');
 | 
			
		||||
        themeConfig.value.editorTheme = 'SolarizedLight';
 | 
			
		||||
    }
 | 
			
		||||
    themeConfigStore.switchDark(isDark);
 | 
			
		||||
    saveThemeConfig(themeConfig.value);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// // 菜单搜索点击
 | 
			
		||||
@@ -185,7 +179,7 @@ const onSearchClick = () => {
 | 
			
		||||
const onComponentSizeChange = (size: string) => {
 | 
			
		||||
    removeLocal('themeConfig');
 | 
			
		||||
    themeConfig.value.globalComponentSize = size;
 | 
			
		||||
    setLocal('themeConfig', themeConfig.value);
 | 
			
		||||
    saveThemeConfig(themeConfig.value);
 | 
			
		||||
    // proxy.$ELEMENT.size = size;
 | 
			
		||||
    initComponentSize();
 | 
			
		||||
    window.location.reload();
 | 
			
		||||
@@ -193,7 +187,7 @@ const onComponentSizeChange = (size: string) => {
 | 
			
		||||
 | 
			
		||||
// 初始化全局组件大小
 | 
			
		||||
const initComponentSize = () => {
 | 
			
		||||
    switch (getLocal('themeConfig').globalComponentSize) {
 | 
			
		||||
    switch (getThemeConfig().globalComponentSize) {
 | 
			
		||||
        case '':
 | 
			
		||||
            state.disabledSize = '';
 | 
			
		||||
            break;
 | 
			
		||||
@@ -211,12 +205,10 @@ const initComponentSize = () => {
 | 
			
		||||
 | 
			
		||||
// 页面加载时
 | 
			
		||||
onMounted(() => {
 | 
			
		||||
    if (getLocal('themeConfig')) {
 | 
			
		||||
        const isDark = themeConfig.value.isDark;
 | 
			
		||||
        state.isDark = isDark;
 | 
			
		||||
        switchDark(isDark);
 | 
			
		||||
 | 
			
		||||
    const themeConfig = getThemeConfig();
 | 
			
		||||
    if (themeConfig) {
 | 
			
		||||
        initComponentSize();
 | 
			
		||||
        state.isDark = themeConfig.isDark;
 | 
			
		||||
    }
 | 
			
		||||
});
 | 
			
		||||
</script>
 | 
			
		||||
@@ -8,8 +8,8 @@
 | 
			
		||||
<script lang="ts">
 | 
			
		||||
import { computed } from 'vue';
 | 
			
		||||
import { useThemeConfig } from '@/store/themeConfig';
 | 
			
		||||
import BreadcrumbIndex from '@/views/layout/navBars/breadcrumb/index.vue';
 | 
			
		||||
import TagsView from '@/views/layout/navBars/tagsView/tagsView.vue';
 | 
			
		||||
import BreadcrumbIndex from '@/layout/navBars/breadcrumb/index.vue';
 | 
			
		||||
import TagsView from '@/layout/navBars/tagsView/tagsView.vue';
 | 
			
		||||
export default {
 | 
			
		||||
    name: 'layoutNavBars',
 | 
			
		||||
    components: { BreadcrumbIndex, TagsView },
 | 
			
		||||
							
								
								
									
										138
									
								
								mayfly_go_web/src/layout/navBars/tagsView/contextmenu.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,138 @@
 | 
			
		||||
<template>
 | 
			
		||||
    <transition name="el-zoom-in-center">
 | 
			
		||||
        <div
 | 
			
		||||
            aria-hidden="true"
 | 
			
		||||
            class="el-dropdown__popper el-popper is-light is-pure custom-contextmenu"
 | 
			
		||||
            role="tooltip"
 | 
			
		||||
            data-popper-placement="bottom"
 | 
			
		||||
            :style="`top: ${dropdowns.y + 5}px;left: ${dropdowns.x}px;`"
 | 
			
		||||
            :key="Math.random()"
 | 
			
		||||
            v-show="state.isShow"
 | 
			
		||||
        >
 | 
			
		||||
            <ul class="el-dropdown-menu">
 | 
			
		||||
                <template v-for="(v, k) in state.dropdownList">
 | 
			
		||||
                    <li
 | 
			
		||||
                        class="el-dropdown-menu__item"
 | 
			
		||||
                        aria-disabled="false"
 | 
			
		||||
                        tabindex="-1"
 | 
			
		||||
                        :key="k"
 | 
			
		||||
                        v-if="!v.affix"
 | 
			
		||||
                        @click="onCurrentContextmenuClick(v.contextMenuClickId)"
 | 
			
		||||
                    >
 | 
			
		||||
                        <SvgIcon :name="v.icon" />
 | 
			
		||||
                        <span>{{ v.txt }}</span>
 | 
			
		||||
                    </li>
 | 
			
		||||
                </template>
 | 
			
		||||
            </ul>
 | 
			
		||||
            <div class="el-popper__arrow" :style="{ left: `${state.arrowLeft}px` }"></div>
 | 
			
		||||
        </div>
 | 
			
		||||
    </transition>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script setup lang="ts" name="layoutTagsViewContextmenu">
 | 
			
		||||
import { computed, reactive, onMounted, onUnmounted, watch } from 'vue';
 | 
			
		||||
 | 
			
		||||
// 定义父组件传过来的值
 | 
			
		||||
const props = defineProps({
 | 
			
		||||
    dropdown: {
 | 
			
		||||
        type: Object,
 | 
			
		||||
        default: () => {
 | 
			
		||||
            return {
 | 
			
		||||
                x: 0,
 | 
			
		||||
                y: 0,
 | 
			
		||||
            };
 | 
			
		||||
        },
 | 
			
		||||
    },
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
// 定义子组件向父组件传值/事件
 | 
			
		||||
const emit = defineEmits(['currentContextmenuClick']);
 | 
			
		||||
 | 
			
		||||
// 定义变量内容
 | 
			
		||||
const state = reactive({
 | 
			
		||||
    isShow: false,
 | 
			
		||||
    dropdownList: [
 | 
			
		||||
        { contextMenuClickId: 0, txt: '刷新', affix: false, icon: 'RefreshRight' },
 | 
			
		||||
        { contextMenuClickId: 1, txt: '关闭', affix: false, icon: 'Close' },
 | 
			
		||||
        { contextMenuClickId: 2, txt: '关闭其他', affix: false, icon: 'CircleClose' },
 | 
			
		||||
        { contextMenuClickId: 3, txt: '关闭所有', affix: false, icon: 'FolderDelete' },
 | 
			
		||||
        {
 | 
			
		||||
            contextMenuClickId: 4,
 | 
			
		||||
            txt: '当前页全屏',
 | 
			
		||||
            affix: false,
 | 
			
		||||
            icon: 'full-screen',
 | 
			
		||||
        },
 | 
			
		||||
    ],
 | 
			
		||||
    item: {} as any,
 | 
			
		||||
    arrowLeft: 10,
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
// 父级传过来的坐标 x,y 值
 | 
			
		||||
const dropdowns = computed(() => {
 | 
			
		||||
    // 117 为 `Dropdown 下拉菜单` 的宽度
 | 
			
		||||
    if (props.dropdown.x + 117 > document.documentElement.clientWidth) {
 | 
			
		||||
        return {
 | 
			
		||||
            x: document.documentElement.clientWidth - 117 - 5,
 | 
			
		||||
            y: props.dropdown.y,
 | 
			
		||||
        };
 | 
			
		||||
    } else {
 | 
			
		||||
        return props.dropdown;
 | 
			
		||||
    }
 | 
			
		||||
});
 | 
			
		||||
// 当前项菜单点击
 | 
			
		||||
const onCurrentContextmenuClick = (contextMenuClickId: number) => {
 | 
			
		||||
    emit('currentContextmenuClick', { id: contextMenuClickId, path: state.item.path });
 | 
			
		||||
};
 | 
			
		||||
// 打开右键菜单:判断是否固定,固定则不显示关闭按钮
 | 
			
		||||
const openContextmenu = (item: any) => {
 | 
			
		||||
    state.item = item;
 | 
			
		||||
    item.isAffix ? (state.dropdownList[1].affix = true) : (state.dropdownList[1].affix = false);
 | 
			
		||||
    closeContextmenu();
 | 
			
		||||
    setTimeout(() => {
 | 
			
		||||
        state.isShow = true;
 | 
			
		||||
    }, 10);
 | 
			
		||||
};
 | 
			
		||||
// 关闭右键菜单
 | 
			
		||||
const closeContextmenu = () => {
 | 
			
		||||
    state.isShow = false;
 | 
			
		||||
};
 | 
			
		||||
// 监听页面监听进行右键菜单的关闭
 | 
			
		||||
onMounted(() => {
 | 
			
		||||
    document.body.addEventListener('click', closeContextmenu);
 | 
			
		||||
});
 | 
			
		||||
// 页面卸载时,移除右键菜单监听事件
 | 
			
		||||
onUnmounted(() => {
 | 
			
		||||
    document.body.removeEventListener('click', closeContextmenu);
 | 
			
		||||
});
 | 
			
		||||
// 监听下拉菜单位置
 | 
			
		||||
watch(
 | 
			
		||||
    () => props.dropdown,
 | 
			
		||||
    ({ x }) => {
 | 
			
		||||
        if (x + 117 > document.documentElement.clientWidth) state.arrowLeft = 117 - (document.documentElement.clientWidth - x);
 | 
			
		||||
        else state.arrowLeft = 10;
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
        deep: true,
 | 
			
		||||
    }
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
// 暴露变量
 | 
			
		||||
defineExpose({
 | 
			
		||||
    openContextmenu,
 | 
			
		||||
});
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style scoped lang="scss">
 | 
			
		||||
.custom-contextmenu {
 | 
			
		||||
    transform-origin: center top;
 | 
			
		||||
    z-index: 2190;
 | 
			
		||||
    position: fixed;
 | 
			
		||||
    .el-dropdown-menu__item {
 | 
			
		||||
        font-size: 12px !important;
 | 
			
		||||
        white-space: nowrap;
 | 
			
		||||
        i {
 | 
			
		||||
            font-size: 12px !important;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
@@ -3,7 +3,7 @@
 | 
			
		||||
        <el-scrollbar ref="scrollbarRef" @wheel.prevent="onHandleScroll">
 | 
			
		||||
            <ul class="layout-navbars-tagsview-ul" :class="setTagsStyle" ref="tagsUlRef">
 | 
			
		||||
                <li
 | 
			
		||||
                    v-for="(v, k) in state.tagsViewList"
 | 
			
		||||
                    v-for="(v, k) in tagsViews"
 | 
			
		||||
                    :key="k"
 | 
			
		||||
                    class="layout-navbars-tagsview-ul-li"
 | 
			
		||||
                    :data-name="v.name"
 | 
			
		||||
@@ -17,8 +17,8 @@
 | 
			
		||||
                    "
 | 
			
		||||
                >
 | 
			
		||||
                    <SvgIcon name="iconfont icon-tag-view-active" class="layout-navbars-tagsview-ul-li-iconfont font14" v-if="isActive(v)" />
 | 
			
		||||
                    <SvgIcon :name="v.meta.icon" class="layout-navbars-tagsview-ul-li-iconfont" v-if="!isActive(v) && themeConfig.isTagsviewIcon" />
 | 
			
		||||
                    <span>{{ v.meta.title }}</span>
 | 
			
		||||
                    <SvgIcon :name="v.icon" class="layout-navbars-tagsview-ul-li-iconfont" v-if="!isActive(v) && themeConfig.isTagsviewIcon" />
 | 
			
		||||
                    <span>{{ v.title }}</span>
 | 
			
		||||
                    <template v-if="isActive(v)">
 | 
			
		||||
                        <SvgIcon
 | 
			
		||||
                            name="RefreshRight"
 | 
			
		||||
@@ -28,7 +28,7 @@
 | 
			
		||||
                        <SvgIcon
 | 
			
		||||
                            name="Close"
 | 
			
		||||
                            class="font14 layout-navbars-tagsview-ul-li-icon layout-icon-active"
 | 
			
		||||
                            v-if="!v.meta.isAffix"
 | 
			
		||||
                            v-if="!v.isAffix"
 | 
			
		||||
                            @click.stop="closeCurrentTagsView(themeConfig.isShareTagsView ? v.path : v.path)"
 | 
			
		||||
                        />
 | 
			
		||||
                    </template>
 | 
			
		||||
@@ -36,7 +36,7 @@
 | 
			
		||||
                    <SvgIcon
 | 
			
		||||
                        name="Close"
 | 
			
		||||
                        class="font14 layout-navbars-tagsview-ul-li-icon layout-icon-three"
 | 
			
		||||
                        v-if="!v.meta.isAffix"
 | 
			
		||||
                        v-if="!v.isAffix"
 | 
			
		||||
                        @click.stop="closeCurrentTagsView(themeConfig.isShareTagsView ? v.path : v.path)"
 | 
			
		||||
                    />
 | 
			
		||||
                </li>
 | 
			
		||||
@@ -52,17 +52,24 @@ import { useRoute, useRouter, onBeforeRouteUpdate } from 'vue-router';
 | 
			
		||||
import screenfull from 'screenfull';
 | 
			
		||||
import { storeToRefs } from 'pinia';
 | 
			
		||||
import { useThemeConfig } from '@/store/themeConfig';
 | 
			
		||||
import { getSession, setSession, removeSession } from '@/common/utils/storage';
 | 
			
		||||
import mittBus from '@/common/utils/mitt';
 | 
			
		||||
import Sortable from 'sortablejs';
 | 
			
		||||
import Contextmenu from '@/views/layout/navBars/tagsView/contextmenu.vue';
 | 
			
		||||
import Contextmenu from '@/layout/navBars/tagsView/contextmenu.vue';
 | 
			
		||||
import { getTagViews, setTagViews, removeTagViews } from '@/common/utils/storage';
 | 
			
		||||
import { useTagsViews } from '@/store/tagsViews';
 | 
			
		||||
import { useKeepALiveNames } from '@/store/keepAliveNames';
 | 
			
		||||
 | 
			
		||||
const { proxy } = getCurrentInstance() as any;
 | 
			
		||||
const tagsRefs = ref([]) as any;
 | 
			
		||||
const scrollbarRef = ref();
 | 
			
		||||
const contextmenuRef = ref();
 | 
			
		||||
const tagsUlRef = ref();
 | 
			
		||||
 | 
			
		||||
const { themeConfig } = storeToRefs(useThemeConfig());
 | 
			
		||||
const { tagsViews } = storeToRefs(useTagsViews());
 | 
			
		||||
 | 
			
		||||
const keepAliveNamesStores = useKeepALiveNames();
 | 
			
		||||
 | 
			
		||||
const route = useRoute();
 | 
			
		||||
const router = useRouter();
 | 
			
		||||
 | 
			
		||||
@@ -70,7 +77,6 @@ const state = reactive({
 | 
			
		||||
    routePath: route.fullPath,
 | 
			
		||||
    dropdown: { x: '', y: '' },
 | 
			
		||||
    tagsRefsIndex: 0,
 | 
			
		||||
    tagsViewList: [] as any,
 | 
			
		||||
    sortable: '' as any,
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
@@ -81,119 +87,153 @@ const setTagsStyle = computed(() => {
 | 
			
		||||
 | 
			
		||||
// 存储 tagsViewList 到浏览器临时缓存中,页面刷新时,保留记录
 | 
			
		||||
const addBrowserSetSession = (tagsViewList: Array<object>) => {
 | 
			
		||||
    setSession('tagsViewList', tagsViewList);
 | 
			
		||||
    setTagViews(tagsViewList);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// 获取 vuex 中的 tagsViewRoutes 列表
 | 
			
		||||
// 获取  tagsViewRoutes 列表
 | 
			
		||||
const getTagsViewRoutes = () => {
 | 
			
		||||
    state.routePath = route.fullPath;
 | 
			
		||||
    state.tagsViewList = [];
 | 
			
		||||
    if (!themeConfig.value.isCacheTagsView) removeSession('tagsViewList');
 | 
			
		||||
    tagsViews.value = [];
 | 
			
		||||
    if (!themeConfig.value.isCacheTagsView) {
 | 
			
		||||
        removeTagViews();
 | 
			
		||||
    }
 | 
			
		||||
    initTagsView();
 | 
			
		||||
};
 | 
			
		||||
// vuex 中获取路由信息:如果是设置了固定的(isAffix),进行初始化显示
 | 
			
		||||
// 获取路由信息:如果是设置了固定的(isAffix),进行初始化显示
 | 
			
		||||
const initTagsView = () => {
 | 
			
		||||
    if (getSession('tagsViewList') && themeConfig.value.isCacheTagsView) {
 | 
			
		||||
        state.tagsViewList = getSession('tagsViewList');
 | 
			
		||||
    const tagViews = getTagViews();
 | 
			
		||||
    if (tagViews && themeConfig.value.isCacheTagsView) {
 | 
			
		||||
        tagsViews.value = tagViews;
 | 
			
		||||
    } else {
 | 
			
		||||
        state.tagsViewList?.map((v: any) => {
 | 
			
		||||
            if (v.meta.isAffix && !v.meta.isHide) state.tagsViewList.push({ ...v });
 | 
			
		||||
        tagsViews.value?.map((v: any) => {
 | 
			
		||||
            if (v.isAffix && !v.isHide) {
 | 
			
		||||
                tagsViews.value.push({ ...v });
 | 
			
		||||
                keepAliveNamesStores.setCacheKeepAlive(v);
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
        addTagsView(route.fullPath);
 | 
			
		||||
    }
 | 
			
		||||
    // 初始化当前元素(li)的下标
 | 
			
		||||
    getTagsRefsIndex(route.fullPath);
 | 
			
		||||
    setTagsRefsIndex(route.fullPath);
 | 
			
		||||
    // 添加初始化横向滚动条移动到对应位置
 | 
			
		||||
    tagsViewmoveToCurrentTag();
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// 1、添加 tagsView:未设置隐藏(isHide)也添加到在 tagsView 中
 | 
			
		||||
// path为fullPath
 | 
			
		||||
const addTagsView = (path: string, to: any = null) => {
 | 
			
		||||
    if (!to) {
 | 
			
		||||
        to = route;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    path = decodeURI(path);
 | 
			
		||||
    for (let tv of state.tagsViewList) {
 | 
			
		||||
        if (tv.fullPath === path) {
 | 
			
		||||
            return false;
 | 
			
		||||
const addTagsView = (path: string, to: any = null, tagViewIndex: number = -1) => {
 | 
			
		||||
    nextTick(async () => {
 | 
			
		||||
        if (!to) {
 | 
			
		||||
            to = route;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const tagView = { ...to };
 | 
			
		||||
    // 防止Converting circular structure to JSON错误
 | 
			
		||||
    tagView.matched = null;
 | 
			
		||||
    tagView.redirectedFrom = null;
 | 
			
		||||
    state.tagsViewList.push(tagView);
 | 
			
		||||
    addBrowserSetSession(state.tagsViewList);
 | 
			
		||||
        for (let tv of tagsViews.value) {
 | 
			
		||||
            if (tv.path === path) {
 | 
			
		||||
                return false;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        const tagView = {
 | 
			
		||||
            path: path,
 | 
			
		||||
            name: to.name,
 | 
			
		||||
            query: to.query,
 | 
			
		||||
            title: to.meta.title,
 | 
			
		||||
            icon: to.meta.icon,
 | 
			
		||||
            isAffix: to.meta.isAffix,
 | 
			
		||||
            isKeepAlive: to.meta.isKeepAlive,
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        if (tagViewIndex != -1) {
 | 
			
		||||
            tagsViews.value.splice(tagViewIndex + 1, 0, tagView);
 | 
			
		||||
        } else {
 | 
			
		||||
            tagsViews.value.push(tagView);
 | 
			
		||||
        }
 | 
			
		||||
        await keepAliveNamesStores.addCachedView(tagView);
 | 
			
		||||
        addBrowserSetSession(tagsViews.value);
 | 
			
		||||
    });
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// 2、刷新当前 tagsView:
 | 
			
		||||
// path为fullPath
 | 
			
		||||
const refreshCurrentTagsView = (path: string) => {
 | 
			
		||||
const refreshCurrentTagsView = async (path: string) => {
 | 
			
		||||
    const item = getTagsView(path);
 | 
			
		||||
    await keepAliveNamesStores.delCachedView(item);
 | 
			
		||||
    keepAliveNamesStores.addCachedView(item);
 | 
			
		||||
    mittBus.emit('onTagsViewRefreshRouterView', path);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const getTagsView = (path: string) => {
 | 
			
		||||
    return tagsViews.value.find((v: any) => v.path === path);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// 3、关闭当前 tagsView:如果是设置了固定的(isAffix),不可以关闭
 | 
			
		||||
// path为fullPath
 | 
			
		||||
const closeCurrentTagsView = (path: string) => {
 | 
			
		||||
    state.tagsViewList.map((v: any, k: number, arr: any) => {
 | 
			
		||||
        if (!v.meta.isAffix) {
 | 
			
		||||
            if (v.fullPath === path) {
 | 
			
		||||
                state.tagsViewList.splice(k, 1);
 | 
			
		||||
    tagsViews.value.map((v: TagsView, k: number, arr: any) => {
 | 
			
		||||
        if (!v.isAffix) {
 | 
			
		||||
            if (v.path === path) {
 | 
			
		||||
                keepAliveNamesStores.delCachedView(v);
 | 
			
		||||
                tagsViews.value.splice(k, 1);
 | 
			
		||||
                setTimeout(() => {
 | 
			
		||||
                    if (state.routePath !== path) {
 | 
			
		||||
                        return;
 | 
			
		||||
                    }
 | 
			
		||||
                    let next;
 | 
			
		||||
                    let next: TagsView;
 | 
			
		||||
                    // 最后一个且高亮时
 | 
			
		||||
                    if (state.tagsViewList.length === k) {
 | 
			
		||||
                    if (tagsViews.value.length === k) {
 | 
			
		||||
                        next = k !== arr.length ? arr[k] : arr[arr.length - 1];
 | 
			
		||||
                    } else {
 | 
			
		||||
                        next = arr[k];
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    if (next.meta.isDynamic) {
 | 
			
		||||
                        router.push({ name: next.name, params: next.params });
 | 
			
		||||
                    } else {
 | 
			
		||||
                    if (next) {
 | 
			
		||||
                        router.push({ path: next.path, query: next.query });
 | 
			
		||||
                    } else {
 | 
			
		||||
                        router.push({ path: '/' });
 | 
			
		||||
                    }
 | 
			
		||||
                }, 0);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    });
 | 
			
		||||
    addBrowserSetSession(state.tagsViewList);
 | 
			
		||||
    addBrowserSetSession(tagsViews.value);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// 4、关闭其它 tagsView:如果是设置了固定的(isAffix),不进行关闭
 | 
			
		||||
const closeOtherTagsView = (path: string) => {
 | 
			
		||||
    const oldTagViews = state.tagsViewList;
 | 
			
		||||
    state.tagsViewList = [];
 | 
			
		||||
    oldTagViews.map((v: any) => {
 | 
			
		||||
        if (v.meta.isAffix && !v.meta.isHide) state.tagsViewList.push({ ...v });
 | 
			
		||||
    const oldTagViews = tagsViews.value;
 | 
			
		||||
    tagsViews.value = [];
 | 
			
		||||
    oldTagViews.map((v: TagsView) => {
 | 
			
		||||
        if (v.isAffix && !v.isHide) {
 | 
			
		||||
            keepAliveNamesStores.delOthersCachedViews(v);
 | 
			
		||||
            tagsViews.value.push({ ...v });
 | 
			
		||||
        }
 | 
			
		||||
    });
 | 
			
		||||
    addTagsView(path);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// 5、关闭全部 tagsView:如果是设置了固定的(isAffix),不进行关闭
 | 
			
		||||
const closeAllTagsView = (path: string) => {
 | 
			
		||||
    const oldTagViews = state.tagsViewList;
 | 
			
		||||
    state.tagsViewList = [];
 | 
			
		||||
    keepAliveNamesStores.delAllCachedViews();
 | 
			
		||||
    const oldTagViews = tagsViews.value;
 | 
			
		||||
    tagsViews.value = [];
 | 
			
		||||
    oldTagViews.map((v: any) => {
 | 
			
		||||
        if (v.meta.isAffix && !v.meta.isHide) {
 | 
			
		||||
            state.tagsViewList.push({ ...v });
 | 
			
		||||
            if (state.tagsViewList.some((v: any) => v.path === path)) router.push({ path, query: route.query });
 | 
			
		||||
            else router.push({ path: v.path, query: route.query });
 | 
			
		||||
        if (v.isAffix && !v.isHide) {
 | 
			
		||||
            tagsViews.value.push({ ...v });
 | 
			
		||||
            if (tagsViews.value.some((v: any) => v.path === path)) {
 | 
			
		||||
                router.push({ path, query: route.query });
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    });
 | 
			
		||||
    addBrowserSetSession(state.tagsViewList);
 | 
			
		||||
    if (tagsViews.value) {
 | 
			
		||||
        router.push({ path: '/' });
 | 
			
		||||
    }
 | 
			
		||||
    addBrowserSetSession(tagsViews.value);
 | 
			
		||||
};
 | 
			
		||||
// 6、开启当前页面全屏
 | 
			
		||||
const openCurrenFullscreen = (path: string) => {
 | 
			
		||||
    const item = state.tagsViewList.find((v: any) => v.fullPath === path);
 | 
			
		||||
    const item = tagsViews.value.find((v: any) => v.path === path);
 | 
			
		||||
    nextTick(() => {
 | 
			
		||||
        router.push({ path, query: item.query });
 | 
			
		||||
        router.push({ path, query: item?.query });
 | 
			
		||||
        const element = document.querySelector('.layout-main');
 | 
			
		||||
        const screenfulls: any = screenfull;
 | 
			
		||||
        screenfulls.request(element);
 | 
			
		||||
@@ -203,17 +243,17 @@ const openCurrenFullscreen = (path: string) => {
 | 
			
		||||
const onCurrentContextmenuClick = (data: any) => {
 | 
			
		||||
    // path为fullPath
 | 
			
		||||
    let { id, path } = data;
 | 
			
		||||
    let currentTag = state.tagsViewList.find((v: any) => v.fullPath === path);
 | 
			
		||||
    let currentTag = tagsViews.value.find((v: any) => v.path === path);
 | 
			
		||||
    switch (id) {
 | 
			
		||||
        case 0:
 | 
			
		||||
            refreshCurrentTagsView(path);
 | 
			
		||||
            router.push({ path, query: currentTag.query });
 | 
			
		||||
            router.push({ path, query: currentTag?.query });
 | 
			
		||||
            break;
 | 
			
		||||
        case 1:
 | 
			
		||||
            closeCurrentTagsView(path);
 | 
			
		||||
            break;
 | 
			
		||||
        case 2:
 | 
			
		||||
            router.push({ path, query: currentTag.query });
 | 
			
		||||
            router.push({ path, query: currentTag?.query });
 | 
			
		||||
            closeOtherTagsView(path);
 | 
			
		||||
            break;
 | 
			
		||||
        case 3:
 | 
			
		||||
@@ -225,8 +265,8 @@ const onCurrentContextmenuClick = (data: any) => {
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
// 判断页面高亮
 | 
			
		||||
const isActive = (route: any) => {
 | 
			
		||||
    return route.fullPath === state.routePath;
 | 
			
		||||
const isActive = (tagView: TagsView) => {
 | 
			
		||||
    return tagView.path === state.routePath;
 | 
			
		||||
};
 | 
			
		||||
// 右键点击时:传 x,y 坐标值到子组件中(props)
 | 
			
		||||
const onContextmenu = (v: any, e: any) => {
 | 
			
		||||
@@ -237,7 +277,7 @@ const onContextmenu = (v: any, e: any) => {
 | 
			
		||||
};
 | 
			
		||||
// 当前的 tagsView 项点击时
 | 
			
		||||
const onTagsClick = (v: any, k: number) => {
 | 
			
		||||
    state.routePath = decodeURI(v.fullPath);
 | 
			
		||||
    state.routePath = decodeURI(v.path);
 | 
			
		||||
    state.tagsRefsIndex = k;
 | 
			
		||||
    router.push(v);
 | 
			
		||||
};
 | 
			
		||||
@@ -302,9 +342,9 @@ const tagsViewmoveToCurrentTag = () => {
 | 
			
		||||
    });
 | 
			
		||||
};
 | 
			
		||||
// 获取 tagsView 的下标:用于处理 tagsView 点击时的横向滚动
 | 
			
		||||
const getTagsRefsIndex = (path: string) => {
 | 
			
		||||
    if (state.tagsViewList.length > 0) {
 | 
			
		||||
        state.tagsRefsIndex = state.tagsViewList.findIndex((item: any) => item.fullPath === path);
 | 
			
		||||
const setTagsRefsIndex = (path: string) => {
 | 
			
		||||
    if (tagsViews.value.length > 0) {
 | 
			
		||||
        state.tagsRefsIndex = tagsViews.value.findIndex((item: any) => item.path === path);
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
// 设置 tagsView 可以进行拖拽
 | 
			
		||||
@@ -319,7 +359,7 @@ const initSortable = () => {
 | 
			
		||||
            onEnd: () => {
 | 
			
		||||
                const sortEndList: any = [];
 | 
			
		||||
                state.sortable.toArray().map((val: any) => {
 | 
			
		||||
                    state.tagsViewList.map((v: any) => {
 | 
			
		||||
                    tagsViews.value.map((v: any) => {
 | 
			
		||||
                        if (v.name === val) sortEndList.push({ ...v });
 | 
			
		||||
                    });
 | 
			
		||||
                });
 | 
			
		||||
@@ -329,18 +369,6 @@ const initSortable = () => {
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// 监听路由的变化,动态赋值给 tagsView
 | 
			
		||||
// watch(
 | 
			
		||||
// 	pinia.state,
 | 
			
		||||
// 	(val) => {
 | 
			
		||||
// 		if (val.tagsViewRoutes.tagsViewRoutes.length === state.tagsViewRoutesList.length) return false;
 | 
			
		||||
// 		getTagsViewRoutes();
 | 
			
		||||
// 	},
 | 
			
		||||
// 	{
 | 
			
		||||
// 		deep: true,
 | 
			
		||||
// 	}
 | 
			
		||||
// );
 | 
			
		||||
 | 
			
		||||
// 页面加载前
 | 
			
		||||
onBeforeMount(() => {
 | 
			
		||||
    // 监听非本页面调用 0 刷新当前,1 关闭当前,2 关闭其它,3 关闭全部 4 当前页全屏
 | 
			
		||||
@@ -371,9 +399,10 @@ onMounted(() => {
 | 
			
		||||
});
 | 
			
		||||
// 路由更新时
 | 
			
		||||
onBeforeRouteUpdate((to) => {
 | 
			
		||||
    state.routePath = decodeURI(to.fullPath);
 | 
			
		||||
    addTagsView(to.fullPath, to);
 | 
			
		||||
    getTagsRefsIndex(to.fullPath);
 | 
			
		||||
    const path = decodeURI(to.fullPath);
 | 
			
		||||
    state.routePath = path;
 | 
			
		||||
    addTagsView(path, to, state.tagsRefsIndex);
 | 
			
		||||
    setTagsRefsIndex(path);
 | 
			
		||||
    tagsViewmoveToCurrentTag();
 | 
			
		||||
});
 | 
			
		||||
</script>
 | 
			
		||||
@@ -1,24 +1,23 @@
 | 
			
		||||
<template>
 | 
			
		||||
    <div class="el-menu-horizontal-warp">
 | 
			
		||||
        <el-scrollbar @wheel.prevent="onElMenuHorizontalScroll" ref="elMenuHorizontalScrollRef">
 | 
			
		||||
            <el-menu router :default-active="state.defaultActive" background-color="transparent" mode="horizontal"
 | 
			
		||||
                @select="onHorizontalSelect">
 | 
			
		||||
            <el-menu router :default-active="state.defaultActive" background-color="transparent" mode="horizontal" @select="onHorizontalSelect">
 | 
			
		||||
                <template v-for="val in menuLists">
 | 
			
		||||
                    <el-sub-menu :index="val.path" v-if="val.children && val.children.length > 0" :key="val.path">
 | 
			
		||||
                        <template #title>
 | 
			
		||||
                            <SvgIcon :name="val.meta.icon"/>
 | 
			
		||||
                            <SvgIcon :name="val.meta.icon" />
 | 
			
		||||
                            <span>{{ val.meta.title }}</span>
 | 
			
		||||
                        </template>
 | 
			
		||||
                        <SubItem :chil="val.children" />
 | 
			
		||||
                    </el-sub-menu>
 | 
			
		||||
                    <el-menu-item :index="val.path" :key="val?.path" v-else>
 | 
			
		||||
                        <template #title v-if="!val.meta.link || (val.meta.link && val.meta.linkType == 1)">
 | 
			
		||||
                            <SvgIcon :name="val.meta.icon"/>
 | 
			
		||||
                            <SvgIcon :name="val.meta.icon" />
 | 
			
		||||
                            {{ val.meta.title }}
 | 
			
		||||
                        </template>
 | 
			
		||||
                        <template #title v-else>
 | 
			
		||||
                            <a :href="val.meta.link" target="_blank">
 | 
			
		||||
                                <SvgIcon :name="val.meta.icon"/>
 | 
			
		||||
                                <SvgIcon :name="val.meta.icon" />
 | 
			
		||||
                                {{ val.meta.title }}
 | 
			
		||||
                            </a>
 | 
			
		||||
                        </template>
 | 
			
		||||
@@ -32,7 +31,7 @@
 | 
			
		||||
<script lang="ts" setup name="navMenuHorizontal">
 | 
			
		||||
import { reactive, computed, getCurrentInstance, onMounted, nextTick } from 'vue';
 | 
			
		||||
import { useRoute, onBeforeRouteUpdate } from 'vue-router';
 | 
			
		||||
import SubItem from '@/views/layout/navMenu/subItem.vue';
 | 
			
		||||
import SubItem from '@/layout/navMenu/subItem.vue';
 | 
			
		||||
import { useRoutesList } from '@/store/routesList';
 | 
			
		||||
import { useThemeConfig } from '@/store/themeConfig';
 | 
			
		||||
import mittBus from '@/common/utils/mitt';
 | 
			
		||||
@@ -1,21 +1,28 @@
 | 
			
		||||
<template>
 | 
			
		||||
    <el-menu router :default-active="state.defaultActive" background-color="transparent" :collapse="setIsCollapse"
 | 
			
		||||
        :unique-opened="themeConfig.isUniqueOpened" :collapse-transition="false">
 | 
			
		||||
    <el-menu
 | 
			
		||||
        router
 | 
			
		||||
        :default-active="state.defaultActive"
 | 
			
		||||
        background-color="transparent"
 | 
			
		||||
        :collapse="setIsCollapse"
 | 
			
		||||
        :unique-opened="themeConfig.isUniqueOpened"
 | 
			
		||||
        :collapse-transition="false"
 | 
			
		||||
    >
 | 
			
		||||
        <template v-for="val in menuLists">
 | 
			
		||||
            <el-sub-menu :index="val.path" v-if="val.children && val.children.length > 0" :key="val.path">
 | 
			
		||||
                <template #title>
 | 
			
		||||
                    <SvgIcon :name="val.meta.icon"/>
 | 
			
		||||
                    <SvgIcon :name="val.meta.icon" />
 | 
			
		||||
                    <span>{{ val.meta.title }}</span>
 | 
			
		||||
                </template>
 | 
			
		||||
                <SubItem :chil="val.children" />
 | 
			
		||||
            </el-sub-menu>
 | 
			
		||||
            <el-menu-item :index="val.path" :key="val?.path" v-else>
 | 
			
		||||
                <SvgIcon :name="val.meta.icon"/>
 | 
			
		||||
                <SvgIcon :name="val.meta.icon" />
 | 
			
		||||
                <template #title v-if="!val.meta.link || (val.meta.link && val.meta.linkType == 1)">
 | 
			
		||||
                    <span>{{ val.meta.title }}</span>
 | 
			
		||||
                </template>
 | 
			
		||||
                <template #title v-else>
 | 
			
		||||
                    <a :href="val.meta.link" target="_blank">{{ val.meta.title }}</a></template>
 | 
			
		||||
                    <a :href="val.meta.link" target="_blank">{{ val.meta.title }}</a></template
 | 
			
		||||
                >
 | 
			
		||||
            </el-menu-item>
 | 
			
		||||
        </template>
 | 
			
		||||
    </el-menu>
 | 
			
		||||
@@ -26,7 +33,7 @@ import { reactive, computed } from 'vue';
 | 
			
		||||
import { storeToRefs } from 'pinia';
 | 
			
		||||
import { useThemeConfig } from '@/store/themeConfig';
 | 
			
		||||
import { useRoute, onBeforeRouteUpdate } from 'vue-router';
 | 
			
		||||
import SubItem from '@/views/layout/navMenu/subItem.vue';
 | 
			
		||||
import SubItem from '@/layout/navMenu/subItem.vue';
 | 
			
		||||
import mittBus from '@/common/utils/mitt';
 | 
			
		||||
 | 
			
		||||
// 定义父组件传过来的值
 | 
			
		||||
@@ -2,7 +2,7 @@
 | 
			
		||||
    <div class="h100">
 | 
			
		||||
        <router-view v-slot="{ Component }">
 | 
			
		||||
            <transition :name="setTransitionName" mode="out-in">
 | 
			
		||||
                <keep-alive :include="state.keepAliveNameList">
 | 
			
		||||
                <keep-alive :include="getKeepAliveNames">
 | 
			
		||||
                    <component :is="Component" :key="state.refreshRouterViewKey" class="w100" />
 | 
			
		||||
                </keep-alive>
 | 
			
		||||
            </transition>
 | 
			
		||||
@@ -11,38 +11,62 @@
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script lang="ts" setup name="layoutParentView">
 | 
			
		||||
import { computed, reactive, onBeforeMount, onUnmounted, nextTick } from 'vue';
 | 
			
		||||
import { computed, watch, reactive, onBeforeMount, onMounted, onUnmounted, nextTick } from 'vue';
 | 
			
		||||
import { useRoute } from 'vue-router';
 | 
			
		||||
import { storeToRefs } from 'pinia';
 | 
			
		||||
import { useThemeConfig } from '@/store/themeConfig';
 | 
			
		||||
import { useKeepALiveNames } from '@/store/keepAliveNames';
 | 
			
		||||
import mittBus from '@/common/utils/mitt';
 | 
			
		||||
import { getTagViews } from '@/common/utils/storage';
 | 
			
		||||
 | 
			
		||||
const route = useRoute();
 | 
			
		||||
const { themeConfig } = storeToRefs(useThemeConfig());
 | 
			
		||||
const { keepAliveNames } = storeToRefs(useKeepALiveNames());
 | 
			
		||||
const { keepAliveNames, cachedViews } = storeToRefs(useKeepALiveNames());
 | 
			
		||||
 | 
			
		||||
const state: any = reactive({
 | 
			
		||||
    refreshRouterViewKey: null,
 | 
			
		||||
    keepAliveNameList: [],
 | 
			
		||||
    keepAliveNameNewList: [],
 | 
			
		||||
});
 | 
			
		||||
// 监听路由的变化,动态赋值给refreshRouterViewKey
 | 
			
		||||
// onBeforeRouteUpdate((to: any) => {
 | 
			
		||||
// 	state.refreshRouterViewKey = decodeURI(to.fullPath);
 | 
			
		||||
// });
 | 
			
		||||
 | 
			
		||||
// 获取组件缓存列表(name值)
 | 
			
		||||
const getKeepAliveNames = computed(() => {
 | 
			
		||||
    return themeConfig.value.isTagsview ? cachedViews.value : state.keepAliveNameList;
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
// 页面加载前,处理缓存,页面刷新时路由缓存处理
 | 
			
		||||
onBeforeMount(() => {
 | 
			
		||||
    state.keepAliveNameList = keepAliveNames.value;
 | 
			
		||||
    mittBus.on('onTagsViewRefreshRouterView', (path: string) => {
 | 
			
		||||
        if (decodeURI(route.fullPath) !== path) return false;
 | 
			
		||||
        state.keepAliveNameList = keepAliveNames.value.filter((name: string) => route.name !== name);
 | 
			
		||||
        state.refreshRouterViewKey = route.path;
 | 
			
		||||
        state.refreshRouterViewKey = '';
 | 
			
		||||
        nextTick(() => {
 | 
			
		||||
            state.refreshRouterViewKey = null;
 | 
			
		||||
            state.refreshRouterViewKey = path;
 | 
			
		||||
            state.keepAliveNameList = keepAliveNames.value;
 | 
			
		||||
        });
 | 
			
		||||
    });
 | 
			
		||||
});
 | 
			
		||||
// 页面加载时
 | 
			
		||||
onMounted(() => {
 | 
			
		||||
    nextTick(() => {
 | 
			
		||||
        setTimeout(() => {
 | 
			
		||||
            if (themeConfig.value.isCacheTagsView) {
 | 
			
		||||
                let tagsViewArr: any = getTagViews() || [];
 | 
			
		||||
                cachedViews.value = tagsViewArr.filter((item: any) => item?.isKeepAlive).map((item: any) => item.name as string);
 | 
			
		||||
            }
 | 
			
		||||
        }, 0);
 | 
			
		||||
    });
 | 
			
		||||
});
 | 
			
		||||
// 监听路由变化,防止 tagsView 多标签时,切换动画消失
 | 
			
		||||
watch(
 | 
			
		||||
    () => route.fullPath,
 | 
			
		||||
    () => {
 | 
			
		||||
        state.refreshRouterViewKey = decodeURI(route.fullPath);
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
        immediate: true,
 | 
			
		||||
    }
 | 
			
		||||
);
 | 
			
		||||
// 设置主界面切换动画
 | 
			
		||||
const setTransitionName = computed(() => {
 | 
			
		||||
    return themeConfig.value.animation;
 | 
			
		||||
@@ -1,7 +1,7 @@
 | 
			
		||||
import { createRouter, createWebHashHistory, RouteRecordRaw } from 'vue-router';
 | 
			
		||||
import NProgress from 'nprogress';
 | 
			
		||||
import 'nprogress/nprogress.css';
 | 
			
		||||
import { getSession, clearSession } from '@/common/utils/storage';
 | 
			
		||||
import { clearSession, getToken } from '@/common/utils/storage';
 | 
			
		||||
import { templateResolve } from '@/common/utils/string';
 | 
			
		||||
import { NextLoading } from '@/common/utils/loading';
 | 
			
		||||
import { dynamicRoutes, staticRoutes, pathMatch } from './route';
 | 
			
		||||
@@ -18,7 +18,7 @@ import { useKeepALiveNames } from '@/store/keepAliveNames';
 | 
			
		||||
 * @method import.meta.glob
 | 
			
		||||
 * @link 参考:https://cn.vitejs.dev/guide/features.html#json
 | 
			
		||||
 */
 | 
			
		||||
const viewsModules: any = import.meta.glob(['../views/**/*.{vue,tsx}', '!../views/layout/**/*.{vue,tsx}']);
 | 
			
		||||
const viewsModules: any = import.meta.glob(['../views/**/*.{vue,tsx}']);
 | 
			
		||||
const dynamicViewsModules: Record<string, Function> = Object.assign({}, { ...viewsModules });
 | 
			
		||||
 | 
			
		||||
// 添加静态路由
 | 
			
		||||
@@ -29,8 +29,7 @@ const router = createRouter({
 | 
			
		||||
 | 
			
		||||
// 前端控制路由:初始化方法,防止刷新时丢失
 | 
			
		||||
export function initAllFun() {
 | 
			
		||||
    NextLoading.start(); // 界面 loading 动画开始执行
 | 
			
		||||
    const token = getSession('token'); // 获取浏览器缓存 token 值
 | 
			
		||||
    const token = getToken(); // 获取浏览器缓存 token 值
 | 
			
		||||
    if (!token) {
 | 
			
		||||
        // 无 token 停止执行下一步
 | 
			
		||||
        return false;
 | 
			
		||||
@@ -38,18 +37,15 @@ export function initAllFun() {
 | 
			
		||||
    useUserInfo().setUserInfo({});
 | 
			
		||||
    router.addRoute(pathMatch); // 添加404界面
 | 
			
		||||
    resetRoute(); // 删除/重置路由
 | 
			
		||||
    // 添加动态路由
 | 
			
		||||
    setFilterRouteEnd().forEach((route: any) => {
 | 
			
		||||
        router.addRoute(route as unknown as RouteRecordRaw);
 | 
			
		||||
    });
 | 
			
		||||
    router.addRoute(dynamicRoutes[0]);
 | 
			
		||||
 | 
			
		||||
    // 过滤权限菜单
 | 
			
		||||
    useRoutesList().setRoutesList(setFilterMenuFun(dynamicRoutes[0].children, useUserInfo().userInfo.menus));
 | 
			
		||||
    useRoutesList().setRoutesList(dynamicRoutes[0].children);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 后端控制路由:执行路由数据初始化
 | 
			
		||||
export async function initBackEndControlRoutesFun() {
 | 
			
		||||
    NextLoading.start(); // 界面 loading 动画开始执行
 | 
			
		||||
    const token = getSession('token'); // 获取浏览器缓存 token 值
 | 
			
		||||
    const token = getToken(); // 获取浏览器缓存 token 值
 | 
			
		||||
    if (!token) {
 | 
			
		||||
        // 无 token 停止执行下一步
 | 
			
		||||
        return false;
 | 
			
		||||
@@ -57,14 +53,25 @@ export async function initBackEndControlRoutesFun() {
 | 
			
		||||
    useUserInfo().setUserInfo({});
 | 
			
		||||
    // 获取路由
 | 
			
		||||
    let menuRoute = await getBackEndControlRoutes();
 | 
			
		||||
    dynamicRoutes[0].children = backEndRouterConverter(menuRoute); // 处理路由(component)
 | 
			
		||||
 | 
			
		||||
    const cacheList: Array<string> = [];
 | 
			
		||||
    // 处理路由(component)
 | 
			
		||||
    dynamicRoutes[0].children = backEndRouterConverter(menuRoute, (router: any) => {
 | 
			
		||||
        // 可能为false时不存在isKeepAlive属性
 | 
			
		||||
        if (!router.meta.isKeepAlive) {
 | 
			
		||||
            router.meta.isKeepAlive = false;
 | 
			
		||||
        }
 | 
			
		||||
        if (router.meta.isKeepAlive) {
 | 
			
		||||
            cacheList.push(router.name);
 | 
			
		||||
        }
 | 
			
		||||
    });
 | 
			
		||||
    useKeepALiveNames().setCacheKeepAlive(cacheList);
 | 
			
		||||
 | 
			
		||||
    // 添加404界面
 | 
			
		||||
    router.addRoute(pathMatch);
 | 
			
		||||
    resetRoute(); // 删除/重置路由
 | 
			
		||||
    // 添加动态路由
 | 
			
		||||
    formatTwoStageRoutes(formatFlatteningRoutes(dynamicRoutes)).forEach((route: any) => {
 | 
			
		||||
        router.addRoute(route as unknown as RouteRecordRaw);
 | 
			
		||||
    });
 | 
			
		||||
    router.addRoute(dynamicRoutes[0] as unknown as RouteRecordRaw);
 | 
			
		||||
 | 
			
		||||
    useRoutesList().setRoutesList(dynamicRoutes[0].children);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -81,8 +88,10 @@ export async function getBackEndControlRoutes() {
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type RouterConvCallbackFunc = (router: any) => void;
 | 
			
		||||
 | 
			
		||||
// 后端控制路由,后端返回路由 转换为vue route
 | 
			
		||||
export function backEndRouterConverter(routes: any, parentPath: string = '/') {
 | 
			
		||||
export function backEndRouterConverter(routes: any, callbackFunc: RouterConvCallbackFunc = null as any, parentPath: string = '/') {
 | 
			
		||||
    if (!routes) return;
 | 
			
		||||
    return routes.map((item: any) => {
 | 
			
		||||
        if (!item.meta) {
 | 
			
		||||
@@ -117,7 +126,9 @@ export function backEndRouterConverter(routes: any, parentPath: string = '/') {
 | 
			
		||||
            item.redirect = item.meta.redirect;
 | 
			
		||||
            delete item.meta['redirect'];
 | 
			
		||||
        }
 | 
			
		||||
        item.children && backEndRouterConverter(item.children, item.path);
 | 
			
		||||
        // 存在回调,则执行回调
 | 
			
		||||
        callbackFunc && callbackFunc(item);
 | 
			
		||||
        item.children && backEndRouterConverter(item.children, callbackFunc, item.path);
 | 
			
		||||
        return item;
 | 
			
		||||
    });
 | 
			
		||||
}
 | 
			
		||||
@@ -143,86 +154,6 @@ export function dynamicImport(dynamicViewsModules: Record<string, Function>, com
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 多级嵌套数组处理成一维数组
 | 
			
		||||
export function formatFlatteningRoutes(arr: any) {
 | 
			
		||||
    if (arr.length <= 0) return false;
 | 
			
		||||
    for (let i = 0; i < arr.length; i++) {
 | 
			
		||||
        if (arr[i].children) {
 | 
			
		||||
            arr = arr.slice(0, i + 1).concat(arr[i].children, arr.slice(i + 1));
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    return arr;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 多级嵌套数组处理后的一维数组,再处理成 `定义动态路由` 的格式
 | 
			
		||||
// 只保留二级:也就是二级以上全部处理成只有二级,keep-alive 支持二级缓存
 | 
			
		||||
// isKeepAlive 处理 `name` 值,进行缓存。顶级关闭,全部不缓存
 | 
			
		||||
export function formatTwoStageRoutes(arr: any) {
 | 
			
		||||
    if (arr.length <= 0) return false;
 | 
			
		||||
    const newArr: any = [];
 | 
			
		||||
    const cacheList: Array<string> = [];
 | 
			
		||||
    arr.forEach((v: any) => {
 | 
			
		||||
        if (v.path === '/') {
 | 
			
		||||
            newArr.push({ component: v.component, name: v.name, path: v.path, redirect: v.redirect, meta: v.meta, children: [] });
 | 
			
		||||
        } else {
 | 
			
		||||
            newArr[0].children.push({ ...v });
 | 
			
		||||
            if (newArr[0].meta.isKeepAlive && v.meta.isKeepAlive) {
 | 
			
		||||
                cacheList.push(v.name);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    });
 | 
			
		||||
    useKeepALiveNames().setCacheKeepAlive(cacheList);
 | 
			
		||||
    return newArr;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 判断路由code 是否包含当前登录用户menus字段中,menus为字符串code数组
 | 
			
		||||
export function hasAnth(menus: any, route: any) {
 | 
			
		||||
    if (route.meta && route.meta.code) {
 | 
			
		||||
        return menus.includes(route.meta.code);
 | 
			
		||||
    }
 | 
			
		||||
    return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 递归过滤有权限的路由
 | 
			
		||||
export function setFilterMenuFun(routes: any, menus: any) {
 | 
			
		||||
    const menu: any = [];
 | 
			
		||||
    routes.forEach((route: any) => {
 | 
			
		||||
        const item = { ...route };
 | 
			
		||||
        if (hasAnth(menus, item)) {
 | 
			
		||||
            if (item.children) {
 | 
			
		||||
                item.children = setFilterMenuFun(item.children, menus);
 | 
			
		||||
            }
 | 
			
		||||
            menu.push(item);
 | 
			
		||||
        }
 | 
			
		||||
    });
 | 
			
		||||
    return menu;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 获取当前用户的权限去比对路由表,用于动态路由的添加
 | 
			
		||||
export function setFilterRoute(chil: any) {
 | 
			
		||||
    let filterRoute: any = [];
 | 
			
		||||
    chil.forEach((route: any) => {
 | 
			
		||||
        // 如果路由需要拥有指定code才可访问,则校验该用户菜单是否存在该code
 | 
			
		||||
        if (route.meta.code) {
 | 
			
		||||
            useUserInfo().userInfo.menus.forEach((m: any) => {
 | 
			
		||||
                if (route.meta.code == m) {
 | 
			
		||||
                    filterRoute.push({ ...route });
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
        } else {
 | 
			
		||||
            filterRoute.push({ ...route });
 | 
			
		||||
        }
 | 
			
		||||
    });
 | 
			
		||||
    return filterRoute;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 比对后的路由表,进行重新赋值
 | 
			
		||||
export function setFilterRouteEnd() {
 | 
			
		||||
    let filterRouteEnd: any = formatTwoStageRoutes(formatFlatteningRoutes(dynamicRoutes));
 | 
			
		||||
    filterRouteEnd[0].children = setFilterRoute(filterRouteEnd[0].children);
 | 
			
		||||
    return filterRouteEnd;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 删除/重置路由
 | 
			
		||||
export function resetRoute() {
 | 
			
		||||
    useRoutesList().routesList.forEach((route: any) => {
 | 
			
		||||
@@ -232,14 +163,19 @@ export function resetRoute() {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export async function initRouter() {
 | 
			
		||||
    // 初始化方法执行
 | 
			
		||||
    const { isRequestRoutes } = useThemeConfig(pinia).themeConfig;
 | 
			
		||||
    if (!isRequestRoutes) {
 | 
			
		||||
        // 未开启后端控制路由
 | 
			
		||||
        initAllFun();
 | 
			
		||||
    } else if (isRequestRoutes) {
 | 
			
		||||
        // 后端控制路由,isRequestRoutes 为 true,则开启后端控制路由
 | 
			
		||||
        await initBackEndControlRoutesFun();
 | 
			
		||||
    NextLoading.start(); // 界面 loading 动画开始执行
 | 
			
		||||
    try {
 | 
			
		||||
        // 初始化方法执行
 | 
			
		||||
        const { isRequestRoutes } = useThemeConfig(pinia).themeConfig;
 | 
			
		||||
        if (!isRequestRoutes) {
 | 
			
		||||
            // 未开启后端控制路由
 | 
			
		||||
            initAllFun();
 | 
			
		||||
        } else if (isRequestRoutes) {
 | 
			
		||||
            // 后端控制路由,isRequestRoutes 为 true,则开启后端控制路由
 | 
			
		||||
            await initBackEndControlRoutesFun();
 | 
			
		||||
        }
 | 
			
		||||
    } finally {
 | 
			
		||||
        NextLoading.done();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -252,11 +188,11 @@ router.beforeEach(async (to, from, next) => {
 | 
			
		||||
    if (to.meta.title) NProgress.start();
 | 
			
		||||
 | 
			
		||||
    // 如果有标题参数,则再原标题后加上参数来区别
 | 
			
		||||
    if (to.meta.titleRename) {
 | 
			
		||||
    if (to.meta.titleRename && to.meta.title) {
 | 
			
		||||
        to.meta.title = templateResolve(to.meta.title as string, to.query);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const token = getSession('token');
 | 
			
		||||
    const token = getToken();
 | 
			
		||||
    if ((to.path === '/login' || to.path == '/oauth2/callback') && !token) {
 | 
			
		||||
        next();
 | 
			
		||||
        NProgress.done();
 | 
			
		||||
@@ -270,7 +206,7 @@ router.beforeEach(async (to, from, next) => {
 | 
			
		||||
 | 
			
		||||
        if (SysWs) {
 | 
			
		||||
            SysWs.close();
 | 
			
		||||
            SysWs = null;
 | 
			
		||||
            SysWs = undefined;
 | 
			
		||||
        }
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
@@ -297,7 +233,6 @@ router.beforeEach(async (to, from, next) => {
 | 
			
		||||
// 路由加载后
 | 
			
		||||
router.afterEach(() => {
 | 
			
		||||
    NProgress.done();
 | 
			
		||||
    NextLoading.done();
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
// 导出路由
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,5 @@
 | 
			
		||||
import { RouteRecordRaw } from 'vue-router';
 | 
			
		||||
import Layout from '@/views/layout/index.vue';
 | 
			
		||||
import Layout from '@/layout/index.vue';
 | 
			
		||||
 | 
			
		||||
// 定义动态路由
 | 
			
		||||
export const dynamicRoutes = [
 | 
			
		||||
 
 | 
			
		||||
@@ -18,14 +18,16 @@ export const useKeepALiveNames = defineStore('keepALiveNames', {
 | 
			
		||||
            this.keepAliveNames = data;
 | 
			
		||||
        },
 | 
			
		||||
        async addCachedView(view: any) {
 | 
			
		||||
            if (view.meta.isKeepAlive) this.cachedViews?.push(view.name);
 | 
			
		||||
            if (view.isKeepAlive) {
 | 
			
		||||
                this.cachedViews?.push(view.name);
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        async delCachedView(view: any) {
 | 
			
		||||
            const index = this.cachedViews.indexOf(view.name);
 | 
			
		||||
            index > -1 && this.cachedViews.splice(index, 1);
 | 
			
		||||
        },
 | 
			
		||||
        async delOthersCachedViews(view: any) {
 | 
			
		||||
            if (view.meta.isKeepAlive) this.cachedViews = [view.name];
 | 
			
		||||
            if (view.isKeepAlive) this.cachedViews = [view.name];
 | 
			
		||||
            else this.cachedViews = [];
 | 
			
		||||
        },
 | 
			
		||||
        async delAllCachedViews() {
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										25
									
								
								mayfly_go_web/src/store/tagsViews.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,25 @@
 | 
			
		||||
import { getNowUrl } from '@/common/utils/url';
 | 
			
		||||
import { defineStore } from 'pinia';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * tags view
 | 
			
		||||
 */
 | 
			
		||||
export const useTagsViews = defineStore('tagsViews', {
 | 
			
		||||
    state: (): TagsViewsState => ({
 | 
			
		||||
        tagsViews: [],
 | 
			
		||||
    }),
 | 
			
		||||
    actions: {
 | 
			
		||||
        setTagsViews(data: Array<TagsView>) {
 | 
			
		||||
            this.tagsViews = data;
 | 
			
		||||
        },
 | 
			
		||||
        // 设置当前页面的tags view title
 | 
			
		||||
        setNowTitle(title: string) {
 | 
			
		||||
            this.tagsViews.forEach((item) => {
 | 
			
		||||
                // console.log(getNowUrl(), item.path);
 | 
			
		||||
                if (item.path == getNowUrl()) {
 | 
			
		||||
                    item.title = title;
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
        },
 | 
			
		||||
    },
 | 
			
		||||
});
 | 
			
		||||
@@ -1,4 +1,6 @@
 | 
			
		||||
import { defineStore } from 'pinia';
 | 
			
		||||
import { dateFormat2 } from '@/common/utils/date';
 | 
			
		||||
import { useUserInfo } from '@/store/userInfo';
 | 
			
		||||
 | 
			
		||||
export const useThemeConfig = defineStore('themeConfig', {
 | 
			
		||||
    state: (): ThemeConfigState => ({
 | 
			
		||||
@@ -88,9 +90,9 @@ export const useThemeConfig = defineStore('themeConfig', {
 | 
			
		||||
            // 是否开启色弱模式
 | 
			
		||||
            isInvert: false,
 | 
			
		||||
            // 是否开启水印
 | 
			
		||||
            isWartermark: false,
 | 
			
		||||
            // 水印文案
 | 
			
		||||
            wartermarkText: 'mayfly',
 | 
			
		||||
            isWatermark: false,
 | 
			
		||||
            // 水印文案数组,0->用户信息  1->当前时间 2->额外信息
 | 
			
		||||
            watermarkText: ['', '', ''],
 | 
			
		||||
 | 
			
		||||
            /* 其它设置
 | 
			
		||||
            ------------------------------- */
 | 
			
		||||
@@ -140,5 +142,40 @@ 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 = 'SolarizedLight';
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        // 设置水印配置信息
 | 
			
		||||
        setWatermarkConfig(useWatermarkConfig: any) {
 | 
			
		||||
            this.themeConfig.watermarkText = [];
 | 
			
		||||
            this.themeConfig.isWatermark = useWatermarkConfig.isUse;
 | 
			
		||||
            if (!useWatermarkConfig.isUse) {
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
            // 索引2为用户自定义水印信息
 | 
			
		||||
            this.themeConfig.watermarkText[2] = useWatermarkConfig.content;
 | 
			
		||||
        },
 | 
			
		||||
        // 设置水印用户信息
 | 
			
		||||
        setWatermarkUser(del: boolean = false) {
 | 
			
		||||
            const userinfo = useUserInfo().userInfo;
 | 
			
		||||
            let desc = '';
 | 
			
		||||
            if (!del && userinfo && userinfo.username) {
 | 
			
		||||
                desc = `${userinfo.username}(${userinfo.name})`;
 | 
			
		||||
            }
 | 
			
		||||
            this.themeConfig.watermarkText[0] = desc;
 | 
			
		||||
        },
 | 
			
		||||
        // 设置水印时间为当前时间
 | 
			
		||||
        setWatermarkNowTime() {
 | 
			
		||||
            this.themeConfig.watermarkText[1] = dateFormat2('yyyy-MM-dd HH:mm:ss', new Date());
 | 
			
		||||
        },
 | 
			
		||||
    },
 | 
			
		||||
});
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,5 @@
 | 
			
		||||
import { defineStore } from 'pinia';
 | 
			
		||||
import { getSession } from '@/common/utils/storage';
 | 
			
		||||
import { getUser } from '@/common/utils/storage';
 | 
			
		||||
 | 
			
		||||
export const useUserInfo = defineStore('userInfo', {
 | 
			
		||||
    state: (): UserInfoState => ({
 | 
			
		||||
@@ -8,7 +8,7 @@ export const useUserInfo = defineStore('userInfo', {
 | 
			
		||||
    actions: {
 | 
			
		||||
        // 设置用户信息
 | 
			
		||||
        async setUserInfo(data: object) {
 | 
			
		||||
            const ui = getSession('userInfo');
 | 
			
		||||
            const ui = getUser();
 | 
			
		||||
            if (ui) {
 | 
			
		||||
                this.userInfo = ui;
 | 
			
		||||
            } else {
 | 
			
		||||
 
 | 
			
		||||
@@ -83,3 +83,42 @@
 | 
			
		||||
		opacity: 1;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* 登录页动画
 | 
			
		||||
------------------------------- */
 | 
			
		||||
@keyframes loginLeft {
 | 
			
		||||
	0% {
 | 
			
		||||
		left: -100%;
 | 
			
		||||
	}
 | 
			
		||||
	50%,
 | 
			
		||||
	100% {
 | 
			
		||||
		left: 100%;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
@keyframes loginTop {
 | 
			
		||||
	0% {
 | 
			
		||||
		top: -100%;
 | 
			
		||||
	}
 | 
			
		||||
	50%,
 | 
			
		||||
	100% {
 | 
			
		||||
		top: 100%;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
@keyframes loginRight {
 | 
			
		||||
	0% {
 | 
			
		||||
		right: -100%;
 | 
			
		||||
	}
 | 
			
		||||
	50%,
 | 
			
		||||
	100% {
 | 
			
		||||
		right: 100%;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
@keyframes loginBottom {
 | 
			
		||||
	0% {
 | 
			
		||||
		bottom: -100%;
 | 
			
		||||
	}
 | 
			
		||||
	50%,
 | 
			
		||||
	100% {
 | 
			
		||||
		bottom: 100%;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
@@ -335,8 +335,14 @@
 | 
			
		||||
    /* Set padding to ensure the height is 32px */
 | 
			
		||||
  //   padding: 6px 12px;
 | 
			
		||||
    background: linear-gradient(90deg, rgb(159, 229, 151), rgb(204, 229, 129));
 | 
			
		||||
  }
 | 
			
		||||
  .el-popper.is-customized .el-popper__arrow::before {
 | 
			
		||||
}
 | 
			
		||||
.el-popper.is-customized .el-popper__arrow::before {
 | 
			
		||||
    background: linear-gradient(45deg, #b2e68d, #bce689);
 | 
			
		||||
    right: 0;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
.el-dialog {
 | 
			
		||||
    border-radius: 6px; /* 设置圆角 */
 | 
			
		||||
    box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1); /* 添加轻微阴影效果 */
 | 
			
		||||
}
 | 
			
		||||
@@ -48,4 +48,4 @@
 | 
			
		||||
	35% {
 | 
			
		||||
		transform: scale3D(0, 0, 1);
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										40
									
								
								mayfly_go_web/src/types/pinia.d.ts
									
									
									
									
										vendored
									
									
								
							
							
						
						@@ -40,8 +40,8 @@ declare interface ThemeConfigState {
 | 
			
		||||
        isDark: boolean;
 | 
			
		||||
        isGrayscale: boolean;
 | 
			
		||||
        isInvert: boolean;
 | 
			
		||||
        isWartermark: boolean;
 | 
			
		||||
        wartermarkText: string;
 | 
			
		||||
        isWatermark: boolean;
 | 
			
		||||
        watermarkText: Array<string>;
 | 
			
		||||
        tagsStyle: string;
 | 
			
		||||
        animation: string;
 | 
			
		||||
        columnsAsideStyle: string;
 | 
			
		||||
@@ -60,10 +60,40 @@ declare interface ThemeConfigState {
 | 
			
		||||
    };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
declare interface TagsView {
 | 
			
		||||
    /**
 | 
			
		||||
     * 路径
 | 
			
		||||
     */
 | 
			
		||||
    path: string;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 标题
 | 
			
		||||
     */
 | 
			
		||||
    title: string;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * router name
 | 
			
		||||
     */
 | 
			
		||||
    name: string;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * router query
 | 
			
		||||
     */
 | 
			
		||||
    query: any;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 图标
 | 
			
		||||
     */
 | 
			
		||||
    icon: string;
 | 
			
		||||
 | 
			
		||||
    isAffix: boolean;
 | 
			
		||||
    isKeepAlive: boolean;
 | 
			
		||||
    isHide?: boolean;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// TagsView 路由列表
 | 
			
		||||
declare interface TagsViewRoutesState<T = any> {
 | 
			
		||||
    tagsViewRoutes: T[];
 | 
			
		||||
    isTagsViewCurrenFull: Boolean;
 | 
			
		||||
declare interface TagsViewsState<T = any> {
 | 
			
		||||
    tagsViews: TagsView[];
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 路由列表
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										11
									
								
								mayfly_go_web/src/types/shim.d.ts
									
									
									
									
										vendored
									
									
								
							
							
						
						@@ -1,14 +1,5 @@
 | 
			
		||||
/* eslint-disable */
 | 
			
		||||
import {IDisposable} from 'monaco-editor';
 | 
			
		||||
declare global {
 | 
			
		||||
	interface Window {
 | 
			
		||||
		completionItemProvider?: IDisposable | undefined;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 申明外部 npm 插件模块
 | 
			
		||||
declare module 'sql-formatter';
 | 
			
		||||
declare module 'jsoneditor';
 | 
			
		||||
declare module 'asciinema-player';
 | 
			
		||||
declare module 'monaco-editor';
 | 
			
		||||
declare module 'vue-grid-layout';
 | 
			
		||||
declare module 'vue-grid-layout';
 | 
			
		||||
 
 | 
			
		||||
@@ -12,7 +12,7 @@
 | 
			
		||||
                </div>
 | 
			
		||||
            </div>
 | 
			
		||||
            <div class="right">
 | 
			
		||||
                <img src="@/assets/image/401.png" />
 | 
			
		||||
                <img src="@/assets/image/401.svg" />
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
@@ -20,7 +20,7 @@
 | 
			
		||||
 | 
			
		||||
<script lang="ts">
 | 
			
		||||
import { useRouter } from 'vue-router';
 | 
			
		||||
import { clearSession } from '@/common/utils/storage.ts';
 | 
			
		||||
import { clearSession } from '@/common/utils/storage';
 | 
			
		||||
export default {
 | 
			
		||||
    name: '401',
 | 
			
		||||
    setup() {
 | 
			
		||||
@@ -39,7 +39,7 @@ export default {
 | 
			
		||||
<style scoped lang="scss">
 | 
			
		||||
.error {
 | 
			
		||||
    height: 100%;
 | 
			
		||||
    background-color: white;
 | 
			
		||||
    background-color: var(--bg-main-color);
 | 
			
		||||
    display: flex;
 | 
			
		||||
    .error-flex {
 | 
			
		||||
        margin: auto;
 | 
			
		||||
@@ -64,7 +64,7 @@ export default {
 | 
			
		||||
                }
 | 
			
		||||
                .left-item-title {
 | 
			
		||||
                    font-size: 20px;
 | 
			
		||||
                    color: #333333;
 | 
			
		||||
                    // color: #333333;
 | 
			
		||||
                    margin: 15px 0 5px 0;
 | 
			
		||||
                    animation-delay: 0.1s;
 | 
			
		||||
                }
 | 
			
		||||
 
 | 
			
		||||
@@ -12,7 +12,7 @@
 | 
			
		||||
                </div>
 | 
			
		||||
            </div>
 | 
			
		||||
            <div class="right">
 | 
			
		||||
                <img src="@/assets/image/404.png" />
 | 
			
		||||
                <img src="@/assets/image/404.svg" />
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
@@ -37,7 +37,7 @@ export default {
 | 
			
		||||
<style scoped lang="scss">
 | 
			
		||||
.error {
 | 
			
		||||
    height: 100%;
 | 
			
		||||
    background-color: white;
 | 
			
		||||
    background-color: var(--bg-main-color);
 | 
			
		||||
    display: flex;
 | 
			
		||||
    .error-flex {
 | 
			
		||||
        margin: auto;
 | 
			
		||||
@@ -62,7 +62,7 @@ export default {
 | 
			
		||||
                }
 | 
			
		||||
                .left-item-title {
 | 
			
		||||
                    font-size: 20px;
 | 
			
		||||
                    color: #333333;
 | 
			
		||||
                    // color: #333333;
 | 
			
		||||
                    margin: 15px 0 5px 0;
 | 
			
		||||
                    animation-delay: 0.1s;
 | 
			
		||||
                }
 | 
			
		||||
 
 | 
			
		||||
@@ -1,138 +0,0 @@
 | 
			
		||||
<template>
 | 
			
		||||
	<transition name="el-zoom-in-center">
 | 
			
		||||
		<div
 | 
			
		||||
			aria-hidden="true"
 | 
			
		||||
			class="el-dropdown__popper el-popper is-light is-pure custom-contextmenu"
 | 
			
		||||
			role="tooltip"
 | 
			
		||||
			data-popper-placement="bottom"
 | 
			
		||||
			:style="`top: ${dropdowns.y + 5}px;left: ${dropdowns.x}px;`"
 | 
			
		||||
			:key="Math.random()"
 | 
			
		||||
			v-show="state.isShow"
 | 
			
		||||
		>
 | 
			
		||||
			<ul class="el-dropdown-menu">
 | 
			
		||||
				<template v-for="(v, k) in state.dropdownList">
 | 
			
		||||
					<li
 | 
			
		||||
						class="el-dropdown-menu__item"
 | 
			
		||||
						aria-disabled="false"
 | 
			
		||||
						tabindex="-1"
 | 
			
		||||
						:key="k"
 | 
			
		||||
						v-if="!v.affix"
 | 
			
		||||
						@click="onCurrentContextmenuClick(v.contextMenuClickId)"
 | 
			
		||||
					>
 | 
			
		||||
						<SvgIcon :name="v.icon" />
 | 
			
		||||
						<span>{{ v.txt }}</span>
 | 
			
		||||
					</li>
 | 
			
		||||
				</template>
 | 
			
		||||
			</ul>
 | 
			
		||||
			<div class="el-popper__arrow" :style="{ left: `${state.arrowLeft}px` }"></div>
 | 
			
		||||
		</div>
 | 
			
		||||
	</transition>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script setup lang="ts" name="layoutTagsViewContextmenu">
 | 
			
		||||
import { computed, reactive, onMounted, onUnmounted, watch } from 'vue';
 | 
			
		||||
 | 
			
		||||
// 定义父组件传过来的值
 | 
			
		||||
const props = defineProps({
 | 
			
		||||
	dropdown: {
 | 
			
		||||
		type: Object,
 | 
			
		||||
		default: () => {
 | 
			
		||||
			return {
 | 
			
		||||
				x: 0,
 | 
			
		||||
				y: 0,
 | 
			
		||||
			};
 | 
			
		||||
		},
 | 
			
		||||
	},
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
// 定义子组件向父组件传值/事件
 | 
			
		||||
const emit = defineEmits(['currentContextmenuClick']);
 | 
			
		||||
 | 
			
		||||
// 定义变量内容
 | 
			
		||||
const state = reactive({
 | 
			
		||||
	isShow: false,
 | 
			
		||||
	dropdownList: [
 | 
			
		||||
		{ contextMenuClickId: 0, txt: '刷新', affix: false, icon: 'RefreshRight' },
 | 
			
		||||
		{ contextMenuClickId: 1, txt: '关闭', affix: false, icon: 'Close' },
 | 
			
		||||
		{ contextMenuClickId: 2, txt: '关闭其他', affix: false, icon: 'CircleClose' },
 | 
			
		||||
		{ contextMenuClickId: 3, txt: '关闭所有', affix: false, icon: 'FolderDelete' },
 | 
			
		||||
		{
 | 
			
		||||
			contextMenuClickId: 4,
 | 
			
		||||
			txt: '当前页全屏',
 | 
			
		||||
			affix: false,
 | 
			
		||||
			icon: 'full-screen',
 | 
			
		||||
		},
 | 
			
		||||
	],
 | 
			
		||||
	item: {} as any,
 | 
			
		||||
	arrowLeft: 10,
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
// 父级传过来的坐标 x,y 值
 | 
			
		||||
const dropdowns = computed(() => {
 | 
			
		||||
	// 117 为 `Dropdown 下拉菜单` 的宽度
 | 
			
		||||
	if (props.dropdown.x + 117 > document.documentElement.clientWidth) {
 | 
			
		||||
		return {
 | 
			
		||||
			x: document.documentElement.clientWidth - 117 - 5,
 | 
			
		||||
			y: props.dropdown.y,
 | 
			
		||||
		};
 | 
			
		||||
	} else {
 | 
			
		||||
		return props.dropdown;
 | 
			
		||||
	}
 | 
			
		||||
});
 | 
			
		||||
// 当前项菜单点击
 | 
			
		||||
const onCurrentContextmenuClick = (contextMenuClickId: number) => {
 | 
			
		||||
	emit('currentContextmenuClick', { id: contextMenuClickId, path: state.item.fullPath });
 | 
			
		||||
};
 | 
			
		||||
// 打开右键菜单:判断是否固定,固定则不显示关闭按钮
 | 
			
		||||
const openContextmenu = (item: any) => {
 | 
			
		||||
	state.item = item;
 | 
			
		||||
	item.meta?.isAffix ? (state.dropdownList[1].affix = true) : (state.dropdownList[1].affix = false);
 | 
			
		||||
	closeContextmenu();
 | 
			
		||||
	setTimeout(() => {
 | 
			
		||||
		state.isShow = true;
 | 
			
		||||
	}, 10);
 | 
			
		||||
};
 | 
			
		||||
// 关闭右键菜单
 | 
			
		||||
const closeContextmenu = () => {
 | 
			
		||||
	state.isShow = false;
 | 
			
		||||
};
 | 
			
		||||
// 监听页面监听进行右键菜单的关闭
 | 
			
		||||
onMounted(() => {
 | 
			
		||||
	document.body.addEventListener('click', closeContextmenu);
 | 
			
		||||
});
 | 
			
		||||
// 页面卸载时,移除右键菜单监听事件
 | 
			
		||||
onUnmounted(() => {
 | 
			
		||||
	document.body.removeEventListener('click', closeContextmenu);
 | 
			
		||||
});
 | 
			
		||||
// 监听下拉菜单位置
 | 
			
		||||
watch(
 | 
			
		||||
	() => props.dropdown,
 | 
			
		||||
	({ x }) => {
 | 
			
		||||
		if (x + 117 > document.documentElement.clientWidth) state.arrowLeft = 117 - (document.documentElement.clientWidth - x);
 | 
			
		||||
		else state.arrowLeft = 10;
 | 
			
		||||
	},
 | 
			
		||||
	{
 | 
			
		||||
		deep: true,
 | 
			
		||||
	}
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
// 暴露变量
 | 
			
		||||
defineExpose({
 | 
			
		||||
	openContextmenu,
 | 
			
		||||
});
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style scoped lang="scss">
 | 
			
		||||
.custom-contextmenu {
 | 
			
		||||
	transform-origin: center top;
 | 
			
		||||
	z-index: 2190;
 | 
			
		||||
	position: fixed;
 | 
			
		||||
	.el-dropdown-menu__item {
 | 
			
		||||
		font-size: 12px !important;
 | 
			
		||||
		white-space: nowrap;
 | 
			
		||||
		i {
 | 
			
		||||
			font-size: 12px !important;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
@@ -132,16 +132,18 @@ import { nextTick, onMounted, ref, toRefs, reactive, computed } from 'vue';
 | 
			
		||||
import { useRoute, useRouter } from 'vue-router';
 | 
			
		||||
import { ElMessage } from 'element-plus';
 | 
			
		||||
import { initRouter } from '@/router/index';
 | 
			
		||||
import { getSession, setSession, setUserInfo2Session, setUseWatermark2Session } from '@/common/utils/storage';
 | 
			
		||||
import { saveToken, saveUser } from '@/common/utils/storage';
 | 
			
		||||
import { formatAxis } from '@/common/utils/format';
 | 
			
		||||
import openApi from '@/common/openApi';
 | 
			
		||||
import { RsaEncrypt } from '@/common/rsa';
 | 
			
		||||
import { getAccountLoginSecurity, getLdapEnabled, useWartermark } from '@/common/sysconfig';
 | 
			
		||||
import { getAccountLoginSecurity, getLdapEnabled } from '@/common/sysconfig';
 | 
			
		||||
import { letterAvatar } from '@/common/utils/string';
 | 
			
		||||
import { useUserInfo } from '@/store/userInfo';
 | 
			
		||||
import QrcodeVue from 'qrcode.vue';
 | 
			
		||||
import { personApi } from '@/views/personal/api';
 | 
			
		||||
import { AccountUsernamePattern } from '@/common/pattern';
 | 
			
		||||
import { getToken } from '@/common/utils/storage';
 | 
			
		||||
import { useThemeConfig } from '@/store/themeConfig';
 | 
			
		||||
 | 
			
		||||
const rules = {
 | 
			
		||||
    username: [{ required: true, message: '请输入用户名', trigger: 'blur' }],
 | 
			
		||||
@@ -149,6 +151,9 @@ const rules = {
 | 
			
		||||
    captcha: [{ required: true, message: '请输入验证码', trigger: 'blur' }],
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// 定义变量内容
 | 
			
		||||
const storesThemeConfig = useThemeConfig();
 | 
			
		||||
 | 
			
		||||
const route = useRoute();
 | 
			
		||||
const router = useRouter();
 | 
			
		||||
const loginFormRef: any = ref(null);
 | 
			
		||||
@@ -159,7 +164,7 @@ const baseInfoFormRef: any = ref(null);
 | 
			
		||||
 | 
			
		||||
const state = reactive({
 | 
			
		||||
    accountLoginSecurity: {
 | 
			
		||||
        useCaptcha: true,
 | 
			
		||||
        useCaptcha: false,
 | 
			
		||||
        useOtp: false,
 | 
			
		||||
        loginFailCount: 5,
 | 
			
		||||
        loginFailMin: 10,
 | 
			
		||||
@@ -354,7 +359,7 @@ const loginResDeal = (loginRes: any) => {
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    // 存储用户信息到浏览器缓存
 | 
			
		||||
    setUserInfo2Session(userInfos);
 | 
			
		||||
    saveUser(userInfos);
 | 
			
		||||
    // 1、请注意执行顺序(存储用户信息到vuex)
 | 
			
		||||
    useUserInfo().setUserInfo(userInfos);
 | 
			
		||||
 | 
			
		||||
@@ -376,10 +381,10 @@ const loginResDeal = (loginRes: any) => {
 | 
			
		||||
// 登录成功后的跳转
 | 
			
		||||
const signInSuccess = async (accessToken: string = '') => {
 | 
			
		||||
    if (!accessToken) {
 | 
			
		||||
        accessToken = getSession('token');
 | 
			
		||||
        accessToken = getToken();
 | 
			
		||||
    }
 | 
			
		||||
    // 存储 token 到浏览器缓存
 | 
			
		||||
    setSession('token', accessToken);
 | 
			
		||||
    saveToken(accessToken);
 | 
			
		||||
    // 初始化路由
 | 
			
		||||
    await initRouter();
 | 
			
		||||
 | 
			
		||||
@@ -404,9 +409,8 @@ const toIndex = async () => {
 | 
			
		||||
        // 关闭 loading
 | 
			
		||||
        state.loading.signIn = true;
 | 
			
		||||
        ElMessage.success(`${currentTimeInfo},欢迎回来!`);
 | 
			
		||||
        if (await useWartermark()) {
 | 
			
		||||
            setUseWatermark2Session(true);
 | 
			
		||||
        }
 | 
			
		||||
        // 水印设置用户信息
 | 
			
		||||
        storesThemeConfig.setWatermarkUser();
 | 
			
		||||
    }, 300);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,70 +1,77 @@
 | 
			
		||||
<template>
 | 
			
		||||
    <div class="login-container">
 | 
			
		||||
        <div class="login-logo">
 | 
			
		||||
            <span>{{ themeConfig.globalViceTitle }}</span>
 | 
			
		||||
    <div class="login-container flex">
 | 
			
		||||
        <div class="login-left">
 | 
			
		||||
            <div class="login-left-logo">
 | 
			
		||||
                <img :src="logoMini" />
 | 
			
		||||
                <div class="login-left-logo-text">
 | 
			
		||||
                    <span>mayfly-go</span>
 | 
			
		||||
                    <!-- <span class="login-left-logo-text-msg">mayfly-go</span> -->
 | 
			
		||||
                </div>
 | 
			
		||||
            </div>
 | 
			
		||||
            <div class="login-left-img">
 | 
			
		||||
                <img :src="loginBgImg" />
 | 
			
		||||
            </div>
 | 
			
		||||
            <img :src="loginBgSplitImg" class="login-left-waves" />
 | 
			
		||||
        </div>
 | 
			
		||||
        <div class="login-content" :class="{ 'login-content-mobile': tabsActiveName === 'mobile' }">
 | 
			
		||||
            <div class="login-content-main">
 | 
			
		||||
                <h4 class="login-content-title">mayfly-go</h4>
 | 
			
		||||
                <el-tabs v-model="tabsActiveName" @tab-click="onTabsClick">
 | 
			
		||||
                    <el-tab-pane label="账号密码登录" name="account" :disabled="tabsActiveName === 'account'">
 | 
			
		||||
                        <transition name="el-zoom-in-center">
 | 
			
		||||
                            <Account v-show="isTabPaneShow" ref="loginForm" />
 | 
			
		||||
                        </transition>
 | 
			
		||||
                    </el-tab-pane>
 | 
			
		||||
                    <!-- <el-tab-pane label="手机号登录" name="mobile" :disabled="tabsActiveName === 'mobile'">
 | 
			
		||||
                        <transition name="el-zoom-in-center">
 | 
			
		||||
                            <Mobile v-show="!isTabPaneShow" />
 | 
			
		||||
                        </transition>
 | 
			
		||||
                    </el-tab-pane> -->
 | 
			
		||||
                </el-tabs>
 | 
			
		||||
                <div class="mt20" v-show="oauth2LoginConfig.enable">
 | 
			
		||||
                    <el-button link size="small">第三方登录: </el-button>
 | 
			
		||||
                    <el-tooltip :content="oauth2LoginConfig.name" placement="top-start">
 | 
			
		||||
                        <el-button link size="small" type="primary" @click="oauth2Login">
 | 
			
		||||
                            <el-icon :size="18">
 | 
			
		||||
                                <Link />
 | 
			
		||||
                            </el-icon>
 | 
			
		||||
                        </el-button>
 | 
			
		||||
                    </el-tooltip>
 | 
			
		||||
        <div class="login-right flex">
 | 
			
		||||
            <div class="login-right-warp flex-margin">
 | 
			
		||||
                <span class="login-right-warp-one"></span>
 | 
			
		||||
                <span class="login-right-warp-two"></span>
 | 
			
		||||
                <div class="login-right-warp-mian">
 | 
			
		||||
                    <div class="login-right-warp-main-title">mayfly-go</div>
 | 
			
		||||
                    <div class="login-right-warp-main-form">
 | 
			
		||||
                        <div v-if="!state.isScan">
 | 
			
		||||
                            <el-tabs v-model="state.tabsActiveName">
 | 
			
		||||
                                <el-tab-pane label="账号密码登录" name="account">
 | 
			
		||||
                                    <Account ref="loginForm" />
 | 
			
		||||
                                </el-tab-pane>
 | 
			
		||||
                            </el-tabs>
 | 
			
		||||
                        </div>
 | 
			
		||||
                        <div class="mt20" v-show="state.oauth2LoginConfig.enable">
 | 
			
		||||
                            <el-button link size="small">第三方登录: </el-button>
 | 
			
		||||
                            <el-tooltip :content="state.oauth2LoginConfig.name" placement="top-start">
 | 
			
		||||
                                <el-button link size="small" type="primary" @click="oauth2Login">
 | 
			
		||||
                                    <el-icon :size="18">
 | 
			
		||||
                                        <Link />
 | 
			
		||||
                                    </el-icon>
 | 
			
		||||
                                </el-button>
 | 
			
		||||
                            </el-tooltip>
 | 
			
		||||
                        </div>
 | 
			
		||||
                    </div>
 | 
			
		||||
                </div>
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
        <!-- <div class="login-copyright">
 | 
			
		||||
            <div class="mb5 login-copyright-company">mayfly</div>
 | 
			
		||||
            <div class="login-copyright-msg">mayfly</div>
 | 
			
		||||
        </div> -->
 | 
			
		||||
    </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script lang="ts" setup>
 | 
			
		||||
import { toRefs, reactive, onMounted, h, ref } from 'vue';
 | 
			
		||||
import Account from '@/views/login/component/AccountLogin.vue';
 | 
			
		||||
import { storeToRefs } from 'pinia';
 | 
			
		||||
<script setup lang="ts" name="loginIndex">
 | 
			
		||||
import { ref, defineAsyncComponent, onMounted, reactive } from 'vue';
 | 
			
		||||
import { useThemeConfig } from '@/store/themeConfig';
 | 
			
		||||
import logoMini from '@/assets/image/logo.svg';
 | 
			
		||||
import loginBgImg from '@/assets/image/login-bg-main.svg';
 | 
			
		||||
import loginBgSplitImg from '@/assets/image/login-bg-split.svg';
 | 
			
		||||
import openApi from '@/common/openApi';
 | 
			
		||||
import config from '@/common/config';
 | 
			
		||||
 | 
			
		||||
const { themeConfig } = storeToRefs(useThemeConfig());
 | 
			
		||||
// 引入组件
 | 
			
		||||
const Account = defineAsyncComponent(() => import('./component/AccountLogin.vue'));
 | 
			
		||||
 | 
			
		||||
const loginForm = ref<{ loginResDeal: (data: any) => void } | null>(null);
 | 
			
		||||
 | 
			
		||||
// 定义变量内容
 | 
			
		||||
const storesThemeConfig = useThemeConfig();
 | 
			
		||||
 | 
			
		||||
const state = reactive({
 | 
			
		||||
    tabsActiveName: 'account',
 | 
			
		||||
    isTabPaneShow: true,
 | 
			
		||||
    isScan: false,
 | 
			
		||||
    oauth2LoginConfig: {
 | 
			
		||||
        name: 'OAuth2登录',
 | 
			
		||||
        enable: false,
 | 
			
		||||
    },
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const loginForm = ref<{ loginResDeal: (data: any) => void } | null>(null);
 | 
			
		||||
 | 
			
		||||
const { isTabPaneShow, tabsActiveName, oauth2LoginConfig: oauth2LoginConfig } = toRefs(state);
 | 
			
		||||
 | 
			
		||||
// 切换密码、手机登录
 | 
			
		||||
const onTabsClick = () => {
 | 
			
		||||
    state.isTabPaneShow = !state.isTabPaneShow;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
onMounted(async () => {
 | 
			
		||||
    storesThemeConfig.setWatermarkUser(true);
 | 
			
		||||
    state.oauth2LoginConfig = await openApi.oauth2LoginConfig();
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
@@ -94,76 +101,178 @@ const oauth2Login = () => {
 | 
			
		||||
 | 
			
		||||
<style scoped lang="scss">
 | 
			
		||||
.login-container {
 | 
			
		||||
    width: 100%;
 | 
			
		||||
    height: 100%;
 | 
			
		||||
    background: url('@/assets/image/bg-login.png') no-repeat;
 | 
			
		||||
    background-size: 100% 100%;
 | 
			
		||||
 | 
			
		||||
    .login-logo {
 | 
			
		||||
        position: absolute;
 | 
			
		||||
        top: 30px;
 | 
			
		||||
        left: 50%;
 | 
			
		||||
        height: 50px;
 | 
			
		||||
        display: flex;
 | 
			
		||||
        align-items: center;
 | 
			
		||||
        font-size: 20px;
 | 
			
		||||
        color: var(--el-color-primary);
 | 
			
		||||
        letter-spacing: 2px;
 | 
			
		||||
        width: 90%;
 | 
			
		||||
        transform: translateX(-50%);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .login-content {
 | 
			
		||||
        width: 500px;
 | 
			
		||||
        padding: 20px;
 | 
			
		||||
        position: absolute;
 | 
			
		||||
        top: 50%;
 | 
			
		||||
        left: 50%;
 | 
			
		||||
        transform: translate(-50%, -50%) translate3d(0, 0, 0);
 | 
			
		||||
        background-color: rgba(255, 255, 255, 0.99);
 | 
			
		||||
        box-shadow: 0 2px 12px 0 var(--el-color-primary-light-5);
 | 
			
		||||
        border-radius: 4px;
 | 
			
		||||
        transition: height 0.2s linear;
 | 
			
		||||
        height: 490px;
 | 
			
		||||
        overflow: hidden;
 | 
			
		||||
        z-index: 1;
 | 
			
		||||
 | 
			
		||||
        .login-content-main {
 | 
			
		||||
            margin: 0 auto;
 | 
			
		||||
            width: 80%;
 | 
			
		||||
 | 
			
		||||
            .login-content-title {
 | 
			
		||||
                color: #333;
 | 
			
		||||
                font-weight: 500;
 | 
			
		||||
                font-size: 22px;
 | 
			
		||||
                text-align: center;
 | 
			
		||||
                letter-spacing: 4px;
 | 
			
		||||
                margin: 15px 0 30px;
 | 
			
		||||
                white-space: nowrap;
 | 
			
		||||
    background: var(--bg-main-color);
 | 
			
		||||
    .login-left {
 | 
			
		||||
        flex: 1;
 | 
			
		||||
        position: relative;
 | 
			
		||||
        background-color: rgba(211, 239, 255, 1);
 | 
			
		||||
        margin-right: 100px;
 | 
			
		||||
        .login-left-logo {
 | 
			
		||||
            display: flex;
 | 
			
		||||
            align-items: center;
 | 
			
		||||
            position: absolute;
 | 
			
		||||
            top: 50px;
 | 
			
		||||
            left: 80px;
 | 
			
		||||
            z-index: 1;
 | 
			
		||||
            animation: logoAnimation 0.3s ease;
 | 
			
		||||
            img {
 | 
			
		||||
                width: 52px;
 | 
			
		||||
                height: 52px;
 | 
			
		||||
            }
 | 
			
		||||
            .login-left-logo-text {
 | 
			
		||||
                display: flex;
 | 
			
		||||
                flex-direction: column;
 | 
			
		||||
                span {
 | 
			
		||||
                    margin-left: 10px;
 | 
			
		||||
                    font-size: 28px;
 | 
			
		||||
                    color: #26a59a;
 | 
			
		||||
                }
 | 
			
		||||
                .login-left-logo-text-msg {
 | 
			
		||||
                    font-size: 12px;
 | 
			
		||||
                    color: #32a99e;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .login-content-mobile {
 | 
			
		||||
        height: 418px;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .login-copyright {
 | 
			
		||||
        position: absolute;
 | 
			
		||||
        left: 50%;
 | 
			
		||||
        transform: translateX(-50%);
 | 
			
		||||
        bottom: 30px;
 | 
			
		||||
        text-align: center;
 | 
			
		||||
        color: white;
 | 
			
		||||
        font-size: 12px;
 | 
			
		||||
        opacity: 0.8;
 | 
			
		||||
 | 
			
		||||
        .login-copyright-company {
 | 
			
		||||
            white-space: nowrap;
 | 
			
		||||
        .login-left-img {
 | 
			
		||||
            position: absolute;
 | 
			
		||||
            top: 50%;
 | 
			
		||||
            left: 50%;
 | 
			
		||||
            transform: translate(-50%, -50%);
 | 
			
		||||
            width: 100%;
 | 
			
		||||
            height: 52%;
 | 
			
		||||
            img {
 | 
			
		||||
                width: 100%;
 | 
			
		||||
                height: 100%;
 | 
			
		||||
                animation: error-num 0.6s ease;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        .login-copyright-msg {
 | 
			
		||||
            @extend .login-copyright-company;
 | 
			
		||||
        .login-left-waves {
 | 
			
		||||
            position: absolute;
 | 
			
		||||
            top: 0;
 | 
			
		||||
            right: -100px;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    .login-right {
 | 
			
		||||
        width: 700px;
 | 
			
		||||
        .login-right-warp {
 | 
			
		||||
            border: 1px solid var(--el-color-primary-light-3);
 | 
			
		||||
            border-radius: 3px;
 | 
			
		||||
            width: 500px;
 | 
			
		||||
            height: 500px;
 | 
			
		||||
            position: relative;
 | 
			
		||||
            overflow: hidden;
 | 
			
		||||
            background-color: var(--bg-main-color);
 | 
			
		||||
            .login-right-warp-one,
 | 
			
		||||
            .login-right-warp-two {
 | 
			
		||||
                position: absolute;
 | 
			
		||||
                display: block;
 | 
			
		||||
                width: inherit;
 | 
			
		||||
                height: inherit;
 | 
			
		||||
                &::before,
 | 
			
		||||
                &::after {
 | 
			
		||||
                    content: '';
 | 
			
		||||
                    position: absolute;
 | 
			
		||||
                    z-index: 1;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            .login-right-warp-one {
 | 
			
		||||
                &::before {
 | 
			
		||||
                    filter: hue-rotate(0deg);
 | 
			
		||||
                    top: 0px;
 | 
			
		||||
                    left: 0;
 | 
			
		||||
                    width: 100%;
 | 
			
		||||
                    height: 3px;
 | 
			
		||||
                    background: linear-gradient(90deg, transparent, var(--el-color-primary));
 | 
			
		||||
                    animation: loginLeft 3s linear infinite;
 | 
			
		||||
                }
 | 
			
		||||
                &::after {
 | 
			
		||||
                    filter: hue-rotate(60deg);
 | 
			
		||||
                    top: -100%;
 | 
			
		||||
                    right: 2px;
 | 
			
		||||
                    width: 3px;
 | 
			
		||||
                    height: 100%;
 | 
			
		||||
                    background: linear-gradient(180deg, transparent, var(--el-color-primary));
 | 
			
		||||
                    animation: loginTop 3s linear infinite;
 | 
			
		||||
                    animation-delay: 0.7s;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            .login-right-warp-two {
 | 
			
		||||
                &::before {
 | 
			
		||||
                    filter: hue-rotate(120deg);
 | 
			
		||||
                    bottom: 2px;
 | 
			
		||||
                    right: -100%;
 | 
			
		||||
                    width: 100%;
 | 
			
		||||
                    height: 3px;
 | 
			
		||||
                    background: linear-gradient(270deg, transparent, var(--el-color-primary));
 | 
			
		||||
                    animation: loginRight 3s linear infinite;
 | 
			
		||||
                    animation-delay: 1.4s;
 | 
			
		||||
                }
 | 
			
		||||
                &::after {
 | 
			
		||||
                    filter: hue-rotate(300deg);
 | 
			
		||||
                    bottom: -100%;
 | 
			
		||||
                    left: 0px;
 | 
			
		||||
                    width: 3px;
 | 
			
		||||
                    height: 100%;
 | 
			
		||||
                    background: linear-gradient(360deg, transparent, var(--el-color-primary));
 | 
			
		||||
                    animation: loginBottom 3s linear infinite;
 | 
			
		||||
                    animation-delay: 2.1s;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            .login-right-warp-mian {
 | 
			
		||||
                display: flex;
 | 
			
		||||
                flex-direction: column;
 | 
			
		||||
                height: 100%;
 | 
			
		||||
                .login-right-warp-main-title {
 | 
			
		||||
                    height: 110px;
 | 
			
		||||
                    line-height: 110px;
 | 
			
		||||
                    font-size: 27px;
 | 
			
		||||
                    text-align: center;
 | 
			
		||||
                    letter-spacing: 3px;
 | 
			
		||||
                    animation: logoAnimation 0.3s ease;
 | 
			
		||||
                    animation-delay: 0.3s;
 | 
			
		||||
                    color: var(--el-text-color-primary);
 | 
			
		||||
                }
 | 
			
		||||
                .login-right-warp-main-form {
 | 
			
		||||
                    flex: 1;
 | 
			
		||||
                    padding: 0 50px 50px;
 | 
			
		||||
                    .login-content-main-sacn {
 | 
			
		||||
                        position: absolute;
 | 
			
		||||
                        top: 0;
 | 
			
		||||
                        right: 0;
 | 
			
		||||
                        width: 50px;
 | 
			
		||||
                        height: 50px;
 | 
			
		||||
                        overflow: hidden;
 | 
			
		||||
                        cursor: pointer;
 | 
			
		||||
                        transition: all ease 0.3s;
 | 
			
		||||
                        color: var(--el-color-primary);
 | 
			
		||||
                        &-delta {
 | 
			
		||||
                            position: absolute;
 | 
			
		||||
                            width: 35px;
 | 
			
		||||
                            height: 70px;
 | 
			
		||||
                            z-index: 2;
 | 
			
		||||
                            top: 2px;
 | 
			
		||||
                            right: 21px;
 | 
			
		||||
                            background: var(--el-color-white);
 | 
			
		||||
                            transform: rotate(-45deg);
 | 
			
		||||
                        }
 | 
			
		||||
                        &:hover {
 | 
			
		||||
                            opacity: 1;
 | 
			
		||||
                            transition: all ease 0.3s;
 | 
			
		||||
                            color: var(--el-color-primary) !important;
 | 
			
		||||
                        }
 | 
			
		||||
                        i {
 | 
			
		||||
                            width: 47px;
 | 
			
		||||
                            height: 50px;
 | 
			
		||||
                            display: inline-block;
 | 
			
		||||
                            font-size: 48px;
 | 
			
		||||
                            position: absolute;
 | 
			
		||||
                            right: 1px;
 | 
			
		||||
                            top: 0px;
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -10,11 +10,11 @@
 | 
			
		||||
            width="38%"
 | 
			
		||||
        >
 | 
			
		||||
            <el-form :model="form" ref="dbForm" :rules="rules" label-width="auto">
 | 
			
		||||
                <el-form-item prop="tagId" label="标签:" required>
 | 
			
		||||
                <el-form-item prop="tagId" label="标签" required>
 | 
			
		||||
                    <tag-select v-model="form.tagId" v-model:tag-path="form.tagPath" style="width: 100%" />
 | 
			
		||||
                </el-form-item>
 | 
			
		||||
 | 
			
		||||
                <el-form-item prop="instanceId" label="数据库实例:" required>
 | 
			
		||||
                <el-form-item prop="instanceId" label="数据库实例" required>
 | 
			
		||||
                    <el-select
 | 
			
		||||
                        :disabled="form.id !== undefined"
 | 
			
		||||
                        remote
 | 
			
		||||
@@ -37,11 +37,11 @@
 | 
			
		||||
                    </el-select>
 | 
			
		||||
                </el-form-item>
 | 
			
		||||
 | 
			
		||||
                <el-form-item prop="name" label="别名:" required>
 | 
			
		||||
                <el-form-item prop="name" label="别名" required>
 | 
			
		||||
                    <el-input v-model.trim="form.name" placeholder="请输入数据库别名" auto-complete="off"></el-input>
 | 
			
		||||
                </el-form-item>
 | 
			
		||||
 | 
			
		||||
                <el-form-item prop="database" label="数据库名:" required>
 | 
			
		||||
                <el-form-item prop="database" label="数据库名" required>
 | 
			
		||||
                    <el-select
 | 
			
		||||
                        @change="changeDatabase"
 | 
			
		||||
                        v-model="databaseList"
 | 
			
		||||
@@ -58,7 +58,7 @@
 | 
			
		||||
                    </el-select>
 | 
			
		||||
                </el-form-item>
 | 
			
		||||
 | 
			
		||||
                <el-form-item prop="remark" label="备注:">
 | 
			
		||||
                <el-form-item prop="remark" label="备注">
 | 
			
		||||
                    <el-input v-model.trim="form.remark" auto-complete="off" type="textarea"></el-input>
 | 
			
		||||
                </el-form-item>
 | 
			
		||||
            </el-form>
 | 
			
		||||
 
 | 
			
		||||
@@ -172,7 +172,7 @@ import { ref, toRefs, reactive, onMounted, defineAsyncComponent } from 'vue';
 | 
			
		||||
import { ElMessage, ElMessageBox } from 'element-plus';
 | 
			
		||||
import { dbApi } from './api';
 | 
			
		||||
import config from '@/common/config';
 | 
			
		||||
import { getSession } from '@/common/utils/storage';
 | 
			
		||||
import { getToken } from '@/common/utils/storage';
 | 
			
		||||
import { isTrue } from '@/common/assert';
 | 
			
		||||
import { Search as SearchIcon } from '@element-plus/icons-vue';
 | 
			
		||||
import { dateFormat } from '@/common/utils/date';
 | 
			
		||||
@@ -406,7 +406,7 @@ const dumpDbs = () => {
 | 
			
		||||
        'href',
 | 
			
		||||
        `${config.baseApiUrl}/dbs/${state.exportDialog.dbId}/dump?db=${state.exportDialog.value.join(',')}&type=${type}&extName=${
 | 
			
		||||
            state.exportDialog.extName
 | 
			
		||||
        }&token=${getSession('token')}`
 | 
			
		||||
        }&token=${getToken()}`
 | 
			
		||||
    );
 | 
			
		||||
    a.click();
 | 
			
		||||
    state.exportDialog.visible = false;
 | 
			
		||||
 
 | 
			
		||||
@@ -4,16 +4,16 @@
 | 
			
		||||
            <el-form :model="form" ref="dbForm" :rules="rules" label-width="auto">
 | 
			
		||||
                <el-tabs v-model="tabActiveName">
 | 
			
		||||
                    <el-tab-pane label="基础信息" name="basic">
 | 
			
		||||
                        <el-form-item prop="name" label="别名:" required>
 | 
			
		||||
                        <el-form-item prop="name" label="别名" required>
 | 
			
		||||
                            <el-input v-model.trim="form.name" placeholder="请输入数据库别名" auto-complete="off"></el-input>
 | 
			
		||||
                        </el-form-item>
 | 
			
		||||
                        <el-form-item prop="type" label="类型:" required>
 | 
			
		||||
                        <el-form-item prop="type" label="类型" required>
 | 
			
		||||
                            <el-select style="width: 100%" v-model="form.type" placeholder="请选择数据库类型">
 | 
			
		||||
                                <el-option key="item.id" label="mysql" value="mysql"> </el-option>
 | 
			
		||||
                                <el-option key="item.id" label="postgres" value="postgres"> </el-option>
 | 
			
		||||
                            </el-select>
 | 
			
		||||
                        </el-form-item>
 | 
			
		||||
                        <el-form-item prop="host" label="host:" required>
 | 
			
		||||
                        <el-form-item prop="host" label="host" required>
 | 
			
		||||
                            <el-col :span="18">
 | 
			
		||||
                                <el-input :disabled="form.id !== undefined" v-model.trim="form.host" placeholder="请输入主机ip" auto-complete="off"></el-input>
 | 
			
		||||
                            </el-col>
 | 
			
		||||
@@ -22,10 +22,10 @@
 | 
			
		||||
                                <el-input type="number" v-model.number="form.port" placeholder="请输入端口"></el-input>
 | 
			
		||||
                            </el-col>
 | 
			
		||||
                        </el-form-item>
 | 
			
		||||
                        <el-form-item prop="username" label="用户名:" required>
 | 
			
		||||
                        <el-form-item prop="username" label="用户名" required>
 | 
			
		||||
                            <el-input v-model.trim="form.username" placeholder="请输入用户名"></el-input>
 | 
			
		||||
                        </el-form-item>
 | 
			
		||||
                        <el-form-item prop="password" label="密码:">
 | 
			
		||||
                        <el-form-item prop="password" label="密码">
 | 
			
		||||
                            <el-input type="password" show-password v-model.trim="form.password" placeholder="请输入密码" autocomplete="new-password">
 | 
			
		||||
                                <template v-if="form.id && form.id != 0" #suffix>
 | 
			
		||||
                                    <el-popover @hide="pwd = ''" placement="right" title="原密码" :width="200" trigger="click" :content="pwd">
 | 
			
		||||
@@ -39,13 +39,13 @@
 | 
			
		||||
                            </el-input>
 | 
			
		||||
                        </el-form-item>
 | 
			
		||||
 | 
			
		||||
                        <el-form-item prop="remark" label="备注:">
 | 
			
		||||
                        <el-form-item prop="remark" label="备注">
 | 
			
		||||
                            <el-input v-model.trim="form.remark" auto-complete="off" type="textarea"></el-input>
 | 
			
		||||
                        </el-form-item>
 | 
			
		||||
                    </el-tab-pane>
 | 
			
		||||
 | 
			
		||||
                    <el-tab-pane label="其他配置" name="other">
 | 
			
		||||
                        <el-form-item prop="params" label="连接参数:">
 | 
			
		||||
                        <el-form-item prop="params" label="连接参数">
 | 
			
		||||
                            <el-input v-model.trim="form.params" placeholder="其他连接参数,形如: key1=value1&key2=value2">
 | 
			
		||||
                                <template #suffix>
 | 
			
		||||
                                    <el-link
 | 
			
		||||
@@ -60,7 +60,7 @@
 | 
			
		||||
                            </el-input>
 | 
			
		||||
                        </el-form-item>
 | 
			
		||||
 | 
			
		||||
                        <el-form-item prop="sshTunnelMachineId" label="SSH隧道:">
 | 
			
		||||
                        <el-form-item prop="sshTunnelMachineId" label="SSH隧道">
 | 
			
		||||
                            <ssh-tunnel-select v-model="form.sshTunnelMachineId" />
 | 
			
		||||
                        </el-form-item>
 | 
			
		||||
                    </el-tab-pane>
 | 
			
		||||
 
 | 
			
		||||
@@ -2,7 +2,12 @@
 | 
			
		||||
    <div>
 | 
			
		||||
        <el-row class="mb5">
 | 
			
		||||
            <el-col :span="4">
 | 
			
		||||
                <el-button type="primary" icon="plus" @click="addQueryTab({ id: nowDbInst.id, dbs: nowDbInst.databases.split(' ') }, state.db)" size="small"
 | 
			
		||||
                <el-button
 | 
			
		||||
                    :disabled="!state.db || !nowDbInst.id"
 | 
			
		||||
                    type="primary"
 | 
			
		||||
                    icon="plus"
 | 
			
		||||
                    @click="addQueryTab({ id: nowDbInst.id, dbs: nowDbInst.databases?.split(' ') }, state.db)"
 | 
			
		||||
                    size="small"
 | 
			
		||||
                    >新建查询</el-button
 | 
			
		||||
                >
 | 
			
		||||
            </el-col>
 | 
			
		||||
@@ -95,13 +100,14 @@
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script lang="ts" setup>
 | 
			
		||||
import { defineAsyncComponent, onMounted, reactive, ref, toRefs } from 'vue';
 | 
			
		||||
import { defineAsyncComponent, onMounted, reactive, ref, toRefs, onBeforeUnmount } from 'vue';
 | 
			
		||||
import { ElMessage } from 'element-plus';
 | 
			
		||||
 | 
			
		||||
import { DbInst, TabInfo, TabType } from './db';
 | 
			
		||||
import { DbInst, TabInfo, TabType, registerDbCompletionItemProvider } from './db';
 | 
			
		||||
import { TagTreeNode } from '../component/tag';
 | 
			
		||||
import TagTree from '../component/TagTree.vue';
 | 
			
		||||
import { dbApi } from './api';
 | 
			
		||||
import { dispposeCompletionItemProvider } from '../../../components/monaco/completionItemProvider';
 | 
			
		||||
 | 
			
		||||
const Query = defineAsyncComponent(() => import('./component/tab/Query.vue'));
 | 
			
		||||
const TableData = defineAsyncComponent(() => import('./component/tab/TableData.vue'));
 | 
			
		||||
@@ -144,12 +150,15 @@ const state = reactive({
 | 
			
		||||
const { nowDbInst } = toRefs(state);
 | 
			
		||||
 | 
			
		||||
onMounted(() => {
 | 
			
		||||
    self.completionItemProvider?.dispose();
 | 
			
		||||
    setHeight();
 | 
			
		||||
    // 监听浏览器窗口大小变化,更新对应组件高度
 | 
			
		||||
    window.onresize = () => setHeight();
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
onBeforeUnmount(() => {
 | 
			
		||||
    dispposeCompletionItemProvider('sql');
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 设置editor高度和数据表高度
 | 
			
		||||
 */
 | 
			
		||||
@@ -207,7 +216,6 @@ const loadNode = async (node: any) => {
 | 
			
		||||
    // 点击数据库实例 -> 加载库列表
 | 
			
		||||
    if (nodeType === NodeType.DbInst) {
 | 
			
		||||
        const dbs = params.database.split(' ')?.sort();
 | 
			
		||||
        console.log(dbs);
 | 
			
		||||
        return dbs.map((x: any) => {
 | 
			
		||||
            return new TagTreeNode(`${data.key}.${x}`, x, NodeType.Db).withParams({
 | 
			
		||||
                tagPath: params.tagPath,
 | 
			
		||||
@@ -406,9 +414,15 @@ const onTabChange = () => {
 | 
			
		||||
        state.db = '';
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const nowTab = state.tabs.get(state.activeName);
 | 
			
		||||
    state.nowDbInst = DbInst.getInst(nowTab?.dbId);
 | 
			
		||||
    state.db = nowTab?.db as string;
 | 
			
		||||
 | 
			
		||||
    if (nowTab?.type == TabType.Query) {
 | 
			
		||||
        // 注册sql提示
 | 
			
		||||
        registerDbCompletionItemProvider('sql', nowTab.dbId, nowTab.db, nowTab.params.dbs);
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const onGenerateInsertSql = async (sql: string) => {
 | 
			
		||||
 
 | 
			
		||||
@@ -44,9 +44,7 @@
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
 | 
			
		||||
        <div class="mt5 sqlEditor">
 | 
			
		||||
            <div :id="'MonacoTextarea-' + ti.key" :style="{ height: editorHeight }"></div>
 | 
			
		||||
        </div>
 | 
			
		||||
        <MonacoEditor ref="monacoEditorRef" class="mt5" v-model="state.sql" language="sql" :height="editorHeight" :id="'MonacoTextarea-' + ti.key" />
 | 
			
		||||
 | 
			
		||||
        <div class="editor-move-resize" @mousedown="onDragSetHeight">
 | 
			
		||||
            <el-icon>
 | 
			
		||||
@@ -90,44 +88,22 @@
 | 
			
		||||
 | 
			
		||||
<script lang="ts" setup>
 | 
			
		||||
import { nextTick, watch, onMounted, reactive, toRefs, ref, Ref } from 'vue';
 | 
			
		||||
import { storeToRefs } from 'pinia';
 | 
			
		||||
import { useThemeConfig } from '@/store/themeConfig';
 | 
			
		||||
import { getSession } from '@/common/utils/storage';
 | 
			
		||||
import { getToken } from '@/common/utils/storage';
 | 
			
		||||
import { isTrue, notBlank } from '@/common/assert';
 | 
			
		||||
import { format as sqlFormatter } from 'sql-formatter';
 | 
			
		||||
import config from '@/common/config';
 | 
			
		||||
import { ElMessage, ElMessageBox } from 'element-plus';
 | 
			
		||||
 | 
			
		||||
import { language as sqlLanguage } from 'monaco-editor/esm/vs/basic-languages/mysql/mysql.js';
 | 
			
		||||
import { language as addSqlLanguage } from '../../lang/mysql.js';
 | 
			
		||||
import EditorWorker from 'monaco-editor/esm/vs/editor/editor.worker.js?worker';
 | 
			
		||||
// import * as monaco from 'monaco-editor';
 | 
			
		||||
import * as monaco from 'monaco-editor/esm/vs/editor/editor.api';
 | 
			
		||||
import { editor, languages, Position } from 'monaco-editor';
 | 
			
		||||
// 相关语言
 | 
			
		||||
import 'monaco-editor/esm/vs/basic-languages/sql/sql.contribution.js';
 | 
			
		||||
import 'monaco-editor/esm/vs/editor/contrib/suggest/browser/suggestController.js';
 | 
			
		||||
import 'monaco-editor/esm/vs/editor/contrib/suggest/browser/suggestInlineCompletions.js';
 | 
			
		||||
// 右键菜单
 | 
			
		||||
import 'monaco-editor/esm/vs/editor/contrib/contextmenu/browser/contextmenu.js';
 | 
			
		||||
import 'monaco-editor/esm/vs/editor/contrib/caretOperations/browser/caretOperations.js';
 | 
			
		||||
import 'monaco-editor/esm/vs/editor/contrib/clipboard//browser/clipboard.js';
 | 
			
		||||
import 'monaco-editor/esm/vs/editor/contrib/find/browser/findController.js';
 | 
			
		||||
import 'monaco-editor/esm/vs/editor/contrib/format//browser/formatActions.js';
 | 
			
		||||
import { editor } from 'monaco-editor';
 | 
			
		||||
 | 
			
		||||
// 主题仓库 https://github.com/brijeshb42/monaco-themes
 | 
			
		||||
// 主题例子 https://editor.bitwiser.in/
 | 
			
		||||
import SolarizedLight from 'monaco-themes/themes/Solarized-light.json';
 | 
			
		||||
import DbTable from '../DbTable.vue';
 | 
			
		||||
import { DbInst, TabInfo } from '../../db';
 | 
			
		||||
import { TabInfo } from '../../db';
 | 
			
		||||
import { exportCsv } from '@/common/utils/export';
 | 
			
		||||
import { dateStrFormat } from '@/common/utils/date';
 | 
			
		||||
import { dbApi } from '../../api';
 | 
			
		||||
 | 
			
		||||
const sqlCompletionKeywords = [...sqlLanguage.keywords, ...addSqlLanguage.keywords];
 | 
			
		||||
const sqlCompletionOperators = [...sqlLanguage.operators, ...addSqlLanguage.operators];
 | 
			
		||||
const sqlCompletionBuiltinFunctions = [...sqlLanguage.builtinFunctions, ...addSqlLanguage.builtinFunctions];
 | 
			
		||||
const sqlCompletionBuiltinVariables = [...sqlLanguage.builtinVariables, ...addSqlLanguage.builtinVariables];
 | 
			
		||||
import MonacoEditor from '@/components/monaco/MonacoEditor.vue';
 | 
			
		||||
 | 
			
		||||
const emits = defineEmits(['saveSqlSuccess', 'deleteSqlSuccess']);
 | 
			
		||||
 | 
			
		||||
@@ -147,11 +123,12 @@ const props = defineProps({
 | 
			
		||||
    },
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const { themeConfig } = storeToRefs(useThemeConfig());
 | 
			
		||||
const token = getSession('token');
 | 
			
		||||
let monacoEditor = {} as editor.IStandaloneCodeEditor;
 | 
			
		||||
const token = getToken();
 | 
			
		||||
const monacoEditorRef: any = ref(null);
 | 
			
		||||
const dbTableRef = ref(null) as Ref;
 | 
			
		||||
 | 
			
		||||
let monacoEditor: editor.IStandaloneCodeEditor;
 | 
			
		||||
 | 
			
		||||
const state = reactive({
 | 
			
		||||
    token,
 | 
			
		||||
    ti: {} as TabInfo,
 | 
			
		||||
@@ -180,15 +157,6 @@ watch(
 | 
			
		||||
    }
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
// 监听 themeConfig editorTheme配置文件的变化
 | 
			
		||||
watch(
 | 
			
		||||
    () => themeConfig.value.editorTheme,
 | 
			
		||||
    (val) => {
 | 
			
		||||
        console.log('monaco editor theme change: ', val);
 | 
			
		||||
        monaco?.editor?.setTheme(val);
 | 
			
		||||
    }
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
onMounted(async () => {
 | 
			
		||||
    console.log('in query mounted');
 | 
			
		||||
    state.ti = props.data;
 | 
			
		||||
@@ -207,39 +175,8 @@ onMounted(async () => {
 | 
			
		||||
    await state.ti.getNowDbInst().loadDbHints(state.ti.db);
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
self.MonacoEnvironment = {
 | 
			
		||||
    getWorker() {
 | 
			
		||||
        return new EditorWorker();
 | 
			
		||||
    },
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const initMonacoEditor = () => {
 | 
			
		||||
    registerSqlCompletionItemProvider();
 | 
			
		||||
 | 
			
		||||
    let monacoTextarea = document.getElementById('MonacoTextarea-' + state.ti.key) as HTMLElement;
 | 
			
		||||
    // options参数参考 https://microsoft.github.io/monaco-editor/api/interfaces/monaco.editor.IStandaloneEditorConstructionOptions.html#language
 | 
			
		||||
    // 初始化一些主题
 | 
			
		||||
    monaco.editor.defineTheme('SolarizedLight', SolarizedLight);
 | 
			
		||||
    monacoEditor = monaco.editor.create(monacoTextarea, {
 | 
			
		||||
        language: 'sql',
 | 
			
		||||
        theme: themeConfig.value.editorTheme,
 | 
			
		||||
        automaticLayout: true, //自适应宽高布局
 | 
			
		||||
        folding: false,
 | 
			
		||||
        roundedSelection: false, // 禁用选择文本背景的圆角
 | 
			
		||||
        matchBrackets: 'near',
 | 
			
		||||
        linkedEditing: true,
 | 
			
		||||
        cursorBlinking: 'smooth', // 光标闪烁样式
 | 
			
		||||
        mouseWheelZoom: true, // 在按住Ctrl键的同时使用鼠标滚轮时,在编辑器中缩放字体
 | 
			
		||||
        overviewRulerBorder: false, // 不要滚动条的边框
 | 
			
		||||
        tabSize: 2, // tab 缩进长度
 | 
			
		||||
        // fontFamily: 'JetBrainsMono', // 字体 暂时不要设置,否则光标容易错位
 | 
			
		||||
        fontWeight: 'bold',
 | 
			
		||||
        // letterSpacing: 1, 字符间距
 | 
			
		||||
        // quickSuggestions:false, // 禁用代码提示
 | 
			
		||||
        minimap: {
 | 
			
		||||
            enabled: false, // 不要小地图
 | 
			
		||||
        },
 | 
			
		||||
    });
 | 
			
		||||
    monacoEditor = monacoEditorRef.value.getEditor();
 | 
			
		||||
 | 
			
		||||
    // 注册快捷键:ctrl + R 运行选中的sql
 | 
			
		||||
    monacoEditor.addAction({
 | 
			
		||||
@@ -294,11 +231,6 @@ const initMonacoEditor = () => {
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    // 如果sql有值,则默认赋值
 | 
			
		||||
    if (state.sql) {
 | 
			
		||||
        monacoEditor.getModel()?.setValue(state.sql);
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
@@ -308,7 +240,7 @@ const onDragSetHeight = () => {
 | 
			
		||||
    document.onmousemove = (e) => {
 | 
			
		||||
        e.preventDefault();
 | 
			
		||||
        //得到鼠标拖动的宽高距离:取绝对值
 | 
			
		||||
        state.editorHeight = `${document.getElementById('MonacoTextarea-' + state.ti.key)!.offsetHeight + e.movementY}px`;
 | 
			
		||||
        state.editorHeight = `${document.getElementById('MonacoTextarea-' + state.ti.key)!.clientHeight + e.movementY}px`;
 | 
			
		||||
        state.tableDataHeight -= e.movementY;
 | 
			
		||||
    };
 | 
			
		||||
    document.onmouseup = () => {
 | 
			
		||||
@@ -590,308 +522,6 @@ const submitUpdateFields = () => {
 | 
			
		||||
const cancelUpdateFields = () => {
 | 
			
		||||
    dbTableRef.value.cancelUpdateFields();
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const registerSqlCompletionItemProvider = () => {
 | 
			
		||||
    // 参考 https://microsoft.github.io/monaco-editor/playground.html#extending-language-services-completion-provider-example
 | 
			
		||||
    self.completionItemProvider =
 | 
			
		||||
        self.completionItemProvider ||
 | 
			
		||||
        monaco.languages.registerCompletionItemProvider('sql', {
 | 
			
		||||
            triggerCharacters: ['.', ' '],
 | 
			
		||||
            provideCompletionItems: async (model: editor.ITextModel, position: Position): Promise<languages.CompletionList | null | undefined> => {
 | 
			
		||||
                let word = model.getWordUntilPosition(position);
 | 
			
		||||
                const nowTab = props.data;
 | 
			
		||||
                if (!nowTab) {
 | 
			
		||||
                    return;
 | 
			
		||||
                }
 | 
			
		||||
                const { db, dbId } = nowTab;
 | 
			
		||||
                const dbInst = DbInst.getInst(dbId);
 | 
			
		||||
                const { lineNumber, column } = position;
 | 
			
		||||
                const { startColumn, endColumn } = word;
 | 
			
		||||
 | 
			
		||||
                // 当前行文本
 | 
			
		||||
                let lineContent = model.getLineContent(lineNumber);
 | 
			
		||||
                // 注释行不需要代码提示
 | 
			
		||||
                if (lineContent.startsWith('--')) {
 | 
			
		||||
                    return { suggestions: [] };
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                let range = {
 | 
			
		||||
                    startLineNumber: lineNumber,
 | 
			
		||||
                    endLineNumber: lineNumber,
 | 
			
		||||
                    startColumn,
 | 
			
		||||
                    endColumn,
 | 
			
		||||
                };
 | 
			
		||||
 | 
			
		||||
                //  光标前文本
 | 
			
		||||
                const textBeforePointer = model.getValueInRange({
 | 
			
		||||
                    startLineNumber: lineNumber,
 | 
			
		||||
                    startColumn: 0,
 | 
			
		||||
                    endLineNumber: lineNumber,
 | 
			
		||||
                    endColumn: column,
 | 
			
		||||
                });
 | 
			
		||||
                const textBeforePointerMulti = model.getValueInRange({
 | 
			
		||||
                    startLineNumber: 1,
 | 
			
		||||
                    startColumn: 0,
 | 
			
		||||
                    endLineNumber: lineNumber,
 | 
			
		||||
                    endColumn: column,
 | 
			
		||||
                });
 | 
			
		||||
                // 光标后文本
 | 
			
		||||
                const textAfterPointerMulti = model.getValueInRange({
 | 
			
		||||
                    startLineNumber: lineNumber,
 | 
			
		||||
                    startColumn: column,
 | 
			
		||||
                    endLineNumber: model.getLineCount(),
 | 
			
		||||
                    endColumn: model.getLineMaxColumn(model.getLineCount()),
 | 
			
		||||
                });
 | 
			
		||||
                // // const nextTokens = textAfterPointer.trim().split(/\s+/)
 | 
			
		||||
                // // const nextToken = nextTokens[0].toLowerCase()
 | 
			
		||||
                const tokens = textBeforePointer.trim().split(/\s+/);
 | 
			
		||||
                let lastToken = tokens[tokens.length - 1].toLowerCase();
 | 
			
		||||
                const secondToken = (tokens.length > 2 && tokens[tokens.length - 2].toLowerCase()) || '';
 | 
			
		||||
 | 
			
		||||
                const dbs = (nowTab.params && nowTab.params.dbs && nowTab.params.dbs) || [];
 | 
			
		||||
                // console.log("光标前文本:=>" + textBeforePointerMulti)
 | 
			
		||||
                // console.log("最后输入的:=>" + lastToken)
 | 
			
		||||
 | 
			
		||||
                let suggestions: languages.CompletionItem[] = [];
 | 
			
		||||
                const tables = await dbInst.loadTables(db);
 | 
			
		||||
 | 
			
		||||
                async function hintTableColumns(tableName: any, db: any) {
 | 
			
		||||
                    let dbHits = await dbInst.loadDbHints(db);
 | 
			
		||||
                    let columns = dbHits[tableName];
 | 
			
		||||
                    let suggestions: languages.CompletionItem[] = [];
 | 
			
		||||
                    columns?.forEach((a: string, index: any) => {
 | 
			
		||||
                        // 字段数据格式  字段名 字段注释,  如: create_time  [datetime][创建时间]
 | 
			
		||||
                        const nameAndComment = a.split('  ');
 | 
			
		||||
                        const fieldName = nameAndComment[0];
 | 
			
		||||
                        suggestions.push({
 | 
			
		||||
                            label: {
 | 
			
		||||
                                label: a,
 | 
			
		||||
                                description: 'column',
 | 
			
		||||
                            },
 | 
			
		||||
                            kind: monaco.languages.CompletionItemKind.Property,
 | 
			
		||||
                            detail: '', // 不显示detail, 否则选中时备注等会被遮挡
 | 
			
		||||
                            insertText: fieldName, // create_time
 | 
			
		||||
                            range,
 | 
			
		||||
                            sortText: 100 + index + '', // 使用表字段声明顺序排序,排序需为字符串类型
 | 
			
		||||
                        });
 | 
			
		||||
                    });
 | 
			
		||||
                    return suggestions;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                if (lastToken.indexOf('.') > -1 || secondToken.indexOf('.') > -1) {
 | 
			
		||||
                    // 如果是.触发代码提示,则进行【 库.表名联想 】 或 【 表别名.表字段联想 】
 | 
			
		||||
                    let str = lastToken.substring(0, lastToken.lastIndexOf('.'));
 | 
			
		||||
                    if (lastToken.trim().startsWith('.')) {
 | 
			
		||||
                        str = secondToken;
 | 
			
		||||
                    }
 | 
			
		||||
                    // 如果字符串粘连起了如:'a.creator,a.',需要重新取出别名
 | 
			
		||||
                    let aliasArr = lastToken.split(',');
 | 
			
		||||
                    if (aliasArr.length > 1) {
 | 
			
		||||
                        lastToken = aliasArr[aliasArr.length - 1];
 | 
			
		||||
                        str = lastToken.substring(0, lastToken.lastIndexOf('.'));
 | 
			
		||||
                        if (lastToken.trim().startsWith('.')) {
 | 
			
		||||
                            str = secondToken;
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                    // 库.表名联想
 | 
			
		||||
                    if (dbs && dbs.filter((a: any) => a === str)?.length > 0) {
 | 
			
		||||
                        let tables = await dbInst.loadTables(str);
 | 
			
		||||
                        let suggestions: languages.CompletionItem[] = [];
 | 
			
		||||
                        for (let item of tables) {
 | 
			
		||||
                            const { tableName, tableComment } = item;
 | 
			
		||||
                            suggestions.push({
 | 
			
		||||
                                label: {
 | 
			
		||||
                                    label: tableName + (tableComment ? ' - ' + tableComment : ''),
 | 
			
		||||
                                    description: 'table',
 | 
			
		||||
                                },
 | 
			
		||||
                                kind: monaco.languages.CompletionItemKind.File,
 | 
			
		||||
                                insertText: tableName,
 | 
			
		||||
                                range,
 | 
			
		||||
                            });
 | 
			
		||||
                        }
 | 
			
		||||
                        return { suggestions };
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    let sql = textBeforePointerMulti.split(';')[textBeforePointerMulti.split(';').length - 1] + textAfterPointerMulti.split(';')[0];
 | 
			
		||||
                    // 表别名.表字段联想
 | 
			
		||||
                    let tableInfo = getTableByAlias(sql, db, str);
 | 
			
		||||
                    if (tableInfo.tableName) {
 | 
			
		||||
                        let tableName = tableInfo.tableName;
 | 
			
		||||
                        let db = tableInfo.dbName;
 | 
			
		||||
                        // 取出表名并提示
 | 
			
		||||
                        let suggestions = await hintTableColumns(tableName, db);
 | 
			
		||||
                        if (suggestions.length > 0) {
 | 
			
		||||
                            return { suggestions };
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                    return { suggestions: [] };
 | 
			
		||||
                } else {
 | 
			
		||||
                    // 如果sql里含有表名,则提示表字段
 | 
			
		||||
                    let mat = textBeforePointerMulti.match(/[from|update]\n*\s+\n*(\w+)\n*\s+\n*/i);
 | 
			
		||||
                    if (mat && mat.length > 1) {
 | 
			
		||||
                        let tableName = mat[1];
 | 
			
		||||
                        // 取出表名并提示
 | 
			
		||||
                        let addSuggestions = await hintTableColumns(tableName, db);
 | 
			
		||||
                        if (addSuggestions.length > 0) {
 | 
			
		||||
                            suggestions = suggestions.concat(addSuggestions);
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                // 表名联想
 | 
			
		||||
                tables.forEach((tableMeta: any) => {
 | 
			
		||||
                    const { tableName, tableComment } = tableMeta;
 | 
			
		||||
                    suggestions.push({
 | 
			
		||||
                        label: {
 | 
			
		||||
                            label: tableName + ' - ' + tableComment,
 | 
			
		||||
                            description: 'table',
 | 
			
		||||
                        },
 | 
			
		||||
                        kind: monaco.languages.CompletionItemKind.File,
 | 
			
		||||
                        detail: tableComment,
 | 
			
		||||
                        insertText: tableName + ' ',
 | 
			
		||||
                        range,
 | 
			
		||||
                    });
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
                // mysql关键字
 | 
			
		||||
                sqlCompletionKeywords.forEach((item: any) => {
 | 
			
		||||
                    suggestions.push({
 | 
			
		||||
                        label: {
 | 
			
		||||
                            label: item,
 | 
			
		||||
                            description: 'keyword',
 | 
			
		||||
                        },
 | 
			
		||||
                        kind: monaco.languages.CompletionItemKind.Keyword,
 | 
			
		||||
                        insertText: item,
 | 
			
		||||
                        range,
 | 
			
		||||
                    });
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
                // 操作符
 | 
			
		||||
                sqlCompletionOperators.forEach((item: any) => {
 | 
			
		||||
                    suggestions.push({
 | 
			
		||||
                        label: {
 | 
			
		||||
                            label: item,
 | 
			
		||||
                            description: 'opt',
 | 
			
		||||
                        },
 | 
			
		||||
                        kind: monaco.languages.CompletionItemKind.Operator,
 | 
			
		||||
                        insertText: item,
 | 
			
		||||
                        range,
 | 
			
		||||
                    });
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
                let replacedFunctions = [] as string[];
 | 
			
		||||
 | 
			
		||||
                // 添加的函数
 | 
			
		||||
                addSqlLanguage.replaceFunctions.forEach((item: any) => {
 | 
			
		||||
                    replacedFunctions.push(item.label);
 | 
			
		||||
                    suggestions.push({
 | 
			
		||||
                        label: {
 | 
			
		||||
                            label: item.label,
 | 
			
		||||
                            description: item.description,
 | 
			
		||||
                        },
 | 
			
		||||
                        kind: monaco.languages.CompletionItemKind.Function,
 | 
			
		||||
                        insertText: item.insertText,
 | 
			
		||||
                        range,
 | 
			
		||||
                    });
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
                // 内置函数
 | 
			
		||||
                sqlCompletionBuiltinFunctions.forEach((item: any) => {
 | 
			
		||||
                    replacedFunctions.indexOf(item) < 0 &&
 | 
			
		||||
                        suggestions.push({
 | 
			
		||||
                            label: {
 | 
			
		||||
                                label: item,
 | 
			
		||||
                                description: 'func',
 | 
			
		||||
                            },
 | 
			
		||||
                            kind: monaco.languages.CompletionItemKind.Function,
 | 
			
		||||
                            insertText: item,
 | 
			
		||||
                            range,
 | 
			
		||||
                        });
 | 
			
		||||
                });
 | 
			
		||||
                // 内置变量
 | 
			
		||||
                sqlCompletionBuiltinVariables.forEach((item: string) => {
 | 
			
		||||
                    suggestions.push({
 | 
			
		||||
                        label: {
 | 
			
		||||
                            label: item,
 | 
			
		||||
                            description: 'var',
 | 
			
		||||
                        },
 | 
			
		||||
                        kind: monaco.languages.CompletionItemKind.Variable,
 | 
			
		||||
                        insertText: item,
 | 
			
		||||
                        range,
 | 
			
		||||
                    });
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
                // 库名提示
 | 
			
		||||
                if (dbs && dbs.length > 0) {
 | 
			
		||||
                    dbs.forEach((a: any) => {
 | 
			
		||||
                        suggestions.push({
 | 
			
		||||
                            label: {
 | 
			
		||||
                                label: a,
 | 
			
		||||
                                description: 'schema',
 | 
			
		||||
                            },
 | 
			
		||||
                            kind: monaco.languages.CompletionItemKind.Folder,
 | 
			
		||||
                            insertText: a,
 | 
			
		||||
                            range,
 | 
			
		||||
                        });
 | 
			
		||||
                    });
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                // 默认提示
 | 
			
		||||
                return {
 | 
			
		||||
                    suggestions: suggestions,
 | 
			
		||||
                };
 | 
			
		||||
            },
 | 
			
		||||
        });
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 根据别名获取sql里的表名
 | 
			
		||||
 * @param sql sql
 | 
			
		||||
 * @param db 默认数据库
 | 
			
		||||
 * @param alias 别名
 | 
			
		||||
 */
 | 
			
		||||
const getTableByAlias = (sql: string, db: string, alias: string): { dbName: string; tableName: string } => {
 | 
			
		||||
    // 表别名:表名
 | 
			
		||||
    let result = {};
 | 
			
		||||
    let defName = '';
 | 
			
		||||
    let defResult = {};
 | 
			
		||||
    // 正则匹配取出表名和表别名
 | 
			
		||||
    // 测试sql
 | 
			
		||||
    /*
 | 
			
		||||
 | 
			
		||||
    `select * from database.Outvisit l
 | 
			
		||||
left join patient p on l.patid=p.patientid
 | 
			
		||||
join patstatic c on   l.patid=c.patid inner join patphone  ph  on l.patid=ph.patid
 | 
			
		||||
where l.name='kevin' and exsits(select 1 from pharmacywestpas pw where p.outvisitid=l.outvisitid)
 | 
			
		||||
unit all
 | 
			
		||||
select * from invisit v where`.match(/(join|from)\s+(\w*-?\w*\.?\w+)\s*(as)?\s*(\w*)/gi)
 | 
			
		||||
     */
 | 
			
		||||
    let match = sql.match(/(join|from)\n*\s+\n*(\w*-?\w*\.?\w+)\s*(as)?\s*(\w*)\n*/gi);
 | 
			
		||||
    if (match && match.length > 0) {
 | 
			
		||||
        match.forEach((a) => {
 | 
			
		||||
            // 去掉前缀,取出
 | 
			
		||||
            let t = a
 | 
			
		||||
                .substring(5, a.length)
 | 
			
		||||
                .replaceAll(/\s+/g, ' ')
 | 
			
		||||
                .replaceAll(/\s+as\s+/gi, ' ')
 | 
			
		||||
                .replaceAll(/\r\n/g, ' ')
 | 
			
		||||
                .trim()
 | 
			
		||||
                .split(/\s+/);
 | 
			
		||||
            let withDb = t[0].split('.');
 | 
			
		||||
            // 表名是 db名.表名
 | 
			
		||||
            let tName = withDb.length > 1 ? withDb[1] : withDb[0];
 | 
			
		||||
            let dbName = withDb.length > 1 ? withDb[0] : db || '';
 | 
			
		||||
            if (t.length == 2) {
 | 
			
		||||
                // 表别名:表名
 | 
			
		||||
                result[t[1]] = { tableName: tName, dbName };
 | 
			
		||||
            } else {
 | 
			
		||||
                // 只有表名无别名 取第一个无别名的表为默认表
 | 
			
		||||
                !defName && (defResult = { tableName: tName, dbName: db });
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
    return result[alias] || defResult;
 | 
			
		||||
};
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style lang="scss">
 | 
			
		||||
@@ -905,12 +535,6 @@ select * from invisit v where`.match(/(join|from)\s+(\w*-?\w*\.?\w+)\s*(as)?\s*(
 | 
			
		||||
    text-decoration: none;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.sqlEditor {
 | 
			
		||||
    font-size: 8pt;
 | 
			
		||||
    font-weight: 600;
 | 
			
		||||
    border: 1px solid var(--el-border-color-light, #ebeef5);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.update_field_active {
 | 
			
		||||
    background-color: var(--el-color-success);
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -3,6 +3,18 @@ import { dbApi } from './api';
 | 
			
		||||
import { getTextWidth } from '@/common/utils/string';
 | 
			
		||||
import SqlExecBox from './component/SqlExecBox';
 | 
			
		||||
 | 
			
		||||
import { language as sqlLanguage } from 'monaco-editor/esm/vs/basic-languages/mysql/mysql.js';
 | 
			
		||||
import { language as addSqlLanguage } from './lang/mysql.js';
 | 
			
		||||
import * as monaco from 'monaco-editor/esm/vs/editor/editor.api';
 | 
			
		||||
import { editor, languages, Position } from 'monaco-editor';
 | 
			
		||||
 | 
			
		||||
import { registerCompletionItemProvider } from '@/components/monaco/completionItemProvider';
 | 
			
		||||
 | 
			
		||||
const sqlCompletionKeywords = [...sqlLanguage.keywords, ...addSqlLanguage.keywords];
 | 
			
		||||
const sqlCompletionOperators = [...sqlLanguage.operators, ...addSqlLanguage.operators];
 | 
			
		||||
const sqlCompletionBuiltinFunctions = [...sqlLanguage.builtinFunctions, ...addSqlLanguage.builtinFunctions];
 | 
			
		||||
const sqlCompletionBuiltinVariables = [...sqlLanguage.builtinVariables, ...addSqlLanguage.builtinVariables];
 | 
			
		||||
 | 
			
		||||
const dbInstCache: Map<number, DbInst> = new Map();
 | 
			
		||||
 | 
			
		||||
export class DbInst {
 | 
			
		||||
@@ -463,3 +475,276 @@ export type FieldsMeta = {
 | 
			
		||||
    // 新值
 | 
			
		||||
    newValue: string;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 注册数据库表、字段等信息提示
 | 
			
		||||
 *
 | 
			
		||||
 * @param language 语言
 | 
			
		||||
 * @param dbId 数据库id
 | 
			
		||||
 * @param db 库名
 | 
			
		||||
 * @param dbs 该库所有库名
 | 
			
		||||
 */
 | 
			
		||||
export function registerDbCompletionItemProvider(language: string, dbId: number, db: string, dbs: [] = []) {
 | 
			
		||||
    registerCompletionItemProvider(language, {
 | 
			
		||||
        triggerCharacters: ['.', ' '],
 | 
			
		||||
        provideCompletionItems: async (model: editor.ITextModel, position: Position): Promise<languages.CompletionList | null | undefined> => {
 | 
			
		||||
            let word = model.getWordUntilPosition(position);
 | 
			
		||||
            const dbInst = DbInst.getInst(dbId);
 | 
			
		||||
            const { lineNumber, column } = position;
 | 
			
		||||
            const { startColumn, endColumn } = word;
 | 
			
		||||
 | 
			
		||||
            // 当前行文本
 | 
			
		||||
            let lineContent = model.getLineContent(lineNumber);
 | 
			
		||||
            // 注释行不需要代码提示
 | 
			
		||||
            if (lineContent.startsWith('--')) {
 | 
			
		||||
                return { suggestions: [] };
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            let range = {
 | 
			
		||||
                startLineNumber: lineNumber,
 | 
			
		||||
                endLineNumber: lineNumber,
 | 
			
		||||
                startColumn,
 | 
			
		||||
                endColumn,
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
            //  光标前文本
 | 
			
		||||
            const textBeforePointer = model.getValueInRange({
 | 
			
		||||
                startLineNumber: lineNumber,
 | 
			
		||||
                startColumn: 0,
 | 
			
		||||
                endLineNumber: lineNumber,
 | 
			
		||||
                endColumn: column,
 | 
			
		||||
            });
 | 
			
		||||
            // // const nextTokens = textAfterPointer.trim().split(/\s+/)
 | 
			
		||||
            // // const nextToken = nextTokens[0].toLowerCase()
 | 
			
		||||
            const tokens = textBeforePointer.trim().split(/\s+/);
 | 
			
		||||
            let lastToken = tokens[tokens.length - 1].toLowerCase();
 | 
			
		||||
            const secondToken = (tokens.length > 2 && tokens[tokens.length - 2].toLowerCase()) || '';
 | 
			
		||||
 | 
			
		||||
            // console.log("光标前文本:=>" + textBeforePointerMulti)
 | 
			
		||||
            // console.log("最后输入的:=>" + lastToken)
 | 
			
		||||
 | 
			
		||||
            let suggestions: languages.CompletionItem[] = [];
 | 
			
		||||
 | 
			
		||||
            let alias = '';
 | 
			
		||||
            if (lastToken.indexOf('.') > -1 || secondToken.indexOf('.') > -1) {
 | 
			
		||||
                // 如果是.触发代码提示,则进行【 库.表名联想 】 或 【 表别名.表字段联想 】
 | 
			
		||||
                alias = lastToken.substring(0, lastToken.lastIndexOf('.'));
 | 
			
		||||
                if (lastToken.trim().startsWith('.')) {
 | 
			
		||||
                    alias = secondToken;
 | 
			
		||||
                }
 | 
			
		||||
                // 如果字符串粘连起了如:'a.creator,a.',需要重新取出别名
 | 
			
		||||
                let aliasArr = lastToken.split(',');
 | 
			
		||||
                if (aliasArr.length > 1) {
 | 
			
		||||
                    lastToken = aliasArr[aliasArr.length - 1];
 | 
			
		||||
                    alias = lastToken.substring(0, lastToken.lastIndexOf('.'));
 | 
			
		||||
                    if (lastToken.trim().startsWith('.')) {
 | 
			
		||||
                        alias = secondToken;
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // 获取光标所在行之前的所有文本内容
 | 
			
		||||
            const textBeforeCursor = model.getValueInRange({
 | 
			
		||||
                startLineNumber: 1,
 | 
			
		||||
                startColumn: 0,
 | 
			
		||||
                endLineNumber: lineNumber,
 | 
			
		||||
                endColumn: column,
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            // 获取光标所在行之后的所有文本内容
 | 
			
		||||
            const textAfterCursor = model.getValueInRange({
 | 
			
		||||
                startLineNumber: lineNumber,
 | 
			
		||||
                startColumn: column,
 | 
			
		||||
                endLineNumber: model.getLineCount(),
 | 
			
		||||
                endColumn: model.getLineMaxColumn(model.getLineCount()),
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            // 检测光标前后文本中的分号位置,确定完整 SQL 语句的范围
 | 
			
		||||
            const start = textBeforeCursor.lastIndexOf(';');
 | 
			
		||||
            const end = textAfterCursor.indexOf(';');
 | 
			
		||||
 | 
			
		||||
            let sqlStatement = '';
 | 
			
		||||
            // 如果光标前后都有分号,则取二者之间的文本作为完整 SQL 语句
 | 
			
		||||
            if (start !== -1 && end !== -1) {
 | 
			
		||||
                sqlStatement = textBeforeCursor.substring(start + 1) + textAfterCursor.substring(0, end);
 | 
			
		||||
            }
 | 
			
		||||
            // 如果只有光标前面有分号,则取分号后的文本作为完整 SQL 语句
 | 
			
		||||
            else if (start !== -1) {
 | 
			
		||||
                sqlStatement = textBeforeCursor.substring(start + 1) + textAfterCursor;
 | 
			
		||||
            }
 | 
			
		||||
            // 如果只有光标后面有分号,则取分号前的文本作为完整 SQL 语句
 | 
			
		||||
            else if (end !== -1) {
 | 
			
		||||
                sqlStatement = textBeforeCursor + textAfterCursor.substring(0, end);
 | 
			
		||||
            }
 | 
			
		||||
            // 如果光标前后都没有分号,则取整个文本作为完整 SQL 语句
 | 
			
		||||
            else {
 | 
			
		||||
                sqlStatement = textBeforeCursor + textAfterCursor;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            const tableName = getTableName4SqlCtx(sqlStatement, alias);
 | 
			
		||||
            // 提出到表名,则将表对应的字段也添加进提示建议
 | 
			
		||||
            if (tableName) {
 | 
			
		||||
                let dbHits = await dbInst.loadDbHints(db);
 | 
			
		||||
                let columns = dbHits[tableName];
 | 
			
		||||
                columns?.forEach((a: string, index: any) => {
 | 
			
		||||
                    // 字段数据格式  字段名 字段注释,  如: create_time  [datetime][创建时间]
 | 
			
		||||
                    const nameAndComment = a.split('  ');
 | 
			
		||||
                    const fieldName = nameAndComment[0];
 | 
			
		||||
                    suggestions.push({
 | 
			
		||||
                        label: {
 | 
			
		||||
                            label: a,
 | 
			
		||||
                            description: 'column',
 | 
			
		||||
                        },
 | 
			
		||||
                        kind: monaco.languages.CompletionItemKind.Property,
 | 
			
		||||
                        detail: '', // 不显示detail, 否则选中时备注等会被遮挡
 | 
			
		||||
                        insertText: fieldName, // create_time
 | 
			
		||||
                        range,
 | 
			
		||||
                        sortText: 100 + index + '', // 使用表字段声明顺序排序,排序需为字符串类型
 | 
			
		||||
                    });
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
                // 若存在字段提示,并且有别名,则提示字段即可,不完善后续的表名以及函数等
 | 
			
		||||
                if (suggestions.length > 0 && alias) {
 | 
			
		||||
                    return {
 | 
			
		||||
                        suggestions: suggestions,
 | 
			
		||||
                    };
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            const tables = await dbInst.loadTables(db);
 | 
			
		||||
 | 
			
		||||
            // 表名联想
 | 
			
		||||
            tables.forEach((tableMeta: any, index: any) => {
 | 
			
		||||
                const { tableName, tableComment } = tableMeta;
 | 
			
		||||
                suggestions.push({
 | 
			
		||||
                    label: {
 | 
			
		||||
                        label: tableName + ' - ' + tableComment,
 | 
			
		||||
                        description: 'table',
 | 
			
		||||
                    },
 | 
			
		||||
                    kind: monaco.languages.CompletionItemKind.File,
 | 
			
		||||
                    detail: tableComment,
 | 
			
		||||
                    insertText: tableName + ' ',
 | 
			
		||||
                    range,
 | 
			
		||||
                    sortText: 300 + index + '',
 | 
			
		||||
                });
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            // mysql关键字
 | 
			
		||||
            sqlCompletionKeywords.forEach((item: any) => {
 | 
			
		||||
                suggestions.push({
 | 
			
		||||
                    label: {
 | 
			
		||||
                        label: item,
 | 
			
		||||
                        description: 'keyword',
 | 
			
		||||
                    },
 | 
			
		||||
                    kind: monaco.languages.CompletionItemKind.Keyword,
 | 
			
		||||
                    insertText: item,
 | 
			
		||||
                    range,
 | 
			
		||||
                });
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            // 操作符
 | 
			
		||||
            sqlCompletionOperators.forEach((item: any) => {
 | 
			
		||||
                suggestions.push({
 | 
			
		||||
                    label: {
 | 
			
		||||
                        label: item,
 | 
			
		||||
                        description: 'opt',
 | 
			
		||||
                    },
 | 
			
		||||
                    kind: monaco.languages.CompletionItemKind.Operator,
 | 
			
		||||
                    insertText: item,
 | 
			
		||||
                    range,
 | 
			
		||||
                });
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            let replacedFunctions = [] as string[];
 | 
			
		||||
 | 
			
		||||
            // 添加的函数
 | 
			
		||||
            addSqlLanguage.replaceFunctions.forEach((item: any) => {
 | 
			
		||||
                replacedFunctions.push(item.label);
 | 
			
		||||
                suggestions.push({
 | 
			
		||||
                    label: {
 | 
			
		||||
                        label: item.label,
 | 
			
		||||
                        description: item.description,
 | 
			
		||||
                    },
 | 
			
		||||
                    kind: monaco.languages.CompletionItemKind.Function,
 | 
			
		||||
                    insertText: item.insertText,
 | 
			
		||||
                    range,
 | 
			
		||||
                });
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            // 内置函数
 | 
			
		||||
            sqlCompletionBuiltinFunctions.forEach((item: any) => {
 | 
			
		||||
                replacedFunctions.indexOf(item) < 0 &&
 | 
			
		||||
                    suggestions.push({
 | 
			
		||||
                        label: {
 | 
			
		||||
                            label: item,
 | 
			
		||||
                            description: 'func',
 | 
			
		||||
                        },
 | 
			
		||||
                        kind: monaco.languages.CompletionItemKind.Function,
 | 
			
		||||
                        insertText: item,
 | 
			
		||||
                        range,
 | 
			
		||||
                    });
 | 
			
		||||
            });
 | 
			
		||||
            // 内置变量
 | 
			
		||||
            sqlCompletionBuiltinVariables.forEach((item: string) => {
 | 
			
		||||
                suggestions.push({
 | 
			
		||||
                    label: {
 | 
			
		||||
                        label: item,
 | 
			
		||||
                        description: 'var',
 | 
			
		||||
                    },
 | 
			
		||||
                    kind: monaco.languages.CompletionItemKind.Variable,
 | 
			
		||||
                    insertText: item,
 | 
			
		||||
                    range,
 | 
			
		||||
                });
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            // 库名提示
 | 
			
		||||
            if (dbs && dbs.length > 0) {
 | 
			
		||||
                dbs.forEach((a: any) => {
 | 
			
		||||
                    suggestions.push({
 | 
			
		||||
                        label: {
 | 
			
		||||
                            label: a,
 | 
			
		||||
                            description: 'schema',
 | 
			
		||||
                        },
 | 
			
		||||
                        kind: monaco.languages.CompletionItemKind.Folder,
 | 
			
		||||
                        insertText: a,
 | 
			
		||||
                        range,
 | 
			
		||||
                    });
 | 
			
		||||
                });
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // 默认提示
 | 
			
		||||
            return {
 | 
			
		||||
                suggestions: suggestions,
 | 
			
		||||
            };
 | 
			
		||||
        },
 | 
			
		||||
    });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function getTableName4SqlCtx(sql: string, alias: string = '') {
 | 
			
		||||
    // 去除多余的换行、空格和制表符
 | 
			
		||||
    sql = sql.replace(/[\r\n\s\t]+/g, ' ');
 | 
			
		||||
 | 
			
		||||
    // 提取所有可能的表名和别名
 | 
			
		||||
    const regex = /(?:(?:FROM|JOIN|UPDATE)\s+(\S+)\s+(?:AS\s+)?(\S+))/gi;
 | 
			
		||||
    let matches;
 | 
			
		||||
    const tables = [];
 | 
			
		||||
 | 
			
		||||
    // 使用正则表达式匹配所有的表和别名
 | 
			
		||||
    while ((matches = regex.exec(sql)) !== null) {
 | 
			
		||||
        const tableName = matches[1].replace(/[`"]/g, '');
 | 
			
		||||
        const tableAlias = matches[2] ? matches[2].replace(/[`"]/g, '') : tableName;
 | 
			
		||||
        tables.push({ tableName, tableAlias });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // console.log('sql....', sql);
 | 
			
		||||
    // console.log('alias....', alias);
 | 
			
		||||
    // console.log('parset tables...', tables);
 | 
			
		||||
    if (alias) {
 | 
			
		||||
        // 如果指定了别名参数,则返回对应的表名
 | 
			
		||||
        const table = tables.find((t) => t.tableAlias === alias);
 | 
			
		||||
        return table ? table.tableName : '';
 | 
			
		||||
    } else {
 | 
			
		||||
        // 如果未指定别名参数,则返回第一个表名
 | 
			
		||||
        return tables.length > 0 ? tables[0].tableName : '';
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -124,7 +124,7 @@ import { formatByteSize } from '@/common/utils/format';
 | 
			
		||||
import { dbApi } from '../api';
 | 
			
		||||
import SqlExecBox from '../component/SqlExecBox';
 | 
			
		||||
import config from '@/common/config';
 | 
			
		||||
import { getSession } from '@/common/utils/storage';
 | 
			
		||||
import { getToken } from '@/common/utils/storage';
 | 
			
		||||
import { isTrue } from '@/common/assert';
 | 
			
		||||
 | 
			
		||||
const DbTableEdit = defineAsyncComponent(() => import('./DbTableEdit.vue'));
 | 
			
		||||
@@ -259,9 +259,7 @@ const dump = (db: string) => {
 | 
			
		||||
    const a = document.createElement('a');
 | 
			
		||||
    a.setAttribute(
 | 
			
		||||
        'href',
 | 
			
		||||
        `${config.baseApiUrl}/dbs/${props.dbId}/dump?db=${db}&type=${state.dumpInfo.type}&tables=${state.dumpInfo.tables.join(',')}&token=${getSession(
 | 
			
		||||
            'token'
 | 
			
		||||
        )}`
 | 
			
		||||
        `${config.baseApiUrl}/dbs/${props.dbId}/dump?db=${db}&type=${state.dumpInfo.type}&tables=${state.dumpInfo.tables.join(',')}&token=${getToken()}`
 | 
			
		||||
    );
 | 
			
		||||
    a.click();
 | 
			
		||||
    state.showDumpInfo = false;
 | 
			
		||||
 
 | 
			
		||||
@@ -4,13 +4,13 @@
 | 
			
		||||
            <el-form :model="form" ref="machineForm" :rules="rules" label-width="auto">
 | 
			
		||||
                <el-tabs v-model="tabActiveName">
 | 
			
		||||
                    <el-tab-pane label="基础信息" name="basic">
 | 
			
		||||
                        <el-form-item prop="tagId" label="标签:">
 | 
			
		||||
                        <el-form-item prop="tagId" label="标签">
 | 
			
		||||
                            <tag-select v-model="form.tagId" v-model:tag-path="form.tagPath" style="width: 100%" />
 | 
			
		||||
                        </el-form-item>
 | 
			
		||||
                        <el-form-item prop="name" label="名称:" required>
 | 
			
		||||
                        <el-form-item prop="name" label="名称" required>
 | 
			
		||||
                            <el-input v-model.trim="form.name" placeholder="请输入机器别名" auto-complete="off"></el-input>
 | 
			
		||||
                        </el-form-item>
 | 
			
		||||
                        <el-form-item prop="ip" label="ip:" required>
 | 
			
		||||
                        <el-form-item prop="ip" label="ip" required>
 | 
			
		||||
                            <el-col :span="18">
 | 
			
		||||
                                <el-input :disabled="form.id" v-model.trim="form.ip" placeholder="主机ip" auto-complete="off"> </el-input>
 | 
			
		||||
                            </el-col>
 | 
			
		||||
@@ -20,36 +20,36 @@
 | 
			
		||||
                            </el-col>
 | 
			
		||||
                        </el-form-item>
 | 
			
		||||
 | 
			
		||||
                        <el-form-item prop="username" label="用户名:">
 | 
			
		||||
                        <el-form-item prop="username" label="用户名">
 | 
			
		||||
                            <el-input v-model.trim="form.username" placeholder="请输授权用户名" autocomplete="new-password"> </el-input>
 | 
			
		||||
                        </el-form-item>
 | 
			
		||||
 | 
			
		||||
                        <el-form-item label="认证方式:">
 | 
			
		||||
                        <el-form-item label="认证方式">
 | 
			
		||||
                            <el-select @change="changeAuthMethod" style="width: 100%" v-model="state.authType" placeholder="请选认证方式">
 | 
			
		||||
                                <el-option key="1" label="密码" :value="1"> </el-option>
 | 
			
		||||
                                <el-option key="2" label="授权凭证" :value="2"> </el-option>
 | 
			
		||||
                            </el-select>
 | 
			
		||||
                        </el-form-item>
 | 
			
		||||
                        <el-form-item v-if="state.authType == 1" prop="password" label="密码:">
 | 
			
		||||
                        <el-form-item v-if="state.authType == 1" prop="password" label="密码">
 | 
			
		||||
                            <el-input type="password" show-password v-model.trim="form.password" placeholder="请输入密码" autocomplete="new-password">
 | 
			
		||||
                            </el-input>
 | 
			
		||||
                        </el-form-item>
 | 
			
		||||
 | 
			
		||||
                        <el-form-item v-if="state.authType == 2" prop="authCertId" label="授权凭证:" required>
 | 
			
		||||
                        <el-form-item v-if="state.authType == 2" prop="authCertId" label="授权凭证" required>
 | 
			
		||||
                            <auth-cert-select ref="authCertSelectRef" v-model="form.authCertId" />
 | 
			
		||||
                        </el-form-item>
 | 
			
		||||
 | 
			
		||||
                        <el-form-item prop="remark" label="备注:">
 | 
			
		||||
                        <el-form-item prop="remark" label="备注">
 | 
			
		||||
                            <el-input type="textarea" v-model="form.remark"></el-input>
 | 
			
		||||
                        </el-form-item>
 | 
			
		||||
                    </el-tab-pane>
 | 
			
		||||
 | 
			
		||||
                    <el-tab-pane label="其他配置" name="other">
 | 
			
		||||
                        <el-form-item prop="enableRecorder" label="终端回放:">
 | 
			
		||||
                        <el-form-item prop="enableRecorder" label="终端回放">
 | 
			
		||||
                            <el-checkbox v-model="form.enableRecorder" :true-label="1" :false-label="-1"></el-checkbox>
 | 
			
		||||
                        </el-form-item>
 | 
			
		||||
 | 
			
		||||
                        <el-form-item prop="sshTunnelMachineId" label="SSH隧道:">
 | 
			
		||||
                        <el-form-item prop="sshTunnelMachineId" label="SSH隧道">
 | 
			
		||||
                            <ssh-tunnel-select v-model="form.sshTunnelMachineId" />
 | 
			
		||||
                        </el-form-item>
 | 
			
		||||
                    </el-tab-pane>
 | 
			
		||||
 
 | 
			
		||||
@@ -81,7 +81,6 @@ import { ref, toRefs, reactive, watch } from 'vue';
 | 
			
		||||
import { ElMessage } from 'element-plus';
 | 
			
		||||
import { machineApi } from './api';
 | 
			
		||||
import { ScriptResultEnum } from './enums';
 | 
			
		||||
import { notEmpty } from '@/common/assert';
 | 
			
		||||
import MonacoEditor from '@/components/monaco/MonacoEditor.vue';
 | 
			
		||||
 | 
			
		||||
const props = defineProps({
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,6 @@
 | 
			
		||||
import Api from '@/common/Api';
 | 
			
		||||
import config from '@/common/config';
 | 
			
		||||
import { getSession } from '@/common/utils/storage';
 | 
			
		||||
import { getToken } from '@/common/utils/storage';
 | 
			
		||||
 | 
			
		||||
export const machineApi = {
 | 
			
		||||
    // 获取权限列表
 | 
			
		||||
@@ -63,5 +63,5 @@ export const cronJobApi = {
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export function getMachineTerminalSocketUrl(machineId: any) {
 | 
			
		||||
    return `${config.baseWsUrl}/machines/${machineId}/terminal?token=${getSession('token')}`;
 | 
			
		||||
    return `${config.baseWsUrl}/machines/${machineId}/terminal?token=${getToken()}`;
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -2,27 +2,27 @@
 | 
			
		||||
    <div>
 | 
			
		||||
        <el-dialog :title="title" v-model="dvisible" :show-close="false" :before-close="cancel" width="500px" :destroy-on-close="true">
 | 
			
		||||
            <el-form ref="acForm" :rules="rules" :model="form" label-width="auto">
 | 
			
		||||
                <el-form-item prop="name" label="名称:" required>
 | 
			
		||||
                <el-form-item prop="name" label="名称" required>
 | 
			
		||||
                    <el-input v-model="form.name"></el-input>
 | 
			
		||||
                </el-form-item>
 | 
			
		||||
                <el-form-item prop="authMethod" label="认证方式:" required>
 | 
			
		||||
                <el-form-item prop="authMethod" label="认证方式" required>
 | 
			
		||||
                    <el-select style="width: 100%" v-model="form.authMethod" placeholder="请选择认证方式">
 | 
			
		||||
                        <el-option key="1" label="密码" :value="1"> </el-option>
 | 
			
		||||
                        <el-option key="2" label="密钥" :value="2"> </el-option>
 | 
			
		||||
                    </el-select>
 | 
			
		||||
                </el-form-item>
 | 
			
		||||
                <el-form-item v-if="form.authMethod == 1" prop="password" label="密码:">
 | 
			
		||||
                <el-form-item v-if="form.authMethod == 1" prop="password" label="密码">
 | 
			
		||||
                    <el-input type="password" show-password clearable v-model.trim="form.password" placeholder="请输入密码" autocomplete="new-password">
 | 
			
		||||
                    </el-input>
 | 
			
		||||
                </el-form-item>
 | 
			
		||||
                <el-form-item v-if="form.authMethod == 2" prop="password" label="秘钥:">
 | 
			
		||||
                <el-form-item v-if="form.authMethod == 2" prop="password" label="秘钥">
 | 
			
		||||
                    <el-input type="textarea" :rows="5" v-model="form.password" placeholder="请将私钥文件内容拷贝至此"> </el-input>
 | 
			
		||||
                </el-form-item>
 | 
			
		||||
                <el-form-item v-if="form.authMethod == 2" prop="passphrase" label="秘钥密码:">
 | 
			
		||||
                <el-form-item v-if="form.authMethod == 2" prop="passphrase" label="秘钥密码">
 | 
			
		||||
                    <el-input type="password" v-model="form.passphrase"> </el-input>
 | 
			
		||||
                </el-form-item>
 | 
			
		||||
 | 
			
		||||
                <el-form-item label="备注:">
 | 
			
		||||
                <el-form-item label="备注">
 | 
			
		||||
                    <el-input v-model="form.remark" type="textarea" :rows="2"></el-input>
 | 
			
		||||
                </el-form-item>
 | 
			
		||||
            </el-form>
 | 
			
		||||
 
 | 
			
		||||
@@ -5,7 +5,7 @@
 | 
			
		||||
 | 
			
		||||
            <el-row class="mb10">
 | 
			
		||||
                <el-breadcrumb separator-icon="ArrowRight">
 | 
			
		||||
                    <el-breadcrumb-item v-for="path in filePathNav">
 | 
			
		||||
                    <el-breadcrumb-item v-for="path in filePathNav" :key="path">
 | 
			
		||||
                        <el-link @click="setFiles(path.path)" style="font-weight: bold">{{ path.name }}</el-link>
 | 
			
		||||
                    </el-breadcrumb-item>
 | 
			
		||||
                </el-breadcrumb>
 | 
			
		||||
@@ -131,7 +131,7 @@
 | 
			
		||||
                                <el-button-group v-if="state.copyOrMvFile.paths.length > 0" size="small" class="ml5">
 | 
			
		||||
                                    <el-tooltip effect="customized" raw-content placement="top">
 | 
			
		||||
                                        <template #content>
 | 
			
		||||
                                            <div v-for="path in state.copyOrMvFile.paths">{{ path }}</div>
 | 
			
		||||
                                            <div v-for="path in state.copyOrMvFile.paths" v-bind:key="path">{{ path }}</div>
 | 
			
		||||
                                        </template>
 | 
			
		||||
 | 
			
		||||
                                        <el-button @click="pasteFile" type="primary"
 | 
			
		||||
@@ -157,7 +157,7 @@
 | 
			
		||||
                            <SvgIcon :size="15" name="document" />
 | 
			
		||||
                        </span>
 | 
			
		||||
 | 
			
		||||
                        <span class="ml5" style="display: inline-block; width: 300px">
 | 
			
		||||
                        <span class="ml5" style="display: inline-block; width: 90%">
 | 
			
		||||
                            <div v-if="scope.row.nameEdit">
 | 
			
		||||
                                <el-input
 | 
			
		||||
                                    @keyup.enter="fileRename(scope.row)"
 | 
			
		||||
@@ -195,15 +195,6 @@
 | 
			
		||||
                        操作
 | 
			
		||||
                    </template>
 | 
			
		||||
                    <template #default="scope">
 | 
			
		||||
                        <el-link
 | 
			
		||||
                            @click="downloadFile(scope.row)"
 | 
			
		||||
                            v-if="scope.row.type == '-'"
 | 
			
		||||
                            v-auth="'machine:file:write'"
 | 
			
		||||
                            type="primary"
 | 
			
		||||
                            icon="download"
 | 
			
		||||
                            :underline="false"
 | 
			
		||||
                        ></el-link>
 | 
			
		||||
 | 
			
		||||
                        <el-link
 | 
			
		||||
                            @click="deleteFile([scope.row])"
 | 
			
		||||
                            v-if="!dontOperate(scope.row)"
 | 
			
		||||
@@ -211,7 +202,18 @@
 | 
			
		||||
                            type="danger"
 | 
			
		||||
                            icon="delete"
 | 
			
		||||
                            :underline="false"
 | 
			
		||||
                            title="删除"
 | 
			
		||||
                        ></el-link>
 | 
			
		||||
 | 
			
		||||
                        <el-link
 | 
			
		||||
                            @click="downloadFile(scope.row)"
 | 
			
		||||
                            v-if="scope.row.type == '-'"
 | 
			
		||||
                            v-auth="'machine:file:write'"
 | 
			
		||||
                            type="primary"
 | 
			
		||||
                            icon="download"
 | 
			
		||||
                            :underline="false"
 | 
			
		||||
                            class="ml10"
 | 
			
		||||
                            title="下载"
 | 
			
		||||
                        ></el-link>
 | 
			
		||||
 | 
			
		||||
                        <el-popover placement="top-start" :title="`${scope.row.path}-文件详情`" :width="520" trigger="click" @show="showFileStat(scope.row)">
 | 
			
		||||
@@ -244,10 +246,10 @@
 | 
			
		||||
            width="400px"
 | 
			
		||||
        >
 | 
			
		||||
            <div>
 | 
			
		||||
                <el-form-item prop="name" label="名称:">
 | 
			
		||||
                <el-form-item prop="name" label="名称">
 | 
			
		||||
                    <el-input v-model.trim="createFileDialog.name" placeholder="请输入名称" auto-complete="off"></el-input>
 | 
			
		||||
                </el-form-item>
 | 
			
		||||
                <el-form-item prop="type" label="类型:">
 | 
			
		||||
                <el-form-item prop="type" label="类型">
 | 
			
		||||
                    <el-radio-group v-model="createFileDialog.type">
 | 
			
		||||
                        <el-radio label="d">文件夹</el-radio>
 | 
			
		||||
                        <el-radio label="-">文件</el-radio>
 | 
			
		||||
@@ -272,7 +274,7 @@ import { ref, toRefs, reactive, onMounted, computed } from 'vue';
 | 
			
		||||
import { ElMessage, ElMessageBox, ElInput } from 'element-plus';
 | 
			
		||||
import { machineApi } from '../api';
 | 
			
		||||
 | 
			
		||||
import { getSession } from '@/common/utils/storage';
 | 
			
		||||
import { getToken } from '@/common/utils/storage';
 | 
			
		||||
import config from '@/common/config';
 | 
			
		||||
import { isTrue } from '@/common/assert';
 | 
			
		||||
import MachineFileContent from './MachineFileContent.vue';
 | 
			
		||||
@@ -285,11 +287,11 @@ const props = defineProps({
 | 
			
		||||
    isFolder: { type: Boolean, default: true },
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const token = getSession('token');
 | 
			
		||||
const token = getToken();
 | 
			
		||||
const folderUploadRef: any = ref();
 | 
			
		||||
 | 
			
		||||
const folderType = 'd';
 | 
			
		||||
const fileType = '-';
 | 
			
		||||
 | 
			
		||||
// 路径分隔符
 | 
			
		||||
const pathSep = '/';
 | 
			
		||||
 | 
			
		||||
@@ -597,6 +599,7 @@ const deleteFile = async (files: any) => {
 | 
			
		||||
        ElMessage.success('删除成功');
 | 
			
		||||
        refresh();
 | 
			
		||||
    } catch (e) {
 | 
			
		||||
        //
 | 
			
		||||
    } finally {
 | 
			
		||||
        state.loading = false;
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -4,7 +4,7 @@
 | 
			
		||||
            <el-form :model="form" ref="mongoForm" :rules="rules" label-width="85px">
 | 
			
		||||
                <el-tabs v-model="tabActiveName">
 | 
			
		||||
                    <el-tab-pane label="基础信息" name="basic">
 | 
			
		||||
                        <el-form-item prop="tagId" label="标签:" required>
 | 
			
		||||
                        <el-form-item prop="tagId" label="标签" required>
 | 
			
		||||
                            <tag-select v-model="form.tagId" v-model:tag-path="form.tagPath" style="width: 100%" />
 | 
			
		||||
                        </el-form-item>
 | 
			
		||||
 | 
			
		||||
@@ -23,7 +23,7 @@
 | 
			
		||||
                    </el-tab-pane>
 | 
			
		||||
 | 
			
		||||
                    <el-tab-pane label="其他配置" name="other">
 | 
			
		||||
                        <el-form-item prop="sshTunnelMachineId" label="SSH隧道:">
 | 
			
		||||
                        <el-form-item prop="sshTunnelMachineId" label="SSH隧道">
 | 
			
		||||
                            <ssh-tunnel-select v-model="form.sshTunnelMachineId" />
 | 
			
		||||
                        </el-form-item>
 | 
			
		||||
                    </el-tab-pane>
 | 
			
		||||
 
 | 
			
		||||
@@ -4,20 +4,20 @@
 | 
			
		||||
            <el-form :model="form" ref="redisForm" :rules="rules" label-width="auto">
 | 
			
		||||
                <el-tabs v-model="tabActiveName">
 | 
			
		||||
                    <el-tab-pane label="基础信息" name="basic">
 | 
			
		||||
                        <el-form-item prop="tagId" label="标签:" required>
 | 
			
		||||
                        <el-form-item prop="tagId" label="标签" required>
 | 
			
		||||
                            <tag-select v-model="form.tagId" v-model:tag-path="form.tagPath" style="width: 100%" />
 | 
			
		||||
                        </el-form-item>
 | 
			
		||||
                        <el-form-item prop="name" label="名称:" required>
 | 
			
		||||
                        <el-form-item prop="name" label="名称" required>
 | 
			
		||||
                            <el-input v-model.trim="form.name" placeholder="请输入redis名称" auto-complete="off"></el-input>
 | 
			
		||||
                        </el-form-item>
 | 
			
		||||
                        <el-form-item prop="mode" label="mode:" required>
 | 
			
		||||
                        <el-form-item prop="mode" label="mode" required>
 | 
			
		||||
                            <el-select style="width: 100%" v-model="form.mode" placeholder="请选择模式">
 | 
			
		||||
                                <el-option label="standalone" value="standalone"> </el-option>
 | 
			
		||||
                                <el-option label="cluster" value="cluster"> </el-option>
 | 
			
		||||
                                <el-option label="sentinel" value="sentinel"> </el-option>
 | 
			
		||||
                            </el-select>
 | 
			
		||||
                        </el-form-item>
 | 
			
		||||
                        <el-form-item prop="host" label="host:" required>
 | 
			
		||||
                        <el-form-item prop="host" label="host" required>
 | 
			
		||||
                            <el-input
 | 
			
		||||
                                v-model.trim="form.host"
 | 
			
		||||
                                placeholder="请输入host:port;sentinel模式为: mastername=sentinelhost:port,若集群或哨兵需设多个节点可使用','分割"
 | 
			
		||||
@@ -25,10 +25,10 @@
 | 
			
		||||
                                type="textarea"
 | 
			
		||||
                            ></el-input>
 | 
			
		||||
                        </el-form-item>
 | 
			
		||||
                        <el-form-item prop="username" label="用户名:">
 | 
			
		||||
                        <el-form-item prop="username" label="用户名">
 | 
			
		||||
                            <el-input v-model.trim="form.username" placeholder="用户名"></el-input>
 | 
			
		||||
                        </el-form-item>
 | 
			
		||||
                        <el-form-item prop="password" label="密码:">
 | 
			
		||||
                        <el-form-item prop="password" label="密码">
 | 
			
		||||
                            <el-input
 | 
			
		||||
                                type="password"
 | 
			
		||||
                                show-password
 | 
			
		||||
@@ -44,7 +44,7 @@
 | 
			
		||||
                                </template></el-input
 | 
			
		||||
                            >
 | 
			
		||||
                        </el-form-item>
 | 
			
		||||
                        <el-form-item prop="db" label="库号:" required>
 | 
			
		||||
                        <el-form-item prop="db" label="库号" required>
 | 
			
		||||
                            <el-select
 | 
			
		||||
                                @change="changeDb"
 | 
			
		||||
                                :disabled="form.mode == 'cluster'"
 | 
			
		||||
@@ -58,13 +58,13 @@
 | 
			
		||||
                                <el-option v-for="db in [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]" :key="db" :label="db" :value="db" />
 | 
			
		||||
                            </el-select>
 | 
			
		||||
                        </el-form-item>
 | 
			
		||||
                        <el-form-item prop="remark" label="备注:">
 | 
			
		||||
                        <el-form-item prop="remark" label="备注">
 | 
			
		||||
                            <el-input v-model.trim="form.remark" auto-complete="off" type="textarea"></el-input>
 | 
			
		||||
                        </el-form-item>
 | 
			
		||||
                    </el-tab-pane>
 | 
			
		||||
 | 
			
		||||
                    <el-tab-pane label="其他配置" name="other">
 | 
			
		||||
                        <el-form-item prop="sshTunnelMachineId" label="SSH隧道:">
 | 
			
		||||
                        <el-form-item prop="sshTunnelMachineId" label="SSH隧道">
 | 
			
		||||
                            <ssh-tunnel-select v-model="form.sshTunnelMachineId" />
 | 
			
		||||
                        </el-form-item>
 | 
			
		||||
                    </el-tab-pane>
 | 
			
		||||
 
 | 
			
		||||
@@ -35,10 +35,10 @@
 | 
			
		||||
 | 
			
		||||
        <el-dialog width="400px" title="团队编辑" :before-close="cancelSaveTeam" v-model="addTeamDialog.visible">
 | 
			
		||||
            <el-form ref="teamForm" :model="addTeamDialog.form" label-width="auto">
 | 
			
		||||
                <el-form-item prop="name" label="团队名:" required>
 | 
			
		||||
                <el-form-item prop="name" label="团队名" required>
 | 
			
		||||
                    <el-input v-model="addTeamDialog.form.name" auto-complete="off"></el-input>
 | 
			
		||||
                </el-form-item>
 | 
			
		||||
                <el-form-item label="备注:">
 | 
			
		||||
                <el-form-item label="备注">
 | 
			
		||||
                    <el-input v-model="addTeamDialog.form.remark" auto-complete="off"></el-input>
 | 
			
		||||
                </el-form-item>
 | 
			
		||||
            </el-form>
 | 
			
		||||
@@ -52,7 +52,7 @@
 | 
			
		||||
 | 
			
		||||
        <el-dialog width="500px" :title="showTagDialog.title" :before-close="closeTagDialog" v-model="showTagDialog.visible">
 | 
			
		||||
            <el-form label-width="auto">
 | 
			
		||||
                <el-form-item prop="tag" label="标签:">
 | 
			
		||||
                <el-form-item prop="tag" label="标签">
 | 
			
		||||
                    <el-tree-select
 | 
			
		||||
                        ref="tagTreeRef"
 | 
			
		||||
                        style="width: 100%"
 | 
			
		||||
@@ -111,7 +111,7 @@
 | 
			
		||||
 | 
			
		||||
            <el-dialog width="400px" title="添加成员" :before-close="cancelAddMember" v-model="showMemDialog.addVisible">
 | 
			
		||||
                <el-form :model="showMemDialog.memForm" label-width="auto">
 | 
			
		||||
                    <el-form-item label="账号:">
 | 
			
		||||
                    <el-form-item label="账号">
 | 
			
		||||
                        <el-select
 | 
			
		||||
                            style="width: 100%"
 | 
			
		||||
                            remote
 | 
			
		||||
@@ -344,7 +344,7 @@ const saveTags = async () => {
 | 
			
		||||
    closeTagDialog();
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const tagTreeNodeCheck = (data: any) => {
 | 
			
		||||
const tagTreeNodeCheck = () => {
 | 
			
		||||
    // const node = tagTreeRef.value.getNode(data.id);
 | 
			
		||||
    // console.log(node);
 | 
			
		||||
    // // state.showTagDialog.tagTreeTeams = [16]
 | 
			
		||||
 
 | 
			
		||||
@@ -179,7 +179,7 @@ import { dateFormat } from '@/common/utils/date';
 | 
			
		||||
import { storeToRefs } from 'pinia';
 | 
			
		||||
import { useUserInfo } from '@/store/userInfo';
 | 
			
		||||
import config from '@/common/config';
 | 
			
		||||
import { getSession } from '@/common/utils/storage';
 | 
			
		||||
import { getToken } from '@/common/utils/storage';
 | 
			
		||||
 | 
			
		||||
const { userInfo } = storeToRefs(useUserInfo());
 | 
			
		||||
const state = reactive({
 | 
			
		||||
@@ -248,7 +248,7 @@ const bindOAuth2 = () => {
 | 
			
		||||
    var iLeft = (window.screen.width - 10 - width) / 2; //获得窗口的水平位置;
 | 
			
		||||
    // 小窗口打开oauth2鉴权
 | 
			
		||||
    let oauthWindow = window.open(
 | 
			
		||||
        config.baseApiUrl + '/auth/oauth2/bind?token=' + getSession('token'),
 | 
			
		||||
        config.baseApiUrl + '/auth/oauth2/bind?token=' + getToken(),
 | 
			
		||||
        'oauth2',
 | 
			
		||||
        `height=${height},width=${width},top=${iTop},left=${iLeft},location=no`
 | 
			
		||||
    );
 | 
			
		||||
 
 | 
			
		||||
@@ -1,20 +1,20 @@
 | 
			
		||||
<template>
 | 
			
		||||
    <div class="account-dialog">
 | 
			
		||||
        <el-dialog :title="title" v-model="dialogVisible" :before-close="cancel" :show-close="false" width="35%" :destroy-on-close="true">
 | 
			
		||||
        <el-dialog :title="title" v-model="dialogVisible" :before-close="cancel" :show-close="false" width="500px" :destroy-on-close="true">
 | 
			
		||||
            <el-form :model="form" ref="accountForm" :rules="rules" label-width="auto">
 | 
			
		||||
                <el-form-item prop="name" label="姓名:">
 | 
			
		||||
                <el-form-item prop="name" label="姓名">
 | 
			
		||||
                    <el-input v-model.trim="form.name" placeholder="请输入姓名" auto-complete="off" clearable></el-input>
 | 
			
		||||
                </el-form-item>
 | 
			
		||||
                <el-form-item prop="username" label="用户名:">
 | 
			
		||||
                <el-form-item prop="username" label="用户名">
 | 
			
		||||
                    <el-input
 | 
			
		||||
                        :disabled="edit"
 | 
			
		||||
                        v-model.trim="form.username"
 | 
			
		||||
                        placeholder="请输入账号用户名,密码默认与账号名一致"
 | 
			
		||||
                        placeholder="请输入账号用户名,密码默认与用户名一致"
 | 
			
		||||
                        auto-complete="off"
 | 
			
		||||
                        clearable
 | 
			
		||||
                    ></el-input>
 | 
			
		||||
                </el-form-item>
 | 
			
		||||
                <el-form-item v-if="edit" prop="password" label="密码:">
 | 
			
		||||
                <el-form-item v-if="edit" prop="password" label="密码">
 | 
			
		||||
                    <el-input type="password" v-model.trim="form.password" placeholder="输入密码可修改用户密码" autocomplete="new-password"></el-input>
 | 
			
		||||
                </el-form-item>
 | 
			
		||||
            </el-form>
 | 
			
		||||
 
 | 
			
		||||
@@ -2,13 +2,13 @@
 | 
			
		||||
    <div>
 | 
			
		||||
        <el-dialog :title="title" v-model="dvisible" :show-close="false" :before-close="cancel" width="750px" :destroy-on-close="true">
 | 
			
		||||
            <el-form ref="configForm" :model="form" label-width="auto">
 | 
			
		||||
                <el-form-item prop="name" label="配置项:" required>
 | 
			
		||||
                <el-form-item prop="name" label="配置项" required>
 | 
			
		||||
                    <el-input v-model="form.name"></el-input>
 | 
			
		||||
                </el-form-item>
 | 
			
		||||
                <el-form-item prop="key" label="配置key:" required>
 | 
			
		||||
                <el-form-item prop="key" label="配置key" required>
 | 
			
		||||
                    <el-input :disabled="form.id != null" v-model="form.key"></el-input>
 | 
			
		||||
                </el-form-item>
 | 
			
		||||
                <el-form-item prop="permission" label="权限:">
 | 
			
		||||
                <el-form-item prop="permission" label="权限">
 | 
			
		||||
                    <el-select
 | 
			
		||||
                        style="width: 100%"
 | 
			
		||||
                        remote
 | 
			
		||||
@@ -59,7 +59,7 @@
 | 
			
		||||
                <!-- <el-form-item prop="value" label="配置值:" required>
 | 
			
		||||
                    <el-input v-model="form.value"></el-input>
 | 
			
		||||
                </el-form-item> -->
 | 
			
		||||
                <el-form-item label="备注:">
 | 
			
		||||
                <el-form-item label="备注">
 | 
			
		||||
                    <el-input v-model="form.remark" type="textarea" :rows="2"></el-input>
 | 
			
		||||
                </el-form-item>
 | 
			
		||||
            </el-form>
 | 
			
		||||
 
 | 
			
		||||
@@ -2,13 +2,13 @@
 | 
			
		||||
    <div class="role-dialog">
 | 
			
		||||
        <el-dialog :title="title" v-model="dvisible" :show-close="false" :before-close="cancel" width="500px" :destroy-on-close="true">
 | 
			
		||||
            <el-form ref="roleForm" :model="form" label-width="auto">
 | 
			
		||||
                <el-form-item prop="name" label="角色名称:" required>
 | 
			
		||||
                <el-form-item prop="name" label="角色名称" required>
 | 
			
		||||
                    <el-input v-model="form.name" auto-complete="off"></el-input>
 | 
			
		||||
                </el-form-item>
 | 
			
		||||
                <el-form-item prop="code" label="角色code:" required>
 | 
			
		||||
                <el-form-item prop="code" label="角色code" required>
 | 
			
		||||
                    <el-input :disabled="form.id != null" v-model="form.code" placeholder="COMMON开头则为所有账号共有角色" auto-complete="off"></el-input>
 | 
			
		||||
                </el-form-item>
 | 
			
		||||
                <el-form-item label="角色描述:">
 | 
			
		||||
                <el-form-item label="角色描述">
 | 
			
		||||
                    <el-input v-model="form.remark" type="textarea" :rows="3" placeholder="请输入角色描述"></el-input>
 | 
			
		||||
                </el-form-item>
 | 
			
		||||
            </el-form>
 | 
			
		||||
 
 | 
			
		||||
@@ -29,7 +29,7 @@ const viteConfig: UserConfig = {
 | 
			
		||||
        open: VITE_OPEN,
 | 
			
		||||
        proxy: {
 | 
			
		||||
            '/api': {
 | 
			
		||||
                target: 'http://localhost:8888',
 | 
			
		||||
                target: 'http://localhost:18888',
 | 
			
		||||
                ws: true,
 | 
			
		||||
                changeOrigin: true,
 | 
			
		||||
            },
 | 
			
		||||
@@ -49,6 +49,7 @@ const viteConfig: UserConfig = {
 | 
			
		||||
                manualChunks: {
 | 
			
		||||
                    vue: ['vue', 'vue-router', 'pinia'],
 | 
			
		||||
                    echarts: ['echarts'],
 | 
			
		||||
                    monaco: ['monaco-editor'],
 | 
			
		||||
                },
 | 
			
		||||
            },
 | 
			
		||||
        },
 | 
			
		||||
 
 | 
			
		||||
@@ -2,11 +2,6 @@
 | 
			
		||||
# yarn lockfile v1
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
"@babel/parser@^7.16.4":
 | 
			
		||||
  version "7.19.1"
 | 
			
		||||
  resolved "https://registry.npmmirror.com/@babel/parser/-/parser-7.19.1.tgz"
 | 
			
		||||
  integrity sha512-h7RCSorm1DdTVGJf3P2Mhj3kdnkmF/EiysUkzS2TdgAYqyjFdMQJbVuXOBej2SBJaXan/lIVtT6KkGbyyq753A==
 | 
			
		||||
 | 
			
		||||
"@babel/parser@^7.20.15", "@babel/parser@^7.21.3":
 | 
			
		||||
  version "7.21.8"
 | 
			
		||||
  resolved "https://registry.npmmirror.com/@babel/parser/-/parser-7.21.8.tgz#642af7d0333eab9c0ad70b14ac5e76dbde7bfdf8"
 | 
			
		||||
@@ -144,6 +139,18 @@
 | 
			
		||||
  resolved "https://registry.npmmirror.com/@esbuild/win32-x64/-/win32-x64-0.18.11.tgz#6526c7e1b40d5b9f0a222c6b767c22f6fb97aa57"
 | 
			
		||||
  integrity sha512-rQI4cjLHd2hGsM1LqgDI7oOCYbQ6IBOVsX9ejuRMSze0GqXUG2ekwiKkiBU1pRGSeCqFFHxTrcEydB2Hyoz9CA==
 | 
			
		||||
 | 
			
		||||
"@eslint-community/eslint-utils@^4.4.0":
 | 
			
		||||
  version "4.4.0"
 | 
			
		||||
  resolved "https://registry.npmmirror.com/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz#a23514e8fb9af1269d5f7788aa556798d61c6b59"
 | 
			
		||||
  integrity sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==
 | 
			
		||||
  dependencies:
 | 
			
		||||
    eslint-visitor-keys "^3.3.0"
 | 
			
		||||
 | 
			
		||||
"@eslint-community/regexpp@^4.5.1":
 | 
			
		||||
  version "4.9.1"
 | 
			
		||||
  resolved "https://registry.npmmirror.com/@eslint-community/regexpp/-/regexpp-4.9.1.tgz#449dfa81a57a1d755b09aa58d826c1262e4283b4"
 | 
			
		||||
  integrity sha512-Y27x+MBLjXa+0JWDhykM3+JE+il3kHKAEqabfEWq3SDhZjLYb6/BHL/JKFnH3fe207JaXkyDo685Oc2Glt6ifA==
 | 
			
		||||
 | 
			
		||||
"@eslint/eslintrc@^2.0.0":
 | 
			
		||||
  version "2.0.0"
 | 
			
		||||
  resolved "https://registry.npmmirror.com/@eslint/eslintrc/-/eslintrc-2.0.0.tgz#943309d8697c52fc82c076e90c1c74fbbe69dbff"
 | 
			
		||||
@@ -231,10 +238,10 @@
 | 
			
		||||
  resolved "https://registry.npmmirror.com/@types/antlr4/-/antlr4-4.7.0.tgz"
 | 
			
		||||
  integrity sha512-WdyHH4PHxBQkeWoRTbuC/dvf0QErJpJE4UpESQSRmKoMER15DCLFHAHQjkwevMKQie0kqawS/eTY563GGMbz/g==
 | 
			
		||||
 | 
			
		||||
"@types/json-schema@^7.0.7":
 | 
			
		||||
  version "7.0.9"
 | 
			
		||||
  resolved "https://registry.npmmirror.com/@types/json-schema/download/@types/json-schema-7.0.9.tgz?cache=0&sync_timestamp=1637266073261&other_urls=https%3A%2F%2Fregistry.npmmirror.com%2F%40types%2Fjson-schema%2Fdownload%2F%40types%2Fjson-schema-7.0.9.tgz"
 | 
			
		||||
  integrity sha1-l+3JA36gw4WFMgsolk3eOznkZg0=
 | 
			
		||||
"@types/json-schema@^7.0.12":
 | 
			
		||||
  version "7.0.13"
 | 
			
		||||
  resolved "https://registry.npmmirror.com/@types/json-schema/-/json-schema-7.0.13.tgz#02c24f4363176d2d18fc8b70b9f3c54aba178a85"
 | 
			
		||||
  integrity sha512-RbSSoHliUbnXj3ny0CNFOoxrIDV6SUGyStHsvDqosw6CkdPV8TtWGlfecuK4ToyMEAql6pzNxgCFKanovUzlgQ==
 | 
			
		||||
 | 
			
		||||
"@types/lodash-es@^4.17.6":
 | 
			
		||||
  version "4.17.6"
 | 
			
		||||
@@ -263,100 +270,110 @@
 | 
			
		||||
  resolved "https://registry.npmmirror.com/@types/nprogress/download/@types/nprogress-0.2.0.tgz"
 | 
			
		||||
  integrity sha1-hsWTaC1BmSEqBQnMPE1WK7vW5F8=
 | 
			
		||||
 | 
			
		||||
"@types/sortablejs@^1.10.6":
 | 
			
		||||
  version "1.10.7"
 | 
			
		||||
  resolved "https://registry.npmmirror.com/@types/sortablejs/download/@types/sortablejs-1.10.7.tgz"
 | 
			
		||||
  integrity sha1-q5A5yFQp8FFpVextvAuyATlBexU=
 | 
			
		||||
"@types/semver@^7.5.0":
 | 
			
		||||
  version "7.5.3"
 | 
			
		||||
  resolved "https://registry.npmmirror.com/@types/semver/-/semver-7.5.3.tgz#9a726e116beb26c24f1ccd6850201e1246122e04"
 | 
			
		||||
  integrity sha512-OxepLK9EuNEIPxWNME+C6WwbRAOOI2o2BaQEGzz5Lu2e4Z5eDnEo+/aVEDMIXywoJitJ7xWd641wrGLZdtwRyw==
 | 
			
		||||
 | 
			
		||||
"@types/sortablejs@^1.15.3":
 | 
			
		||||
  version "1.15.3"
 | 
			
		||||
  resolved "https://registry.npmmirror.com/@types/sortablejs/-/sortablejs-1.15.3.tgz#b9c0e2740100ae94919c9f138a38600c8f8124ea"
 | 
			
		||||
  integrity sha512-v+zh6TZP/cLeMUK0MDx1onp8e7Jk2/4iTQ7sb/n80rTAvBm14yJkpOEfJdrTCkHiF7IZbPjxGX2NRJfogRoYIg==
 | 
			
		||||
 | 
			
		||||
"@types/web-bluetooth@^0.0.15":
 | 
			
		||||
  version "0.0.15"
 | 
			
		||||
  resolved "https://registry.npmmirror.com/@types/web-bluetooth/-/web-bluetooth-0.0.15.tgz"
 | 
			
		||||
  integrity sha512-w7hEHXnPMEZ+4nGKl/KDRVpxkwYxYExuHOYXyzIzCDzEZ9ZCGMAewulr9IqJu2LR4N37fcnb1XVeuZ09qgOxhA==
 | 
			
		||||
 | 
			
		||||
"@typescript-eslint/eslint-plugin@^4.23.0":
 | 
			
		||||
  version "4.33.0"
 | 
			
		||||
  resolved "https://registry.npmmirror.com/@typescript-eslint/eslint-plugin/download/@typescript-eslint/eslint-plugin-4.33.0.tgz"
 | 
			
		||||
  integrity sha1-wk3HyAacdwa8QNmfb6h+3LIAUnY=
 | 
			
		||||
"@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"
 | 
			
		||||
  integrity sha512-DAbgDXwtX+pDkAHwiGhqP3zWUGpW49B7eqmgpPtg+BKJXwdct79ut9+ifqOFPJGClGKSHXn2PTBatCnldJRUoA==
 | 
			
		||||
  dependencies:
 | 
			
		||||
    "@typescript-eslint/experimental-utils" "4.33.0"
 | 
			
		||||
    "@typescript-eslint/scope-manager" "4.33.0"
 | 
			
		||||
    debug "^4.3.1"
 | 
			
		||||
    functional-red-black-tree "^1.0.1"
 | 
			
		||||
    ignore "^5.1.8"
 | 
			
		||||
    regexpp "^3.1.0"
 | 
			
		||||
    semver "^7.3.5"
 | 
			
		||||
    tsutils "^3.21.0"
 | 
			
		||||
    "@eslint-community/regexpp" "^4.5.1"
 | 
			
		||||
    "@typescript-eslint/scope-manager" "6.7.4"
 | 
			
		||||
    "@typescript-eslint/type-utils" "6.7.4"
 | 
			
		||||
    "@typescript-eslint/utils" "6.7.4"
 | 
			
		||||
    "@typescript-eslint/visitor-keys" "6.7.4"
 | 
			
		||||
    debug "^4.3.4"
 | 
			
		||||
    graphemer "^1.4.0"
 | 
			
		||||
    ignore "^5.2.4"
 | 
			
		||||
    natural-compare "^1.4.0"
 | 
			
		||||
    semver "^7.5.4"
 | 
			
		||||
    ts-api-utils "^1.0.1"
 | 
			
		||||
 | 
			
		||||
"@typescript-eslint/experimental-utils@4.33.0":
 | 
			
		||||
  version "4.33.0"
 | 
			
		||||
  resolved "https://registry.npmmirror.com/@typescript-eslint/experimental-utils/download/@typescript-eslint/experimental-utils-4.33.0.tgz"
 | 
			
		||||
  integrity sha1-byp4akIJ+iIimJ6TgLUzGygQ9/0=
 | 
			
		||||
"@typescript-eslint/parser@^6.7.4":
 | 
			
		||||
  version "6.7.4"
 | 
			
		||||
  resolved "https://registry.npmmirror.com/@typescript-eslint/parser/-/parser-6.7.4.tgz#23d1dd4fe5d295c7fa2ab651f5406cd9ad0bd435"
 | 
			
		||||
  integrity sha512-I5zVZFY+cw4IMZUeNCU7Sh2PO5O57F7Lr0uyhgCJmhN/BuTlnc55KxPonR4+EM3GBdfiCyGZye6DgMjtubQkmA==
 | 
			
		||||
  dependencies:
 | 
			
		||||
    "@types/json-schema" "^7.0.7"
 | 
			
		||||
    "@typescript-eslint/scope-manager" "4.33.0"
 | 
			
		||||
    "@typescript-eslint/types" "4.33.0"
 | 
			
		||||
    "@typescript-eslint/typescript-estree" "4.33.0"
 | 
			
		||||
    eslint-scope "^5.1.1"
 | 
			
		||||
    eslint-utils "^3.0.0"
 | 
			
		||||
    "@typescript-eslint/scope-manager" "6.7.4"
 | 
			
		||||
    "@typescript-eslint/types" "6.7.4"
 | 
			
		||||
    "@typescript-eslint/typescript-estree" "6.7.4"
 | 
			
		||||
    "@typescript-eslint/visitor-keys" "6.7.4"
 | 
			
		||||
    debug "^4.3.4"
 | 
			
		||||
 | 
			
		||||
"@typescript-eslint/parser@^4.23.0":
 | 
			
		||||
  version "4.33.0"
 | 
			
		||||
  resolved "https://registry.npmmirror.com/@typescript-eslint/parser/download/@typescript-eslint/parser-4.33.0.tgz"
 | 
			
		||||
  integrity sha1-3+eXVw2WlOVgUo0Y7srYbIx0SJk=
 | 
			
		||||
"@typescript-eslint/scope-manager@6.7.4":
 | 
			
		||||
  version "6.7.4"
 | 
			
		||||
  resolved "https://registry.npmmirror.com/@typescript-eslint/scope-manager/-/scope-manager-6.7.4.tgz#a484a17aa219e96044db40813429eb7214d7b386"
 | 
			
		||||
  integrity sha512-SdGqSLUPTXAXi7c3Ob7peAGVnmMoGzZ361VswK2Mqf8UOYcODiYvs8rs5ILqEdfvX1lE7wEZbLyELCW+Yrql1A==
 | 
			
		||||
  dependencies:
 | 
			
		||||
    "@typescript-eslint/scope-manager" "4.33.0"
 | 
			
		||||
    "@typescript-eslint/types" "4.33.0"
 | 
			
		||||
    "@typescript-eslint/typescript-estree" "4.33.0"
 | 
			
		||||
    debug "^4.3.1"
 | 
			
		||||
    "@typescript-eslint/types" "6.7.4"
 | 
			
		||||
    "@typescript-eslint/visitor-keys" "6.7.4"
 | 
			
		||||
 | 
			
		||||
"@typescript-eslint/scope-manager@4.33.0":
 | 
			
		||||
  version "4.33.0"
 | 
			
		||||
  resolved "https://registry.npmmirror.com/@typescript-eslint/scope-manager/download/@typescript-eslint/scope-manager-4.33.0.tgz"
 | 
			
		||||
  integrity sha1-045JKA2YPody4pEhz4xukiHygKM=
 | 
			
		||||
"@typescript-eslint/type-utils@6.7.4":
 | 
			
		||||
  version "6.7.4"
 | 
			
		||||
  resolved "https://registry.npmmirror.com/@typescript-eslint/type-utils/-/type-utils-6.7.4.tgz#847cd3b59baf948984499be3e0a12ff07373e321"
 | 
			
		||||
  integrity sha512-n+g3zi1QzpcAdHFP9KQF+rEFxMb2KxtnJGID3teA/nxKHOVi3ylKovaqEzGBbVY2pBttU6z85gp0D00ufLzViQ==
 | 
			
		||||
  dependencies:
 | 
			
		||||
    "@typescript-eslint/types" "4.33.0"
 | 
			
		||||
    "@typescript-eslint/visitor-keys" "4.33.0"
 | 
			
		||||
    "@typescript-eslint/typescript-estree" "6.7.4"
 | 
			
		||||
    "@typescript-eslint/utils" "6.7.4"
 | 
			
		||||
    debug "^4.3.4"
 | 
			
		||||
    ts-api-utils "^1.0.1"
 | 
			
		||||
 | 
			
		||||
"@typescript-eslint/types@4.33.0":
 | 
			
		||||
  version "4.33.0"
 | 
			
		||||
  resolved "https://registry.npmmirror.com/@typescript-eslint/types/download/@typescript-eslint/types-4.33.0.tgz"
 | 
			
		||||
  integrity sha1-oeWQNqO1OuhDDO6/KpGdx/mvbXI=
 | 
			
		||||
"@typescript-eslint/types@6.7.4":
 | 
			
		||||
  version "6.7.4"
 | 
			
		||||
  resolved "https://registry.npmmirror.com/@typescript-eslint/types/-/types-6.7.4.tgz#5d358484d2be986980c039de68e9f1eb62ea7897"
 | 
			
		||||
  integrity sha512-o9XWK2FLW6eSS/0r/tgjAGsYasLAnOWg7hvZ/dGYSSNjCh+49k5ocPN8OmG5aZcSJ8pclSOyVKP2x03Sj+RrCA==
 | 
			
		||||
 | 
			
		||||
"@typescript-eslint/typescript-estree@4.33.0":
 | 
			
		||||
  version "4.33.0"
 | 
			
		||||
  resolved "https://registry.npmmirror.com/@typescript-eslint/typescript-estree/download/@typescript-eslint/typescript-estree-4.33.0.tgz"
 | 
			
		||||
  integrity sha1-DftRwpCPaMXAjYKu/q8WahfCRgk=
 | 
			
		||||
"@typescript-eslint/typescript-estree@6.7.4":
 | 
			
		||||
  version "6.7.4"
 | 
			
		||||
  resolved "https://registry.npmmirror.com/@typescript-eslint/typescript-estree/-/typescript-estree-6.7.4.tgz#f2baece09f7bb1df9296e32638b2e1130014ef1a"
 | 
			
		||||
  integrity sha512-ty8b5qHKatlNYd9vmpHooQz3Vki3gG+3PchmtsA4TgrZBKWHNjWfkQid7K7xQogBqqc7/BhGazxMD5vr6Ha+iQ==
 | 
			
		||||
  dependencies:
 | 
			
		||||
    "@typescript-eslint/types" "4.33.0"
 | 
			
		||||
    "@typescript-eslint/visitor-keys" "4.33.0"
 | 
			
		||||
    debug "^4.3.1"
 | 
			
		||||
    globby "^11.0.3"
 | 
			
		||||
    is-glob "^4.0.1"
 | 
			
		||||
    semver "^7.3.5"
 | 
			
		||||
    tsutils "^3.21.0"
 | 
			
		||||
    "@typescript-eslint/types" "6.7.4"
 | 
			
		||||
    "@typescript-eslint/visitor-keys" "6.7.4"
 | 
			
		||||
    debug "^4.3.4"
 | 
			
		||||
    globby "^11.1.0"
 | 
			
		||||
    is-glob "^4.0.3"
 | 
			
		||||
    semver "^7.5.4"
 | 
			
		||||
    ts-api-utils "^1.0.1"
 | 
			
		||||
 | 
			
		||||
"@typescript-eslint/visitor-keys@4.33.0":
 | 
			
		||||
  version "4.33.0"
 | 
			
		||||
  resolved "https://registry.npmmirror.com/@typescript-eslint/visitor-keys/download/@typescript-eslint/visitor-keys-4.33.0.tgz"
 | 
			
		||||
  integrity sha1-KiL3ekFgQom3oYZYbp7EjKku8d0=
 | 
			
		||||
"@typescript-eslint/utils@6.7.4":
 | 
			
		||||
  version "6.7.4"
 | 
			
		||||
  resolved "https://registry.npmmirror.com/@typescript-eslint/utils/-/utils-6.7.4.tgz#2236f72b10e38277ee05ef06142522e1de470ff2"
 | 
			
		||||
  integrity sha512-PRQAs+HUn85Qdk+khAxsVV+oULy3VkbH3hQ8hxLRJXWBEd7iI+GbQxH5SEUSH7kbEoTp6oT1bOwyga24ELALTA==
 | 
			
		||||
  dependencies:
 | 
			
		||||
    "@typescript-eslint/types" "4.33.0"
 | 
			
		||||
    eslint-visitor-keys "^2.0.0"
 | 
			
		||||
    "@eslint-community/eslint-utils" "^4.4.0"
 | 
			
		||||
    "@types/json-schema" "^7.0.12"
 | 
			
		||||
    "@types/semver" "^7.5.0"
 | 
			
		||||
    "@typescript-eslint/scope-manager" "6.7.4"
 | 
			
		||||
    "@typescript-eslint/types" "6.7.4"
 | 
			
		||||
    "@typescript-eslint/typescript-estree" "6.7.4"
 | 
			
		||||
    semver "^7.5.4"
 | 
			
		||||
 | 
			
		||||
"@vitejs/plugin-vue@^4.0.0":
 | 
			
		||||
  version "4.0.0"
 | 
			
		||||
  resolved "https://registry.npmmirror.com/@vitejs/plugin-vue/-/plugin-vue-4.0.0.tgz#93815beffd23db46288c787352a8ea31a0c03e5e"
 | 
			
		||||
  integrity sha512-e0X4jErIxAB5oLtDqbHvHpJe/uWNkdpYV83AOG2xo2tEVSzCzewgJMtREZM30wXnM5ls90hxiOtAuVU6H5JgbA==
 | 
			
		||||
 | 
			
		||||
"@vue/compiler-core@3.2.39":
 | 
			
		||||
  version "3.2.39"
 | 
			
		||||
  resolved "https://registry.npmmirror.com/@vue/compiler-core/-/compiler-core-3.2.39.tgz"
 | 
			
		||||
  integrity sha512-mf/36OWXqWn0wsC40nwRRGheR/qoID+lZXbIuLnr4/AngM0ov8Xvv8GHunC0rKRIkh60bTqydlqTeBo49rlbqw==
 | 
			
		||||
"@typescript-eslint/visitor-keys@6.7.4":
 | 
			
		||||
  version "6.7.4"
 | 
			
		||||
  resolved "https://registry.npmmirror.com/@typescript-eslint/visitor-keys/-/visitor-keys-6.7.4.tgz#80dfecf820fc67574012375859085f91a4dff043"
 | 
			
		||||
  integrity sha512-pOW37DUhlTZbvph50x5zZCkFn3xzwkGtNoJHzIM3svpiSkJzwOYr/kVBaXmf+RAQiUDs1AHEZVNPg6UJCJpwRA==
 | 
			
		||||
  dependencies:
 | 
			
		||||
    "@babel/parser" "^7.16.4"
 | 
			
		||||
    "@vue/shared" "3.2.39"
 | 
			
		||||
    estree-walker "^2.0.2"
 | 
			
		||||
    source-map "^0.6.1"
 | 
			
		||||
    "@typescript-eslint/types" "6.7.4"
 | 
			
		||||
    eslint-visitor-keys "^3.4.1"
 | 
			
		||||
 | 
			
		||||
"@vitejs/plugin-vue@^4.4.0":
 | 
			
		||||
  version "4.4.0"
 | 
			
		||||
  resolved "https://registry.npmmirror.com/@vitejs/plugin-vue/-/plugin-vue-4.4.0.tgz#8ae96573236cdb12de6850a6d929b5537ec85390"
 | 
			
		||||
  integrity sha512-xdguqb+VUwiRpSg+nsc2HtbAUSGak25DXYvpQQi4RVU1Xq1uworyoH/md9Rfd8zMmPR/pSghr309QNcftUVseg==
 | 
			
		||||
 | 
			
		||||
"@vue/compiler-core@3.3.4":
 | 
			
		||||
  version "3.3.4"
 | 
			
		||||
@@ -368,14 +385,6 @@
 | 
			
		||||
    estree-walker "^2.0.2"
 | 
			
		||||
    source-map-js "^1.0.2"
 | 
			
		||||
 | 
			
		||||
"@vue/compiler-dom@3.2.39":
 | 
			
		||||
  version "3.2.39"
 | 
			
		||||
  resolved "https://registry.npmmirror.com/@vue/compiler-dom/-/compiler-dom-3.2.39.tgz"
 | 
			
		||||
  integrity sha512-HMFI25Be1C8vLEEv1hgEO1dWwG9QQ8LTTPmCkblVJY/O3OvWx6r1+zsox5mKPMGvqYEZa6l8j+xgOfUspgo7hw==
 | 
			
		||||
  dependencies:
 | 
			
		||||
    "@vue/compiler-core" "3.2.39"
 | 
			
		||||
    "@vue/shared" "3.2.39"
 | 
			
		||||
 | 
			
		||||
"@vue/compiler-dom@3.3.4":
 | 
			
		||||
  version "3.3.4"
 | 
			
		||||
  resolved "https://registry.npmmirror.com/@vue/compiler-dom/-/compiler-dom-3.3.4.tgz#f56e09b5f4d7dc350f981784de9713d823341151"
 | 
			
		||||
@@ -384,7 +393,7 @@
 | 
			
		||||
    "@vue/compiler-core" "3.3.4"
 | 
			
		||||
    "@vue/shared" "3.3.4"
 | 
			
		||||
 | 
			
		||||
"@vue/compiler-sfc@3.3.4":
 | 
			
		||||
"@vue/compiler-sfc@3.3.4", "@vue/compiler-sfc@^3.3.4":
 | 
			
		||||
  version "3.3.4"
 | 
			
		||||
  resolved "https://registry.npmmirror.com/@vue/compiler-sfc/-/compiler-sfc-3.3.4.tgz#b19d942c71938893535b46226d602720593001df"
 | 
			
		||||
  integrity sha512-6y/d8uw+5TkCuzBkgLS0v3lSM3hJDntFEiUORM11pQ/hKvkhSKZrXW6i69UyXlJQisJxuUEJKAWEqWbWsLeNKQ==
 | 
			
		||||
@@ -400,30 +409,6 @@
 | 
			
		||||
    postcss "^8.1.10"
 | 
			
		||||
    source-map-js "^1.0.2"
 | 
			
		||||
 | 
			
		||||
"@vue/compiler-sfc@^3.0.11":
 | 
			
		||||
  version "3.2.39"
 | 
			
		||||
  resolved "https://registry.npmmirror.com/@vue/compiler-sfc/-/compiler-sfc-3.2.39.tgz"
 | 
			
		||||
  integrity sha512-fqAQgFs1/BxTUZkd0Vakn3teKUt//J3c420BgnYgEOoVdTwYpBTSXCMJ88GOBCylmUBbtquGPli9tVs7LzsWIA==
 | 
			
		||||
  dependencies:
 | 
			
		||||
    "@babel/parser" "^7.16.4"
 | 
			
		||||
    "@vue/compiler-core" "3.2.39"
 | 
			
		||||
    "@vue/compiler-dom" "3.2.39"
 | 
			
		||||
    "@vue/compiler-ssr" "3.2.39"
 | 
			
		||||
    "@vue/reactivity-transform" "3.2.39"
 | 
			
		||||
    "@vue/shared" "3.2.39"
 | 
			
		||||
    estree-walker "^2.0.2"
 | 
			
		||||
    magic-string "^0.25.7"
 | 
			
		||||
    postcss "^8.1.10"
 | 
			
		||||
    source-map "^0.6.1"
 | 
			
		||||
 | 
			
		||||
"@vue/compiler-ssr@3.2.39":
 | 
			
		||||
  version "3.2.39"
 | 
			
		||||
  resolved "https://registry.npmmirror.com/@vue/compiler-ssr/-/compiler-ssr-3.2.39.tgz"
 | 
			
		||||
  integrity sha512-EoGCJ6lincKOZGW+0Ky4WOKsSmqL7hp1ZYgen8M7u/mlvvEQUaO9tKKOy7K43M9U2aA3tPv0TuYYQFrEbK2eFQ==
 | 
			
		||||
  dependencies:
 | 
			
		||||
    "@vue/compiler-dom" "3.2.39"
 | 
			
		||||
    "@vue/shared" "3.2.39"
 | 
			
		||||
 | 
			
		||||
"@vue/compiler-ssr@3.3.4":
 | 
			
		||||
  version "3.3.4"
 | 
			
		||||
  resolved "https://registry.npmmirror.com/@vue/compiler-ssr/-/compiler-ssr-3.3.4.tgz#9d1379abffa4f2b0cd844174ceec4a9721138777"
 | 
			
		||||
@@ -437,17 +422,6 @@
 | 
			
		||||
  resolved "https://registry.npmmirror.com/@vue/devtools-api/-/devtools-api-6.5.0.tgz#98b99425edee70b4c992692628fa1ea2c1e57d07"
 | 
			
		||||
  integrity sha512-o9KfBeaBmCKl10usN4crU53fYtC1r7jJwdGKjPT24t348rHxgfpZ0xL3Xm/gLUYnc0oTp8LAmrxOeLyu6tbk2Q==
 | 
			
		||||
 | 
			
		||||
"@vue/reactivity-transform@3.2.39":
 | 
			
		||||
  version "3.2.39"
 | 
			
		||||
  resolved "https://registry.npmmirror.com/@vue/reactivity-transform/-/reactivity-transform-3.2.39.tgz"
 | 
			
		||||
  integrity sha512-HGuWu864zStiWs9wBC6JYOP1E00UjMdDWIG5W+FpUx28hV3uz9ODOKVNm/vdOy/Pvzg8+OcANxAVC85WFBbl3A==
 | 
			
		||||
  dependencies:
 | 
			
		||||
    "@babel/parser" "^7.16.4"
 | 
			
		||||
    "@vue/compiler-core" "3.2.39"
 | 
			
		||||
    "@vue/shared" "3.2.39"
 | 
			
		||||
    estree-walker "^2.0.2"
 | 
			
		||||
    magic-string "^0.25.7"
 | 
			
		||||
 | 
			
		||||
"@vue/reactivity-transform@3.3.4":
 | 
			
		||||
  version "3.3.4"
 | 
			
		||||
  resolved "https://registry.npmmirror.com/@vue/reactivity-transform/-/reactivity-transform-3.3.4.tgz#52908476e34d6a65c6c21cd2722d41ed8ae51929"
 | 
			
		||||
@@ -491,11 +465,6 @@
 | 
			
		||||
    "@vue/compiler-ssr" "3.3.4"
 | 
			
		||||
    "@vue/shared" "3.3.4"
 | 
			
		||||
 | 
			
		||||
"@vue/shared@3.2.39":
 | 
			
		||||
  version "3.2.39"
 | 
			
		||||
  resolved "https://registry.npmmirror.com/@vue/shared/-/shared-3.2.39.tgz"
 | 
			
		||||
  integrity sha512-D3dl2ZB9qE6mTuWPk9RlhDeP1dgNRUKC3NJxji74A4yL8M2MwlhLKUC/49WHjrNzSPug58fWx/yFbaTzGAQSBw==
 | 
			
		||||
 | 
			
		||||
"@vue/shared@3.3.4":
 | 
			
		||||
  version "3.3.4"
 | 
			
		||||
  resolved "https://registry.npmmirror.com/@vue/shared/-/shared-3.3.4.tgz#06e83c5027f464eef861c329be81454bc8b70780"
 | 
			
		||||
@@ -566,9 +535,9 @@ antlr4@4.7.2:
 | 
			
		||||
  integrity sha512-vZA1xYufXLe3LX+ja9rIVxjRmILb1x3k7KYZHltRbfJtXjJ1DlFIqt+CbPYmghx0EuzY9DajiDw+MdyEt1qAsQ==
 | 
			
		||||
 | 
			
		||||
anymatch@~3.1.2:
 | 
			
		||||
  version "3.1.2"
 | 
			
		||||
  resolved "https://registry.nlark.com/anymatch/download/anymatch-3.1.2.tgz"
 | 
			
		||||
  integrity sha1-wFV8CWrzLxBhmPT04qODU343hxY=
 | 
			
		||||
  version "3.1.3"
 | 
			
		||||
  resolved "https://registry.npmmirror.com/anymatch/-/anymatch-3.1.3.tgz#790c58b19ba1720a84205b57c618d5ad8524973e"
 | 
			
		||||
  integrity sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==
 | 
			
		||||
  dependencies:
 | 
			
		||||
    normalize-path "^3.0.0"
 | 
			
		||||
    picomatch "^2.0.4"
 | 
			
		||||
@@ -583,10 +552,10 @@ array-union@^2.1.0:
 | 
			
		||||
  resolved "https://registry.npm.taobao.org/array-union/download/array-union-2.1.0.tgz?cache=0&sync_timestamp=1614624262896&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Farray-union%2Fdownload%2Farray-union-2.1.0.tgz"
 | 
			
		||||
  integrity sha1-t5hCCtvrHego2ErNii4j0+/oXo0=
 | 
			
		||||
 | 
			
		||||
asciinema-player@^3.5.0:
 | 
			
		||||
  version "3.5.0"
 | 
			
		||||
  resolved "https://registry.npmmirror.com/asciinema-player/-/asciinema-player-3.5.0.tgz#a4d1c01b56b72dfb6834e9ff90fee5c9652c7dae"
 | 
			
		||||
  integrity sha512-o4B2AscBuCZo4+JB9TBGrfZ7GQL99wsbm08WwmuNJTPd1lyLQJq8wgacnBsdvb2sC0K875ScYr8T5XmfeH/6dg==
 | 
			
		||||
asciinema-player@^3.6.2:
 | 
			
		||||
  version "3.6.2"
 | 
			
		||||
  resolved "https://registry.npmmirror.com/asciinema-player/-/asciinema-player-3.6.2.tgz#f62133f8d38875839881cd15ded713c6022021bd"
 | 
			
		||||
  integrity sha512-698O3/Vm2+V6uFlc6oYma67IZByQsiNpduhEGhuqrxBmKpIYpgouLNNJ3R8DrRPTNNMISHfnLgvAp1x8ChgrTw==
 | 
			
		||||
  dependencies:
 | 
			
		||||
    "@babel/runtime" "^7.21.0"
 | 
			
		||||
    solid-js "^1.3.0"
 | 
			
		||||
@@ -617,8 +586,8 @@ balanced-match@^1.0.0:
 | 
			
		||||
 | 
			
		||||
binary-extensions@^2.0.0:
 | 
			
		||||
  version "2.2.0"
 | 
			
		||||
  resolved "https://registry.nlark.com/binary-extensions/download/binary-extensions-2.2.0.tgz"
 | 
			
		||||
  integrity sha1-dfUC7q+f/eQvyYgpZFvk6na9ni0=
 | 
			
		||||
  resolved "https://registry.npmmirror.com/binary-extensions/-/binary-extensions-2.2.0.tgz#75f502eeaf9ffde42fc98829645be4ea76bd9e2d"
 | 
			
		||||
  integrity sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==
 | 
			
		||||
 | 
			
		||||
brace-expansion@^1.1.7:
 | 
			
		||||
  version "1.1.11"
 | 
			
		||||
@@ -649,9 +618,9 @@ chalk@^4.0.0:
 | 
			
		||||
    supports-color "^7.1.0"
 | 
			
		||||
 | 
			
		||||
"chokidar@>=3.0.0 <4.0.0":
 | 
			
		||||
  version "3.5.2"
 | 
			
		||||
  resolved "https://registry.npmmirror.com/chokidar/download/chokidar-3.5.2.tgz"
 | 
			
		||||
  integrity sha1-26OXb8rbAW9m/TZQIdkWANAcHnU=
 | 
			
		||||
  version "3.5.3"
 | 
			
		||||
  resolved "https://registry.npmmirror.com/chokidar/-/chokidar-3.5.3.tgz#1cf37c8707b932bd1af1ae22c0432e2acd1903bd"
 | 
			
		||||
  integrity sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==
 | 
			
		||||
  dependencies:
 | 
			
		||||
    anymatch "~3.1.2"
 | 
			
		||||
    braces "~3.0.2"
 | 
			
		||||
@@ -735,7 +704,7 @@ dayjs@^1.11.3:
 | 
			
		||||
  resolved "https://registry.npmmirror.com/dayjs/-/dayjs-1.11.3.tgz"
 | 
			
		||||
  integrity sha512-xxwlswWOlGhzgQ4TKzASQkUhqERI3egRNqgV4ScR8wlANA/A9tZ7miXa44vTTKEq5l7vWoL5G57bG3zA+Kow0A==
 | 
			
		||||
 | 
			
		||||
debug@^4.1.1, debug@^4.3.1, debug@^4.3.2:
 | 
			
		||||
debug@^4.1.1, debug@^4.3.2:
 | 
			
		||||
  version "4.3.3"
 | 
			
		||||
  resolved "https://registry.npmmirror.com/debug/download/debug-4.3.3.tgz"
 | 
			
		||||
  integrity sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==
 | 
			
		||||
@@ -783,10 +752,10 @@ doctrine@^3.0.0:
 | 
			
		||||
  dependencies:
 | 
			
		||||
    esutils "^2.0.2"
 | 
			
		||||
 | 
			
		||||
dotenv@^10.0.0:
 | 
			
		||||
  version "10.0.0"
 | 
			
		||||
  resolved "https://registry.nlark.com/dotenv/download/dotenv-10.0.0.tgz"
 | 
			
		||||
  integrity sha1-PUInuPuV+BCWzdK2ZlP7LHCFuoE=
 | 
			
		||||
dotenv@^16.3.1:
 | 
			
		||||
  version "16.3.1"
 | 
			
		||||
  resolved "https://registry.npmmirror.com/dotenv/-/dotenv-16.3.1.tgz#369034de7d7e5b120972693352a3bf112172cc3e"
 | 
			
		||||
  integrity sha512-IPzF4w4/Rd94bA9imS68tZBaYyBWSCE47V1RGuMrB94iyTOIEwRmVL2x/4An+6mETpLrKJ5hQkB8W4kFAadeIQ==
 | 
			
		||||
 | 
			
		||||
dt-sql-parser@^4.0.0-beta.3.2:
 | 
			
		||||
  version "4.0.0-beta.3.2"
 | 
			
		||||
@@ -804,10 +773,10 @@ echarts@^5.4.0:
 | 
			
		||||
    tslib "2.3.0"
 | 
			
		||||
    zrender "5.4.0"
 | 
			
		||||
 | 
			
		||||
element-plus@^2.3.12:
 | 
			
		||||
  version "2.3.12"
 | 
			
		||||
  resolved "https://registry.npmmirror.com/element-plus/-/element-plus-2.3.12.tgz#d3c91d0c701b2b3e67d06a351cb0c42dcc46460e"
 | 
			
		||||
  integrity sha512-fAWpbKCyt+l1dsqSNPOs/F/dBN4Wp5CGAyxbiS5zqDwI4q3QPM+LxLU2h3GUHMIBtMGCvmsG98j5HPMkTKkvcA==
 | 
			
		||||
element-plus@^2.4.0:
 | 
			
		||||
  version "2.4.0"
 | 
			
		||||
  resolved "https://registry.npmmirror.com/element-plus/-/element-plus-2.4.0.tgz#e79249ac4c0a606d377c2f31ad553aa992286fe3"
 | 
			
		||||
  integrity sha512-yJEa8LXkGOOgkfkeqMMEdeX/Dc8EH9qPcRuX91dlhSXxgCKKbp9tH3QFTOG99ibZsrN/Em62nh7ddvbc7I1frw==
 | 
			
		||||
  dependencies:
 | 
			
		||||
    "@ctrl/tinycolor" "^3.4.1"
 | 
			
		||||
    "@element-plus/icons-vue" "^2.0.6"
 | 
			
		||||
@@ -873,14 +842,6 @@ eslint-plugin-vue@^8.2.0:
 | 
			
		||||
    semver "^7.3.5"
 | 
			
		||||
    vue-eslint-parser "^8.0.1"
 | 
			
		||||
 | 
			
		||||
eslint-scope@^5.1.1:
 | 
			
		||||
  version "5.1.1"
 | 
			
		||||
  resolved "https://registry.npmmirror.com/eslint-scope/download/eslint-scope-5.1.1.tgz?cache=0&sync_timestamp=1637466913662&other_urls=https%3A%2F%2Fregistry.npmmirror.com%2Feslint-scope%2Fdownload%2Feslint-scope-5.1.1.tgz"
 | 
			
		||||
  integrity sha1-54blmmbLkrP2wfsNUIqrF0hI9Iw=
 | 
			
		||||
  dependencies:
 | 
			
		||||
    esrecurse "^4.3.0"
 | 
			
		||||
    estraverse "^4.1.1"
 | 
			
		||||
 | 
			
		||||
eslint-scope@^6.0.0:
 | 
			
		||||
  version "6.0.0"
 | 
			
		||||
  resolved "https://registry.npmmirror.com/eslint-scope/download/eslint-scope-6.0.0.tgz?cache=0&sync_timestamp=1637466831846&other_urls=https%3A%2F%2Fregistry.npmmirror.com%2Feslint-scope%2Fdownload%2Feslint-scope-6.0.0.tgz"
 | 
			
		||||
@@ -924,6 +885,11 @@ eslint-visitor-keys@^3.4.0:
 | 
			
		||||
  resolved "https://registry.npmmirror.com/eslint-visitor-keys/-/eslint-visitor-keys-3.4.0.tgz#c7f0f956124ce677047ddbc192a68f999454dedc"
 | 
			
		||||
  integrity sha512-HPpKPUBQcAsZOsHAFwTtIKcYlCje62XB7SEAcxjtmW6TD1WVpkS6i6/hOVtTZIl4zGj/mBqpFVGvaDneik+VoQ==
 | 
			
		||||
 | 
			
		||||
eslint-visitor-keys@^3.4.1:
 | 
			
		||||
  version "3.4.3"
 | 
			
		||||
  resolved "https://registry.npmmirror.com/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz#0cd72fe8550e3c2eae156a96a4dddcd1c8ac5800"
 | 
			
		||||
  integrity sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==
 | 
			
		||||
 | 
			
		||||
eslint@^8.35.0:
 | 
			
		||||
  version "8.35.0"
 | 
			
		||||
  resolved "https://registry.npmmirror.com/eslint/-/eslint-8.35.0.tgz#fffad7c7e326bae606f0e8f436a6158566d42323"
 | 
			
		||||
@@ -1018,11 +984,6 @@ esrecurse@^4.3.0:
 | 
			
		||||
  dependencies:
 | 
			
		||||
    estraverse "^5.2.0"
 | 
			
		||||
 | 
			
		||||
estraverse@^4.1.1:
 | 
			
		||||
  version "4.3.0"
 | 
			
		||||
  resolved "https://registry.npmmirror.com/estraverse/download/estraverse-4.3.0.tgz?cache=0&sync_timestamp=1635237716974&other_urls=https%3A%2F%2Fregistry.npmmirror.com%2Festraverse%2Fdownload%2Festraverse-4.3.0.tgz"
 | 
			
		||||
  integrity sha1-OYrT88WiSUi+dyXoPRGn3ijNvR0=
 | 
			
		||||
 | 
			
		||||
estraverse@^5.1.0, estraverse@^5.2.0:
 | 
			
		||||
  version "5.3.0"
 | 
			
		||||
  resolved "https://registry.npmmirror.com/estraverse/download/estraverse-5.3.0.tgz?cache=0&sync_timestamp=1635237716974&other_urls=https%3A%2F%2Fregistry.npmmirror.com%2Festraverse%2Fdownload%2Festraverse-5.3.0.tgz"
 | 
			
		||||
@@ -1043,10 +1004,10 @@ fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3:
 | 
			
		||||
  resolved "https://registry.nlark.com/fast-deep-equal/download/fast-deep-equal-3.1.3.tgz"
 | 
			
		||||
  integrity sha1-On1WtVnWy8PrUSMlJE5hmmXGxSU=
 | 
			
		||||
 | 
			
		||||
fast-glob@^3.1.1:
 | 
			
		||||
  version "3.2.7"
 | 
			
		||||
  resolved "https://registry.nlark.com/fast-glob/download/fast-glob-3.2.7.tgz"
 | 
			
		||||
  integrity sha1-/Wy3otfpqnp4RhEehaGW1rL3ZqE=
 | 
			
		||||
fast-glob@^3.2.9:
 | 
			
		||||
  version "3.3.1"
 | 
			
		||||
  resolved "https://registry.npmmirror.com/fast-glob/-/fast-glob-3.3.1.tgz#784b4e897340f3dbbef17413b3f11acf03c874c4"
 | 
			
		||||
  integrity sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg==
 | 
			
		||||
  dependencies:
 | 
			
		||||
    "@nodelib/fs.stat" "^2.0.2"
 | 
			
		||||
    "@nodelib/fs.walk" "^1.2.3"
 | 
			
		||||
@@ -1135,11 +1096,6 @@ fsevents@~2.3.2:
 | 
			
		||||
  resolved "https://registry.npmmirror.com/fsevents/download/fsevents-2.3.2.tgz"
 | 
			
		||||
  integrity sha1-ilJveLj99GI7cJ4Ll1xSwkwC/Ro=
 | 
			
		||||
 | 
			
		||||
functional-red-black-tree@^1.0.1:
 | 
			
		||||
  version "1.0.1"
 | 
			
		||||
  resolved "https://registry.npm.taobao.org/functional-red-black-tree/download/functional-red-black-tree-1.0.1.tgz?cache=0&sync_timestamp=1577806294691&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Ffunctional-red-black-tree%2Fdownload%2Ffunctional-red-black-tree-1.0.1.tgz"
 | 
			
		||||
  integrity sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=
 | 
			
		||||
 | 
			
		||||
glob-parent@^5.1.2, glob-parent@~5.1.2:
 | 
			
		||||
  version "5.1.2"
 | 
			
		||||
  resolved "https://registry.npmmirror.com/glob-parent/download/glob-parent-5.1.2.tgz"
 | 
			
		||||
@@ -1173,16 +1129,16 @@ globals@^13.19.0:
 | 
			
		||||
  dependencies:
 | 
			
		||||
    type-fest "^0.20.2"
 | 
			
		||||
 | 
			
		||||
globby@^11.0.3:
 | 
			
		||||
  version "11.0.4"
 | 
			
		||||
  resolved "https://registry.nlark.com/globby/download/globby-11.0.4.tgz"
 | 
			
		||||
  integrity sha1-LLr/d8Lypi5x6bKBOme5ejowAaU=
 | 
			
		||||
globby@^11.1.0:
 | 
			
		||||
  version "11.1.0"
 | 
			
		||||
  resolved "https://registry.npmmirror.com/globby/-/globby-11.1.0.tgz#bd4be98bb042f83d796f7e3811991fbe82a0d34b"
 | 
			
		||||
  integrity sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==
 | 
			
		||||
  dependencies:
 | 
			
		||||
    array-union "^2.1.0"
 | 
			
		||||
    dir-glob "^3.0.1"
 | 
			
		||||
    fast-glob "^3.1.1"
 | 
			
		||||
    ignore "^5.1.4"
 | 
			
		||||
    merge2 "^1.3.0"
 | 
			
		||||
    fast-glob "^3.2.9"
 | 
			
		||||
    ignore "^5.2.0"
 | 
			
		||||
    merge2 "^1.4.1"
 | 
			
		||||
    slash "^3.0.0"
 | 
			
		||||
 | 
			
		||||
good-listener@^1.2.2:
 | 
			
		||||
@@ -1197,25 +1153,25 @@ grapheme-splitter@^1.0.4:
 | 
			
		||||
  resolved "https://registry.npmmirror.com/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz#9cf3a665c6247479896834af35cf1dbb4400767e"
 | 
			
		||||
  integrity sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==
 | 
			
		||||
 | 
			
		||||
graphemer@^1.4.0:
 | 
			
		||||
  version "1.4.0"
 | 
			
		||||
  resolved "https://registry.npmmirror.com/graphemer/-/graphemer-1.4.0.tgz#fb2f1d55e0e3a1849aeffc90c4fa0dd53a0e66c6"
 | 
			
		||||
  integrity sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==
 | 
			
		||||
 | 
			
		||||
has-flag@^4.0.0:
 | 
			
		||||
  version "4.0.0"
 | 
			
		||||
  resolved "https://registry.nlark.com/has-flag/download/has-flag-4.0.0.tgz?cache=0&sync_timestamp=1626715907927&other_urls=https%3A%2F%2Fregistry.nlark.com%2Fhas-flag%2Fdownload%2Fhas-flag-4.0.0.tgz"
 | 
			
		||||
  integrity sha1-lEdx/ZyByBJlxNaUGGDaBrtZR5s=
 | 
			
		||||
 | 
			
		||||
ignore@^5.1.4, ignore@^5.1.8:
 | 
			
		||||
  version "5.1.9"
 | 
			
		||||
  resolved "https://registry.npmmirror.com/ignore/download/ignore-5.1.9.tgz?cache=0&sync_timestamp=1635926740448&other_urls=https%3A%2F%2Fregistry.npmmirror.com%2Fignore%2Fdownload%2Fignore-5.1.9.tgz"
 | 
			
		||||
  integrity sha1-nsGly+jhRG7GDUQgBg1Dqm5zgvs=
 | 
			
		||||
 | 
			
		||||
ignore@^5.2.0:
 | 
			
		||||
ignore@^5.2.0, ignore@^5.2.4:
 | 
			
		||||
  version "5.2.4"
 | 
			
		||||
  resolved "https://registry.npmmirror.com/ignore/-/ignore-5.2.4.tgz#a291c0c6178ff1b960befe47fcdec301674a6324"
 | 
			
		||||
  integrity sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==
 | 
			
		||||
 | 
			
		||||
immutable@^4.0.0:
 | 
			
		||||
  version "4.0.0"
 | 
			
		||||
  resolved "https://registry.npmmirror.com/immutable/download/immutable-4.0.0.tgz?cache=0&sync_timestamp=1633650644342&other_urls=https%3A%2F%2Fregistry.npmmirror.com%2Fimmutable%2Fdownload%2Fimmutable-4.0.0.tgz"
 | 
			
		||||
  integrity sha1-uG943mre82CDle+yaakUYnl+LCM=
 | 
			
		||||
  version "4.3.4"
 | 
			
		||||
  resolved "https://registry.npmmirror.com/immutable/-/immutable-4.3.4.tgz#2e07b33837b4bb7662f288c244d1ced1ef65a78f"
 | 
			
		||||
  integrity sha512-fsXeu4J4i6WNWSikpI88v/PcVflZz+6kMhUfIwc5SY+poQRPnaf5V7qds6SUyUN3cVxEzuCab7QIoLOQ+DQ1wA==
 | 
			
		||||
 | 
			
		||||
import-fresh@^3.0.0, import-fresh@^3.2.1:
 | 
			
		||||
  version "3.3.0"
 | 
			
		||||
@@ -1245,8 +1201,8 @@ inherits@2:
 | 
			
		||||
 | 
			
		||||
is-binary-path@~2.1.0:
 | 
			
		||||
  version "2.1.0"
 | 
			
		||||
  resolved "https://registry.nlark.com/is-binary-path/download/is-binary-path-2.1.0.tgz"
 | 
			
		||||
  integrity sha1-6h9/O4DwZCNug0cPhsCcJU+0Wwk=
 | 
			
		||||
  resolved "https://registry.npmmirror.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09"
 | 
			
		||||
  integrity sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==
 | 
			
		||||
  dependencies:
 | 
			
		||||
    binary-extensions "^2.0.0"
 | 
			
		||||
 | 
			
		||||
@@ -1304,11 +1260,6 @@ json-stable-stringify-without-jsonify@^1.0.1:
 | 
			
		||||
  resolved "https://registry.nlark.com/json-stable-stringify-without-jsonify/download/json-stable-stringify-without-jsonify-1.0.1.tgz"
 | 
			
		||||
  integrity sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=
 | 
			
		||||
 | 
			
		||||
klona@^2.0.4:
 | 
			
		||||
  version "2.0.5"
 | 
			
		||||
  resolved "https://registry.npmmirror.com/klona/download/klona-2.0.5.tgz"
 | 
			
		||||
  integrity sha1-0WZXTZAHY5XZljqnqSj6u412r7w=
 | 
			
		||||
 | 
			
		||||
levn@^0.4.1:
 | 
			
		||||
  version "0.4.1"
 | 
			
		||||
  resolved "https://registry.nlark.com/levn/download/levn-0.4.1.tgz"
 | 
			
		||||
@@ -1351,13 +1302,6 @@ lru-cache@^6.0.0:
 | 
			
		||||
  dependencies:
 | 
			
		||||
    yallist "^4.0.0"
 | 
			
		||||
 | 
			
		||||
magic-string@^0.25.7:
 | 
			
		||||
  version "0.25.9"
 | 
			
		||||
  resolved "https://registry.npmmirror.com/magic-string/-/magic-string-0.25.9.tgz"
 | 
			
		||||
  integrity sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==
 | 
			
		||||
  dependencies:
 | 
			
		||||
    sourcemap-codec "^1.4.8"
 | 
			
		||||
 | 
			
		||||
magic-string@^0.30.0:
 | 
			
		||||
  version "0.30.0"
 | 
			
		||||
  resolved "https://registry.npmmirror.com/magic-string/-/magic-string-0.30.0.tgz#fd58a4748c5c4547338a424e90fa5dd17f4de529"
 | 
			
		||||
@@ -1370,10 +1314,10 @@ memoize-one@^6.0.0:
 | 
			
		||||
  resolved "https://registry.npmmirror.com/memoize-one/download/memoize-one-6.0.0.tgz?cache=0&sync_timestamp=1634697208428&other_urls=https%3A%2F%2Fregistry.npmmirror.com%2Fmemoize-one%2Fdownload%2Fmemoize-one-6.0.0.tgz"
 | 
			
		||||
  integrity sha1-slkbhx7YKUiu5HJ9xqvO7qyMEEU=
 | 
			
		||||
 | 
			
		||||
merge2@^1.3.0:
 | 
			
		||||
merge2@^1.3.0, merge2@^1.4.1:
 | 
			
		||||
  version "1.4.1"
 | 
			
		||||
  resolved "https://registry.npm.taobao.org/merge2/download/merge2-1.4.1.tgz"
 | 
			
		||||
  integrity sha1-Q2iJL4hekHRVpv19xVwMnUBJkK4=
 | 
			
		||||
  resolved "https://registry.npmmirror.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae"
 | 
			
		||||
  integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==
 | 
			
		||||
 | 
			
		||||
micromatch@^4.0.4:
 | 
			
		||||
  version "4.0.4"
 | 
			
		||||
@@ -1414,10 +1358,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.43.0:
 | 
			
		||||
  version "0.43.0"
 | 
			
		||||
  resolved "https://registry.npmmirror.com/monaco-editor/-/monaco-editor-0.43.0.tgz#cb02a8d23d1249ad00b7cffe8bbecc2ac09d4baf"
 | 
			
		||||
  integrity sha512-cnoqwQi/9fml2Szamv1XbSJieGJ1Dc8tENVMD26Kcfl7xGQWp7OBKMjlwKVGYFJ3/AXJjSOGvcqK7Ry/j9BM1Q==
 | 
			
		||||
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-sql-languages@^0.11.0:
 | 
			
		||||
  version "0.11.0"
 | 
			
		||||
@@ -1468,15 +1412,10 @@ nearley@^2.20.1:
 | 
			
		||||
    railroad-diagrams "^1.0.0"
 | 
			
		||||
    randexp "0.4.6"
 | 
			
		||||
 | 
			
		||||
neo-async@^2.6.2:
 | 
			
		||||
  version "2.6.2"
 | 
			
		||||
  resolved "https://registry.npm.taobao.org/neo-async/download/neo-async-2.6.2.tgz"
 | 
			
		||||
  integrity sha1-tKr7k+OustgXTKU88WOrfXMIMF8=
 | 
			
		||||
 | 
			
		||||
normalize-path@^3.0.0, normalize-path@~3.0.0:
 | 
			
		||||
  version "3.0.0"
 | 
			
		||||
  resolved "https://registry.npm.taobao.org/normalize-path/download/normalize-path-3.0.0.tgz"
 | 
			
		||||
  integrity sha1-Dc1p/yOhybEf0JeDFmRKA4ghamU=
 | 
			
		||||
  resolved "https://registry.npmmirror.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65"
 | 
			
		||||
  integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==
 | 
			
		||||
 | 
			
		||||
normalize-wheel-es@^1.2.0:
 | 
			
		||||
  version "1.2.0"
 | 
			
		||||
@@ -1553,15 +1492,20 @@ picocolors@^1.0.0:
 | 
			
		||||
  resolved "https://registry.npmmirror.com/picocolors/download/picocolors-1.0.0.tgz?cache=0&sync_timestamp=1634093378416&other_urls=https%3A%2F%2Fregistry.npmmirror.com%2Fpicocolors%2Fdownload%2Fpicocolors-1.0.0.tgz"
 | 
			
		||||
  integrity sha1-y1vcdP8/UYkiNur3nWi8RFZKuBw=
 | 
			
		||||
 | 
			
		||||
picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.2.3:
 | 
			
		||||
picomatch@^2.0.4, picomatch@^2.2.1:
 | 
			
		||||
  version "2.3.1"
 | 
			
		||||
  resolved "https://registry.npmmirror.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42"
 | 
			
		||||
  integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==
 | 
			
		||||
 | 
			
		||||
picomatch@^2.2.3:
 | 
			
		||||
  version "2.3.0"
 | 
			
		||||
  resolved "https://registry.nlark.com/picomatch/download/picomatch-2.3.0.tgz?cache=0&sync_timestamp=1621648246651&other_urls=https%3A%2F%2Fregistry.nlark.com%2Fpicomatch%2Fdownload%2Fpicomatch-2.3.0.tgz"
 | 
			
		||||
  integrity sha1-8fBh3o9qS/AiiS4tEoI0+5gwKXI=
 | 
			
		||||
 | 
			
		||||
pinia@^2.1.6:
 | 
			
		||||
  version "2.1.6"
 | 
			
		||||
  resolved "https://registry.npmmirror.com/pinia/-/pinia-2.1.6.tgz#e88959f14b61c4debd9c42d0c9944e2875cbe0fa"
 | 
			
		||||
  integrity sha512-bIU6QuE5qZviMmct5XwCesXelb5VavdOWKWaB17ggk++NUwQWWbP5YnsONTk3b752QkW9sACiR81rorpeOMSvQ==
 | 
			
		||||
pinia@^2.1.7:
 | 
			
		||||
  version "2.1.7"
 | 
			
		||||
  resolved "https://registry.npmmirror.com/pinia/-/pinia-2.1.7.tgz#4cf5420d9324ca00b7b4984d3fbf693222115bbc"
 | 
			
		||||
  integrity sha512-+C2AHFtcFqjPih0zpYuvof37SFxMQ7OEG2zV9jRI12i9BOy3YQVAHwdKtyyc8pDcDyIc33WCIsZaCFWU7WWxGQ==
 | 
			
		||||
  dependencies:
 | 
			
		||||
    "@vue/devtools-api" "^6.5.0"
 | 
			
		||||
    vue-demi ">=0.14.5"
 | 
			
		||||
@@ -1629,8 +1573,8 @@ randexp@0.4.6:
 | 
			
		||||
 | 
			
		||||
readdirp@~3.6.0:
 | 
			
		||||
  version "3.6.0"
 | 
			
		||||
  resolved "https://registry.npm.taobao.org/readdirp/download/readdirp-3.6.0.tgz"
 | 
			
		||||
  integrity sha1-dKNwvYVxFuJFspzJc0DNQxoCpsc=
 | 
			
		||||
  resolved "https://registry.npmmirror.com/readdirp/-/readdirp-3.6.0.tgz#74a370bd857116e245b29cc97340cd431a02a6c7"
 | 
			
		||||
  integrity sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==
 | 
			
		||||
  dependencies:
 | 
			
		||||
    picomatch "^2.2.1"
 | 
			
		||||
 | 
			
		||||
@@ -1639,7 +1583,7 @@ regenerator-runtime@^0.13.11:
 | 
			
		||||
  resolved "https://registry.npmmirror.com/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz#f6dca3e7ceec20590d07ada785636a90cdca17f9"
 | 
			
		||||
  integrity sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==
 | 
			
		||||
 | 
			
		||||
regexpp@^3.1.0, regexpp@^3.2.0:
 | 
			
		||||
regexpp@^3.2.0:
 | 
			
		||||
  version "3.2.0"
 | 
			
		||||
  resolved "https://registry.nlark.com/regexpp/download/regexpp-3.2.0.tgz?cache=0&sync_timestamp=1623668872577&other_urls=https%3A%2F%2Fregistry.nlark.com%2Fregexpp%2Fdownload%2Fregexpp-3.2.0.tgz"
 | 
			
		||||
  integrity sha1-BCWido2PI7rXDKS5BGH6LxIT4bI=
 | 
			
		||||
@@ -1680,18 +1624,10 @@ run-parallel@^1.1.9:
 | 
			
		||||
  dependencies:
 | 
			
		||||
    queue-microtask "^1.2.2"
 | 
			
		||||
 | 
			
		||||
sass-loader@^13.2.0:
 | 
			
		||||
  version "13.2.0"
 | 
			
		||||
  resolved "https://registry.npmmirror.com/sass-loader/-/sass-loader-13.2.0.tgz#80195050f58c9aac63b792fa52acb6f5e0f6bdc3"
 | 
			
		||||
  integrity sha512-JWEp48djQA4nbZxmgC02/Wh0eroSUutulROUusYJO9P9zltRbNN80JCBHqRGzjd4cmZCa/r88xgfkjGD0TXsHg==
 | 
			
		||||
  dependencies:
 | 
			
		||||
    klona "^2.0.4"
 | 
			
		||||
    neo-async "^2.6.2"
 | 
			
		||||
 | 
			
		||||
sass@^1.62.0:
 | 
			
		||||
  version "1.62.0"
 | 
			
		||||
  resolved "https://registry.npmmirror.com/sass/-/sass-1.62.0.tgz#3686b2195b93295d20765135e562366b33ece37d"
 | 
			
		||||
  integrity sha512-Q4USplo4pLYgCi+XlipZCWUQz5pkg/ruSSgJ0WRDSb/+3z9tXUOkQ7QPYn4XrhZKYAK4HlpaQecRwKLJX6+DBg==
 | 
			
		||||
sass@^1.69.0:
 | 
			
		||||
  version "1.69.0"
 | 
			
		||||
  resolved "https://registry.npmmirror.com/sass/-/sass-1.69.0.tgz#5195075371c239ed556280cf2f5944d234f42679"
 | 
			
		||||
  integrity sha512-l3bbFpfTOGgQZCLU/gvm1lbsQ5mC/WnLz3djL2v4WCJBDrWm58PO+jgngcGRNnKUh6wSsdm50YaovTqskZ0xDQ==
 | 
			
		||||
  dependencies:
 | 
			
		||||
    chokidar ">=3.0.0 <4.0.0"
 | 
			
		||||
    immutable "^4.0.0"
 | 
			
		||||
@@ -1721,6 +1657,13 @@ semver@^7.3.6:
 | 
			
		||||
  dependencies:
 | 
			
		||||
    lru-cache "^6.0.0"
 | 
			
		||||
 | 
			
		||||
semver@^7.5.4:
 | 
			
		||||
  version "7.5.4"
 | 
			
		||||
  resolved "https://registry.npmmirror.com/semver/-/semver-7.5.4.tgz#483986ec4ed38e1c6c48c34894a9182dbff68a6e"
 | 
			
		||||
  integrity sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==
 | 
			
		||||
  dependencies:
 | 
			
		||||
    lru-cache "^6.0.0"
 | 
			
		||||
 | 
			
		||||
shebang-command@^2.0.0:
 | 
			
		||||
  version "2.0.0"
 | 
			
		||||
  resolved "https://registry.nlark.com/shebang-command/download/shebang-command-2.0.0.tgz"
 | 
			
		||||
@@ -1745,30 +1688,20 @@ solid-js@^1.3.0:
 | 
			
		||||
  dependencies:
 | 
			
		||||
    csstype "^3.1.0"
 | 
			
		||||
 | 
			
		||||
sortablejs@^1.13.0:
 | 
			
		||||
  version "1.14.0"
 | 
			
		||||
  resolved "https://registry.nlark.com/sortablejs/download/sortablejs-1.14.0.tgz?cache=0&sync_timestamp=1625423971526&other_urls=https%3A%2F%2Fregistry.nlark.com%2Fsortablejs%2Fdownload%2Fsortablejs-1.14.0.tgz"
 | 
			
		||||
  integrity sha1-bS4XzL2yX0ZHNN9iHU811Ks1s9g=
 | 
			
		||||
sortablejs@^1.15.0:
 | 
			
		||||
  version "1.15.0"
 | 
			
		||||
  resolved "https://registry.npmmirror.com/sortablejs/-/sortablejs-1.15.0.tgz#53230b8aa3502bb77a29e2005808ffdb4a5f7e2a"
 | 
			
		||||
  integrity sha512-bv9qgVMjUMf89wAvM6AxVvS/4MX3sPeN0+agqShejLU5z5GX4C75ow1O2e5k4L6XItUyAK3gH6AxSbXrOM5e8w==
 | 
			
		||||
 | 
			
		||||
"source-map-js@>=0.6.2 <2.0.0", source-map-js@^1.0.1:
 | 
			
		||||
  version "1.0.1"
 | 
			
		||||
  resolved "https://registry.npmmirror.com/source-map-js/download/source-map-js-1.0.1.tgz?cache=0&sync_timestamp=1636400753943&other_urls=https%3A%2F%2Fregistry.npmmirror.com%2Fsource-map-js%2Fdownload%2Fsource-map-js-1.0.1.tgz"
 | 
			
		||||
  integrity sha512-4+TN2b3tqOCd/kaGRJ/sTYA0tR0mdXx26ipdolxcwtJVqEnqNYvlCAt1q3ypy4QMlYus+Zh34RNtYLoq2oQ4IA==
 | 
			
		||||
 | 
			
		||||
source-map-js@^1.0.2:
 | 
			
		||||
"source-map-js@>=0.6.2 <2.0.0", source-map-js@^1.0.2:
 | 
			
		||||
  version "1.0.2"
 | 
			
		||||
  resolved "https://registry.npmmirror.com/source-map-js/-/source-map-js-1.0.2.tgz"
 | 
			
		||||
  integrity sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==
 | 
			
		||||
 | 
			
		||||
source-map@^0.6.1:
 | 
			
		||||
  version "0.6.1"
 | 
			
		||||
  resolved "https://registry.npmmirror.com/source-map/-/source-map-0.6.1.tgz"
 | 
			
		||||
  integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==
 | 
			
		||||
 | 
			
		||||
sourcemap-codec@^1.4.8:
 | 
			
		||||
  version "1.4.8"
 | 
			
		||||
  resolved "https://registry.npmmirror.com/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz"
 | 
			
		||||
  integrity sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==
 | 
			
		||||
source-map-js@^1.0.1:
 | 
			
		||||
  version "1.0.1"
 | 
			
		||||
  resolved "https://registry.npmmirror.com/source-map-js/download/source-map-js-1.0.1.tgz?cache=0&sync_timestamp=1636400753943&other_urls=https%3A%2F%2Fregistry.npmmirror.com%2Fsource-map-js%2Fdownload%2Fsource-map-js-1.0.1.tgz"
 | 
			
		||||
  integrity sha512-4+TN2b3tqOCd/kaGRJ/sTYA0tR0mdXx26ipdolxcwtJVqEnqNYvlCAt1q3ypy4QMlYus+Zh34RNtYLoq2oQ4IA==
 | 
			
		||||
 | 
			
		||||
sql-formatter@^12.1.2:
 | 
			
		||||
  version "12.1.2"
 | 
			
		||||
@@ -1814,23 +1747,16 @@ to-regex-range@^5.0.1:
 | 
			
		||||
  dependencies:
 | 
			
		||||
    is-number "^7.0.0"
 | 
			
		||||
 | 
			
		||||
ts-api-utils@^1.0.1:
 | 
			
		||||
  version "1.0.3"
 | 
			
		||||
  resolved "https://registry.npmmirror.com/ts-api-utils/-/ts-api-utils-1.0.3.tgz#f12c1c781d04427313dbac808f453f050e54a331"
 | 
			
		||||
  integrity sha512-wNMeqtMz5NtwpT/UZGY5alT+VoKdSsOOP/kqHFcUW1P/VRhH2wJ48+DN2WwUliNbQ976ETwDL0Ifd2VVvgonvg==
 | 
			
		||||
 | 
			
		||||
tslib@2.3.0:
 | 
			
		||||
  version "2.3.0"
 | 
			
		||||
  resolved "https://registry.nlark.com/tslib/download/tslib-2.3.0.tgz"
 | 
			
		||||
  integrity sha1-gDuM2rPhK6WBpMpByIObuw2ssJ4=
 | 
			
		||||
 | 
			
		||||
tslib@^1.8.1:
 | 
			
		||||
  version "1.14.1"
 | 
			
		||||
  resolved "https://registry.nlark.com/tslib/download/tslib-1.14.1.tgz"
 | 
			
		||||
  integrity sha1-zy04vcNKE0vK8QkcQfZhni9nLQA=
 | 
			
		||||
 | 
			
		||||
tsutils@^3.21.0:
 | 
			
		||||
  version "3.21.0"
 | 
			
		||||
  resolved "https://registry.npm.taobao.org/tsutils/download/tsutils-3.21.0.tgz"
 | 
			
		||||
  integrity sha1-tIcX05TOpsHglpg+7Vjp1hcVtiM=
 | 
			
		||||
  dependencies:
 | 
			
		||||
    tslib "^1.8.1"
 | 
			
		||||
 | 
			
		||||
type-check@^0.4.0, type-check@~0.4.0:
 | 
			
		||||
  version "0.4.0"
 | 
			
		||||
  resolved "https://registry.nlark.com/type-check/download/type-check-0.4.0.tgz"
 | 
			
		||||
@@ -1855,10 +1781,10 @@ uri-js@^4.2.2:
 | 
			
		||||
  dependencies:
 | 
			
		||||
    punycode "^2.1.0"
 | 
			
		||||
 | 
			
		||||
vite@^4.4.9:
 | 
			
		||||
  version "4.4.9"
 | 
			
		||||
  resolved "https://registry.npmmirror.com/vite/-/vite-4.4.9.tgz#1402423f1a2f8d66fd8d15e351127c7236d29d3d"
 | 
			
		||||
  integrity sha512-2mbUn2LlUmNASWwSCNSJ/EG2HuSRTnVNaydp6vMCm5VIqJsjMfbIWtbH2kDuwUVW5mMUKKZvGPX/rqeqVvv1XA==
 | 
			
		||||
vite@^4.4.11:
 | 
			
		||||
  version "4.4.11"
 | 
			
		||||
  resolved "https://registry.npmmirror.com/vite/-/vite-4.4.11.tgz#babdb055b08c69cfc4c468072a2e6c9ca62102b0"
 | 
			
		||||
  integrity sha512-ksNZJlkcU9b0lBwAGZGGaZHCMqHsc8OpgtoYhsQ4/I2v5cnpmmmqe5pM4nv/4Hn6G/2GhTdj0DhZh2e+Er1q5A==
 | 
			
		||||
  dependencies:
 | 
			
		||||
    esbuild "^0.18.10"
 | 
			
		||||
    postcss "^8.4.27"
 | 
			
		||||
@@ -1896,10 +1822,10 @@ vue-eslint-parser@^8.0.1:
 | 
			
		||||
    lodash "^4.17.21"
 | 
			
		||||
    semver "^7.3.5"
 | 
			
		||||
 | 
			
		||||
vue-eslint-parser@^9.1.1:
 | 
			
		||||
  version "9.1.1"
 | 
			
		||||
  resolved "https://registry.npmmirror.com/vue-eslint-parser/-/vue-eslint-parser-9.1.1.tgz#3f4859be7e9bb7edaa1dc7edb05abffee72bf3dd"
 | 
			
		||||
  integrity sha512-C2aI/r85Q6tYcz4dpgvrs4wH/MqVrRAVIdpYedrxnATDHHkb+TroeRcDpKWGZCx/OcECMWfz7tVwQ8e+Opy6rA==
 | 
			
		||||
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"
 | 
			
		||||
  integrity sha512-Clr85iD2XFZ3lJ52/ppmUDG/spxQu6+MAeHXjjyI4I1NUYZ9xmenQp4N0oaHJhrA8OOxltCVxMRfANGa70vU0g==
 | 
			
		||||
  dependencies:
 | 
			
		||||
    debug "^4.3.4"
 | 
			
		||||
    eslint-scope "^7.1.1"
 | 
			
		||||
@@ -1909,10 +1835,10 @@ vue-eslint-parser@^9.1.1:
 | 
			
		||||
    lodash "^4.17.21"
 | 
			
		||||
    semver "^7.3.6"
 | 
			
		||||
 | 
			
		||||
vue-router@^4.2.4:
 | 
			
		||||
  version "4.2.4"
 | 
			
		||||
  resolved "https://registry.npmmirror.com/vue-router/-/vue-router-4.2.4.tgz#382467a7e2923e6a85f015d081e1508052c191b9"
 | 
			
		||||
  integrity sha512-9PISkmaCO02OzPVOMq2w82ilty6+xJmQrarYZDkjZBfl4RvYAlt4PKnEX21oW4KTtWfa9OuO/b3qk1Od3AEdCQ==
 | 
			
		||||
vue-router@^4.2.5:
 | 
			
		||||
  version "4.2.5"
 | 
			
		||||
  resolved "https://registry.npmmirror.com/vue-router/-/vue-router-4.2.5.tgz#b9e3e08f1bd9ea363fdd173032620bc50cf0e98a"
 | 
			
		||||
  integrity sha512-DIUpKcyg4+PTQKfFPX88UWhlagBEBEfJ5A8XDXRJLUnZOvcpMF8o/dnL90vpVkGaPbjvXazV/rC1qBKrZlFugw==
 | 
			
		||||
  dependencies:
 | 
			
		||||
    "@vue/devtools-api" "^6.5.0"
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -17,6 +17,7 @@ jwt:
 | 
			
		||||
# 资源密码aes加密key
 | 
			
		||||
aes:
 | 
			
		||||
  key: 1111111111111111
 | 
			
		||||
# 若存在mysql配置,优先使用mysql
 | 
			
		||||
mysql:
 | 
			
		||||
  # 自动升级数据库
 | 
			
		||||
  auto-migration: false
 | 
			
		||||
@@ -26,6 +27,9 @@ mysql:
 | 
			
		||||
  db-name: mayfly-go
 | 
			
		||||
  config: charset=utf8&loc=Local&parseTime=true
 | 
			
		||||
  max-idle-conns: 5
 | 
			
		||||
sqlite:
 | 
			
		||||
  path: ./mayfly-go.sqlite
 | 
			
		||||
  max-idle-conns: 5
 | 
			
		||||
# 若同时部署多台机器,则需要配置redis信息用于缓存权限码、验证码、公私钥等
 | 
			
		||||
# redis:
 | 
			
		||||
#   host: localhost
 | 
			
		||||
 
 | 
			
		||||
@@ -13,22 +13,24 @@ require (
 | 
			
		||||
	github.com/go-sql-driver/mysql v1.7.1
 | 
			
		||||
	github.com/golang-jwt/jwt/v5 v5.0.0
 | 
			
		||||
	github.com/gorilla/websocket v1.5.0
 | 
			
		||||
	github.com/kanzihuang/vitess/go/vt/sqlparser v0.0.0-20231007020222-b91ee5ef3b31
 | 
			
		||||
	github.com/lib/pq v1.10.9
 | 
			
		||||
	github.com/lionsoul2014/ip2region/binding/golang v0.0.0-20230712084735-068dc2aee82d
 | 
			
		||||
	github.com/mojocn/base64Captcha v1.3.5 // 验证码
 | 
			
		||||
	github.com/pkg/errors v0.9.1
 | 
			
		||||
	github.com/pkg/sftp v1.13.6
 | 
			
		||||
	github.com/pquerna/otp v1.4.0
 | 
			
		||||
	github.com/redis/go-redis/v9 v9.1.0
 | 
			
		||||
	github.com/redis/go-redis/v9 v9.2.1
 | 
			
		||||
	github.com/robfig/cron/v3 v3.0.1 // 定时任务
 | 
			
		||||
	github.com/xwb1989/sqlparser v0.0.0-20180606152119-120387863bf2
 | 
			
		||||
	github.com/stretchr/testify v1.8.4
 | 
			
		||||
	go.mongodb.org/mongo-driver v1.12.1 // mongo
 | 
			
		||||
	golang.org/x/crypto v0.13.0 // ssh
 | 
			
		||||
	golang.org/x/oauth2 v0.12.0
 | 
			
		||||
	golang.org/x/crypto v0.14.0 // ssh
 | 
			
		||||
	golang.org/x/oauth2 v0.13.0
 | 
			
		||||
	gopkg.in/yaml.v3 v3.0.1
 | 
			
		||||
	// gorm
 | 
			
		||||
	gorm.io/driver/mysql v1.5.1
 | 
			
		||||
	gorm.io/gorm v1.25.4
 | 
			
		||||
	gorm.io/driver/mysql v1.5.2
 | 
			
		||||
	gorm.io/driver/sqlite v1.5.4
 | 
			
		||||
	gorm.io/gorm v1.25.5
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
require (
 | 
			
		||||
@@ -37,27 +39,31 @@ require (
 | 
			
		||||
	github.com/bytedance/sonic v1.9.1 // indirect
 | 
			
		||||
	github.com/cespare/xxhash/v2 v2.2.0 // indirect
 | 
			
		||||
	github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect
 | 
			
		||||
	github.com/davecgh/go-spew v1.1.1 // indirect
 | 
			
		||||
	github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
 | 
			
		||||
	github.com/gabriel-vasile/mimetype v1.4.2 // indirect
 | 
			
		||||
	github.com/gin-contrib/sse v0.1.0 // indirect
 | 
			
		||||
	github.com/go-asn1-ber/asn1-ber v1.5.4 // indirect
 | 
			
		||||
	github.com/goccy/go-json v0.10.2 // indirect
 | 
			
		||||
	github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect
 | 
			
		||||
	github.com/golang/glog v1.0.0 // indirect
 | 
			
		||||
	github.com/golang/protobuf v1.5.3 // indirect
 | 
			
		||||
	github.com/golang/snappy v0.0.1 // indirect
 | 
			
		||||
	github.com/golang/snappy v0.0.4 // indirect
 | 
			
		||||
	github.com/jinzhu/inflection v1.0.0 // indirect
 | 
			
		||||
	github.com/jinzhu/now v1.1.5 // indirect
 | 
			
		||||
	github.com/json-iterator/go v1.1.12 // indirect
 | 
			
		||||
	github.com/klauspost/compress v1.13.6 // indirect
 | 
			
		||||
	github.com/klauspost/compress v1.16.5 // indirect
 | 
			
		||||
	github.com/klauspost/cpuid/v2 v2.2.4 // indirect
 | 
			
		||||
	github.com/kr/fs v0.1.0 // indirect
 | 
			
		||||
	github.com/kr/pretty v0.3.0 // indirect
 | 
			
		||||
	github.com/leodido/go-urn v1.2.4 // indirect
 | 
			
		||||
	github.com/mattn/go-isatty v0.0.19 // indirect
 | 
			
		||||
	github.com/mattn/go-sqlite3 v1.14.17 // indirect
 | 
			
		||||
	github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
 | 
			
		||||
	github.com/modern-go/reflect2 v1.0.2 // indirect
 | 
			
		||||
	github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe // indirect
 | 
			
		||||
	github.com/montanaflynn/stats v0.7.0 // indirect
 | 
			
		||||
	github.com/pelletier/go-toml/v2 v2.0.8 // indirect
 | 
			
		||||
	github.com/pmezard/go-difflib v1.0.0 // indirect
 | 
			
		||||
	github.com/spf13/pflag v1.0.5 // indirect
 | 
			
		||||
	github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
 | 
			
		||||
	github.com/ugorji/go/codec v1.2.11 // indirect
 | 
			
		||||
	github.com/xdg-go/pbkdf2 v1.0.0 // indirect
 | 
			
		||||
@@ -65,12 +71,15 @@ require (
 | 
			
		||||
	github.com/xdg-go/stringprep v1.0.4 // indirect
 | 
			
		||||
	github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d // indirect
 | 
			
		||||
	golang.org/x/arch v0.3.0 // indirect
 | 
			
		||||
	golang.org/x/exp v0.0.0-20230519143937-03e91628a987 // indirect
 | 
			
		||||
	golang.org/x/image v0.0.0-20220302094943-723b81ca9867 // indirect
 | 
			
		||||
	golang.org/x/net v0.15.0 // indirect
 | 
			
		||||
	golang.org/x/net v0.16.0 // indirect
 | 
			
		||||
	golang.org/x/sync v0.1.0 // indirect
 | 
			
		||||
	golang.org/x/sys v0.12.0 // indirect
 | 
			
		||||
	golang.org/x/sys v0.13.0 // indirect
 | 
			
		||||
	golang.org/x/text v0.13.0 // indirect
 | 
			
		||||
	google.golang.org/appengine v1.6.7 // indirect
 | 
			
		||||
	google.golang.org/genproto v0.0.0-20230131230820-1c016267d619 // indirect
 | 
			
		||||
	google.golang.org/grpc v1.52.3 // indirect
 | 
			
		||||
	google.golang.org/protobuf v1.31.0 // indirect
 | 
			
		||||
	gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
 | 
			
		||||
	vitess.io/vitess v0.17.3 // indirect
 | 
			
		||||
)
 | 
			
		||||
 
 | 
			
		||||
@@ -15,8 +15,8 @@ import (
 | 
			
		||||
	"mayfly-go/pkg/ginx"
 | 
			
		||||
	"mayfly-go/pkg/otp"
 | 
			
		||||
	"mayfly-go/pkg/req"
 | 
			
		||||
	"mayfly-go/pkg/utils/collx"
 | 
			
		||||
	"mayfly-go/pkg/utils/cryptox"
 | 
			
		||||
	"mayfly-go/pkg/utils/jsonx"
 | 
			
		||||
	"mayfly-go/pkg/ws"
 | 
			
		||||
	"strconv"
 | 
			
		||||
	"time"
 | 
			
		||||
@@ -43,7 +43,7 @@ func (a *AccountLogin) Login(rc *req.Ctx) {
 | 
			
		||||
	username := loginForm.Username
 | 
			
		||||
 | 
			
		||||
	clientIp := getIpAndRegion(rc)
 | 
			
		||||
	rc.ReqParam = jsonx.Kvs("username", username, "ip", clientIp)
 | 
			
		||||
	rc.ReqParam = collx.Kvs("username", username, "ip", clientIp)
 | 
			
		||||
 | 
			
		||||
	originPwd, err := cryptox.DefaultRsaDecrypt(loginForm.Password, true)
 | 
			
		||||
	biz.ErrIsNilAppendErr(err, "解密密码错误: %s")
 | 
			
		||||
 
 | 
			
		||||
@@ -11,6 +11,7 @@ import (
 | 
			
		||||
	"mayfly-go/pkg/cache"
 | 
			
		||||
	"mayfly-go/pkg/otp"
 | 
			
		||||
	"mayfly-go/pkg/req"
 | 
			
		||||
	"mayfly-go/pkg/utils/collx"
 | 
			
		||||
	"mayfly-go/pkg/utils/jsonx"
 | 
			
		||||
	"mayfly-go/pkg/utils/netx"
 | 
			
		||||
	"mayfly-go/pkg/utils/stringx"
 | 
			
		||||
@@ -29,7 +30,7 @@ func LastLoginCheck(account *sysentity.Account, accountLoginSecurity *config.Acc
 | 
			
		||||
	biz.IsTrue(account.IsEnable(), "该账号不可用")
 | 
			
		||||
	username := account.Username
 | 
			
		||||
 | 
			
		||||
	res := map[string]any{
 | 
			
		||||
	res := collx.M{
 | 
			
		||||
		"name":          account.Name,
 | 
			
		||||
		"username":      username,
 | 
			
		||||
		"lastLoginTime": account.LastLoginTime,
 | 
			
		||||
 
 | 
			
		||||
@@ -13,8 +13,8 @@ import (
 | 
			
		||||
	"mayfly-go/pkg/captcha"
 | 
			
		||||
	"mayfly-go/pkg/ginx"
 | 
			
		||||
	"mayfly-go/pkg/req"
 | 
			
		||||
	"mayfly-go/pkg/utils/collx"
 | 
			
		||||
	"mayfly-go/pkg/utils/cryptox"
 | 
			
		||||
	"mayfly-go/pkg/utils/jsonx"
 | 
			
		||||
	"strconv"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"time"
 | 
			
		||||
@@ -49,7 +49,7 @@ func (a *LdapLogin) Login(rc *req.Ctx) {
 | 
			
		||||
	username := loginForm.Username
 | 
			
		||||
 | 
			
		||||
	clientIp := getIpAndRegion(rc)
 | 
			
		||||
	rc.ReqParam = jsonx.Kvs("username", username, "ip", clientIp)
 | 
			
		||||
	rc.ReqParam = collx.Kvs("username", username, "ip", clientIp)
 | 
			
		||||
 | 
			
		||||
	originPwd, err := cryptox.DefaultRsaDecrypt(loginForm.Password, true)
 | 
			
		||||
	biz.ErrIsNilAppendErr(err, "解密密码错误: %s")
 | 
			
		||||
 
 | 
			
		||||
@@ -14,6 +14,7 @@ import (
 | 
			
		||||
	"mayfly-go/pkg/cache"
 | 
			
		||||
	"mayfly-go/pkg/model"
 | 
			
		||||
	"mayfly-go/pkg/req"
 | 
			
		||||
	"mayfly-go/pkg/utils/collx"
 | 
			
		||||
	"mayfly-go/pkg/utils/jsonx"
 | 
			
		||||
	"mayfly-go/pkg/utils/stringx"
 | 
			
		||||
	"net/http"
 | 
			
		||||
@@ -98,7 +99,7 @@ func (a *Oauth2Login) OAuth2Callback(rc *req.Ctx) {
 | 
			
		||||
		account.Id = accountId
 | 
			
		||||
		err = a.AccountApp.GetAccount(account, "username")
 | 
			
		||||
		biz.ErrIsNilAppendErr(err, "该账号不存在")
 | 
			
		||||
		rc.ReqParam = jsonx.Kvs("username", account.Username, "type", "bind")
 | 
			
		||||
		rc.ReqParam = collx.Kvs("username", account.Username, "type", "bind")
 | 
			
		||||
 | 
			
		||||
		err = a.Oauth2App.GetOAuthAccount(&entity.Oauth2Account{
 | 
			
		||||
			AccountId: accountId,
 | 
			
		||||
@@ -118,7 +119,7 @@ func (a *Oauth2Login) OAuth2Callback(rc *req.Ctx) {
 | 
			
		||||
			UpdateTime: &now,
 | 
			
		||||
		})
 | 
			
		||||
		biz.ErrIsNilAppendErr(err, "绑定用户失败: %s")
 | 
			
		||||
		res := map[string]any{
 | 
			
		||||
		res := collx.M{
 | 
			
		||||
			"action": "oauthBind",
 | 
			
		||||
			"bind":   true,
 | 
			
		||||
		}
 | 
			
		||||
@@ -173,7 +174,7 @@ func (a *Oauth2Login) doLoginAction(rc *req.Ctx, userId string, oauth *config.Oa
 | 
			
		||||
	biz.ErrIsNilAppendErr(err, "获取用户信息失败: %s")
 | 
			
		||||
 | 
			
		||||
	clientIp := getIpAndRegion(rc)
 | 
			
		||||
	rc.ReqParam = jsonx.Kvs("username", account.Username, "ip", clientIp, "type", "login")
 | 
			
		||||
	rc.ReqParam = collx.Kvs("username", account.Username, "ip", clientIp, "type", "login")
 | 
			
		||||
 | 
			
		||||
	res := LastLoginCheck(account, config.GetAccountLoginSecurity(), clientIp)
 | 
			
		||||
	res["action"] = "oauthLogin"
 | 
			
		||||
@@ -220,7 +221,7 @@ func (a *Oauth2Login) Oauth2Unbind(rc *req.Ctx) {
 | 
			
		||||
// 获取oauth2登录配置信息,因为有些字段是敏感字段,故单独使用接口获取
 | 
			
		||||
func (c *Oauth2Login) Oauth2Config(rc *req.Ctx) {
 | 
			
		||||
	oauth2LoginConfig := config.GetOauth2Login()
 | 
			
		||||
	rc.ResData = map[string]any{
 | 
			
		||||
	rc.ResData = collx.M{
 | 
			
		||||
		"enable": oauth2LoginConfig.Enable,
 | 
			
		||||
		"name":   oauth2LoginConfig.Name,
 | 
			
		||||
	}
 | 
			
		||||
 
 | 
			
		||||
@@ -11,6 +11,7 @@ import (
 | 
			
		||||
	redisentity "mayfly-go/internal/redis/domain/entity"
 | 
			
		||||
	tagapp "mayfly-go/internal/tag/application"
 | 
			
		||||
	"mayfly-go/pkg/req"
 | 
			
		||||
	"mayfly-go/pkg/utils/collx"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type Index struct {
 | 
			
		||||
@@ -36,7 +37,7 @@ func (i *Index) Count(rc *req.Ctx) {
 | 
			
		||||
		dbNum = i.DbApp.Count(&dbentity.DbQuery{TagIds: tagIds})
 | 
			
		||||
		redisNum = i.RedisApp.Count(&redisentity.RedisQuery{TagIds: tagIds})
 | 
			
		||||
	}
 | 
			
		||||
	rc.ResData = map[string]any{
 | 
			
		||||
	rc.ResData = collx.M{
 | 
			
		||||
		"mongoNum":   mongoNum,
 | 
			
		||||
		"machineNum": machienNum,
 | 
			
		||||
		"dbNum":      dbNum,
 | 
			
		||||
 
 | 
			
		||||
@@ -3,6 +3,8 @@ package consts
 | 
			
		||||
import "time"
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
	AdminId = 1
 | 
			
		||||
 | 
			
		||||
	MachineConnExpireTime = 60 * time.Minute
 | 
			
		||||
	DbConnExpireTime      = 45 * time.Minute
 | 
			
		||||
	RedisConnExpireTime   = 30 * time.Minute
 | 
			
		||||
 
 | 
			
		||||
@@ -3,25 +3,31 @@ package api
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"io"
 | 
			
		||||
	"mayfly-go/pkg/utils/collx"
 | 
			
		||||
	"mayfly-go/pkg/utils/uniqueid"
 | 
			
		||||
	"mayfly-go/pkg/ws"
 | 
			
		||||
 | 
			
		||||
	"github.com/lib/pq"
 | 
			
		||||
 | 
			
		||||
	"mayfly-go/internal/db/api/form"
 | 
			
		||||
	"mayfly-go/internal/db/api/vo"
 | 
			
		||||
	"mayfly-go/internal/db/application"
 | 
			
		||||
	"mayfly-go/internal/db/domain/entity"
 | 
			
		||||
	msgapp "mayfly-go/internal/msg/application"
 | 
			
		||||
	msgdto "mayfly-go/internal/msg/application/dto"
 | 
			
		||||
	tagapp "mayfly-go/internal/tag/application"
 | 
			
		||||
	"mayfly-go/pkg/biz"
 | 
			
		||||
	"mayfly-go/pkg/ginx"
 | 
			
		||||
	"mayfly-go/pkg/gormx"
 | 
			
		||||
	"mayfly-go/pkg/model"
 | 
			
		||||
	"mayfly-go/pkg/req"
 | 
			
		||||
	"mayfly-go/pkg/sqlparser"
 | 
			
		||||
	"mayfly-go/pkg/utils/stringx"
 | 
			
		||||
	"mayfly-go/pkg/ws"
 | 
			
		||||
	"strconv"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/gin-gonic/gin"
 | 
			
		||||
	"github.com/xwb1989/sqlparser"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type Db struct {
 | 
			
		||||
@@ -77,9 +83,7 @@ func (d *Db) DeleteDb(rc *req.Ctx) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (d *Db) getDbConnection(g *gin.Context) *application.DbConnection {
 | 
			
		||||
	dbName := g.Query("db")
 | 
			
		||||
	biz.NotEmpty(dbName, "db不能为空")
 | 
			
		||||
	return d.DbApp.GetDbConnection(getDbId(g), dbName)
 | 
			
		||||
	return d.DbApp.GetDbConnection(getDbId(g), getDbName(g))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (d *Db) TableInfos(rc *req.Ctx) {
 | 
			
		||||
@@ -150,77 +154,117 @@ func (d *Db) ExecSql(rc *req.Ctx) {
 | 
			
		||||
	rc.ResData = colAndRes
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// progressCategory sql文件执行进度消息类型
 | 
			
		||||
const progressCategory = "execSqlFileProgress"
 | 
			
		||||
 | 
			
		||||
// progressMsg sql文件执行进度消息
 | 
			
		||||
type progressMsg struct {
 | 
			
		||||
	Id                 uint64 `json:"id"`
 | 
			
		||||
	SqlFileName        string `json:"sqlFileName"`
 | 
			
		||||
	ExecutedStatements int    `json:"executedStatements"`
 | 
			
		||||
	Terminated         bool   `json:"terminated"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 执行sql文件
 | 
			
		||||
func (d *Db) ExecSqlFile(rc *req.Ctx) {
 | 
			
		||||
	g := rc.GinCtx
 | 
			
		||||
	fileheader, err := g.FormFile("file")
 | 
			
		||||
	multipart, err := g.Request.MultipartReader()
 | 
			
		||||
	biz.ErrIsNilAppendErr(err, "读取sql文件失败: %s")
 | 
			
		||||
 | 
			
		||||
	file, _ := fileheader.Open()
 | 
			
		||||
	filename := fileheader.Filename
 | 
			
		||||
	file, err := multipart.NextPart()
 | 
			
		||||
	biz.ErrIsNilAppendErr(err, "读取sql文件失败: %s")
 | 
			
		||||
	defer file.Close()
 | 
			
		||||
	filename := file.FileName()
 | 
			
		||||
	dbId := getDbId(g)
 | 
			
		||||
	dbName := getDbName(g)
 | 
			
		||||
 | 
			
		||||
	dbConn := d.getDbConnection(rc.GinCtx)
 | 
			
		||||
	dbConn := d.DbApp.GetDbConnection(dbId, dbName)
 | 
			
		||||
	biz.ErrIsNilAppendErr(d.TagApp.CanAccess(rc.LoginAccount.Id, dbConn.Info.TagPath), "%s")
 | 
			
		||||
	rc.ReqParam = fmt.Sprintf("%s -> filename: %s", dbConn.Info.GetLogDesc(), filename)
 | 
			
		||||
 | 
			
		||||
	logExecRecord := true
 | 
			
		||||
	// 如果执行sql文件大于该值则不记录sql执行记录
 | 
			
		||||
	if fileheader.Size > 50*1024 {
 | 
			
		||||
		logExecRecord = false
 | 
			
		||||
	defer func() {
 | 
			
		||||
		var errInfo string
 | 
			
		||||
		switch t := recover().(type) {
 | 
			
		||||
		case error:
 | 
			
		||||
			errInfo = t.Error()
 | 
			
		||||
		case string:
 | 
			
		||||
			errInfo = t
 | 
			
		||||
		}
 | 
			
		||||
		if len(errInfo) > 0 {
 | 
			
		||||
			d.MsgApp.CreateAndSend(rc.LoginAccount, msgdto.ErrSysMsg("sql脚本执行失败", fmt.Sprintf("[%s]%s执行失败: [%s]", filename, dbConn.Info.GetLogDesc(), errInfo)))
 | 
			
		||||
		}
 | 
			
		||||
	}()
 | 
			
		||||
 | 
			
		||||
	execReq := &application.DbSqlExecReq{
 | 
			
		||||
		DbId:         dbId,
 | 
			
		||||
		Db:           dbName,
 | 
			
		||||
		Remark:       filename,
 | 
			
		||||
		DbConn:       dbConn,
 | 
			
		||||
		LoginAccount: rc.LoginAccount,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	go func() {
 | 
			
		||||
		defer func() {
 | 
			
		||||
			if err := recover(); err != nil {
 | 
			
		||||
				var errInfo string
 | 
			
		||||
				switch t := err.(type) {
 | 
			
		||||
				case biz.BizError:
 | 
			
		||||
					errInfo = t.Error()
 | 
			
		||||
				case *biz.BizError:
 | 
			
		||||
					errInfo = t.Error()
 | 
			
		||||
				}
 | 
			
		||||
				if len(errInfo) > 0 {
 | 
			
		||||
					d.MsgApp.CreateAndSend(rc.LoginAccount, ws.ErrSysMsg("sql脚本执行失败", fmt.Sprintf("[%s]%s执行失败: [%s]", filename, dbConn.Info.GetLogDesc(), errInfo)))
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}()
 | 
			
		||||
	progressId := uniqueid.IncrementID()
 | 
			
		||||
	executedStatements := 0
 | 
			
		||||
	defer ws.SendJsonMsg(rc.LoginAccount.Id, msgdto.InfoSysMsg("sql脚本执行进度", &progressMsg{
 | 
			
		||||
		Id:                 progressId,
 | 
			
		||||
		SqlFileName:        filename,
 | 
			
		||||
		ExecutedStatements: executedStatements,
 | 
			
		||||
		Terminated:         true,
 | 
			
		||||
	}).WithCategory(progressCategory))
 | 
			
		||||
 | 
			
		||||
		execReq := &application.DbSqlExecReq{
 | 
			
		||||
			DbId:         dbId,
 | 
			
		||||
			Db:           dbName,
 | 
			
		||||
			Remark:       fileheader.Filename,
 | 
			
		||||
			DbConn:       dbConn,
 | 
			
		||||
			LoginAccount: rc.LoginAccount,
 | 
			
		||||
	var parser sqlparser.Parser
 | 
			
		||||
	if dbConn.Info.Type == entity.DbTypeMysql {
 | 
			
		||||
		parser = sqlparser.NewMysqlParser(file)
 | 
			
		||||
	} else {
 | 
			
		||||
		parser = sqlparser.NewPostgresParser(file)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ticker := time.NewTicker(time.Second * 1)
 | 
			
		||||
	defer ticker.Stop()
 | 
			
		||||
	for {
 | 
			
		||||
		select {
 | 
			
		||||
		case <-ticker.C:
 | 
			
		||||
			ws.SendJsonMsg(rc.LoginAccount.Id, msgdto.InfoSysMsg("sql脚本执行进度", &progressMsg{
 | 
			
		||||
				Id:                 progressId,
 | 
			
		||||
				SqlFileName:        filename,
 | 
			
		||||
				ExecutedStatements: executedStatements,
 | 
			
		||||
				Terminated:         false,
 | 
			
		||||
			}).WithCategory(progressCategory))
 | 
			
		||||
		default:
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		tokens := sqlparser.NewTokenizer(file)
 | 
			
		||||
		for {
 | 
			
		||||
			stmt, err := sqlparser.ParseNext(tokens)
 | 
			
		||||
			if err == io.EOF {
 | 
			
		||||
				break
 | 
			
		||||
		err = parser.Next()
 | 
			
		||||
		if err == io.EOF {
 | 
			
		||||
			break
 | 
			
		||||
		}
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			d.MsgApp.CreateAndSend(rc.LoginAccount, msgdto.ErrSysMsg("sql脚本执行失败", fmt.Sprintf("[%s][%s] 解析SQL失败: [%s]", filename, dbConn.Info.GetLogDesc(), err.Error())))
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
		sql := parser.Current()
 | 
			
		||||
		const prefixUse = "use "
 | 
			
		||||
		if strings.HasPrefix(sql, prefixUse) {
 | 
			
		||||
			dbNameExec := strings.Trim(sql[len(prefixUse):], " `;\n")
 | 
			
		||||
			if len(dbNameExec) > 0 {
 | 
			
		||||
				dbConn = d.DbApp.GetDbConnection(dbId, dbNameExec)
 | 
			
		||||
				biz.ErrIsNilAppendErr(d.TagApp.CanAccess(rc.LoginAccount.Id, dbConn.Info.TagPath), "%s")
 | 
			
		||||
				execReq.DbConn = dbConn
 | 
			
		||||
			}
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				d.MsgApp.CreateAndSend(rc.LoginAccount, ws.ErrSysMsg("sql脚本执行失败", fmt.Sprintf("[%s][%s] 解析SQL失败: [%s]", filename, dbConn.Info.GetLogDesc(), err.Error())))
 | 
			
		||||
				return
 | 
			
		||||
			}
 | 
			
		||||
			sql := sqlparser.String(stmt)
 | 
			
		||||
		}
 | 
			
		||||
		// 需要记录执行记录
 | 
			
		||||
		const maxRecordStatements = 64
 | 
			
		||||
		if executedStatements < maxRecordStatements {
 | 
			
		||||
			execReq.Sql = sql
 | 
			
		||||
			// 需要记录执行记录
 | 
			
		||||
			if logExecRecord {
 | 
			
		||||
				_, err = d.DbSqlExecApp.Exec(execReq)
 | 
			
		||||
			} else {
 | 
			
		||||
				_, err = dbConn.Exec(sql)
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				d.MsgApp.CreateAndSend(rc.LoginAccount, ws.ErrSysMsg("sql脚本执行失败", fmt.Sprintf("[%s][%s] -> sql=[%s] 执行失败: [%s]", filename, dbConn.Info.GetLogDesc(), sql, err.Error())))
 | 
			
		||||
				return
 | 
			
		||||
			}
 | 
			
		||||
			_, err = d.DbSqlExecApp.Exec(execReq)
 | 
			
		||||
		} else {
 | 
			
		||||
			_, err = dbConn.Exec(sql)
 | 
			
		||||
		}
 | 
			
		||||
		d.MsgApp.CreateAndSend(rc.LoginAccount, ws.SuccessSysMsg("sql脚本执行成功", fmt.Sprintf("[%s]执行完成 -> %s", filename, dbConn.Info.GetLogDesc())))
 | 
			
		||||
	}()
 | 
			
		||||
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			d.MsgApp.CreateAndSend(rc.LoginAccount, msgdto.ErrSysMsg("sql脚本执行失败", fmt.Sprintf("[%s][%s] -> sql=[%s] 执行失败: [%s]", filename, dbConn.Info.GetLogDesc(), sql, err.Error())))
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
		executedStatements++
 | 
			
		||||
	}
 | 
			
		||||
	d.MsgApp.CreateAndSend(rc.LoginAccount, msgdto.SuccessSysMsg("sql脚本执行成功", fmt.Sprintf("[%s]执行完成 -> %s", filename, dbConn.Info.GetLogDesc())))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 数据库dump
 | 
			
		||||
@@ -266,16 +310,14 @@ func (d *Db) DumpSql(rc *req.Ctx) {
 | 
			
		||||
		var msg string
 | 
			
		||||
		if err := recover(); err != nil {
 | 
			
		||||
			switch t := err.(type) {
 | 
			
		||||
			case biz.BizError:
 | 
			
		||||
				msg = t.Error()
 | 
			
		||||
			case *biz.BizError:
 | 
			
		||||
			case error:
 | 
			
		||||
				msg = t.Error()
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		if len(msg) > 0 {
 | 
			
		||||
			msg = "数据库导出失败: " + msg
 | 
			
		||||
			writer.WriteString(msg)
 | 
			
		||||
			d.MsgApp.CreateAndSend(rc.LoginAccount, ws.ErrSysMsg("数据库导出失败", msg))
 | 
			
		||||
			d.MsgApp.CreateAndSend(rc.LoginAccount, msgdto.ErrSysMsg("数据库导出失败", msg))
 | 
			
		||||
		}
 | 
			
		||||
		writer.Close()
 | 
			
		||||
	}()
 | 
			
		||||
@@ -283,26 +325,47 @@ func (d *Db) DumpSql(rc *req.Ctx) {
 | 
			
		||||
		d.dumpDb(writer, dbId, dbName, tables, needStruct, needData, len(dbNames) > 1)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	rc.ReqParam = fmt.Sprintf("DB[id=%d, tag=%s, name=%s, databases=%s, tables=%s, dumpType=%s]", db.Id, db.TagPath, db.Name, dbNamesStr, tablesStr, dumpType)
 | 
			
		||||
	rc.ReqParam = collx.Kvs("db", db, "databases", dbNamesStr, "tables", tablesStr, "dumpType", dumpType)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func escapeSql(dbType string, sql string) string {
 | 
			
		||||
	if dbType == entity.DbTypePostgres {
 | 
			
		||||
		return pq.QuoteLiteral(sql)
 | 
			
		||||
	} else {
 | 
			
		||||
		sql = strings.ReplaceAll(sql, `\`, `\\`)
 | 
			
		||||
		sql = strings.ReplaceAll(sql, `'`, `''`)
 | 
			
		||||
		return "'" + sql + "'"
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func quoteTable(dbType string, table string) string {
 | 
			
		||||
	if dbType == entity.DbTypePostgres {
 | 
			
		||||
		return "\"" + table + "\""
 | 
			
		||||
	} else {
 | 
			
		||||
		return "`" + table + "`"
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (d *Db) dumpDb(writer *gzipWriter, dbId uint64, dbName string, tables []string, needStruct bool, needData bool, switchDb bool) {
 | 
			
		||||
	dbConn := d.DbApp.GetDbConnection(dbId, dbName)
 | 
			
		||||
	writer.WriteString("-- ----------------------------")
 | 
			
		||||
	writer.WriteString("\n-- ----------------------------")
 | 
			
		||||
	writer.WriteString("\n-- 导出平台: mayfly-go")
 | 
			
		||||
	writer.WriteString(fmt.Sprintf("\n-- 导出时间: %s ", time.Now().Format("2006-01-02 15:04:05")))
 | 
			
		||||
	writer.WriteString(fmt.Sprintf("\n-- 导出数据库: %s ", dbName))
 | 
			
		||||
	writer.WriteString("\n-- ----------------------------\n")
 | 
			
		||||
	writer.TryFlush()
 | 
			
		||||
 | 
			
		||||
	if switchDb {
 | 
			
		||||
		switch dbConn.Info.Type {
 | 
			
		||||
		case entity.DbTypeMysql:
 | 
			
		||||
			writer.WriteString(fmt.Sprintf("use `%s`;\n", dbName))
 | 
			
		||||
			writer.WriteString(fmt.Sprintf("USE `%s`;\n", dbName))
 | 
			
		||||
		default:
 | 
			
		||||
			biz.IsTrue(false, "数据库类型必须为 %s", entity.DbTypeMysql)
 | 
			
		||||
			biz.IsTrue(false, "同时导出多个数据库,数据库类型必须为 %s", entity.DbTypeMysql)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	if dbConn.Info.Type == entity.DbTypeMysql {
 | 
			
		||||
		writer.WriteString("\nSET FOREIGN_KEY_CHECKS = 0;\n")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	dbMeta := dbConn.GetMeta()
 | 
			
		||||
	if len(tables) == 0 {
 | 
			
		||||
		ti := dbMeta.GetTableInfos()
 | 
			
		||||
@@ -313,23 +376,22 @@ func (d *Db) dumpDb(writer *gzipWriter, dbId uint64, dbName string, tables []str
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, table := range tables {
 | 
			
		||||
		writer.TryFlush()
 | 
			
		||||
		quotedTable := quoteTable(dbConn.Info.Type, table)
 | 
			
		||||
		if needStruct {
 | 
			
		||||
			writer.WriteString(fmt.Sprintf("\n-- ----------------------------\n-- 表结构: %s \n-- ----------------------------\n", table))
 | 
			
		||||
			writer.WriteString(fmt.Sprintf("DROP TABLE IF EXISTS `%s`;\n", table))
 | 
			
		||||
			writer.WriteString(fmt.Sprintf("DROP TABLE IF EXISTS %s;\n", quotedTable))
 | 
			
		||||
			writer.WriteString(dbMeta.GetCreateTableDdl(table) + ";\n")
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if !needData {
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		writer.WriteString(fmt.Sprintf("\n-- ----------------------------\n-- 表记录: %s \n-- ----------------------------\n", table))
 | 
			
		||||
		writer.WriteString("BEGIN;\n")
 | 
			
		||||
 | 
			
		||||
		insertSql := "INSERT INTO `%s` VALUES (%s);\n"
 | 
			
		||||
 | 
			
		||||
		insertSql := "INSERT INTO %s VALUES (%s);\n"
 | 
			
		||||
		dbMeta.WalkTableRecord(table, func(record map[string]any, columns []string) {
 | 
			
		||||
			var values []string
 | 
			
		||||
			writer.TryFlush()
 | 
			
		||||
			for _, column := range columns {
 | 
			
		||||
				value := record[column]
 | 
			
		||||
				if value == nil {
 | 
			
		||||
@@ -338,17 +400,18 @@ func (d *Db) dumpDb(writer *gzipWriter, dbId uint64, dbName string, tables []str
 | 
			
		||||
				}
 | 
			
		||||
				strValue, ok := value.(string)
 | 
			
		||||
				if ok {
 | 
			
		||||
					values = append(values, fmt.Sprintf("%#v", strValue))
 | 
			
		||||
					strValue = escapeSql(dbConn.Info.Type, strValue)
 | 
			
		||||
					values = append(values, strValue)
 | 
			
		||||
				} else {
 | 
			
		||||
					values = append(values, stringx.AnyToStr(value))
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
			writer.WriteString(fmt.Sprintf(insertSql, table, strings.Join(values, ", ")))
 | 
			
		||||
			writer.TryFlush()
 | 
			
		||||
			writer.WriteString(fmt.Sprintf(insertSql, quotedTable, strings.Join(values, ", ")))
 | 
			
		||||
		})
 | 
			
		||||
 | 
			
		||||
		writer.WriteString("COMMIT;\n")
 | 
			
		||||
		writer.TryFlush()
 | 
			
		||||
	}
 | 
			
		||||
	if dbConn.Info.Type == entity.DbTypeMysql {
 | 
			
		||||
		writer.WriteString("\nSET FOREIGN_KEY_CHECKS = 1;\n")
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||