浏览代码

Merge branch 'professional2' of http://git.mydig.net/Sagoo-Cloud/sagoo-admin-ui into professional2

vera_min 5 月之前
父节点
当前提交
3b34f4dc0e

+ 158 - 55
package-lock.json

@@ -11,6 +11,7 @@
       "dependencies": {
         "@antv/g2plot": "2.4.20",
         "@element-plus/icons-vue": "2.0.9",
+        "@guolao/vue-monaco-editor": "^1.5.5",
         "axios": "0.26.0",
         "clipboard": "2.0.11",
         "codemirror": "5.65.16",
@@ -24,11 +25,13 @@
         "echarts-wordcloud": "2.0.0",
         "element-plus": "2.2.28",
         "event-source-polyfill": "1.0.31",
+        "html2canvas": "^1.4.1",
         "js-cookie": "3.0.5",
         "jsplumb": "2.15.6",
         "jsrsasign": "10.8.6",
         "loadsh": "0.0.4",
         "mitt": "3.0.0",
+        "monaco-editor": "^0.52.2",
         "nprogress": "0.2.0",
         "pako": "1.0.11",
         "print-js": "1.6.0",
@@ -44,6 +47,7 @@
         "vform3-builds": "3.0.8",
         "vue": "3.2.37",
         "vue-clipboard3": "1.0.1",
+        "vue-data-ui": "^2.4.17",
         "vue-grid-layout": "3.0.0-beta1",
         "vue-i18n": "9.1.10",
         "vue-router": "4.0.13",
@@ -63,7 +67,8 @@
         "@typescript-eslint/parser": "5.13.0",
         "@vitejs/plugin-vue": "3.1.0",
         "@vue/compiler-sfc": "3.2.45",
-        "dotenv": "16.0.0",
+        "cross-env": "^7.0.3",
+        "dotenv": "^16.4.5",
         "eslint": "8.10.0",
         "eslint-plugin-vue": "8.5.0",
         "prettier": "2.5.1",
@@ -454,6 +459,26 @@
       "resolved": "https://registry.npmmirror.com/@floating-ui/utils/-/utils-0.2.7.tgz",
       "integrity": "sha512-X8R8Oj771YRl/w+c1HqAC1szL8zWQRwFvgDwT129k9ACdBoud/+/rX9V0qiMl6LWUdP9voC2nDVZYPMQQsb6eA=="
     },
+    "node_modules/@guolao/vue-monaco-editor": {
+      "version": "1.5.5",
+      "resolved": "https://registry.npmjs.org/@guolao/vue-monaco-editor/-/vue-monaco-editor-1.5.5.tgz",
+      "integrity": "sha512-NFGImQ8dBYj6ehIxy1DngPRkctB9b6GbxvCm6aXZztNsgm/TtM4u+YM9ZwZHQPlXt7a4IODXoKCcTYEVycBSyA==",
+      "license": "MIT",
+      "dependencies": {
+        "@monaco-editor/loader": "^1.5.0",
+        "vue-demi": "latest"
+      },
+      "peerDependencies": {
+        "@vue/composition-api": "^1.7.1",
+        "monaco-editor": ">=0.43.0",
+        "vue": "^2.6.14 || >=3.0.0"
+      },
+      "peerDependenciesMeta": {
+        "@vue/composition-api": {
+          "optional": true
+        }
+      }
+    },
     "node_modules/@humanwhocodes/config-array": {
       "version": "0.9.5",
       "resolved": "https://registry.npmmirror.com/@humanwhocodes/config-array/-/config-array-0.9.5.tgz",
@@ -750,6 +775,15 @@
         "node": ">= 0.4"
       }
     },
+    "node_modules/@monaco-editor/loader": {
+      "version": "1.5.0",
+      "resolved": "https://registry.npmjs.org/@monaco-editor/loader/-/loader-1.5.0.tgz",
+      "integrity": "sha512-hKoGSM+7aAc7eRTRjpqAZucPmoNOC4UUbknb/VNoTkEIkCPhqV8LfbsgM1webRM7S/z21eHEx9Fkwx8Z/C/+Xw==",
+      "license": "MIT",
+      "dependencies": {
+        "state-local": "^1.0.6"
+      }
+    },
     "node_modules/@nodelib/fs.scandir": {
       "version": "2.1.5",
       "resolved": "https://registry.npmmirror.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
@@ -1272,31 +1306,6 @@
         "url": "https://github.com/sponsors/antfu"
       }
     },
-    "node_modules/@vueuse/core/node_modules/vue-demi": {
-      "version": "0.14.10",
-      "resolved": "https://registry.npmmirror.com/vue-demi/-/vue-demi-0.14.10.tgz",
-      "integrity": "sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==",
-      "hasInstallScript": true,
-      "bin": {
-        "vue-demi-fix": "bin/vue-demi-fix.js",
-        "vue-demi-switch": "bin/vue-demi-switch.js"
-      },
-      "engines": {
-        "node": ">=12"
-      },
-      "funding": {
-        "url": "https://github.com/sponsors/antfu"
-      },
-      "peerDependencies": {
-        "@vue/composition-api": "^1.0.0-rc.1",
-        "vue": "^3.0.0-0 || ^2.6.0"
-      },
-      "peerDependenciesMeta": {
-        "@vue/composition-api": {
-          "optional": true
-        }
-      }
-    },
     "node_modules/@vueuse/metadata": {
       "version": "9.13.0",
       "resolved": "https://registry.npmmirror.com/@vueuse/metadata/-/metadata-9.13.0.tgz",
@@ -1316,31 +1325,6 @@
         "url": "https://github.com/sponsors/antfu"
       }
     },
-    "node_modules/@vueuse/shared/node_modules/vue-demi": {
-      "version": "0.14.10",
-      "resolved": "https://registry.npmmirror.com/vue-demi/-/vue-demi-0.14.10.tgz",
-      "integrity": "sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==",
-      "hasInstallScript": true,
-      "bin": {
-        "vue-demi-fix": "bin/vue-demi-fix.js",
-        "vue-demi-switch": "bin/vue-demi-switch.js"
-      },
-      "engines": {
-        "node": ">=12"
-      },
-      "funding": {
-        "url": "https://github.com/sponsors/antfu"
-      },
-      "peerDependencies": {
-        "@vue/composition-api": "^1.0.0-rc.1",
-        "vue": "^3.0.0-0 || ^2.6.0"
-      },
-      "peerDependenciesMeta": {
-        "@vue/composition-api": {
-          "optional": true
-        }
-      }
-    },
     "node_modules/@wdns/vue-code-block": {
       "version": "2.3.3",
       "resolved": "https://registry.npmmirror.com/@wdns/vue-code-block/-/vue-code-block-2.3.3.tgz",
@@ -1673,6 +1657,15 @@
       "resolved": "https://registry.npmmirror.com/balanced-match/-/balanced-match-1.0.2.tgz",
       "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="
     },
+    "node_modules/base64-arraybuffer": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-1.0.2.tgz",
+      "integrity": "sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.6.0"
+      }
+    },
     "node_modules/batch-processor": {
       "version": "1.0.0",
       "resolved": "https://registry.npmmirror.com/batch-processor/-/batch-processor-1.0.0.tgz",
@@ -1967,6 +1960,25 @@
       "resolved": "https://registry.npmmirror.com/cropperjs/-/cropperjs-1.5.12.tgz",
       "integrity": "sha512-re7UdjE5UnwdrovyhNzZ6gathI4Rs3KGCBSc8HCIjUo5hO42CtzyblmWLj6QWVw7huHyDMfpKxhiO2II77nhDw=="
     },
+    "node_modules/cross-env": {
+      "version": "7.0.3",
+      "resolved": "https://registry.npmjs.org/cross-env/-/cross-env-7.0.3.tgz",
+      "integrity": "sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "cross-spawn": "^7.0.1"
+      },
+      "bin": {
+        "cross-env": "src/bin/cross-env.js",
+        "cross-env-shell": "src/bin/cross-env-shell.js"
+      },
+      "engines": {
+        "node": ">=10.14",
+        "npm": ">=6",
+        "yarn": ">=1"
+      }
+    },
     "node_modules/cross-spawn": {
       "version": "7.0.3",
       "resolved": "https://registry.npmmirror.com/cross-spawn/-/cross-spawn-7.0.3.tgz",
@@ -1981,6 +1993,15 @@
         "node": ">= 8"
       }
     },
+    "node_modules/css-line-break": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/css-line-break/-/css-line-break-2.1.0.tgz",
+      "integrity": "sha512-FHcKFCZcAha3LwfVBhCQbW2nCNbkZXn7KVUJcsT5/P8YmfsVja0FMPJr0B903j/E69HUphKiV9iQArX8SDYA4w==",
+      "license": "MIT",
+      "dependencies": {
+        "utrie": "^1.0.2"
+      }
+    },
     "node_modules/css-tree": {
       "version": "2.3.1",
       "resolved": "https://registry.npmmirror.com/css-tree/-/css-tree-2.3.1.tgz",
@@ -2220,12 +2241,16 @@
       }
     },
     "node_modules/dotenv": {
-      "version": "16.0.0",
-      "resolved": "https://registry.npmmirror.com/dotenv/-/dotenv-16.0.0.tgz",
-      "integrity": "sha512-qD9WU0MPM4SWLPJy/r2Be+2WgQj8plChsyrCNQzW/0WjvcJQiKQJ9mH3ZgB3fxbUUxgc/11ZJ0Fi5KiimWGz2Q==",
+      "version": "16.5.0",
+      "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.5.0.tgz",
+      "integrity": "sha512-m/C+AwOAr9/W1UOIZUo232ejMNnJAJtYQjUbHoNTBNTJSvqzzDh7vnrei3o3r3m9blf6ZoDkvcw0VmozNRFJxg==",
       "dev": true,
+      "license": "BSD-2-Clause",
       "engines": {
         "node": ">=12"
+      },
+      "funding": {
+        "url": "https://dotenvx.com"
       }
     },
     "node_modules/dotignore": {
@@ -3547,6 +3572,19 @@
         "node": ">=12.0.0"
       }
     },
+    "node_modules/html2canvas": {
+      "version": "1.4.1",
+      "resolved": "https://registry.npmjs.org/html2canvas/-/html2canvas-1.4.1.tgz",
+      "integrity": "sha512-fPU6BHNpsyIhr8yyMpTLLxAbkaK8ArIBcmZIRiBLiDhjeqvXolaEmDGmELFuX9I4xDcaKKcJl+TKZLqruBbmWA==",
+      "license": "MIT",
+      "dependencies": {
+        "css-line-break": "^2.1.0",
+        "text-segmentation": "^1.0.3"
+      },
+      "engines": {
+        "node": ">=8.0.0"
+      }
+    },
     "node_modules/ignore": {
       "version": "5.3.2",
       "resolved": "https://registry.npmmirror.com/ignore/-/ignore-5.3.2.tgz",
@@ -4205,6 +4243,12 @@
         "url": "https://github.com/sponsors/ljharb"
       }
     },
+    "node_modules/monaco-editor": {
+      "version": "0.52.2",
+      "resolved": "https://registry.npmjs.org/monaco-editor/-/monaco-editor-0.52.2.tgz",
+      "integrity": "sha512-GEQWEZmfkOGLdd3XK8ryrfWz3AIP8YymVXiPHEdewrUq7mh0qrKrfHLNCXcbB6sTnMLnOZ3ztSiKcciFUkIJwQ==",
+      "license": "MIT"
+    },
     "node_modules/ms": {
       "version": "2.1.3",
       "resolved": "https://registry.npmmirror.com/ms/-/ms-2.1.3.tgz",
@@ -5013,6 +5057,12 @@
         "node": ">=0.8"
       }
     },
+    "node_modules/state-local": {
+      "version": "1.0.7",
+      "resolved": "https://registry.npmjs.org/state-local/-/state-local-1.0.7.tgz",
+      "integrity": "sha512-HTEHMNieakEnoe33shBYcZ7NX83ACUjCu8c40iOGEZsngj9zRnkqS9j1pqQPXwobB0ZcVTk27REb7COQ0UR59w==",
+      "license": "MIT"
+    },
     "node_modules/string.prototype.trim": {
       "version": "1.2.9",
       "resolved": "https://registry.npmmirror.com/string.prototype.trim/-/string.prototype.trim-1.2.9.tgz",
@@ -5135,6 +5185,15 @@
         "url": "https://github.com/sponsors/ljharb"
       }
     },
+    "node_modules/text-segmentation": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/text-segmentation/-/text-segmentation-1.0.3.tgz",
+      "integrity": "sha512-iOiPUo/BGnZ6+54OsWxZidGCsdU8YbE4PSpdPinp7DeMtUJNJBoJ/ouUSTJjHkh1KntHaltHl/gDs2FC4i5+Nw==",
+      "license": "MIT",
+      "dependencies": {
+        "utrie": "^1.0.2"
+      }
+    },
     "node_modules/text-table": {
       "version": "0.2.0",
       "resolved": "https://registry.npmmirror.com/text-table/-/text-table-0.2.0.tgz",
@@ -5288,7 +5347,7 @@
       "version": "4.6.2",
       "resolved": "https://registry.npmmirror.com/typescript/-/typescript-4.6.2.tgz",
       "integrity": "sha512-HM/hFigTBHZhLXshn9sN37H085+hQGeJHJ/X7LpBWLID/fbc2acUMfU+lGD98X81sKP+pFa9f0DZmCwB9GnbAg==",
-      "devOptional": true,
+      "dev": true,
       "bin": {
         "tsc": "bin/tsc",
         "tsserver": "bin/tsserver"
@@ -5388,6 +5447,15 @@
         "punycode": "^2.1.0"
       }
     },
+    "node_modules/utrie": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/utrie/-/utrie-1.0.2.tgz",
+      "integrity": "sha512-1MLa5ouZiOmQzUbjbu9VmjLzn1QLXBhwpUa7kdLUQK+KQ5KA9I1vk5U4YHe/X2Ch7PYnJfWuWT+VbuxbGwljhw==",
+      "license": "MIT",
+      "dependencies": {
+        "base64-arraybuffer": "^1.0.2"
+      }
+    },
     "node_modules/uuid": {
       "version": "9.0.0",
       "resolved": "https://registry.npmmirror.com/uuid/-/uuid-9.0.0.tgz",
@@ -5505,6 +5573,41 @@
         "clipboard": "^2.0.6"
       }
     },
+    "node_modules/vue-data-ui": {
+      "version": "2.6.46",
+      "resolved": "https://registry.npmjs.org/vue-data-ui/-/vue-data-ui-2.6.46.tgz",
+      "integrity": "sha512-wg6wCNRxogYBRYSmVsaEni2DVBMjATmye3zHVAX4EGuntreLsSRmFBA5Iq1g0tnVqPhv/E/wN8Y9UD0YJ+oUPA==",
+      "license": "MIT",
+      "peerDependencies": {
+        "vue": ">=3.3.0"
+      }
+    },
+    "node_modules/vue-demi": {
+      "version": "0.14.10",
+      "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.10.tgz",
+      "integrity": "sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==",
+      "hasInstallScript": true,
+      "license": "MIT",
+      "bin": {
+        "vue-demi-fix": "bin/vue-demi-fix.js",
+        "vue-demi-switch": "bin/vue-demi-switch.js"
+      },
+      "engines": {
+        "node": ">=12"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/antfu"
+      },
+      "peerDependencies": {
+        "@vue/composition-api": "^1.0.0-rc.1",
+        "vue": "^3.0.0-0 || ^2.6.0"
+      },
+      "peerDependenciesMeta": {
+        "@vue/composition-api": {
+          "optional": true
+        }
+      }
+    },
     "node_modules/vue-eslint-parser": {
       "version": "8.3.0",
       "resolved": "https://registry.npmmirror.com/vue-eslint-parser/-/vue-eslint-parser-8.3.0.tgz",

+ 2 - 0
package.json

@@ -25,6 +25,7 @@
   "dependencies": {
     "@antv/g2plot": "2.4.20",
     "@element-plus/icons-vue": "2.0.9",
+    "@guolao/vue-monaco-editor": "^1.5.5",
     "axios": "0.26.0",
     "clipboard": "2.0.11",
     "codemirror": "5.65.16",
@@ -44,6 +45,7 @@
     "jsrsasign": "10.8.6",
     "loadsh": "0.0.4",
     "mitt": "3.0.0",
+    "monaco-editor": "^0.52.2",
     "nprogress": "0.2.0",
     "pako": "1.0.11",
     "print-js": "1.6.0",

+ 40 - 0
pnpm-lock.yaml

@@ -14,6 +14,9 @@ importers:
       '@element-plus/icons-vue':
         specifier: 2.0.9
         version: 2.0.9(vue@3.2.37)
+      '@guolao/vue-monaco-editor':
+        specifier: ^1.5.5
+        version: 1.5.5(monaco-editor@0.52.2)(vue@3.2.37)
       axios:
         specifier: 0.26.0
         version: 0.26.0
@@ -71,6 +74,9 @@ importers:
       mitt:
         specifier: 3.0.0
         version: 3.0.0
+      monaco-editor:
+        specifier: ^0.52.2
+        version: 0.52.2
       nprogress:
         specifier: 0.2.0
         version: 0.2.0
@@ -318,6 +324,16 @@ packages:
   '@floating-ui/utils@0.2.7':
     resolution: {integrity: sha512-X8R8Oj771YRl/w+c1HqAC1szL8zWQRwFvgDwT129k9ACdBoud/+/rX9V0qiMl6LWUdP9voC2nDVZYPMQQsb6eA==}
 
+  '@guolao/vue-monaco-editor@1.5.5':
+    resolution: {integrity: sha512-NFGImQ8dBYj6ehIxy1DngPRkctB9b6GbxvCm6aXZztNsgm/TtM4u+YM9ZwZHQPlXt7a4IODXoKCcTYEVycBSyA==}
+    peerDependencies:
+      '@vue/composition-api': ^1.7.1
+      monaco-editor: '>=0.43.0'
+      vue: ^2.6.14 || >=3.0.0
+    peerDependenciesMeta:
+      '@vue/composition-api':
+        optional: true
+
   '@humanwhocodes/config-array@0.9.5':
     resolution: {integrity: sha512-ObyMyWxZiCu/yTisA7uzx81s40xR2fD5Cg/2Kq7G02ajkNubJf6BopgDTmDyc3U7sXpNKM8cYOw7s7Tyr+DnCw==}
     engines: {node: '>=10.10.0'}
@@ -439,6 +455,9 @@ packages:
     resolution: {integrity: sha512-/gKJun8NNiWGZJkGzI/Ragc53cOdcLNdzjLaIa+GEjguQs0ulsurx8WN0jijdK9yPqDvziX995sMRLyLt1uZMQ==}
     engines: {node: '>= 0.4'}
 
+  '@monaco-editor/loader@1.5.0':
+    resolution: {integrity: sha512-hKoGSM+7aAc7eRTRjpqAZucPmoNOC4UUbknb/VNoTkEIkCPhqV8LfbsgM1webRM7S/z21eHEx9Fkwx8Z/C/+Xw==}
+
   '@nodelib/fs.scandir@2.1.5':
     resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==}
     engines: {node: '>= 8'}
@@ -1631,6 +1650,9 @@ packages:
     resolution: {integrity: sha512-2emPTb1reeLLYwHxyVx993iYyCHEiRRO+y8NFXFPL5kl5q14sgTK76cXyEKkeKCHeRw35SfdkUJ10Q1KfHuiIQ==}
     engines: {node: '>= 0.4'}
 
+  monaco-editor@0.52.2:
+    resolution: {integrity: sha512-GEQWEZmfkOGLdd3XK8ryrfWz3AIP8YymVXiPHEdewrUq7mh0qrKrfHLNCXcbB6sTnMLnOZ3ztSiKcciFUkIJwQ==}
+
   ms@2.1.3:
     resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
 
@@ -1922,6 +1944,9 @@ packages:
     resolution: {integrity: sha512-+idbmIXoYET47hH+d7dfm2epdOMUDjqcB4648sTZ+t2JwoyBFL/insLfB/racrDmsKB3diwsDA696pZMieAC5g==}
     engines: {node: '>=0.8'}
 
+  state-local@1.0.7:
+    resolution: {integrity: sha512-HTEHMNieakEnoe33shBYcZ7NX83ACUjCu8c40iOGEZsngj9zRnkqS9j1pqQPXwobB0ZcVTk27REb7COQ0UR59w==}
+
   string.prototype.trim@1.2.9:
     resolution: {integrity: sha512-klHuCNxiMZ8MlsOihJhJEBJAiMVqU3Z2nEXWfWnIqjN0gEFS9J9+IxKozWWtQGcgoa1WUZzLjKPTr4ZHNFTFxw==}
     engines: {node: '>= 0.4'}
@@ -2412,6 +2437,13 @@ snapshots:
 
   '@floating-ui/utils@0.2.7': {}
 
+  '@guolao/vue-monaco-editor@1.5.5(monaco-editor@0.52.2)(vue@3.2.37)':
+    dependencies:
+      '@monaco-editor/loader': 1.5.0
+      monaco-editor: 0.52.2
+      vue: 3.2.37
+      vue-demi: 0.14.10(vue@3.2.37)
+
   '@humanwhocodes/config-array@0.9.5':
     dependencies:
       '@humanwhocodes/object-schema': 1.2.1
@@ -2565,6 +2597,10 @@ snapshots:
     dependencies:
       call-bind: 1.0.7
 
+  '@monaco-editor/loader@1.5.0':
+    dependencies:
+      state-local: 1.0.7
+
   '@nodelib/fs.scandir@2.1.5':
     dependencies:
       '@nodelib/fs.stat': 2.0.5
@@ -3903,6 +3939,8 @@ snapshots:
       hasown: 2.0.2
       isarray: 2.0.5
 
+  monaco-editor@0.52.2: {}
+
   ms@2.1.3: {}
 
   nanoid@3.3.7: {}
@@ -4139,6 +4177,8 @@ snapshots:
     dependencies:
       frac: 1.1.2
 
+  state-local@1.0.7: {}
+
   string.prototype.trim@1.2.9:
     dependencies:
       call-bind: 1.0.7

+ 27 - 0
src/api/modules/apiHub.ts

@@ -0,0 +1,27 @@
+import { get, post, del, put } from '/@/utils/request';
+
+export default {
+  list: (params: object) => get('/api/list', params),
+  add: (data: object) => post('/api/add', data),
+  edit: (data: object) => put('/api/edit', data),
+  delete: (ids: number[]) => del('/api/delete', { ids }),
+  get: (id: number) => get(`/api/get`, { id }),
+  publish: (id: number) => put(`/api/publish`, { id }),
+  deprecate: (id: number) => put(`/api/deprecate`, { id }),
+  test: (data: object) => post(`/api/test`, data),
+  group: {
+    tree: () => get('/api_group/tree'),
+    get: (id: number) => get(`/api_group/get`, { id }),
+    delete: (ids: number[]) => del('/api_group/delete', { ids }),
+    add: (data: object) => post('/api_group/add', data),
+    edit: (data: object) => put('/api_group/edit', data),
+  },
+  dataSource: {
+    list: (params: object) => get('/datasource/list', params),
+    get: (id: number) => get(`/datasource/get`, { id }),
+    add: (data: object) => post('/datasource/add', data),
+    edit: (data: object) => put('/datasource/edit', data),
+    delete: (ids: number[]) => del('/datasource/delete', { ids }),
+    test: (data: object) => post('/datasource/test', data),
+  }
+}

+ 513 - 581
src/views/apihub/apilist.vue

@@ -1,699 +1,631 @@
 <template>
-	<div class="page">
-		<div class="apihub-container">
-			<!-- 左侧分组树 -->
-			<div class="apihub-sidebar">
-				<el-card shadow="never" class="group-card">
-					<template #header>
-						<div class="card-header">
-							<span>API分组</span>
-							<div class="header-actions">
-								<el-button type="primary" size="small" @click="addGroup" v-auth="'add_group'">
-									<el-icon><ele-Plus /></el-icon>
-								</el-button>
-								<el-button type="primary" size="small" @click="refreshGroups">
-									<el-icon><ele-Refresh /></el-icon>
-								</el-button>
-							</div>
-						</div>
-					</template>
-					<div class="group-search">
-						<el-input
-							v-model="groupSearchKey"
-							placeholder="搜索分组"
-							clearable
-							prefix-icon="ele-Search"
-							@input="filterGroups"
-						/>
-					</div>
-					<div class="group-tree-container">
-						<el-tree
-							ref="groupTreeRef"
-							:data="groupTreeData"
-							:props="{ label: 'Name', children: 'Children' }"
-							node-key="Id"
-							highlight-current
-							:expand-on-click-node="true"
-							default-expand-all
-							@node-click="handleGroupClick"
-						>
-							<template #default="{ node, data }">
-								<div class="custom-tree-node">
-									<span>{{ node.label }}</span>
-									<span class="api-count" v-if="data.ApiCount">{{ data.ApiCount }}</span>
-									<div class="node-actions">
-										<el-dropdown @command="(command) => handleGroupCommand(command, data)" trigger="click">
-											<el-icon><ele-More /></el-icon>
-											<template #dropdown>
-												<el-dropdown-menu>
-													<el-dropdown-item command="edit" v-auth="'edit_group'">编辑</el-dropdown-item>
-													<el-dropdown-item command="add_child" v-auth="'add_group'">添加子分组</el-dropdown-item>
-													<el-dropdown-item command="delete" v-auth="'delete_group'">删除</el-dropdown-item>
-												</el-dropdown-menu>
-											</template>
-										</el-dropdown>
-									</div>
-								</div>
-							</template>
-						</el-tree>
-					</div>
-				</el-card>
-			</div>
-
-			<!-- 右侧API列表 -->
-			<div class="apihub-content">
-				<el-card shadow="never">
-					<div class="api-header">
-						<div class="current-group" v-if="currentGroup.name">
-							当前分组: <span class="group-name">{{ currentGroup.name }}</span>
-						</div>
-						<div class="current-group" v-else>
-							全部API
-						</div>
-					</div>
-					<el-form :model="params" inline ref="queryRef">
-				<el-form-item label="API名称" prop="keyWord">
-					<el-input v-model="params.keyWord" placeholder="请输入API名称" clearable style="width: 180px" @keyup.enter.native="getList(1)" />
-				</el-form-item>
-				<el-form-item label="数据源" prop="dataSourceId">
-					<el-select v-model="params.dataSourceId" placeholder="请选择数据源" clearable style="width: 180px">
-						<el-option v-for="item in dataSources" :key="item.id" :label="item.name" :value="item.id" />
-					</el-select>
-				</el-form-item>
-				<el-form-item label="状态" prop="status">
-					<el-select v-model="params.status" placeholder="请选择状态" clearable style="width: 120px">
-						<el-option label="全部" value="" />
-						<el-option label="草稿" value="Draft" />
-						<el-option label="已发布" value="Published" />
-						<el-option label="已废弃" value="Deprecated" />
-					</el-select>
-				</el-form-item>
-				<el-form-item label="日期范围" prop="dateRange">
-					<el-date-picker
-						v-model="params.dateRange"
-						type="daterange"
-						range-separator="至"
-						start-placeholder="开始日期"
-						end-placeholder="结束日期"
-						value-format="YYYY-MM-DD"
-						style="width: 240px"
-					/>
-				</el-form-item>
-				<el-form-item>
-					<el-button type="primary" class="ml10" @click="getList(1)">
-						<el-icon>
-							<ele-Search />
-						</el-icon>
-						查询
-					</el-button>
-					<el-button @click="resetQuery()">
-						<el-icon>
-							<ele-Refresh />
-						</el-icon>
-						重置
-					</el-button>
-					<el-button type="primary" @click="addOrEdit()" v-auth="'add'">
-						<el-icon>
-							<ele-FolderAdd />
-						</el-icon>
-						新增API
-					</el-button>
-				</el-form-item>
-			</el-form>
-			<el-table :data="tableData" style="width: 100%" v-loading="loading" row-key="id">
-				<el-table-column type="selection" width="55" align="center" />
-				<el-table-column prop="id" label="ID" width="80" align="center" />
-				<el-table-column prop="name" label="API名称" min-width="120" show-overflow-tooltip></el-table-column>
-				<el-table-column prop="path" label="API路径" min-width="150" show-overflow-tooltip></el-table-column>
-				<el-table-column prop="method" label="请求方法" width="100" align="center">
-					<template #default="scope">
-						<el-tag
-							:type="getMethodTagType(scope.row.method)"
-							size="small"
-						>
-							{{ scope.row.method }}
-						</el-tag>
-					</template>
-				</el-table-column>
-				<el-table-column prop="dataSourceName" label="数据源" width="120" show-overflow-tooltip></el-table-column>
-				<el-table-column prop="sqlType" label="SQL类型" width="100" align="center">
-					<template #default="scope">
-						<el-tag size="small" type="info" v-if="scope.row.sqlType === 'query'">查询</el-tag>
-						<el-tag size="small" type="warning" v-else-if="scope.row.sqlType === 'procedure'">存储过程</el-tag>
-						<span v-else>{{ scope.row.sqlType }}</span>
-					</template>
-				</el-table-column>
-				<el-table-column prop="version" label="版本" width="80" align="center"></el-table-column>
-				<el-table-column prop="status" label="状态" width="100" align="center">
-					<template #default="scope">
-						<el-tag size="small" v-if="scope.row.status === 'Draft'">草稿</el-tag>
-						<el-tag size="small" type="success" v-else-if="scope.row.status === 'Published'">已发布</el-tag>
-						<el-tag size="small" type="info" v-else-if="scope.row.status === 'Deprecated'">已废弃</el-tag>
-						<span v-else>{{ scope.row.status }}</span>
-					</template>
-				</el-table-column>
-				<el-table-column prop="createdAt" label="创建时间" width="160" align="center"></el-table-column>
-				<el-table-column label="操作" width="220" align="center">
-					<template #default="scope">
-						<el-button size="small" text type="primary" @click="viewDetail(scope.row)" v-auth="'view'">查看</el-button>
-						<el-button size="small" text type="warning" @click="addOrEdit(scope.row)" v-auth="'edit'">编辑</el-button>
-						<el-button size="small" text type="success" @click="testApi(scope.row)" v-auth="'test'">测试</el-button>
-						<el-dropdown @command="(command) => handleCommand(command, scope.row)">
-							<el-button size="small" text type="primary">
-								更多<el-icon class="el-icon--right"><arrow-down /></el-icon>
-							</el-button>
-							<template #dropdown>
-								<el-dropdown-menu>
-									<el-dropdown-item command="publish" v-if="scope.row.status === 'Draft'" v-auth="'publish'">发布</el-dropdown-item>
-									<el-dropdown-item command="deprecate" v-if="scope.row.status === 'Published'" v-auth="'deprecate'">废弃</el-dropdown-item>
-									<el-dropdown-item command="delete" v-auth="'delete'">删除</el-dropdown-item>
-								</el-dropdown-menu>
-							</template>
-						</el-dropdown>
-					</template>
-				</el-table-column>
-			</el-table>
-					<pagination v-if="params.total" :total="params.total" v-model:page="params.pageNum" v-model:limit="params.pageSize" @pagination="getList()" />
-				</el-card>
-			</div>
-		</div>
-
-		<!-- 组件 -->
-		<EditForm ref="editFormRef" @getList="getList(1)"></EditForm>
-		<ViewDetail ref="viewDetailRef"></ViewDetail>
-		<TestApi ref="testApiRef"></TestApi>
-		<GroupForm ref="groupFormRef" @refresh="refreshGroups"></GroupForm>
-	</div>
+  <div class="page">
+    <div class="apihub-container">
+      <!-- 左侧分组树 -->
+      <div class="apihub-sidebar">
+        <el-card shadow="never" class="group-card">
+          <template #header>
+            <div class="card-header">
+              <span>API分组</span>
+              <div class="header-actions">
+                <el-button type="primary" size="small" @click="addGroup" v-auth="'add_group'">
+                  <el-icon><ele-Plus /></el-icon>
+                </el-button>
+                <el-button type="primary" size="small" @click="refreshGroups">
+                  <el-icon><ele-Refresh /></el-icon>
+                </el-button>
+              </div>
+            </div>
+          </template>
+          <div class="group-search">
+            <el-input v-model="groupSearchKey" placeholder="搜索分组" clearable prefix-icon="ele-Search" @input="filterGroups" />
+          </div>
+          <div class="group-tree-container">
+            <el-tree ref="groupTreeRef" :data="groupTreeData" :props="{ label: 'Name', children: 'Children' }" node-key="Id" highlight-current :expand-on-click-node="true" default-expand-all @node-click="handleGroupClick">
+              <template #default="{ node, data }">
+                <div class="custom-tree-node">
+                  <span>{{ node.label }}</span>
+                  <span class="api-count" v-if="data.ApiCount">{{ data.ApiCount }}</span>
+                  <div class="node-actions">
+                    <el-dropdown @command="(command: string) => handleGroupCommand(command, data)" trigger="click">
+                      <el-icon><ele-More /></el-icon>
+                      <template #dropdown>
+                        <el-dropdown-menu>
+                          <el-dropdown-item command="edit" v-auth="'edit_group'">编辑</el-dropdown-item>
+                          <el-dropdown-item command="add_child" v-auth="'add_group'">添加子分组</el-dropdown-item>
+                          <el-dropdown-item command="delete" v-auth="'delete_group'">删除</el-dropdown-item>
+                        </el-dropdown-menu>
+                      </template>
+                    </el-dropdown>
+                  </div>
+                </div>
+              </template>
+            </el-tree>
+          </div>
+        </el-card>
+      </div>
+
+      <!-- 右侧API列表 -->
+      <el-card shadow="never">
+        <div class="api-header">
+          <div class="current-group" v-if="currentGroup.Name">
+            当前分组: <span class="group-name">{{ currentGroup.Name }}</span>
+            <el-link type="primary" class="view-all-link" @click="viewAllData">查看全部API</el-link>
+          </div>
+          <div class="current-group" v-else>全部API</div>
+        </div>
+        <el-form :model="params" inline ref="queryRef">
+          <el-form-item label="API名称" prop="keyWord">
+            <el-input v-model="params.keyWord" placeholder="请输入API名称" clearable style="width: 150px" @keyup.enter.native="getList(1)" />
+          </el-form-item>
+          <el-form-item label="数据源" prop="dataSourceKey">
+            <el-select v-model="params.dataSourceKey" placeholder="请选择数据源" clearable style="width: 180px">
+              <el-option v-for="item in dataSources" :key="item.dsKey" :label="item.name" :value="item.dsKey" />
+            </el-select>
+          </el-form-item>
+          <el-form-item label="状态" prop="status">
+            <el-select v-model="params.status" placeholder="请选择状态" clearable style="width: 80px">
+              <el-option label="全部" value="" />
+              <el-option label="草稿" value="Draft" />
+              <el-option label="已发布" value="Published" />
+              <el-option label="已废弃" value="Deprecated" />
+            </el-select>
+          </el-form-item>
+<!--          <el-form-item label="日期范围" prop="dateRange">-->
+<!--            <el-date-picker v-model="params.dateRange" type="daterange" range-separator="至" start-placeholder="开始日期" end-placeholder="结束日期" value-format="YYYY-MM-DD" style="width: 240px" />-->
+<!--          </el-form-item>-->
+          <el-form-item>
+            <el-button type="primary" class="ml10" @click="getList(1)">
+              <el-icon>
+                <ele-Search />
+              </el-icon>
+              查询
+            </el-button>
+<!--            <el-button @click="resetQuery()">-->
+<!--              <el-icon>-->
+<!--                <ele-Refresh />-->
+<!--              </el-icon>-->
+<!--              重置-->
+<!--            </el-button>-->
+            <el-button type="primary" @click="addOrEdit()" v-auth="'add'">
+              <el-icon>
+                <ele-FolderAdd />
+              </el-icon>
+              新增API
+            </el-button>
+          </el-form-item>
+        </el-form>
+        <el-table :data="tableData" style="width: 100%" v-loading="loading" row-key="id">
+          <el-table-column type="selection" width="40" align="center" />
+<!--          <el-table-column prop="id" label="ID" width="80" align="center" />-->
+          <el-table-column prop="method" label="请求" width="85" align="center">
+            <template #default="scope">
+              <el-tag :type="getMethodTagType(scope.row.method)" size="small">
+                {{ scope.row.method }}
+              </el-tag>
+            </template>
+          </el-table-column>
+          <el-table-column prop="name" label="API名称" min-width="140" show-overflow-tooltip></el-table-column>
+          <el-table-column prop="path" label="API路径" min-width="150" show-overflow-tooltip></el-table-column>
+<!--          <el-table-column prop="dataSourceName" label="数据源" width="120" show-overflow-tooltip></el-table-column>-->
+<!--          <el-table-column prop="sqlType" label="SQL类型" width="100" align="center">-->
+<!--            <template #default="scope">-->
+<!--              <el-tag size="small" type="info" v-if="scope.row.sqlType === 'query'">查询</el-tag>-->
+<!--              <el-tag size="small" type="warning" v-else-if="scope.row.sqlType === 'procedure'">存储过程</el-tag>-->
+<!--              <span v-else>{{ scope.row.sqlType }}</span>-->
+<!--            </template>-->
+<!--          </el-table-column>-->
+          <el-table-column prop="version" label="版本" width="80" align="center"></el-table-column>
+          <el-table-column prop="description" label="描述" width="140" />
+
+          <el-table-column prop="status" label="状态" width="100" align="center">
+            <template #default="scope">
+              <el-tag size="small" v-if="scope.row.status === 'Draft'">草稿</el-tag>
+              <el-tag size="small" type="success" v-else-if="scope.row.status === 'Published'">已发布</el-tag>
+              <el-tag size="small" type="info" v-else-if="scope.row.status === 'Deprecated'">已废弃</el-tag>
+              <span v-else>{{ scope.row.status }}</span>
+            </template>
+          </el-table-column>
+<!--          <el-table-column prop="createdAt" label="创建时间" width="160" align="center"></el-table-column>-->
+          <el-table-column label="操作" width="190" align="center" fixed="right">
+            <template #default="scope">
+              <div class="flex-row">
+                <el-button size="small" text type="primary" @click="viewDetail(scope.row)" v-auth="'view'">查看</el-button>
+                <el-button size="small" text type="warning" @click="addOrEdit(scope.row)" v-auth="'edit'">编辑</el-button>
+                <el-button size="small" text type="success" @click="testApi(scope.row)" v-auth="'test'">测试</el-button>
+                <el-dropdown @command="(command: string) => handleCommand(command, scope.row)">
+                  <el-button size="small" text type="primary" style="margin-left: 12px; vertical-align: top">
+                    更多<el-icon class="el-icon--right" style="font-size: 12px !important"><arrow-down /></el-icon>
+                  </el-button>
+                  <template #dropdown>
+                    <el-dropdown-menu>
+                      <el-dropdown-item command="publish" v-if="scope.row.status === 'Draft'" v-auth="'publish'">发布</el-dropdown-item>
+                      <el-dropdown-item command="deprecate" v-if="scope.row.status === 'Published'" v-auth="'deprecate'">废弃</el-dropdown-item>
+                      <el-dropdown-item command="delete" v-auth="'delete'">删除</el-dropdown-item>
+                    </el-dropdown-menu>
+                  </template>
+                </el-dropdown>
+              </div>
+            </template>
+          </el-table-column>
+        </el-table>
+        <pagination v-if="params.total" :total="params.total" v-model:page="params.pageNum" v-model:limit="params.pageSize" @pagination="getList()" />
+      </el-card>
+    </div>
+
+    <!-- 组件 -->
+    <EditForm ref="editFormRef" @getList="getList(1)"></EditForm>
+    <ViewDetail ref="viewDetailRef"></ViewDetail>
+    <TestApi ref="testApiRef"></TestApi>
+    <GroupForm ref="groupFormRef" @refresh="refreshGroups"></GroupForm>
+  </div>
 </template>
 
 <script lang="ts" setup>
-import { ref, onMounted, reactive } from 'vue'
-import EditForm from './component/edit.vue'
-import ViewDetail from './component/view.vue'
-import TestApi from './component/test.vue'
-import GroupForm from './component/group.vue'
-import { useSearch } from '/@/hooks/useCommon'
-import { ElMessageBox, ElMessage } from 'element-plus'
-import { ArrowDown } from '@element-plus/icons-vue'
-import request from '/@/utils/request'
+import { ref, onMounted, reactive } from "vue";
+import EditForm from "./component/edit.vue";
+import ViewDetail from "./component/view.vue";
+import TestApi from "./component/test.vue";
+import GroupForm from "./component/group.vue";
+import { useSearch } from "/@/hooks/useCommon";
+import { ElMessageBox, ElMessage } from "element-plus";
+import { ArrowDown } from "@element-plus/icons-vue";
+import api from "/@/api/modules/apiHub";
 
 // 定义API接口类型
 interface ApiDefinition {
-	id?: number
-	name: string
-	path: string
-	method: string
-	dataSourceId: number
-	dataSourceName?: string
-	sqlType: string
-	sqlContent: string
-	parameters?: any[]
-	returnFormat: string
-	version: string
-	status: string
-	plugins?: any[]
-	description?: string
-	createdAt?: string
-	updatedAt?: string
+  id?: number;
+  name: string;
+  path: string;
+  method: string;
+  dataSourceKey: string;
+  dataSourceName?: string;
+  sqlType: string;
+  sqlContent: string;
+  parameters?: any[];
+  returnFormat: string;
+  version: string;
+  status: string;
+  plugins?: any[];
+  description?: string;
+  createdAt?: string;
+  updatedAt?: string;
 }
 
 // 引用组件
-const editFormRef = ref()
-const viewDetailRef = ref()
-const testApiRef = ref()
-const queryRef = ref()
-const groupFormRef = ref()
-const groupTreeRef = ref()
+const editFormRef = ref();
+const viewDetailRef = ref();
+const testApiRef = ref();
+const queryRef = ref();
+const groupFormRef = ref();
+const groupTreeRef = ref();
 
 // 分组相关状态
-const groupSearchKey = ref('')
-const groupTreeData = ref([])
-const currentGroup = reactive({
-	Id: undefined,
-	GroupKey: '',
-	Name: '',
-	ParentId: 0,
-	Description: ''
-})
-const originalGroupTree = ref([])
+const groupSearchKey = ref("");
+const groupTreeData = ref<any[]>([]);
+const currentGroup = reactive<any>({
+  Id: undefined,
+  GroupKey: "",
+  Name: "",
+  ParentId: 0,
+  Description: "",
+});
+const originalGroupTree = ref<any[]>([]);
 
 // 数据源列表
-const dataSources = ref([])
-
-// API列表请求函数
-const apiRequest = (params: any) => {
-	// 这里使用request函数发起请求
-	// 实际使用时替换为真实API路径
-	return request({
-		url: '/api/list',
-		method: 'get',
-		params
-	})
-}
-
-// 获取分组树形结构
-const getGroupTree = () => {
-	return request({
-		url: '/api_group/tree',
-		method: 'get'
-	})
-}
-
-// 删除分组
-const deleteGroup = (ids: number[]) => {
-	return request({
-		url: '/api_group/delete',
-		method: 'post',
-		data: { ids }
-	})
-}
+const dataSources = ref<any[]>([]);
 
 // 使用通用搜索钩子
-const { params, tableData, getList, loading } = useSearch<ApiDefinition[]>(
-	apiRequest,
-	'data',
-	{
-		keyWord: '',
-		dataSourceId: '',
-		status: '',
-		dateRange: [],
-		orderBy: '',
-		pageNum: 1,
-		pageSize: 10
-	}
-)
+const { params, tableData, getList, loading } = useSearch<ApiDefinition[]>(api.list, "Data", {
+  keyWord: "",
+  dataSourceKey: "",
+  status: "",
+  dateRange: [],
+  orderBy: "",
+});
 
 // 加载数据源列表
 const loadDataSources = () => {
-	// 实际使用时替换为真实API调用
-	// 模拟数据
-	dataSources.value = [
-		{ id: 1, name: '主数据库' },
-		{ id: 2, name: '业务数据库' },
-		{ id: 3, name: '日志数据库' }
-	]
-}
+  api.dataSource.list().then((res: any) => {
+    dataSources.value = res.list;
+  });
+};
 
 // 页面加载时获取列表数据
 onMounted(() => {
-	// 获取API列表
-	getList(1)
-	
-	// 加载数据源列表
-	loadDataSources()
-	
-	// 加载分组树
-	refreshGroups()
-})
+  // 获取API列表
+  getList(1);
+
+  // 加载数据源列表
+  loadDataSources();
+
+  // 加载分组树
+  refreshGroups();
+});
 
 // 将扁平数组转换为树形结构
-const convertToTree = (flatData) => {
-	// 创建一个映射表,用于快速查找节点
-	const map = {}
-	const result = []
-	
-	// 首先创建所有节点的映射
-	flatData.forEach(item => {
-		// 确保每个节点都有Children属性
-		map[item.Id] = { ...item, Children: [] }
-	})
-	
-	// 然后建立父子关系
-	flatData.forEach(item => {
-		const node = map[item.Id]
-		
-		if (item.ParentId === 0 || !map[item.ParentId]) {
-			// 如果ParentId为0或者父节点不存在,则为顶级节点
-			result.push(node)
-		} else {
-			// 否则将该节点添加到父节点的Children中
-			map[item.ParentId].Children.push(node)
-		}
-	})
-	
-	// 清理空的Children数组
-	flatData.forEach(item => {
-		if (map[item.Id].Children.length === 0) {
-			map[item.Id].Children = null
-		}
-	})
-	
-	return result
-}
+const convertToTree = (flatData: any[]) => {
+  // 创建一个映射表,用于快速查找节点
+  const map: any = {};
+  const result: any[] = [];
+
+  // 首先创建所有节点的映射
+  flatData.forEach((item) => {
+    // 确保每个节点都有Children属性
+    map[item.Id] = { ...item, Children: [] };
+  });
+
+  // 然后建立父子关系
+  flatData.forEach((item) => {
+    const node = map[item.Id];
+
+    if (item.ParentId === 0 || !map[item.ParentId]) {
+      // 如果ParentId为0或者父节点不存在,则为顶级节点
+      result.push(node);
+    } else {
+      // 否则将该节点添加到父节点的Children中
+      map[item.ParentId].Children.push(node);
+    }
+  });
+
+  // 清理空的Children数组
+  flatData.forEach((item) => {
+    if (map[item.Id].Children.length === 0) {
+      map[item.Id].Children = null;
+    }
+  });
+
+  return result;
+};
 
 // 检查数据是否已经是树形结构
-const isTreeStructure = (data) => {
-	// 检查数据中是否有包含非空的Children字段的项
-	return data.some(item => item.Children && Array.isArray(item.Children) && item.Children.length > 0)
-}
+const isTreeStructure = (data: any[]) => {
+  // 检查数据中是否有包含非空的Children字段的项
+  return data.some((item) => item.Children && Array.isArray(item.Children) && item.Children.length > 0);
+};
 
 // 刷新分组树
 const refreshGroups = async () => {
-	try {
-		// 调用API获取分组树
-		const res = await getGroupTree()
-		console.log('获取到的API分组数据:', res)
-
-		// 使用API返回的数据
-		if (res.data && res.data.list) {
-			// 获取原始数据
-			const apiData = res.data.list || []
-			console.log('原始数据:', apiData)
-			
-			// 检查数据是否已经是树形结构
-			const hasTreeStructure = isTreeStructure(apiData)
-			console.log('是否已经是树形结构:', hasTreeStructure)
-			
-			let treeData
-			if (hasTreeStructure) {
-				// 如果已经是树形结构,直接使用
-				treeData = apiData
-				console.log('使用原始树形结构')
-			} else {
-				// 如果是扁平结构,通过ParentId构建树形结构
-				treeData = convertToTree(apiData)
-				console.log('通过ParentId构建的树形结构:', treeData)
-			}
-			
-			// 手动设置测试数据,确认组件是否正常工作
-			const testData = [
-				{
-					"Id": 10,
-					"GroupKey": "group_1746503398664_968",
-					"Name": "巡检管理",
-					"ParentId": 0,
-					"Sort": 0,
-					"Description": "",
-					"Children": [
-						{
-							"Id": 11,
-							"GroupKey": "group_1746515959076_509",
-							"Name": "能耗分析",
-							"ParentId": 10,
-							"Sort": 0,
-							"Description": "",
-							"Children": null,
-							"ApiCount": 0
-						}
-					],
-					"ApiCount": 0
-				}
-			]
-			console.log('测试数据:', testData)
-			
-			// 设置到组件中
-			groupTreeData.value = testData
-			originalGroupTree.value = JSON.parse(JSON.stringify(testData))
-			console.log('设置后的分组数据:', groupTreeData.value)
-			return
-		}
-
-		// 如果没有数据,初始化为空数组
-		groupTreeData.value = []
-		originalGroupTree.value = []
-	} catch (error) {
-		ElMessage.error('获取分组数据失败')
-	}
-}
+  try {
+    // 调用API获取分组树
+    const res: any = await api.group.tree();
+
+    // 使用API返回的数据
+    if (res?.list) {
+      // 获取原始数据
+      const apiData = res.list || [];
+
+      // 检查数据是否已经是树形结构
+      const hasTreeStructure = isTreeStructure(apiData);
+
+      let treeData: any[];
+      if (hasTreeStructure) {
+        // 如果已经是树形结构,直接使用
+        treeData = apiData;
+      } else {
+        // 如果是扁平结构,通过ParentId构建树形结构
+        treeData = convertToTree(apiData);
+      }
+
+      // 设置到组件中
+      groupTreeData.value = treeData;
+      originalGroupTree.value = JSON.parse(JSON.stringify(treeData));
+      return;
+    }
+
+    // 如果没有数据,初始化为空数组
+    groupTreeData.value = [];
+    originalGroupTree.value = [];
+  } catch (error) {
+    ElMessage.error("获取分组数据失败");
+  }
+};
 
 // 搜索过滤分组
 const filterGroups = () => {
-	if (!groupSearchKey.value) {
-		// 如果搜索关键字为空,恢复原始数据
-		groupTreeData.value = JSON.parse(JSON.stringify(originalGroupTree.value))
-		return
-	}
-
-	// 递归搜索函数
-	const searchTree = (nodes) => {
-		return nodes.filter(node => {
-			// 当前节点名称匹配
-			const matchesName = node.Name.toLowerCase().includes(groupSearchKey.value.toLowerCase())
-
-			// 递归搜索子节点
-			if (node.Children && node.Children.length) {
-				node.Children = searchTree(node.Children)
-				// 如果子节点有匹配项,则保留父节点
-				return matchesName || node.Children.length > 0
-			}
-
-			return matchesName
-		})
-	}
-
-	groupTreeData.value = searchTree(JSON.parse(JSON.stringify(originalGroupTree.value)))
-}
+  if (!groupSearchKey.value) {
+    // 如果搜索关键字为空,恢复原始数据
+    groupTreeData.value = JSON.parse(JSON.stringify(originalGroupTree.value));
+    return;
+  }
+
+  // 递归搜索函数
+  const searchTree = (nodes: any[]) => {
+    return nodes.filter((node) => {
+      // 当前节点名称匹配
+      const matchesName = node.Name.toLowerCase().includes(groupSearchKey.value.toLowerCase());
+
+      // 递归搜索子节点
+      if (node.Children && node.Children.length) {
+        node.Children = searchTree(node.Children);
+        // 如果子节点有匹配项,则保留父节点
+        return matchesName || node.Children.length > 0;
+      }
+
+      return matchesName;
+    });
+  };
+
+  groupTreeData.value = searchTree(JSON.parse(JSON.stringify(originalGroupTree.value)));
+};
 
 // 点击分组节点
-const handleGroupClick = (data) => {
-	// 设置当前选中分组
-	Object.assign(currentGroup, data)
+const handleGroupClick = (data: any) => {
+  console.log(data);
+  // 设置当前选中分组
+  Object.assign(currentGroup, data);
 
-	// 更新查询参数,加入分组条件
-	params.groupKey = data.GroupKey
+  // 更新查询参数,加入分组条件
+  params.groupKey = data.GroupKey;
 
-	// 重新获取列表
-	getList(1)
-}
+  // 重新获取列表
+  getList(1);
+};
 
 // 添加分组
 const addGroup = () => {
-	groupFormRef.value.open()
-}
+  groupFormRef.value.open();
+};
 
 // 处理分组操作
-const handleGroupCommand = (command, data) => {
-	switch (command) {
-		case 'edit':
-			groupFormRef.value.open(data)
-			break
-		case 'add_child':
-			groupFormRef.value.open(null, data.GroupKey)
-			break
-		case 'delete':
-			deleteGroupConfirm(data)
-			break
-	}
-}
+const handleGroupCommand = (command: string, data: any) => {
+  switch (command) {
+    case "edit":
+      groupFormRef.value.open(data);
+      break;
+    case "add_child":
+      groupFormRef.value.open(null, data.GroupKey);
+      break;
+    case "delete":
+      deleteGroupConfirm(data);
+      break;
+  }
+};
 
 // 删除分组确认
-const deleteGroupConfirm = (data) => {
-	ElMessageBox.confirm(`确定要删除分组「${data.Name}」吗?如果包含子分组或API,将一并删除。`, '警告', {
-		confirmButtonText: '确定',
-		cancelButtonText: '取消',
-		type: 'warning'
-	}).then(async () => {
-		try {
-			// 实际使用时调用API
-			await deleteGroup([data.Id])
-			ElMessage.success('删除成功')
-
-			// 如果当前选中的是要删除的分组,则清空当前分组
-			if (currentGroup.GroupKey === data.GroupKey) {
-				Object.assign(currentGroup, {
-					Id: undefined,
-					GroupKey: '',
-					Name: '',
-					ParentId: 0,
-					Description: ''
-				})
-				params.groupKey = ''
-				getList(1)
-			}
-
-			// 刷新分组树
-			refreshGroups()
-		} catch (error) {
-			ElMessage.error('删除失败')
-		}
-	}).catch(() => {})
-}
+const deleteGroupConfirm = (data: any) => {
+  ElMessageBox.confirm(`确定要删除分组「${data.Name}」吗?如果包含子分组或API,将一并删除。`, "警告", {
+    confirmButtonText: "确定",
+    cancelButtonText: "取消",
+    type: "warning",
+  })
+    .then(async () => {
+      try {
+        // 实际使用时调用API
+        await api.group.delete([data.Id]);
+        ElMessage.success("删除成功");
+
+        // 如果当前选中的是要删除的分组,则清空当前分组
+        if (currentGroup.GroupKey === data.GroupKey) {
+          Object.assign(currentGroup, {
+            Id: undefined,
+            GroupKey: "",
+            Name: "",
+            ParentId: 0,
+            Description: "",
+          });
+          params.groupKey = "";
+          getList(1);
+        }
+
+        // 刷新分组树
+        refreshGroups();
+      } catch (error) {
+        ElMessage.error("删除失败");
+      }
+    })
+    .catch(() => {});
+};
 
 // 根据请求方法返回不同的标签类型
 const getMethodTagType = (method: string) => {
-	switch (method.toUpperCase()) {
-		case 'GET':
-			return 'success'
-		case 'POST':
-			return 'primary'
-		case 'PUT':
-			return 'warning'
-		case 'DELETE':
-			return 'danger'
-		default:
-			return 'info'
-	}
-}
+  switch (method.toUpperCase()) {
+    case "GET":
+      return "success";
+    case "POST":
+      return "primary";
+    case "PUT":
+      return "warning";
+    case "DELETE":
+      return "danger";
+    default:
+      return "info";
+  }
+};
 
 // 重置查询表单
 const resetQuery = () => {
-	queryRef.value.resetFields()
-	getList(1)
-}
+  queryRef.value.resetFields();
+  getList(1);
+};
+
+// 查看全部数据
+const viewAllData = () => {
+  // 清空当前分组条件
+  params.groupKey = "";
+  // 清空当前分组信息
+  Object.assign(currentGroup, {
+    Id: undefined,
+    GroupKey: "",
+    Name: "",
+    ParentId: 0,
+    Description: "",
+  });
+  // 保留其他查询条件
+  getList(1);
+};
 
 // 新增或编辑API
 const addOrEdit = (row?: ApiDefinition) => {
-	if (row) {
-		// 编辑现有API
-		// 实际使用时,可能需要先获取详情
-		editFormRef.value.open(row)
-	} else {
-		// 新增API,如果有选中分组,则传递分组标识
-		editFormRef.value.open(null, currentGroup.groupKey)
-	}
-}
+  if (row) {
+    // 编辑现有API
+    // 实际使用时,可能需要先获取详情
+    editFormRef.value.open(row);
+  } else {
+    // 新增API,如果有选中分组,则传递分组标识
+    editFormRef.value.open(null, currentGroup.GroupKey);
+  }
+};
 
 // 查看API详情
 const viewDetail = (row: ApiDefinition) => {
-	viewDetailRef.value.open(row)
-}
+  viewDetailRef.value.open(row);
+};
 
 // 测试API
 const testApi = (row: ApiDefinition) => {
-	testApiRef.value.open(row)
-}
+  testApiRef.value.open(row);
+};
 
 // 处理下拉菜单命令
 const handleCommand = (command: string, row: ApiDefinition) => {
-	switch (command) {
-		case 'publish':
-			publishApi(row)
-			break
-		case 'deprecate':
-			deprecateApi(row)
-			break
-		case 'delete':
-			deleteApi(row)
-			break
-	}
-}
+  switch (command) {
+    case "publish":
+      publishApi(row);
+      break;
+    case "deprecate":
+      deprecateApi(row);
+      break;
+    case "delete":
+      deleteApi(row);
+      break;
+  }
+};
 
 // 发布API
 const publishApi = (row: ApiDefinition) => {
-	ElMessageBox.confirm(`确定要发布API「${row.name}」吗?`, '提示', {
-		confirmButtonText: '确定',
-		cancelButtonText: '取消',
-		type: 'warning'
-	}).then(async () => {
-		// 实际使用时替换为真实API调用
-		// await api.apihub.publish({ id: row.id })
-		ElMessage.success('发布成功')
-		getList()
-	}).catch(() => {})
-}
+  ElMessageBox.confirm(`确定要发布API「${row.name}」吗?`, "提示", {
+    confirmButtonText: "确定",
+    cancelButtonText: "取消",
+    type: "warning",
+  }).then(async () => {
+    await api.publish(row.id!);
+    ElMessage.success("发布成功");
+    getList();
+  });
+};
 
 // 废弃API
 const deprecateApi = (row: ApiDefinition) => {
-	ElMessageBox.confirm(`确定要废弃API「${row.name}」吗?`, '提示', {
-		confirmButtonText: '确定',
-		cancelButtonText: '取消',
-		type: 'warning'
-	}).then(async () => {
-		// 实际使用时替换为真实API调用
-		// await api.apihub.deprecate({ id: row.id })
-		ElMessage.success('废弃成功')
-		getList()
-	}).catch(() => {})
-}
+  ElMessageBox.confirm(`确定要废弃API「${row.name}」吗?`, "提示", {
+    confirmButtonText: "确定",
+    cancelButtonText: "取消",
+    type: "warning",
+  }).then(async () => {
+    await api.deprecate(row.id!);
+    ElMessage.success("废弃成功");
+    getList();
+  });
+};
 
 // 删除API
 const deleteApi = (row: ApiDefinition) => {
-	ElMessageBox.confirm(`确定要删除API「${row.name}」吗?此操作不可恢复!`, '警告', {
-		confirmButtonText: '确定',
-		cancelButtonText: '取消',
-		type: 'error'
-	}).then(async () => {
-		// 实际使用时替换为真实API调用
-		// await api.apihub.delete({ ids: [row.id] })
-		ElMessage.success('删除成功')
-		getList()
-	}).catch(() => {})
-}
+  ElMessageBox.confirm(`确定要删除API「${row.name}」吗?此操作不可恢复!`, "警告", {
+    confirmButtonText: "确定",
+    cancelButtonText: "取消",
+    type: "error",
+  }).then(async () => {
+    await api.delete([row.id!]);
+    ElMessage.success("删除成功");
+    getList();
+  });
+};
 </script>
 
 <style scoped>
 .ml10 {
-	margin-left: 10px;
+  margin-left: 10px;
 }
 
 .apihub-container {
-	display: flex;
-	height: calc(100vh - 160px);
+  display: flex;
+  height: 100%;
+  align-items: stretch;
 }
 
 .apihub-sidebar {
-	width: 280px;
-	margin-right: 16px;
-	overflow: hidden;
+  width: 280px;
+  min-width: 280px;
+  margin-right: 16px;
+  overflow-y: auto;
+  height: 100%;
 }
 
 .apihub-content {
-	flex: 1;
-	overflow: hidden;
+  flex: 1;
+  overflow-y: auto;
+  height: 100%;
 }
 
 .group-card {
-	height: 100%;
-	display: flex;
-	flex-direction: column;
+  height: 100%;
+  display: flex;
+  flex-direction: column;
 }
 
 .card-header {
-	display: flex;
-	justify-content: space-between;
-	align-items: center;
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
 }
 
 .header-actions {
-	display: flex;
-	gap: 8px;
+  display: flex;
+  gap: 8px;
 }
 
 .group-search {
-	margin-bottom: 12px;
+  margin-bottom: 12px;
 }
 
 .group-tree-container {
-	overflow-y: auto;
-	flex: 1;
-	min-height: 200px; /* 确保容器有最小高度 */
-	border: 1px solid #ebeef5; /* 添加边框以便于调试 */
-	padding: 10px;
+  overflow-y: auto;
+  flex: 1;
+  min-height: 200px;
+  /* 确保容器有最小高度 */
+  border: 1px solid #ebeef5;
+  /* 添加边框以便于调试 */
+  padding: 10px;
 }
 
 .custom-tree-node {
-	flex: 1;
-	display: flex;
-	align-items: center;
-	justify-content: space-between;
-	font-size: 14px;
-	padding-right: 8px;
+  flex: 1;
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  font-size: 14px;
+  padding-right: 8px;
 }
 
 .api-count {
-	padding: 0 6px;
-	background-color: #f0f0f0;
-	border-radius: 10px;
-	font-size: 12px;
-	color: #606266;
+  padding: 0 6px;
+  background-color: #f0f0f0;
+  border-radius: 10px;
+  font-size: 12px;
+  color: #606266;
 }
 
 .node-actions {
-	margin-left: 8px;
-	visibility: hidden;
+  margin-left: 8px;
+  visibility: hidden;
 }
 
 .custom-tree-node:hover .node-actions {
-	visibility: visible;
+  visibility: visible;
 }
 
 .api-header {
-	margin-bottom: 16px;
-	font-size: 16px;
+  margin-bottom: 16px;
+  font-size: 16px;
 }
 
 .group-name {
-	font-weight: bold;
-	color: #409EFF;
+  font-weight: bold;
+  color: #409eff;
+}
+
+.view-all-link {
+  margin-left: 10px;
+  font-size: 14px;
 }
 </style>

+ 940 - 299
src/views/apihub/component/edit.vue

@@ -1,340 +1,981 @@
 <template>
-	<el-dialog
-		class="api-edit"
-		v-model="showDialog"
-		:title="`${formData.id ? '编辑API' : '新增API'}`"
-		width="800px"
-		:close-on-click-modal="false"
-		:close-on-press-escape="false"
-	>
-		<el-form ref="formRef" :model="formData" :rules="ruleForm" label-width="100px" @keyup.enter="onSubmit">
-			<el-form-item label="API名称" prop="name">
-				<el-input v-model="formData.name" placeholder="请输入API名称" />
-			</el-form-item>
-			<el-form-item label="API路径" prop="path">
-				<el-input v-model="formData.path" placeholder="请输入API路径,如/api/v1/users" />
-			</el-form-item>
-			<el-form-item label="请求方法" prop="method">
-				<el-select v-model="formData.method" placeholder="请选择请求方法">
-					<el-option label="GET" value="GET"></el-option>
-					<el-option label="POST" value="POST"></el-option>
-					<el-option label="PUT" value="PUT"></el-option>
-					<el-option label="DELETE" value="DELETE"></el-option>
-				</el-select>
-			</el-form-item>
-			<el-form-item label="数据源" prop="dataSourceId">
-				<el-select v-model="formData.dataSourceId" placeholder="请选择数据源" filterable>
-					<el-option v-for="item in dataSources" :key="item.id" :label="item.name" :value="item.id"></el-option>
-				</el-select>
-			</el-form-item>
-			<el-form-item label="SQL类型" prop="sqlType">
-				<el-radio-group v-model="formData.sqlType">
-					<el-radio label="query">查询</el-radio>
-					<el-radio label="procedure">存储过程</el-radio>
-				</el-radio-group>
-			</el-form-item>
-			<el-form-item label="所属分组" prop="groupKey">
-				<el-cascader
-					v-model="formData.groupKey"
-					:options="groupOptions"
-					:props="{ checkStrictly: true, emitPath: false, value: 'groupKey', label: 'name' }"
-					placeholder="请选择所属分组"
-					clearable
-					style="width: 100%"
-				/>
-			</el-form-item>
-			<el-form-item label="SQL内容" prop="sqlContent">
-				<el-input v-model="formData.sqlContent" type="textarea" :rows="4" placeholder="请输入SQL语句或存储过程名称" />
-			</el-form-item>
-			
-			<el-form-item label="参数定义">
-				<el-button type="primary" size="small" @click="addParameter">
-					<el-icon><ele-Plus /></el-icon>添加参数
-				</el-button>
-				<el-table :data="formData.parameters" style="width: 100%; margin-top: 10px;" border>
-					<el-table-column label="参数名" width="150">
-						<template #default="scope">
-							<el-input v-model="scope.row.name" placeholder="参数名"></el-input>
-						</template>
-					</el-table-column>
-					<el-table-column label="类型" width="120">
-						<template #default="scope">
-							<el-select v-model="scope.row.type" placeholder="类型">
-								<el-option label="string" value="string"></el-option>
-								<el-option label="int" value="int"></el-option>
-								<el-option label="float" value="float"></el-option>
-								<el-option label="bool" value="bool"></el-option>
-							</el-select>
-						</template>
-					</el-table-column>
-					<el-table-column label="必填" width="80">
-						<template #default="scope">
-							<el-checkbox v-model="scope.row.required"></el-checkbox>
-						</template>
-					</el-table-column>
-					<el-table-column label="默认值" width="120">
-						<template #default="scope">
-							<el-input v-model="scope.row.defaultValue" placeholder="默认值"></el-input>
-						</template>
-					</el-table-column>
-					<el-table-column label="描述">
-						<template #default="scope">
-							<el-input v-model="scope.row.description" placeholder="参数描述"></el-input>
-						</template>
-					</el-table-column>
-					<el-table-column label="操作" width="80">
-						<template #default="scope">
-							<el-button type="danger" size="small" @click="removeParameter(scope.$index)" text>
-								<el-icon><ele-Delete /></el-icon>
-							</el-button>
-						</template>
-					</el-table-column>
-				</el-table>
-			</el-form-item>
-			
-			<el-form-item label="返回格式" prop="returnFormat">
-				<el-select v-model="formData.returnFormat" placeholder="请选择返回格式">
-					<el-option label="JSON" value="JSON"></el-option>
-					<el-option label="XML" value="XML"></el-option>
-				</el-select>
-			</el-form-item>
-			<el-form-item label="版本" prop="version">
-				<el-input v-model="formData.version" placeholder="请输入版本号,如1.0" />
-			</el-form-item>
-			<el-form-item label="状态" prop="status">
-				<el-select v-model="formData.status" placeholder="请选择状态">
-					<el-option label="草稿" value="Draft"></el-option>
-					<el-option label="已发布" value="Published"></el-option>
-					<el-option label="已废弃" value="Deprecated"></el-option>
-				</el-select>
-			</el-form-item>
-			<el-form-item label="描述" prop="description">
-				<el-input v-model="formData.description" type="textarea" :rows="3" placeholder="请输入API描述" />
-			</el-form-item>
-		</el-form>
-		<template #footer>
-			<div class="dialog-footer">
-				<el-button @click="cancel">取消</el-button>
-				<el-button type="primary" @click="onSubmit">确定</el-button>
-			</div>
-		</template>
-	</el-dialog>
+  <el-dialog
+    class="api-edit"
+    v-model="showDialog"
+    :title="`${formData.id ? '编辑API' : '新增API'}`"
+    :width="isFullscreen ? '100%' : '1000px'"
+    :close-on-click-modal="false"
+    :close-on-press-escape="false"
+    :fullscreen="isFullscreen"
+    :before-close="cancel"
+  >
+    <template #header>
+      <div class="dialog-header">
+        <span class="dialog-title">{{ formData.id ? '编辑API' : '新增API' }}</span>
+        <div class="dialog-tools">
+          <el-button
+            type="text"
+            @click="toggleFullscreen"
+          >
+            <el-icon :size="20">
+              <ele-FullScreen v-if="!isFullscreen" />
+              <ele-ScaleToOriginal v-else />
+            </el-icon>
+          </el-button>
+        </div>
+      </div>
+    </template>
+    <div class="dialog-body-with-tabs">
+      <div class="tab-navigation">
+        <ul>
+          <li @click="activeTab = 'basic'" :class="{ active: activeTab === 'basic' }">基本信息</li>
+          <li @click="activeTab = 'executor'" :class="{ active: activeTab === 'executor' }">执行器</li>
+          <li @click="activeTab = 'plugins'" :class="{ active: activeTab === 'plugins' }">全局插件</li>
+        </ul>
+      </div>
+      <div class="tab-content">
+        <el-form ref="formRef" :model="formData" :rules="ruleForm" label-width="100px">
+          <!-- 基本信息 Tab -->
+          <div v-show="activeTab === 'basic'" class="basic-info-tab">
+            <div class="form-section-title">基础配置</div>
+            <el-form-item label="API信息" required>
+              <div class="method-name-container">
+                <div class="method-select">
+                  <el-select v-model="formData.method" placeholder="请求方法">
+                    <el-option label="GET" value="GET"></el-option>
+                    <el-option label="POST" value="POST"></el-option>
+                    <el-option label="PUT" value="PUT"></el-option>
+                    <el-option label="DELETE" value="DELETE"></el-option>
+                  </el-select>
+                </div>
+                <div class="name-input">
+                  <el-input v-model="formData.name" placeholder="请输入API名称" />
+                </div>
+              </div>
+            </el-form-item>
+            <el-form-item label="API路径" prop="path">
+              <div class="path-input-container">
+                <div class="path-prefix">
+                  http://127.0.0.1:8199/api/v1/apihub/
+                </div>
+                <el-input v-model="formData.path" placeholder="请输入API路径" class="path-input" />
+              </div>
+            </el-form-item>
+            <el-form-item label="所属分组" prop="groupKey">
+              <el-cascader v-model="formData.groupKey" :options="groupOptions" :props="{ checkStrictly: true, emitPath: false, value: 'GroupKey', label: 'Name', children: 'Children' }" placeholder="请选择所属分组" clearable style="width: 100%" />
+            </el-form-item>
+            <el-form-item label="版本" prop="version">
+              <el-input v-model="formData.version" placeholder="请输入版本号,如1.0" />
+            </el-form-item>
+            <!-- <el-form-item label="状态" prop="status">
+              <el-select v-model="formData.status" placeholder="请选择状态">
+                <el-option label="草稿" value="Draft"></el-option>
+                <el-option label="已发布" value="Published"></el-option>
+                <el-option label="已废弃" value="Deprecated"></el-option>
+              </el-select>
+            </el-form-item> -->
+            <el-form-item label="描述" prop="description">
+              <el-input v-model="formData.description" type="textarea" :rows="3" placeholder="请输入API描述" />
+            </el-form-item>
+
+            <div class="form-section-title">参数定义
+              <el-button type="primary" link @click="addParameter" style="margin-left: 10px;">
+                <el-icon><ele-Plus /></el-icon>添加参数
+              </el-button>
+            </div>
+            <el-form-item label-width="0">
+              <el-table :data="formData.parameters" style="width: 100%" border>
+                <el-table-column label="参数名" width="150">
+                  <template #default="scope">
+                    <el-input v-model="scope.row.name" placeholder="参数名"></el-input>
+                  </template>
+                </el-table-column>
+                <el-table-column label="类型" width="120">
+                  <template #default="scope">
+                    <el-select v-model="scope.row.type" placeholder="类型">
+                      <el-option label="string" value="string"></el-option>
+                      <el-option label="int" value="int"></el-option>
+                      <el-option label="float" value="float"></el-option>
+                      <el-option label="bool" value="bool"></el-option>
+                    </el-select>
+                  </template>
+                </el-table-column>
+                <el-table-column label="必填" width="80">
+                  <template #default="scope">
+                    <el-checkbox v-model="scope.row.required"></el-checkbox>
+                  </template>
+                </el-table-column>
+                <el-table-column label="默认值" width="120">
+                  <template #default="scope">
+                    <el-input v-model="scope.row.defaultValue" placeholder="默认值"></el-input>
+                  </template>
+                </el-table-column>
+                <el-table-column label="描述">
+                  <template #default="scope">
+                    <el-input v-model="scope.row.description" placeholder="参数描述"></el-input>
+                  </template>
+                </el-table-column>
+                <el-table-column label="操作" width="80">
+                  <template #default="scope">
+                    <el-button type="danger" link @click="removeParameter(scope.$index)">
+                      <el-icon><ele-Delete /></el-icon>
+                    </el-button>
+                  </template>
+                </el-table-column>
+              </el-table>
+            </el-form-item>
+          </div>
+
+          <!-- 执行器 Tab -->
+          <div v-show="activeTab === 'executor'" class="executor-tab">
+            <div class="form-section-title">SQL配置</div>
+            <el-form-item label="数据源" prop="dataSourceKey">
+              <el-select v-model="formData.dataSourceKey" placeholder="请选择数据源" filterable>
+                <el-option v-for="item in dataSources" :key="item.id" :label="item.name" :value="item.dsKey"></el-option>
+              </el-select>
+            </el-form-item>
+            <el-form-item label="SQL类型" prop="sqlType">
+              <el-radio-group v-model="formData.sqlType">
+                <el-radio label="query">查询</el-radio>
+                <el-radio label="procedure">存储过程</el-radio>
+              </el-radio-group>
+            </el-form-item>
+            <el-form-item label="SQL内容" prop="sqlContent" class="sql-content-item">
+              <div class="monaco-editor-wrapper">
+                <div class="line-number-container">
+                  <div v-for="n in lineCount" :key="n" class="line-number">{{ n }}</div>
+                </div>
+                <div class="monaco-editor-container">
+                  <MonacoEditor
+                    v-model:value="formData.sqlContent"
+                    :options="monacoOptions"
+                    theme="vs-dark"
+                    language="sql"
+                    @change="onSqlContentChange"
+                    @editorDidMount="editorDidMount"
+                    @editorWillMount="editorWillMount"
+                  />
+                </div>
+              </div>
+            </el-form-item>
+            <el-form-item label="返回格式" prop="returnFormat">
+              <el-select v-model="formData.returnFormat" placeholder="请选择返回格式">
+                <el-option label="JSON" value="JSON"></el-option>
+                <el-option label="XML" value="XML"></el-option>
+              </el-select>
+            </el-form-item>
+          </div>
+
+          <!-- 全局插件 Tab -->
+          <div v-show="activeTab === 'plugins'" class="plugins-tab">
+            <div class="form-section-title">插件列表
+              <el-button type="primary" link @click="addPlugin" style="margin-left: 10px;">
+                <el-icon><ele-Plus /></el-icon>添加插件
+              </el-button>
+            </div>
+            <div v-if="!formData.plugins || formData.plugins.length === 0" class="empty-plugins">
+              <el-empty description="暂无插件配置" :image-size="80"></el-empty>
+            </div>
+            <div v-else>
+              <div v-for="(plugin, index) in formData.plugins" :key="index" class="plugin-list-item">
+                <div class="plugin-item-details">
+                  <el-form-item
+                    :label="`名称`"
+                    :prop="`plugins[${index}].name`"
+                    :rules="[{ required: true, message: '插件名称不能为空', trigger: 'blur' }]"
+                    label-width="60px"
+                    class="plugin-field"
+                  >
+                    <el-input v-model="plugin.name" placeholder="插件Bean名称或类完整路径" size="small" />
+                  </el-form-item>
+                  <el-form-item
+                    :label="`参数`"
+                    :prop="`plugins[${index}].config`"
+                    label-width="60px"
+                    class="plugin-field"
+                  >
+                    <el-input
+                      v-model="plugin.config"
+                      type="textarea"
+                      :rows="2"
+                      :placeholder="pluginParamsPlaceholder"
+                      size="small"
+                    />
+                  </el-form-item>
+                </div>
+                <div class="plugin-remove-action">
+                  <el-button
+                    type="danger"
+                    link
+                    icon="ele-Delete"
+                    @click="removePlugin(index)"
+                    size="small"
+                  />
+                </div>
+              </div>
+            </div>
+          </div>
+        </el-form>
+      </div>
+    </div>
+    <template #footer>
+      <div class="dialog-footer">
+        <el-button @click="cancel">取消</el-button>
+        <el-button type="primary" @click="onSubmit">确定</el-button>
+      </div>
+    </template>
+  </el-dialog>
 </template>
 
 <script lang="ts" setup>
-import { ref, reactive, nextTick } from 'vue'
-import { ElMessage } from 'element-plus'
-import { ruleRequired } from '/@/utils/validator'
-import request from '/@/utils/request'
+import { ref, reactive, nextTick } from "vue";
+import { ElMessage, ElMessageBox } from "element-plus";
+import { ruleRequired } from "/@/utils/validator";
+import api from "/@/api/modules/apiHub";
+import {
+  Plus as ElePlus,
+  Delete as EleDelete,
+  FullScreen as EleFullScreen,
+  ScaleToOriginal as EleScaleToOriginal
+} from '@element-plus/icons-vue';
+import MonacoEditor from "@guolao/vue-monaco-editor";
+
+const emit = defineEmits(["getList"]);
+
+const showDialog = ref(false);
+const formRef = ref();
+const dataSources = ref<any[]>([]);
+const activeTab = ref('basic'); // 当前激活的Tab
+const isFullscreen = ref(false); // 是否全屏显示
+const lineCount = ref(1); // SQL编辑器行数
+const pluginParamsPlaceholder = '插件参数 (JSON格式,例如:{"key": "value"})';
+
+// Monaco Editor 配置项
+const monacoOptions = {
+  automaticLayout: true,
+  scrollBeyondLastLine: false,
+  minimap: { enabled: false },
+  fontSize: 14,
+  tabSize: 2,
+  lineNumbers: 'off', // 关闭内置行号,因为我们自定义了行号显示
+  scrollbar: {
+    useShadows: false,
+    verticalScrollbarSize: 10,
+    horizontalScrollbarSize: 10,
+    alwaysConsumeMouseWheel: false
+  },
+  lineHeight: 20,
+  renderLineHighlight: 'line',
+  glyphMargin: false,
+  folding: true,
+  contextmenu: true,
+  cursorStyle: 'line',
+  fontFamily: 'Menlo, Monaco, Consolas, "Courier New", monospace',
+  // 自动提示相关配置 - 使用Monaco编辑器自带的SQL支持
+  quickSuggestions: {
+    other: true,
+    comments: true,
+    strings: true
+  },
+  suggestOnTriggerCharacters: true,
+  acceptSuggestionOnEnter: 'on',
+  tabCompletion: 'on',
+  wordBasedSuggestions: true,
+  // 设置触发建议的字符数
+  quickSuggestionsDelay: 0, // 立即显示提示
+  // 自动提示相关配置
+  suggest: {
+    showKeywords: true,
+    showSnippets: true,
+    showWords: true,
+    showFunctions: true,
+    showIcons: true,
+    maxVisibleSuggestions: 15,
+    filterGraceful: false, // 不过滤建议,显示所有可能的选项
+    snippetSuggestions: 'top' // 将代码片段放在提示列表顶部
+  }
+};
+
+// SQL内容变化回调
+const onSqlContentChange = (value) => {
+  formData.sqlContent = value;
+  // 计算行数
+  const lines = value ? value.split('\n').length : 1;
+  lineCount.value = Math.max(1, lines);
+};
+
+// 编辑器将要加载时的回调
+const editorWillMount = (monaco) => {
+  // 自定义SQL关键字高亮
+  monaco.editor.defineTheme('sqlTheme', {
+    base: 'vs-dark',
+    inherit: true,
+    rules: [
+      { token: 'keyword', foreground: '569cd6', fontStyle: 'bold' },
+      { token: 'operator', foreground: 'd4d4d4' },
+      { token: 'string', foreground: 'ce9178' },
+      { token: 'number', foreground: 'b5cea8' },
+      { token: 'comment', foreground: '6a9955', fontStyle: 'italic' }
+    ],
+    colors: {}
+  });
+};
 
-const emit = defineEmits(['getList'])
+// 编辑器挂载完成回调
+const editorDidMount = (editor) => {
+  // 初始化行数
+  const lines = formData.sqlContent ? formData.sqlContent.split('\n').length : 1;
+  lineCount.value = Math.max(1, lines);
 
-const showDialog = ref(false)
-const formRef = ref()
-const dataSources = ref([
-	{ id: 1, name: '主数据库' },
-	{ id: 2, name: '业务数据库' },
-	{ id: 3, name: '日志数据库' }
-])
+  // 使用editor实例以避免lint警告
+  if (editor) {
+    // 设置焦点
+    editor.focus();
+    
+    // 添加输入监听,在用户开始输入时触发提示
+    editor.onDidChangeModelContent(() => {
+      // 触发自动提示
+      editor.trigger('keyboard', 'editor.action.triggerSuggest', {});
+    });
+    
+    // 初始化时就触发一次提示
+    setTimeout(() => {
+      editor.trigger('keyboard', 'editor.action.triggerSuggest', {});
+    }, 100);
+    
+    // 添加键盘事件处理,阻止回车键触发表单提交
+    editor.onKeyDown((e) => {
+      // 当用户在编辑器中按下回车键时
+      if (e.keyCode === 13 || e.code === 'Enter') {
+        // 阻止事件冒泡到表单
+        e.stopPropagation();
+      }
+    });
+  }
+};
+
+// 检查哪个标签页有验证错误并切换到该标签页
+const checkTabWithErrors = () => {
+  // 获取所有验证失败的字段
+  const fields = formRef.value.fields;
+  const errorFields = [];
+  
+  // 收集所有错误字段
+  for (const field in fields) {
+    if (fields[field].validateState === 'error') {
+      errorFields.push(field);
+    }
+  }
+  
+  if (errorFields.length === 0) return;
+  
+  // 基本信息标签页的字段
+  const basicTabFields = ['name', 'path', 'version', 'description', 'groupKey'];
+  // 执行器标签页的字段
+  const executorTabFields = ['dataSourceKey', 'sqlType', 'sqlContent', 'returnFormat', 'status'];
+  // 全局插件标签页的字段
+  const pluginsTabFields = [];
+  // 检查插件相关字段
+  errorFields.forEach(field => {
+    if (field.startsWith('plugins[')) {
+      pluginsTabFields.push(field);
+    }
+  });
+  
+  // 检查每个标签页并切换到有错误的标签页
+  const errorTabs = [];
+  
+  if (errorFields.some(field => basicTabFields.includes(field))) {
+    errorTabs.push({ tab: 'basic', name: '基本信息' });
+  }
+  if (errorFields.some(field => executorTabFields.includes(field))) {
+    errorTabs.push({ tab: 'executor', name: '执行器' });
+  }
+  if (pluginsTabFields.length > 0) {
+    errorTabs.push({ tab: 'plugins', name: '全局插件' });
+  }
+  
+  if (errorTabs.length > 0) {
+    // 如果当前标签页不在错误标签页列表中,切换到第一个错误标签页
+    const currentTabHasError = errorTabs.some(tab => tab.tab === activeTab.value);
+    if (!currentTabHasError) {
+      activeTab.value = errorTabs[0].tab;
+    }
+    
+    // 显示错误提示
+    showErrorTooltip(errorTabs);
+    
+    // 等待DOM更新后滚动到错误字段
+    nextTick(() => {
+      const errorElement = document.querySelector('.is-error');
+      if (errorElement) {
+        errorElement.scrollIntoView({ behavior: 'smooth', block: 'center' });
+      }
+    });
+  }
+};
+
+// 显示错误提示
+const showErrorTooltip = (errorTabs) => {
+  // 创建错误标签页列表的HTML
+  let errorMessage = '<div class="error-tabs-list">';
+  errorMessage += '<div class="error-title">以下标签页有必填项未填写:</div>';
+  errorMessage += '<ul>';
+  errorTabs.forEach(tab => {
+    errorMessage += `<li class="error-tab-item" data-tab="${tab.tab}">${tab.name}</li>`;
+  });
+  errorMessage += '</ul>';
+  errorMessage += '</div>';
+  
+  // 显示消息提示
+  ElMessage({
+    dangerouslyUseHTMLString: true,
+    message: errorMessage,
+    type: 'error',
+    duration: 5000,
+    showClose: true,
+    onClose: () => {
+      // 消息关闭后移除事件监听
+      removeErrorTabClickListeners();
+    }
+  });
+  
+  // 等待DOM更新后添加点击事件
+  nextTick(() => {
+    addErrorTabClickListeners();
+  });
+};
+
+// 添加错误标签页点击事件
+const addErrorTabClickListeners = () => {
+  const tabItems = document.querySelectorAll('.error-tab-item');
+  tabItems.forEach(item => {
+    item.addEventListener('click', handleErrorTabClick);
+    // 添加手型光标和下划线样式
+    item.style.cursor = 'pointer';
+    item.style.textDecoration = 'underline';
+    item.style.color = '#409EFF';
+  });
+};
+
+// 移除错误标签页点击事件
+const removeErrorTabClickListeners = () => {
+  const tabItems = document.querySelectorAll('.error-tab-item');
+  tabItems.forEach(item => {
+    item.removeEventListener('click', handleErrorTabClick);
+  });
+};
+
+// 错误标签页点击处理函数
+const handleErrorTabClick = (event) => {
+  const tabName = event.target.getAttribute('data-tab');
+  if (tabName) {
+    activeTab.value = tabName;
+    // 等待DOM更新后滚动到错误字段
+    nextTick(() => {
+      const errorElement = document.querySelector('.is-error');
+      if (errorElement) {
+        errorElement.scrollIntoView({ behavior: 'smooth', block: 'center' });
+      }
+    });
+  }
+};
+
+api.dataSource.list().then((res: any) => {
+  dataSources.value = res.list;
+});
 
 // 分组选项
-const groupOptions = ref([])
+const groupOptions = ref([]);
 
 // 定义基础表单数据
 const baseForm = {
-	id: undefined,
-	name: '',
-	path: '',
-	method: 'GET',
-	dataSourceId: undefined,
-	sqlType: 'query',
-	sqlContent: '',
-	parameters: [],
-	returnFormat: 'JSON',
-	version: '1.0',
-	status: 'Draft',
-	plugins: [],
-	groupKey: '',
-	description: ''
-}
-
-const formData = reactive(JSON.parse(JSON.stringify(baseForm)))
+  id: null,
+  name: "",
+  path: "",
+  method: "GET",
+  dataSourceKey: null,
+  sqlType: "query",
+  sqlContent: "",
+  parameters: [],
+  returnFormat: "JSON",
+  version: "1.0",
+  status: "Draft",
+  plugins: [],
+  groupKey: "",
+  description: "",
+};
+
+const formData = reactive(JSON.parse(JSON.stringify(baseForm)));
 
 // 表单验证规则
 const ruleForm = {
-	name: [ruleRequired('API名称不能为空', 'change')],
-	path: [ruleRequired('API路径不能为空', 'change')],
-	method: [ruleRequired('请求方法不能为空', 'change')],
-	dataSourceId: [ruleRequired('数据源不能为空', 'change')],
-	sqlType: [ruleRequired('SQL类型不能为空', 'change')],
-	sqlContent: [ruleRequired('SQL内容不能为空', 'change')],
-	returnFormat: [ruleRequired('返回格式不能为空', 'change')],
-	version: [ruleRequired('版本不能为空', 'change')],
-	status: [ruleRequired('状态不能为空', 'change')]
-}
+  name: [ruleRequired("API名称不能为空", "change")],
+  path: [ruleRequired("API路径不能为空", "change")],
+  method: [ruleRequired("请求方法不能为空", "change")],
+  dataSourceKey: [ruleRequired("数据源不能为空", "change")],
+  sqlType: [ruleRequired("SQL类型不能为空", "change")],
+  sqlContent: [ruleRequired("SQL内容不能为空", "change")],
+  returnFormat: [ruleRequired("返回格式不能为空", "change")],
+  version: [ruleRequired("版本不能为空", "change")],
+  status: [ruleRequired("状态不能为空", "change")],
+};
 
 // 添加参数
 const addParameter = () => {
-	if (!formData.parameters) {
-		formData.parameters = []
-	}
-	formData.parameters.push({
-		name: '',
-		type: 'string',
-		required: false,
-		defaultValue: '',
-		description: ''
-	})
-}
+  if (!formData.parameters) {
+    formData.parameters = [];
+  }
+  formData.parameters.push({
+    name: "",
+    type: "string",
+    required: false,
+    defaultValue: "",
+    description: "",
+  });
+};
 
 // 移除参数
-const removeParameter = (index) => {
-	formData.parameters.splice(index, 1)
-}
+const removeParameter = (index: number) => {
+  formData.parameters.splice(index, 1);
+};
 
-// 添加API
-const addApi = (data) => {
-	return request({
-		url: '/api/add',
-		method: 'post',
-		data
-	})
-}
+// 添加插件
+const addPlugin = () => {
+  if (!formData.plugins) {
+    formData.plugins = [];
+  }
+  formData.plugins.push({
+    name: "",
+    config: "",
+  });
+};
 
-// 编辑API
-const editApi = (data) => {
-	return request({
-		url: '/api/edit',
-		method: 'put',
-		data
-	})
-}
+// 移除插件
+const removePlugin = (index: number) => {
+  formData.plugins.splice(index, 1);
+};
+
+// 切换全屏显示
+const toggleFullscreen = () => {
+  isFullscreen.value = !isFullscreen.value;
+
+  // 全屏模式下调整内容容器高度
+  if (isFullscreen.value) {
+    nextTick(() => {
+      const dialogBodyWithTabs = document.querySelector('.dialog-body-with-tabs');
+      if (dialogBodyWithTabs) {
+        (dialogBodyWithTabs as HTMLElement).style.height = 'calc(100vh - 120px)';
+      }
+    });
+  } else {
+    nextTick(() => {
+      const dialogBodyWithTabs = document.querySelector('.dialog-body-with-tabs');
+      if (dialogBodyWithTabs) {
+        (dialogBodyWithTabs as HTMLElement).style.height = '600px';
+      }
+    });
+  }
+};
 
 // 提交表单
-const onSubmit = async () => {
-	try {
-		// 表单验证
-		await formRef.value.validate()
-		
-		// 准备提交数据
-		const submitData = JSON.parse(JSON.stringify(formData))
-		
-		// 调用API
-		if (submitData.id) {
-			// 编辑模式
-			await editApi(submitData)
-		} else {
-			// 新增模式
-			await addApi(submitData)
-		}
-		
-		ElMessage.success('操作成功')
-		resetForm()
-		showDialog.value = false
-		emit('getList')
-	} catch (error) {
-		ElMessage.error(error.message || '操作失败')
-	}
-}
+const onSubmit = () => {
+  // 使用回调形式的验证,而不是异步验证
+  formRef.value.validate((valid, fields) => {
+    if (valid) {
+      // 验证通过
+      // 准备提交数据
+      const submitData = JSON.parse(JSON.stringify(formData));
+
+      // 调用API
+      if (submitData.id) {
+        // 编辑模式
+        api.edit(submitData).then(() => {
+          ElMessage.success("操作成功");
+          resetForm();
+          showDialog.value = false;
+          emit("getList");
+        });
+      } else {
+        // 新增模式
+        delete submitData.id;
+        api.add(submitData).then(() => {
+          ElMessage.success("操作成功");
+          resetForm();
+          showDialog.value = false;
+          emit("getList");
+        });
+      }
+    } else {
+      // 验证失败
+      // 直接显示错误提示
+      showValidationErrorMessage(fields);
+      
+      // 验证失败时检查是哪个标签页的必填项出错
+      checkTabWithErrors();
+      
+      return false;
+    }
+  });
+};
+
+// 显示验证错误消息
+const showValidationErrorMessage = (fields) => {
+  // 获取所有错误字段
+  const errorFields = Object.keys(fields);
+  if (errorFields.length === 0) return;
+  
+  // 基本信息标签页的字段
+  const basicTabFields = ['name', 'path', 'version', 'description', 'groupKey'];
+  // 执行器标签页的字段
+  const executorTabFields = ['dataSourceKey', 'sqlType', 'sqlContent', 'returnFormat', 'status'];
+  
+  // 检查每个标签页的错误
+  const errorTabs = [];
+  
+  if (errorFields.some(field => basicTabFields.includes(field))) {
+    errorTabs.push('基本信息');
+  }
+  if (errorFields.some(field => executorTabFields.includes(field))) {
+    errorTabs.push('执行器');
+  }
+  if (errorFields.some(field => field.startsWith('plugins['))) {
+    errorTabs.push('全局插件');
+  }
+  
+  if (errorTabs.length > 0) {
+    // 显示错误消息
+    ElMessage({
+      message: `请检查以下标签页的必填项:${errorTabs.join('、')}`,
+      type: 'error',
+      duration: 5000,
+      showClose: true
+    });
+  }
+};
 
 // 重置表单
 const resetForm = () => {
-	Object.assign(formData, JSON.parse(JSON.stringify(baseForm)))
-	formRef.value && formRef.value.resetFields()
-}
+  Object.assign(formData, JSON.parse(JSON.stringify(baseForm)));
+  activeTab.value = 'basic'; // 重置 Tab
+  formRef.value && formRef.value.resetFields();
+};
+
+
 
 // 取消操作
 const cancel = () => {
-	resetForm()
-	showDialog.value = false
-}
-
-// 获取分组树形结构
-const getGroupTree = async () => {
-	try {
-		// 调用API获取分组树
-		const res = await request({
-			url: '/api_group/tree',
-			method: 'get'
-		})
-		return res.data?.list || []
-		
-		// 模拟数据
-		return [
-			{
-				id: 1,
-				groupKey: 'test_group',
-				name: '测试分组',
-				parentKey: '',
-				sort: 0,
-				description: '这是一个测试分组',
-				children: [
-					{
-						id: 3,
-						groupKey: 'test_subgroup',
-						name: '测试子分组',
-						parentKey: 'test_group',
-						sort: 0,
-						description: '这是一个测试子分组',
-						children: []
-					}
-				]
-			},
-			{
-				id: 2,
-				groupKey: 'user_api',
-				name: '用户API',
-				parentKey: '',
-				sort: 1,
-				description: '用户相关API',
-				children: []
-			}
-		]
-	} catch (error) {
-		ElMessage.error('获取分组树失败')
-		return []
-	}
-}
+  ElMessageBox.confirm(
+    '确定要关闭吗?未保存的内容将会丢失',
+    '提示',
+    {
+      confirmButtonText: '确定',
+      cancelButtonText: '取消',
+      type: 'warning',
+    }
+  )
+    .then(() => {
+      showDialog.value = false;
+      resetForm();
+    })
+    .catch(() => {
+      // 用户取消关闭,不做任何操作
+    });
+};
 
 // 加载分组选项
 const loadGroupOptions = async () => {
-	const treeData = await getGroupTree()
-	groupOptions.value = treeData
-}
+  api.group.tree().then((res: any) => {
+    groupOptions.value = res.list || [];
+  });
+};
 
 // 打开对话框
 const open = async (row?: any, defaultGroupKey?: string) => {
-	resetForm()
-	showDialog.value = true
-	
-	// 加载分组选项
-	await loadGroupOptions()
-	
-	if (row) {
-		// 编辑模式,填充表单数据
-		nextTick(() => {
-			// 如果是编辑模式,需要先获取详情
-			// 实际使用时,可能需要先调用API获取完整数据
-			// 这里模拟直接使用传入的行数据
-			Object.assign(formData, JSON.parse(JSON.stringify(row)))
-			
-			// 确保参数数组存在
-			if (!formData.parameters) {
-				formData.parameters = []
-			}
-		})
-	} else if (defaultGroupKey) {
-		// 新增模式,设置默认分组
-		formData.groupKey = defaultGroupKey
-	}
-}
-
-defineExpose({ open })
+  resetForm();
+  showDialog.value = true;
+
+  // 加载分组选项
+  await loadGroupOptions();
+
+  if (row) {
+    // 编辑模式,填充表单数据
+    nextTick(() => {
+      // 如果是编辑模式,需要先获取详情
+      // 实际使用时,可能需要先调用API获取完整数据
+      // 这里模拟直接使用传入的行数据
+
+      Object.assign(formData, JSON.parse(JSON.stringify(row)));
+
+      // 确保参数数组存在
+      if (!formData.parameters) {
+        formData.parameters = [];
+      }
+    });
+  }
+  if (defaultGroupKey) {
+    // 新增模式,设置默认分组
+    formData.groupKey = defaultGroupKey;
+  }
+};
+
+defineExpose({ open });
 </script>
 
 <style scoped>
+/* 错误提示样式 */
+:deep(.error-tabs-list) {
+  text-align: left;
+  padding: 5px 0;
+}
+
+:deep(.error-title) {
+  font-weight: bold;
+  margin-bottom: 5px;
+}
+
+:deep(.error-tabs-list ul) {
+  list-style: disc;
+  padding-left: 20px;
+  margin: 5px 0;
+}
+
+:deep(.error-tab-item) {
+  margin: 3px 0;
+  padding: 2px 0;
+}
+
+.dialog-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  padding: 0 20px;
+}
+
+.dialog-title {
+  font-size: 18px;
+  font-weight: bold;
+  color: #303133;
+}
+
+.dialog-tools {
+  display: flex;
+  align-items: center;
+}
+
+:deep(.el-dialog__header) {
+  padding: 15px 0;
+  margin: 0;
+}
+
+:deep(.el-dialog__headerbtn) {
+  top: 15px;
+  right: 20px;
+}
+
+:deep(.el-dialog__body) {
+  padding-top: 0;
+}
+
+.api-edit .dialog-body-with-tabs {
+  display: flex;
+  height: 600px; /* 固定高度,使切换tab时不会改变 */
+}
+
+.tab-navigation {
+  width: 150px; /* 稍微加宽一点导航宽度 */
+  border-right: 1px solid #e0e0e0; /* 颜色调整 */
+  padding-right: 0; /* 移除右边padding,让li撑满 */
+  margin-right: 0; /* 移除右边margin */
+}
+
+.tab-navigation ul {
+  list-style: none;
+  padding: 10px 0;
+  margin: 0;
+}
+
+.tab-navigation li {
+  padding: 12px 20px; /* 调整padding */
+  cursor: pointer;
+  /* border-radius: 4px; */ /* 移除圆角,使其更像设计图的tab */
+  margin-bottom: 2px; /* tab之间的间距 */
+  font-size: 14px;
+  color: #303133;
+}
+
+.tab-navigation li:hover {
+  background-color: #f0f2f5; /* hover颜色调整 */
+}
+
+.tab-navigation li.active {
+  background-color: #e6f7ff; /* active背景色调整 */
+  color: #1890ff; /* active文字颜色调整 */
+  border-right: 3px solid #1890ff; /* active时右侧有蓝色竖线 */
+  font-weight: 500; /* active时文字稍微加粗 */
+}
+
+.tab-content {
+  flex: 1;
+  overflow-y: auto;
+  height: 100%; /* 使内容区域填充整个高度 */
+  padding: 10px 20px; /* tab内容的padding */
+}
+
+.form-section-title {
+  font-size: 14px;
+  font-weight: 600;
+  color: #303133;
+  padding: 10px 0;
+  margin-bottom: 15px;
+  border-bottom: 1px solid #e0e0e0;
+  display: flex;
+  align-items: center;
+}
+
+.basic-info-tab,
+.executor-tab,
+.plugins-tab {
+  height: 100%; /* 使每个标签页内容区域填充整个高度 */
+  overflow-y: auto; /* 内容超出时显示滚动条 */
+}
+
+.basic-info-tab .el-form-item,
+.executor-tab .el-form-item,
+.plugins-tab .el-form-item {
+  margin-bottom: 18px; /* 统一表单项间距 */
+}
+
+/* 全局插件样式 */
+.plugins-tab .form-section-title {
+  margin-bottom: 15px; /* Ensure space before the first plugin item or empty state */
+}
+
+.plugin-list-item {
+  display: flex;
+  align-items: flex-start; /* Aligns items to the top, good if details section can vary in height */
+  padding: 12px 0;
+  border-bottom: 1px solid #ebeef5;
+}
+.plugin-list-item:last-of-type {
+  border-bottom: none;
+  padding-bottom: 0; /* No padding if it's the last item and no border */
+}
+
+.plugin-item-details {
+  flex-grow: 1;
+}
+
+.plugin-item-details .plugin-field {
+  margin-bottom: 10px;
+}
+.plugin-item-details .plugin-field:last-of-type {
+  margin-bottom: 0; /* No margin for the last field in the details section */
+}
+
+.plugin-remove-action {
+  flex-shrink: 0;
+  margin-left: 15px;
+  padding-top: 5px; /* Small top padding to align better with first line of text or input */
+}
+
+.empty-plugins {
+  text-align: center;
+  padding: 20px 0;
+  color: #909399;
+}
+
+/* Monaco Editor 样式 */
+.sql-content-item {
+  width: 100%;
+}
+
+.monaco-editor-wrapper {
+  display: flex;
+  align-items: stretch;
+  width: 100%;
+  border: 1px solid #dcdfe6;
+  border-radius: 4px;
+  overflow: hidden;
+  min-width: 500px;
+}
+
+.line-number-container {
+  background-color: #f5f7fa;
+  border-right: 1px solid #dcdfe6;
+  min-width: 40px;
+  width: 40px;
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  padding-top: 8px;
+}
+
+.line-number {
+  color: #909399;
+  font-family: monospace;
+  font-size: 12px;
+  line-height: 1.5;
+  text-align: center;
+  width: 100%;
+}
+
+.monaco-editor-container {
+  height: 350px; /* 增加编辑器高度,使其更好地利用空间 */
+  flex-grow: 1;
+  overflow: hidden;
+  border: none;
+}
+
+/* API路径输入框样式 */
+.path-input-container {
+  display: flex;
+  align-items: center;
+  width: 100%;
+}
+
+.path-prefix {
+  background-color: #f5f7fa;
+  padding: 0 10px;
+  height: 32px;
+  line-height: 32px;
+  border: 1px solid #dcdfe6;
+  border-right: none;
+  border-radius: 4px 0 0 4px;
+  color: #606266;
+  white-space: nowrap;
+}
+
+.path-input {
+  flex: 1;
+}
+
+:deep(.path-input .el-input__inner) {
+  border-top-left-radius: 0;
+  border-bottom-left-radius: 0;
+}
+
+/* 请求方法和API名称同行样式 */
+.method-name-container {
+  display: flex;
+  align-items: center;
+  width: 100%;
+  gap: 10px;
+}
+
+.method-select {
+  width: 120px;
+  flex-shrink: 0;
+}
+
+.name-input {
+  flex: 1;
+}
+
 .dialog-footer {
-	display: flex;
-	justify-content: flex-end;
+  display: flex;
+  justify-content: flex-end;
+  padding: 15px 20px;
+  border-top: 1px solid #e0e0e0;
 }
 </style>

+ 129 - 171
src/views/apihub/component/group.vue

@@ -1,205 +1,163 @@
 <template>
-	<el-dialog
-		class="group-edit"
-		v-model="showDialog"
-		:title="`${formData.id ? '编辑分组' : '新增分组'}`"
-		width="500px"
-		:close-on-click-modal="false"
-		:close-on-press-escape="false"
-	>
-		<el-form ref="formRef" :model="formData" :rules="ruleForm" label-width="100px" @keyup.enter="onSubmit">
-			<el-form-item label="分组名称" prop="name">
-				<el-input v-model="formData.name" placeholder="请输入分组名称" />
-			</el-form-item>
-			<el-form-item label="分组标识" prop="groupKey">
-				<el-input v-model="formData.groupKey" placeholder="请输入分组唯一标识,留空则自动生成" />
-			</el-form-item>
-			<el-form-item label="父级分组" prop="parentKey">
-				<el-cascader
-					v-model="formData.parentKey"
-					:options="groupOptions"
-					:props="{ checkStrictly: true, emitPath: false, value: 'groupKey', label: 'name' }"
-					placeholder="请选择父级分组,不选择则为顶级分组"
-					clearable
-					style="width: 100%"
-				/>
-			</el-form-item>
-			<el-form-item label="排序号" prop="sort">
-				<el-input-number v-model="formData.sort" :min="0" :max="999" />
-			</el-form-item>
-			<el-form-item label="分组描述" prop="description">
-				<el-input v-model="formData.description" type="textarea" :rows="3" placeholder="请输入分组描述" />
-			</el-form-item>
-		</el-form>
-		<template #footer>
-			<div class="dialog-footer">
-				<el-button @click="cancel">取消</el-button>
-				<el-button type="primary" @click="onSubmit">确定</el-button>
-			</div>
-		</template>
-	</el-dialog>
+  <el-dialog class="group-edit" v-model="showDialog" :title="`${formData.id ? '编辑分组' : '新增分组'}`" width="500px" :close-on-click-modal="false" :close-on-press-escape="false">
+    <el-form ref="formRef" :model="formData" :rules="ruleForm" label-width="100px" @keyup.enter="onSubmit">
+      <el-form-item label="分组名称" prop="name">
+        <el-input v-model="formData.name" placeholder="请输入分组名称" />
+      </el-form-item>
+      <el-form-item label="分组标识" prop="groupKey">
+        <el-input v-model="formData.groupKey" placeholder="请输入分组唯一标识,留空则自动生成" />
+      </el-form-item>
+      <el-form-item label="父级分组" prop="parentId">
+        <el-cascader v-model="formData.parentId" :options="groupOptions" :props="{ checkStrictly: true, emitPath: false, value: 'Id', label: 'Name', children: 'Children' }" placeholder="请选择父级分组,不选择则为顶级分组" clearable style="width: 100%" />
+      </el-form-item>
+      <el-form-item label="排序号" prop="sort">
+        <el-input-number v-model="formData.sort" :min="0" :max="999" />
+      </el-form-item>
+      <el-form-item label="分组描述" prop="description">
+        <el-input v-model="formData.description" type="textarea" :rows="3" placeholder="请输入分组描述" />
+      </el-form-item>
+    </el-form>
+    <template #footer>
+      <div class="dialog-footer">
+        <el-button @click="cancel">取消</el-button>
+        <el-button type="primary" @click="onSubmit">确定</el-button>
+      </div>
+    </template>
+  </el-dialog>
 </template>
 
 <script lang="ts" setup>
-import { ref, reactive, nextTick } from 'vue'
-import { ElMessage } from 'element-plus'
-import { ruleRequired } from '/@/utils/validator'
-import request from '/@/utils/request'
+import { ref, reactive, nextTick } from "vue";
+import { ElMessage } from "element-plus";
+import { ruleRequired } from "/@/utils/validator";
+import api from "/@/api/modules/apiHub";
 
-const emit = defineEmits(['refresh'])
+const emit = defineEmits(["refresh"]);
 
-const showDialog = ref(false)
-const formRef = ref()
-const groupOptions = ref([])
+const showDialog = ref(false);
+const formRef = ref();
+const groupOptions = ref<any[]>([]);
+let treeData: any[] = [];
 
 // 定义基础表单数据
 const baseForm = {
-	id: undefined,
-	groupKey: '',
-	name: '',
-	parentKey: '',
-	sort: 0,
-	description: ''
-}
+  id: undefined,
+  groupKey: "",
+  name: "",
+  parentId: "",
+  sort: 0,
+  description: "",
+};
 
-const formData = reactive({...baseForm})
+const formData = reactive({ ...baseForm });
 
 // 表单验证规则
 const ruleForm = {
-	name: [ruleRequired('分组名称不能为空', 'change')]
-}
+  name: [ruleRequired("分组名称不能为空", "change")],
+};
 
 // 获取分组树形结构
-const getGroupTree = async () => {
-	try {
-		// 调用API获取分组树
-		const res = await request({
-			url: '/api_group/tree',
-			method: 'get'
-		})
-		return res.data?.list || []
-	} catch (error) {
-		ElMessage.error('获取分组树失败')
-		return []
-	}
-}
-
-// 添加分组
-const addGroup = async (data) => {
-	return request({
-		url: '/api_group/add',
-		method: 'post',
-		data
-	})
-}
-
-// 编辑分组
-const editGroup = async (data) => {
-	return request({
-		url: '/api_group/edit',
-		method: 'post',
-		data
-	})
-}
+api.group.tree().then((res: any) => {
+  treeData = res.list || [];
+  groupOptions.value = res.list || [];
+});
 
 // 提交表单
 const onSubmit = async () => {
-	try {
-		// 表单验证
-		await formRef.value.validate()
-
-		// 准备提交数据
-		const submitData = JSON.parse(JSON.stringify(formData))
-
-		// 如果没有设置groupKey,生成一个基于名称的唯一标识
-		if (!submitData.groupKey) {
-			submitData.groupKey = 'group_' + Date.now() + '_' + Math.floor(Math.random() * 1000)
-		}
-
-		// 处理空的parent_key,将空字符串设置为null
-		if (submitData.parentKey === '') {
-			submitData.parentKey = null
-		}
-
-		// 调用API
-		if (submitData.id) {
-			// 编辑模式
-			await editGroup(submitData)
-		} else {
-			// 新增模式
-			await addGroup(submitData)
-		}
-
-		ElMessage.success('操作成功')
-		resetForm()
-		showDialog.value = false
-		emit('refresh')
-	} catch (error) {
-		ElMessage.error(error.message || '操作失败')
-	}
-}
+  await formRef.value.validate();
+
+  // 准备提交数据
+  const submitData = JSON.parse(JSON.stringify(formData));
+
+  // 如果没有设置groupKey,生成一个基于名称的唯一标识
+  if (!submitData.groupKey) {
+    submitData.groupKey = "group_" + Date.now() + "_" + Math.floor(Math.random() * 1000);
+  }
+
+  // 处理空的parent_key,将空字符串设置为null
+  if (submitData.parentId === "") {
+    submitData.parentId = null;
+  }
+
+  // 调用API
+  if (submitData.id) {
+    // 编辑模式
+    await api.group.edit(submitData);
+  } else {
+    // 新增模式
+    await api.group.add(submitData);
+  }
+
+  ElMessage.success("操作成功");
+  resetForm();
+  showDialog.value = false;
+  emit("refresh");
+};
 
 // 重置表单
 const resetForm = () => {
-	Object.assign(formData, {...baseForm})
-	formRef.value && formRef.value.resetFields()
-}
+  Object.assign(formData, { ...baseForm });
+  formRef.value && formRef.value.resetFields();
+};
 
 // 取消操作
 const cancel = () => {
-	resetForm()
-	showDialog.value = false
-}
+  resetForm();
+  showDialog.value = false;
+};
 
 // 加载分组选项
-const loadGroupOptions = async (excludeId) => {
-	const treeData = await getGroupTree()
-
-	// 如果是编辑模式,需要排除当前分组及其子分组
-	if (excludeId) {
-		const filterTree = (nodes) => {
-			return nodes.filter(node => {
-				if (node.id === excludeId) {
-					return false
-				}
-				if (node.children && node.children.length) {
-					node.children = filterTree(node.children)
-				}
-				return true
-			})
-		}
-
-		groupOptions.value = filterTree(treeData)
-	} else {
-		groupOptions.value = treeData
-	}
-}
+const loadGroupOptions = async (excludeId?: string) => {
+  // 如果是编辑模式,需要排除当前分组及其子分组
+  if (excludeId) {
+    const filterTree = (nodes: any[]) => {
+      return nodes.filter((node) => {
+        if (node.Id === excludeId) {
+          return false;
+        }
+        if (node.Children && node.Children.length) {
+          node.Children = filterTree(node.Children);
+        }
+        return true;
+      });
+    };
+
+    groupOptions.value = filterTree(treeData);
+  } else {
+    groupOptions.value = treeData;
+  }
+};
 
 // 打开对话框
-const open = async (row?: any, parentKey?: string) => {
-	resetForm()
-	showDialog.value = true
-
-	// 加载分组选项
-	await loadGroupOptions(row?.id)
-
-	if (row) {
-		// 编辑模式,填充表单数据
-		nextTick(() => {
-			Object.assign(formData, JSON.parse(JSON.stringify(row)))
-		})
-	} else if (parentKey) {
-		// 新增子分组模式
-		formData.parentKey = parentKey
-	}
-}
-
-defineExpose({ open })
+const open = async (row?: any, parentId?: string) => {
+  resetForm();
+  showDialog.value = true;
+
+  // 加载分组选项
+  await loadGroupOptions(row?.Id);
+
+  if (row) {
+    // 编辑模式,填充表单数据
+    nextTick(() => {
+      formData.id = row.Id;
+      formData.groupKey = row.GroupKey;
+      formData.name = row.Name;
+      formData.parentId = row.ParentId;
+      formData.sort = row.Sort;
+      formData.description = row.Description;
+    });
+  } else if (parentId) {
+    // 新增子分组模式
+    formData.parentId = parentId;
+  }
+};
+
+defineExpose({ open });
 </script>
 
 <style scoped>
 .dialog-footer {
-	display: flex;
-	justify-content: flex-end;
+  display: flex;
+  justify-content: flex-end;
 }
 </style>

+ 212 - 267
src/views/apihub/component/test.vue

@@ -1,318 +1,263 @@
 <template>
-	<el-dialog
-		class="api-test"
-		v-model="showDialog"
-		title="测试API"
-		width="800px"
-		:close-on-click-modal="false"
-		:close-on-press-escape="false"
-	>
-		<el-descriptions :column="2" border>
-			<el-descriptions-item label="API名称" :span="2">{{ apiData.name }}</el-descriptions-item>
-			<el-descriptions-item label="API路径" :span="2">{{ apiData.path }}</el-descriptions-item>
-			<el-descriptions-item label="请求方法">
-				<el-tag :type="getMethodTagType(apiData.method)" size="small">{{ apiData.method }}</el-tag>
-			</el-descriptions-item>
-			<el-descriptions-item label="数据源">{{ apiData.dataSourceName }}</el-descriptions-item>
-		</el-descriptions>
-		
-		<div class="section-title">参数设置</div>
-		<el-form :model="testParams" label-width="120px" v-if="apiData.parameters && apiData.parameters.length">
-			<el-form-item 
-				v-for="param in apiData.parameters" 
-				:key="param.name" 
-				:label="param.name" 
-				:required="param.required"
-			>
-				<el-input 
-					v-if="param.type === 'string'" 
-					v-model="testParams[param.name]" 
-					:placeholder="getParamPlaceholder(param)"
-				></el-input>
-				<el-input-number 
-					v-else-if="param.type === 'int' || param.type === 'float'" 
-					v-model="testParams[param.name]" 
-					:placeholder="getParamPlaceholder(param)"
-				></el-input-number>
-				<el-switch 
-					v-else-if="param.type === 'bool'" 
-					v-model="testParams[param.name]" 
-					:active-value="true" 
-					:inactive-value="false"
-				></el-switch>
-				<el-input 
-					v-else 
-					v-model="testParams[param.name]" 
-					:placeholder="getParamPlaceholder(param)"
-				></el-input>
-				<div class="param-desc" v-if="param.description">{{ param.description }}</div>
-			</el-form-item>
-		</el-form>
-		<el-empty description="该API没有定义参数" v-else></el-empty>
-		
-		<div class="section-title">测试结果</div>
-		<div v-if="!testResult.success && !loading" class="test-placeholder">点击下方"执行测试"按钮开始测试</div>
-		<div v-else>
-			<el-alert
-				:title="testResult.message"
-				:type="testResult.success ? 'success' : 'error'"
-				:closable="false"
-				show-icon
-			></el-alert>
-			
-			<div v-if="testResult.success && testResult.data" class="result-container">
-				<el-tabs v-model="activeTab">
-					<el-tab-pane label="结果数据" name="data">
-						<pre class="result-json">{{ formatJson(testResult.data) }}</pre>
-					</el-tab-pane>
-					<el-tab-pane label="原始响应" name="raw">
-						<pre class="result-json">{{ formatJson(testResult) }}</pre>
-					</el-tab-pane>
-				</el-tabs>
-			</div>
-		</div>
-		
-		<template #footer>
-			<div class="dialog-footer">
-				<el-button @click="closeDialog">关闭</el-button>
-				<el-button type="primary" @click="runTest" :loading="loading">执行测试</el-button>
-			</div>
-		</template>
-	</el-dialog>
+  <el-dialog class="api-test" v-model="showDialog" title="测试API" width="800px" :close-on-click-modal="false" :close-on-press-escape="false">
+    <el-descriptions :column="2" border>
+      <el-descriptions-item label="API名称" :span="2">{{ apiData.name }}</el-descriptions-item>
+      <el-descriptions-item label="API路径" :span="2">{{ apiData.path }}</el-descriptions-item>
+      <el-descriptions-item label="请求方法">
+        <el-tag :type="getMethodTagType(apiData.method)" size="small">{{ apiData.method }}</el-tag>
+      </el-descriptions-item>
+      <el-descriptions-item label="数据源">{{ apiData.dataSourceName }}</el-descriptions-item>
+    </el-descriptions>
+
+    <div class="section-title">参数设置</div>
+    <el-form :model="testParams" label-width="120px" v-if="apiData.parameters && apiData.parameters.length">
+      <el-form-item v-for="param in apiData.parameters" :key="param.name" :label="param.name" :required="param.required">
+        <el-input v-if="param.type === 'string'" v-model="testParams[param.name]" :placeholder="getParamPlaceholder(param)"></el-input>
+        <el-input-number v-else-if="param.type === 'int' || param.type === 'float'" v-model="testParams[param.name]" :placeholder="getParamPlaceholder(param)"></el-input-number>
+        <el-switch v-else-if="param.type === 'bool'" v-model="testParams[param.name]" :active-value="true" :inactive-value="false"></el-switch>
+        <el-input v-else v-model="testParams[param.name]" :placeholder="getParamPlaceholder(param)"></el-input>
+        <div class="param-desc" v-if="param.description">{{ param.description }}</div>
+      </el-form-item>
+    </el-form>
+    <el-empty description="该API没有定义参数" v-else></el-empty>
+
+    <div class="section-title">测试结果</div>
+    <div v-if="!testResult.success && !loading" class="test-placeholder">点击下方"执行测试"按钮开始测试</div>
+    <div v-else>
+      <el-alert :title="testResult.message" :type="testResult.success ? 'success' : 'error'" :closable="false" show-icon></el-alert>
+      <div v-if="testResult.success && testResult.data" class="result-container">
+        <el-tabs v-model="activeTab">
+          <el-tab-pane label="结果数据" name="data">
+            <pre class="result-json">{{ formatJson(testResult.data) }}</pre>
+          </el-tab-pane>
+          <el-tab-pane label="原始响应" name="raw">
+            <pre class="result-json">{{ formatJson(testResult) }}</pre>
+          </el-tab-pane>
+        </el-tabs>
+      </div>
+    </div>
+
+    <template #footer>
+      <div class="dialog-footer">
+        <el-button @click="closeDialog">关闭</el-button>
+        <el-button type="primary" @click="runTest" :loading="loading">执行测试</el-button>
+      </div>
+    </template>
+  </el-dialog>
 </template>
 
 <script lang="ts" setup>
-import { ref, reactive, watch } from 'vue'
+import { ref, reactive, watch } from "vue";
+import apiHub from "/@/api/modules/apiHub";
 
-const showDialog = ref(false)
-const loading = ref(false)
-const activeTab = ref('data')
+const showDialog = ref(false);
+const loading = ref(false);
+const activeTab = ref("data");
 
 // API数据
-const apiData = reactive({
-	id: undefined,
-	name: '',
-	path: '',
-	method: '',
-	dataSourceId: undefined,
-	dataSourceName: '',
-	sqlType: '',
-	sqlContent: '',
-	parameters: [],
-	returnFormat: '',
-	version: '',
-	status: '',
-	plugins: [],
-	description: '',
-	createdAt: '',
-	updatedAt: ''
-})
+const apiData = reactive<any>({
+  id: null,
+  name: "",
+  path: "",
+  method: "",
+  dataSourceId: null,
+  dataSourceName: "",
+  sqlType: "",
+  sqlContent: "",
+  parameters: [],
+  returnFormat: "",
+  version: "",
+  status: "",
+  plugins: [],
+  description: "",
+  createdAt: "",
+  updatedAt: "",
+});
 
 // 测试参数
-const testParams = reactive({})
+const testParams = reactive<any>({});
 
 // 测试结果
-const testResult = reactive({
-	success: false,
-	message: '',
-	data: null
-})
+const testResult = reactive<any>({
+  success: false,
+  message: "",
+  data: null,
+});
 
 // 监听API数据变化,初始化测试参数
-watch(() => apiData.parameters, (newParams) => {
-	// 清空测试参数
-	Object.keys(testParams).forEach(key => {
-		delete testParams[key]
-	})
-	
-	// 根据参数定义初始化测试参数
-	if (newParams && newParams.length) {
-		newParams.forEach(param => {
-			// 如果有默认值,使用默认值
-			if (param.defaultValue !== undefined && param.defaultValue !== '') {
-				// 根据类型转换默认值
-				if (param.type === 'int') {
-					testParams[param.name] = parseInt(param.defaultValue)
-				} else if (param.type === 'float') {
-					testParams[param.name] = parseFloat(param.defaultValue)
-				} else if (param.type === 'bool') {
-					testParams[param.name] = param.defaultValue === 'true'
-				} else {
-					testParams[param.name] = param.defaultValue
-				}
-			} else {
-				// 否则设置为空值
-				if (param.type === 'int' || param.type === 'float') {
-					testParams[param.name] = undefined
-				} else if (param.type === 'bool') {
-					testParams[param.name] = false
-				} else {
-					testParams[param.name] = ''
-				}
-			}
-		})
-	}
-}, { deep: true })
+watch(
+  () => apiData.parameters,
+  (newParams) => {
+    // 清空测试参数
+    Object.keys(testParams).forEach((key) => {
+      delete testParams[key];
+    });
+
+    // 根据参数定义初始化测试参数
+    if (newParams && newParams.length) {
+      newParams.forEach((param: any) => {
+        // 如果有默认值,使用默认值
+        if (param.defaultValue !== undefined && param.defaultValue !== "") {
+          // 根据类型转换默认值
+          if (param.type === "int") {
+            testParams[param.name] = parseInt(param.defaultValue);
+          } else if (param.type === "float") {
+            testParams[param.name] = parseFloat(param.defaultValue);
+          } else if (param.type === "bool") {
+            testParams[param.name] = param.defaultValue === "true";
+          } else {
+            testParams[param.name] = param.defaultValue;
+          }
+        } else {
+          // 否则设置为空值
+          if (param.type === "int" || param.type === "float") {
+            testParams[param.name] = null;
+          } else if (param.type === "bool") {
+            testParams[param.name] = false;
+          } else {
+            testParams[param.name] = "";
+          }
+        }
+      });
+    }
+  },
+  { deep: true }
+);
 
 // 根据请求方法返回不同的标签类型
 const getMethodTagType = (method: string) => {
-	switch (method?.toUpperCase()) {
-		case 'GET':
-			return 'success'
-		case 'POST':
-			return 'primary'
-		case 'PUT':
-			return 'warning'
-		case 'DELETE':
-			return 'danger'
-		default:
-			return 'info'
-	}
-}
+  switch (method?.toUpperCase()) {
+    case "GET":
+      return "success";
+    case "POST":
+      return "primary";
+    case "PUT":
+      return "warning";
+    case "DELETE":
+      return "danger";
+    default:
+      return "info";
+  }
+};
 
 // 获取参数占位符文本
-const getParamPlaceholder = (param) => {
-	if (param.required) {
-		return `请输入${param.description || param.name}(必填)`
-	}
-	return `请输入${param.description || param.name}(选填)`
-}
+const getParamPlaceholder = (param: any) => {
+  if (param.required) {
+    return `请输入${param.description || param.name}(必填)`;
+  }
+  return `请输入${param.description || param.name}(选填)`;
+};
 
 // 格式化JSON
-const formatJson = (json) => {
-	try {
-		return JSON.stringify(json, null, 2)
-	} catch (e) {
-		return json
-	}
-}
+const formatJson = (json: any) => {
+  try {
+    return JSON.stringify(json, null, 2);
+  } catch (e) {
+    return json;
+  }
+};
 
 // 关闭对话框
 const closeDialog = () => {
-	showDialog.value = false
-}
+  showDialog.value = false;
+};
 
 // 执行测试
 const runTest = async () => {
-	// 重置测试结果
-	testResult.success = false
-	testResult.message = ''
-	testResult.data = null
-	
-	loading.value = true
-	
-	try {
-		// 实际使用时,应该调用API进行测试
-		// const res = await api.apihub.test({ 
-		//   id: apiData.id,
-		//   parameters: testParams
-		// })
-		
-		// 模拟API调用
-		await new Promise(resolve => setTimeout(resolve, 1000))
-		
-		// 模拟测试结果
-		testResult.success = true
-		testResult.message = '测试成功'
-		
-		// 根据API类型模拟不同的测试数据
-		if (apiData.path.includes('users')) {
-			if (apiData.method === 'GET') {
-				testResult.data = {
-					id: 1,
-					username: 'admin',
-					email: 'admin@example.com',
-					created_at: '2025-05-01 10:00:00'
-				}
-			} else {
-				testResult.data = {
-					id: 10,
-					username: testParams.username || 'newuser',
-					email: testParams.email || 'newuser@example.com',
-					created_at: '2025-05-06 11:00:00'
-				}
-			}
-		} else {
-			testResult.data = {
-				result: '测试数据',
-				timestamp: new Date().toISOString()
-			}
-		}
-	} catch (error) {
-		testResult.success = false
-		testResult.message = error.message || '测试失败'
-	} finally {
-		loading.value = false
-	}
-}
+  // 重置测试结果
+  testResult.success = false;
+  testResult.message = "";
+  testResult.data = null;
+
+  loading.value = true;
+
+  apiHub
+    .test({
+      id: apiData.id,
+      parameters: testParams,
+    })
+    .then((res: any) => {
+      testResult.success = true;
+      testResult.message = "测试成功";
+
+      testResult.data = res;
+    })
+    .catch((error: any) => {
+      testResult.success = false;
+      testResult.message = error?.message || "测试失败";
+    })
+    .finally(() => {
+      loading.value = false;
+    });
+};
 
 // 打开对话框
 const open = async (row: any) => {
-	// 清空数据
-	Object.keys(apiData).forEach(key => {
-		apiData[key] = undefined
-	})
-	
-	// 重置测试结果
-	testResult.success = false
-	testResult.message = ''
-	testResult.data = null
-	
-	showDialog.value = true
-	
-	// 实际使用时,应该调用API获取详细信息
-	// const res = await api.apihub.getDetail({ id: row.id })
-	// Object.assign(apiData, res)
-	
-	// 这里模拟直接使用传入的行数据
-	Object.assign(apiData, row)
-}
+  // 清空数据
+  Object.keys(apiData).forEach((key) => {
+    apiData[key] = undefined;
+  });
+
+  // 重置测试结果
+  testResult.success = false;
+  testResult.message = "";
+  testResult.data = null;
+
+  showDialog.value = true;
+
+  // 实际使用时,应该调用API获取详细信息
+  // const res = await apiHub.get(row.id)
+  // Object.assign(apiData, res)
+
+  // 这里模拟直接使用传入的行数据
+  Object.assign(apiData, row);
+};
 
-defineExpose({ open })
+defineExpose({ open });
 </script>
 
 <style scoped>
 .dialog-footer {
-	display: flex;
-	justify-content: flex-end;
+  display: flex;
+  justify-content: flex-end;
 }
 
 .section-title {
-	font-weight: bold;
-	margin: 20px 0 10px;
-	font-size: 16px;
-	border-left: 4px solid #409EFF;
-	padding-left: 10px;
+  font-weight: bold;
+  margin: 20px 0 10px;
+  font-size: 16px;
+  border-left: 4px solid #409eff;
+  padding-left: 10px;
 }
 
 .param-desc {
-	font-size: 12px;
-	color: #909399;
-	margin-top: 5px;
+  font-size: 12px;
+  color: #909399;
+  margin-top: 5px;
 }
 
 .test-placeholder {
-	text-align: center;
-	color: #909399;
-	padding: 20px;
-	background-color: #f5f7fa;
-	border-radius: 4px;
+  text-align: center;
+  color: #909399;
+  padding: 20px;
+  background-color: #f5f7fa;
+  border-radius: 4px;
 }
 
 .result-container {
-	margin-top: 15px;
-	border: 1px solid #e4e7ed;
-	border-radius: 4px;
+  margin-top: 15px;
+  border: 1px solid #e4e7ed;
+  border-radius: 4px;
 }
 
 .result-json {
-	background-color: #f5f7fa;
-	padding: 10px;
-	font-family: monospace;
-	white-space: pre-wrap;
-	word-break: break-all;
-	margin: 0;
-	max-height: 300px;
-	overflow: auto;
+  background-color: #f5f7fa;
+  padding: 10px;
+  font-family: monospace;
+  white-space: pre-wrap;
+  word-break: break-all;
+  margin: 0;
+  max-height: 300px;
+  overflow: auto;
 }
 </style>

+ 124 - 131
src/views/apihub/component/view.vue

@@ -1,162 +1,155 @@
 <template>
-	<el-dialog
-		class="api-view"
-		v-model="showDialog"
-		title="API详情"
-		width="800px"
-		:close-on-click-modal="false"
-		:close-on-press-escape="false"
-	>
-		<el-descriptions :column="2" border>
-			<el-descriptions-item label="API名称" :span="2">{{ apiData.name }}</el-descriptions-item>
-			<el-descriptions-item label="API路径" :span="2">{{ apiData.path }}</el-descriptions-item>
-			<el-descriptions-item label="请求方法">
-				<el-tag :type="getMethodTagType(apiData.method)" size="small">{{ apiData.method }}</el-tag>
-			</el-descriptions-item>
-			<el-descriptions-item label="状态">
-				<el-tag size="small" v-if="apiData.status === 'Draft'">草稿</el-tag>
-				<el-tag size="small" type="success" v-else-if="apiData.status === 'Published'">已发布</el-tag>
-				<el-tag size="small" type="info" v-else-if="apiData.status === 'Deprecated'">已废弃</el-tag>
-				<span v-else>{{ apiData.status }}</span>
-			</el-descriptions-item>
-			<el-descriptions-item label="数据源">{{ apiData.dataSourceName }}</el-descriptions-item>
-			<el-descriptions-item label="版本">{{ apiData.version }}</el-descriptions-item>
-			<el-descriptions-item label="SQL类型">
-				<el-tag size="small" type="info" v-if="apiData.sqlType === 'query'">查询</el-tag>
-				<el-tag size="small" type="warning" v-else-if="apiData.sqlType === 'procedure'">存储过程</el-tag>
-				<span v-else>{{ apiData.sqlType }}</span>
-			</el-descriptions-item>
-			<el-descriptions-item label="返回格式">{{ apiData.returnFormat }}</el-descriptions-item>
-			<el-descriptions-item label="SQL内容" :span="2">
-				<div class="code-block">{{ apiData.sqlContent }}</div>
-			</el-descriptions-item>
-			<el-descriptions-item label="描述" :span="2">{{ apiData.description || '暂无描述' }}</el-descriptions-item>
-			<el-descriptions-item label="创建时间">{{ apiData.createdAt }}</el-descriptions-item>
-			<el-descriptions-item label="更新时间">{{ apiData.updatedAt }}</el-descriptions-item>
-		</el-descriptions>
-		
-		<div class="section-title">参数定义</div>
-		<el-table :data="apiData.parameters || []" style="width: 100%" border v-if="apiData.parameters && apiData.parameters.length">
-			<el-table-column prop="name" label="参数名" width="150"></el-table-column>
-			<el-table-column prop="type" label="类型" width="100"></el-table-column>
-			<el-table-column label="必填" width="80">
-				<template #default="scope">
-					<el-tag size="small" type="success" v-if="scope.row.required">是</el-tag>
-					<el-tag size="small" type="info" v-else>否</el-tag>
-				</template>
-			</el-table-column>
-			<el-table-column prop="defaultValue" label="默认值" width="120"></el-table-column>
-			<el-table-column prop="description" label="描述"></el-table-column>
-		</el-table>
-		<el-empty description="暂无参数" v-else></el-empty>
-		
-		<div class="section-title" v-if="apiData.plugins && apiData.plugins.length">关联插件</div>
-		<el-table :data="apiData.plugins || []" style="width: 100%" border v-if="apiData.plugins && apiData.plugins.length">
-			<el-table-column prop="pluginId" label="插件ID" width="100"></el-table-column>
-			<el-table-column prop="config" label="插件配置"></el-table-column>
-		</el-table>
-		
-		<template #footer>
-			<div class="dialog-footer">
-				<el-button @click="closeDialog">关闭</el-button>
-				<el-button type="primary" @click="testApi">测试API</el-button>
-			</div>
-		</template>
-	</el-dialog>
+  <el-dialog class="api-view" v-model="showDialog" title="API详情" width="800px" :close-on-click-modal="false" :close-on-press-escape="false">
+    <el-descriptions :column="2" border>
+      <el-descriptions-item label="API名称" :span="2">{{ apiData.name }}</el-descriptions-item>
+      <el-descriptions-item label="API路径" :span="2">{{ apiData.path }}</el-descriptions-item>
+      <el-descriptions-item label="请求方法">
+        <el-tag :type="getMethodTagType(apiData.method)" size="small">{{ apiData.method }}</el-tag>
+      </el-descriptions-item>
+      <el-descriptions-item label="状态">
+        <el-tag size="small" v-if="apiData.status === 'Draft'">草稿</el-tag>
+        <el-tag size="small" type="success" v-else-if="apiData.status === 'Published'">已发布</el-tag>
+        <el-tag size="small" type="info" v-else-if="apiData.status === 'Deprecated'">已废弃</el-tag>
+        <span v-else>{{ apiData.status }}</span>
+      </el-descriptions-item>
+      <el-descriptions-item label="数据源">{{ apiData.dataSourceName }}</el-descriptions-item>
+      <el-descriptions-item label="版本">{{ apiData.version }}</el-descriptions-item>
+      <el-descriptions-item label="SQL类型">
+        <el-tag size="small" type="info" v-if="apiData.sqlType === 'query'">查询</el-tag>
+        <el-tag size="small" type="warning" v-else-if="apiData.sqlType === 'procedure'">存储过程</el-tag>
+        <span v-else>{{ apiData.sqlType }}</span>
+      </el-descriptions-item>
+      <el-descriptions-item label="返回格式">{{ apiData.returnFormat }}</el-descriptions-item>
+      <el-descriptions-item label="SQL内容" :span="2">
+        <div class="code-block">{{ apiData.sqlContent }}</div>
+      </el-descriptions-item>
+      <el-descriptions-item label="描述" :span="2">{{ apiData.description || "暂无描述" }}</el-descriptions-item>
+      <el-descriptions-item label="创建时间">{{ apiData.createdAt }}</el-descriptions-item>
+      <el-descriptions-item label="更新时间">{{ apiData.updatedAt }}</el-descriptions-item>
+    </el-descriptions>
+
+    <div class="section-title">参数定义</div>
+    <el-table :data="apiData.parameters || []" style="width: 100%" border v-if="apiData.parameters && apiData.parameters.length">
+      <el-table-column prop="name" label="参数名" width="150"></el-table-column>
+      <el-table-column prop="type" label="类型" width="100"></el-table-column>
+      <el-table-column label="必填" width="80">
+        <template #default="scope">
+          <el-tag size="small" type="success" v-if="scope.row.required">是</el-tag>
+          <el-tag size="small" type="info" v-else>否</el-tag>
+        </template>
+      </el-table-column>
+      <el-table-column prop="defaultValue" label="默认值" width="120"></el-table-column>
+      <el-table-column prop="description" label="描述"></el-table-column>
+    </el-table>
+    <el-empty description="暂无参数" v-else></el-empty>
+
+    <div class="section-title" v-if="apiData.plugins && apiData.plugins.length">关联插件</div>
+    <el-table :data="apiData.plugins || []" style="width: 100%" border v-if="apiData.plugins && apiData.plugins.length">
+      <el-table-column prop="pluginId" label="插件ID" width="100"></el-table-column>
+      <el-table-column prop="config" label="插件配置"></el-table-column>
+    </el-table>
+
+    <template #footer>
+      <div class="dialog-footer">
+        <el-button @click="closeDialog">关闭</el-button>
+        <el-button type="primary" @click="testApi">测试API</el-button>
+      </div>
+    </template>
+  </el-dialog>
 </template>
 
 <script lang="ts" setup>
-import { ref, reactive } from 'vue'
+import { ref, reactive } from "vue";
 
-const emit = defineEmits(['test'])
-const showDialog = ref(false)
+const emit = defineEmits(["test"]);
+const showDialog = ref(false);
 const apiData = reactive({
-	id: undefined,
-	name: '',
-	path: '',
-	method: '',
-	dataSourceId: undefined,
-	dataSourceName: '',
-	sqlType: '',
-	sqlContent: '',
-	parameters: [],
-	returnFormat: '',
-	version: '',
-	status: '',
-	plugins: [],
-	description: '',
-	createdAt: '',
-	updatedAt: ''
-})
+  id: undefined,
+  name: "",
+  path: "",
+  method: "",
+  dataSourceId: undefined,
+  dataSourceName: "",
+  sqlType: "",
+  sqlContent: "",
+  parameters: [],
+  returnFormat: "",
+  version: "",
+  status: "",
+  plugins: [],
+  description: "",
+  createdAt: "",
+  updatedAt: "",
+});
 
 // 根据请求方法返回不同的标签类型
 const getMethodTagType = (method: string) => {
-	switch (method?.toUpperCase()) {
-		case 'GET':
-			return 'success'
-		case 'POST':
-			return 'primary'
-		case 'PUT':
-			return 'warning'
-		case 'DELETE':
-			return 'danger'
-		default:
-			return 'info'
-	}
-}
+  switch (method?.toUpperCase()) {
+    case "GET":
+      return "success";
+    case "POST":
+      return "primary";
+    case "PUT":
+      return "warning";
+    case "DELETE":
+      return "danger";
+    default:
+      return "info";
+  }
+};
 
 // 关闭对话框
 const closeDialog = () => {
-	showDialog.value = false
-}
+  showDialog.value = false;
+};
 
 // 测试API
 const testApi = () => {
-	closeDialog()
-	emit('test', apiData)
-}
+  closeDialog();
+  emit("test", apiData);
+};
 
 // 打开对话框
 const open = async (row: any) => {
-	// 清空数据
-	Object.keys(apiData).forEach(key => {
-		apiData[key] = undefined
-	})
-	
-	showDialog.value = true
-	
-	// 实际使用时,应该调用API获取详细信息
-	// const res = await api.apihub.getDetail({ id: row.id })
-	// Object.assign(apiData, res)
-	
-	// 这里模拟直接使用传入的行数据
-	Object.assign(apiData, row)
-}
+  // 清空数据
+  Object.keys(apiData).forEach((key) => {
+    apiData[key] = undefined;
+  });
+
+  showDialog.value = true;
+
+  // 实际使用时,应该调用API获取详细信息
+  // const res = await api.apihub.getDetail({ id: row.id })
+  // Object.assign(apiData, res)
+
+  // 这里模拟直接使用传入的行数据
+  Object.assign(apiData, row);
+};
 
-defineExpose({ open })
+defineExpose({ open });
 </script>
 
 <style scoped>
 .dialog-footer {
-	display: flex;
-	justify-content: flex-end;
+  display: flex;
+  justify-content: flex-end;
 }
 
 .section-title {
-	font-weight: bold;
-	margin: 20px 0 10px;
-	font-size: 16px;
-	border-left: 4px solid #409EFF;
-	padding-left: 10px;
+  font-weight: bold;
+  margin: 20px 0 10px;
+  font-size: 16px;
+  border-left: 4px solid #409eff;
+  padding-left: 10px;
 }
 
 .code-block {
-	background-color: #f5f7fa;
-	border: 1px solid #e4e7ed;
-	border-radius: 4px;
-	padding: 10px;
-	font-family: monospace;
-	white-space: pre-wrap;
-	word-break: break-all;
+  background-color: #f5f7fa;
+  border: 1px solid #e4e7ed;
+  border-radius: 4px;
+  padding: 10px;
+  font-family: monospace;
+  white-space: pre-wrap;
+  word-break: break-all;
 }
 </style>

+ 154 - 243
src/views/apihub/datasource.vue

@@ -1,98 +1,67 @@
 <template>
   <div class="page">
     <el-card shadow="never">
-      <template #header>
-        <div class="card-header">
-          <span>数据源管理</span>
-        </div>
-      </template>
-      
       <!-- 搜索表单 -->
       <el-form :model="params" inline ref="queryRef">
         <el-form-item label="数据源名称" prop="keyWord">
           <el-input v-model="params.keyWord" placeholder="请输入数据源名称" clearable style="width: 200px" @keyup.enter.native="getList(1)" />
         </el-form-item>
-        <el-form-item label="日期范围" prop="dateRange">
-          <el-date-picker
-            v-model="params.dateRange"
-            type="daterange"
-            range-separator="至"
-            start-placeholder="开始日期"
-            end-placeholder="结束日期"
-            value-format="YYYY-MM-DD"
-            style="width: 240px"
-          />
-        </el-form-item>
+        <!--        <el-form-item label="日期范围" prop="dateRange">-->
+        <!--          <el-date-picker v-model="params.dateRange" type="daterange" range-separator="至" start-placeholder="开始日期" end-placeholder="结束日期" value-format="YYYY-MM-DD" style="width: 240px" />-->
+        <!--        </el-form-item>-->
         <el-form-item>
           <el-button type="primary" @click="getList(1)">
             <el-icon><ele-Search /></el-icon>查询
           </el-button>
-          <el-button @click="resetQuery">
-            <el-icon><ele-Refresh /></el-icon>重置
-          </el-button>
+<!--          <el-button @click="resetQuery">-->
+<!--            <el-icon><ele-Refresh /></el-icon>重置-->
+<!--          </el-button>-->
           <el-button type="primary" @click="addOrEdit()" v-auth="'add_datasource'">
             <el-icon><ele-Plus /></el-icon>新增
           </el-button>
         </el-form-item>
       </el-form>
-      
+
       <!-- 数据表格 -->
       <el-table :data="tableData" style="width: 100%" v-loading="loading" row-key="id">
-        <el-table-column type="selection" width="55" align="center" />
+        <el-table-column type="selection" width="50" align="center" />
         <el-table-column prop="id" label="ID" width="80" align="center" />
-        <el-table-column prop="name" label="数据源名称" min-width="120" show-overflow-tooltip />
+        <el-table-column prop="name" label="数据源名称" min-width="150" show-overflow-tooltip />
         <el-table-column prop="type" label="数据库类型" width="120" />
-        <el-table-column prop="host" label="主机地址" min-width="120" show-overflow-tooltip />
-        <el-table-column prop="port" label="端口" width="80" align="center" />
-        <el-table-column prop="database" label="数据库名" min-width="120" show-overflow-tooltip />
+        <!--        <el-table-column prop="host" label="主机地址" min-width="120" show-overflow-tooltip />-->
+        <!--        <el-table-column prop="port" label="端口" width="80" align="center" />-->
+        <!--        <el-table-column prop="database" label="数据库名" min-width="120" show-overflow-tooltip />-->
         <el-table-column prop="remark" label="备注" min-width="150" show-overflow-tooltip />
         <el-table-column prop="createdAt" label="创建时间" width="160" />
-        <el-table-column label="操作" width="200" fixed="right">
+        <el-table-column label="操作" width="160" align="center" fixed="right">
           <template #default="{ row }">
-            <el-button type="primary" link @click="testConnection(row)" v-auth="'test_datasource'">
-              测试连接
-            </el-button>
-            <el-button type="primary" link @click="addOrEdit(row)" v-auth="'edit_datasource'">
-              编辑
-            </el-button>
-            <el-button type="danger" link @click="deleteDataSource(row)" v-auth="'delete_datasource'">
-              删除
-            </el-button>
+            <el-button type="primary" size="small" text @click="testConnection(row)" v-auth="'test_datasource'"> 测试连接 </el-button>
+            <el-button type="primary" size="small" text @click="addOrEdit(row)" v-auth="'edit_datasource'"> 编辑 </el-button>
+            <el-button type="danger" size="small" text @click="deleteDataSource(row)" v-auth="'delete_datasource'"> 删除 </el-button>
           </template>
         </el-table-column>
       </el-table>
-      
+
       <!-- 分页 -->
       <div class="pagination-container">
-        <el-pagination
-          v-model:current-page="params.pageNum"
-          v-model:page-size="params.pageSize"
-          :page-sizes="[10, 20, 50, 100]"
-          layout="total, sizes, prev, pager, next, jumper"
-          :total="total"
-          @size-change="getList()"
-          @current-change="getList()"
-        />
+        <el-pagination v-model:current-page="params.pageNum" v-model:page-size="params.pageSize" :page-sizes="[10, 20, 50, 100]" layout="total, sizes, prev, pager, next, jumper" :total="params.total" @size-change="getList()" @current-change="getList()" />
       </div>
     </el-card>
-    
+
     <!-- 数据源表单对话框 -->
-    <el-dialog
-      v-model="dialogVisible"
-      :title="formData.id ? '编辑数据源' : '新增数据源'"
-      width="600px"
-      :close-on-click-modal="false"
-      :close-on-press-escape="false"
-    >
+    <el-dialog v-model="dialogVisible" :title="formData.id ? '编辑数据源' : '新增数据源'" width="600px" :close-on-click-modal="false" :close-on-press-escape="false">
       <el-form ref="formRef" :model="formData" :rules="rules" label-width="120px">
         <el-form-item label="数据源名称" prop="name">
           <el-input v-model="formData.name" placeholder="请输入数据源名称" />
         </el-form-item>
         <el-form-item label="数据库类型" prop="type">
           <el-select v-model="formData.type" placeholder="请选择数据库类型" style="width: 100%">
-            <el-option label="MySQL" value="MySQL" />
-            <el-option label="PostgreSQL" value="PostgreSQL" />
-            <el-option label="SQL Server" value="SQL Server" />
+            <template v-if="database_type.length">
+              <el-option v-for="item in database_type" :key="item.value" :label="item.label" :value="item.value"></el-option>
+            </template>
+            <template v-else>
+              <el-option label="MySQL" value="mysql" />
+            </template>
           </el-select>
         </el-form-item>
         <el-form-item label="主机地址" prop="host">
@@ -133,13 +102,9 @@
         </div>
       </template>
     </el-dialog>
-    
+
     <!-- 测试连接对话框 -->
-    <el-dialog
-      v-model="testDialogVisible"
-      title="测试数据源连接"
-      width="500px"
-    >
+    <el-dialog v-model="testDialogVisible" title="测试数据源连接" width="500px">
       <el-form ref="testFormRef" :model="testFormData" :rules="testRules" label-width="120px">
         <el-form-item label="数据库类型" prop="type">
           <el-select v-model="testFormData.type" placeholder="请选择数据库类型" style="width: 100%">
@@ -175,208 +140,165 @@
 </template>
 
 <script lang="ts" setup>
-import { ref, reactive, onMounted } from 'vue'
-import { ElMessage, ElMessageBox } from 'element-plus'
-import request from '/@/utils/request'
-import { useSearch } from '/@/hooks/useSearch'
+import { ref, reactive, onMounted, getCurrentInstance } from "vue";
+import { ElMessage, ElMessageBox } from "element-plus";
+import { useSearch } from "/@/hooks/useCommon";
+import apiHub from "/@/api/modules/apiHub";
+
+const { proxy } = getCurrentInstance() as any;
+const { database_type } = proxy.useDict("database_type");
 
 // 定义数据源类型
 interface DataSource {
-  id?: number
-  name: string
-  type: string
-  host: string
-  port: number
-  username: string
-  password?: string
-  database: string
-  maxOpenConns?: number
-  maxIdleConns?: number
-  connMaxLifetime?: number
-  remark?: string
-  createdAt?: string
-  updatedAt?: string
+  id?: number;
+  name: string;
+  type: string;
+  host: string;
+  port: number;
+  username: string;
+  password?: string;
+  database: string;
+  maxOpenConns?: number;
+  maxIdleConns?: number;
+  connMaxLifetime?: number;
+  remark?: string;
+  createdAt?: string;
+  updatedAt?: string;
 }
 
 // 表单引用
-const formRef = ref()
-const testFormRef = ref()
-const queryRef = ref()
+const formRef = ref();
+const testFormRef = ref();
+const queryRef = ref();
 
 // 对话框状态
-const dialogVisible = ref(false)
-const testDialogVisible = ref(false)
-
-// 总数
-const total = ref(0)
-
-// 加载状态
-const loading = ref(false)
+const dialogVisible = ref(false);
+const testDialogVisible = ref(false);
 
 // 表单数据
 const formData = reactive<DataSource>({
-  name: '',
-  type: 'MySQL',
-  host: '',
+  name: "",
+  type: "MySQL",
+  host: "",
   port: 3306,
-  username: '',
-  password: '',
-  database: '',
+  username: "",
+  password: "",
+  database: "",
   maxOpenConns: 100,
   maxIdleConns: 10,
   connMaxLifetime: 3600,
-  remark: ''
-})
+  remark: "",
+});
 
 // 测试连接表单数据
 const testFormData = reactive({
-  type: 'MySQL',
-  host: '',
+  type: "MySQL",
+  host: "",
   port: 3306,
-  username: '',
-  password: '',
-  database: ''
-})
+  username: "",
+  password: "",
+  database: "",
+});
 
 // 表单验证规则
 const rules = {
-  name: [{ required: true, message: '请输入数据源名称', trigger: 'blur' }],
-  type: [{ required: true, message: '请选择数据库类型', trigger: 'change' }],
-  host: [{ required: true, message: '请输入主机地址', trigger: 'blur' }],
-  port: [{ required: true, message: '请输入端口', trigger: 'blur' }],
-  username: [{ required: true, message: '请输入用户名', trigger: 'blur' }],
-  password: [{ required: true, message: '请输入密码', trigger: 'blur' }],
-  database: [{ required: true, message: '请输入数据库名', trigger: 'blur' }]
-}
+  name: [{ required: true, message: "请输入数据源名称", trigger: "blur" }],
+  type: [{ required: true, message: "请选择数据库类型", trigger: "change" }],
+  host: [{ required: true, message: "请输入主机地址", trigger: "blur" }],
+  port: [{ required: true, message: "请输入端口", trigger: "blur" }],
+  username: [{ required: true, message: "请输入用户名", trigger: "blur" }],
+  password: [{ required: true, message: "请输入密码", trigger: "blur" }],
+  database: [{ required: true, message: "请输入数据库名", trigger: "blur" }],
+};
 
 // 测试连接表单验证规则
 const testRules = {
-  type: [{ required: true, message: '请选择数据库类型', trigger: 'change' }],
-  host: [{ required: true, message: '请输入主机地址', trigger: 'blur' }],
-  port: [{ required: true, message: '请输入端口', trigger: 'blur' }],
-  username: [{ required: true, message: '请输入用户名', trigger: 'blur' }],
-  password: [{ required: true, message: '请输入密码', trigger: 'blur' }],
-  database: [{ required: true, message: '请输入数据库名', trigger: 'blur' }]
-}
-
-// 数据源列表请求函数
-const dataSourceRequest = (params: any) => {
-  return request({
-    url: '/datasource/list',
-    method: 'get',
-    params
-  })
-}
+  type: [{ required: true, message: "请选择数据库类型", trigger: "change" }],
+  host: [{ required: true, message: "请输入主机地址", trigger: "blur" }],
+  port: [{ required: true, message: "请输入端口", trigger: "blur" }],
+  username: [{ required: true, message: "请输入用户名", trigger: "blur" }],
+  password: [{ required: true, message: "请输入密码", trigger: "blur" }],
+  database: [{ required: true, message: "请输入数据库名", trigger: "blur" }],
+};
 
 // 使用通用搜索钩子
-const { params, tableData, getList } = useSearch<DataSource[]>(
-  dataSourceRequest,
-  'data',
-  (res: any) => {
-    total.value = res.pagination?.total || 0
-    return res.data || []
-  },
-  {
-    keyWord: '',
-    dateRange: [],
-    pageNum: 1,
-    pageSize: 10
-  }
-)
+const { params, tableData, getList, loading } = useSearch<DataSource[]>(apiHub.dataSource.list, "Data", {
+  keyWord: "",
+  dateRange: [],
+});
 
 // 页面加载时获取列表数据
 onMounted(() => {
-  getList(1)
-})
+  getList(1);
+});
 
 // 重置查询表单
 const resetQuery = () => {
-  queryRef.value?.resetFields()
-  getList(1)
-}
+  queryRef.value?.resetFields();
+  getList(1);
+};
 
 // 新增或编辑数据源
 const addOrEdit = (row?: DataSource) => {
   // 重置表单
   Object.assign(formData, {
     id: undefined,
-    name: '',
-    type: 'MySQL',
-    host: '',
+    name: "",
+    type: "MySQL",
+    host: "",
     port: 3306,
-    username: '',
-    password: '',
-    database: '',
+    username: "",
+    password: "",
+    database: "",
     maxOpenConns: 100,
     maxIdleConns: 10,
     connMaxLifetime: 3600,
-    remark: ''
-  })
-  
+    remark: "",
+  });
+
   // 如果是编辑,填充表单数据
   if (row) {
-    Object.assign(formData, { ...row, password: '' })
+    Object.assign(formData, { ...row, password: "" });
   }
-  
+
   // 显示对话框
-  dialogVisible.value = true
-}
+  dialogVisible.value = true;
+};
 
 // 提交表单
 const submitForm = async () => {
   // 表单验证
-  await formRef.value.validate()
-  
-  try {
-    // 根据是否有ID判断是新增还是编辑
-    const url = formData.id ? '/datasource/edit' : '/datasource/add'
-    const method = formData.id ? 'put' : 'post'
-    
-    // 发送请求
-    await request({
-      url,
-      method,
-      data: formData
-    })
-    
-    // 关闭对话框
-    dialogVisible.value = false
-    
-    // 刷新列表
-    getList()
-    
-    // 提示成功
-    ElMessage.success(formData.id ? '编辑成功' : '添加成功')
-  } catch (error) {
-    console.error('提交表单失败:', error)
-  }
-}
+  await formRef.value.validate();
+
+  const theApi = formData.id ? apiHub.dataSource.edit : apiHub.dataSource.add;
+
+  // 发送请求
+  await theApi(formData);
+
+  // 关闭对话框
+  dialogVisible.value = false;
+
+  // 刷新列表
+  getList();
+
+  // 提示成功
+  ElMessage.success(formData.id ? "编辑成功" : "添加成功");
+};
 
 // 删除数据源
 const deleteDataSource = (row: DataSource) => {
-  ElMessageBox.confirm(`确定要删除数据源「${row.name}」吗?`, '警告', {
-    confirmButtonText: '确定',
-    cancelButtonText: '取消',
-    type: 'warning'
-  }).then(async () => {
-    try {
-      // 发送删除请求
-      await request({
-        url: '/datasource/delete',
-        method: 'delete',
-        data: { ids: [row.id] }
-      })
-      
-      // 刷新列表
-      getList()
-      
-      // 提示成功
-      ElMessage.success('删除成功')
-    } catch (error) {
-      console.error('删除失败:', error)
-    }
-  }).catch(() => {})
-}
+  ElMessageBox.confirm(`确定要删除数据源「${row.name}」吗?`, "警告", {
+    confirmButtonText: "确定",
+    cancelButtonText: "取消",
+    type: "warning",
+  })
+    .then(async () => {
+      await apiHub.dataSource.delete([row.id!]);
+      getList();
+      ElMessage.success("删除成功");
+    })
+    .catch(() => {});
+};
 
 // 测试连接(从列表)
 const testConnection = (row: DataSource) => {
@@ -386,19 +308,19 @@ const testConnection = (row: DataSource) => {
     host: row.host,
     port: row.port,
     username: row.username,
-    password: '', // 密码需要重新输入
-    database: row.database
-  })
-  
+    password: "", // 密码需要重新输入
+    database: row.database,
+  });
+
   // 显示测试对话框
-  testDialogVisible.value = true
-}
+  testDialogVisible.value = true;
+};
 
 // 测试连接(从表单)
 const testConnectionForm = async () => {
   // 表单验证
-  await formRef.value.validate()
-  
+  await formRef.value.validate();
+
   // 填充测试表单
   Object.assign(testFormData, {
     type: formData.type,
@@ -406,37 +328,26 @@ const testConnectionForm = async () => {
     port: formData.port,
     username: formData.username,
     password: formData.password,
-    database: formData.database
-  })
-  
+    database: formData.database,
+  });
+
   // 显示测试对话框
-  testDialogVisible.value = true
-}
+  testDialogVisible.value = true;
+};
 
 // 提交测试连接
 const submitTestConnection = async () => {
   // 表单验证
-  await testFormRef.value.validate()
-  
-  try {
-    // 发送测试连接请求
-    const res = await request({
-      url: '/datasource/test',
-      method: 'post',
-      data: testFormData
-    })
-    
-    // 根据测试结果显示提示
-    if (res.data.success) {
-      ElMessage.success(res.data.message || '连接成功')
+  await testFormRef.value.validate();
+
+  await apiHub.dataSource.test(testFormData).then((res: any) => {
+    if (res.success) {
+      ElMessage.success(res.message || "连接成功");
     } else {
-      ElMessage.error(res.data.message || '连接失败')
+      ElMessage.error(res.message || "连接失败");
     }
-  } catch (error) {
-    console.error('测试连接失败:', error)
-    ElMessage.error('测试连接失败')
-  }
-}
+  });
+};
 </script>
 
 <style scoped>

+ 11 - 7
src/views/iot/rule-engine/edit.vue

@@ -4,6 +4,13 @@
       <el-form-item label="名称" prop="name">
         <el-input v-model.trim="formData.name" placeholder="输入名称" />
       </el-form-item>
+      <el-form-item label="根规则链" prop="types">
+        <el-switch v-model="formData.types" :active-value="1" :inactive-value="0" active-text="创建根规则链" inactive-text="创建子规则链"></el-switch>
+      </el-form-item>
+      <el-form-item label="调试模式" prop="status">
+        <el-switch v-model="formData.status" :active-value="2" :inactive-value="0"></el-switch>
+        开启后会覆盖节点的调试模式配置,所有节点会打印调试日志
+      </el-form-item>
       <el-form-item label="说明" prop="expound">
         <el-input v-model="formData.expound" type="textarea" :rows="3" />
       </el-form-item>
@@ -29,10 +36,6 @@ import { v4 as uuid } from "uuid";
 const emit = defineEmits(["getList"]);
 
 const props = defineProps({
-  types: {
-    type: Number,
-    default: 0, // 规则编排是0 数据转发是1
-  },
   model: {
     type: String,
     default: "sagoo-rule", // sagoo-rule 、node-red
@@ -51,10 +54,10 @@ const formRef = ref();
 const baseForm = {
   id: undefined,
   name: "",
-  types: props.types,
-  // types: 0,
+  types: 1,
   flowId: "",
   expound: "",
+  status: 0,
 };
 
 const formData = reactive({
@@ -78,7 +81,8 @@ const onSubmit = async () => {
           ruleChain: {
             id: id,
             name: formData.name,
-            root: true,
+            root: formData.types === 1,
+            debugMode: formData.status === 2,
             additionalInfo: {
               description: formData.expound,
               layoutX: "130",

+ 12 - 6
src/views/iot/rule-engine/index.vue

@@ -14,18 +14,24 @@
       <el-table :data="tableData" style="width: 100%" v-loading="loading">
         <el-table-column type="index" label="序号" width="80" align="center" />
         <el-table-column prop="name" label="名称" show-overflow-tooltip></el-table-column>
+        <el-table-column prop="types" label="规则链类型" width="110" align="center">
+          <template #default="scope">
+            <el-tag type="primary" size="small" v-if="scope.row.types == 1">根规则链</el-tag>
+            <el-tag type="success" size="small" v-else>子规则链</el-tag>
+          </template>
+        </el-table-column>
         <el-table-column prop="expound" label="说明" show-overflow-tooltip></el-table-column>
         <el-table-column prop="createdAt" label="创建时间" min-width="100" align="center"></el-table-column>
-        <el-table-column prop="status" label="状态" width="100" align="center">
+        <!-- <el-table-column prop="status" label="状态" width="100" align="center">
           <template #default="scope">
             <el-tag type="success" size="small" v-if="scope.row.status == 1">已启动</el-tag>
             <el-tag type="info" size="small" v-else>已停止</el-tag>
           </template>
-        </el-table-column>
+        </el-table-column> -->
         <el-table-column label="操作" width="200" align="center">
           <template #default="scope">
-            <el-button size="small" text type="info" v-auth="'startOrStop'" v-if="scope.row.status" @click="setStatus(scope.row, 0)">停止</el-button>
-            <el-button size="small" text type="primary" v-auth="'startOrStop'" v-else @click="setStatus(scope.row, 1)">启动</el-button>
+            <!-- <el-button size="small" text type="info" v-auth="'startOrStop'" v-if="scope.row.status" @click="setStatus(scope.row, 0)">停止</el-button>
+            <el-button size="small" text type="primary" v-auth="'startOrStop'" v-else @click="setStatus(scope.row, 1)">启动</el-button> -->
             <el-button size="small" text type="primary" v-auth="'edit'" @click="addOrEdit(scope.row)">编辑</el-button>
             <el-button size="small" text type="warning" @click="edit(scope.row)">规则编辑</el-button>
             <el-button size="small" text type="info" v-auth="'del'" @click="onDel(scope.row)">删除</el-button>
@@ -33,7 +39,7 @@
         </el-table-column>
       </el-table>
       <pagination v-if="params.total" :total="params.total" v-model:page="params.pageNum" v-model:limit="params.pageSize" @pagination="getList()" />
-      <EditForm :model="model" ref="editFormRef" @getList="getList(1)" :types="0"></EditForm>
+      <EditForm :model="model" ref="editFormRef" @getList="getList(1)"></EditForm>
     </el-card>
   </div>
 </template>
@@ -52,7 +58,7 @@ const editFormRef = ref();
 // 规则引擎模式 node-red、 sagoo-rule
 const model: "node-red" | "sagoo-rule" = import.meta.env.VITE_RULE_MODEL;
 
-const { params, tableData, getList, loading } = useSearch<any[]>(api.getList, "Data", { types: 0 });
+const { params, tableData, getList, loading } = useSearch<any[]>(api.getList, "Data", { types: null, status: null });
 
 const headers = {
   Authorization: "Bearer " + getToken(),

+ 223 - 218
yarn.lock

@@ -203,7 +203,7 @@
   resolved "https://registry.npmmirror.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.7.tgz"
   integrity sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w==
 
-"@babel/parser@^7.16.4", "@babel/parser@^7.25.3":
+"@babel/parser@^7.12.0", "@babel/parser@^7.16.4", "@babel/parser@^7.25.3":
   version "7.25.6"
   resolved "https://registry.npmmirror.com/@babel/parser/-/parser-7.25.6.tgz"
   integrity sha512-trGdfBdbD0l1ZPmcJ83eNxB9rbEax4ALFTF7fN386TMYbeCQbyme5cOEXQhbGXKebwGaB/J52w1mrklMcbgy6Q==
@@ -225,7 +225,7 @@
   dependencies:
     regenerator-runtime "^0.14.0"
 
-"@babel/types@^7.25.6":
+"@babel/types@^7.12.0", "@babel/types@^7.25.6":
   version "7.25.6"
   resolved "https://registry.npmmirror.com/@babel/types/-/types-7.25.6.tgz"
   integrity sha512-/l42B1qxpG6RdfYf343Uw1vmDjeNhneUXtzhojE7pDgfpEypmRhI6j1kr17XCVv4Cgl9HdAiQY2x0GwKm7rWCw==
@@ -239,7 +239,7 @@
   resolved "https://registry.npmmirror.com/@ctrl/tinycolor/-/tinycolor-3.6.1.tgz"
   integrity sha512-SITSV6aIXsuVNV3f3O0f2n/cgyEDWoSqtZMYiAmcsYHydcKrOz3gUxB/iXd/Qf08+IZX4KpgNbvUdMBmWz+kcA==
 
-"@element-plus/icons-vue@2.0.9", "@element-plus/icons-vue@^2.0.6":
+"@element-plus/icons-vue@^2.0.6", "@element-plus/icons-vue@2.0.9":
   version "2.0.9"
   resolved "https://registry.npmmirror.com/@element-plus/icons-vue/-/icons-vue-2.0.9.tgz"
   integrity sha512-okdrwiVeKBmW41Hkl0eMrXDjzJwhQMuKiBOu17rOszqM+LS/yBYpNQNV5Jvoh06Wc+89fMmb/uhzf8NZuDuUaQ==
@@ -249,16 +249,6 @@
   resolved "https://registry.npmmirror.com/@element-plus/icons/-/icons-0.0.11.tgz"
   integrity sha512-iKQXSxXu131Ai+I9Ymtcof9WId7kaXvB1+WRfAfpQCW7UiAMYgdNDqb/u0hgTo2Yq3MwC4MWJnNuTBEpG8r7+A==
 
-"@esbuild/android-arm@0.15.18":
-  version "0.15.18"
-  resolved "https://registry.npmmirror.com/@esbuild/android-arm/-/android-arm-0.15.18.tgz#266d40b8fdcf87962df8af05b76219bc786b4f80"
-  integrity sha512-5GT+kcs2WVGjVs7+boataCkO5Fg0y4kCjzkB5bAip7H4jfnOS3dA6KPiww9W1OEKTKeAcUVhdZGvgI65OXmUnw==
-
-"@esbuild/linux-loong64@0.15.18":
-  version "0.15.18"
-  resolved "https://registry.npmmirror.com/@esbuild/linux-loong64/-/linux-loong64-0.15.18.tgz#128b76ecb9be48b60cf5cfc1c63a4f00691a3239"
-  integrity sha512-L4jVKS82XVhw2nvzLg/19ClLWg0y27ulRwuP7lcyL6AbUWB5aPglXY3M21mauDQMDfRLs8cQmeT03r/+X3cZYQ==
-
 "@eslint/eslintrc@^1.2.0":
   version "1.4.1"
   resolved "https://registry.npmmirror.com/@eslint/eslintrc/-/eslintrc-1.4.1.tgz"
@@ -294,6 +284,14 @@
   resolved "https://registry.npmmirror.com/@floating-ui/utils/-/utils-0.2.7.tgz"
   integrity sha512-X8R8Oj771YRl/w+c1HqAC1szL8zWQRwFvgDwT129k9ACdBoud/+/rX9V0qiMl6LWUdP9voC2nDVZYPMQQsb6eA==
 
+"@guolao/vue-monaco-editor@^1.5.5":
+  version "1.5.5"
+  resolved "https://registry.npmjs.org/@guolao/vue-monaco-editor/-/vue-monaco-editor-1.5.5.tgz"
+  integrity sha512-NFGImQ8dBYj6ehIxy1DngPRkctB9b6GbxvCm6aXZztNsgm/TtM4u+YM9ZwZHQPlXt7a4IODXoKCcTYEVycBSyA==
+  dependencies:
+    "@monaco-editor/loader" "^1.5.0"
+    vue-demi latest
+
 "@humanwhocodes/config-array@^0.9.2":
   version "0.9.5"
   resolved "https://registry.npmmirror.com/@humanwhocodes/config-array/-/config-array-0.9.5.tgz"
@@ -308,7 +306,7 @@
   resolved "https://registry.npmmirror.com/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz"
   integrity sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==
 
-"@interactjs/actions@1.10.27", "@interactjs/actions@^1.10.2":
+"@interactjs/actions@^1.10.2", "@interactjs/actions@1.10.27":
   version "1.10.27"
   resolved "https://registry.npmmirror.com/@interactjs/actions/-/actions-1.10.27.tgz"
   integrity sha512-FCRg5KwB+stkPcAMx/Cn0fgGP6p4LyMX9S/Upcn/W+hpYme31bPi54PCqmOebzz6myTthN6zFf9jMyLOqtI/gg==
@@ -322,7 +320,7 @@
   optionalDependencies:
     "@interactjs/interact" "1.10.27"
 
-"@interactjs/auto-start@1.10.27", "@interactjs/auto-start@^1.10.2":
+"@interactjs/auto-start@^1.10.2", "@interactjs/auto-start@1.10.27":
   version "1.10.27"
   resolved "https://registry.npmmirror.com/@interactjs/auto-start/-/auto-start-1.10.27.tgz"
   integrity sha512-ECLBO/nxmaF1knncJKIE5F7la3KKRgEkn0Cu2JTPOYj9xy/LpfYElo3wkRHsodgOqF651nR70GK2/IzPR2lO9A==
@@ -334,7 +332,7 @@
   resolved "https://registry.npmmirror.com/@interactjs/core/-/core-1.10.27.tgz"
   integrity sha512-SliUr/3ZbLAdED8LokzYzWHWMdCB5Cq+UnpXuRy+BIod1j97m4IUFf/D1iIKUBBjBcucgXbz28z96WnenVCB7Q==
 
-"@interactjs/dev-tools@1.10.27", "@interactjs/dev-tools@^1.10.2":
+"@interactjs/dev-tools@^1.10.2", "@interactjs/dev-tools@1.10.27":
   version "1.10.27"
   resolved "https://registry.npmmirror.com/@interactjs/dev-tools/-/dev-tools-1.10.27.tgz"
   integrity sha512-YolmBwRaKH1gWbvyLeV3m5QSwtD38lOZnCBA87PCAlcd9PQAC2gb03fEPeEyD336bE20oLB8f0WZt4Wre+afiw==
@@ -377,7 +375,7 @@
     "@interactjs/reflow" "1.10.27"
     "@interactjs/utils" "1.10.27"
 
-"@interactjs/modifiers@1.10.27", "@interactjs/modifiers@^1.10.2":
+"@interactjs/modifiers@^1.10.2", "@interactjs/modifiers@1.10.27":
   version "1.10.27"
   resolved "https://registry.npmmirror.com/@interactjs/modifiers/-/modifiers-1.10.27.tgz"
   integrity sha512-ei/qfoQ+9/8k6WzNzdNqHI6cWkIV576N4Ap16r5CoqOWwhA6Xzj3OMHf1g0t1O4eSq2HdJsVJn3eLNfw9HsbeQ==
@@ -494,6 +492,13 @@
   dependencies:
     call-bind "^1.0.7"
 
+"@monaco-editor/loader@^1.5.0":
+  version "1.5.0"
+  resolved "https://registry.npmjs.org/@monaco-editor/loader/-/loader-1.5.0.tgz"
+  integrity sha512-hKoGSM+7aAc7eRTRjpqAZucPmoNOC4UUbknb/VNoTkEIkCPhqV8LfbsgM1webRM7S/z21eHEx9Fkwx8Z/C/+Xw==
+  dependencies:
+    state-local "^1.0.6"
+
 "@nodelib/fs.scandir@2.1.5":
   version "2.1.5"
   resolved "https://registry.npmmirror.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz"
@@ -502,7 +507,7 @@
     "@nodelib/fs.stat" "2.0.5"
     run-parallel "^1.1.9"
 
-"@nodelib/fs.stat@2.0.5", "@nodelib/fs.stat@^2.0.2":
+"@nodelib/fs.stat@^2.0.2", "@nodelib/fs.stat@2.0.5":
   version "2.0.5"
   resolved "https://registry.npmmirror.com/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz"
   integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==
@@ -652,7 +657,7 @@
     "@typescript-eslint/types" "5.13.0"
     eslint-visitor-keys "^3.0.0"
 
-"@unocss/core@0.58.9", "@unocss/core@^0.58.9":
+"@unocss/core@^0.58.9", "@unocss/core@0.58.9":
   version "0.58.9"
   resolved "https://registry.npmmirror.com/@unocss/core/-/core-0.58.9.tgz"
   integrity sha512-wYpPIPPsOIbIoMIDuH8ihehJk5pAZmyFKXIYO/Kro98GEOFhz6lJoLsy6/PZuitlgp2/TSlubUuWGjHWvp5osw==
@@ -679,6 +684,17 @@
   resolved "https://registry.npmmirror.com/@vitejs/plugin-vue/-/plugin-vue-3.1.0.tgz"
   integrity sha512-fmxtHPjSOEIRg6vHYDaem+97iwCUg/uSIaTzp98lhELt2ISOQuDo2hbkBdXod0g15IhfPMQmAxh4heUks2zvDA==
 
+"@vue/compiler-core@3.1.5":
+  version "3.1.5"
+  resolved "https://registry.npmmirror.com/@vue/compiler-core/-/compiler-core-3.1.5.tgz"
+  integrity sha512-TXBhFinoBaXKDykJzY26UEuQU1K07FOp/0Ie+OXySqqk0bS0ZO7Xvl7UmiTUPYcLrWbxWBR7Bs/y55AI0MNc2Q==
+  dependencies:
+    "@babel/parser" "^7.12.0"
+    "@babel/types" "^7.12.0"
+    "@vue/shared" "3.1.5"
+    estree-walker "^2.0.1"
+    source-map "^0.6.1"
+
 "@vue/compiler-core@3.2.37":
   version "3.2.37"
   resolved "https://registry.npmmirror.com/@vue/compiler-core/-/compiler-core-3.2.37.tgz"
@@ -710,6 +726,14 @@
     estree-walker "^2.0.2"
     source-map-js "^1.2.0"
 
+"@vue/compiler-dom@3.1.5":
+  version "3.1.5"
+  resolved "https://registry.npmmirror.com/@vue/compiler-dom/-/compiler-dom-3.1.5.tgz"
+  integrity sha512-ZsL3jqJ52OjGU/YiT/9XiuZAmWClKInZM2aFJh9gnsAPqOrj2JIELMbkIFpVKR/CrVO/f2VxfPiiQdQTr65jcQ==
+  dependencies:
+    "@vue/compiler-core" "3.1.5"
+    "@vue/shared" "3.1.5"
+
 "@vue/compiler-dom@3.2.37":
   version "3.2.37"
   resolved "https://registry.npmmirror.com/@vue/compiler-dom/-/compiler-dom-3.2.37.tgz"
@@ -832,6 +856,13 @@
     estree-walker "^2.0.2"
     magic-string "^0.25.7"
 
+"@vue/reactivity@3.1.5":
+  version "3.1.5"
+  resolved "https://registry.npmmirror.com/@vue/reactivity/-/reactivity-3.1.5.tgz"
+  integrity sha512-1tdfLmNjWG6t/CsPldh+foumYFo3cpyCHgBYQ34ylaMsJ+SNHQ1kApMIa8jN+i593zQuaw3AdWH0nJTARzCFhg==
+  dependencies:
+    "@vue/shared" "3.1.5"
+
 "@vue/reactivity@3.2.37":
   version "3.2.37"
   resolved "https://registry.npmmirror.com/@vue/reactivity/-/reactivity-3.2.37.tgz"
@@ -846,6 +877,14 @@
   dependencies:
     "@vue/shared" "3.5.3"
 
+"@vue/runtime-core@3.1.5":
+  version "3.1.5"
+  resolved "https://registry.npmmirror.com/@vue/runtime-core/-/runtime-core-3.1.5.tgz"
+  integrity sha512-YQbG5cBktN1RowQDKA22itmvQ+b40f0WgQ6CXK4VYoYICAiAfu6Cc14777ve8zp1rJRGtk5oIeS149TOculrTg==
+  dependencies:
+    "@vue/reactivity" "3.1.5"
+    "@vue/shared" "3.1.5"
+
 "@vue/runtime-core@3.2.37":
   version "3.2.37"
   resolved "https://registry.npmmirror.com/@vue/runtime-core/-/runtime-core-3.2.37.tgz"
@@ -862,6 +901,15 @@
     "@vue/reactivity" "3.5.3"
     "@vue/shared" "3.5.3"
 
+"@vue/runtime-dom@3.1.5":
+  version "3.1.5"
+  resolved "https://registry.npmmirror.com/@vue/runtime-dom/-/runtime-dom-3.1.5.tgz"
+  integrity sha512-tNcf3JhVR0RfW0kw1p8xZgv30nvX8Y9rsz7eiQ0dHe273sfoCngAG0y4GvMaY4Xd8FsjUwFedd4suQ8Lu8meXg==
+  dependencies:
+    "@vue/runtime-core" "3.1.5"
+    "@vue/shared" "3.1.5"
+    csstype "^2.6.8"
+
 "@vue/runtime-dom@3.2.37":
   version "3.2.37"
   resolved "https://registry.npmmirror.com/@vue/runtime-dom/-/runtime-dom-3.2.37.tgz"
@@ -897,6 +945,11 @@
     "@vue/compiler-ssr" "3.5.3"
     "@vue/shared" "3.5.3"
 
+"@vue/shared@3.1.5":
+  version "3.1.5"
+  resolved "https://registry.npmmirror.com/@vue/shared/-/shared-3.1.5.tgz"
+  integrity sha512-oJ4F3TnvpXaQwZJNF3ZK+kLPHKarDmJjJ6jyzVNDKH9md1dptjC7lWR//jrGuLdek/U6iltWxqAnYOu8gCiOvA==
+
 "@vue/shared@3.2.37":
   version "3.2.37"
   resolved "https://registry.npmmirror.com/@vue/shared/-/shared-3.2.37.tgz"
@@ -944,11 +997,6 @@
     ua-parser-js "^1.0.38"
     vue "^3.4.31"
 
-JSV@^4.0.2:
-  version "4.0.2"
-  resolved "https://registry.npmmirror.com/JSV/-/JSV-4.0.2.tgz"
-  integrity sha512-ZJ6wx9xaKJ3yFUhq5/sk82PJMuUyLk277I8mQeyDgCTjGdjWJIvPfaU5LIXaMuaN2UO1X3kZH4+lgphublZUHw==
-
 acorn-jsx@^5.3.2:
   version "5.3.2"
   resolved "https://registry.npmmirror.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz"
@@ -1096,7 +1144,7 @@ balanced-match@^1.0.0:
 
 base64-arraybuffer@^1.0.2:
   version "1.0.2"
-  resolved "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-1.0.2.tgz#1c37589a7c4b0746e34bd1feb951da2df01c1bdc"
+  resolved "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-1.0.2.tgz"
   integrity sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ==
 
 batch-processor@1.0.0:
@@ -1209,7 +1257,7 @@ claygl@^1.2.1:
   resolved "https://registry.npmmirror.com/claygl/-/claygl-1.3.0.tgz"
   integrity sha512-+gGtJjT6SSHD2l2yC3MCubW/sCV40tZuSs5opdtn79vFSGUgp/lH139RNEQ6Jy078/L0aV8odCw8RSrUcMfLaQ==
 
-clipboard@2.0.11, clipboard@^2.0.10, clipboard@^2.0.6:
+clipboard@^2.0.10, clipboard@^2.0.6, clipboard@2.0.11:
   version "2.0.11"
   resolved "https://registry.npmmirror.com/clipboard/-/clipboard-2.0.11.tgz"
   integrity sha512-C+0bbOqkezLIsmWSvlsXS0Q0bmkugu7jcfMIACB+RDEntIzQIkdr148we28AfSloQLRdZlYL/QYyrq05j/3Faw==
@@ -1238,7 +1286,7 @@ codemirror-editor-vue3@2.5.8:
     diff-match-patch "^1.0.5"
     jsonlint-mod "^1.7.6"
 
-codemirror@5.65.16, codemirror@^5:
+codemirror@^5, codemirror@5.65.16:
   version "5.65.16"
   resolved "https://registry.npmmirror.com/codemirror/-/codemirror-5.65.16.tgz"
   integrity sha512-br21LjYmSlVL0vFCPWPfhzUCT34FM/pAdK7rRIZwa0rrtrIdotvP4Oh4GUHsu2E3IrQMCfRkL/fN3ytMNxVQvg==
@@ -1265,16 +1313,16 @@ color-convert@^2.0.1:
   dependencies:
     color-name "~1.1.4"
 
-color-name@1.1.3:
-  version "1.1.3"
-  resolved "https://registry.npmmirror.com/color-name/-/color-name-1.1.3.tgz"
-  integrity sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==
-
 color-name@~1.1.4:
   version "1.1.4"
   resolved "https://registry.npmmirror.com/color-name/-/color-name-1.1.4.tgz"
   integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==
 
+color-name@1.1.3:
+  version "1.1.3"
+  resolved "https://registry.npmmirror.com/color-name/-/color-name-1.1.3.tgz"
+  integrity sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==
+
 commander@~2.14.1:
   version "2.14.1"
   resolved "https://registry.npmmirror.com/commander/-/commander-2.14.1.tgz"
@@ -1322,21 +1370,12 @@ cropperjs@1.5.12:
 
 cross-env@^7.0.3:
   version "7.0.3"
-  resolved "https://registry.npmmirror.com/cross-env/-/cross-env-7.0.3.tgz#865264b29677dc015ba8418918965dd232fc54cf"
+  resolved "https://registry.npmjs.org/cross-env/-/cross-env-7.0.3.tgz"
   integrity sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw==
   dependencies:
     cross-spawn "^7.0.1"
 
-cross-spawn@^7.0.1:
-  version "7.0.4"
-  resolved "https://registry.npmmirror.com/cross-spawn/-/cross-spawn-7.0.4.tgz#36d9cb36c32ae7a0df935f0191f79959962a2165"
-  integrity sha512-9KdyVPPtLHjPAD7tcuzSFs64UfHlLJt7U6qP4/bFVLyjLceyizj6s6jO6YBaV5d0G7g/9KnY/dOpLR4Rcg8YDg==
-  dependencies:
-    path-key "^3.1.0"
-    shebang-command "^2.0.0"
-    which "^2.0.1"
-
-cross-spawn@^7.0.2:
+cross-spawn@^7.0.1, cross-spawn@^7.0.2:
   version "7.0.3"
   resolved "https://registry.npmmirror.com/cross-spawn/-/cross-spawn-7.0.3.tgz"
   integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==
@@ -1347,7 +1386,7 @@ cross-spawn@^7.0.2:
 
 css-line-break@^2.1.0:
   version "2.1.0"
-  resolved "https://registry.npmjs.org/css-line-break/-/css-line-break-2.1.0.tgz#bfef660dfa6f5397ea54116bb3cb4873edbc4fa0"
+  resolved "https://registry.npmjs.org/css-line-break/-/css-line-break-2.1.0.tgz"
   integrity sha512-FHcKFCZcAha3LwfVBhCQbW2nCNbkZXn7KVUJcsT5/P8YmfsVja0FMPJr0B903j/E69HUphKiV9iQArX8SDYA4w==
   dependencies:
     utrie "^1.0.2"
@@ -1429,7 +1468,7 @@ data-view-byte-offset@^1.0.0:
     es-errors "^1.3.0"
     is-data-view "^1.0.1"
 
-dayjs@1.11.8, dayjs@1.x, dayjs@^1.11.3:
+dayjs@^1.11.3, dayjs@1.11.8, dayjs@1.x:
   version "1.11.8"
   resolved "https://registry.npmmirror.com/dayjs/-/dayjs-1.11.8.tgz"
   integrity sha512-LcgxzFoWMEPO7ggRv1Y2N31hUf2R0Vj7fuy/m+Bg1K8rr+KAs1AEy4y9jd5DXe8pbHgX+srkHNS7TH6Q6ZhYeQ==
@@ -1516,9 +1555,9 @@ doctrine@^3.0.0:
     esutils "^2.0.2"
 
 dotenv@^16.4.5:
-  version "16.4.5"
-  resolved "https://registry.npmmirror.com/dotenv/-/dotenv-16.4.5.tgz#cdd3b3b604cb327e286b4762e13502f717cb099f"
-  integrity sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==
+  version "16.5.0"
+  resolved "https://registry.npmjs.org/dotenv/-/dotenv-16.5.0.tgz"
+  integrity sha512-m/C+AwOAr9/W1UOIZUo232ejMNnJAJtYQjUbHoNTBNTJSvqzzDh7vnrei3o3r3m9blf6ZoDkvcw0VmozNRFJxg==
 
 dotignore@~0.1.2:
   version "0.1.2"
@@ -1553,6 +1592,20 @@ echarts@5.5.0:
     tslib "2.3.0"
     zrender "5.5.0"
 
+element-plus@^1.0.2-beta.28:
+  version "1.0.2-beta.71"
+  resolved "https://registry.npmmirror.com/element-plus/-/element-plus-1.0.2-beta.71.tgz"
+  integrity sha512-tlfbRORIav8gJcIpjZI5F6aJIVHIaDuGO6/vKu43lgYq4JS2JPNRTjvrSE2p4f5xLfaFNfOWjCS3sybXLfMg8g==
+  dependencies:
+    "@element-plus/icons" "^0.0.11"
+    "@popperjs/core" "^2.4.4"
+    async-validator "^3.4.0"
+    dayjs "1.x"
+    lodash "^4.17.20"
+    mitt "^2.1.0"
+    normalize-wheel "^1.0.1"
+    resize-observer-polyfill "^1.5.1"
+
 element-plus@2.2.28:
   version "2.2.28"
   resolved "https://registry.npmmirror.com/element-plus/-/element-plus-2.2.28.tgz"
@@ -1574,20 +1627,6 @@ element-plus@2.2.28:
     memoize-one "^6.0.0"
     normalize-wheel-es "^1.2.0"
 
-element-plus@^1.0.2-beta.28:
-  version "1.0.2-beta.71"
-  resolved "https://registry.npmmirror.com/element-plus/-/element-plus-1.0.2-beta.71.tgz"
-  integrity sha512-tlfbRORIav8gJcIpjZI5F6aJIVHIaDuGO6/vKu43lgYq4JS2JPNRTjvrSE2p4f5xLfaFNfOWjCS3sybXLfMg8g==
-  dependencies:
-    "@element-plus/icons" "^0.0.11"
-    "@popperjs/core" "^2.4.4"
-    async-validator "^3.4.0"
-    dayjs "1.x"
-    lodash "^4.17.20"
-    mitt "^2.1.0"
-    normalize-wheel "^1.0.1"
-    resize-observer-polyfill "^1.5.1"
-
 element-resize-detector@^1.2.1:
   version "1.2.4"
   resolved "https://registry.npmmirror.com/element-resize-detector/-/element-resize-detector-1.2.4.tgz"
@@ -1689,106 +1728,11 @@ es-to-primitive@^1.2.1:
     is-date-object "^1.0.1"
     is-symbol "^1.0.2"
 
-esbuild-android-64@0.15.18:
-  version "0.15.18"
-  resolved "https://registry.npmmirror.com/esbuild-android-64/-/esbuild-android-64-0.15.18.tgz#20a7ae1416c8eaade917fb2453c1259302c637a5"
-  integrity sha512-wnpt3OXRhcjfIDSZu9bnzT4/TNTDsOUvip0foZOUBG7QbSt//w3QV4FInVJxNhKc/ErhUxc5z4QjHtMi7/TbgA==
-
-esbuild-android-arm64@0.15.18:
-  version "0.15.18"
-  resolved "https://registry.npmmirror.com/esbuild-android-arm64/-/esbuild-android-arm64-0.15.18.tgz#9cc0ec60581d6ad267568f29cf4895ffdd9f2f04"
-  integrity sha512-G4xu89B8FCzav9XU8EjsXacCKSG2FT7wW9J6hOc18soEHJdtWu03L3TQDGf0geNxfLTtxENKBzMSq9LlbjS8OQ==
-
-esbuild-darwin-64@0.15.18:
-  version "0.15.18"
-  resolved "https://registry.npmmirror.com/esbuild-darwin-64/-/esbuild-darwin-64-0.15.18.tgz"
-  integrity sha512-2WAvs95uPnVJPuYKP0Eqx+Dl/jaYseZEUUT1sjg97TJa4oBtbAKnPnl3b5M9l51/nbx7+QAEtuummJZW0sBEmg==
-
 esbuild-darwin-arm64@0.15.18:
   version "0.15.18"
-  resolved "https://registry.npmmirror.com/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.15.18.tgz#b6dfc7799115a2917f35970bfbc93ae50256b337"
+  resolved "https://registry.npmmirror.com/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.15.18.tgz"
   integrity sha512-tKPSxcTJ5OmNb1btVikATJ8NftlyNlc8BVNtyT/UAr62JFOhwHlnoPrhYWz09akBLHI9nElFVfWSTSRsrZiDUA==
 
-esbuild-freebsd-64@0.15.18:
-  version "0.15.18"
-  resolved "https://registry.npmmirror.com/esbuild-freebsd-64/-/esbuild-freebsd-64-0.15.18.tgz#4e190d9c2d1e67164619ae30a438be87d5eedaf2"
-  integrity sha512-TT3uBUxkteAjR1QbsmvSsjpKjOX6UkCstr8nMr+q7zi3NuZ1oIpa8U41Y8I8dJH2fJgdC3Dj3CXO5biLQpfdZA==
-
-esbuild-freebsd-arm64@0.15.18:
-  version "0.15.18"
-  resolved "https://registry.npmmirror.com/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.15.18.tgz#18a4c0344ee23bd5a6d06d18c76e2fd6d3f91635"
-  integrity sha512-R/oVr+X3Tkh+S0+tL41wRMbdWtpWB8hEAMsOXDumSSa6qJR89U0S/PpLXrGF7Wk/JykfpWNokERUpCeHDl47wA==
-
-esbuild-linux-32@0.15.18:
-  version "0.15.18"
-  resolved "https://registry.npmmirror.com/esbuild-linux-32/-/esbuild-linux-32-0.15.18.tgz#9a329731ee079b12262b793fb84eea762e82e0ce"
-  integrity sha512-lphF3HiCSYtaa9p1DtXndiQEeQDKPl9eN/XNoBf2amEghugNuqXNZA/ZovthNE2aa4EN43WroO0B85xVSjYkbg==
-
-esbuild-linux-64@0.15.18:
-  version "0.15.18"
-  resolved "https://registry.npmmirror.com/esbuild-linux-64/-/esbuild-linux-64-0.15.18.tgz#532738075397b994467b514e524aeb520c191b6c"
-  integrity sha512-hNSeP97IviD7oxLKFuii5sDPJ+QHeiFTFLoLm7NZQligur8poNOWGIgpQ7Qf8Balb69hptMZzyOBIPtY09GZYw==
-
-esbuild-linux-arm64@0.15.18:
-  version "0.15.18"
-  resolved "https://registry.npmmirror.com/esbuild-linux-arm64/-/esbuild-linux-arm64-0.15.18.tgz#5372e7993ac2da8f06b2ba313710d722b7a86e5d"
-  integrity sha512-54qr8kg/6ilcxd+0V3h9rjT4qmjc0CccMVWrjOEM/pEcUzt8X62HfBSeZfT2ECpM7104mk4yfQXkosY8Quptug==
-
-esbuild-linux-arm@0.15.18:
-  version "0.15.18"
-  resolved "https://registry.npmmirror.com/esbuild-linux-arm/-/esbuild-linux-arm-0.15.18.tgz#e734aaf259a2e3d109d4886c9e81ec0f2fd9a9cc"
-  integrity sha512-UH779gstRblS4aoS2qpMl3wjg7U0j+ygu3GjIeTonCcN79ZvpPee12Qun3vcdxX+37O5LFxz39XeW2I9bybMVA==
-
-esbuild-linux-mips64le@0.15.18:
-  version "0.15.18"
-  resolved "https://registry.npmmirror.com/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.15.18.tgz#c0487c14a9371a84eb08fab0e1d7b045a77105eb"
-  integrity sha512-Mk6Ppwzzz3YbMl/ZZL2P0q1tnYqh/trYZ1VfNP47C31yT0K8t9s7Z077QrDA/guU60tGNp2GOwCQnp+DYv7bxQ==
-
-esbuild-linux-ppc64le@0.15.18:
-  version "0.15.18"
-  resolved "https://registry.npmmirror.com/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.15.18.tgz#af048ad94eed0ce32f6d5a873f7abe9115012507"
-  integrity sha512-b0XkN4pL9WUulPTa/VKHx2wLCgvIAbgwABGnKMY19WhKZPT+8BxhZdqz6EgkqCLld7X5qiCY2F/bfpUUlnFZ9w==
-
-esbuild-linux-riscv64@0.15.18:
-  version "0.15.18"
-  resolved "https://registry.npmmirror.com/esbuild-linux-riscv64/-/esbuild-linux-riscv64-0.15.18.tgz#423ed4e5927bd77f842bd566972178f424d455e6"
-  integrity sha512-ba2COaoF5wL6VLZWn04k+ACZjZ6NYniMSQStodFKH/Pu6RxzQqzsmjR1t9QC89VYJxBeyVPTaHuBMCejl3O/xg==
-
-esbuild-linux-s390x@0.15.18:
-  version "0.15.18"
-  resolved "https://registry.npmmirror.com/esbuild-linux-s390x/-/esbuild-linux-s390x-0.15.18.tgz#21d21eaa962a183bfb76312e5a01cc5ae48ce8eb"
-  integrity sha512-VbpGuXEl5FCs1wDVp93O8UIzl3ZrglgnSQ+Hu79g7hZu6te6/YHgVJxCM2SqfIila0J3k0csfnf8VD2W7u2kzQ==
-
-esbuild-netbsd-64@0.15.18:
-  version "0.15.18"
-  resolved "https://registry.npmmirror.com/esbuild-netbsd-64/-/esbuild-netbsd-64-0.15.18.tgz#ae75682f60d08560b1fe9482bfe0173e5110b998"
-  integrity sha512-98ukeCdvdX7wr1vUYQzKo4kQ0N2p27H7I11maINv73fVEXt2kyh4K4m9f35U1K43Xc2QGXlzAw0K9yoU7JUjOg==
-
-esbuild-openbsd-64@0.15.18:
-  version "0.15.18"
-  resolved "https://registry.npmmirror.com/esbuild-openbsd-64/-/esbuild-openbsd-64-0.15.18.tgz#79591a90aa3b03e4863f93beec0d2bab2853d0a8"
-  integrity sha512-yK5NCcH31Uae076AyQAXeJzt/vxIo9+omZRKj1pauhk3ITuADzuOx5N2fdHrAKPxN+zH3w96uFKlY7yIn490xQ==
-
-esbuild-sunos-64@0.15.18:
-  version "0.15.18"
-  resolved "https://registry.npmmirror.com/esbuild-sunos-64/-/esbuild-sunos-64-0.15.18.tgz#fd528aa5da5374b7e1e93d36ef9b07c3dfed2971"
-  integrity sha512-On22LLFlBeLNj/YF3FT+cXcyKPEI263nflYlAhz5crxtp3yRG1Ugfr7ITyxmCmjm4vbN/dGrb/B7w7U8yJR9yw==
-
-esbuild-windows-32@0.15.18:
-  version "0.15.18"
-  resolved "https://registry.npmmirror.com/esbuild-windows-32/-/esbuild-windows-32-0.15.18.tgz#0e92b66ecdf5435a76813c4bc5ccda0696f4efc3"
-  integrity sha512-o+eyLu2MjVny/nt+E0uPnBxYuJHBvho8vWsC2lV61A7wwTWC3jkN2w36jtA+yv1UgYkHRihPuQsL23hsCYGcOQ==
-
-esbuild-windows-64@0.15.18:
-  version "0.15.18"
-  resolved "https://registry.npmmirror.com/esbuild-windows-64/-/esbuild-windows-64-0.15.18.tgz#0fc761d785414284fc408e7914226d33f82420d0"
-  integrity sha512-qinug1iTTaIIrCorAUjR0fcBk24fjzEedFYhhispP8Oc7SFvs+XeW3YpAKiKp8dRpizl4YYAhxMjlftAMJiaUw==
-
-esbuild-windows-arm64@0.15.18:
-  version "0.15.18"
-  resolved "https://registry.npmmirror.com/esbuild-windows-arm64/-/esbuild-windows-arm64-0.15.18.tgz#5b5bdc56d341d0922ee94965c89ee120a6a86eb7"
-  integrity sha512-q9bsYzegpZcLziq0zgUi5KqGVtfhjxGbnksaBFYmWLxeV/S1fK4OLdq2DFYnXcLMjlZw2L0jLsk1eGoB522WXQ==
-
 esbuild@^0.15.9:
   version "0.15.18"
   resolved "https://registry.npmmirror.com/esbuild/-/esbuild-0.15.18.tgz"
@@ -1822,7 +1766,12 @@ escape-html@^1.0.3:
   resolved "https://registry.npmmirror.com/escape-html/-/escape-html-1.0.3.tgz"
   integrity sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==
 
-escape-string-regexp@^1.0.2, escape-string-regexp@^1.0.5:
+escape-string-regexp@^1.0.2:
+  version "1.0.5"
+  resolved "https://registry.npmmirror.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz"
+  integrity sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==
+
+escape-string-regexp@^1.0.5:
   version "1.0.5"
   resolved "https://registry.npmmirror.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz"
   integrity sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==
@@ -1850,7 +1799,15 @@ eslint-scope@^5.1.1:
     esrecurse "^4.3.0"
     estraverse "^4.1.1"
 
-eslint-scope@^7.0.0, eslint-scope@^7.1.1:
+eslint-scope@^7.0.0:
+  version "7.2.2"
+  resolved "https://registry.npmmirror.com/eslint-scope/-/eslint-scope-7.2.2.tgz"
+  integrity sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==
+  dependencies:
+    esrecurse "^4.3.0"
+    estraverse "^5.2.0"
+
+eslint-scope@^7.1.1:
   version "7.2.2"
   resolved "https://registry.npmmirror.com/eslint-scope/-/eslint-scope-7.2.2.tgz"
   integrity sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==
@@ -1944,12 +1901,17 @@ estraverse@^4.1.1:
   resolved "https://registry.npmmirror.com/estraverse/-/estraverse-4.3.0.tgz"
   integrity sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==
 
-estraverse@^5.1.0, estraverse@^5.2.0:
+estraverse@^5.1.0:
   version "5.3.0"
   resolved "https://registry.npmmirror.com/estraverse/-/estraverse-5.3.0.tgz"
   integrity sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==
 
-estree-walker@^2.0.2:
+estraverse@^5.2.0:
+  version "5.3.0"
+  resolved "https://registry.npmmirror.com/estraverse/-/estraverse-5.3.0.tgz"
+  integrity sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==
+
+estree-walker@^2.0.1, estree-walker@^2.0.2:
   version "2.0.2"
   resolved "https://registry.npmmirror.com/estree-walker/-/estree-walker-2.0.2.tgz"
   integrity sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==
@@ -2137,7 +2099,7 @@ gl-matrix@^3.0.0, gl-matrix@^3.3.0, gl-matrix@^3.4.3:
   resolved "https://registry.npmmirror.com/gl-matrix/-/gl-matrix-3.4.3.tgz"
   integrity sha512-wcCp8vu8FT22BnvKVPjXa/ICBWRq/zjFfdofZy1WSpQZpphblv12/bOQLBC1rMM7SGOFS9ltVmKOHil5+Ml7gA==
 
-glob-parent@^5.1.2, glob-parent@~5.1.2:
+glob-parent@^5.1.2:
   version "5.1.2"
   resolved "https://registry.npmmirror.com/glob-parent/-/glob-parent-5.1.2.tgz"
   integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==
@@ -2151,6 +2113,13 @@ glob-parent@^6.0.1:
   dependencies:
     is-glob "^4.0.3"
 
+glob-parent@~5.1.2:
+  version "5.1.2"
+  resolved "https://registry.npmmirror.com/glob-parent/-/glob-parent-5.1.2.tgz"
+  integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==
+  dependencies:
+    is-glob "^4.0.1"
+
 glob@^7.1.3, glob@~7.2.3:
   version "7.2.3"
   resolved "https://registry.npmmirror.com/glob/-/glob-7.2.3.tgz"
@@ -2274,7 +2243,7 @@ highlight.js@^11.8.0:
 
 html2canvas@^1.4.1:
   version "1.4.1"
-  resolved "https://registry.npmjs.org/html2canvas/-/html2canvas-1.4.1.tgz#7cef1888311b5011d507794a066041b14669a543"
+  resolved "https://registry.npmjs.org/html2canvas/-/html2canvas-1.4.1.tgz"
   integrity sha512-fPU6BHNpsyIhr8yyMpTLLxAbkaK8ArIBcmZIRiBLiDhjeqvXolaEmDGmELFuX9I4xDcaKKcJl+TKZLqruBbmWA==
   dependencies:
     css-line-break "^2.1.0"
@@ -2311,7 +2280,7 @@ inflight@^1.0.4:
     once "^1.3.0"
     wrappy "1"
 
-inherits@2, inherits@~2.0.4:
+inherits@~2.0.4, inherits@2:
   version "2.0.4"
   resolved "https://registry.npmmirror.com/inherits/-/inherits-2.0.4.tgz"
   integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
@@ -2524,8 +2493,8 @@ jsonlint-mod@^1.7.6:
   resolved "https://registry.npmmirror.com/jsonlint-mod/-/jsonlint-mod-1.7.6.tgz"
   integrity sha512-oGuk6E1ehmIpw0w9ttgb2KsDQQgGXBzZczREW8OfxEm9eCQYL9/LCexSnh++0z3AiYGcXpBgqDSx9AAgzl/Bvg==
   dependencies:
-    JSV "^4.0.2"
     chalk "^2.4.2"
+    JSV "^4.0.2"
     underscore "^1.9.1"
 
 jsplumb@2.15.6:
@@ -2538,6 +2507,11 @@ jsrsasign@10.8.6:
   resolved "https://registry.npmmirror.com/jsrsasign/-/jsrsasign-10.8.6.tgz"
   integrity sha512-bQmbVtsfbgaKBTWCKiDCPlUPbdlRIK/FzSwT3BzIgZl/cU6TqXu6pZJsCI/dJVrZ9Gir5GC4woqw9shH/v7MBw==
 
+JSV@^4.0.2:
+  version "4.0.2"
+  resolved "https://registry.npmmirror.com/JSV/-/JSV-4.0.2.tgz"
+  integrity sha512-ZJ6wx9xaKJ3yFUhq5/sk82PJMuUyLk277I8mQeyDgCTjGdjWJIvPfaU5LIXaMuaN2UO1X3kZH4+lgphublZUHw==
+
 keyv@^4.5.3:
   version "4.5.4"
   resolved "https://registry.npmmirror.com/keyv/-/keyv-4.5.4.tgz"
@@ -2602,7 +2576,14 @@ magic-string@^0.25.7:
   dependencies:
     sourcemap-codec "^1.4.8"
 
-magic-string@^0.30.11, magic-string@^0.30.8:
+magic-string@^0.30.11:
+  version "0.30.11"
+  resolved "https://registry.npmmirror.com/magic-string/-/magic-string-0.30.11.tgz"
+  integrity sha512-+Wri9p0QHMy+545hKww7YAu5NyzF8iomPL/RQazugQ9+Ez4Ic3mERMd8ZTX5rfK944j+560ZJi8iAwgak1Ac7A==
+  dependencies:
+    "@jridgewell/sourcemap-codec" "^1.5.0"
+
+magic-string@^0.30.8:
   version "0.30.11"
   resolved "https://registry.npmmirror.com/magic-string/-/magic-string-0.30.11.tgz"
   integrity sha512-+Wri9p0QHMy+545hKww7YAu5NyzF8iomPL/RQazugQ9+Ez4Ic3mERMd8ZTX5rfK944j+560ZJi8iAwgak1Ac7A==
@@ -2644,16 +2625,16 @@ minimist@^1.2.0, minimist@~1.2.8:
   resolved "https://registry.npmmirror.com/minimist/-/minimist-1.2.8.tgz"
   integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==
 
-mitt@3.0.0:
-  version "3.0.0"
-  resolved "https://registry.npmmirror.com/mitt/-/mitt-3.0.0.tgz"
-  integrity sha512-7dX2/10ITVyqh4aOSVI9gdape+t9l2/8QxHrFmUXu4EEUpdlxl6RudZUPZoc+zuY2hk1j7XxVroIVIan/pD/SQ==
-
 mitt@^2.1.0:
   version "2.1.0"
   resolved "https://registry.npmmirror.com/mitt/-/mitt-2.1.0.tgz"
   integrity sha512-ILj2TpLiysu2wkBbWjAmww7TkZb65aiQO+DkVdUTBpBXq+MHYiETENkKFMtsJZX1Lf4pe4QOrTSjIfUwN5lRdg==
 
+mitt@3.0.0:
+  version "3.0.0"
+  resolved "https://registry.npmmirror.com/mitt/-/mitt-3.0.0.tgz"
+  integrity sha512-7dX2/10ITVyqh4aOSVI9gdape+t9l2/8QxHrFmUXu4EEUpdlxl6RudZUPZoc+zuY2hk1j7XxVroIVIan/pD/SQ==
+
 mock-property@~1.0.0:
   version "1.0.3"
   resolved "https://registry.npmmirror.com/mock-property/-/mock-property-1.0.3.tgz"
@@ -2666,6 +2647,11 @@ mock-property@~1.0.0:
     hasown "^2.0.0"
     isarray "^2.0.5"
 
+monaco-editor@^0.52.2:
+  version "0.52.2"
+  resolved "https://registry.npmjs.org/monaco-editor/-/monaco-editor-0.52.2.tgz"
+  integrity sha512-GEQWEZmfkOGLdd3XK8ryrfWz3AIP8YymVXiPHEdewrUq7mh0qrKrfHLNCXcbB6sTnMLnOZ3ztSiKcciFUkIJwQ==
+
 ms@^2.1.3:
   version "2.1.3"
   resolved "https://registry.npmmirror.com/ms/-/ms-2.1.3.tgz"
@@ -3009,7 +2995,7 @@ select@^1.1.2:
   resolved "https://registry.npmmirror.com/select/-/select-1.1.2.tgz"
   integrity sha512-OwpTSOfy6xSs1+pwcNrv0RBMOzI39Lp3qQKUTPVVPRjCdNa5JH/oPRiqsesIskK8TVgmRiHwO4KXlV2Li9dANA==
 
-semver@7.6.2, semver@^7.3.5:
+semver@^7.3.5, semver@7.6.2:
   version "7.6.2"
   resolved "https://registry.npmmirror.com/semver/-/semver-7.6.2.tgz"
   integrity sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w==
@@ -3073,7 +3059,7 @@ sortablejs@1.14.0:
   resolved "https://registry.npmmirror.com/sortablejs/-/sortablejs-1.14.0.tgz"
   integrity sha512-pBXvQCs5/33fdN1/39pPL0NZF20LeRbLQ5jtnheIPN9JQAaufGjKdWduZn4U7wCtVuzKhmRkI0DFYHYRbB2H1w==
 
-"source-map-js@>=0.6.2 <2.0.0", source-map-js@^1.0.1, source-map-js@^1.2.0:
+source-map-js@^1.0.1, source-map-js@^1.2.0, "source-map-js@>=0.6.2 <2.0.0":
   version "1.2.0"
   resolved "https://registry.npmmirror.com/source-map-js/-/source-map-js-1.2.0.tgz"
   integrity sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==
@@ -3085,14 +3071,7 @@ source-map-support@^0.3.2:
   dependencies:
     source-map "0.1.32"
 
-source-map@0.1.32:
-  version "0.1.32"
-  resolved "https://registry.npmmirror.com/source-map/-/source-map-0.1.32.tgz"
-  integrity sha512-htQyLrrRLkQ87Zfrir4/yN+vAUd6DNjVayEjTSHXu29AYQJw57I4/xEL/M6p6E/woPNJwvZt6rVlzc7gFEJccQ==
-  dependencies:
-    amdefine ">=0.0.4"
-
-source-map@0.6.1, source-map@^0.6.1:
+source-map@^0.6.1, 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==
@@ -3102,6 +3081,13 @@ source-map@~0.5.1:
   resolved "https://registry.npmmirror.com/source-map/-/source-map-0.5.7.tgz"
   integrity sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==
 
+source-map@0.1.32:
+  version "0.1.32"
+  resolved "https://registry.npmmirror.com/source-map/-/source-map-0.1.32.tgz"
+  integrity sha512-htQyLrrRLkQ87Zfrir4/yN+vAUd6DNjVayEjTSHXu29AYQJw57I4/xEL/M6p6E/woPNJwvZt6rVlzc7gFEJccQ==
+  dependencies:
+    amdefine ">=0.0.4"
+
 sourcemap-codec@^1.4.8:
   version "1.4.8"
   resolved "https://registry.npmmirror.com/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz"
@@ -3119,6 +3105,11 @@ ssf@~0.11.2:
   dependencies:
     frac "~1.1.2"
 
+state-local@^1.0.6:
+  version "1.0.7"
+  resolved "https://registry.npmjs.org/state-local/-/state-local-1.0.7.tgz"
+  integrity sha512-HTEHMNieakEnoe33shBYcZ7NX83ACUjCu8c40iOGEZsngj9zRnkqS9j1pqQPXwobB0ZcVTk27REb7COQ0UR59w==
+
 string.prototype.trim@^1.2.9, string.prototype.trim@~1.2.8:
   version "1.2.9"
   resolved "https://registry.npmmirror.com/string.prototype.trim/-/string.prototype.trim-1.2.9.tgz"
@@ -3214,7 +3205,7 @@ tape@^4.5.1:
 
 text-segmentation@^1.0.3:
   version "1.0.3"
-  resolved "https://registry.npmjs.org/text-segmentation/-/text-segmentation-1.0.3.tgz#52a388159efffe746b24a63ba311b6ac9f2d7943"
+  resolved "https://registry.npmjs.org/text-segmentation/-/text-segmentation-1.0.3.tgz"
   integrity sha512-iOiPUo/BGnZ6+54OsWxZidGCsdU8YbE4PSpdPinp7DeMtUJNJBoJ/ouUSTJjHkh1KntHaltHl/gDs2FC4i5+Nw==
   dependencies:
     utrie "^1.0.2"
@@ -3241,12 +3232,12 @@ to-regex-range@^5.0.1:
   dependencies:
     is-number "^7.0.0"
 
-tslib@2.3.0:
-  version "2.3.0"
-  resolved "https://registry.npmmirror.com/tslib/-/tslib-2.3.0.tgz"
-  integrity sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==
+tslib@^1.10.0:
+  version "1.14.1"
+  resolved "https://registry.npmmirror.com/tslib/-/tslib-1.14.1.tgz"
+  integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==
 
-tslib@^1.10.0, tslib@^1.8.1:
+tslib@^1.8.1:
   version "1.14.1"
   resolved "https://registry.npmmirror.com/tslib/-/tslib-1.14.1.tgz"
   integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==
@@ -3256,6 +3247,11 @@ tslib@^2.0.0, tslib@^2.0.3, tslib@^2.1.0, tslib@^2.3.1:
   resolved "https://registry.npmmirror.com/tslib/-/tslib-2.7.0.tgz"
   integrity sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==
 
+tslib@2.3.0:
+  version "2.3.0"
+  resolved "https://registry.npmmirror.com/tslib/-/tslib-2.3.0.tgz"
+  integrity sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==
+
 tsutils@^3.21.0:
   version "3.21.0"
   resolved "https://registry.npmmirror.com/tsutils/-/tsutils-3.21.0.tgz"
@@ -3373,7 +3369,7 @@ uri-js@^4.2.2:
 
 utrie@^1.0.2:
   version "1.0.2"
-  resolved "https://registry.npmjs.org/utrie/-/utrie-1.0.2.tgz#d42fe44de9bc0119c25de7f564a6ed1b2c87a645"
+  resolved "https://registry.npmjs.org/utrie/-/utrie-1.0.2.tgz"
   integrity sha512-1MLa5ouZiOmQzUbjbu9VmjLzn1QLXBhwpUa7kdLUQK+KQ5KA9I1vk5U4YHe/X2Ch7PYnJfWuWT+VbuxbGwljhw==
   dependencies:
     base64-arraybuffer "^1.0.2"
@@ -3422,16 +3418,16 @@ vue-clipboard3@1.0.1:
     clipboard "^2.0.6"
 
 vue-data-ui@^2.4.17:
-  version "2.6.2"
-  resolved "https://registry.npmjs.org/vue-data-ui/-/vue-data-ui-2.6.2.tgz#960569237e0ccf65e797e7517756bc735ec4a075"
-  integrity sha512-YQxX04a8raB/BVk95HeQegtvTXrvmDMbaIDE52s5lEPNUArGHVIF6G5EUPuNuPRgZDtJJW6csZ/0zOYAaCafAA==
+  version "2.6.46"
+  resolved "https://registry.npmjs.org/vue-data-ui/-/vue-data-ui-2.6.46.tgz"
+  integrity sha512-wg6wCNRxogYBRYSmVsaEni2DVBMjATmye3zHVAX4EGuntreLsSRmFBA5Iq1g0tnVqPhv/E/wN8Y9UD0YJ+oUPA==
 
-vue-demi@*:
+vue-demi@*, vue-demi@latest:
   version "0.14.10"
-  resolved "https://registry.npmmirror.com/vue-demi/-/vue-demi-0.14.10.tgz"
+  resolved "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.10.tgz"
   integrity sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==
 
-vue-eslint-parser@8.3.0, vue-eslint-parser@^8.0.1:
+vue-eslint-parser@^8.0.1, vue-eslint-parser@8.3.0:
   version "8.3.0"
   resolved "https://registry.npmmirror.com/vue-eslint-parser/-/vue-eslint-parser-8.3.0.tgz"
   integrity sha512-dzHGG3+sYwSf6zFBa0Gi9ZDshD7+ad14DGOdTLjruRVgZXe2J+DcZ9iUhyR48z5g1PqRa20yt3Njna/veLJL/g==
@@ -3474,6 +3470,37 @@ vue-router@4.0.13:
   dependencies:
     "@vue/devtools-api" "^6.0.0"
 
+vue@^3.0.0:
+  version "3.1.5"
+  resolved "https://registry.npmmirror.com/vue/-/vue-3.1.5.tgz"
+  integrity sha512-Ho7HNb1nfDoO+HVb6qYZgeaobt1XbY6KXFe4HGs1b9X6RhkWG/113n4/SrtM1LUclM6OrP/Se5aPHHvAPG1iVQ==
+  dependencies:
+    "@vue/compiler-dom" "3.1.5"
+    "@vue/runtime-dom" "3.1.5"
+    "@vue/shared" "3.1.5"
+
+vue@^3.4.31:
+  version "3.5.3"
+  resolved "https://registry.npmmirror.com/vue/-/vue-3.5.3.tgz"
+  integrity sha512-xvRbd0HpuLovYbOHXRHlSBsSvmUJbo0pzbkKTApWnQGf3/cu5Z39mQeA5cZdLRVIoNf3zI6MSoOgHUT5i2jO+Q==
+  dependencies:
+    "@vue/compiler-dom" "3.5.3"
+    "@vue/compiler-sfc" "3.5.3"
+    "@vue/runtime-dom" "3.5.3"
+    "@vue/server-renderer" "3.5.3"
+    "@vue/shared" "3.5.3"
+
+vue@3, vue@3.2.37:
+  version "3.2.37"
+  resolved "https://registry.npmmirror.com/vue/-/vue-3.2.37.tgz"
+  integrity sha512-bOKEZxrm8Eh+fveCqS1/NkG/n6aMidsI6hahas7pa0w/l7jkbssJVsRhVDs07IdDq7h9KHswZOgItnwJAgtVtQ==
+  dependencies:
+    "@vue/compiler-dom" "3.2.37"
+    "@vue/compiler-sfc" "3.2.37"
+    "@vue/runtime-dom" "3.2.37"
+    "@vue/server-renderer" "3.2.37"
+    "@vue/shared" "3.2.37"
+
 vue3-clipboard@1.0.0:
   version "1.0.0"
   resolved "https://registry.npmmirror.com/vue3-clipboard/-/vue3-clipboard-1.0.0.tgz"
@@ -3497,28 +3524,6 @@ vue3-json-viewer@2.2.2:
   dependencies:
     clipboard "^2.0.10"
 
-vue@3, vue@3.2.37, vue@^3.0.0:
-  version "3.2.37"
-  resolved "https://registry.npmmirror.com/vue/-/vue-3.2.37.tgz"
-  integrity sha512-bOKEZxrm8Eh+fveCqS1/NkG/n6aMidsI6hahas7pa0w/l7jkbssJVsRhVDs07IdDq7h9KHswZOgItnwJAgtVtQ==
-  dependencies:
-    "@vue/compiler-dom" "3.2.37"
-    "@vue/compiler-sfc" "3.2.37"
-    "@vue/runtime-dom" "3.2.37"
-    "@vue/server-renderer" "3.2.37"
-    "@vue/shared" "3.2.37"
-
-vue@^3.4.31:
-  version "3.5.3"
-  resolved "https://registry.npmmirror.com/vue/-/vue-3.5.3.tgz"
-  integrity sha512-xvRbd0HpuLovYbOHXRHlSBsSvmUJbo0pzbkKTApWnQGf3/cu5Z39mQeA5cZdLRVIoNf3zI6MSoOgHUT5i2jO+Q==
-  dependencies:
-    "@vue/compiler-dom" "3.5.3"
-    "@vue/compiler-sfc" "3.5.3"
-    "@vue/runtime-dom" "3.5.3"
-    "@vue/server-renderer" "3.5.3"
-    "@vue/shared" "3.5.3"
-
 vuex@4.0.2:
   version "4.0.2"
   resolved "https://registry.npmmirror.com/vuex/-/vuex-4.0.2.tgz"
@@ -3620,7 +3625,7 @@ yargs@~3.10.0:
     decamelize "^1.0.0"
     window-size "0.1.0"
 
-zrender@5.5.0, zrender@^5.1.1:
+zrender@^5.1.1, zrender@5.5.0:
   version "5.5.0"
   resolved "https://registry.npmmirror.com/zrender/-/zrender-5.5.0.tgz"
   integrity sha512-O3MilSi/9mwoovx77m6ROZM7sXShR/O/JIanvzTwjN3FORfLSr81PsUGd7jlaYOeds9d8tw82oP44+3YucVo+w==