瀏覽代碼

First Commit

繁花落叶已成霜 3 年之前
當前提交
8aff66c1da
共有 68 個文件被更改,包括 10659 次插入0 次删除
  1. 0 0
      .env.production
  2. 15 0
      .eslintrc.js
  3. 29 0
      .gitignore
  4. 7 0
      README.md
  5. 12 0
      index.html
  6. 14 0
      jsconfig.json
  7. 4708 0
      package-lock.json
  8. 27 0
      package.json
  9. 19 0
      prettier.config.js
  10. 3 0
      src/App.vue
  11. 31 0
      src/api/admin/login.js
  12. 48 0
      src/api/admin/menu.js
  13. 35 0
      src/api/admin/post.js
  14. 51 0
      src/api/admin/role.js
  15. 35 0
      src/api/admin/sys-api.js
  16. 35 0
      src/api/admin/sys-config.js
  17. 35 0
      src/api/admin/sys-dept.js
  18. 35 0
      src/api/admin/sys-dict-data.js
  19. 35 0
      src/api/admin/sys-dict.js
  20. 19 0
      src/api/admin/sys-login-log.js
  21. 19 0
      src/api/admin/sys-opera-log.js
  22. 66 0
      src/api/admin/sys-user.js
  23. 62 0
      src/api/sys-job.js
  24. 10 0
      src/api/sys-tools/monitor.js
  25. 74 0
      src/components/Avatar/index.vue
  26. 3 0
      src/components/Dashboard.vue
  27. 91 0
      src/components/Menu/Menu.vue
  28. 37 0
      src/components/Menu/SubMenu.vue
  29. 0 0
      src/components/SvgIcon.vue
  30. 0 0
      src/icons/index.js
  31. 1 0
      src/icons/svg/api-management.svg
  32. 15 0
      src/layout/components/AppMain.vue
  33. 93 0
      src/layout/components/Navbar.vue
  34. 2 0
      src/layout/components/index.js
  35. 70 0
      src/layout/index.vue
  36. 34 0
      src/main.js
  37. 78 0
      src/router/index.js
  38. 56 0
      src/store/permission.js
  39. 44 0
      src/store/userInfo.js
  40. 34 0
      src/style/dark-theme.scss
  41. 52 0
      src/style/index.scss
  42. 28 0
      src/style/transition.scss
  43. 3 0
      src/style/variables.scss
  44. 41 0
      src/utils/parseTime.js
  45. 56 0
      src/utils/request.js
  46. 41 0
      src/utils/storage.js
  47. 248 0
      src/views/admin/dict/data.vue
  48. 293 0
      src/views/admin/dict/index.vue
  49. 232 0
      src/views/admin/sys-api/index.vue
  50. 324 0
      src/views/admin/sys-config/index.vue
  51. 242 0
      src/views/admin/sys-dept/index.vue
  52. 174 0
      src/views/admin/sys-login-log/index.vue
  53. 369 0
      src/views/admin/sys-menu/index.vue
  54. 185 0
      src/views/admin/sys-oper-log/index.vue
  55. 272 0
      src/views/admin/sys-post/index.vue
  56. 386 0
      src/views/admin/sys-role/index.vue
  57. 77 0
      src/views/admin/sys-set/index.vue
  58. 31 0
      src/views/admin/sys-user/components/TreeDept.vue
  59. 658 0
      src/views/admin/sys-user/index.vue
  60. 11 0
      src/views/dev-tools/swagger/index.vue
  61. 25 0
      src/views/error-page/404.vue
  62. 3 0
      src/views/index.vue
  63. 284 0
      src/views/login/index.vue
  64. 1 0
      src/views/profile/api-management.svg
  65. 181 0
      src/views/profile/index.vue
  66. 266 0
      src/views/schedule/index.vue
  67. 160 0
      src/views/sys-tools/monitor/index.vue
  68. 34 0
      vite.config.js

+ 0 - 0
.env.production


+ 15 - 0
.eslintrc.js

@@ -0,0 +1,15 @@
+module.exports = {
+  extends: [
+    // add more generic rulesets here, such as:
+    // 'eslint:recommended',
+    'plugin:vue/base',
+    'plugin:vue/vue3-essential',
+    // 'plugin:vue/vue3-strongly-recommended'
+    //'plugin:vue/vue3-recommended',
+    // 'plugin:vue/recommended' // Use this if you are using Vue.js 2.x.
+  ],
+  rules: {
+    // override/add rules settings here, such as:
+    // 'vue/no-unused-vars': 'error'
+  }
+}

+ 29 - 0
.gitignore

@@ -0,0 +1,29 @@
+# Logs
+logs
+*.log
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+pnpm-debug.log*
+lerna-debug.log*
+
+yarn.lock
+
+node_modules
+dist
+dist-ssr
+*.local
+
+# Editor directories and files
+.vscode/*
+!.vscode/extensions.json
+.idea
+.DS_Store
+*.suo
+*.ntvs*
+*.njsproj
+*.sln
+*.sw?
+
+# node_modules
+node_modules

+ 7 - 0
README.md

@@ -0,0 +1,7 @@
+# Vue 3 + Vite
+
+This template should help get you started developing with Vue 3 in Vite. The template uses Vue 3 `<script setup>` SFCs, check out the [script setup docs](https://v3.vuejs.org/api/sfc-script-setup.html#sfc-script-setup) to learn more.
+
+## Recommended IDE Setup
+
+- [VS Code](https://code.visualstudio.com/) + [Volar](https://marketplace.visualstudio.com/items?itemName=johnsoncodehk.volar)

+ 12 - 0
index.html

@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<html lang="en">
+  <head>
+    <meta charset="UTF-8" />
+    <link rel="icon" href="/favicon.ico" />
+    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+  </head>
+  <body>
+    <div id="app"></div>
+    <script type="module" src="/src/main.js"></script>
+  </body>
+</html>

+ 14 - 0
jsconfig.json

@@ -0,0 +1,14 @@
+{
+  "compilerOptions": {
+      "target": "ES6",
+      "module": "commonjs",
+      "allowSyntheticDefaultImports": true,
+      "baseUrl": "./",
+      "paths": {
+        "@/*": ["src/*"]
+      }
+  },
+  "exclude": [
+      "node_modules"
+  ]
+}

+ 4708 - 0
package-lock.json

@@ -0,0 +1,4708 @@
+{
+  "name": "quark_arco",
+  "version": "0.0.0",
+  "lockfileVersion": 2,
+  "requires": true,
+  "packages": {
+    "": {
+      "name": "quark_arco",
+      "version": "0.0.0",
+      "dependencies": {
+        "@ant-design/icons": "^4.7.0",
+        "axios": "^0.26.1",
+        "pinia": "^2.0.14",
+        "vue": "^3.2.25",
+        "vue-router": "^4.0.14"
+      },
+      "devDependencies": {
+        "@arco-design/web-vue": "^2.24.1",
+        "@vitejs/plugin-vue": "^2.3.1",
+        "eslint": "^8.15.0",
+        "eslint-plugin-vue": "^9.0.1",
+        "mockjs": "^1.1.0",
+        "sass": "^1.50.1",
+        "vite": "^2.9.5",
+        "vite-plugin-mock": "^2.9.6"
+      }
+    },
+    "node_modules/@ant-design/colors": {
+      "version": "6.0.0",
+      "resolved": "https://registry.npmjs.org/@ant-design/colors/-/colors-6.0.0.tgz",
+      "integrity": "sha512-qAZRvPzfdWHtfameEGP2Qvuf838NhergR35o+EuVyB5XvSA98xod5r4utvi4TJ3ywmevm290g9nsCG5MryrdWQ==",
+      "dependencies": {
+        "@ctrl/tinycolor": "^3.4.0"
+      }
+    },
+    "node_modules/@ant-design/icons": {
+      "version": "4.7.0",
+      "resolved": "https://registry.npmjs.org/@ant-design/icons/-/icons-4.7.0.tgz",
+      "integrity": "sha512-aoB4Z7JA431rt6d4u+8xcNPPCrdufSRMUOpxa1ab6mz1JCQZOEVolj2WVs/tDFmN62zzK30mNelEsprLYsSF3g==",
+      "dependencies": {
+        "@ant-design/colors": "^6.0.0",
+        "@ant-design/icons-svg": "^4.2.1",
+        "@babel/runtime": "^7.11.2",
+        "classnames": "^2.2.6",
+        "rc-util": "^5.9.4"
+      },
+      "engines": {
+        "node": ">=8"
+      },
+      "peerDependencies": {
+        "react": ">=16.0.0",
+        "react-dom": ">=16.0.0"
+      }
+    },
+    "node_modules/@ant-design/icons-svg": {
+      "version": "4.2.1",
+      "resolved": "https://registry.npmjs.org/@ant-design/icons-svg/-/icons-svg-4.2.1.tgz",
+      "integrity": "sha512-EB0iwlKDGpG93hW8f85CTJTs4SvMX7tt5ceupvhALp1IF44SeUFOMhKUOYqpsoYWQKAOuTRDMqn75rEaKDp0Xw=="
+    },
+    "node_modules/@arco-design/color": {
+      "version": "0.4.0",
+      "resolved": "https://registry.npmjs.org/@arco-design/color/-/color-0.4.0.tgz",
+      "integrity": "sha512-s7p9MSwJgHeL8DwcATaXvWT3m2SigKpxx4JA1BGPHL4gfvaQsmQfrLBDpjOJFJuJ2jG2dMt3R3P8Pm9E65q18g==",
+      "dev": true,
+      "dependencies": {
+        "color": "^3.1.3"
+      }
+    },
+    "node_modules/@arco-design/web-vue": {
+      "version": "2.32.1",
+      "resolved": "https://registry.npmjs.org/@arco-design/web-vue/-/web-vue-2.32.1.tgz",
+      "integrity": "sha512-dywxT9qNEwxMT8WdqErXI8Nv5kDZR5e1RpCFKiipU8RKx6aZMovm4JNZ3nCIQq/qrkOp+4h203quFEJ35PvomQ==",
+      "dev": true,
+      "dependencies": {
+        "@arco-design/color": "^0.4.0",
+        "b-tween": "^0.3.3",
+        "b-validate": "^1.3.1",
+        "compute-scroll-into-view": "^1.0.17",
+        "dayjs": "^1.10.3",
+        "number-precision": "^1.5.0",
+        "resize-observer-polyfill": "^1.5.1",
+        "scroll-into-view-if-needed": "^2.2.28"
+      },
+      "peerDependencies": {
+        "vue": "^3.1.0"
+      }
+    },
+    "node_modules/@babel/parser": {
+      "version": "7.18.6",
+      "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.18.6.tgz",
+      "integrity": "sha512-uQVSa9jJUe/G/304lXspfWVpKpK4euFLgGiMQFOCpM/bgcAdeoHwi/OQz23O9GK2osz26ZiXRRV9aV+Yl1O8tw==",
+      "bin": {
+        "parser": "bin/babel-parser.js"
+      },
+      "engines": {
+        "node": ">=6.0.0"
+      }
+    },
+    "node_modules/@babel/runtime": {
+      "version": "7.18.6",
+      "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.18.6.tgz",
+      "integrity": "sha512-t9wi7/AW6XtKahAe20Yw0/mMljKq0B1r2fPdvaAdV/KPDZewFXdaaa6K7lxmZBZ8FBNpCiAT6iHPmd6QO9bKfQ==",
+      "dependencies": {
+        "regenerator-runtime": "^0.13.4"
+      },
+      "engines": {
+        "node": ">=6.9.0"
+      }
+    },
+    "node_modules/@ctrl/tinycolor": {
+      "version": "3.4.1",
+      "resolved": "https://registry.npmjs.org/@ctrl/tinycolor/-/tinycolor-3.4.1.tgz",
+      "integrity": "sha512-ej5oVy6lykXsvieQtqZxCOaLT+xD4+QNarq78cIYISHmZXshCvROLudpQN3lfL8G0NL7plMSSK+zlyvCaIJ4Iw==",
+      "engines": {
+        "node": ">=10"
+      }
+    },
+    "node_modules/@eslint/eslintrc": {
+      "version": "1.3.0",
+      "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.3.0.tgz",
+      "integrity": "sha512-UWW0TMTmk2d7hLcWD1/e2g5HDM/HQ3csaLSqXCfqwh4uNDuNqlaKWXmEsL4Cs41Z0KnILNvwbHAah3C2yt06kw==",
+      "dev": true,
+      "dependencies": {
+        "ajv": "^6.12.4",
+        "debug": "^4.3.2",
+        "espree": "^9.3.2",
+        "globals": "^13.15.0",
+        "ignore": "^5.2.0",
+        "import-fresh": "^3.2.1",
+        "js-yaml": "^4.1.0",
+        "minimatch": "^3.1.2",
+        "strip-json-comments": "^3.1.1"
+      },
+      "engines": {
+        "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+      }
+    },
+    "node_modules/@humanwhocodes/config-array": {
+      "version": "0.9.5",
+      "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.9.5.tgz",
+      "integrity": "sha512-ObyMyWxZiCu/yTisA7uzx81s40xR2fD5Cg/2Kq7G02ajkNubJf6BopgDTmDyc3U7sXpNKM8cYOw7s7Tyr+DnCw==",
+      "dev": true,
+      "dependencies": {
+        "@humanwhocodes/object-schema": "^1.2.1",
+        "debug": "^4.1.1",
+        "minimatch": "^3.0.4"
+      },
+      "engines": {
+        "node": ">=10.10.0"
+      }
+    },
+    "node_modules/@humanwhocodes/object-schema": {
+      "version": "1.2.1",
+      "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz",
+      "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==",
+      "dev": true
+    },
+    "node_modules/@nodelib/fs.scandir": {
+      "version": "2.1.5",
+      "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
+      "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==",
+      "dev": true,
+      "dependencies": {
+        "@nodelib/fs.stat": "2.0.5",
+        "run-parallel": "^1.1.9"
+      },
+      "engines": {
+        "node": ">= 8"
+      }
+    },
+    "node_modules/@nodelib/fs.stat": {
+      "version": "2.0.5",
+      "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz",
+      "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==",
+      "dev": true,
+      "engines": {
+        "node": ">= 8"
+      }
+    },
+    "node_modules/@nodelib/fs.walk": {
+      "version": "1.2.8",
+      "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz",
+      "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==",
+      "dev": true,
+      "dependencies": {
+        "@nodelib/fs.scandir": "2.1.5",
+        "fastq": "^1.6.0"
+      },
+      "engines": {
+        "node": ">= 8"
+      }
+    },
+    "node_modules/@rollup/plugin-node-resolve": {
+      "version": "13.3.0",
+      "resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-13.3.0.tgz",
+      "integrity": "sha512-Lus8rbUo1eEcnS4yTFKLZrVumLPY+YayBdWXgFSHYhTT2iJbMhoaaBL3xl5NCdeRytErGr8tZ0L71BMRmnlwSw==",
+      "dev": true,
+      "dependencies": {
+        "@rollup/pluginutils": "^3.1.0",
+        "@types/resolve": "1.17.1",
+        "deepmerge": "^4.2.2",
+        "is-builtin-module": "^3.1.0",
+        "is-module": "^1.0.0",
+        "resolve": "^1.19.0"
+      },
+      "engines": {
+        "node": ">= 10.0.0"
+      },
+      "peerDependencies": {
+        "rollup": "^2.42.0"
+      }
+    },
+    "node_modules/@rollup/pluginutils": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-3.1.0.tgz",
+      "integrity": "sha512-GksZ6pr6TpIjHm8h9lSQ8pi8BE9VeubNT0OMJ3B5uZJ8pz73NPiqOtCog/x2/QzM1ENChPKxMDhiQuRHsqc+lg==",
+      "dev": true,
+      "dependencies": {
+        "@types/estree": "0.0.39",
+        "estree-walker": "^1.0.1",
+        "picomatch": "^2.2.2"
+      },
+      "engines": {
+        "node": ">= 8.0.0"
+      },
+      "peerDependencies": {
+        "rollup": "^1.20.0||^2.0.0"
+      }
+    },
+    "node_modules/@types/estree": {
+      "version": "0.0.39",
+      "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.39.tgz",
+      "integrity": "sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==",
+      "dev": true
+    },
+    "node_modules/@types/mockjs": {
+      "version": "1.0.6",
+      "resolved": "https://registry.npmjs.org/@types/mockjs/-/mockjs-1.0.6.tgz",
+      "integrity": "sha512-Yu5YlqbYZyqsd6LjO4e8ONJDN9pTSnciHDcRP4teNOh/au2b8helFhgRx+3w8xsTFEnwr9jtfTVJbAx+eYmlHA==",
+      "dev": true
+    },
+    "node_modules/@types/node": {
+      "version": "18.0.3",
+      "resolved": "https://registry.npmjs.org/@types/node/-/node-18.0.3.tgz",
+      "integrity": "sha512-HzNRZtp4eepNitP+BD6k2L6DROIDG4Q0fm4x+dwfsr6LGmROENnok75VGw40628xf+iR24WeMFcHuuBDUAzzsQ==",
+      "dev": true
+    },
+    "node_modules/@types/resolve": {
+      "version": "1.17.1",
+      "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.17.1.tgz",
+      "integrity": "sha512-yy7HuzQhj0dhGpD8RLXSZWEkLsV9ibvxvi6EiJ3bkqLAO1RGo0WbkWQiwpRlSFymTJRz0d3k5LM3kkx8ArDbLw==",
+      "dev": true,
+      "dependencies": {
+        "@types/node": "*"
+      }
+    },
+    "node_modules/@vitejs/plugin-vue": {
+      "version": "2.3.3",
+      "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-2.3.3.tgz",
+      "integrity": "sha512-SmQLDyhz+6lGJhPELsBdzXGc+AcaT8stgkbiTFGpXPe8Tl1tJaBw1A6pxDqDuRsVkD8uscrkx3hA7QDOoKYtyw==",
+      "dev": true,
+      "engines": {
+        "node": ">=12.0.0"
+      },
+      "peerDependencies": {
+        "vite": "^2.5.10",
+        "vue": "^3.2.25"
+      }
+    },
+    "node_modules/@vue/compiler-core": {
+      "version": "3.2.37",
+      "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.2.37.tgz",
+      "integrity": "sha512-81KhEjo7YAOh0vQJoSmAD68wLfYqJvoiD4ulyedzF+OEk/bk6/hx3fTNVfuzugIIaTrOx4PGx6pAiBRe5e9Zmg==",
+      "dependencies": {
+        "@babel/parser": "^7.16.4",
+        "@vue/shared": "3.2.37",
+        "estree-walker": "^2.0.2",
+        "source-map": "^0.6.1"
+      }
+    },
+    "node_modules/@vue/compiler-core/node_modules/estree-walker": {
+      "version": "2.0.2",
+      "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz",
+      "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="
+    },
+    "node_modules/@vue/compiler-dom": {
+      "version": "3.2.37",
+      "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.2.37.tgz",
+      "integrity": "sha512-yxJLH167fucHKxaqXpYk7x8z7mMEnXOw3G2q62FTkmsvNxu4FQSu5+3UMb+L7fjKa26DEzhrmCxAgFLLIzVfqQ==",
+      "dependencies": {
+        "@vue/compiler-core": "3.2.37",
+        "@vue/shared": "3.2.37"
+      }
+    },
+    "node_modules/@vue/compiler-sfc": {
+      "version": "3.2.37",
+      "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.2.37.tgz",
+      "integrity": "sha512-+7i/2+9LYlpqDv+KTtWhOZH+pa8/HnX/905MdVmAcI/mPQOBwkHHIzrsEsucyOIZQYMkXUiTkmZq5am/NyXKkg==",
+      "dependencies": {
+        "@babel/parser": "^7.16.4",
+        "@vue/compiler-core": "3.2.37",
+        "@vue/compiler-dom": "3.2.37",
+        "@vue/compiler-ssr": "3.2.37",
+        "@vue/reactivity-transform": "3.2.37",
+        "@vue/shared": "3.2.37",
+        "estree-walker": "^2.0.2",
+        "magic-string": "^0.25.7",
+        "postcss": "^8.1.10",
+        "source-map": "^0.6.1"
+      }
+    },
+    "node_modules/@vue/compiler-sfc/node_modules/estree-walker": {
+      "version": "2.0.2",
+      "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz",
+      "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="
+    },
+    "node_modules/@vue/compiler-ssr": {
+      "version": "3.2.37",
+      "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.2.37.tgz",
+      "integrity": "sha512-7mQJD7HdXxQjktmsWp/J67lThEIcxLemz1Vb5I6rYJHR5vI+lON3nPGOH3ubmbvYGt8xEUaAr1j7/tIFWiEOqw==",
+      "dependencies": {
+        "@vue/compiler-dom": "3.2.37",
+        "@vue/shared": "3.2.37"
+      }
+    },
+    "node_modules/@vue/devtools-api": {
+      "version": "6.2.0",
+      "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-6.2.0.tgz",
+      "integrity": "sha512-pF1G4wky+hkifDiZSWn8xfuLOJI1ZXtuambpBEYaf7Xaf6zC/pM29rvAGpd3qaGXnr4BAXU1Pxz/VfvBGwexGA=="
+    },
+    "node_modules/@vue/reactivity": {
+      "version": "3.2.37",
+      "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.2.37.tgz",
+      "integrity": "sha512-/7WRafBOshOc6m3F7plwzPeCu/RCVv9uMpOwa/5PiY1Zz+WLVRWiy0MYKwmg19KBdGtFWsmZ4cD+LOdVPcs52A==",
+      "dependencies": {
+        "@vue/shared": "3.2.37"
+      }
+    },
+    "node_modules/@vue/reactivity-transform": {
+      "version": "3.2.37",
+      "resolved": "https://registry.npmjs.org/@vue/reactivity-transform/-/reactivity-transform-3.2.37.tgz",
+      "integrity": "sha512-IWopkKEb+8qpu/1eMKVeXrK0NLw9HicGviJzhJDEyfxTR9e1WtpnnbYkJWurX6WwoFP0sz10xQg8yL8lgskAZg==",
+      "dependencies": {
+        "@babel/parser": "^7.16.4",
+        "@vue/compiler-core": "3.2.37",
+        "@vue/shared": "3.2.37",
+        "estree-walker": "^2.0.2",
+        "magic-string": "^0.25.7"
+      }
+    },
+    "node_modules/@vue/reactivity-transform/node_modules/estree-walker": {
+      "version": "2.0.2",
+      "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz",
+      "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="
+    },
+    "node_modules/@vue/runtime-core": {
+      "version": "3.2.37",
+      "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.2.37.tgz",
+      "integrity": "sha512-JPcd9kFyEdXLl/i0ClS7lwgcs0QpUAWj+SKX2ZC3ANKi1U4DOtiEr6cRqFXsPwY5u1L9fAjkinIdB8Rz3FoYNQ==",
+      "dependencies": {
+        "@vue/reactivity": "3.2.37",
+        "@vue/shared": "3.2.37"
+      }
+    },
+    "node_modules/@vue/runtime-dom": {
+      "version": "3.2.37",
+      "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.2.37.tgz",
+      "integrity": "sha512-HimKdh9BepShW6YozwRKAYjYQWg9mQn63RGEiSswMbW+ssIht1MILYlVGkAGGQbkhSh31PCdoUcfiu4apXJoPw==",
+      "dependencies": {
+        "@vue/runtime-core": "3.2.37",
+        "@vue/shared": "3.2.37",
+        "csstype": "^2.6.8"
+      }
+    },
+    "node_modules/@vue/server-renderer": {
+      "version": "3.2.37",
+      "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.2.37.tgz",
+      "integrity": "sha512-kLITEJvaYgZQ2h47hIzPh2K3jG8c1zCVbp/o/bzQOyvzaKiCquKS7AaioPI28GNxIsE/zSx+EwWYsNxDCX95MA==",
+      "dependencies": {
+        "@vue/compiler-ssr": "3.2.37",
+        "@vue/shared": "3.2.37"
+      },
+      "peerDependencies": {
+        "vue": "3.2.37"
+      }
+    },
+    "node_modules/@vue/shared": {
+      "version": "3.2.37",
+      "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.2.37.tgz",
+      "integrity": "sha512-4rSJemR2NQIo9Klm1vabqWjD8rs/ZaJSzMxkMNeJS6lHiUjjUeYFbooN19NgFjztubEKh3WlZUeOLVdbbUWHsw=="
+    },
+    "node_modules/acorn": {
+      "version": "8.7.1",
+      "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.7.1.tgz",
+      "integrity": "sha512-Xx54uLJQZ19lKygFXOWsscKUbsBZW0CPykPhVQdhIeIwrbPmJzqeASDInc8nKBnp/JT6igTs82qPXz069H8I/A==",
+      "dev": true,
+      "bin": {
+        "acorn": "bin/acorn"
+      },
+      "engines": {
+        "node": ">=0.4.0"
+      }
+    },
+    "node_modules/acorn-jsx": {
+      "version": "5.3.2",
+      "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz",
+      "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==",
+      "dev": true,
+      "peerDependencies": {
+        "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0"
+      }
+    },
+    "node_modules/ajv": {
+      "version": "6.12.6",
+      "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
+      "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
+      "dev": true,
+      "dependencies": {
+        "fast-deep-equal": "^3.1.1",
+        "fast-json-stable-stringify": "^2.0.0",
+        "json-schema-traverse": "^0.4.1",
+        "uri-js": "^4.2.2"
+      },
+      "funding": {
+        "type": "github",
+        "url": "https://github.com/sponsors/epoberezkin"
+      }
+    },
+    "node_modules/ansi-regex": {
+      "version": "5.0.1",
+      "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
+      "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
+      "dev": true,
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/ansi-styles": {
+      "version": "4.3.0",
+      "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
+      "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+      "dev": true,
+      "dependencies": {
+        "color-convert": "^2.0.1"
+      },
+      "engines": {
+        "node": ">=8"
+      },
+      "funding": {
+        "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+      }
+    },
+    "node_modules/ansi-styles/node_modules/color-convert": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
+      "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+      "dev": true,
+      "dependencies": {
+        "color-name": "~1.1.4"
+      },
+      "engines": {
+        "node": ">=7.0.0"
+      }
+    },
+    "node_modules/ansi-styles/node_modules/color-name": {
+      "version": "1.1.4",
+      "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
+      "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
+      "dev": true
+    },
+    "node_modules/anymatch": {
+      "version": "3.1.2",
+      "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz",
+      "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==",
+      "dev": true,
+      "dependencies": {
+        "normalize-path": "^3.0.0",
+        "picomatch": "^2.0.4"
+      },
+      "engines": {
+        "node": ">= 8"
+      }
+    },
+    "node_modules/argparse": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
+      "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
+      "dev": true
+    },
+    "node_modules/axios": {
+      "version": "0.26.1",
+      "resolved": "https://registry.npmjs.org/axios/-/axios-0.26.1.tgz",
+      "integrity": "sha512-fPwcX4EvnSHuInCMItEhAGnaSEXRBjtzh9fOtsE6E1G6p7vl7edEeZe11QHf18+6+9gR5PbKV/sGKNaD8YaMeA==",
+      "dependencies": {
+        "follow-redirects": "^1.14.8"
+      }
+    },
+    "node_modules/b-tween": {
+      "version": "0.3.3",
+      "resolved": "https://registry.npmjs.org/b-tween/-/b-tween-0.3.3.tgz",
+      "integrity": "sha512-oEHegcRpA7fAuc9KC4nktucuZn2aS8htymCPcP3qkEGPqiBH+GfqtqoG2l7LxHngg6O0HFM7hOeOYExl1Oz4ZA==",
+      "dev": true
+    },
+    "node_modules/b-validate": {
+      "version": "1.4.1",
+      "resolved": "https://registry.npmjs.org/b-validate/-/b-validate-1.4.1.tgz",
+      "integrity": "sha512-X6ImDku5YY8NfWTh/hX8CAaronWnNXpb159cqs6lDWLtI4OWiehZ4B0NshfatTuKt1HIeNq9PObE/Xl5YoJYUg==",
+      "dev": true
+    },
+    "node_modules/balanced-match": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
+      "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
+      "dev": true
+    },
+    "node_modules/binary-extensions": {
+      "version": "2.2.0",
+      "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz",
+      "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==",
+      "dev": true,
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/boolbase": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz",
+      "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==",
+      "dev": true
+    },
+    "node_modules/brace-expansion": {
+      "version": "1.1.11",
+      "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
+      "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
+      "dev": true,
+      "dependencies": {
+        "balanced-match": "^1.0.0",
+        "concat-map": "0.0.1"
+      }
+    },
+    "node_modules/braces": {
+      "version": "3.0.2",
+      "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
+      "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==",
+      "dev": true,
+      "dependencies": {
+        "fill-range": "^7.0.1"
+      },
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/builtin-modules": {
+      "version": "3.3.0",
+      "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.3.0.tgz",
+      "integrity": "sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==",
+      "dev": true,
+      "engines": {
+        "node": ">=6"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/callsites": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
+      "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==",
+      "dev": true,
+      "engines": {
+        "node": ">=6"
+      }
+    },
+    "node_modules/chalk": {
+      "version": "4.1.2",
+      "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
+      "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
+      "dev": true,
+      "dependencies": {
+        "ansi-styles": "^4.1.0",
+        "supports-color": "^7.1.0"
+      },
+      "engines": {
+        "node": ">=10"
+      },
+      "funding": {
+        "url": "https://github.com/chalk/chalk?sponsor=1"
+      }
+    },
+    "node_modules/chokidar": {
+      "version": "3.5.3",
+      "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz",
+      "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==",
+      "dev": true,
+      "funding": [
+        {
+          "type": "individual",
+          "url": "https://paulmillr.com/funding/"
+        }
+      ],
+      "dependencies": {
+        "anymatch": "~3.1.2",
+        "braces": "~3.0.2",
+        "glob-parent": "~5.1.2",
+        "is-binary-path": "~2.1.0",
+        "is-glob": "~4.0.1",
+        "normalize-path": "~3.0.0",
+        "readdirp": "~3.6.0"
+      },
+      "engines": {
+        "node": ">= 8.10.0"
+      },
+      "optionalDependencies": {
+        "fsevents": "~2.3.2"
+      }
+    },
+    "node_modules/chokidar/node_modules/glob-parent": {
+      "version": "5.1.2",
+      "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
+      "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
+      "dev": true,
+      "dependencies": {
+        "is-glob": "^4.0.1"
+      },
+      "engines": {
+        "node": ">= 6"
+      }
+    },
+    "node_modules/classnames": {
+      "version": "2.3.1",
+      "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.3.1.tgz",
+      "integrity": "sha512-OlQdbZ7gLfGarSqxesMesDa5uz7KFbID8Kpq/SxIoNGDqY8lSYs0D+hhtBXhcdB3rcbXArFr7vlHheLk1voeNA=="
+    },
+    "node_modules/color": {
+      "version": "3.2.1",
+      "resolved": "https://registry.npmjs.org/color/-/color-3.2.1.tgz",
+      "integrity": "sha512-aBl7dZI9ENN6fUGC7mWpMTPNHmWUSNan9tuWN6ahh5ZLNk9baLJOnSMlrQkHcrfFgz2/RigjUVAjdx36VcemKA==",
+      "dev": true,
+      "dependencies": {
+        "color-convert": "^1.9.3",
+        "color-string": "^1.6.0"
+      }
+    },
+    "node_modules/color-convert": {
+      "version": "1.9.3",
+      "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
+      "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
+      "dev": true,
+      "dependencies": {
+        "color-name": "1.1.3"
+      }
+    },
+    "node_modules/color-name": {
+      "version": "1.1.3",
+      "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
+      "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==",
+      "dev": true
+    },
+    "node_modules/color-string": {
+      "version": "1.9.1",
+      "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz",
+      "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==",
+      "dev": true,
+      "dependencies": {
+        "color-name": "^1.0.0",
+        "simple-swizzle": "^0.2.2"
+      }
+    },
+    "node_modules/commander": {
+      "version": "9.3.0",
+      "resolved": "https://registry.npmjs.org/commander/-/commander-9.3.0.tgz",
+      "integrity": "sha512-hv95iU5uXPbK83mjrJKuZyFM/LBAoCV/XhVGkS5Je6tl7sxr6A0ITMw5WoRV46/UaJ46Nllm3Xt7IaJhXTIkzw==",
+      "dev": true,
+      "engines": {
+        "node": "^12.20.0 || >=14"
+      }
+    },
+    "node_modules/compute-scroll-into-view": {
+      "version": "1.0.17",
+      "resolved": "https://registry.npmjs.org/compute-scroll-into-view/-/compute-scroll-into-view-1.0.17.tgz",
+      "integrity": "sha512-j4dx+Fb0URmzbwwMUrhqWM2BEWHdFGx+qZ9qqASHRPqvTYdqvWnHg0H1hIbcyLnvgnoNAVMlwkepyqM3DaIFUg==",
+      "dev": true
+    },
+    "node_modules/concat-map": {
+      "version": "0.0.1",
+      "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
+      "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
+      "dev": true
+    },
+    "node_modules/connect": {
+      "version": "3.7.0",
+      "resolved": "https://registry.npmjs.org/connect/-/connect-3.7.0.tgz",
+      "integrity": "sha512-ZqRXc+tZukToSNmh5C2iWMSoV3X1YUcPbqEM4DkEG5tNQXrQUZCNVGGv3IuicnkMtPfGf3Xtp8WCXs295iQ1pQ==",
+      "dev": true,
+      "dependencies": {
+        "debug": "2.6.9",
+        "finalhandler": "1.1.2",
+        "parseurl": "~1.3.3",
+        "utils-merge": "1.0.1"
+      },
+      "engines": {
+        "node": ">= 0.10.0"
+      }
+    },
+    "node_modules/connect/node_modules/debug": {
+      "version": "2.6.9",
+      "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
+      "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+      "dev": true,
+      "dependencies": {
+        "ms": "2.0.0"
+      }
+    },
+    "node_modules/connect/node_modules/ms": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+      "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
+      "dev": true
+    },
+    "node_modules/cross-spawn": {
+      "version": "7.0.3",
+      "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
+      "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==",
+      "dev": true,
+      "dependencies": {
+        "path-key": "^3.1.0",
+        "shebang-command": "^2.0.0",
+        "which": "^2.0.1"
+      },
+      "engines": {
+        "node": ">= 8"
+      }
+    },
+    "node_modules/cssesc": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz",
+      "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==",
+      "dev": true,
+      "bin": {
+        "cssesc": "bin/cssesc"
+      },
+      "engines": {
+        "node": ">=4"
+      }
+    },
+    "node_modules/csstype": {
+      "version": "2.6.20",
+      "resolved": "https://registry.npmjs.org/csstype/-/csstype-2.6.20.tgz",
+      "integrity": "sha512-/WwNkdXfckNgw6S5R125rrW8ez139lBHWouiBvX8dfMFtcn6V81REDqnH7+CRpRipfYlyU1CmOnOxrmGcFOjeA=="
+    },
+    "node_modules/dayjs": {
+      "version": "1.11.3",
+      "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.3.tgz",
+      "integrity": "sha512-xxwlswWOlGhzgQ4TKzASQkUhqERI3egRNqgV4ScR8wlANA/A9tZ7miXa44vTTKEq5l7vWoL5G57bG3zA+Kow0A==",
+      "dev": true
+    },
+    "node_modules/debug": {
+      "version": "4.3.4",
+      "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
+      "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
+      "dev": true,
+      "dependencies": {
+        "ms": "2.1.2"
+      },
+      "engines": {
+        "node": ">=6.0"
+      },
+      "peerDependenciesMeta": {
+        "supports-color": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/deep-is": {
+      "version": "0.1.4",
+      "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz",
+      "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==",
+      "dev": true
+    },
+    "node_modules/deepmerge": {
+      "version": "4.2.2",
+      "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz",
+      "integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==",
+      "dev": true,
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/doctrine": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz",
+      "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==",
+      "dev": true,
+      "dependencies": {
+        "esutils": "^2.0.2"
+      },
+      "engines": {
+        "node": ">=6.0.0"
+      }
+    },
+    "node_modules/ee-first": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
+      "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==",
+      "dev": true
+    },
+    "node_modules/encodeurl": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
+      "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==",
+      "dev": true,
+      "engines": {
+        "node": ">= 0.8"
+      }
+    },
+    "node_modules/esbuild": {
+      "version": "0.14.48",
+      "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.14.48.tgz",
+      "integrity": "sha512-w6N1Yn5MtqK2U1/WZTX9ZqUVb8IOLZkZ5AdHkT6x3cHDMVsYWC7WPdiLmx19w3i4Rwzy5LqsEMtVihG3e4rFzA==",
+      "dev": true,
+      "hasInstallScript": true,
+      "bin": {
+        "esbuild": "bin/esbuild"
+      },
+      "engines": {
+        "node": ">=12"
+      },
+      "optionalDependencies": {
+        "esbuild-android-64": "0.14.48",
+        "esbuild-android-arm64": "0.14.48",
+        "esbuild-darwin-64": "0.14.48",
+        "esbuild-darwin-arm64": "0.14.48",
+        "esbuild-freebsd-64": "0.14.48",
+        "esbuild-freebsd-arm64": "0.14.48",
+        "esbuild-linux-32": "0.14.48",
+        "esbuild-linux-64": "0.14.48",
+        "esbuild-linux-arm": "0.14.48",
+        "esbuild-linux-arm64": "0.14.48",
+        "esbuild-linux-mips64le": "0.14.48",
+        "esbuild-linux-ppc64le": "0.14.48",
+        "esbuild-linux-riscv64": "0.14.48",
+        "esbuild-linux-s390x": "0.14.48",
+        "esbuild-netbsd-64": "0.14.48",
+        "esbuild-openbsd-64": "0.14.48",
+        "esbuild-sunos-64": "0.14.48",
+        "esbuild-windows-32": "0.14.48",
+        "esbuild-windows-64": "0.14.48",
+        "esbuild-windows-arm64": "0.14.48"
+      }
+    },
+    "node_modules/esbuild-android-64": {
+      "version": "0.14.48",
+      "resolved": "https://registry.npmjs.org/esbuild-android-64/-/esbuild-android-64-0.14.48.tgz",
+      "integrity": "sha512-3aMjboap/kqwCUpGWIjsk20TtxVoKck8/4Tu19rubh7t5Ra0Yrpg30Mt1QXXlipOazrEceGeWurXKeFJgkPOUg==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "android"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/esbuild-android-arm64": {
+      "version": "0.14.48",
+      "resolved": "https://registry.npmjs.org/esbuild-android-arm64/-/esbuild-android-arm64-0.14.48.tgz",
+      "integrity": "sha512-vptI3K0wGALiDq+EvRuZotZrJqkYkN5282iAfcffjI5lmGG9G1ta/CIVauhY42MBXwEgDJkweiDcDMRLzBZC4g==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "android"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/esbuild-darwin-64": {
+      "version": "0.14.48",
+      "resolved": "https://registry.npmjs.org/esbuild-darwin-64/-/esbuild-darwin-64-0.14.48.tgz",
+      "integrity": "sha512-gGQZa4+hab2Va/Zww94YbshLuWteyKGD3+EsVon8EWTWhnHFRm5N9NbALNbwi/7hQ/hM1Zm4FuHg+k6BLsl5UA==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "darwin"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/esbuild-darwin-arm64": {
+      "version": "0.14.48",
+      "resolved": "https://registry.npmjs.org/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.14.48.tgz",
+      "integrity": "sha512-bFjnNEXjhZT+IZ8RvRGNJthLWNHV5JkCtuOFOnjvo5pC0sk2/QVk0Qc06g2PV3J0TcU6kaPC3RN9yy9w2PSLEA==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "darwin"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/esbuild-freebsd-64": {
+      "version": "0.14.48",
+      "resolved": "https://registry.npmjs.org/esbuild-freebsd-64/-/esbuild-freebsd-64-0.14.48.tgz",
+      "integrity": "sha512-1NOlwRxmOsnPcWOGTB10JKAkYSb2nue0oM1AfHWunW/mv3wERfJmnYlGzL3UAOIUXZqW8GeA2mv+QGwq7DToqA==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "freebsd"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/esbuild-freebsd-arm64": {
+      "version": "0.14.48",
+      "resolved": "https://registry.npmjs.org/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.14.48.tgz",
+      "integrity": "sha512-gXqKdO8wabVcYtluAbikDH2jhXp+Klq5oCD5qbVyUG6tFiGhrC9oczKq3vIrrtwcxDQqK6+HDYK8Zrd4bCA9Gw==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "freebsd"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/esbuild-linux-32": {
+      "version": "0.14.48",
+      "resolved": "https://registry.npmjs.org/esbuild-linux-32/-/esbuild-linux-32-0.14.48.tgz",
+      "integrity": "sha512-ghGyDfS289z/LReZQUuuKq9KlTiTspxL8SITBFQFAFRA/IkIvDpnZnCAKTCjGXAmUqroMQfKJXMxyjJA69c/nQ==",
+      "cpu": [
+        "ia32"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/esbuild-linux-64": {
+      "version": "0.14.48",
+      "resolved": "https://registry.npmjs.org/esbuild-linux-64/-/esbuild-linux-64-0.14.48.tgz",
+      "integrity": "sha512-vni3p/gppLMVZLghI7oMqbOZdGmLbbKR23XFARKnszCIBpEMEDxOMNIKPmMItQrmH/iJrL1z8Jt2nynY0bE1ug==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/esbuild-linux-arm": {
+      "version": "0.14.48",
+      "resolved": "https://registry.npmjs.org/esbuild-linux-arm/-/esbuild-linux-arm-0.14.48.tgz",
+      "integrity": "sha512-+VfSV7Akh1XUiDNXgqgY1cUP1i2vjI+BmlyXRfVz5AfV3jbpde8JTs5Q9sYgaoq5cWfuKfoZB/QkGOI+QcL1Tw==",
+      "cpu": [
+        "arm"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/esbuild-linux-arm64": {
+      "version": "0.14.48",
+      "resolved": "https://registry.npmjs.org/esbuild-linux-arm64/-/esbuild-linux-arm64-0.14.48.tgz",
+      "integrity": "sha512-3CFsOlpoxlKPRevEHq8aAntgYGYkE1N9yRYAcPyng/p4Wyx0tPR5SBYsxLKcgPB9mR8chHEhtWYz6EZ+H199Zw==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/esbuild-linux-mips64le": {
+      "version": "0.14.48",
+      "resolved": "https://registry.npmjs.org/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.14.48.tgz",
+      "integrity": "sha512-cs0uOiRlPp6ymknDnjajCgvDMSsLw5mST2UXh+ZIrXTj2Ifyf2aAP3Iw4DiqgnyYLV2O/v/yWBJx+WfmKEpNLA==",
+      "cpu": [
+        "mips64el"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/esbuild-linux-ppc64le": {
+      "version": "0.14.48",
+      "resolved": "https://registry.npmjs.org/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.14.48.tgz",
+      "integrity": "sha512-+2F0vJMkuI0Wie/wcSPDCqXvSFEELH7Jubxb7mpWrA/4NpT+/byjxDz0gG6R1WJoeDefcrMfpBx4GFNN1JQorQ==",
+      "cpu": [
+        "ppc64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/esbuild-linux-riscv64": {
+      "version": "0.14.48",
+      "resolved": "https://registry.npmjs.org/esbuild-linux-riscv64/-/esbuild-linux-riscv64-0.14.48.tgz",
+      "integrity": "sha512-BmaK/GfEE+5F2/QDrIXteFGKnVHGxlnK9MjdVKMTfvtmudjY3k2t8NtlY4qemKSizc+QwyombGWTBDc76rxePA==",
+      "cpu": [
+        "riscv64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/esbuild-linux-s390x": {
+      "version": "0.14.48",
+      "resolved": "https://registry.npmjs.org/esbuild-linux-s390x/-/esbuild-linux-s390x-0.14.48.tgz",
+      "integrity": "sha512-tndw/0B9jiCL+KWKo0TSMaUm5UWBLsfCKVdbfMlb3d5LeV9WbijZ8Ordia8SAYv38VSJWOEt6eDCdOx8LqkC4g==",
+      "cpu": [
+        "s390x"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/esbuild-netbsd-64": {
+      "version": "0.14.48",
+      "resolved": "https://registry.npmjs.org/esbuild-netbsd-64/-/esbuild-netbsd-64-0.14.48.tgz",
+      "integrity": "sha512-V9hgXfwf/T901Lr1wkOfoevtyNkrxmMcRHyticybBUHookznipMOHoF41Al68QBsqBxnITCEpjjd4yAos7z9Tw==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "netbsd"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/esbuild-openbsd-64": {
+      "version": "0.14.48",
+      "resolved": "https://registry.npmjs.org/esbuild-openbsd-64/-/esbuild-openbsd-64-0.14.48.tgz",
+      "integrity": "sha512-+IHf4JcbnnBl4T52egorXMatil/za0awqzg2Vy6FBgPcBpisDWT2sVz/tNdrK9kAqj+GZG/jZdrOkj7wsrNTKA==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "openbsd"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/esbuild-sunos-64": {
+      "version": "0.14.48",
+      "resolved": "https://registry.npmjs.org/esbuild-sunos-64/-/esbuild-sunos-64-0.14.48.tgz",
+      "integrity": "sha512-77m8bsr5wOpOWbGi9KSqDphcq6dFeJyun8TA+12JW/GAjyfTwVtOnN8DOt6DSPUfEV+ltVMNqtXUeTeMAxl5KA==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "sunos"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/esbuild-windows-32": {
+      "version": "0.14.48",
+      "resolved": "https://registry.npmjs.org/esbuild-windows-32/-/esbuild-windows-32-0.14.48.tgz",
+      "integrity": "sha512-EPgRuTPP8vK9maxpTGDe5lSoIBHGKO/AuxDncg5O3NkrPeLNdvvK8oywB0zGaAZXxYWfNNSHskvvDgmfVTguhg==",
+      "cpu": [
+        "ia32"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "win32"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/esbuild-windows-64": {
+      "version": "0.14.48",
+      "resolved": "https://registry.npmjs.org/esbuild-windows-64/-/esbuild-windows-64-0.14.48.tgz",
+      "integrity": "sha512-YmpXjdT1q0b8ictSdGwH3M8VCoqPpK1/UArze3X199w6u8hUx3V8BhAi1WjbsfDYRBanVVtduAhh2sirImtAvA==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "win32"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/esbuild-windows-arm64": {
+      "version": "0.14.48",
+      "resolved": "https://registry.npmjs.org/esbuild-windows-arm64/-/esbuild-windows-arm64-0.14.48.tgz",
+      "integrity": "sha512-HHaOMCsCXp0rz5BT2crTka6MPWVno121NKApsGs/OIW5QC0ggC69YMGs1aJct9/9FSUF4A1xNE/cLvgB5svR4g==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "win32"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/escape-html": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
+      "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==",
+      "dev": true
+    },
+    "node_modules/escape-string-regexp": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
+      "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
+      "dev": true,
+      "engines": {
+        "node": ">=10"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/eslint": {
+      "version": "8.19.0",
+      "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.19.0.tgz",
+      "integrity": "sha512-SXOPj3x9VKvPe81TjjUJCYlV4oJjQw68Uek+AM0X4p+33dj2HY5bpTZOgnQHcG2eAm1mtCU9uNMnJi7exU/kYw==",
+      "dev": true,
+      "dependencies": {
+        "@eslint/eslintrc": "^1.3.0",
+        "@humanwhocodes/config-array": "^0.9.2",
+        "ajv": "^6.10.0",
+        "chalk": "^4.0.0",
+        "cross-spawn": "^7.0.2",
+        "debug": "^4.3.2",
+        "doctrine": "^3.0.0",
+        "escape-string-regexp": "^4.0.0",
+        "eslint-scope": "^7.1.1",
+        "eslint-utils": "^3.0.0",
+        "eslint-visitor-keys": "^3.3.0",
+        "espree": "^9.3.2",
+        "esquery": "^1.4.0",
+        "esutils": "^2.0.2",
+        "fast-deep-equal": "^3.1.3",
+        "file-entry-cache": "^6.0.1",
+        "functional-red-black-tree": "^1.0.1",
+        "glob-parent": "^6.0.1",
+        "globals": "^13.15.0",
+        "ignore": "^5.2.0",
+        "import-fresh": "^3.0.0",
+        "imurmurhash": "^0.1.4",
+        "is-glob": "^4.0.0",
+        "js-yaml": "^4.1.0",
+        "json-stable-stringify-without-jsonify": "^1.0.1",
+        "levn": "^0.4.1",
+        "lodash.merge": "^4.6.2",
+        "minimatch": "^3.1.2",
+        "natural-compare": "^1.4.0",
+        "optionator": "^0.9.1",
+        "regexpp": "^3.2.0",
+        "strip-ansi": "^6.0.1",
+        "strip-json-comments": "^3.1.0",
+        "text-table": "^0.2.0",
+        "v8-compile-cache": "^2.0.3"
+      },
+      "bin": {
+        "eslint": "bin/eslint.js"
+      },
+      "engines": {
+        "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+      },
+      "funding": {
+        "url": "https://opencollective.com/eslint"
+      }
+    },
+    "node_modules/eslint-plugin-vue": {
+      "version": "9.2.0",
+      "resolved": "https://registry.npmjs.org/eslint-plugin-vue/-/eslint-plugin-vue-9.2.0.tgz",
+      "integrity": "sha512-W2hc+NUXoce8sZtWgZ45miQTy6jNyuSdub5aZ1IBune4JDeAyzucYX0TzkrQ1jMO52sNUDYlCIHDoaNePe0p5g==",
+      "dev": true,
+      "dependencies": {
+        "eslint-utils": "^3.0.0",
+        "natural-compare": "^1.4.0",
+        "nth-check": "^2.0.1",
+        "postcss-selector-parser": "^6.0.9",
+        "semver": "^7.3.5",
+        "vue-eslint-parser": "^9.0.1",
+        "xml-name-validator": "^4.0.0"
+      },
+      "engines": {
+        "node": "^14.17.0 || >=16.0.0"
+      },
+      "peerDependencies": {
+        "eslint": "^6.2.0 || ^7.0.0 || ^8.0.0"
+      }
+    },
+    "node_modules/eslint-scope": {
+      "version": "7.1.1",
+      "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.1.1.tgz",
+      "integrity": "sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw==",
+      "dev": true,
+      "dependencies": {
+        "esrecurse": "^4.3.0",
+        "estraverse": "^5.2.0"
+      },
+      "engines": {
+        "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+      }
+    },
+    "node_modules/eslint-utils": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz",
+      "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==",
+      "dev": true,
+      "dependencies": {
+        "eslint-visitor-keys": "^2.0.0"
+      },
+      "engines": {
+        "node": "^10.0.0 || ^12.0.0 || >= 14.0.0"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/mysticatea"
+      },
+      "peerDependencies": {
+        "eslint": ">=5"
+      }
+    },
+    "node_modules/eslint-utils/node_modules/eslint-visitor-keys": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz",
+      "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==",
+      "dev": true,
+      "engines": {
+        "node": ">=10"
+      }
+    },
+    "node_modules/eslint-visitor-keys": {
+      "version": "3.3.0",
+      "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz",
+      "integrity": "sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==",
+      "dev": true,
+      "engines": {
+        "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+      }
+    },
+    "node_modules/espree": {
+      "version": "9.3.2",
+      "resolved": "https://registry.npmjs.org/espree/-/espree-9.3.2.tgz",
+      "integrity": "sha512-D211tC7ZwouTIuY5x9XnS0E9sWNChB7IYKX/Xp5eQj3nFXhqmiUDB9q27y76oFl8jTg3pXcQx/bpxMfs3CIZbA==",
+      "dev": true,
+      "dependencies": {
+        "acorn": "^8.7.1",
+        "acorn-jsx": "^5.3.2",
+        "eslint-visitor-keys": "^3.3.0"
+      },
+      "engines": {
+        "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+      }
+    },
+    "node_modules/esquery": {
+      "version": "1.4.0",
+      "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz",
+      "integrity": "sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==",
+      "dev": true,
+      "dependencies": {
+        "estraverse": "^5.1.0"
+      },
+      "engines": {
+        "node": ">=0.10"
+      }
+    },
+    "node_modules/esrecurse": {
+      "version": "4.3.0",
+      "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz",
+      "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==",
+      "dev": true,
+      "dependencies": {
+        "estraverse": "^5.2.0"
+      },
+      "engines": {
+        "node": ">=4.0"
+      }
+    },
+    "node_modules/estraverse": {
+      "version": "5.3.0",
+      "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz",
+      "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==",
+      "dev": true,
+      "engines": {
+        "node": ">=4.0"
+      }
+    },
+    "node_modules/estree-walker": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-1.0.1.tgz",
+      "integrity": "sha512-1fMXF3YP4pZZVozF8j/ZLfvnR8NSIljt56UhbZ5PeeDmmGHpgpdwQt7ITlGvYaQukCvuBRMLEiKiYC+oeIg4cg==",
+      "dev": true
+    },
+    "node_modules/esutils": {
+      "version": "2.0.3",
+      "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
+      "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==",
+      "dev": true,
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/fast-deep-equal": {
+      "version": "3.1.3",
+      "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
+      "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
+      "dev": true
+    },
+    "node_modules/fast-glob": {
+      "version": "3.2.11",
+      "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.11.tgz",
+      "integrity": "sha512-xrO3+1bxSo3ZVHAnqzyuewYT6aMFHRAd4Kcs92MAonjwQZLsK9d0SF1IyQ3k5PoirxTW0Oe/RqFgMQ6TcNE5Ew==",
+      "dev": true,
+      "dependencies": {
+        "@nodelib/fs.stat": "^2.0.2",
+        "@nodelib/fs.walk": "^1.2.3",
+        "glob-parent": "^5.1.2",
+        "merge2": "^1.3.0",
+        "micromatch": "^4.0.4"
+      },
+      "engines": {
+        "node": ">=8.6.0"
+      }
+    },
+    "node_modules/fast-glob/node_modules/glob-parent": {
+      "version": "5.1.2",
+      "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
+      "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
+      "dev": true,
+      "dependencies": {
+        "is-glob": "^4.0.1"
+      },
+      "engines": {
+        "node": ">= 6"
+      }
+    },
+    "node_modules/fast-json-stable-stringify": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
+      "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==",
+      "dev": true
+    },
+    "node_modules/fast-levenshtein": {
+      "version": "2.0.6",
+      "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz",
+      "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==",
+      "dev": true
+    },
+    "node_modules/fastq": {
+      "version": "1.13.0",
+      "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.13.0.tgz",
+      "integrity": "sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw==",
+      "dev": true,
+      "dependencies": {
+        "reusify": "^1.0.4"
+      }
+    },
+    "node_modules/file-entry-cache": {
+      "version": "6.0.1",
+      "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz",
+      "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==",
+      "dev": true,
+      "dependencies": {
+        "flat-cache": "^3.0.4"
+      },
+      "engines": {
+        "node": "^10.12.0 || >=12.0.0"
+      }
+    },
+    "node_modules/fill-range": {
+      "version": "7.0.1",
+      "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
+      "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==",
+      "dev": true,
+      "dependencies": {
+        "to-regex-range": "^5.0.1"
+      },
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/finalhandler": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz",
+      "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==",
+      "dev": true,
+      "dependencies": {
+        "debug": "2.6.9",
+        "encodeurl": "~1.0.2",
+        "escape-html": "~1.0.3",
+        "on-finished": "~2.3.0",
+        "parseurl": "~1.3.3",
+        "statuses": "~1.5.0",
+        "unpipe": "~1.0.0"
+      },
+      "engines": {
+        "node": ">= 0.8"
+      }
+    },
+    "node_modules/finalhandler/node_modules/debug": {
+      "version": "2.6.9",
+      "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
+      "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+      "dev": true,
+      "dependencies": {
+        "ms": "2.0.0"
+      }
+    },
+    "node_modules/finalhandler/node_modules/ms": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+      "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
+      "dev": true
+    },
+    "node_modules/flat-cache": {
+      "version": "3.0.4",
+      "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz",
+      "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==",
+      "dev": true,
+      "dependencies": {
+        "flatted": "^3.1.0",
+        "rimraf": "^3.0.2"
+      },
+      "engines": {
+        "node": "^10.12.0 || >=12.0.0"
+      }
+    },
+    "node_modules/flatted": {
+      "version": "3.2.6",
+      "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.6.tgz",
+      "integrity": "sha512-0sQoMh9s0BYsm+12Huy/rkKxVu4R1+r96YX5cG44rHV0pQ6iC3Q+mkoMFaGWObMFYQxCVT+ssG1ksneA2MI9KQ==",
+      "dev": true
+    },
+    "node_modules/follow-redirects": {
+      "version": "1.15.1",
+      "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.1.tgz",
+      "integrity": "sha512-yLAMQs+k0b2m7cVxpS1VKJVvoz7SS9Td1zss3XRwXj+ZDH00RJgnuLx7E44wx02kQLrdM3aOOy+FpzS7+8OizA==",
+      "funding": [
+        {
+          "type": "individual",
+          "url": "https://github.com/sponsors/RubenVerborgh"
+        }
+      ],
+      "engines": {
+        "node": ">=4.0"
+      },
+      "peerDependenciesMeta": {
+        "debug": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/fs.realpath": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
+      "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==",
+      "dev": true
+    },
+    "node_modules/fsevents": {
+      "version": "2.3.2",
+      "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
+      "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
+      "dev": true,
+      "hasInstallScript": true,
+      "optional": true,
+      "os": [
+        "darwin"
+      ],
+      "engines": {
+        "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
+      }
+    },
+    "node_modules/function-bind": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
+      "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==",
+      "dev": true
+    },
+    "node_modules/functional-red-black-tree": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz",
+      "integrity": "sha512-dsKNQNdj6xA3T+QlADDA7mOSlX0qiMINjn0cgr+eGHGsbSHzTabcIogz2+p/iqP1Xs6EP/sS2SbqH+brGTbq0g==",
+      "dev": true
+    },
+    "node_modules/glob": {
+      "version": "7.2.3",
+      "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
+      "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
+      "dev": true,
+      "dependencies": {
+        "fs.realpath": "^1.0.0",
+        "inflight": "^1.0.4",
+        "inherits": "2",
+        "minimatch": "^3.1.1",
+        "once": "^1.3.0",
+        "path-is-absolute": "^1.0.0"
+      },
+      "engines": {
+        "node": "*"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/isaacs"
+      }
+    },
+    "node_modules/glob-parent": {
+      "version": "6.0.2",
+      "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
+      "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==",
+      "dev": true,
+      "dependencies": {
+        "is-glob": "^4.0.3"
+      },
+      "engines": {
+        "node": ">=10.13.0"
+      }
+    },
+    "node_modules/globals": {
+      "version": "13.16.0",
+      "resolved": "https://registry.npmjs.org/globals/-/globals-13.16.0.tgz",
+      "integrity": "sha512-A1lrQfpNF+McdPOnnFqY3kSN0AFTy485bTi1bkLk4mVPODIUEcSfhHgRqA+QdXPksrSTTztYXx37NFV+GpGk3Q==",
+      "dev": true,
+      "dependencies": {
+        "type-fest": "^0.20.2"
+      },
+      "engines": {
+        "node": ">=8"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/has": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
+      "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==",
+      "dev": true,
+      "dependencies": {
+        "function-bind": "^1.1.1"
+      },
+      "engines": {
+        "node": ">= 0.4.0"
+      }
+    },
+    "node_modules/has-flag": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
+      "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
+      "dev": true,
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/ignore": {
+      "version": "5.2.0",
+      "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.0.tgz",
+      "integrity": "sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==",
+      "dev": true,
+      "engines": {
+        "node": ">= 4"
+      }
+    },
+    "node_modules/immutable": {
+      "version": "4.1.0",
+      "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.1.0.tgz",
+      "integrity": "sha512-oNkuqVTA8jqG1Q6c+UglTOD1xhC1BtjKI7XkCXRkZHrN5m18/XsnUp8Q89GkQO/z+0WjonSvl0FLhDYftp46nQ==",
+      "dev": true
+    },
+    "node_modules/import-fresh": {
+      "version": "3.3.0",
+      "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz",
+      "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==",
+      "dev": true,
+      "dependencies": {
+        "parent-module": "^1.0.0",
+        "resolve-from": "^4.0.0"
+      },
+      "engines": {
+        "node": ">=6"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/imurmurhash": {
+      "version": "0.1.4",
+      "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
+      "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==",
+      "dev": true,
+      "engines": {
+        "node": ">=0.8.19"
+      }
+    },
+    "node_modules/inflight": {
+      "version": "1.0.6",
+      "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
+      "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==",
+      "dev": true,
+      "dependencies": {
+        "once": "^1.3.0",
+        "wrappy": "1"
+      }
+    },
+    "node_modules/inherits": {
+      "version": "2.0.4",
+      "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
+      "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
+      "dev": true
+    },
+    "node_modules/is-arrayish": {
+      "version": "0.3.2",
+      "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz",
+      "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==",
+      "dev": true
+    },
+    "node_modules/is-binary-path": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
+      "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
+      "dev": true,
+      "dependencies": {
+        "binary-extensions": "^2.0.0"
+      },
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/is-builtin-module": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-3.1.0.tgz",
+      "integrity": "sha512-OV7JjAgOTfAFJmHZLvpSTb4qi0nIILDV1gWPYDnDJUTNFM5aGlRAhk4QcT8i7TuAleeEV5Fdkqn3t4mS+Q11fg==",
+      "dev": true,
+      "dependencies": {
+        "builtin-modules": "^3.0.0"
+      },
+      "engines": {
+        "node": ">=6"
+      }
+    },
+    "node_modules/is-core-module": {
+      "version": "2.9.0",
+      "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.9.0.tgz",
+      "integrity": "sha512-+5FPy5PnwmO3lvfMb0AsoPaBG+5KHUI0wYFXOtYPnVVVspTFUuMZNfNaNVRt3FZadstu2c8x23vykRW/NBoU6A==",
+      "dev": true,
+      "dependencies": {
+        "has": "^1.0.3"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/is-extglob": {
+      "version": "2.1.1",
+      "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
+      "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
+      "dev": true,
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/is-glob": {
+      "version": "4.0.3",
+      "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
+      "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
+      "dev": true,
+      "dependencies": {
+        "is-extglob": "^2.1.1"
+      },
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/is-module": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz",
+      "integrity": "sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g==",
+      "dev": true
+    },
+    "node_modules/is-number": {
+      "version": "7.0.0",
+      "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
+      "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
+      "dev": true,
+      "engines": {
+        "node": ">=0.12.0"
+      }
+    },
+    "node_modules/isexe": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
+      "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
+      "dev": true
+    },
+    "node_modules/js-tokens": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
+      "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
+      "peer": true
+    },
+    "node_modules/js-yaml": {
+      "version": "4.1.0",
+      "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
+      "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
+      "dev": true,
+      "dependencies": {
+        "argparse": "^2.0.1"
+      },
+      "bin": {
+        "js-yaml": "bin/js-yaml.js"
+      }
+    },
+    "node_modules/json-schema-traverse": {
+      "version": "0.4.1",
+      "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
+      "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
+      "dev": true
+    },
+    "node_modules/json-stable-stringify-without-jsonify": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz",
+      "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==",
+      "dev": true
+    },
+    "node_modules/levn": {
+      "version": "0.4.1",
+      "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz",
+      "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==",
+      "dev": true,
+      "dependencies": {
+        "prelude-ls": "^1.2.1",
+        "type-check": "~0.4.0"
+      },
+      "engines": {
+        "node": ">= 0.8.0"
+      }
+    },
+    "node_modules/lodash": {
+      "version": "4.17.21",
+      "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
+      "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
+      "dev": true
+    },
+    "node_modules/lodash.merge": {
+      "version": "4.6.2",
+      "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
+      "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==",
+      "dev": true
+    },
+    "node_modules/loose-envify": {
+      "version": "1.4.0",
+      "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
+      "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
+      "peer": true,
+      "dependencies": {
+        "js-tokens": "^3.0.0 || ^4.0.0"
+      },
+      "bin": {
+        "loose-envify": "cli.js"
+      }
+    },
+    "node_modules/lru-cache": {
+      "version": "6.0.0",
+      "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
+      "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
+      "dev": true,
+      "dependencies": {
+        "yallist": "^4.0.0"
+      },
+      "engines": {
+        "node": ">=10"
+      }
+    },
+    "node_modules/magic-string": {
+      "version": "0.25.9",
+      "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.9.tgz",
+      "integrity": "sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==",
+      "dependencies": {
+        "sourcemap-codec": "^1.4.8"
+      }
+    },
+    "node_modules/merge2": {
+      "version": "1.4.1",
+      "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
+      "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==",
+      "dev": true,
+      "engines": {
+        "node": ">= 8"
+      }
+    },
+    "node_modules/micromatch": {
+      "version": "4.0.5",
+      "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz",
+      "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==",
+      "dev": true,
+      "dependencies": {
+        "braces": "^3.0.2",
+        "picomatch": "^2.3.1"
+      },
+      "engines": {
+        "node": ">=8.6"
+      }
+    },
+    "node_modules/minimatch": {
+      "version": "3.1.2",
+      "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
+      "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
+      "dev": true,
+      "dependencies": {
+        "brace-expansion": "^1.1.7"
+      },
+      "engines": {
+        "node": "*"
+      }
+    },
+    "node_modules/mockjs": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/mockjs/-/mockjs-1.1.0.tgz",
+      "integrity": "sha512-eQsKcWzIaZzEZ07NuEyO4Nw65g0hdWAyurVol1IPl1gahRwY+svqzfgfey8U8dahLwG44d6/RwEzuK52rSa/JQ==",
+      "dev": true,
+      "dependencies": {
+        "commander": "*"
+      },
+      "bin": {
+        "random": "bin/random"
+      }
+    },
+    "node_modules/ms": {
+      "version": "2.1.2",
+      "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
+      "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
+      "dev": true
+    },
+    "node_modules/nanoid": {
+      "version": "3.3.4",
+      "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz",
+      "integrity": "sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==",
+      "bin": {
+        "nanoid": "bin/nanoid.cjs"
+      },
+      "engines": {
+        "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
+      }
+    },
+    "node_modules/natural-compare": {
+      "version": "1.4.0",
+      "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz",
+      "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==",
+      "dev": true
+    },
+    "node_modules/normalize-path": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
+      "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
+      "dev": true,
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/nth-check": {
+      "version": "2.1.1",
+      "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz",
+      "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==",
+      "dev": true,
+      "dependencies": {
+        "boolbase": "^1.0.0"
+      },
+      "funding": {
+        "url": "https://github.com/fb55/nth-check?sponsor=1"
+      }
+    },
+    "node_modules/number-precision": {
+      "version": "1.5.2",
+      "resolved": "https://registry.npmjs.org/number-precision/-/number-precision-1.5.2.tgz",
+      "integrity": "sha512-q7C1ZW3FyjsJ+IpGB6ykX8OWWa5+6M+hEY0zXBlzq1Sq1IPY9GeI3CQ9b2i6CMIYoeSuFhop2Av/OhCxClXqag==",
+      "dev": true
+    },
+    "node_modules/on-finished": {
+      "version": "2.3.0",
+      "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz",
+      "integrity": "sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==",
+      "dev": true,
+      "dependencies": {
+        "ee-first": "1.1.1"
+      },
+      "engines": {
+        "node": ">= 0.8"
+      }
+    },
+    "node_modules/once": {
+      "version": "1.4.0",
+      "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
+      "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
+      "dev": true,
+      "dependencies": {
+        "wrappy": "1"
+      }
+    },
+    "node_modules/optionator": {
+      "version": "0.9.1",
+      "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz",
+      "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==",
+      "dev": true,
+      "dependencies": {
+        "deep-is": "^0.1.3",
+        "fast-levenshtein": "^2.0.6",
+        "levn": "^0.4.1",
+        "prelude-ls": "^1.2.1",
+        "type-check": "^0.4.0",
+        "word-wrap": "^1.2.3"
+      },
+      "engines": {
+        "node": ">= 0.8.0"
+      }
+    },
+    "node_modules/parent-module": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
+      "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==",
+      "dev": true,
+      "dependencies": {
+        "callsites": "^3.0.0"
+      },
+      "engines": {
+        "node": ">=6"
+      }
+    },
+    "node_modules/parseurl": {
+      "version": "1.3.3",
+      "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
+      "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==",
+      "dev": true,
+      "engines": {
+        "node": ">= 0.8"
+      }
+    },
+    "node_modules/path-is-absolute": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
+      "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==",
+      "dev": true,
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/path-key": {
+      "version": "3.1.1",
+      "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
+      "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
+      "dev": true,
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/path-parse": {
+      "version": "1.0.7",
+      "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
+      "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
+      "dev": true
+    },
+    "node_modules/path-to-regexp": {
+      "version": "6.2.1",
+      "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.2.1.tgz",
+      "integrity": "sha512-JLyh7xT1kizaEvcaXOQwOc2/Yhw6KZOvPf1S8401UyLk86CU79LN3vl7ztXGm/pZ+YjoyAJ4rxmHwbkBXJX+yw==",
+      "dev": true
+    },
+    "node_modules/picocolors": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz",
+      "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ=="
+    },
+    "node_modules/picomatch": {
+      "version": "2.3.1",
+      "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
+      "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
+      "dev": true,
+      "engines": {
+        "node": ">=8.6"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/jonschlinkert"
+      }
+    },
+    "node_modules/pinia": {
+      "version": "2.0.14",
+      "resolved": "https://registry.npmjs.org/pinia/-/pinia-2.0.14.tgz",
+      "integrity": "sha512-0nPuZR4TetT/WcLN+feMSjWJku3SQU7dBbXC6uw+R6FLQJCsg+/0pzXyD82T1FmAYe0lsx+jnEDQ1BLgkRKlxA==",
+      "dependencies": {
+        "@vue/devtools-api": "^6.1.4",
+        "vue-demi": "*"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/posva"
+      },
+      "peerDependencies": {
+        "@vue/composition-api": "^1.4.0",
+        "typescript": ">=4.4.4",
+        "vue": "^2.6.14 || ^3.2.0"
+      },
+      "peerDependenciesMeta": {
+        "@vue/composition-api": {
+          "optional": true
+        },
+        "typescript": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/pinia/node_modules/vue-demi": {
+      "version": "0.13.2",
+      "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.13.2.tgz",
+      "integrity": "sha512-41ukrclEbMddAyP7PvxMSYqnOSzPV6r7GNnyTSKSCNTaz19GehxmTiXyP9kwHSUv2+Dr6hHqiUiF7L1VAw2KdQ==",
+      "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/postcss": {
+      "version": "8.4.14",
+      "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.14.tgz",
+      "integrity": "sha512-E398TUmfAYFPBSdzgeieK2Y1+1cpdxJx8yXbK/m57nRhKSmk1GB2tO4lbLBtlkfPQTDKfe4Xqv1ASWPpayPEig==",
+      "funding": [
+        {
+          "type": "opencollective",
+          "url": "https://opencollective.com/postcss/"
+        },
+        {
+          "type": "tidelift",
+          "url": "https://tidelift.com/funding/github/npm/postcss"
+        }
+      ],
+      "dependencies": {
+        "nanoid": "^3.3.4",
+        "picocolors": "^1.0.0",
+        "source-map-js": "^1.0.2"
+      },
+      "engines": {
+        "node": "^10 || ^12 || >=14"
+      }
+    },
+    "node_modules/postcss-selector-parser": {
+      "version": "6.0.10",
+      "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.10.tgz",
+      "integrity": "sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w==",
+      "dev": true,
+      "dependencies": {
+        "cssesc": "^3.0.0",
+        "util-deprecate": "^1.0.2"
+      },
+      "engines": {
+        "node": ">=4"
+      }
+    },
+    "node_modules/prelude-ls": {
+      "version": "1.2.1",
+      "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
+      "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==",
+      "dev": true,
+      "engines": {
+        "node": ">= 0.8.0"
+      }
+    },
+    "node_modules/punycode": {
+      "version": "2.1.1",
+      "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz",
+      "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==",
+      "dev": true,
+      "engines": {
+        "node": ">=6"
+      }
+    },
+    "node_modules/queue-microtask": {
+      "version": "1.2.3",
+      "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
+      "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==",
+      "dev": true,
+      "funding": [
+        {
+          "type": "github",
+          "url": "https://github.com/sponsors/feross"
+        },
+        {
+          "type": "patreon",
+          "url": "https://www.patreon.com/feross"
+        },
+        {
+          "type": "consulting",
+          "url": "https://feross.org/support"
+        }
+      ]
+    },
+    "node_modules/rc-util": {
+      "version": "5.22.5",
+      "resolved": "https://registry.npmjs.org/rc-util/-/rc-util-5.22.5.tgz",
+      "integrity": "sha512-awD2TGMGU97OZftT2R3JwrHWjR8k/xIwqjwcivPskciweUdgXE7QsyXkBKVSBHXS+c17AWWMDWuKWsJSheQy8g==",
+      "dependencies": {
+        "@babel/runtime": "^7.18.3",
+        "react-is": "^16.12.0",
+        "shallowequal": "^1.1.0"
+      },
+      "peerDependencies": {
+        "react": ">=16.9.0",
+        "react-dom": ">=16.9.0"
+      }
+    },
+    "node_modules/react": {
+      "version": "18.2.0",
+      "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz",
+      "integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==",
+      "peer": true,
+      "dependencies": {
+        "loose-envify": "^1.1.0"
+      },
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/react-dom": {
+      "version": "18.2.0",
+      "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz",
+      "integrity": "sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==",
+      "peer": true,
+      "dependencies": {
+        "loose-envify": "^1.1.0",
+        "scheduler": "^0.23.0"
+      },
+      "peerDependencies": {
+        "react": "^18.2.0"
+      }
+    },
+    "node_modules/react-is": {
+      "version": "16.13.1",
+      "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
+      "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
+    },
+    "node_modules/readdirp": {
+      "version": "3.6.0",
+      "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
+      "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
+      "dev": true,
+      "dependencies": {
+        "picomatch": "^2.2.1"
+      },
+      "engines": {
+        "node": ">=8.10.0"
+      }
+    },
+    "node_modules/regenerator-runtime": {
+      "version": "0.13.9",
+      "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz",
+      "integrity": "sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA=="
+    },
+    "node_modules/regexpp": {
+      "version": "3.2.0",
+      "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz",
+      "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==",
+      "dev": true,
+      "engines": {
+        "node": ">=8"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/mysticatea"
+      }
+    },
+    "node_modules/resize-observer-polyfill": {
+      "version": "1.5.1",
+      "resolved": "https://registry.npmjs.org/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz",
+      "integrity": "sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg==",
+      "dev": true
+    },
+    "node_modules/resolve": {
+      "version": "1.22.1",
+      "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz",
+      "integrity": "sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==",
+      "dev": true,
+      "dependencies": {
+        "is-core-module": "^2.9.0",
+        "path-parse": "^1.0.7",
+        "supports-preserve-symlinks-flag": "^1.0.0"
+      },
+      "bin": {
+        "resolve": "bin/resolve"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/resolve-from": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
+      "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==",
+      "dev": true,
+      "engines": {
+        "node": ">=4"
+      }
+    },
+    "node_modules/reusify": {
+      "version": "1.0.4",
+      "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz",
+      "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==",
+      "dev": true,
+      "engines": {
+        "iojs": ">=1.0.0",
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/rimraf": {
+      "version": "3.0.2",
+      "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",
+      "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==",
+      "dev": true,
+      "dependencies": {
+        "glob": "^7.1.3"
+      },
+      "bin": {
+        "rimraf": "bin.js"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/isaacs"
+      }
+    },
+    "node_modules/rollup": {
+      "version": "2.75.7",
+      "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.75.7.tgz",
+      "integrity": "sha512-VSE1iy0eaAYNCxEXaleThdFXqZJ42qDBatAwrfnPlENEZ8erQ+0LYX4JXOLPceWfZpV1VtZwZ3dFCuOZiSyFtQ==",
+      "dev": true,
+      "bin": {
+        "rollup": "dist/bin/rollup"
+      },
+      "engines": {
+        "node": ">=10.0.0"
+      },
+      "optionalDependencies": {
+        "fsevents": "~2.3.2"
+      }
+    },
+    "node_modules/run-parallel": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz",
+      "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==",
+      "dev": true,
+      "funding": [
+        {
+          "type": "github",
+          "url": "https://github.com/sponsors/feross"
+        },
+        {
+          "type": "patreon",
+          "url": "https://www.patreon.com/feross"
+        },
+        {
+          "type": "consulting",
+          "url": "https://feross.org/support"
+        }
+      ],
+      "dependencies": {
+        "queue-microtask": "^1.2.2"
+      }
+    },
+    "node_modules/sass": {
+      "version": "1.53.0",
+      "resolved": "https://registry.npmjs.org/sass/-/sass-1.53.0.tgz",
+      "integrity": "sha512-zb/oMirbKhUgRQ0/GFz8TSAwRq2IlR29vOUJZOx0l8sV+CkHUfHa4u5nqrG+1VceZp7Jfj59SVW9ogdhTvJDcQ==",
+      "dev": true,
+      "dependencies": {
+        "chokidar": ">=3.0.0 <4.0.0",
+        "immutable": "^4.0.0",
+        "source-map-js": ">=0.6.2 <2.0.0"
+      },
+      "bin": {
+        "sass": "sass.js"
+      },
+      "engines": {
+        "node": ">=12.0.0"
+      }
+    },
+    "node_modules/scheduler": {
+      "version": "0.23.0",
+      "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.0.tgz",
+      "integrity": "sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==",
+      "peer": true,
+      "dependencies": {
+        "loose-envify": "^1.1.0"
+      }
+    },
+    "node_modules/scroll-into-view-if-needed": {
+      "version": "2.2.29",
+      "resolved": "https://registry.npmjs.org/scroll-into-view-if-needed/-/scroll-into-view-if-needed-2.2.29.tgz",
+      "integrity": "sha512-hxpAR6AN+Gh53AdAimHM6C8oTN1ppwVZITihix+WqalywBeFcQ6LdQP5ABNl26nX8GTEL7VT+b8lKpdqq65wXg==",
+      "dev": true,
+      "dependencies": {
+        "compute-scroll-into-view": "^1.0.17"
+      }
+    },
+    "node_modules/semver": {
+      "version": "7.3.7",
+      "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz",
+      "integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==",
+      "dev": true,
+      "dependencies": {
+        "lru-cache": "^6.0.0"
+      },
+      "bin": {
+        "semver": "bin/semver.js"
+      },
+      "engines": {
+        "node": ">=10"
+      }
+    },
+    "node_modules/shallowequal": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz",
+      "integrity": "sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ=="
+    },
+    "node_modules/shebang-command": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
+      "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
+      "dev": true,
+      "dependencies": {
+        "shebang-regex": "^3.0.0"
+      },
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/shebang-regex": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
+      "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
+      "dev": true,
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/simple-swizzle": {
+      "version": "0.2.2",
+      "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz",
+      "integrity": "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==",
+      "dev": true,
+      "dependencies": {
+        "is-arrayish": "^0.3.1"
+      }
+    },
+    "node_modules/source-map": {
+      "version": "0.6.1",
+      "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
+      "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/source-map-js": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz",
+      "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==",
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/sourcemap-codec": {
+      "version": "1.4.8",
+      "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz",
+      "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA=="
+    },
+    "node_modules/statuses": {
+      "version": "1.5.0",
+      "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz",
+      "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==",
+      "dev": true,
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/strip-ansi": {
+      "version": "6.0.1",
+      "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+      "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+      "dev": true,
+      "dependencies": {
+        "ansi-regex": "^5.0.1"
+      },
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/strip-json-comments": {
+      "version": "3.1.1",
+      "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz",
+      "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==",
+      "dev": true,
+      "engines": {
+        "node": ">=8"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/supports-color": {
+      "version": "7.2.0",
+      "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
+      "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
+      "dev": true,
+      "dependencies": {
+        "has-flag": "^4.0.0"
+      },
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/supports-preserve-symlinks-flag": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
+      "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==",
+      "dev": true,
+      "engines": {
+        "node": ">= 0.4"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/text-table": {
+      "version": "0.2.0",
+      "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz",
+      "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==",
+      "dev": true
+    },
+    "node_modules/to-regex-range": {
+      "version": "5.0.1",
+      "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
+      "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
+      "dev": true,
+      "dependencies": {
+        "is-number": "^7.0.0"
+      },
+      "engines": {
+        "node": ">=8.0"
+      }
+    },
+    "node_modules/type-check": {
+      "version": "0.4.0",
+      "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
+      "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==",
+      "dev": true,
+      "dependencies": {
+        "prelude-ls": "^1.2.1"
+      },
+      "engines": {
+        "node": ">= 0.8.0"
+      }
+    },
+    "node_modules/type-fest": {
+      "version": "0.20.2",
+      "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz",
+      "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==",
+      "dev": true,
+      "engines": {
+        "node": ">=10"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/unpipe": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
+      "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==",
+      "dev": true,
+      "engines": {
+        "node": ">= 0.8"
+      }
+    },
+    "node_modules/uri-js": {
+      "version": "4.4.1",
+      "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
+      "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==",
+      "dev": true,
+      "dependencies": {
+        "punycode": "^2.1.0"
+      }
+    },
+    "node_modules/util-deprecate": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
+      "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
+      "dev": true
+    },
+    "node_modules/utils-merge": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
+      "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==",
+      "dev": true,
+      "engines": {
+        "node": ">= 0.4.0"
+      }
+    },
+    "node_modules/v8-compile-cache": {
+      "version": "2.3.0",
+      "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz",
+      "integrity": "sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==",
+      "dev": true
+    },
+    "node_modules/vite": {
+      "version": "2.9.13",
+      "resolved": "https://registry.npmjs.org/vite/-/vite-2.9.13.tgz",
+      "integrity": "sha512-AsOBAaT0AD7Mhe8DuK+/kE4aWYFMx/i0ZNi98hJclxb4e0OhQcZYUrvLjIaQ8e59Ui7txcvKMiJC1yftqpQoDw==",
+      "dev": true,
+      "dependencies": {
+        "esbuild": "^0.14.27",
+        "postcss": "^8.4.13",
+        "resolve": "^1.22.0",
+        "rollup": "^2.59.0"
+      },
+      "bin": {
+        "vite": "bin/vite.js"
+      },
+      "engines": {
+        "node": ">=12.2.0"
+      },
+      "optionalDependencies": {
+        "fsevents": "~2.3.2"
+      },
+      "peerDependencies": {
+        "less": "*",
+        "sass": "*",
+        "stylus": "*"
+      },
+      "peerDependenciesMeta": {
+        "less": {
+          "optional": true
+        },
+        "sass": {
+          "optional": true
+        },
+        "stylus": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/vite-plugin-mock": {
+      "version": "2.9.6",
+      "resolved": "https://registry.npmjs.org/vite-plugin-mock/-/vite-plugin-mock-2.9.6.tgz",
+      "integrity": "sha512-/Rm59oPppe/ncbkSrUuAxIQihlI2YcBmnbR4ST1RA2VzM1C0tEQc1KlbQvnUGhXECAGTaQN2JyasiwXP6EtKgg==",
+      "dev": true,
+      "dependencies": {
+        "@rollup/plugin-node-resolve": "^13.0.4",
+        "@types/mockjs": "^1.0.4",
+        "chalk": "^4.1.2",
+        "chokidar": "^3.5.2",
+        "connect": "^3.7.0",
+        "debug": "^4.3.2",
+        "esbuild": "0.11.3",
+        "fast-glob": "^3.2.7",
+        "path-to-regexp": "^6.2.0"
+      },
+      "engines": {
+        "node": ">=12.0.0"
+      },
+      "peerDependencies": {
+        "mockjs": ">=1.1.0",
+        "vite": ">=2.0.0"
+      }
+    },
+    "node_modules/vite-plugin-mock/node_modules/esbuild": {
+      "version": "0.11.3",
+      "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.11.3.tgz",
+      "integrity": "sha512-BzVRHcCtFepjS9WcqRjqoIxLqgpK21a8J4Zi4msSGxDxiXVO1IbcqT1KjhdDDnJxKfe7bvzZrvMEX+bVO0Elcw==",
+      "dev": true,
+      "hasInstallScript": true,
+      "bin": {
+        "esbuild": "bin/esbuild"
+      }
+    },
+    "node_modules/vue": {
+      "version": "3.2.37",
+      "resolved": "https://registry.npmjs.org/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"
+      }
+    },
+    "node_modules/vue-eslint-parser": {
+      "version": "9.0.3",
+      "resolved": "https://registry.npmjs.org/vue-eslint-parser/-/vue-eslint-parser-9.0.3.tgz",
+      "integrity": "sha512-yL+ZDb+9T0ELG4VIFo/2anAOz8SvBdlqEnQnvJ3M7Scq56DvtjY0VY88bByRZB0D4J0u8olBcfrXTVONXsh4og==",
+      "dev": true,
+      "dependencies": {
+        "debug": "^4.3.4",
+        "eslint-scope": "^7.1.1",
+        "eslint-visitor-keys": "^3.3.0",
+        "espree": "^9.3.1",
+        "esquery": "^1.4.0",
+        "lodash": "^4.17.21",
+        "semver": "^7.3.6"
+      },
+      "engines": {
+        "node": "^14.17.0 || >=16.0.0"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/mysticatea"
+      },
+      "peerDependencies": {
+        "eslint": ">=6.0.0"
+      }
+    },
+    "node_modules/vue-router": {
+      "version": "4.1.1",
+      "resolved": "https://registry.npmjs.org/vue-router/-/vue-router-4.1.1.tgz",
+      "integrity": "sha512-Wp1mEf2xCwT0ez7o9JvgpfBp9JGnVb+dPERzXDbugTatzJAJ60VWOhJKifQty85k+jOreoFHER4r5fu062PhPw==",
+      "dependencies": {
+        "@vue/devtools-api": "^6.1.4"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/posva"
+      },
+      "peerDependencies": {
+        "vue": "^3.2.0"
+      }
+    },
+    "node_modules/which": {
+      "version": "2.0.2",
+      "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
+      "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
+      "dev": true,
+      "dependencies": {
+        "isexe": "^2.0.0"
+      },
+      "bin": {
+        "node-which": "bin/node-which"
+      },
+      "engines": {
+        "node": ">= 8"
+      }
+    },
+    "node_modules/word-wrap": {
+      "version": "1.2.3",
+      "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz",
+      "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==",
+      "dev": true,
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/wrappy": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
+      "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
+      "dev": true
+    },
+    "node_modules/xml-name-validator": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-4.0.0.tgz",
+      "integrity": "sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==",
+      "dev": true,
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/yallist": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
+      "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
+      "dev": true
+    }
+  },
+  "dependencies": {
+    "@ant-design/colors": {
+      "version": "6.0.0",
+      "resolved": "https://registry.npmjs.org/@ant-design/colors/-/colors-6.0.0.tgz",
+      "integrity": "sha512-qAZRvPzfdWHtfameEGP2Qvuf838NhergR35o+EuVyB5XvSA98xod5r4utvi4TJ3ywmevm290g9nsCG5MryrdWQ==",
+      "requires": {
+        "@ctrl/tinycolor": "^3.4.0"
+      }
+    },
+    "@ant-design/icons": {
+      "version": "4.7.0",
+      "resolved": "https://registry.npmjs.org/@ant-design/icons/-/icons-4.7.0.tgz",
+      "integrity": "sha512-aoB4Z7JA431rt6d4u+8xcNPPCrdufSRMUOpxa1ab6mz1JCQZOEVolj2WVs/tDFmN62zzK30mNelEsprLYsSF3g==",
+      "requires": {
+        "@ant-design/colors": "^6.0.0",
+        "@ant-design/icons-svg": "^4.2.1",
+        "@babel/runtime": "^7.11.2",
+        "classnames": "^2.2.6",
+        "rc-util": "^5.9.4"
+      }
+    },
+    "@ant-design/icons-svg": {
+      "version": "4.2.1",
+      "resolved": "https://registry.npmjs.org/@ant-design/icons-svg/-/icons-svg-4.2.1.tgz",
+      "integrity": "sha512-EB0iwlKDGpG93hW8f85CTJTs4SvMX7tt5ceupvhALp1IF44SeUFOMhKUOYqpsoYWQKAOuTRDMqn75rEaKDp0Xw=="
+    },
+    "@arco-design/color": {
+      "version": "0.4.0",
+      "resolved": "https://registry.npmjs.org/@arco-design/color/-/color-0.4.0.tgz",
+      "integrity": "sha512-s7p9MSwJgHeL8DwcATaXvWT3m2SigKpxx4JA1BGPHL4gfvaQsmQfrLBDpjOJFJuJ2jG2dMt3R3P8Pm9E65q18g==",
+      "dev": true,
+      "requires": {
+        "color": "^3.1.3"
+      }
+    },
+    "@arco-design/web-vue": {
+      "version": "2.32.1",
+      "resolved": "https://registry.npmjs.org/@arco-design/web-vue/-/web-vue-2.32.1.tgz",
+      "integrity": "sha512-dywxT9qNEwxMT8WdqErXI8Nv5kDZR5e1RpCFKiipU8RKx6aZMovm4JNZ3nCIQq/qrkOp+4h203quFEJ35PvomQ==",
+      "dev": true,
+      "requires": {
+        "@arco-design/color": "^0.4.0",
+        "b-tween": "^0.3.3",
+        "b-validate": "^1.3.1",
+        "compute-scroll-into-view": "^1.0.17",
+        "dayjs": "^1.10.3",
+        "number-precision": "^1.5.0",
+        "resize-observer-polyfill": "^1.5.1",
+        "scroll-into-view-if-needed": "^2.2.28"
+      }
+    },
+    "@babel/parser": {
+      "version": "7.18.6",
+      "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.18.6.tgz",
+      "integrity": "sha512-uQVSa9jJUe/G/304lXspfWVpKpK4euFLgGiMQFOCpM/bgcAdeoHwi/OQz23O9GK2osz26ZiXRRV9aV+Yl1O8tw=="
+    },
+    "@babel/runtime": {
+      "version": "7.18.6",
+      "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.18.6.tgz",
+      "integrity": "sha512-t9wi7/AW6XtKahAe20Yw0/mMljKq0B1r2fPdvaAdV/KPDZewFXdaaa6K7lxmZBZ8FBNpCiAT6iHPmd6QO9bKfQ==",
+      "requires": {
+        "regenerator-runtime": "^0.13.4"
+      }
+    },
+    "@ctrl/tinycolor": {
+      "version": "3.4.1",
+      "resolved": "https://registry.npmjs.org/@ctrl/tinycolor/-/tinycolor-3.4.1.tgz",
+      "integrity": "sha512-ej5oVy6lykXsvieQtqZxCOaLT+xD4+QNarq78cIYISHmZXshCvROLudpQN3lfL8G0NL7plMSSK+zlyvCaIJ4Iw=="
+    },
+    "@eslint/eslintrc": {
+      "version": "1.3.0",
+      "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.3.0.tgz",
+      "integrity": "sha512-UWW0TMTmk2d7hLcWD1/e2g5HDM/HQ3csaLSqXCfqwh4uNDuNqlaKWXmEsL4Cs41Z0KnILNvwbHAah3C2yt06kw==",
+      "dev": true,
+      "requires": {
+        "ajv": "^6.12.4",
+        "debug": "^4.3.2",
+        "espree": "^9.3.2",
+        "globals": "^13.15.0",
+        "ignore": "^5.2.0",
+        "import-fresh": "^3.2.1",
+        "js-yaml": "^4.1.0",
+        "minimatch": "^3.1.2",
+        "strip-json-comments": "^3.1.1"
+      }
+    },
+    "@humanwhocodes/config-array": {
+      "version": "0.9.5",
+      "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.9.5.tgz",
+      "integrity": "sha512-ObyMyWxZiCu/yTisA7uzx81s40xR2fD5Cg/2Kq7G02ajkNubJf6BopgDTmDyc3U7sXpNKM8cYOw7s7Tyr+DnCw==",
+      "dev": true,
+      "requires": {
+        "@humanwhocodes/object-schema": "^1.2.1",
+        "debug": "^4.1.1",
+        "minimatch": "^3.0.4"
+      }
+    },
+    "@humanwhocodes/object-schema": {
+      "version": "1.2.1",
+      "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz",
+      "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==",
+      "dev": true
+    },
+    "@nodelib/fs.scandir": {
+      "version": "2.1.5",
+      "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
+      "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==",
+      "dev": true,
+      "requires": {
+        "@nodelib/fs.stat": "2.0.5",
+        "run-parallel": "^1.1.9"
+      }
+    },
+    "@nodelib/fs.stat": {
+      "version": "2.0.5",
+      "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz",
+      "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==",
+      "dev": true
+    },
+    "@nodelib/fs.walk": {
+      "version": "1.2.8",
+      "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz",
+      "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==",
+      "dev": true,
+      "requires": {
+        "@nodelib/fs.scandir": "2.1.5",
+        "fastq": "^1.6.0"
+      }
+    },
+    "@rollup/plugin-node-resolve": {
+      "version": "13.3.0",
+      "resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-13.3.0.tgz",
+      "integrity": "sha512-Lus8rbUo1eEcnS4yTFKLZrVumLPY+YayBdWXgFSHYhTT2iJbMhoaaBL3xl5NCdeRytErGr8tZ0L71BMRmnlwSw==",
+      "dev": true,
+      "requires": {
+        "@rollup/pluginutils": "^3.1.0",
+        "@types/resolve": "1.17.1",
+        "deepmerge": "^4.2.2",
+        "is-builtin-module": "^3.1.0",
+        "is-module": "^1.0.0",
+        "resolve": "^1.19.0"
+      }
+    },
+    "@rollup/pluginutils": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-3.1.0.tgz",
+      "integrity": "sha512-GksZ6pr6TpIjHm8h9lSQ8pi8BE9VeubNT0OMJ3B5uZJ8pz73NPiqOtCog/x2/QzM1ENChPKxMDhiQuRHsqc+lg==",
+      "dev": true,
+      "requires": {
+        "@types/estree": "0.0.39",
+        "estree-walker": "^1.0.1",
+        "picomatch": "^2.2.2"
+      }
+    },
+    "@types/estree": {
+      "version": "0.0.39",
+      "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.39.tgz",
+      "integrity": "sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==",
+      "dev": true
+    },
+    "@types/mockjs": {
+      "version": "1.0.6",
+      "resolved": "https://registry.npmjs.org/@types/mockjs/-/mockjs-1.0.6.tgz",
+      "integrity": "sha512-Yu5YlqbYZyqsd6LjO4e8ONJDN9pTSnciHDcRP4teNOh/au2b8helFhgRx+3w8xsTFEnwr9jtfTVJbAx+eYmlHA==",
+      "dev": true
+    },
+    "@types/node": {
+      "version": "18.0.3",
+      "resolved": "https://registry.npmjs.org/@types/node/-/node-18.0.3.tgz",
+      "integrity": "sha512-HzNRZtp4eepNitP+BD6k2L6DROIDG4Q0fm4x+dwfsr6LGmROENnok75VGw40628xf+iR24WeMFcHuuBDUAzzsQ==",
+      "dev": true
+    },
+    "@types/resolve": {
+      "version": "1.17.1",
+      "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.17.1.tgz",
+      "integrity": "sha512-yy7HuzQhj0dhGpD8RLXSZWEkLsV9ibvxvi6EiJ3bkqLAO1RGo0WbkWQiwpRlSFymTJRz0d3k5LM3kkx8ArDbLw==",
+      "dev": true,
+      "requires": {
+        "@types/node": "*"
+      }
+    },
+    "@vitejs/plugin-vue": {
+      "version": "2.3.3",
+      "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-2.3.3.tgz",
+      "integrity": "sha512-SmQLDyhz+6lGJhPELsBdzXGc+AcaT8stgkbiTFGpXPe8Tl1tJaBw1A6pxDqDuRsVkD8uscrkx3hA7QDOoKYtyw==",
+      "dev": true,
+      "requires": {}
+    },
+    "@vue/compiler-core": {
+      "version": "3.2.37",
+      "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.2.37.tgz",
+      "integrity": "sha512-81KhEjo7YAOh0vQJoSmAD68wLfYqJvoiD4ulyedzF+OEk/bk6/hx3fTNVfuzugIIaTrOx4PGx6pAiBRe5e9Zmg==",
+      "requires": {
+        "@babel/parser": "^7.16.4",
+        "@vue/shared": "3.2.37",
+        "estree-walker": "^2.0.2",
+        "source-map": "^0.6.1"
+      },
+      "dependencies": {
+        "estree-walker": {
+          "version": "2.0.2",
+          "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz",
+          "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="
+        }
+      }
+    },
+    "@vue/compiler-dom": {
+      "version": "3.2.37",
+      "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.2.37.tgz",
+      "integrity": "sha512-yxJLH167fucHKxaqXpYk7x8z7mMEnXOw3G2q62FTkmsvNxu4FQSu5+3UMb+L7fjKa26DEzhrmCxAgFLLIzVfqQ==",
+      "requires": {
+        "@vue/compiler-core": "3.2.37",
+        "@vue/shared": "3.2.37"
+      }
+    },
+    "@vue/compiler-sfc": {
+      "version": "3.2.37",
+      "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.2.37.tgz",
+      "integrity": "sha512-+7i/2+9LYlpqDv+KTtWhOZH+pa8/HnX/905MdVmAcI/mPQOBwkHHIzrsEsucyOIZQYMkXUiTkmZq5am/NyXKkg==",
+      "requires": {
+        "@babel/parser": "^7.16.4",
+        "@vue/compiler-core": "3.2.37",
+        "@vue/compiler-dom": "3.2.37",
+        "@vue/compiler-ssr": "3.2.37",
+        "@vue/reactivity-transform": "3.2.37",
+        "@vue/shared": "3.2.37",
+        "estree-walker": "^2.0.2",
+        "magic-string": "^0.25.7",
+        "postcss": "^8.1.10",
+        "source-map": "^0.6.1"
+      },
+      "dependencies": {
+        "estree-walker": {
+          "version": "2.0.2",
+          "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz",
+          "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="
+        }
+      }
+    },
+    "@vue/compiler-ssr": {
+      "version": "3.2.37",
+      "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.2.37.tgz",
+      "integrity": "sha512-7mQJD7HdXxQjktmsWp/J67lThEIcxLemz1Vb5I6rYJHR5vI+lON3nPGOH3ubmbvYGt8xEUaAr1j7/tIFWiEOqw==",
+      "requires": {
+        "@vue/compiler-dom": "3.2.37",
+        "@vue/shared": "3.2.37"
+      }
+    },
+    "@vue/devtools-api": {
+      "version": "6.2.0",
+      "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-6.2.0.tgz",
+      "integrity": "sha512-pF1G4wky+hkifDiZSWn8xfuLOJI1ZXtuambpBEYaf7Xaf6zC/pM29rvAGpd3qaGXnr4BAXU1Pxz/VfvBGwexGA=="
+    },
+    "@vue/reactivity": {
+      "version": "3.2.37",
+      "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.2.37.tgz",
+      "integrity": "sha512-/7WRafBOshOc6m3F7plwzPeCu/RCVv9uMpOwa/5PiY1Zz+WLVRWiy0MYKwmg19KBdGtFWsmZ4cD+LOdVPcs52A==",
+      "requires": {
+        "@vue/shared": "3.2.37"
+      }
+    },
+    "@vue/reactivity-transform": {
+      "version": "3.2.37",
+      "resolved": "https://registry.npmjs.org/@vue/reactivity-transform/-/reactivity-transform-3.2.37.tgz",
+      "integrity": "sha512-IWopkKEb+8qpu/1eMKVeXrK0NLw9HicGviJzhJDEyfxTR9e1WtpnnbYkJWurX6WwoFP0sz10xQg8yL8lgskAZg==",
+      "requires": {
+        "@babel/parser": "^7.16.4",
+        "@vue/compiler-core": "3.2.37",
+        "@vue/shared": "3.2.37",
+        "estree-walker": "^2.0.2",
+        "magic-string": "^0.25.7"
+      },
+      "dependencies": {
+        "estree-walker": {
+          "version": "2.0.2",
+          "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz",
+          "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="
+        }
+      }
+    },
+    "@vue/runtime-core": {
+      "version": "3.2.37",
+      "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.2.37.tgz",
+      "integrity": "sha512-JPcd9kFyEdXLl/i0ClS7lwgcs0QpUAWj+SKX2ZC3ANKi1U4DOtiEr6cRqFXsPwY5u1L9fAjkinIdB8Rz3FoYNQ==",
+      "requires": {
+        "@vue/reactivity": "3.2.37",
+        "@vue/shared": "3.2.37"
+      }
+    },
+    "@vue/runtime-dom": {
+      "version": "3.2.37",
+      "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.2.37.tgz",
+      "integrity": "sha512-HimKdh9BepShW6YozwRKAYjYQWg9mQn63RGEiSswMbW+ssIht1MILYlVGkAGGQbkhSh31PCdoUcfiu4apXJoPw==",
+      "requires": {
+        "@vue/runtime-core": "3.2.37",
+        "@vue/shared": "3.2.37",
+        "csstype": "^2.6.8"
+      }
+    },
+    "@vue/server-renderer": {
+      "version": "3.2.37",
+      "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.2.37.tgz",
+      "integrity": "sha512-kLITEJvaYgZQ2h47hIzPh2K3jG8c1zCVbp/o/bzQOyvzaKiCquKS7AaioPI28GNxIsE/zSx+EwWYsNxDCX95MA==",
+      "requires": {
+        "@vue/compiler-ssr": "3.2.37",
+        "@vue/shared": "3.2.37"
+      }
+    },
+    "@vue/shared": {
+      "version": "3.2.37",
+      "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.2.37.tgz",
+      "integrity": "sha512-4rSJemR2NQIo9Klm1vabqWjD8rs/ZaJSzMxkMNeJS6lHiUjjUeYFbooN19NgFjztubEKh3WlZUeOLVdbbUWHsw=="
+    },
+    "acorn": {
+      "version": "8.7.1",
+      "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.7.1.tgz",
+      "integrity": "sha512-Xx54uLJQZ19lKygFXOWsscKUbsBZW0CPykPhVQdhIeIwrbPmJzqeASDInc8nKBnp/JT6igTs82qPXz069H8I/A==",
+      "dev": true
+    },
+    "acorn-jsx": {
+      "version": "5.3.2",
+      "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz",
+      "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==",
+      "dev": true,
+      "requires": {}
+    },
+    "ajv": {
+      "version": "6.12.6",
+      "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
+      "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
+      "dev": true,
+      "requires": {
+        "fast-deep-equal": "^3.1.1",
+        "fast-json-stable-stringify": "^2.0.0",
+        "json-schema-traverse": "^0.4.1",
+        "uri-js": "^4.2.2"
+      }
+    },
+    "ansi-regex": {
+      "version": "5.0.1",
+      "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
+      "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
+      "dev": true
+    },
+    "ansi-styles": {
+      "version": "4.3.0",
+      "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
+      "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+      "dev": true,
+      "requires": {
+        "color-convert": "^2.0.1"
+      },
+      "dependencies": {
+        "color-convert": {
+          "version": "2.0.1",
+          "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
+          "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+          "dev": true,
+          "requires": {
+            "color-name": "~1.1.4"
+          }
+        },
+        "color-name": {
+          "version": "1.1.4",
+          "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
+          "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
+          "dev": true
+        }
+      }
+    },
+    "anymatch": {
+      "version": "3.1.2",
+      "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz",
+      "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==",
+      "dev": true,
+      "requires": {
+        "normalize-path": "^3.0.0",
+        "picomatch": "^2.0.4"
+      }
+    },
+    "argparse": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
+      "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
+      "dev": true
+    },
+    "axios": {
+      "version": "0.26.1",
+      "resolved": "https://registry.npmjs.org/axios/-/axios-0.26.1.tgz",
+      "integrity": "sha512-fPwcX4EvnSHuInCMItEhAGnaSEXRBjtzh9fOtsE6E1G6p7vl7edEeZe11QHf18+6+9gR5PbKV/sGKNaD8YaMeA==",
+      "requires": {
+        "follow-redirects": "^1.14.8"
+      }
+    },
+    "b-tween": {
+      "version": "0.3.3",
+      "resolved": "https://registry.npmjs.org/b-tween/-/b-tween-0.3.3.tgz",
+      "integrity": "sha512-oEHegcRpA7fAuc9KC4nktucuZn2aS8htymCPcP3qkEGPqiBH+GfqtqoG2l7LxHngg6O0HFM7hOeOYExl1Oz4ZA==",
+      "dev": true
+    },
+    "b-validate": {
+      "version": "1.4.1",
+      "resolved": "https://registry.npmjs.org/b-validate/-/b-validate-1.4.1.tgz",
+      "integrity": "sha512-X6ImDku5YY8NfWTh/hX8CAaronWnNXpb159cqs6lDWLtI4OWiehZ4B0NshfatTuKt1HIeNq9PObE/Xl5YoJYUg==",
+      "dev": true
+    },
+    "balanced-match": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
+      "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
+      "dev": true
+    },
+    "binary-extensions": {
+      "version": "2.2.0",
+      "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz",
+      "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==",
+      "dev": true
+    },
+    "boolbase": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz",
+      "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==",
+      "dev": true
+    },
+    "brace-expansion": {
+      "version": "1.1.11",
+      "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
+      "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
+      "dev": true,
+      "requires": {
+        "balanced-match": "^1.0.0",
+        "concat-map": "0.0.1"
+      }
+    },
+    "braces": {
+      "version": "3.0.2",
+      "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
+      "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==",
+      "dev": true,
+      "requires": {
+        "fill-range": "^7.0.1"
+      }
+    },
+    "builtin-modules": {
+      "version": "3.3.0",
+      "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.3.0.tgz",
+      "integrity": "sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==",
+      "dev": true
+    },
+    "callsites": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
+      "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==",
+      "dev": true
+    },
+    "chalk": {
+      "version": "4.1.2",
+      "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
+      "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
+      "dev": true,
+      "requires": {
+        "ansi-styles": "^4.1.0",
+        "supports-color": "^7.1.0"
+      }
+    },
+    "chokidar": {
+      "version": "3.5.3",
+      "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz",
+      "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==",
+      "dev": true,
+      "requires": {
+        "anymatch": "~3.1.2",
+        "braces": "~3.0.2",
+        "fsevents": "~2.3.2",
+        "glob-parent": "~5.1.2",
+        "is-binary-path": "~2.1.0",
+        "is-glob": "~4.0.1",
+        "normalize-path": "~3.0.0",
+        "readdirp": "~3.6.0"
+      },
+      "dependencies": {
+        "glob-parent": {
+          "version": "5.1.2",
+          "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
+          "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
+          "dev": true,
+          "requires": {
+            "is-glob": "^4.0.1"
+          }
+        }
+      }
+    },
+    "classnames": {
+      "version": "2.3.1",
+      "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.3.1.tgz",
+      "integrity": "sha512-OlQdbZ7gLfGarSqxesMesDa5uz7KFbID8Kpq/SxIoNGDqY8lSYs0D+hhtBXhcdB3rcbXArFr7vlHheLk1voeNA=="
+    },
+    "color": {
+      "version": "3.2.1",
+      "resolved": "https://registry.npmjs.org/color/-/color-3.2.1.tgz",
+      "integrity": "sha512-aBl7dZI9ENN6fUGC7mWpMTPNHmWUSNan9tuWN6ahh5ZLNk9baLJOnSMlrQkHcrfFgz2/RigjUVAjdx36VcemKA==",
+      "dev": true,
+      "requires": {
+        "color-convert": "^1.9.3",
+        "color-string": "^1.6.0"
+      }
+    },
+    "color-convert": {
+      "version": "1.9.3",
+      "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
+      "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
+      "dev": true,
+      "requires": {
+        "color-name": "1.1.3"
+      }
+    },
+    "color-name": {
+      "version": "1.1.3",
+      "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
+      "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==",
+      "dev": true
+    },
+    "color-string": {
+      "version": "1.9.1",
+      "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz",
+      "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==",
+      "dev": true,
+      "requires": {
+        "color-name": "^1.0.0",
+        "simple-swizzle": "^0.2.2"
+      }
+    },
+    "commander": {
+      "version": "9.3.0",
+      "resolved": "https://registry.npmjs.org/commander/-/commander-9.3.0.tgz",
+      "integrity": "sha512-hv95iU5uXPbK83mjrJKuZyFM/LBAoCV/XhVGkS5Je6tl7sxr6A0ITMw5WoRV46/UaJ46Nllm3Xt7IaJhXTIkzw==",
+      "dev": true
+    },
+    "compute-scroll-into-view": {
+      "version": "1.0.17",
+      "resolved": "https://registry.npmjs.org/compute-scroll-into-view/-/compute-scroll-into-view-1.0.17.tgz",
+      "integrity": "sha512-j4dx+Fb0URmzbwwMUrhqWM2BEWHdFGx+qZ9qqASHRPqvTYdqvWnHg0H1hIbcyLnvgnoNAVMlwkepyqM3DaIFUg==",
+      "dev": true
+    },
+    "concat-map": {
+      "version": "0.0.1",
+      "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
+      "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
+      "dev": true
+    },
+    "connect": {
+      "version": "3.7.0",
+      "resolved": "https://registry.npmjs.org/connect/-/connect-3.7.0.tgz",
+      "integrity": "sha512-ZqRXc+tZukToSNmh5C2iWMSoV3X1YUcPbqEM4DkEG5tNQXrQUZCNVGGv3IuicnkMtPfGf3Xtp8WCXs295iQ1pQ==",
+      "dev": true,
+      "requires": {
+        "debug": "2.6.9",
+        "finalhandler": "1.1.2",
+        "parseurl": "~1.3.3",
+        "utils-merge": "1.0.1"
+      },
+      "dependencies": {
+        "debug": {
+          "version": "2.6.9",
+          "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
+          "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+          "dev": true,
+          "requires": {
+            "ms": "2.0.0"
+          }
+        },
+        "ms": {
+          "version": "2.0.0",
+          "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+          "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
+          "dev": true
+        }
+      }
+    },
+    "cross-spawn": {
+      "version": "7.0.3",
+      "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
+      "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==",
+      "dev": true,
+      "requires": {
+        "path-key": "^3.1.0",
+        "shebang-command": "^2.0.0",
+        "which": "^2.0.1"
+      }
+    },
+    "cssesc": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz",
+      "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==",
+      "dev": true
+    },
+    "csstype": {
+      "version": "2.6.20",
+      "resolved": "https://registry.npmjs.org/csstype/-/csstype-2.6.20.tgz",
+      "integrity": "sha512-/WwNkdXfckNgw6S5R125rrW8ez139lBHWouiBvX8dfMFtcn6V81REDqnH7+CRpRipfYlyU1CmOnOxrmGcFOjeA=="
+    },
+    "dayjs": {
+      "version": "1.11.3",
+      "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.3.tgz",
+      "integrity": "sha512-xxwlswWOlGhzgQ4TKzASQkUhqERI3egRNqgV4ScR8wlANA/A9tZ7miXa44vTTKEq5l7vWoL5G57bG3zA+Kow0A==",
+      "dev": true
+    },
+    "debug": {
+      "version": "4.3.4",
+      "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
+      "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
+      "dev": true,
+      "requires": {
+        "ms": "2.1.2"
+      }
+    },
+    "deep-is": {
+      "version": "0.1.4",
+      "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz",
+      "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==",
+      "dev": true
+    },
+    "deepmerge": {
+      "version": "4.2.2",
+      "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz",
+      "integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==",
+      "dev": true
+    },
+    "doctrine": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz",
+      "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==",
+      "dev": true,
+      "requires": {
+        "esutils": "^2.0.2"
+      }
+    },
+    "ee-first": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
+      "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==",
+      "dev": true
+    },
+    "encodeurl": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
+      "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==",
+      "dev": true
+    },
+    "esbuild": {
+      "version": "0.14.48",
+      "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.14.48.tgz",
+      "integrity": "sha512-w6N1Yn5MtqK2U1/WZTX9ZqUVb8IOLZkZ5AdHkT6x3cHDMVsYWC7WPdiLmx19w3i4Rwzy5LqsEMtVihG3e4rFzA==",
+      "dev": true,
+      "requires": {
+        "esbuild-android-64": "0.14.48",
+        "esbuild-android-arm64": "0.14.48",
+        "esbuild-darwin-64": "0.14.48",
+        "esbuild-darwin-arm64": "0.14.48",
+        "esbuild-freebsd-64": "0.14.48",
+        "esbuild-freebsd-arm64": "0.14.48",
+        "esbuild-linux-32": "0.14.48",
+        "esbuild-linux-64": "0.14.48",
+        "esbuild-linux-arm": "0.14.48",
+        "esbuild-linux-arm64": "0.14.48",
+        "esbuild-linux-mips64le": "0.14.48",
+        "esbuild-linux-ppc64le": "0.14.48",
+        "esbuild-linux-riscv64": "0.14.48",
+        "esbuild-linux-s390x": "0.14.48",
+        "esbuild-netbsd-64": "0.14.48",
+        "esbuild-openbsd-64": "0.14.48",
+        "esbuild-sunos-64": "0.14.48",
+        "esbuild-windows-32": "0.14.48",
+        "esbuild-windows-64": "0.14.48",
+        "esbuild-windows-arm64": "0.14.48"
+      }
+    },
+    "esbuild-android-64": {
+      "version": "0.14.48",
+      "resolved": "https://registry.npmjs.org/esbuild-android-64/-/esbuild-android-64-0.14.48.tgz",
+      "integrity": "sha512-3aMjboap/kqwCUpGWIjsk20TtxVoKck8/4Tu19rubh7t5Ra0Yrpg30Mt1QXXlipOazrEceGeWurXKeFJgkPOUg==",
+      "dev": true,
+      "optional": true
+    },
+    "esbuild-android-arm64": {
+      "version": "0.14.48",
+      "resolved": "https://registry.npmjs.org/esbuild-android-arm64/-/esbuild-android-arm64-0.14.48.tgz",
+      "integrity": "sha512-vptI3K0wGALiDq+EvRuZotZrJqkYkN5282iAfcffjI5lmGG9G1ta/CIVauhY42MBXwEgDJkweiDcDMRLzBZC4g==",
+      "dev": true,
+      "optional": true
+    },
+    "esbuild-darwin-64": {
+      "version": "0.14.48",
+      "resolved": "https://registry.npmjs.org/esbuild-darwin-64/-/esbuild-darwin-64-0.14.48.tgz",
+      "integrity": "sha512-gGQZa4+hab2Va/Zww94YbshLuWteyKGD3+EsVon8EWTWhnHFRm5N9NbALNbwi/7hQ/hM1Zm4FuHg+k6BLsl5UA==",
+      "dev": true,
+      "optional": true
+    },
+    "esbuild-darwin-arm64": {
+      "version": "0.14.48",
+      "resolved": "https://registry.npmjs.org/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.14.48.tgz",
+      "integrity": "sha512-bFjnNEXjhZT+IZ8RvRGNJthLWNHV5JkCtuOFOnjvo5pC0sk2/QVk0Qc06g2PV3J0TcU6kaPC3RN9yy9w2PSLEA==",
+      "dev": true,
+      "optional": true
+    },
+    "esbuild-freebsd-64": {
+      "version": "0.14.48",
+      "resolved": "https://registry.npmjs.org/esbuild-freebsd-64/-/esbuild-freebsd-64-0.14.48.tgz",
+      "integrity": "sha512-1NOlwRxmOsnPcWOGTB10JKAkYSb2nue0oM1AfHWunW/mv3wERfJmnYlGzL3UAOIUXZqW8GeA2mv+QGwq7DToqA==",
+      "dev": true,
+      "optional": true
+    },
+    "esbuild-freebsd-arm64": {
+      "version": "0.14.48",
+      "resolved": "https://registry.npmjs.org/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.14.48.tgz",
+      "integrity": "sha512-gXqKdO8wabVcYtluAbikDH2jhXp+Klq5oCD5qbVyUG6tFiGhrC9oczKq3vIrrtwcxDQqK6+HDYK8Zrd4bCA9Gw==",
+      "dev": true,
+      "optional": true
+    },
+    "esbuild-linux-32": {
+      "version": "0.14.48",
+      "resolved": "https://registry.npmjs.org/esbuild-linux-32/-/esbuild-linux-32-0.14.48.tgz",
+      "integrity": "sha512-ghGyDfS289z/LReZQUuuKq9KlTiTspxL8SITBFQFAFRA/IkIvDpnZnCAKTCjGXAmUqroMQfKJXMxyjJA69c/nQ==",
+      "dev": true,
+      "optional": true
+    },
+    "esbuild-linux-64": {
+      "version": "0.14.48",
+      "resolved": "https://registry.npmjs.org/esbuild-linux-64/-/esbuild-linux-64-0.14.48.tgz",
+      "integrity": "sha512-vni3p/gppLMVZLghI7oMqbOZdGmLbbKR23XFARKnszCIBpEMEDxOMNIKPmMItQrmH/iJrL1z8Jt2nynY0bE1ug==",
+      "dev": true,
+      "optional": true
+    },
+    "esbuild-linux-arm": {
+      "version": "0.14.48",
+      "resolved": "https://registry.npmjs.org/esbuild-linux-arm/-/esbuild-linux-arm-0.14.48.tgz",
+      "integrity": "sha512-+VfSV7Akh1XUiDNXgqgY1cUP1i2vjI+BmlyXRfVz5AfV3jbpde8JTs5Q9sYgaoq5cWfuKfoZB/QkGOI+QcL1Tw==",
+      "dev": true,
+      "optional": true
+    },
+    "esbuild-linux-arm64": {
+      "version": "0.14.48",
+      "resolved": "https://registry.npmjs.org/esbuild-linux-arm64/-/esbuild-linux-arm64-0.14.48.tgz",
+      "integrity": "sha512-3CFsOlpoxlKPRevEHq8aAntgYGYkE1N9yRYAcPyng/p4Wyx0tPR5SBYsxLKcgPB9mR8chHEhtWYz6EZ+H199Zw==",
+      "dev": true,
+      "optional": true
+    },
+    "esbuild-linux-mips64le": {
+      "version": "0.14.48",
+      "resolved": "https://registry.npmjs.org/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.14.48.tgz",
+      "integrity": "sha512-cs0uOiRlPp6ymknDnjajCgvDMSsLw5mST2UXh+ZIrXTj2Ifyf2aAP3Iw4DiqgnyYLV2O/v/yWBJx+WfmKEpNLA==",
+      "dev": true,
+      "optional": true
+    },
+    "esbuild-linux-ppc64le": {
+      "version": "0.14.48",
+      "resolved": "https://registry.npmjs.org/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.14.48.tgz",
+      "integrity": "sha512-+2F0vJMkuI0Wie/wcSPDCqXvSFEELH7Jubxb7mpWrA/4NpT+/byjxDz0gG6R1WJoeDefcrMfpBx4GFNN1JQorQ==",
+      "dev": true,
+      "optional": true
+    },
+    "esbuild-linux-riscv64": {
+      "version": "0.14.48",
+      "resolved": "https://registry.npmjs.org/esbuild-linux-riscv64/-/esbuild-linux-riscv64-0.14.48.tgz",
+      "integrity": "sha512-BmaK/GfEE+5F2/QDrIXteFGKnVHGxlnK9MjdVKMTfvtmudjY3k2t8NtlY4qemKSizc+QwyombGWTBDc76rxePA==",
+      "dev": true,
+      "optional": true
+    },
+    "esbuild-linux-s390x": {
+      "version": "0.14.48",
+      "resolved": "https://registry.npmjs.org/esbuild-linux-s390x/-/esbuild-linux-s390x-0.14.48.tgz",
+      "integrity": "sha512-tndw/0B9jiCL+KWKo0TSMaUm5UWBLsfCKVdbfMlb3d5LeV9WbijZ8Ordia8SAYv38VSJWOEt6eDCdOx8LqkC4g==",
+      "dev": true,
+      "optional": true
+    },
+    "esbuild-netbsd-64": {
+      "version": "0.14.48",
+      "resolved": "https://registry.npmjs.org/esbuild-netbsd-64/-/esbuild-netbsd-64-0.14.48.tgz",
+      "integrity": "sha512-V9hgXfwf/T901Lr1wkOfoevtyNkrxmMcRHyticybBUHookznipMOHoF41Al68QBsqBxnITCEpjjd4yAos7z9Tw==",
+      "dev": true,
+      "optional": true
+    },
+    "esbuild-openbsd-64": {
+      "version": "0.14.48",
+      "resolved": "https://registry.npmjs.org/esbuild-openbsd-64/-/esbuild-openbsd-64-0.14.48.tgz",
+      "integrity": "sha512-+IHf4JcbnnBl4T52egorXMatil/za0awqzg2Vy6FBgPcBpisDWT2sVz/tNdrK9kAqj+GZG/jZdrOkj7wsrNTKA==",
+      "dev": true,
+      "optional": true
+    },
+    "esbuild-sunos-64": {
+      "version": "0.14.48",
+      "resolved": "https://registry.npmjs.org/esbuild-sunos-64/-/esbuild-sunos-64-0.14.48.tgz",
+      "integrity": "sha512-77m8bsr5wOpOWbGi9KSqDphcq6dFeJyun8TA+12JW/GAjyfTwVtOnN8DOt6DSPUfEV+ltVMNqtXUeTeMAxl5KA==",
+      "dev": true,
+      "optional": true
+    },
+    "esbuild-windows-32": {
+      "version": "0.14.48",
+      "resolved": "https://registry.npmjs.org/esbuild-windows-32/-/esbuild-windows-32-0.14.48.tgz",
+      "integrity": "sha512-EPgRuTPP8vK9maxpTGDe5lSoIBHGKO/AuxDncg5O3NkrPeLNdvvK8oywB0zGaAZXxYWfNNSHskvvDgmfVTguhg==",
+      "dev": true,
+      "optional": true
+    },
+    "esbuild-windows-64": {
+      "version": "0.14.48",
+      "resolved": "https://registry.npmjs.org/esbuild-windows-64/-/esbuild-windows-64-0.14.48.tgz",
+      "integrity": "sha512-YmpXjdT1q0b8ictSdGwH3M8VCoqPpK1/UArze3X199w6u8hUx3V8BhAi1WjbsfDYRBanVVtduAhh2sirImtAvA==",
+      "dev": true,
+      "optional": true
+    },
+    "esbuild-windows-arm64": {
+      "version": "0.14.48",
+      "resolved": "https://registry.npmjs.org/esbuild-windows-arm64/-/esbuild-windows-arm64-0.14.48.tgz",
+      "integrity": "sha512-HHaOMCsCXp0rz5BT2crTka6MPWVno121NKApsGs/OIW5QC0ggC69YMGs1aJct9/9FSUF4A1xNE/cLvgB5svR4g==",
+      "dev": true,
+      "optional": true
+    },
+    "escape-html": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
+      "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==",
+      "dev": true
+    },
+    "escape-string-regexp": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
+      "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
+      "dev": true
+    },
+    "eslint": {
+      "version": "8.19.0",
+      "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.19.0.tgz",
+      "integrity": "sha512-SXOPj3x9VKvPe81TjjUJCYlV4oJjQw68Uek+AM0X4p+33dj2HY5bpTZOgnQHcG2eAm1mtCU9uNMnJi7exU/kYw==",
+      "dev": true,
+      "requires": {
+        "@eslint/eslintrc": "^1.3.0",
+        "@humanwhocodes/config-array": "^0.9.2",
+        "ajv": "^6.10.0",
+        "chalk": "^4.0.0",
+        "cross-spawn": "^7.0.2",
+        "debug": "^4.3.2",
+        "doctrine": "^3.0.0",
+        "escape-string-regexp": "^4.0.0",
+        "eslint-scope": "^7.1.1",
+        "eslint-utils": "^3.0.0",
+        "eslint-visitor-keys": "^3.3.0",
+        "espree": "^9.3.2",
+        "esquery": "^1.4.0",
+        "esutils": "^2.0.2",
+        "fast-deep-equal": "^3.1.3",
+        "file-entry-cache": "^6.0.1",
+        "functional-red-black-tree": "^1.0.1",
+        "glob-parent": "^6.0.1",
+        "globals": "^13.15.0",
+        "ignore": "^5.2.0",
+        "import-fresh": "^3.0.0",
+        "imurmurhash": "^0.1.4",
+        "is-glob": "^4.0.0",
+        "js-yaml": "^4.1.0",
+        "json-stable-stringify-without-jsonify": "^1.0.1",
+        "levn": "^0.4.1",
+        "lodash.merge": "^4.6.2",
+        "minimatch": "^3.1.2",
+        "natural-compare": "^1.4.0",
+        "optionator": "^0.9.1",
+        "regexpp": "^3.2.0",
+        "strip-ansi": "^6.0.1",
+        "strip-json-comments": "^3.1.0",
+        "text-table": "^0.2.0",
+        "v8-compile-cache": "^2.0.3"
+      }
+    },
+    "eslint-plugin-vue": {
+      "version": "9.2.0",
+      "resolved": "https://registry.npmjs.org/eslint-plugin-vue/-/eslint-plugin-vue-9.2.0.tgz",
+      "integrity": "sha512-W2hc+NUXoce8sZtWgZ45miQTy6jNyuSdub5aZ1IBune4JDeAyzucYX0TzkrQ1jMO52sNUDYlCIHDoaNePe0p5g==",
+      "dev": true,
+      "requires": {
+        "eslint-utils": "^3.0.0",
+        "natural-compare": "^1.4.0",
+        "nth-check": "^2.0.1",
+        "postcss-selector-parser": "^6.0.9",
+        "semver": "^7.3.5",
+        "vue-eslint-parser": "^9.0.1",
+        "xml-name-validator": "^4.0.0"
+      }
+    },
+    "eslint-scope": {
+      "version": "7.1.1",
+      "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.1.1.tgz",
+      "integrity": "sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw==",
+      "dev": true,
+      "requires": {
+        "esrecurse": "^4.3.0",
+        "estraverse": "^5.2.0"
+      }
+    },
+    "eslint-utils": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz",
+      "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==",
+      "dev": true,
+      "requires": {
+        "eslint-visitor-keys": "^2.0.0"
+      },
+      "dependencies": {
+        "eslint-visitor-keys": {
+          "version": "2.1.0",
+          "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz",
+          "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==",
+          "dev": true
+        }
+      }
+    },
+    "eslint-visitor-keys": {
+      "version": "3.3.0",
+      "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz",
+      "integrity": "sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==",
+      "dev": true
+    },
+    "espree": {
+      "version": "9.3.2",
+      "resolved": "https://registry.npmjs.org/espree/-/espree-9.3.2.tgz",
+      "integrity": "sha512-D211tC7ZwouTIuY5x9XnS0E9sWNChB7IYKX/Xp5eQj3nFXhqmiUDB9q27y76oFl8jTg3pXcQx/bpxMfs3CIZbA==",
+      "dev": true,
+      "requires": {
+        "acorn": "^8.7.1",
+        "acorn-jsx": "^5.3.2",
+        "eslint-visitor-keys": "^3.3.0"
+      }
+    },
+    "esquery": {
+      "version": "1.4.0",
+      "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz",
+      "integrity": "sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==",
+      "dev": true,
+      "requires": {
+        "estraverse": "^5.1.0"
+      }
+    },
+    "esrecurse": {
+      "version": "4.3.0",
+      "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz",
+      "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==",
+      "dev": true,
+      "requires": {
+        "estraverse": "^5.2.0"
+      }
+    },
+    "estraverse": {
+      "version": "5.3.0",
+      "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz",
+      "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==",
+      "dev": true
+    },
+    "estree-walker": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-1.0.1.tgz",
+      "integrity": "sha512-1fMXF3YP4pZZVozF8j/ZLfvnR8NSIljt56UhbZ5PeeDmmGHpgpdwQt7ITlGvYaQukCvuBRMLEiKiYC+oeIg4cg==",
+      "dev": true
+    },
+    "esutils": {
+      "version": "2.0.3",
+      "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
+      "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==",
+      "dev": true
+    },
+    "fast-deep-equal": {
+      "version": "3.1.3",
+      "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
+      "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
+      "dev": true
+    },
+    "fast-glob": {
+      "version": "3.2.11",
+      "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.11.tgz",
+      "integrity": "sha512-xrO3+1bxSo3ZVHAnqzyuewYT6aMFHRAd4Kcs92MAonjwQZLsK9d0SF1IyQ3k5PoirxTW0Oe/RqFgMQ6TcNE5Ew==",
+      "dev": true,
+      "requires": {
+        "@nodelib/fs.stat": "^2.0.2",
+        "@nodelib/fs.walk": "^1.2.3",
+        "glob-parent": "^5.1.2",
+        "merge2": "^1.3.0",
+        "micromatch": "^4.0.4"
+      },
+      "dependencies": {
+        "glob-parent": {
+          "version": "5.1.2",
+          "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
+          "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
+          "dev": true,
+          "requires": {
+            "is-glob": "^4.0.1"
+          }
+        }
+      }
+    },
+    "fast-json-stable-stringify": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
+      "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==",
+      "dev": true
+    },
+    "fast-levenshtein": {
+      "version": "2.0.6",
+      "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz",
+      "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==",
+      "dev": true
+    },
+    "fastq": {
+      "version": "1.13.0",
+      "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.13.0.tgz",
+      "integrity": "sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw==",
+      "dev": true,
+      "requires": {
+        "reusify": "^1.0.4"
+      }
+    },
+    "file-entry-cache": {
+      "version": "6.0.1",
+      "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz",
+      "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==",
+      "dev": true,
+      "requires": {
+        "flat-cache": "^3.0.4"
+      }
+    },
+    "fill-range": {
+      "version": "7.0.1",
+      "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
+      "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==",
+      "dev": true,
+      "requires": {
+        "to-regex-range": "^5.0.1"
+      }
+    },
+    "finalhandler": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz",
+      "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==",
+      "dev": true,
+      "requires": {
+        "debug": "2.6.9",
+        "encodeurl": "~1.0.2",
+        "escape-html": "~1.0.3",
+        "on-finished": "~2.3.0",
+        "parseurl": "~1.3.3",
+        "statuses": "~1.5.0",
+        "unpipe": "~1.0.0"
+      },
+      "dependencies": {
+        "debug": {
+          "version": "2.6.9",
+          "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
+          "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+          "dev": true,
+          "requires": {
+            "ms": "2.0.0"
+          }
+        },
+        "ms": {
+          "version": "2.0.0",
+          "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+          "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
+          "dev": true
+        }
+      }
+    },
+    "flat-cache": {
+      "version": "3.0.4",
+      "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz",
+      "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==",
+      "dev": true,
+      "requires": {
+        "flatted": "^3.1.0",
+        "rimraf": "^3.0.2"
+      }
+    },
+    "flatted": {
+      "version": "3.2.6",
+      "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.6.tgz",
+      "integrity": "sha512-0sQoMh9s0BYsm+12Huy/rkKxVu4R1+r96YX5cG44rHV0pQ6iC3Q+mkoMFaGWObMFYQxCVT+ssG1ksneA2MI9KQ==",
+      "dev": true
+    },
+    "follow-redirects": {
+      "version": "1.15.1",
+      "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.1.tgz",
+      "integrity": "sha512-yLAMQs+k0b2m7cVxpS1VKJVvoz7SS9Td1zss3XRwXj+ZDH00RJgnuLx7E44wx02kQLrdM3aOOy+FpzS7+8OizA=="
+    },
+    "fs.realpath": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
+      "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==",
+      "dev": true
+    },
+    "fsevents": {
+      "version": "2.3.2",
+      "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
+      "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
+      "dev": true,
+      "optional": true
+    },
+    "function-bind": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
+      "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==",
+      "dev": true
+    },
+    "functional-red-black-tree": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz",
+      "integrity": "sha512-dsKNQNdj6xA3T+QlADDA7mOSlX0qiMINjn0cgr+eGHGsbSHzTabcIogz2+p/iqP1Xs6EP/sS2SbqH+brGTbq0g==",
+      "dev": true
+    },
+    "glob": {
+      "version": "7.2.3",
+      "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
+      "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
+      "dev": true,
+      "requires": {
+        "fs.realpath": "^1.0.0",
+        "inflight": "^1.0.4",
+        "inherits": "2",
+        "minimatch": "^3.1.1",
+        "once": "^1.3.0",
+        "path-is-absolute": "^1.0.0"
+      }
+    },
+    "glob-parent": {
+      "version": "6.0.2",
+      "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
+      "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==",
+      "dev": true,
+      "requires": {
+        "is-glob": "^4.0.3"
+      }
+    },
+    "globals": {
+      "version": "13.16.0",
+      "resolved": "https://registry.npmjs.org/globals/-/globals-13.16.0.tgz",
+      "integrity": "sha512-A1lrQfpNF+McdPOnnFqY3kSN0AFTy485bTi1bkLk4mVPODIUEcSfhHgRqA+QdXPksrSTTztYXx37NFV+GpGk3Q==",
+      "dev": true,
+      "requires": {
+        "type-fest": "^0.20.2"
+      }
+    },
+    "has": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
+      "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==",
+      "dev": true,
+      "requires": {
+        "function-bind": "^1.1.1"
+      }
+    },
+    "has-flag": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
+      "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
+      "dev": true
+    },
+    "ignore": {
+      "version": "5.2.0",
+      "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.0.tgz",
+      "integrity": "sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==",
+      "dev": true
+    },
+    "immutable": {
+      "version": "4.1.0",
+      "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.1.0.tgz",
+      "integrity": "sha512-oNkuqVTA8jqG1Q6c+UglTOD1xhC1BtjKI7XkCXRkZHrN5m18/XsnUp8Q89GkQO/z+0WjonSvl0FLhDYftp46nQ==",
+      "dev": true
+    },
+    "import-fresh": {
+      "version": "3.3.0",
+      "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz",
+      "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==",
+      "dev": true,
+      "requires": {
+        "parent-module": "^1.0.0",
+        "resolve-from": "^4.0.0"
+      }
+    },
+    "imurmurhash": {
+      "version": "0.1.4",
+      "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
+      "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==",
+      "dev": true
+    },
+    "inflight": {
+      "version": "1.0.6",
+      "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
+      "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==",
+      "dev": true,
+      "requires": {
+        "once": "^1.3.0",
+        "wrappy": "1"
+      }
+    },
+    "inherits": {
+      "version": "2.0.4",
+      "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
+      "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
+      "dev": true
+    },
+    "is-arrayish": {
+      "version": "0.3.2",
+      "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz",
+      "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==",
+      "dev": true
+    },
+    "is-binary-path": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
+      "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
+      "dev": true,
+      "requires": {
+        "binary-extensions": "^2.0.0"
+      }
+    },
+    "is-builtin-module": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-3.1.0.tgz",
+      "integrity": "sha512-OV7JjAgOTfAFJmHZLvpSTb4qi0nIILDV1gWPYDnDJUTNFM5aGlRAhk4QcT8i7TuAleeEV5Fdkqn3t4mS+Q11fg==",
+      "dev": true,
+      "requires": {
+        "builtin-modules": "^3.0.0"
+      }
+    },
+    "is-core-module": {
+      "version": "2.9.0",
+      "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.9.0.tgz",
+      "integrity": "sha512-+5FPy5PnwmO3lvfMb0AsoPaBG+5KHUI0wYFXOtYPnVVVspTFUuMZNfNaNVRt3FZadstu2c8x23vykRW/NBoU6A==",
+      "dev": true,
+      "requires": {
+        "has": "^1.0.3"
+      }
+    },
+    "is-extglob": {
+      "version": "2.1.1",
+      "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
+      "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
+      "dev": true
+    },
+    "is-glob": {
+      "version": "4.0.3",
+      "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
+      "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
+      "dev": true,
+      "requires": {
+        "is-extglob": "^2.1.1"
+      }
+    },
+    "is-module": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz",
+      "integrity": "sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g==",
+      "dev": true
+    },
+    "is-number": {
+      "version": "7.0.0",
+      "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
+      "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
+      "dev": true
+    },
+    "isexe": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
+      "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
+      "dev": true
+    },
+    "js-tokens": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
+      "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
+      "peer": true
+    },
+    "js-yaml": {
+      "version": "4.1.0",
+      "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
+      "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
+      "dev": true,
+      "requires": {
+        "argparse": "^2.0.1"
+      }
+    },
+    "json-schema-traverse": {
+      "version": "0.4.1",
+      "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
+      "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
+      "dev": true
+    },
+    "json-stable-stringify-without-jsonify": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz",
+      "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==",
+      "dev": true
+    },
+    "levn": {
+      "version": "0.4.1",
+      "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz",
+      "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==",
+      "dev": true,
+      "requires": {
+        "prelude-ls": "^1.2.1",
+        "type-check": "~0.4.0"
+      }
+    },
+    "lodash": {
+      "version": "4.17.21",
+      "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
+      "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
+      "dev": true
+    },
+    "lodash.merge": {
+      "version": "4.6.2",
+      "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
+      "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==",
+      "dev": true
+    },
+    "loose-envify": {
+      "version": "1.4.0",
+      "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
+      "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
+      "peer": true,
+      "requires": {
+        "js-tokens": "^3.0.0 || ^4.0.0"
+      }
+    },
+    "lru-cache": {
+      "version": "6.0.0",
+      "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
+      "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
+      "dev": true,
+      "requires": {
+        "yallist": "^4.0.0"
+      }
+    },
+    "magic-string": {
+      "version": "0.25.9",
+      "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.9.tgz",
+      "integrity": "sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==",
+      "requires": {
+        "sourcemap-codec": "^1.4.8"
+      }
+    },
+    "merge2": {
+      "version": "1.4.1",
+      "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
+      "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==",
+      "dev": true
+    },
+    "micromatch": {
+      "version": "4.0.5",
+      "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz",
+      "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==",
+      "dev": true,
+      "requires": {
+        "braces": "^3.0.2",
+        "picomatch": "^2.3.1"
+      }
+    },
+    "minimatch": {
+      "version": "3.1.2",
+      "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
+      "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
+      "dev": true,
+      "requires": {
+        "brace-expansion": "^1.1.7"
+      }
+    },
+    "mockjs": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/mockjs/-/mockjs-1.1.0.tgz",
+      "integrity": "sha512-eQsKcWzIaZzEZ07NuEyO4Nw65g0hdWAyurVol1IPl1gahRwY+svqzfgfey8U8dahLwG44d6/RwEzuK52rSa/JQ==",
+      "dev": true,
+      "requires": {
+        "commander": "*"
+      }
+    },
+    "ms": {
+      "version": "2.1.2",
+      "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
+      "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
+      "dev": true
+    },
+    "nanoid": {
+      "version": "3.3.4",
+      "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz",
+      "integrity": "sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw=="
+    },
+    "natural-compare": {
+      "version": "1.4.0",
+      "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz",
+      "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==",
+      "dev": true
+    },
+    "normalize-path": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
+      "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
+      "dev": true
+    },
+    "nth-check": {
+      "version": "2.1.1",
+      "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz",
+      "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==",
+      "dev": true,
+      "requires": {
+        "boolbase": "^1.0.0"
+      }
+    },
+    "number-precision": {
+      "version": "1.5.2",
+      "resolved": "https://registry.npmjs.org/number-precision/-/number-precision-1.5.2.tgz",
+      "integrity": "sha512-q7C1ZW3FyjsJ+IpGB6ykX8OWWa5+6M+hEY0zXBlzq1Sq1IPY9GeI3CQ9b2i6CMIYoeSuFhop2Av/OhCxClXqag==",
+      "dev": true
+    },
+    "on-finished": {
+      "version": "2.3.0",
+      "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz",
+      "integrity": "sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==",
+      "dev": true,
+      "requires": {
+        "ee-first": "1.1.1"
+      }
+    },
+    "once": {
+      "version": "1.4.0",
+      "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
+      "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
+      "dev": true,
+      "requires": {
+        "wrappy": "1"
+      }
+    },
+    "optionator": {
+      "version": "0.9.1",
+      "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz",
+      "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==",
+      "dev": true,
+      "requires": {
+        "deep-is": "^0.1.3",
+        "fast-levenshtein": "^2.0.6",
+        "levn": "^0.4.1",
+        "prelude-ls": "^1.2.1",
+        "type-check": "^0.4.0",
+        "word-wrap": "^1.2.3"
+      }
+    },
+    "parent-module": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
+      "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==",
+      "dev": true,
+      "requires": {
+        "callsites": "^3.0.0"
+      }
+    },
+    "parseurl": {
+      "version": "1.3.3",
+      "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
+      "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==",
+      "dev": true
+    },
+    "path-is-absolute": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
+      "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==",
+      "dev": true
+    },
+    "path-key": {
+      "version": "3.1.1",
+      "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
+      "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
+      "dev": true
+    },
+    "path-parse": {
+      "version": "1.0.7",
+      "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
+      "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
+      "dev": true
+    },
+    "path-to-regexp": {
+      "version": "6.2.1",
+      "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.2.1.tgz",
+      "integrity": "sha512-JLyh7xT1kizaEvcaXOQwOc2/Yhw6KZOvPf1S8401UyLk86CU79LN3vl7ztXGm/pZ+YjoyAJ4rxmHwbkBXJX+yw==",
+      "dev": true
+    },
+    "picocolors": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz",
+      "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ=="
+    },
+    "picomatch": {
+      "version": "2.3.1",
+      "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
+      "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
+      "dev": true
+    },
+    "pinia": {
+      "version": "2.0.14",
+      "resolved": "https://registry.npmjs.org/pinia/-/pinia-2.0.14.tgz",
+      "integrity": "sha512-0nPuZR4TetT/WcLN+feMSjWJku3SQU7dBbXC6uw+R6FLQJCsg+/0pzXyD82T1FmAYe0lsx+jnEDQ1BLgkRKlxA==",
+      "requires": {
+        "@vue/devtools-api": "^6.1.4",
+        "vue-demi": "*"
+      },
+      "dependencies": {
+        "vue-demi": {
+          "version": "0.13.2",
+          "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.13.2.tgz",
+          "integrity": "sha512-41ukrclEbMddAyP7PvxMSYqnOSzPV6r7GNnyTSKSCNTaz19GehxmTiXyP9kwHSUv2+Dr6hHqiUiF7L1VAw2KdQ==",
+          "requires": {}
+        }
+      }
+    },
+    "postcss": {
+      "version": "8.4.14",
+      "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.14.tgz",
+      "integrity": "sha512-E398TUmfAYFPBSdzgeieK2Y1+1cpdxJx8yXbK/m57nRhKSmk1GB2tO4lbLBtlkfPQTDKfe4Xqv1ASWPpayPEig==",
+      "requires": {
+        "nanoid": "^3.3.4",
+        "picocolors": "^1.0.0",
+        "source-map-js": "^1.0.2"
+      }
+    },
+    "postcss-selector-parser": {
+      "version": "6.0.10",
+      "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.10.tgz",
+      "integrity": "sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w==",
+      "dev": true,
+      "requires": {
+        "cssesc": "^3.0.0",
+        "util-deprecate": "^1.0.2"
+      }
+    },
+    "prelude-ls": {
+      "version": "1.2.1",
+      "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
+      "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==",
+      "dev": true
+    },
+    "punycode": {
+      "version": "2.1.1",
+      "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz",
+      "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==",
+      "dev": true
+    },
+    "queue-microtask": {
+      "version": "1.2.3",
+      "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
+      "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==",
+      "dev": true
+    },
+    "rc-util": {
+      "version": "5.22.5",
+      "resolved": "https://registry.npmjs.org/rc-util/-/rc-util-5.22.5.tgz",
+      "integrity": "sha512-awD2TGMGU97OZftT2R3JwrHWjR8k/xIwqjwcivPskciweUdgXE7QsyXkBKVSBHXS+c17AWWMDWuKWsJSheQy8g==",
+      "requires": {
+        "@babel/runtime": "^7.18.3",
+        "react-is": "^16.12.0",
+        "shallowequal": "^1.1.0"
+      }
+    },
+    "react": {
+      "version": "18.2.0",
+      "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz",
+      "integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==",
+      "peer": true,
+      "requires": {
+        "loose-envify": "^1.1.0"
+      }
+    },
+    "react-dom": {
+      "version": "18.2.0",
+      "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz",
+      "integrity": "sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==",
+      "peer": true,
+      "requires": {
+        "loose-envify": "^1.1.0",
+        "scheduler": "^0.23.0"
+      }
+    },
+    "react-is": {
+      "version": "16.13.1",
+      "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
+      "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
+    },
+    "readdirp": {
+      "version": "3.6.0",
+      "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
+      "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
+      "dev": true,
+      "requires": {
+        "picomatch": "^2.2.1"
+      }
+    },
+    "regenerator-runtime": {
+      "version": "0.13.9",
+      "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz",
+      "integrity": "sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA=="
+    },
+    "regexpp": {
+      "version": "3.2.0",
+      "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz",
+      "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==",
+      "dev": true
+    },
+    "resize-observer-polyfill": {
+      "version": "1.5.1",
+      "resolved": "https://registry.npmjs.org/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz",
+      "integrity": "sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg==",
+      "dev": true
+    },
+    "resolve": {
+      "version": "1.22.1",
+      "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz",
+      "integrity": "sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==",
+      "dev": true,
+      "requires": {
+        "is-core-module": "^2.9.0",
+        "path-parse": "^1.0.7",
+        "supports-preserve-symlinks-flag": "^1.0.0"
+      }
+    },
+    "resolve-from": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
+      "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==",
+      "dev": true
+    },
+    "reusify": {
+      "version": "1.0.4",
+      "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz",
+      "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==",
+      "dev": true
+    },
+    "rimraf": {
+      "version": "3.0.2",
+      "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",
+      "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==",
+      "dev": true,
+      "requires": {
+        "glob": "^7.1.3"
+      }
+    },
+    "rollup": {
+      "version": "2.75.7",
+      "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.75.7.tgz",
+      "integrity": "sha512-VSE1iy0eaAYNCxEXaleThdFXqZJ42qDBatAwrfnPlENEZ8erQ+0LYX4JXOLPceWfZpV1VtZwZ3dFCuOZiSyFtQ==",
+      "dev": true,
+      "requires": {
+        "fsevents": "~2.3.2"
+      }
+    },
+    "run-parallel": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz",
+      "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==",
+      "dev": true,
+      "requires": {
+        "queue-microtask": "^1.2.2"
+      }
+    },
+    "sass": {
+      "version": "1.53.0",
+      "resolved": "https://registry.npmjs.org/sass/-/sass-1.53.0.tgz",
+      "integrity": "sha512-zb/oMirbKhUgRQ0/GFz8TSAwRq2IlR29vOUJZOx0l8sV+CkHUfHa4u5nqrG+1VceZp7Jfj59SVW9ogdhTvJDcQ==",
+      "dev": true,
+      "requires": {
+        "chokidar": ">=3.0.0 <4.0.0",
+        "immutable": "^4.0.0",
+        "source-map-js": ">=0.6.2 <2.0.0"
+      }
+    },
+    "scheduler": {
+      "version": "0.23.0",
+      "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.0.tgz",
+      "integrity": "sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==",
+      "peer": true,
+      "requires": {
+        "loose-envify": "^1.1.0"
+      }
+    },
+    "scroll-into-view-if-needed": {
+      "version": "2.2.29",
+      "resolved": "https://registry.npmjs.org/scroll-into-view-if-needed/-/scroll-into-view-if-needed-2.2.29.tgz",
+      "integrity": "sha512-hxpAR6AN+Gh53AdAimHM6C8oTN1ppwVZITihix+WqalywBeFcQ6LdQP5ABNl26nX8GTEL7VT+b8lKpdqq65wXg==",
+      "dev": true,
+      "requires": {
+        "compute-scroll-into-view": "^1.0.17"
+      }
+    },
+    "semver": {
+      "version": "7.3.7",
+      "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz",
+      "integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==",
+      "dev": true,
+      "requires": {
+        "lru-cache": "^6.0.0"
+      }
+    },
+    "shallowequal": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz",
+      "integrity": "sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ=="
+    },
+    "shebang-command": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
+      "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
+      "dev": true,
+      "requires": {
+        "shebang-regex": "^3.0.0"
+      }
+    },
+    "shebang-regex": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
+      "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
+      "dev": true
+    },
+    "simple-swizzle": {
+      "version": "0.2.2",
+      "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz",
+      "integrity": "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==",
+      "dev": true,
+      "requires": {
+        "is-arrayish": "^0.3.1"
+      }
+    },
+    "source-map": {
+      "version": "0.6.1",
+      "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
+      "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="
+    },
+    "source-map-js": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz",
+      "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw=="
+    },
+    "sourcemap-codec": {
+      "version": "1.4.8",
+      "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz",
+      "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA=="
+    },
+    "statuses": {
+      "version": "1.5.0",
+      "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz",
+      "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==",
+      "dev": true
+    },
+    "strip-ansi": {
+      "version": "6.0.1",
+      "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+      "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+      "dev": true,
+      "requires": {
+        "ansi-regex": "^5.0.1"
+      }
+    },
+    "strip-json-comments": {
+      "version": "3.1.1",
+      "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz",
+      "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==",
+      "dev": true
+    },
+    "supports-color": {
+      "version": "7.2.0",
+      "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
+      "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
+      "dev": true,
+      "requires": {
+        "has-flag": "^4.0.0"
+      }
+    },
+    "supports-preserve-symlinks-flag": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
+      "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==",
+      "dev": true
+    },
+    "text-table": {
+      "version": "0.2.0",
+      "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz",
+      "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==",
+      "dev": true
+    },
+    "to-regex-range": {
+      "version": "5.0.1",
+      "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
+      "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
+      "dev": true,
+      "requires": {
+        "is-number": "^7.0.0"
+      }
+    },
+    "type-check": {
+      "version": "0.4.0",
+      "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
+      "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==",
+      "dev": true,
+      "requires": {
+        "prelude-ls": "^1.2.1"
+      }
+    },
+    "type-fest": {
+      "version": "0.20.2",
+      "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz",
+      "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==",
+      "dev": true
+    },
+    "unpipe": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
+      "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==",
+      "dev": true
+    },
+    "uri-js": {
+      "version": "4.4.1",
+      "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
+      "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==",
+      "dev": true,
+      "requires": {
+        "punycode": "^2.1.0"
+      }
+    },
+    "util-deprecate": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
+      "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
+      "dev": true
+    },
+    "utils-merge": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
+      "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==",
+      "dev": true
+    },
+    "v8-compile-cache": {
+      "version": "2.3.0",
+      "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz",
+      "integrity": "sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==",
+      "dev": true
+    },
+    "vite": {
+      "version": "2.9.13",
+      "resolved": "https://registry.npmjs.org/vite/-/vite-2.9.13.tgz",
+      "integrity": "sha512-AsOBAaT0AD7Mhe8DuK+/kE4aWYFMx/i0ZNi98hJclxb4e0OhQcZYUrvLjIaQ8e59Ui7txcvKMiJC1yftqpQoDw==",
+      "dev": true,
+      "requires": {
+        "esbuild": "^0.14.27",
+        "fsevents": "~2.3.2",
+        "postcss": "^8.4.13",
+        "resolve": "^1.22.0",
+        "rollup": "^2.59.0"
+      }
+    },
+    "vite-plugin-mock": {
+      "version": "2.9.6",
+      "resolved": "https://registry.npmjs.org/vite-plugin-mock/-/vite-plugin-mock-2.9.6.tgz",
+      "integrity": "sha512-/Rm59oPppe/ncbkSrUuAxIQihlI2YcBmnbR4ST1RA2VzM1C0tEQc1KlbQvnUGhXECAGTaQN2JyasiwXP6EtKgg==",
+      "dev": true,
+      "requires": {
+        "@rollup/plugin-node-resolve": "^13.0.4",
+        "@types/mockjs": "^1.0.4",
+        "chalk": "^4.1.2",
+        "chokidar": "^3.5.2",
+        "connect": "^3.7.0",
+        "debug": "^4.3.2",
+        "esbuild": "0.11.3",
+        "fast-glob": "^3.2.7",
+        "path-to-regexp": "^6.2.0"
+      },
+      "dependencies": {
+        "esbuild": {
+          "version": "0.11.3",
+          "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.11.3.tgz",
+          "integrity": "sha512-BzVRHcCtFepjS9WcqRjqoIxLqgpK21a8J4Zi4msSGxDxiXVO1IbcqT1KjhdDDnJxKfe7bvzZrvMEX+bVO0Elcw==",
+          "dev": true
+        }
+      }
+    },
+    "vue": {
+      "version": "3.2.37",
+      "resolved": "https://registry.npmjs.org/vue/-/vue-3.2.37.tgz",
+      "integrity": "sha512-bOKEZxrm8Eh+fveCqS1/NkG/n6aMidsI6hahas7pa0w/l7jkbssJVsRhVDs07IdDq7h9KHswZOgItnwJAgtVtQ==",
+      "requires": {
+        "@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-eslint-parser": {
+      "version": "9.0.3",
+      "resolved": "https://registry.npmjs.org/vue-eslint-parser/-/vue-eslint-parser-9.0.3.tgz",
+      "integrity": "sha512-yL+ZDb+9T0ELG4VIFo/2anAOz8SvBdlqEnQnvJ3M7Scq56DvtjY0VY88bByRZB0D4J0u8olBcfrXTVONXsh4og==",
+      "dev": true,
+      "requires": {
+        "debug": "^4.3.4",
+        "eslint-scope": "^7.1.1",
+        "eslint-visitor-keys": "^3.3.0",
+        "espree": "^9.3.1",
+        "esquery": "^1.4.0",
+        "lodash": "^4.17.21",
+        "semver": "^7.3.6"
+      }
+    },
+    "vue-router": {
+      "version": "4.1.1",
+      "resolved": "https://registry.npmjs.org/vue-router/-/vue-router-4.1.1.tgz",
+      "integrity": "sha512-Wp1mEf2xCwT0ez7o9JvgpfBp9JGnVb+dPERzXDbugTatzJAJ60VWOhJKifQty85k+jOreoFHER4r5fu062PhPw==",
+      "requires": {
+        "@vue/devtools-api": "^6.1.4"
+      }
+    },
+    "which": {
+      "version": "2.0.2",
+      "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
+      "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
+      "dev": true,
+      "requires": {
+        "isexe": "^2.0.0"
+      }
+    },
+    "word-wrap": {
+      "version": "1.2.3",
+      "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz",
+      "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==",
+      "dev": true
+    },
+    "wrappy": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
+      "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
+      "dev": true
+    },
+    "xml-name-validator": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-4.0.0.tgz",
+      "integrity": "sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==",
+      "dev": true
+    },
+    "yallist": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
+      "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
+      "dev": true
+    }
+  }
+}

+ 27 - 0
package.json

@@ -0,0 +1,27 @@
+{
+  "name": "quark_arco",
+  "private": true,
+  "version": "0.0.0",
+  "scripts": {
+    "dev": "vite",
+    "build": "vite build",
+    "preview": "vite preview"
+  },
+  "dependencies": {
+    "@ant-design/icons": "^4.7.0",
+    "axios": "^0.26.1",
+    "pinia": "^2.0.14",
+    "vue": "^3.2.25",
+    "vue-router": "^4.0.14"
+  },
+  "devDependencies": {
+    "@arco-design/web-vue": "^2.24.1",
+    "@vitejs/plugin-vue": "^2.3.1",
+    "eslint": "^8.15.0",
+    "eslint-plugin-vue": "^9.0.1",
+    "mockjs": "^1.1.0",
+    "sass": "^1.50.1",
+    "vite": "^2.9.5",
+    "vite-plugin-mock": "^2.9.6"
+  }
+}

+ 19 - 0
prettier.config.js

@@ -0,0 +1,19 @@
+module.exports = {
+  "arrowParens": "always",
+  "bracketSameLine": false,
+  "bracketSpacing": true,
+  "embeddedLanguageFormatting": "auto",
+  "htmlWhitespaceSensitivity": "css",
+  "insertPragma": false,
+  "jsxSingleQuote": false,
+  "printWidth": 80,
+  "proseWrap": "preserve",
+  "quoteProps": "as-needed",
+  "requirePragma": false,
+  "semi": true,
+  "singleQuote": true,
+  "tabWidth": 2,
+  "trailingComma": "es5",
+  "useTabs": false,
+  "vueIndentScriptAndStyle": false
+}

+ 3 - 0
src/App.vue

@@ -0,0 +1,3 @@
+<template>
+  <router-view></router-view>
+</template>

+ 31 - 0
src/api/admin/login.js

@@ -0,0 +1,31 @@
+import request from '../../utils/request';
+
+export function login(data) {
+  return request({
+    url: '/api/v1/login',
+    method: 'post',
+    data
+  })
+}
+
+export function getCaptcha() {
+  return request({
+    url: '/api/v1/captcha',
+    method: 'get'
+  })
+}
+
+export function getAppConfig() {
+  return request({
+    url: '/api/v1/app-config',
+    method: 'get'
+  })
+}
+
+// 根据角色获取菜单
+export function getUserMenuRole() {
+  return request({
+    url: '/api/v1/menurole',
+    method: 'get'
+  })
+}

+ 48 - 0
src/api/admin/menu.js

@@ -0,0 +1,48 @@
+import request from '../../utils/request'
+
+const url = '/api/v1/menu';
+
+export function getMenu(params) {
+  return request({
+    url,
+    method: 'get',
+    params
+  })
+}
+
+export function getMenuDetails(menuId) {
+  return request({
+    url: `${url}/${menuId}`,
+    method: 'get'
+  })
+}
+
+export function addMenu(data) {
+  return request({
+    url,
+    method: 'post',
+    data
+  })
+}
+
+export function removeMenu(data) {
+  return request({
+    url,
+    method: 'delete',
+    data
+  })
+}
+
+/**
+ * 修改菜单
+ * @param {Object} data 
+ * @param {Number} id 
+ * @returns 
+ */
+export function updateMenu(data, id) {
+  return request({
+    url: `${url}/${id}`,
+    method: 'put',
+    data
+  })
+}

+ 35 - 0
src/api/admin/post.js

@@ -0,0 +1,35 @@
+import request from '../../utils/request'
+
+const url = '/api/v1/post';
+
+export function getPost(params) {
+  return request({
+    url,
+    method: 'get',
+    params
+  })
+}
+
+export function addPost(data) {
+  return request({
+    url,
+    method: 'post',
+    data
+  })
+}
+
+export function removePost(data) {
+  return request({
+    url,
+    method: 'delete',
+    data
+  })
+}
+
+export function updatePost(data, id) {
+  return request({
+    url: `${url}/${id}`,
+    method: 'put',
+    data
+  })
+}

+ 51 - 0
src/api/admin/role.js

@@ -0,0 +1,51 @@
+import request from '../../utils/request';
+
+const url = '/api/v1/role';
+
+export function getRole(params) {
+  return request({
+    url,
+    method: 'get',
+    params
+  })
+}
+
+export function addRole(data) {
+  return request({
+    url,
+    method: 'post',
+    data
+  })
+}
+
+export function removeRole(data) {
+  return request({
+    url,
+    method: 'delete',
+    data
+  })
+}
+
+export function updateRole(data, id) {
+  return request({
+    url: `${url}/${id}`,
+    method: 'put',
+    data,
+  })
+}
+
+export function updateRoleScoped(data) {
+  return request({
+    url: '/api/v1/roledatascope',
+    method: 'put',
+    data
+  })
+}
+
+export function getRoleMenuTree(params, roleId) {
+  return request({
+    url: `/api/v1/roleMenuTreeselect/${roleId}`,
+    method: 'get',
+    params
+  })
+}

+ 35 - 0
src/api/admin/sys-api.js

@@ -0,0 +1,35 @@
+import request from '../../utils/request';
+
+const url = '/api/v1/sys-api';
+
+export function getSysApi(params) {
+  return request({
+    url,
+    method: 'get',
+    params
+  })
+}
+
+export function addSysApi(data) {
+  return request({
+    url,
+    method: 'post',
+    data
+  })
+}
+
+export function removeSysApi(data) {
+  return request({
+    url,
+    method: 'delete',
+    data
+  })
+}
+
+export function updateSysApi(data, id) {
+  return request({
+    url: `${url}/${id}`,
+    method: 'put',
+    data,
+  })
+}

+ 35 - 0
src/api/admin/sys-config.js

@@ -0,0 +1,35 @@
+import request from '../../utils/request';
+
+const url = '/api/v1/config';
+
+export function getSysConfig(params) {
+  return request({
+    url,
+    method: 'get',
+    params
+  })
+}
+
+export function addSysConfig(data) {
+  return request({
+    url,
+    method: 'post',
+    data
+  })
+}
+
+export function removeSysConfig(data) {
+  return request({
+    url,
+    method: 'delete',
+    data
+  })
+}
+
+export function updateSysConfig(data, id) {
+  return request({
+    url: `${url}/${id}`,
+    method: 'put',
+    data,
+  })
+}

+ 35 - 0
src/api/admin/sys-dept.js

@@ -0,0 +1,35 @@
+import request from '../../utils/request';
+
+const url = '/api/v1/dept';
+
+export function getDept(params) {
+  return request({
+    url,
+    method: 'get',
+    params
+  })
+}
+
+export function addDept(data) {
+  return request({
+    url,
+    method: 'post',
+    data
+  })
+}
+
+export function removeDept(data) {
+  return request({
+    url,
+    method: 'delete',
+    data
+  })
+}
+
+export function updateDept(data, id) {
+  return request({
+    url: `${url}/${id}`,
+    method: 'put',
+    data,
+  })
+}

+ 35 - 0
src/api/admin/sys-dict-data.js

@@ -0,0 +1,35 @@
+import request from '@/utils/request';
+
+const url = '/api/v1/dict/data';
+
+export function getDictData(params) {
+  return request({
+    url,
+    method: 'get',
+    params
+  })
+}
+
+export function addDictData(data) {
+  return request({
+    url,
+    method: 'post',
+    data
+  })
+}
+
+export function updateDictData(data, dictCode) {
+  return request({
+    url: `${url}/${dictCode}`,
+    method: 'put',
+    data
+  })
+}
+
+export function deleteDictData(data) {
+  return request({
+    url,
+    method: 'delete',
+    data
+  })
+}

+ 35 - 0
src/api/admin/sys-dict.js

@@ -0,0 +1,35 @@
+import request from '../../utils/request';
+
+const url = '/api/v1/dict/type';
+
+export function getDictType(params) {
+  return request({
+    url,
+    method: 'get',
+    params
+  })
+}
+
+export function addDictType(data) {
+  return request({
+    url,
+    method: 'post',
+    data
+  })
+}
+
+export function removeDictType(data) {
+  return request({
+    url,
+    method: 'delete',
+    data
+  })
+}
+
+export function updateDictType(data, id) {
+  return request({
+    url: `${url}/${id}`,
+    method: 'put',
+    data,
+  })
+}

+ 19 - 0
src/api/admin/sys-login-log.js

@@ -0,0 +1,19 @@
+import request from '../../utils/request';
+
+const url = '/api/v1/sys-login-log';
+
+export function getSysLoginLog(params) {
+  return request({
+    url,
+    method: 'GET',
+    params
+  })
+}
+
+export function removeSysLoginLog(data) {
+  return request({
+    url,
+    method: 'DELETE',
+    data
+  })
+}

+ 19 - 0
src/api/admin/sys-opera-log.js

@@ -0,0 +1,19 @@
+import request from '../../utils/request';
+
+const url = '/api/v1/sys-opera-log';
+
+export function getSysOperaLog(params) {
+  return request({
+    url,
+    method: 'get',
+    params,
+  });
+}
+
+export function removeSysOperaLog(data) {
+  return request({
+    url,
+    method: 'delete',
+    data,
+  });
+}

+ 66 - 0
src/api/admin/sys-user.js

@@ -0,0 +1,66 @@
+import request from '../../utils/request';
+
+const url = '/api/v1/sys-user';
+
+export function getUser(params) {
+  return request({
+    url,
+    method: 'get',
+    params
+  })
+}
+
+export function getInfo() {
+  return request({
+    url: '/api/v1/getinfo',
+    method: 'get'
+  })
+}
+
+export function addUser(data) {
+  return request({
+    url,
+    method: 'post',
+    data
+  })
+}
+
+export function removeUser(data) {
+  return request({
+    url,
+    method: 'delete',
+    data
+  })
+}
+
+export function updateUser(data) {
+  return request({
+    url,
+    method: 'put',
+    data,
+  })
+}
+
+export function updateUserStatus(data) {
+  return request({
+    url: '/api/v1/user/status',
+    method: 'put',
+    data
+  })
+}
+
+export function resetUserPwd(data) {
+  return request({
+    url: '/api/v1/user/pwd/reset',
+    method: 'put',
+    data
+  })
+}
+
+// 获取当前登录用户信息
+export function getCurrentUser(uid) {
+  return request({
+    url: `${url}/${uid}`,
+    method: 'get'
+  })
+}

+ 62 - 0
src/api/sys-job.js

@@ -0,0 +1,62 @@
+import request from '@/utils/request'
+
+// 查询SysJob列表
+export function listSysJob(query) {
+  return request({
+    url: '/api/v1/sysjob',
+    method: 'get',
+    params: query
+  })
+}
+
+// 查询SysJob详细
+export function getSysJob(jobId) {
+  return request({
+    url: '/api/v1/sysjob/' + jobId,
+    method: 'get'
+  })
+}
+
+// 新增SysJob
+export function addSysJob(data) {
+  return request({
+    url: '/api/v1/sysjob',
+    method: 'post',
+    data: data
+  })
+}
+
+// 修改SysJob
+export function updateSysJob(data) {
+  return request({
+    url: '/api/v1/sysjob',
+    method: 'put',
+    data: data
+  })
+}
+
+// 删除SysJob
+export function delSysJob(data) {
+  return request({
+    url: '/api/v1/sysjob',
+    method: 'delete',
+    data: data
+  })
+}
+
+// 移除SysJob
+export function removeJob(jobId) {
+  return request({
+    url: '/api/v1/job/remove/' + jobId,
+    method: 'get'
+  })
+}
+
+// 启动SysJob
+export function startJob(jobId) {
+  return request({
+    url: '/api/v1/job/start/' + jobId,
+    method: 'get'
+  })
+}
+

+ 10 - 0
src/api/sys-tools/monitor.js

@@ -0,0 +1,10 @@
+import request from '../../utils/request'
+
+const url = '/api/v1/server-monitor';
+
+export function getServerMonitor() {
+  return request({
+    url,
+    method: 'GET',
+  })
+}

+ 74 - 0
src/components/Avatar/index.vue

@@ -0,0 +1,74 @@
+<template>
+  <a-dropdown position="bl" :style="{ top: '52px' }">
+    <div class="avatar-container">
+      <a-avatar :size="32" :style="{ backgroundColor: '#3370ff' }">
+        <img alt="avatar" :src="userInfo.avatar" />
+      </a-avatar>
+      <div class="user-info">
+        <div class="user-info-name">{{ userInfo.name }}</div>
+        <div class="user-info-desc">{{ userInfo.introduction }}</div>
+      </div>
+    </div>
+    <template #content>
+      <a-doption @click="$router.push('/profile')">
+        <template #icon>
+          <icon-settings />
+        </template>
+        <template #default>用户设置</template>
+      </a-doption>
+      <a-doption @click="handleLogout()">
+        <template #icon>
+          <icon-export />
+        </template>
+        <template #default>退出登陆</template>
+      </a-doption>
+    </template>
+  </a-dropdown>
+</template>
+
+<script setup>
+import { getCurrentInstance } from 'vue';
+import { storeToRefs } from 'pinia';
+import {
+  IconSettings,
+  IconExport,
+} from '@arco-design/web-vue/es/icon';
+import { useUserStore } from '@/store/userInfo';
+import { clearLocalStorage } from '@/utils/storage';
+
+const store = useUserStore();
+const { userInfo } = storeToRefs(store);
+
+const { proxy } = getCurrentInstance();
+
+const handleLogout = () => {
+  proxy.$modal.warning({
+    title: '提示',
+    content: '确定注销并退出登陆系统吗?',
+    hideCancel: false,
+    onOk: () => {
+      window.sessionStorage.removeItem('token');
+      clearLocalStorage();
+      proxy.$router.push('/login');
+    },
+  });
+};
+</script>
+
+<style lang="scss" scoped>
+.avatar-container {
+  display: flex;
+  cursor: pointer;
+  .user-info {
+    margin-left: 10px;
+    &-name {
+      color: $primary-font-color;
+    }
+    &-desc {
+      color: $secondary-font-color;
+      font-size: 12px;
+      line-height: 1.4;
+    }
+  }
+}
+</style>

+ 3 - 0
src/components/Dashboard.vue

@@ -0,0 +1,3 @@
+<template>
+  <div>Hello World</div>
+</template>

+ 91 - 0
src/components/Menu/Menu.vue

@@ -0,0 +1,91 @@
+<template>
+  <div class="sider-logo">
+    <img :src="store.sysConfig.sys_app_logo" />
+    <span class="sider-title" v-if="!props.collapsed">{{
+      store.sysConfig.sys_app_title
+    }}</span>
+  </div>
+  <a-menu
+    class="menu"
+    @menu-item-click="handleMenuClick"
+    :default-open-keys="['/admin']"
+    :default-selected-keys="defaultSelectKeys"
+    :auto-open-selected="true"
+  >
+    <sub-menu :menu-list="permissionStore.menuList" />
+  </a-menu>
+</template>
+
+<script setup>
+import { ref, onBeforeMount } from 'vue';
+import { useRoute, useRouter } from 'vue-router';
+import { useUserStore } from '@/store/userInfo';
+import { usePermissionStore } from '@/store/permission';
+
+import SubMenu from './subMenu.vue';
+
+const store = useUserStore();
+const permissionStore = usePermissionStore();
+
+const props = defineProps({
+  collapsed: {
+    type: Boolean,
+    default: false,
+  },
+});
+
+const route = useRoute();
+const router = useRouter();
+
+// 默认菜单选中
+const defaultSelectKeys = ref([]);
+
+// 刷新保持菜单选中
+const keepDefaultSelect = () => {
+  defaultSelectKeys.value = [];
+  defaultSelectKeys.value.push(route.fullPath);
+};
+
+const handleMenuClick = (key) => {
+  router.push(key);
+};
+
+onBeforeMount(() => {
+  keepDefaultSelect();
+});
+</script>
+
+<style lang="scss" scoped>
+.sider-logo {
+  margin: 10px 0;
+  display: flex;
+  justify-content: center;
+  padding-bottom: 8px;
+  border-bottom: 1px solid #e4e4e4;
+  & img {
+    height: 32px;
+  }
+}
+
+.sider-title {
+  margin-left: 10px;
+  display: flex;
+  align-items: center;
+  font-size: 16px;
+  color: $primary-font-color;
+}
+
+.left-side {
+  height: 50px;
+  width: 50px;
+  font-size: 18px;
+  line-height: 50px;
+  text-align: center;
+  transition: all 0.3s ease-in-out;
+  cursor: pointer;
+
+  &:hover {
+    background-color: #e5e5e5;
+  }
+}
+</style>

+ 37 - 0
src/components/Menu/SubMenu.vue

@@ -0,0 +1,37 @@
+<template>
+  <template v-for="menu in props.menuList" :key="menu.menuId">
+    <a-sub-menu
+      v-if="menu.children && menu.menuType == 'M' && menu.visible == 0"
+      :key="menu.path"
+    >
+      <template #icon>
+        <component :is="menu.icon" />
+      </template>
+      <template #title>{{ menu.title }}</template>
+      <sub-menu :menuList="menu.children" />
+    </a-sub-menu>
+    <a-menu-item
+      v-if="menu.menuType == 'C' && menu.visible && isRouteParams(menu.path)"
+      :key="menu.path"
+      >{{ menu.title }}</a-menu-item>
+  </template>
+</template>
+
+<script setup>
+import { computed } from 'vue'
+
+const props = defineProps({
+  menuList: {
+    type: Array,
+    default: () => [],
+  },
+});
+
+// 判断是否路由需要带参匹配
+const isRouteParams = computed(() => {
+  return (path) => {
+    if (path.indexOf(':') == -1) return true;
+    return false;
+  }
+})
+</script>

+ 0 - 0
src/components/SvgIcon.vue


+ 0 - 0
src/icons/index.js


+ 1 - 0
src/icons/svg/api-management.svg

@@ -0,0 +1 @@
+<svg width="24" height="24" viewBox="0 0 48 48" fill="currentColor"><path fill-rule="evenodd" clip-rule="evenodd" d="M4 15a1 1 0 001 1h2a1 1 0 001-1V8h7a1 1 0 001-1V5a1 1 0 00-1-1H6a2 2 0 00-2 2v9zm4 18a1 1 0 00-1-1H5a1 1 0 00-1 1v9a2 2 0 002 2h9a1 1 0 001-1v-2a1 1 0 00-1-1H8v-7zm35-17a1 1 0 001-1V6a2 2 0 00-2-2h-9a1 1 0 00-1 1v2a1 1 0 001 1h7v7a1 1 0 001 1h2zm1 17a1 1 0 00-1-1h-2a1 1 0 00-1 1v7h-7a1 1 0 00-1 1v2a1 1 0 001 1h9a2 2 0 002-2v-9zM32.835 11h-6.108c-6.512 0-11.882 4.804-12.636 11h-1.992c-.382 0-.52.046-.66.134a.855.855 0 00-.325.378c-.074.162-.114.324-.114.77v1.436c0 .446.04.608.114.77.075.163.185.291.324.378.14.088.279.134.66.134h2.157c1.179 5.706 6.315 10 12.472 10h6.108c.405 0 .552-.041.7-.12a.819.819 0 00.344-.337c.079-.145.121-.29.121-.688V31h.901c.382 0 .52-.046.66-.134a.855.855 0 00.325-.378c.074-.162.114-.324.114-.77v-1.436c0-.446-.04-.608-.114-.77a.855.855 0 00-.325-.378c-.14-.088-.278-.134-.66-.134H34v-7h.901c.382 0 .52-.046.66-.134a.855.855 0 00.325-.378c.074-.162.114-.324.114-.77v-1.436c0-.446-.04-.608-.114-.77a.855.855 0 00-.325-.378c-.14-.088-.278-.134-.66-.134H34v-3.855c0-.398-.042-.543-.121-.688a.819.819 0 00-.344-.338c-.148-.078-.295-.119-.7-.119zm-2.744 3.571h-3.637c-5.02 0-9.09 3.998-9.09 8.929s4.07 8.929 9.09 8.929h3.637V14.57z" fill="currentColor"/></svg>

+ 15 - 0
src/layout/components/AppMain.vue

@@ -0,0 +1,15 @@
+<template>
+  <div class="app-main">
+    <router-view v-slot="{ Component, route }">
+      <transition name="fade-transform" mode="out-in">
+        <component :is="Component" :key="route" />
+      </transition>
+    </router-view>
+  </div>
+</template>
+
+<style lang="scss">
+.app-main {
+  display: flex;
+}
+</style>

+ 93 - 0
src/layout/components/Navbar.vue

@@ -0,0 +1,93 @@
+<template>
+  <div class="navbar">
+    <div class="left-side" @click="onCollapse">
+      <icon-menu-fold v-if="!props.collapsed" />
+      <icon-menu-unfold v-else />
+    </div>
+    <ul class="right-side">
+      <li>
+        <a-button shape="circle" @click="handleDarkTheme">
+          <template #icon>
+            <component
+              :is="!darkTheme ? IconSunFill : IconMoonFill"
+            ></component>
+          </template>
+        </a-button>
+      </li>
+      <li>
+        <Avatar />
+      </li>
+    </ul>
+  </div>
+</template>
+
+<script setup>
+import { ref } from 'vue';
+import Avatar from '../../components/Avatar/index.vue';
+import {
+  IconMenuFold,
+  IconMenuUnfold,
+  IconSunFill,
+  IconMoonFill,
+} from '@arco-design/web-vue/es/icon';
+
+const darkTheme = ref(false);
+
+const props = defineProps({
+  collapsed: Boolean,
+});
+
+const emit = defineEmits(['onCollapse']);
+
+const onCollapse = () => {
+  emit('onCollapse');
+};
+
+// 切换亮色和暗色
+const handleDarkTheme = () => {
+  const theme = document.body.getAttribute('arco-theme');
+  if (!theme) {
+    document.body.setAttribute('arco-theme', 'dark');
+    darkTheme.value = true;
+  } else {
+    document.body.removeAttribute('arco-theme');
+    darkTheme.value = false;
+  }
+};
+</script>
+
+<style lang="scss" scoped>
+.navbar {
+  display: flex;
+  justify-content: space-between;
+  height: 50px;
+  border-bottom: 1px solid #e5e6eb;
+  box-shadow: 0 2px 5px 0 rgba(0,0,0, .1);
+
+  .left-side {
+    height: 50px;
+    width: 50px;
+    font-size: 18px;
+    line-height: 50px;
+    text-align: center;
+    transition: all 0.3s ease-in-out;
+    cursor: pointer;
+
+    &:hover {
+      background-color: #e5e5e5;
+    }
+  }
+
+  .right-side {
+    list-style: none;
+    display: flex;
+    padding-right: 30px;
+
+    & > li {
+      display: flex;
+      align-items: center;
+      padding: 10px;
+    }
+  }
+}
+</style>

+ 2 - 0
src/layout/components/index.js

@@ -0,0 +1,2 @@
+export { default as AppMain } from './AppMain.vue';
+export { default as Navbar } from './Navbar.vue';

+ 70 - 0
src/layout/index.vue

@@ -0,0 +1,70 @@
+<template>
+  <a-layout :style="{ height: '100vh' }">
+    <a-layout-sider
+      class="layout-sider"
+      :width="250"
+      :collapsed-width="50"
+      :collapsed="collapsed"
+    >
+      <Menu :collapsed="collapsed" />
+    </a-layout-sider>
+    <a-layout :class="[ collapsed ? 'fold' : 'unfold' ]">
+      <a-layout-header :class="[ 'layout-header', collapsed ? 'fold' : 'unfold' ]">
+        <Navbar :collapsed="collapsed" @on-collapse="onCollapse" />
+      </a-layout-header>
+      <a-layout-content class="layout-content">
+        <AppMain />
+      </a-layout-content>
+    </a-layout>
+  </a-layout>
+</template>
+
+<script setup>
+import { ref } from 'vue';
+import { AppMain, Navbar } from './components';
+import Menu from '../components/Menu/Menu.vue';
+
+const collapsed = ref(false);
+const onCollapse = () => {
+  collapsed.value = !collapsed.value;
+};
+</script>
+
+<style lang="scss">
+@import '../style/index.scss';
+@import '../style/transition.scss';
+@import '../style/dark-theme.scss';
+
+.layout-sider {
+  width: 250px;
+  position: fixed;
+  top: 0px;
+  left: 0px;
+  bottom: 0px;
+  z-index: 999;
+}
+
+.fold {
+  margin-left: 50px;
+}
+
+.unfold {
+  margin-left: 250px;
+}
+
+.layout-content {
+  margin-top: 50px;
+  padding: 20px;
+  background-color: #f2f3f5;
+}
+
+.layout-header {
+  position: fixed;
+  top: 0;
+  left: 0;
+  right: 0;
+  background-color: #fff;
+  z-index: 998;
+  transition: margin .1s ease-in-out;
+}
+</style>

+ 34 - 0
src/main.js

@@ -0,0 +1,34 @@
+import { createApp } from 'vue';
+import { createPinia } from 'pinia';
+import App from './App.vue';
+import ArcoVue from '@arco-design/web-vue';
+import { Message, Modal, Notification } from '@arco-design/web-vue';
+import '@arco-design/web-vue/dist/arco.css';
+import router from './router/';
+import { parseTime } from '@/utils/parseTime';
+
+// 引入 Arco 图标库
+import * as ArcoIconModules from '@arco-design/web-vue/es/icon';
+
+console.log(import.meta.env);
+
+// Initialize the Pinia instance
+const pinia = createPinia();
+const app = createApp(App);
+
+// 挂载全局变量
+app.config.globalProperties.message = Message;
+app.config.globalProperties.modal = Modal;
+app.config.globalProperties.notification = Notification;
+app.config.globalProperties.parseTime = parseTime;
+
+// 挂载全局图标
+for(const name in ArcoIconModules){
+  app.component(name,ArcoIconModules[name])
+}
+
+app.use(ArcoVue);
+app.use(router);
+app.use(pinia);
+app.mount('#app');
+

+ 78 - 0
src/router/index.js

@@ -0,0 +1,78 @@
+import { createWebHashHistory, createRouter } from 'vue-router';
+import Layout from '../layout/index.vue';
+import { useUserStore } from '../store/userInfo';
+import { usePermissionStore } from '../store/permission';
+
+const routes = [
+  {
+    path: '/',
+    name: '/',
+    redirect: 'admin',
+    component: Layout,
+  },
+  {
+    path: '/login',
+    name: 'login',
+    component: () => import('../views/login/index.vue'),
+  },
+  {
+    path: '/404',
+    name: '404',
+    component: () => import('../views/error-page/404.vue'),
+  },
+  {
+    path: '/profile',
+    name: 'profile',
+    component: () => import('../views/profile/index.vue'),
+    meta: {
+      name: '个人信息',
+    },
+  },
+];
+
+const router = createRouter({
+  history: createWebHashHistory(),
+  routes,
+});
+
+// beforeEach router
+router.beforeEach(async (to, from, next) => {
+  const store = useUserStore();
+
+  const permissionStore = usePermissionStore();
+
+  // 获取系统配置信息
+  await store.getSysConfig();
+
+  // 判断用户Token是否获取
+  if (to.name !== 'login' && !store.token) {
+    next({ name: 'login' });
+  } else {
+    // 判断判断权限有无获取
+    if (store.token && store.roles.length === 0) {
+      store.getUserInfo();
+      await permissionStore.getMenuRole();
+
+      permissionStore.addRouters.forEach((route) => {
+        router.addRoute('/', route);
+      });
+      next(to.fullPath);
+    } else {
+      next();
+    }
+
+  }
+});
+
+// afterEach Router
+router.afterEach((to) => {
+  const store = useUserStore();
+
+  // 修改网页标题
+  if (to.name !== 'login') {
+    document.title = `${to.meta.title}-${store.sysConfig.sys_app_title}`;
+  } else {
+    document.title = store.sysConfig.sys_app_title;
+  }
+});
+export default router;

+ 56 - 0
src/store/permission.js

@@ -0,0 +1,56 @@
+import { defineStore } from 'pinia';
+import { getUserMenuRole } from '@/api/admin/login';
+
+
+const modules = import.meta.glob('../views/**/*.vue');
+
+export const usePermissionStore = defineStore('permisson', {
+  state: () => {
+    return {
+      addRouters: [],
+      menuList: [],
+    };
+  },
+  getters: {
+    getRoutes: (state) => state.addRouters,
+  },
+  actions: {
+    setMenuList(menus) {
+      this.menuList = menus;
+    },
+    GenerateRoutes(routeList) {
+      const routes = [];
+
+      routeList.forEach((item) => {
+        const route = {};
+        if (item.visible == 0) {
+          if (item.menuType === 'M' || item.menuType === 'C') {
+            route.path = item.path;
+            route.name = item.menuName;
+            if (item.menuType === 'M') {
+              route.component = modules[`../views/index.vue`];
+            } else if (item.menuType === 'C') {
+              route.component = modules[`../views${item.component}.vue`];
+            }
+            route.meta = {
+              title: item.title,
+              permission: item.permission,
+            };
+          }
+
+          if (item.children) {
+            route.children = this.GenerateRoutes(item.children);
+          }
+          routes.push(route);
+        }
+      });
+
+      return routes;
+    },
+    async getMenuRole() {
+      const res = await getUserMenuRole();
+      this.setMenuList(res.data);
+      this.addRouters = await this.GenerateRoutes(res.data);
+    },
+  },
+});

+ 44 - 0
src/store/userInfo.js

@@ -0,0 +1,44 @@
+import { defineStore } from 'pinia';
+import { setLocalStorage, getLocalStorage } from '@/utils/storage';
+import { getInfo } from '@/api/admin/sys-user';
+import { getAppConfig } from '@/api/admin/login';
+
+export const useUserStore = defineStore('user', {
+  state: () => {
+    return {
+      token: window.sessionStorage.getItem('token') || '',
+      uid: window.sessionStorage.getItem('uid') || '',
+      sysConfig: getLocalStorage('sysConfig'),
+      userInfo: '',
+    }
+  },
+  getters: {
+    roles: (state) => state.userInfo.roles || [],
+  },
+  actions: {
+    setToken(token) {
+      this.token = token;
+
+      window.sessionStorage.setItem('token', token);
+    },
+    async getUserInfo() {
+      try {
+        const res = await getInfo();
+        // window.sessionStorage.setItem('userInfo', res.data);
+        window.sessionStorage.setItem('uid', res.data.userId);
+        this.userInfo = res.data;
+      } catch (err) {
+        console.error(err);
+      }
+    },
+    async getSysConfig() {
+      try {
+        const res = await getAppConfig();
+        setLocalStorage('sysConfig', res.data);
+        this.sysConfig = res.data;
+      } catch (err) {
+        console.error(err);
+      }
+    }
+  }
+})

+ 34 - 0
src/style/dark-theme.scss

@@ -0,0 +1,34 @@
+// Dark Theme
+
+$dark-bg-1: #17171A;
+$color-bg-2: #232324;
+$color-bg-3: #2a2a2b;
+$color-bg-4: #313132;
+$color-bg-5: #373739;
+
+$--color-text-1: rgba(255, 255, 255, 0.9);
+$--color-text-2: rgba(255, 255, 255, 0.7);
+$--color-text-3: rgba(255, 255, 255, 0.5);
+$--color-text-4: rgba(255, 255, 255, 0.3);
+
+body[arco-theme='dark'] {
+  .navbar {
+    background-color: $dark-bg-1;
+  }
+  .arco-layout-content {
+    background-color: $color-bg-2
+  }
+  .app-container {
+    background-color: $color-bg-3;
+  }
+  .navbar {
+    border-bottom: 1px solid $--color-text-4;
+    .left-side {
+      background-color: $color-bg-2;
+      color: $--color-text-1;
+      &:hover {
+        background-color: $color-bg-5;
+      }
+    }
+  }
+}

+ 52 - 0
src/style/index.scss

@@ -0,0 +1,52 @@
+// main-container 全局样式
+.app-container {
+  flex: 1;
+  padding: 20px 20px 12px 20px;
+  background-color: #fff;
+  border-radius: 5px;
+}
+
+// 表格操作栏样式
+.arco-table-th:last-child .arco-table-th-item-title {
+  margin-left: 16px;
+}
+
+// 数字输入框样式
+.arco-input-number .arco-input {
+  text-align: center;
+}
+
+// Table Tag 样式
+// .arco-table-td .arco-table-cell .arco-tag {
+//   margin-left: -8px;
+// }
+
+
+// 滚动条整体部分
+::-webkit-scrollbar {
+  width: 4px;
+  height: 4px;
+  background-color: transparent
+}
+
+// 滚动条外层轨道
+::-webkit-scrollbar-track {
+  border-radius: 10px;
+}
+
+// 滚动条滑块
+::-webkit-scrollbar-thumb {
+  background-color: rgba(0, 0, 0, .2);
+  border-radius: 10px;
+  transition: all .3s ease-in-out;
+}
+
+// 链接标签样式
+a {
+  text-decoration: none;
+  color: #165DFF;
+}
+
+a:hover {
+  color: #4080FF;
+}

+ 28 - 0
src/style/transition.scss

@@ -0,0 +1,28 @@
+// global transition css
+
+/* fade */
+.fade-enter-active,
+.fade-leave-active {
+  transition: opacity 0.28s;
+}
+
+.fade-enter,
+.fade-leave-active {
+  opacity: 0;
+}
+
+/* fade-transform */
+.fade-transform-leave-active,
+.fade-transform-enter-active {
+  transition: all .5s;
+}
+
+.fade-transform-enter-from {
+  opacity: 0;
+  transform: translateX(-30px);
+}
+
+.fade-transform-leave-to {
+  opacity: 0;
+  transform: translateX(30px);
+}

+ 3 - 0
src/style/variables.scss

@@ -0,0 +1,3 @@
+// base color
+$primary-font-color: #1d2129;
+$secondary-font-color: #86909c;

+ 41 - 0
src/utils/parseTime.js

@@ -0,0 +1,41 @@
+// 日期格式化
+export function parseTime(time, pattern) {
+  if (arguments.length === 0 || !time) {
+    return null
+  }
+  if (time.indexOf('01-01-01') > -1) {
+    return '-'
+  }
+  const format = pattern || '{y}-{m}-{d} {h}:{i}:{s}'
+  let date
+  if (typeof time === 'object') {
+    date = time
+  } else {
+    if ((typeof time === 'string') && (/^[0-9]+$/.test(time))) {
+      time = parseInt(time)
+    }
+    if ((typeof time === 'number') && (time.toString().length === 10)) {
+      time = time * 1000
+    }
+    date = new Date(time)
+  }
+  const formatObj = {
+    y: date.getFullYear(),
+    m: date.getMonth() + 1,
+    d: date.getDate(),
+    h: date.getHours(),
+    i: date.getMinutes(),
+    s: date.getSeconds(),
+    a: date.getDay()
+  }
+  const time_str = format.replace(/{(y|m|d|h|i|s|a)+}/g, (result, key) => {
+    let value = formatObj[key]
+    // Note: getDay() returns 0 on Sunday
+    if (key === 'a') { return ['日', '一', '二', '三', '四', '五', '六'][value] }
+    if (result.length > 0 && value < 10) {
+      value = '0' + value
+    }
+    return value || 0
+  })
+  return time_str
+}

+ 56 - 0
src/utils/request.js

@@ -0,0 +1,56 @@
+import axios from 'axios';
+import { Message } from '@arco-design/web-vue';
+import { useUserStore } from '../store/userInfo'
+
+// create an axios instance
+const service = axios.create({
+  baseUrl: import.meta.env.VITE_APP_BASE_URL,
+  timeout: 8000,
+});
+
+// request interceptor
+service.interceptors.request.use(
+  (config) => {
+    // Store 必须在拦截器内部导入,在外部导入会显示 Pinia 未初始化
+    const store = useUserStore();
+
+    // 设置请求头部 Authorization
+    if (store.token) {
+      config.headers['Authorization'] = 'Bearer ' + store.token;
+    }
+    
+    return config;
+  },
+  (error) => {
+    console.error(error);
+    return Promise.reject(error);
+  }
+);
+
+// response interceptor
+service.interceptors.response.use(
+  (response) => {
+    const res = response.data;
+
+    if (res.code !== 200) {
+      Message.error({
+        content: res.msg,
+        duration: 3000,
+      });
+
+      return Promise.reject(new Error(res.msg));
+    } else {
+      return res;
+    }
+  },
+  (error) => {
+    console.error(`err: ${error}`);
+    Message.error({
+      content: error.message,
+      duration: 3000
+    })
+    return Promise.reject(error);
+  }
+);
+
+export default service;

+ 41 - 0
src/utils/storage.js

@@ -0,0 +1,41 @@
+// LocalStorage
+
+/**
+ * 存储LocalStorage
+ * @param {string, Object} name 
+ * @param {*} value 
+ */
+export const setLocalStorage = (name, value) => {
+  if (!name) throw new Error('name must be specified');
+  if (typeof value !== 'string') {
+    value = JSON.stringify(value);
+  }
+  window.localStorage.setItem(name, value);
+}
+
+/**
+ * 获取LocalStorage
+ * @param {string, Object} name 
+ * @returns 
+ */
+export const getLocalStorage = (name) => {
+  if(!name) throw new Error('name must be specified');
+  const value = window.localStorage.getItem(name);
+  return JSON.parse(value);
+}
+
+/**
+ * 移除指定name
+ * @param {*} name 
+ */
+export const removeLocalStorage = (name) => {
+  if (!name) throw new Error('name must be specified');
+  window.localStorage.removeItem(name);
+}
+
+/**
+ * 清空所有LocalStorage
+ */
+export const clearLocalStorage = () => {
+  window.localStorage.clear();
+}

+ 248 - 0
src/views/admin/dict/data.vue

@@ -0,0 +1,248 @@
+<template>
+  <div class="app-container">
+    <a-form :model="queryForm" layout="inline">
+      <a-form-item field="dictType" label="字典名称">
+        <a-select
+          v-model="queryForm.dictType"
+          :options="dictTypeOptions"
+          :field-names="{ value: 'dictType', label: 'dictName' }"
+        ></a-select>
+      </a-form-item>
+      <a-form-item field="dictTag" label="字典标签">
+        <a-input
+          v-model="queryForm.dictTag"
+          placeholder="请输入字典标签"
+        ></a-input>
+      </a-form-item>
+      <a-form-item field="status" label="字典状态">
+        <a-select v-model="queryForm.status" placeholder="请选择字典状态">
+          <a-option :value="2">正常</a-option>
+          <a-option :value="1">关闭</a-option>
+        </a-select>
+      </a-form-item>
+      <a-form-item>
+        <a-space>
+          <a-button type="primary">搜索</a-button>
+          <a-button>重置</a-button>
+        </a-space>
+      </a-form-item>
+    </a-form>
+
+    <a-divider />
+
+    <div class="table-action">
+      <a-button type="primary" @click="handleAdd">新增</a-button>
+    </div>
+
+    <a-table
+      :data="tableData"
+      :columns="columns"
+      :pagination="{
+        'show-total': true,
+        'show-jumper': true,
+        'show-page-size': true,
+        current: currentPage,
+        total: pager.total,
+      }"
+      @page-change="handlePageChange"
+    >
+      <template #createdAt="{ record }">
+        {{ parseTime(record.createdAt) }}
+      </template>
+
+      <template #action="{ record }">
+        <a-button type="text" @click="handleEdit(record)">修改</a-button>
+        <a-popconfirm content="是否删除当前数据?" type="warning" @ok="handleDelete([record.dictCode])">
+          <a-button type="text" status="danger">删除</a-button>
+        </a-popconfirm>
+      </template>
+
+      <template #status="{ record }">
+        <a-tag v-if="record.status == 2" color="green">正常</a-tag>
+        <a-tag v-else-if="record.status == 1" color="red">停用</a-tag>
+      </template>
+    </a-table>
+
+    <a-modal
+      v-model:visible="modalVisible"
+      :title="modalTitle"
+      title-align="start"
+      @close="$refs.modalFormRef.resetFields()"
+      @before-ok="handleBeforeOk"
+      @ok="handleSubmit"
+    >
+      <a-form :model="modalForm" :rules="rules" ref="modalFormRef">
+        <a-form-item field="dictType" label="字典类型">
+          <a-input v-model="modalForm.dictType" disabled></a-input>
+        </a-form-item>
+        <a-form-item field="dictLabel" label="数据标签">
+          <a-input
+            v-model="modalForm.dictLabel"
+            placeholder="请输入数据标签"
+          ></a-input>
+        </a-form-item>
+        <a-form-item field="dictValue" label="数据键值">
+          <a-input
+            v-model="modalForm.dictValue"
+            placeholder="请输入数据键值"
+          ></a-input>
+        </a-form-item>
+        <a-form-item field="dictSort" label="显示排序">
+          <a-input-number
+            v-model="modalForm.dictSort"
+            :default-value="0"
+            mode="button"
+          ></a-input-number>
+        </a-form-item>
+        <a-form-item field="status" label="状态">
+          <a-radio-group v-model="modalForm.status">
+            <a-radio :value="2">正常</a-radio>
+            <a-radio :value="1">停用</a-radio>
+          </a-radio-group>
+        </a-form-item>
+        <a-form-item field="remark" label="备注">
+          <a-textarea v-model="modalForm.remark"></a-textarea>
+        </a-form-item>
+      </a-form>
+    </a-modal>
+  </div>
+</template>
+
+<script setup>
+import { reactive, ref, getCurrentInstance, onBeforeMount } from 'vue';
+import { getDictData, addDictData, updateDictData, deleteDictData } from '@/api/admin/sys-dict-data';
+import { getDictType } from '@/api/admin/sys-dict';
+
+const { proxy } = getCurrentInstance();
+
+// 当前页码
+const currentPage = ref(1);
+
+// 分页
+const pager = {
+  total: 0,
+  pageIndex: 1,
+  pageSize: 10,
+};
+
+// Rules
+const rules = {
+  dictLabel: [{ required: true, message: '请输入数据标签' }],
+  dictValue: [{ required: true, message: '请输入数据键值' }],
+  dictSort: [{ required: true, message: '请选择排序' }],
+};
+
+// Modal
+const modalVisible = ref(false);
+const modalTitle = ref('默认标题');
+
+// DictType Options
+const dictTypeOptions = ref([]);
+
+// 搜索表单
+const queryForm = reactive({});
+
+// Modal表单
+const modalForm = reactive({
+  status: 2
+});
+
+// 表格
+const tableData = ref([]);
+const columns = [
+  { title: '字典编码', dataIndex: 'dictCode' },
+  { title: '字典标签', dataIndex: 'dictLabel' },
+  { title: '字典键值', dataIndex: 'dictValue' },
+  { title: '字典排序', dataIndex: 'dictSort' },
+  { title: '状态', dataIndex: 'status', slotName: 'status' },
+  { title: '备注', dataIndex: 'remark' },
+  { title: '创建时间', dataIndex: 'createdAt', slotName: 'createdAt' },
+  { title: '操作', dataIndex: 'action', slotName: 'action' },
+];
+
+// 新增字典数据
+const handleAdd = () => {
+  modalVisible.value = true;
+  modalTitle.value = '新增字典数据';
+
+  Object.assign(modalForm, proxy.$route.params);
+};
+
+// 修改字典数据
+const handleEdit = (record) => {
+  modalVisible.value = true;
+  modalTitle.value = '修改字典数据';
+
+  Object.assign(modalForm, proxy.$route.params, record);
+  console.log(modalForm)
+};
+
+// 提交表单前检查
+const handleBeforeOk = (done) => {
+  proxy.$refs.modalFormRef.validate((err) => {
+    if (err) {
+      proxy.$message.error('表单校验失败');
+      done(false);
+    } else {
+      done();
+    }
+  })
+}
+
+// 提交表单
+const handleSubmit = async () => {
+  let res;
+
+  if (modalForm.dictCode) {
+    res = await updateDictData(modalForm, modalForm.dictCode);
+  } else {
+    res = await addDictData(modalForm);
+  }
+  
+  proxy.$message.success(res.msg);
+  getDictDataInfo({...proxy.$route.params, ...pager});
+}
+
+// 删除字典数据
+const handleDelete = async (ids) => {
+  const res = await deleteDictData({ids});
+
+  proxy.$message.success(res.msg);
+  getDictDataInfo({...proxy.$route.params, ...pager});
+}
+
+// 页码改变
+const handlePageChange = (pagerIndex) => {
+  currentPage.value = pagerIndex;
+  pager.pageIndex = pagerIndex;
+  getDictDataInfo({...proxy.$route.params, ...pager});
+}
+
+// 获取字典数据
+const getDictDataInfo = async (params = {}) => {
+  const res = await getDictData(params);
+  const { count, list, pageIndex, pageSize } = res.data;
+
+  tableData.value = list;
+  Object.assign(pager, { total: count, pageIndex, pageSize });
+};
+
+// 获取字典类型
+const getDictTypeInfo = async () => {
+  const res = await getDictType({ pageSize: 1000 });
+
+  dictTypeOptions.value = res.data.list;
+  Object.assign(queryForm, proxy.$route.params);
+};
+
+onBeforeMount(() => {
+  getDictDataInfo(proxy.$route.params);
+  getDictTypeInfo();
+});
+</script>
+
+<style lang="scss" scoped>
+.table-action {
+  margin-bottom: 12px;
+}
+</style>

+ 293 - 0
src/views/admin/dict/index.vue

@@ -0,0 +1,293 @@
+<template>
+  <div class="app-container">
+    <a-form :model="queryForm" ref="queryFormRef" layout="inline">
+      <a-form-item field="dictName" label="字典名称">
+        <a-input
+          v-model="queryForm.dictName"
+          placeholder="请输入字典名称"
+        ></a-input>
+      </a-form-item>
+      <a-form-item field="dictType" label="字典类型">
+        <a-input
+          v-model="queryForm.dictType"
+          placeholder="请输入字典类型"
+        ></a-input>
+      </a-form-item>
+      <a-form-item field="status" label="状态">
+        <a-select v-model="queryForm.status" placeholder="请选择字典状态">
+          <a-option :value="2">正常</a-option>
+          <a-option :value="1">停用</a-option>
+        </a-select>
+      </a-form-item>
+      <a-form-item>
+        <a-space>
+          <a-button type="primary" @click="handleQuery"><icon-search /> 搜索</a-button>
+          <a-button @click="handleResetQuery"><icon-loop /> 重置</a-button>
+        </a-space>
+      </a-form-item>
+    </a-form>
+
+    <a-divider />
+
+    <div class="action">
+      <a-space>
+        <a-button type="primary" @click="handleAdd"><icon-plus /> 新增</a-button>
+        <a-button type="primary" status="danger" @click="handleBatchDelete"><icon-delete /> 批量删除</a-button>
+        <a-button type="primary" status="warning" disabled><icon-download /> 导出</a-button>
+      </a-space>
+    </div>
+
+    <!-- table -->
+    <a-table
+      :columns="tableColumns"
+      :data="tableData"
+      :row-selection="{ type: 'checkbox', showCheckedAll: true }"
+      :pagination="{
+        'show-total': true,
+        'show-jumper': true,
+        'show-page-size': true,
+        current: currentPage,
+        total: pager.total,
+      }"
+      row-key="id"
+      @selection-change="
+        (rowKey) => {
+          batchDelList = rowKey;
+        }
+      "
+      @page-change="handlePageChange"
+      @page-size-change="handlePageSizeChange"
+    >
+      <template #dictType="{ record }">
+        <router-link :to="`/admin/dict/data/${record.dictType}`">{{ record.dictType }}</router-link>
+      </template>
+
+      <template #status="{ record }">
+        <a-tag v-if="record.status == 2" color="green">正常</a-tag>
+        <a-tag v-else color="red">停用</a-tag>
+      </template>
+
+      <template #createdAt="{ record }">
+        {{ parseTime(record.createdAt) }}
+      </template>
+
+      <template #action="{ record }">
+        <a-button type="text" @click="handleUpdate(record)"><icon-edit /> 修改</a-button>
+        <a-popconfirm content="是否删除该条数据?" type="warning" @ok="handleDelete([record.id])">
+          <a-button type="text"><icon-delete /> 删除</a-button>
+        </a-popconfirm>
+      </template>
+    </a-table>
+
+    <!-- Modal弹框 -->
+    <a-modal
+      v-model:visible="modalVisible"
+      title-align="start"
+      :title="modalTitle"
+      @before-ok="handleSubmit"
+      @cancel="$refs.modalFormRef.resetFields()"
+    >
+      <a-form :model="modalForm" ref="modalFormRef">
+        <a-form-item field="dictName" label="字典名称">
+          <a-input
+            v-model="modalForm.dictName"
+            placeholder="请输入字典名称"
+            :disabled="modalForm.id ? true : false"
+          ></a-input>
+        </a-form-item>
+        <a-form-item field="dictType" label="字典类型">
+          <a-input
+            v-model="modalForm.dictType"
+            placeholder="请输入字典类型"
+            :disabled="modalForm.id ? true : false"
+          ></a-input>
+        </a-form-item>
+        <a-form-item field="status" label="状态">
+          <a-radio-group v-model="modalForm.status">
+            <a-radio :value="2">正常</a-radio>
+            <a-radio :value="1">停用</a-radio>
+          </a-radio-group>
+        </a-form-item>
+        <a-form-item field="remark" label="备注">
+          <a-textarea
+            v-model="modalForm.remark"
+            placeholder="请输入内容备注"
+          ></a-textarea>
+        </a-form-item>
+      </a-form>
+    </a-modal>
+  </div>
+</template>
+
+<script setup>
+import { reactive, ref, getCurrentInstance, nextTick, onMounted } from 'vue';
+
+// api
+import {
+  getDictType,
+  addDictType,
+  removeDictType,
+  updateDictType,
+} from '@/api/admin/sys-dict';
+
+const { proxy } = getCurrentInstance();
+
+const currentPage = ref(1);
+
+// pager
+const pager = {
+  total: 0,
+  pageIndex: 1,
+  pageSize: 10,
+};
+
+// Delete List
+const batchDelList = ref([]);
+// form
+const modalForm = reactive({
+  status: 2,
+});
+
+// Table
+const tableData = ref([]);
+const tableColumns = [
+  { title: '编号', dataIndex: 'id' },
+  { title: '字典名称', dataIndex: 'dictName' },
+  { title: '字典类型', dataIndex: 'dictType', slotName: 'dictType' },
+  { title: '字典状态', dataIndex: 'status', slotName: 'status' },
+  { title: '备注', dataIndex: 'remark' },
+  { title: '创建时间', dataIndex: 'createdAt', slotName: 'createdAt' },
+  { title: '操作', slotName: 'action' },
+];
+
+// Modal
+const modalVisible = ref(false);
+const modalTitle = ref('默认标题');
+
+const { queryForm, handleQuery, handleResetQuery } = useQueryDict();
+// 搜索
+function useQueryDict() {
+  const queryForm = reactive({});
+
+  const handleQuery = () => {
+    getSysDictTypeInfo(queryForm);
+  };
+
+  const handleResetQuery = () => {
+    proxy.$refs.queryFormRef.resetFields();
+
+    currentPage.value = 1;
+    getSysDictTypeInfo(queryForm);
+  };
+
+  return {
+    queryForm,
+    handleQuery,
+    handleResetQuery,
+  };
+}
+
+// 页码改变
+const handlePageChange = (page) => {
+  pager.pageIndex = page;
+  currentPage.value = page;
+
+  getSysDictTypeInfo({ ...pager, ...queryForm });
+};
+
+// 每页数据量改变
+const handlePageSizeChange = (pageSize) => {
+  pager.pageSize = pageSize;
+
+  getSysDictTypeInfo({ ...pager, ...queryForm });
+};
+
+// 新增
+const handleAdd = () => {
+  modalVisible.value = true;
+  modalTitle.value = '新增字典';
+};
+
+// 批量删除
+const handleBatchDelete = () => {
+  if (batchDelList.value.length === 0) {
+    proxy.$message.info('请选择要删除的数据!');
+  } else {
+    proxy.$modal.warning({
+      title: '警告',
+      content: '是否删除当前选中的数据?',
+      hideCancel: false,
+      onOk: () => {
+        handleDelete(batchDelList.value);
+      },
+    });
+  }
+};
+
+// 修改
+const handleUpdate = async (record) => {
+  modalVisible.value = true;
+  modalTitle.value = '修改字典';
+
+  await nextTick();
+  Object.assign(modalForm, record);
+};
+
+// Submit
+const handleSubmit = (done) => {
+  proxy.$refs.modalFormRef.validate(async (err) => {
+    if (!err) {
+      try {
+        let res;
+        if (Reflect.has(modalForm, 'id')) {
+          res = await updateDictType(modalForm, modalForm.id);
+        } else {
+          res = await addDictType(modalForm);
+          currentPage.value = Math.ceil(++pager.total / pager.pageSize);
+          pager.pageIndex = currentPage.value;
+        }
+        proxy.$message.success(res.msg);
+        proxy.$refs.modalFormRef.resetFields();
+        done();
+        getSysDictTypeInfo(pager);
+      } catch (e) {
+        done(false);
+      }
+    } else {
+      proxy.$message.error('表单校验失败!');
+      done(false);
+    }
+  });
+};
+
+/**
+ * 删除请求
+ * @param {Array} idList
+ */
+const handleDelete = async (ids) => {
+  if (!Array.isArray(ids)) return false;
+  const res = await removeDictType({ ids });
+  proxy.$message.success(res.msg);
+  getSysDictTypeInfo(pager);
+};
+
+// 获取字典数据
+const getSysDictTypeInfo = async (params = {}) => {
+  const res = await getDictType(params);
+  tableData.value = res.data.list;
+
+  pager.total = res.data.count;
+  pager.pageIndex = res.data.pageIndex;
+  pager.pageSize = res.data.pageSize;
+};
+
+onMounted(() => {
+  getSysDictTypeInfo();
+});
+</script>
+
+<style lang="scss">
+.action {
+  margin-bottom: 12px;
+}
+</style>

+ 232 - 0
src/views/admin/sys-api/index.vue

@@ -0,0 +1,232 @@
+<template>
+  <div class="app-container">
+    <a-form :model="queryForm" ref="queryFormRef" layout="inline">
+      <a-form-item field="title" label="标题">
+        <a-input v-model="queryForm.title" placeholder="请输入标题" />
+      </a-form-item>
+      <a-form-item field="path" label="地址">
+        <a-input v-model="queryForm.path" placeholder="请输入地址" />
+      </a-form-item>
+      <a-form-item field="action" label="类型">
+        <a-select v-model="queryForm.action" placeholder="请选择类型">
+          <a-option>GET</a-option>
+          <a-option>POST</a-option>
+          <a-option>PUT</a-option>
+          <a-option>DELETE</a-option>
+        </a-select>
+      </a-form-item>
+      <a-form-item>
+        <a-space size="medium">
+          <a-button type="primary" @click="handleQuery">
+            <template #icon>
+              <icon-search />
+            </template>
+            搜索
+          </a-button>
+          <a-button @click="handlResetQuery">
+            <template #icon>
+              <icon-loop />
+            </template>
+            重置
+          </a-button>
+        </a-space>
+      </a-form-item>
+    </a-form>
+
+    <a-divider />
+
+    <!-- Table -->
+    <a-table
+      :columns="columns"
+      :data="tableData"
+      :pagination="{
+        'show-total': true,
+        'show-jumper': true,
+        'show-page-size': true,
+        total: pager.total,
+        current: currentPage,
+      }"
+      @page-change="handlePageChange"
+      @page-size-change="handlePageSizeChange"
+    > 
+      <template #reqType="{ record }">
+        <a-tag v-if="record.action.toLowerCase() === 'get'" color="cyan">{{record.action}}</a-tag>
+        <a-tag v-else-if="record.action.toLowerCase() === 'post'" color="gold" >{{ record.action }}</a-tag>
+        <a-tag v-else-if="record.action.toLowerCase() === 'put'" color="green"  >{{ record.action }}</a-tag>
+        <a-tag  v-else-if="record.action.toLowerCase() === 'delete'" color="pinkpurple" >{{ record.action }}</a-tag>
+      </template>
+      <template #createdAt="{ record }"> {{ parseTime(record.createdAt) }}</template>
+      <template #action="{ record }">
+        <a-button type="text" @click="handleUpdate(record)"><icon-edit /> 修改 </a-button>
+      </template>
+    </a-table>
+
+    <!-- Drawer -->
+    <a-drawer
+      :visible="drawerVisible"
+      :width="450"
+      @ok="handleDrawerSubmit"
+      @cancel="handleDrawerCancel"
+    >
+      <template #title> 修改接口管理 </template>
+      <a-form :model="drawerForm" ref="drawerFormRef" :rules="rules">
+        <a-form-item field="handle" label="Handle">
+          <a-input v-model="drawerForm.handle" placeholder="请输入Handle" />
+        </a-form-item>
+        <a-form-item field="title" label="标题">
+          <a-input v-model="drawerForm.title" placeholder="请输入标题" />
+        </a-form-item>
+        <a-form-item field="type" label="类型">
+          <a-select
+            v-model="drawerForm.type"
+            placeholder="请选择类型"
+            allow-clear
+          >
+            <a-option>SYS</a-option>
+            <a-option>BUS</a-option>
+          </a-select>
+        </a-form-item>
+        <a-form-item field="action" label="方式">
+          <a-select
+            v-model="drawerForm.action"
+            placeholder="请选择请求方式"
+            allow-clear
+          >
+            <a-option>GET</a-option>
+            <a-option>POST</a-option>
+            <a-option>PUT</a-option>
+            <a-option>DELETE</a-option>
+          </a-select>
+        </a-form-item>
+        <a-form-item field="path" label="路径">
+          <a-input v-model="drawerForm.path" disabled />
+        </a-form-item>
+      </a-form>
+    </a-drawer>
+  </div>
+</template>
+
+<script setup>
+import { reactive, ref, getCurrentInstance, onMounted, nextTick } from 'vue';
+import { IconSearch, IconLoop } from '@arco-design/web-vue/es/icon';
+import { getSysApi } from '@/api/admin/sys-api';
+
+const { proxy } = getCurrentInstance();
+
+// 分页当前页
+const currentPage = ref(1);
+
+// Pager
+const pager = {
+  total: 0,
+  pageIndex: 1,
+  pageSize: 10,
+};
+
+// From 定义
+const queryForm = reactive({});
+const drawerForm = reactive({});
+
+// From 校验规则
+const rules = {
+  title: [{ required: true, message: '请输入标题' }],
+  method: [{ required: true, message: '请选择方式' }],
+};
+
+// 抽屉显示
+const drawerVisible = ref(false);
+
+// Table Columns
+const columns = [
+  { title: '标题', dataIndex: 'title' },
+  { title: '请求类型', dataIndex: 'action', slotName: 'reqType' },
+  { title: '请求信息', dataIndex: 'path' },
+  { title: '创建时间', dataIndex: 'createdAt', slotName: 'createdAt' },
+  { title: '操作', slotName: 'action' },
+];
+
+// Table Data
+const tableData = ref([]);
+
+// Drawer 确定事件
+const handleDrawerSubmit = () => {
+  proxy.$refs.drawerFormRef.validate((valid) => {
+    if (!valid) {
+      proxy.$message.success('数据更新成功');
+      drawerVisible.value = false;
+    } else {
+      proxy.$message.error('数据校验失败');
+    }
+  });
+};
+
+// Drawer 关闭事件
+const handleDrawerCancel = () => {
+  drawerVisible.value = false;
+};
+
+// 查询
+const handleQuery = async () => {
+  getSysApiInfo(queryForm);
+  currentPage.value = 1;
+};
+
+// 重置查询
+const handlResetQuery = () => {
+  proxy.$refs.queryFormRef.resetFields();
+
+  handlePageChange(1);
+};
+
+// 修改
+const handleUpdate = async (record) => {
+  drawerVisible.value = true;
+
+  await nextTick();
+  Object.assign(drawerForm, record);
+};
+
+/**
+ * 分页改变
+ * @param {Number} [page]
+ */
+const handlePageChange = (page) => {
+  pager.pageIndex = page;
+
+  // 修改当前页码
+  currentPage.value = page;
+  getSysApiInfo({ ...pager, ...queryForm });
+};
+
+// 每页数据量
+const handlePageSizeChange = (pageSize) => {
+  pager.pageSize = pageSize;
+  getSysApiInfo({ ...pager, ...queryForm });
+};
+
+// 获取接口信息
+const getSysApiInfo = async (params = {}) => {
+  const res = await getSysApi(params);
+  const { list, count, pageIndex, pageSize } = res.data;
+
+  tableData.value = list;
+  Object.assign(pager, { total: count, pageIndex, pageSize });
+};
+
+onMounted(() => {
+  getSysApiInfo(pager);
+});
+</script>
+
+<style lang="scss">
+.pagination {
+  margin-top: 12px;
+  display: flex;
+  justify-content: flex-end;
+}
+
+// Table 操作列样式
+.arco-table-th:last-child > span {
+  margin-left: 15px;
+}
+</style>

+ 324 - 0
src/views/admin/sys-config/index.vue

@@ -0,0 +1,324 @@
+<template>
+  <div class="app-container">
+    <a-form :model="queryForm" ref="queryFormRef" layout="inline">
+      <a-form-item field="configName" label="参数名称">
+        <a-input
+          v-model="queryForm.configName"
+          placeholder="请输入参数名称"
+        ></a-input>
+      </a-form-item>
+      <a-form-item field="configKey" label="参数键名">
+        <a-input
+          v-model="queryForm.configKey"
+          placeholder="请输入参数键名"
+        ></a-input>
+      </a-form-item>
+      <a-form-item field="configType" label="系统内置">
+        <a-select v-model="queryForm.configType" placeholder="选择系统内置">
+          <a-option value="Y">是</a-option>
+          <a-option value="N">否</a-option>
+        </a-select>
+      </a-form-item>
+      <a-form-item>
+        <a-space>
+          <a-button type="primary" @click="handleQuery()"><icon-search /> 搜索</a-button>
+          <a-button @click="handleResetQuery()"><icon-loop /> 重置</a-button>
+        </a-space>
+      </a-form-item>
+    </a-form>
+
+    <!-- 分割线 -->
+    <a-divider />
+
+    <div class="action">
+      <a-space>
+        <a-button type="primary" @click="handleAdd"><icon-plus /> 新增</a-button>
+        <a-button type="primary" status="danger" disabled><icon-delete /> 删除</a-button>
+        <a-button type="primary" status="warning" disabled><icon-download /> 导出</a-button>
+      </a-space>
+    </div>
+
+    <a-table
+      :data="tableData"
+      :columns="columns"
+      :pagination="{
+        'show-total': true,
+        'show-jumper': true,
+        'show-page-size': true,
+        total: pager.total,
+        current: currentPage,
+      }"
+      @page-change="handlePageChange"
+      @page-size-change="handlePageSizeChange"
+    >
+      <template #configType="{ record }">
+        <a-tag v-if="record.configType === 'Y'" color="green">是</a-tag>
+        <a-tag v-else-if="record.configType === 'N'" color="red">否</a-tag>
+      </template>
+
+      <template #createdAt="{ record }">
+        {{ parseTime(record.createdAt) }}
+      </template>
+
+      <template #action="{ record }">
+        <a-button type="text" @click="handleUpdate(record)"><icon-edit /> 修改</a-button>
+        <a-popconfirm content="是否删除该条数据?"  @ok="handleDelete([record.id])" position="lt" type="warning">
+          <a-button type="text"><icon-delete /> 删除</a-button>
+        </a-popconfirm>
+      </template>
+    </a-table>
+
+    <a-modal
+      v-model:visible="visible"
+      :title="title"
+      title-align="start"
+      @before-ok="handleBeforeOk"
+      @close="handleResetModalForm"
+    >
+      <a-form :model="modalForm" :rules="rules" ref="modalFormRef">
+        <a-form-item field="configName" label="参数名称">
+          <a-input
+            v-model="modalForm.configName"
+            placeholder="请输入参数名称"
+          />
+        </a-form-item>
+        <a-form-item field="configKey" label="参数键名">
+          <a-input v-model="modalForm.configKey" placeholder="请输入参数键名" />
+        </a-form-item>
+        <a-form-item field="configValue" label="参数键值">
+          <a-input
+            v-model="modalForm.configValue"
+            placeholder="请输入参数键值"
+          />
+        </a-form-item>
+        <a-form-item field="configType" label="系统内置">
+          <a-radio-group v-model="modalForm.configType">
+            <a-radio value="Y">是</a-radio>
+            <a-radio value="N">否</a-radio>
+          </a-radio-group>
+        </a-form-item>
+        <a-form-item field="isFrontend" label="前台显示">
+          <a-radio-group v-model="modalForm.isFrontend">
+            <a-radio :value="1">是</a-radio>
+            <a-radio :value="0">否</a-radio>
+          </a-radio-group>
+        </a-form-item>
+        <a-form-item field="remark" label="备注">
+          <a-textarea
+            v-model="modalForm.remark"
+            placeholder="请输入备注内容"
+          ></a-textarea>
+        </a-form-item>
+      </a-form>
+    </a-modal>
+  </div>
+</template>
+
+<script setup>
+import { onMounted, reactive, ref, getCurrentInstance } from 'vue';
+
+// Api
+import {
+  getSysConfig,
+  addSysConfig,
+  removeSysConfig,
+  updateSysConfig,
+} from '@/api/admin/sys-config';
+
+const { proxy } = getCurrentInstance();
+
+const currentPage = ref(1);
+
+const pager = {
+  total: 0,
+  pageIndex: 1,
+  pageSize: 10,
+};
+
+const { queryForm, handleQuery, handleResetQuery } = useQueryForm();
+const { tableData, columns, handlePageChange, handlePageSizeChange } =
+  useTableData();
+const {
+  rules,
+  title,
+  visible,
+  modalForm,
+  handleAdd,
+  handleUpdate,
+  handleBeforeOk,
+} = useModal();
+
+function useQueryForm() {
+  const queryForm = reactive({});
+
+  const handleQuery = () => {
+    getSysConfigInfo(queryForm);
+  };
+
+  const handleResetQuery = () => {
+    proxy.$refs.queryFormRef.resetFields();
+
+    handlePageChange(1);
+  };
+
+  return {
+    queryForm,
+    handleQuery,
+    handleResetQuery,
+  };
+}
+
+function useTableData() {
+  const tableData = ref([]);
+  const columns = [
+    { title: '编码', dataIndex: 'id', width: 70 },
+    { title: '名称', dataIndex: 'configName' },
+    { title: '键名', dataIndex: 'configKey' },
+    {
+      title: '内置',
+      dataIndex: 'configType',
+      slotName: 'configType',
+      width: 100,
+    },
+    {
+      title: '备注',
+      dataIndex: 'remark',
+      width: 350,
+      ellipsis: true,
+      tooltip: true,
+    },
+    { title: '创建时间', dataIndex: 'createdAt', slotName: 'createdAt' },
+    { title: '操作', dataIndex: 'action', slotName: 'action' },
+  ];
+
+  // 分页改变
+  const handlePageChange = (page) => {
+    pager.pageIndex = page;
+    getSysConfigInfo(pager);
+  };
+
+  // 每页数据量
+  const handlePageSizeChange = (pageSize) => {
+    pager.pageSize = pageSize;
+    getSysConfigInfo(pager);
+  };
+
+  return {
+    columns,
+    tableData,
+    handlePageChange,
+    handlePageSizeChange,
+  };
+}
+
+function useModal() {
+  const visible = ref(false);
+  const title = ref('默认标题');
+  const modalForm = reactive({
+    configType: 'Y',
+    isFrontend: 1,
+  });
+
+  const rules = {
+    configName: [{ required: true, message: '请输入参数名称' }],
+    configKey: [{ required: true, message: '请输入参数键名' }],
+    configValue: [{ required: true, message: '请输入参数键值' }],
+  };
+
+  const handleAdd = () => {
+    visible.value = true;
+    title.value = '新增参数';
+  };
+
+  const handleUpdate = async (record) => {
+    visible.value = true;
+    title.value = '修改参数';
+
+    Object.assign(modalForm, record);
+  };
+
+  const handleBeforeOk = (done) => {
+    proxy.$refs.modalFormRef.validate(async (err) => {
+      if (!err) {
+        try {
+          const msg = await handleSubmit(modalForm);
+          proxy.$message.success(msg);
+          done();
+          getSysConfigInfo();
+        } catch (e) {
+          proxy.$message.error(e);
+          done(false);
+        }
+      } else {
+        proxy.message.error('表单校验失败');
+        done(false);
+      }
+    });
+  };
+
+  return {
+    rules,
+    title,
+    visible,
+    modalForm,
+    handleAdd,
+    handleUpdate,
+    handleBeforeOk,
+  };
+}
+
+// 获取系统配置
+const getSysConfigInfo = async (params = {}) => {
+  const res = await getSysConfig(params);
+  tableData.value = res.data.list;
+
+  pager.total = res.data.count;
+  pager.pageIndex = res.data.pageIndex;
+  pager.pageSize = res.data.pageSize;
+};
+
+// 提交
+const handleSubmit = (data) => {
+  return new Promise(async (resolve, reject) => {
+    try {
+      let res;
+      if (!data.id) {
+        res = await addSysConfig(data);
+        resolve('添加成功');
+      } else {
+        res = await updateSysConfig(data, data.id);
+        resolve('更新成功');
+      }
+    } catch (err) {
+      reject(err);
+    }
+  });
+};
+
+/**
+ * 删除
+ * @param {Array} ids
+ */
+const handleDelete = async (ids) => {
+  const res = await removeSysConfig({ ids });
+  proxy.$message.success(res.msg);
+  getSysConfigInfo();
+};
+
+// 重置表单
+const handleResetModalForm = () => {
+  proxy.$refs.modalFormRef.resetFields();
+
+  modalForm.id = null;
+}
+
+onMounted(() => {
+  getSysConfigInfo();
+});
+</script>
+
+<style lang="scss">
+.action {
+  margin-bottom: 8px;
+}
+</style>

+ 242 - 0
src/views/admin/sys-dept/index.vue

@@ -0,0 +1,242 @@
+<template>
+  <div class="app-container">
+    <a-form :model="queryForm" ref="queryFormRef" layout="inline">
+      <a-form-item field="deptName" label="部门名称">
+        <a-input v-model="queryForm.deptName" placeholder="请输入部门名称" />
+      </a-form-item>
+      <a-form-item field="status" label="部门状态">
+        <a-select v-model="queryForm.status" placeholder="请选择部门状态">
+          <a-option :value="2">正常</a-option>
+          <a-option :value="1">停用</a-option>
+        </a-select>
+      </a-form-item>
+      <a-form-item>
+        <a-space>
+          <a-button type="primary" @click="handleQuery"><icon-search /> 搜索</a-button>
+          <a-button @click="handleResetQuery"><icon-loop /> 重置</a-button>
+        </a-space>
+      </a-form-item>
+    </a-form>
+
+    <a-divider />
+
+    <div class="action">
+      <a-button type="primary" @click="handleAdd()"><icon-plus /> 新增</a-button>
+    </div>
+
+    <!-- 异步数据需要defualt-expanded-keys 传入所有行Key才能默认展开 -->
+    <a-table
+      :columns="columns"
+      :data="tableData"
+      :pagination="false"
+      :default-expanded-keys="[1]"
+      row-key="deptId"
+    >
+      <template #status="{ record }">
+        <a-tag color="green" v-if="record.status === 2">正常</a-tag>
+        <a-tag color="red" v-else> 停用 </a-tag>
+      </template>
+      <template #createdAt="{ record }">
+        {{ parseTime(record.createdAt) }}
+      </template>
+      <template #action="{ record }">
+        <a-button type="text" @click="handleUpdate(record)"><icon-edit /> 修改</a-button>
+        <a-button type="text" @click="handleAdd(record)"><icon-plus /> 新增</a-button>
+        <a-popconfirm content="是否删除该条数据?" type="warning" @ok="handleDelete(record)" >
+          <a-button type="text"><icon-delete /> 删除</a-button>
+        </a-popconfirm>
+      </template>
+    </a-table>
+
+    <!-- Modal -->
+    <a-modal
+      v-model:visible="modalVisible"
+      :title="modalTitle"
+      title-align="start"
+      @before-ok="handleBeforeOk"
+      @close="() => $refs.modalFormRef.resetFields()"
+    >
+      <a-form :model="modalForm" :rules="rules" ref="modalFormRef">
+        <a-form-item field="parentId" label="上级部门">
+          <a-tree-select
+            v-model="modalForm.parentId"
+            :data="tableData"
+            :field-names="{ key: 'deptId', title: 'deptName' }"
+            placeholder="请选择上级部门"
+            allow-clear
+          />
+        </a-form-item>
+        <a-form-item field="deptName" label="部门名称">
+          <a-input v-model="modalForm.deptName" placeholder="请输入部门名称" />
+        </a-form-item>
+        <a-form-item field="leader" label="负责人">
+          <a-input v-model="modalForm.leader" placeholder="请输入负责人" />
+        </a-form-item>
+        <a-form-item field="phone" label="联系电话">
+          <a-input v-model="modalForm.phone" placeholder="请输入联系电话" />
+        </a-form-item>
+        <a-form-item field="email" label="邮箱">
+          <a-input v-model="modalForm.email" placeholder="请输入邮箱" />
+        </a-form-item>
+        <a-form-item field="status" label="部门状态">
+          <a-radio-group v-model="modalForm.status">
+            <a-radio :value="2">正常</a-radio>
+            <a-radio :value="1">停用</a-radio>
+          </a-radio-group>
+        </a-form-item>
+      </a-form>
+    </a-modal>
+  </div>
+</template>
+
+<script setup>
+import { reactive, ref, onMounted, getCurrentInstance, nextTick } from 'vue';
+import { getDept, addDept, removeDept, updateDept } from '@/api/admin/sys-dept';
+
+const { proxy } = getCurrentInstance();
+
+// Modal
+const modalVisible = ref(false);
+const modalTitle = ref('默认标题');
+
+// Form
+const queryForm = reactive({});
+const modalForm = reactive({
+  status: 2,
+});
+
+// rules
+const rules = {
+  parentId: [{ required: true, message: '请选择上级部门' }],
+  deptName: [{ required: true, message: '请输入部门名称' }],
+  leader: [{ required: true, message: '请输入负责人' }],
+};
+
+// Columns
+const columns = [
+  {
+    title: '部门名称',
+    dataIndex: 'deptName',
+  },
+  {
+    title: '排序',
+    dataIndex: 'sort',
+  },
+  {
+    title: '状态',
+    dataIndex: 'status',
+    slotName: 'status',
+  },
+  {
+    title: '创建时间',
+    dataIndex: 'createdAt',
+    slotName: 'createdAt',
+  },
+  {
+    title: '操作',
+    slotName: 'action',
+  },
+];
+
+// Table Data
+const tableData = ref([]);
+
+// 查询
+const handleQuery = async () => {
+  const res = await getDept(queryForm);
+  tableData.value = deepDelChildren(res.data);
+};
+
+// 重置查询
+const handleResetQuery = () => {
+  proxy.$refs.queryFormRef.resetFields();
+
+  getDeptInfo(queryForm);
+}
+
+// 新增
+const handleAdd = ({ deptId, status = 2 } = {}) => {
+  modalVisible.value = true;
+  modalTitle.value = '新增部门';
+
+  if (deptId) Object.assign(modalForm, {parentId: deptId, status});
+};
+
+// 删除
+const handleDelete = async ({ deptId }) => {
+  const res = await removeDept({ ids: [deptId] });
+  proxy.$message.success(res.msg);
+  getDeptInfo();
+};
+
+// 修改
+const handleUpdate = async (record) => {
+  modalVisible.value = true;
+  modalTitle.value = '修改部门信息';
+
+  await nextTick();
+  const { parentId, deptName, leader, phone, email, status, deptId } = record;
+  Object.assign(modalForm, {
+    parentId,
+    deptName,
+    leader,
+    phone,
+    email,
+    status,
+    deptId,
+  });
+};
+
+// 递归删除空Children
+function deepDelChildren(data) {
+  const depts = data;
+  let len = depts?.length;
+  // let len = depts && depts.length;
+
+  for (let i = 0; i < len; i++) {
+    if (depts[i].children.length > 0) {
+      deepDelChildren(depts[i].children);
+    } else {
+      delete depts[i].children;
+    }
+  }
+
+  return depts;
+}
+
+// Modal 表单提交前检查
+const handleBeforeOk = (done) => {
+  proxy.$refs.modalFormRef.validate(async (err) => {
+    if (!err) {
+      let res;
+      if (Reflect.has(modalForm, 'deptId')) {
+        res = await updateDept(modalForm, modalForm.deptId);
+      } else {
+        res = await addDept(modalForm);
+      }
+      proxy.$message.success(res.msg);
+      done();
+      getDeptInfo();
+    } else {
+      proxy.$message.error('数据校验失败');
+      done(false);
+    }
+  });
+};
+
+// 获取部门信息
+const getDeptInfo = async (params = {}) => {
+  const res = await getDept(params);
+  tableData.value = deepDelChildren(res.data);
+};
+
+onMounted(() => {
+  getDeptInfo();
+});
+</script>
+
+<style lang="scss">
+.action {
+  margin-bottom: 8px;
+}
+</style>

+ 174 - 0
src/views/admin/sys-login-log/index.vue

@@ -0,0 +1,174 @@
+<template>
+  <div class="app-container">
+    <a-form :model="queryForm" ref="queryFormRef" layout="inline">
+      <a-form-item field="username" label="用户名">
+        <a-input v-model="queryForm.username" placeholder="请输入用户名" />
+      </a-form-item>
+      <a-form-item field="status" label="状态">
+        <a-select
+          v-model="queryForm.status"
+          placeholder="请选择系统登录日志状态"
+        >
+          <a-option :value="2">正常</a-option>
+          <a-option :value="1">关闭</a-option>
+        </a-select>
+      </a-form-item>
+      <a-form-item field="ipaddr" label="IP地址">
+        <a-input v-model="queryForm.ipaddr" placeholder="请输入IP地址" />
+      </a-form-item>
+      <a-form-item>
+        <a-space>
+          <a-button type="primary" @click="handleQuery"
+            ><icon-search /> 搜索</a-button
+          >
+          <a-button @click="handleResetQuery"><icon-loop /> 重置</a-button>
+        </a-space>
+      </a-form-item>
+    </a-form>
+
+    <a-divider />
+
+    <div class="action">
+      <a-space>
+        <a-button
+          type="primary"
+          status="danger"
+          :disabled="selectedKeys.length === 0"
+          @click="handleBatchDelete"
+          ><icon-delete /> 批量删除</a-button
+        >
+      </a-space>
+    </div>
+
+    <a-table
+      :data="tableData"
+      :columns="columns"
+      row-key="id"
+      :row-selection="{ type: 'checkbox', showCheckedAll: true }"
+      :pagination="{
+        'show-total': true,
+        'show-jumper': true,
+        'show-page-size': true,
+        current: currentPage,
+        total: pager.total,
+      }"
+      v-model:selectedKeys="selectedKeys"
+      @page-change="handlePageChange"
+      @page-size-change="handlePageSizeChange"
+    >
+      <template #status="{ record }">
+        <a-tag v-if="record.status == 2" color="green">正常</a-tag>
+        <a-tag v-if="record.status == 1" color="red">失败</a-tag>
+      </template>
+      <template #loginTime="{ record }">
+        {{ parseTime(record.loginTime) }}
+      </template>
+      <template #action="{ record }">
+        <a-popconfirm
+          content="是否删除当前数据?"
+          @ok="removeSysLoginLogInfo([record.id])"
+        >
+          <a-button type="text" status="danger">删除</a-button>
+        </a-popconfirm>
+      </template>
+    </a-table>
+  </div>
+</template>
+
+<script setup>
+import { ref, reactive, onMounted, getCurrentInstance } from 'vue';
+import { getSysLoginLog, removeSysLoginLog } from '@/api/admin/sys-login-log';
+
+const { proxy } = getCurrentInstance();
+
+// 当前页
+const currentPage = ref(1);
+
+// Pager
+const pager = {
+  total: 0,
+  pageIndex: 1,
+  pageSize: 10,
+};
+
+// 表格多选
+const selectedKeys = ref([]);
+
+const queryForm = reactive({});
+
+const tableData = ref([]);
+const columns = [
+  { title: '用户名', dataIndex: 'username' },
+  { title: '类型', dataIndex: 'msg' },
+  { title: '状态', dataIndex: 'status', slotName: 'status' },
+  { title: 'IP地址', dataIndex: 'ipaddr' },
+  { title: '登陆时间', dataIndex: 'loginTime', slotName: 'loginTime' },
+  { title: '操作', slotName: 'action', slotName: 'action' },
+];
+
+// 查询
+const handleQuery = () => {
+  getSysLoginLogInfo({ ...pager, ...queryForm });
+}
+
+// 重置查询
+const handleResetQuery = () => {
+  proxy.$refs.queryFormRef.resetFields();
+  handlePageChange(1);
+};
+
+// 页码改变
+const handlePageChange = (page) => {
+  currentPage.value = page;
+
+  pager.pageIndex = page;
+  getSysLoginLogInfo({ ...pager, ...queryForm });
+};
+
+// 每页数据量
+const handlePageSizeChange = (pageSize) => {
+  pager.pageSize = pageSize;
+  getSysLoginLogInfo({ ...pager, ...queryForm });
+};
+
+// 批量删除
+const handleBatchDelete = () => {
+  proxy.$modal.confirm({
+    title: '注意',
+    content: '是否删除当前所有勾选数据?',
+    onOk: () => {
+      removeSysLoginLogInfo(selectedKeys.value);
+    },
+    onCancel: () => {
+      proxy.$message.info('已取消操作');
+    }
+  })
+}
+
+// 获取登陆日志信息
+const getSysLoginLogInfo = async (params = {}) => {
+  const res = await getSysLoginLog(params);
+  const { count, list, pageIndex, pageSize } = res.data;
+
+  tableData.value = list;
+  Object.assign(pager, { total: count, pageIndex, pageSize });
+};
+
+// 删除登陆日志信息
+const removeSysLoginLogInfo = async (ids) => {
+  const res = await removeSysLoginLog({ ids });
+  proxy.$message.success(res.msg);
+
+  getSysLoginLogInfo();
+};
+
+onMounted(() => {
+  getSysLoginLogInfo();
+});
+</script>
+
+<style lang="scss">
+.action {
+  margin-bottom: 8px;
+}
+</style>

+ 369 - 0
src/views/admin/sys-menu/index.vue

@@ -0,0 +1,369 @@
+<template>
+  <div class="app-container">
+    <a-form :model="queryForm" ref="queryFormRef" layout="inline">
+      <a-form-item field="menuName" label="菜单名称">
+        <a-input v-model="queryForm.menuName" placeholder="请输入菜单名称" />
+      </a-form-item>
+      <a-form-item field="visible" label="状态">
+        <a-select v-model="queryForm.visible" placeholder="请选择菜单状态">
+          <a-option value="0">显示</a-option>
+          <a-option value="1">隐藏</a-option>
+        </a-select>
+      </a-form-item>
+      <a-form-item>
+        <a-space>
+          <a-button type="primary" @click="handleQuery">搜索</a-button>
+          <a-button @click="$refs.queryFormRef.resetFields()">重置</a-button>
+        </a-space>
+      </a-form-item>
+    </a-form>
+
+    <a-divider />
+
+    <!-- action -->
+    <div class="action">
+      <a-space>
+        <a-button type="primary" @click="handleAddMenu">新增菜单</a-button>
+      </a-space>
+    </div>
+
+    <!-- table -->
+    <a-table :columns="columns" :data="tableData" row-key="menuId">
+      <template #icon="{ record }">
+        <component :is="record.icon" :style="{ fontSize: '18px' }"></component>
+      </template>
+      <template #menutype="{ record }">
+        <span v-if="record.menuType === 'M'">目录</span>
+        <span v-else-if="record.menuType === 'C'">菜单</span>
+        <span v-else-if="record.menuType === 'F'">按钮</span>
+      </template>
+      <template #visible="{ record }">
+        <a-tag v-if="record.visible == 0" color="green">显示</a-tag>
+        <a-tag v-else color="red">隐藏</a-tag>
+      </template>
+      <template #action="{ record }">
+        <a-button type="text" @click="handleAddMenu(record.menuId)"
+          >新增</a-button
+        >
+        <a-button type="text" @click="handleUpdate(record)">修改</a-button>
+        <a-popconfirm
+          position="lt"
+          content="是否删除该条数据?"
+          type="warning"
+          @ok="handleDelete(record)"
+        >
+          <a-button type="text">删除</a-button>
+        </a-popconfirm>
+      </template>
+    </a-table>
+
+    <!-- modal -->
+    <a-modal
+      v-model:visible="modalVisible"
+      :title="modalTitle"
+      title-align="start"
+      :width="800"
+      modal-class="menu-modal"
+      @before-ok="handleSubmit"
+      @close="() => proxy.$refs.modalFormRef.resetFields()"
+    >
+      <a-form
+        :model="modalForm"
+        :rules="modalRules"
+        ref="modalFormRef"
+        auto-label-width
+      >
+        <a-form-item field="menuType" label="菜单类型">
+          <a-radio-group v-model="modalForm.menuType">
+            <a-radio value="M">目录</a-radio>
+            <a-radio value="C">菜单</a-radio>
+            <a-radio value="F">按钮</a-radio>
+          </a-radio-group>
+        </a-form-item>
+        <a-row :gutter="24">
+          <a-col :span="12">
+            <a-form-item field="parentId" label="上级菜单">
+              <a-tree-select
+                v-model="modalForm.parentId"
+                :data="tableData"
+                :field-names="{ key: 'menuId', icon: '_' }"
+                :allow-search="true"
+                :filter-tree-node="filterTreeNode"
+                placeholder="请选择上级菜单"
+              />
+            </a-form-item>
+          </a-col>
+          <a-col :span="12">
+            <a-form-item field="title" label="菜单标题">
+              <a-input v-model="modalForm.title" placeholder="请输入菜单标题" />
+            </a-form-item>
+          </a-col>
+        </a-row>
+        <a-row :gutter="24">
+          <a-col :span="12">
+            <a-form-item field="icon" label="菜单图标">
+              <a-select
+                v-model="modalForm.icon"
+                :options="parseIconName"
+                :field-names="{ name: 'value' }"
+                :trigger-props="{ contentClass: 'iconselect-trigger' }"
+                allow-search
+                placeholder="请选择菜单图标"
+              >
+                <template #option="{ data }">
+                  <component :is="data.value" />
+                </template>
+              </a-select>
+            </a-form-item>
+          </a-col>
+          <a-col :span="12">
+            <a-form-item field="sort" label="显示排序">
+              <a-input-number v-model="modalForm.sort" mode="button" />
+            </a-form-item>
+          </a-col>
+        </a-row>
+        <a-row :gutter="24">
+          <a-col :span="12" v-if="modalForm.menuType !== 'F'">
+            <a-form-item field="menuName" label="路由名称">
+              <a-input
+                v-model="modalForm.menuName"
+                placeholder="请输入路由名称"
+              />
+            </a-form-item>
+          </a-col>
+          <a-col :span="12" v-if="modalForm.menuType !== 'F'">
+            <a-form-item field="component" label="组件名称">
+              <a-input
+                v-model="modalForm.component"
+                placeholder="请输入组件名称"
+              />
+            </a-form-item>
+          </a-col>
+        </a-row>
+        <a-row :gutter="24">
+          <a-col :span="12" v-if="modalForm.menuType !== 'F'">
+            <a-form-item field="path" label="路由地址">
+              <a-input v-model="modalForm.path" placeholder="请输入路由地址" />
+            </a-form-item>
+          </a-col>
+          <a-col
+            :span="12"
+            v-if="modalForm.menuType === 'C' || modalForm.menuType === 'F'"
+          >
+            <a-form-item field="permission" label="权限标识">
+              <a-input
+                v-model="modalForm.permission"
+                placeholder="请输入权限标识"
+              />
+            </a-form-item>
+          </a-col>
+        </a-row>
+        <a-form-item
+          field="isFrame"
+          label="是否外链"
+          v-if="modalForm.menuType !== 'F'"
+        >
+          <a-radio-group v-model="modalForm.isFrame">
+            <a-radio value="0">是</a-radio>
+            <a-radio value="1">否</a-radio>
+          </a-radio-group>
+        </a-form-item>
+        <a-form-item
+          field="visible"
+          label="菜单状态"
+          v-if="modalForm.menuType !== 'F'"
+        >
+          <a-radio-group v-model="modalForm.visible">
+            <a-radio value="0">显示</a-radio>
+            <a-radio value="1">隐藏</a-radio>
+          </a-radio-group>
+        </a-form-item>
+        <a-form-item
+          field="apis"
+          label="API权限"
+          v-if="modalForm.menuType !== 'M'"
+        >
+          <a-transfer
+            v-model:model-value="modalForm.apis"
+            :data="transferData"
+            :title="['未授权', '已授权']"
+            show-search
+          ></a-transfer>
+        </a-form-item>
+      </a-form>
+    </a-modal>
+  </div>
+</template>
+
+<script setup>
+import {
+  onMounted,
+  reactive,
+  ref,
+  getCurrentInstance,
+  computed,
+} from 'vue';
+// 引入 Arco 图标库
+import * as ArcoIconModules from '@arco-design/web-vue/es/icon';
+
+// Api
+import { getMenu } from '@/api/admin/menu';
+import { getSysApi } from '@/api/admin/sys-api';
+import { addMenu, removeMenu, updateMenu, getMenuDetails } from '@/api/admin/menu';
+
+const { proxy } = getCurrentInstance();
+
+const { queryForm, handleQuery } = useQueryData();
+
+// QueryModel
+function useQueryData() {
+  const queryForm = reactive({});
+
+  const handleQuery = () => {
+    getSysMenuInfo(queryForm);
+  };
+
+  return {
+    queryForm,
+    handleQuery,
+  };
+}
+
+// from
+const modalForm = reactive({
+  menuType: 'M',
+  sort: 0,
+  isFrame: '1',
+  visible: '0',
+});
+
+// rules
+const modalRules = {
+  menuName: [{ required: true, message: '请输入菜单标题' }],
+};
+
+// Table
+const columns = [
+  { title: '菜单名称', dataIndex: 'title' },
+  { title: '图标', dataIndex: 'icon', slotName: 'icon' },
+  { title: '组件路径', dataIndex: 'component' },
+  { title: '路由路径', dataIndex: 'path' },
+  { title: '排序', dataIndex: 'sort' },
+  { title: '类型', dataIndex: 'menuType', slotName: 'menutype' },
+  { title: '显示状态', dataIndex: 'visible', slotName: 'visible' },
+  { title: '操作', slotName: 'action' },
+];
+const tableData = ref([]);
+
+// Transfer Data
+const transferData = ref([]);
+
+// Modal
+const modalVisible = ref(false);
+const modalTitle = ref('默认标题');
+
+// 创建新菜单
+const handleAddMenu = (parentId = null) => {
+  modalVisible.value = true;
+  modalTitle.value = '新增菜单';
+
+  if (parentId) modalForm.parentId = parentId;
+};
+
+// TreeSearchFilter
+const filterTreeNode = (searchVal, nodeData) => {
+  return nodeData.title.indexOf(searchVal) > -1;
+};
+
+// 删除菜单
+const handleDelete = async ({ menuId }) => {
+  const res = await removeMenu({ ids: [menuId] });
+  proxy.$message.success(res.msg);
+  getSysMenuInfo();
+};
+
+// 修改菜单
+const handleUpdate = async (record) => {
+  const res = await getMenuDetails(record.menuId);
+  Object.assign(modalForm, res.data);
+  
+  modalTitle.value = '修改菜单';
+  modalVisible.value = true;
+};
+
+// Submit
+const handleSubmit = (done) => {
+  proxy.$refs.modalFormRef.validate(async (err) => {
+    if (!err) {
+      let res;
+      if (Reflect.has(modalForm, 'menuId')) {
+        res = await updateMenu(modalForm, modalForm.menuId);
+      } else {
+        res = await addMenu(modalForm);
+      }
+      proxy.$message.success(res.msg);
+      done();
+      getSysMenuInfo();
+    } else {
+      proxy.$message.error('表单校验失败请检查');
+      done(false);
+    }
+  });
+};
+
+// 获取菜单信息
+const getSysMenuInfo = async (params = {}) => {
+  const res = await getMenu(params);
+  tableData.value = res.data;
+};
+
+// 获取API接口信息
+const getSysApiInfo = async () => {
+  const res = await getSysApi({ pageSize: 10000, type: 'BUS' });
+  transferData.value = res.data.list.map((item) => {
+    return { value: item.id, label: item.title };
+  });
+};
+
+// 转换Icon Object 为 list
+const parseIconName = computed(() => {
+  const iconNameList = [];
+
+  for (let key in ArcoIconModules) {
+    if (ArcoIconModules[key].name) {
+      iconNameList.push({ value: ArcoIconModules[key].name });
+    }
+  }
+
+  return iconNameList;
+});
+
+onMounted(() => {
+  getSysMenuInfo();
+  getSysApiInfo();
+});
+</script>
+
+<style lang="scss">
+.action {
+  margin-bottom: 12px;
+}
+
+// 覆盖默认 select trigger 样式
+.iconselect-trigger .arco-select-dropdown-list {
+  display: flex;
+  flex-wrap: wrap;
+}
+
+.iconselect-trigger .arco-select-option {
+  width: auto;
+}
+
+// 覆盖默认穿梭框样式
+.menu-modal {
+  .arco-transfer-view {
+    height: 350px;
+    width: 250px;
+  }
+}
+</style>

+ 185 - 0
src/views/admin/sys-oper-log/index.vue

@@ -0,0 +1,185 @@
+<template>
+  <div class="app-container">
+    <a-form :model="queryForm" ref="queryFormRef" layout="inline">
+      <a-form-item field="status" label="状态">
+        <a-select
+          v-model="queryForm.status"
+          placeholder="请选择系统操作日志状态"
+        >
+          <a-option :value="2">正常</a-option>
+          <a-option :value="1">关闭</a-option>
+        </a-select>
+      </a-form-item>
+      <a-form-item field="createdAt" label="创建时间">
+        <a-range-picker
+          disabled
+          v-model="queryForm.createdAt"
+          style="width: 254px"
+        />
+      </a-form-item>
+      <a-form-item>
+        <a-space>
+          <a-button type="primary" @click="handleQuery"
+            ><icon-search /> 搜索</a-button
+          >
+          <a-button @click="handleResetQuery"><icon-loop /> 重置</a-button>
+        </a-space>
+      </a-form-item>
+    </a-form>
+
+    <a-divider />
+
+    <div class="action">
+      <a-space>
+        <a-button
+          type="primary"
+          status="danger"
+          @click="handleBatchDelete"
+          :disabled="selectedKeys.length === 0"
+          ><icon-delete /> 批量删除</a-button
+        >
+      </a-space>
+    </div>
+
+    <a-table
+      :data="tableData"
+      :columns="columns"
+      :row-selection="{ type: 'checkbox', showCheckedAll: true }"
+      row-key="id"
+      :pagination="{
+        'show-total': true,
+        'show-jumper': true,
+        'show-page-size': true,
+        current: currentPage,
+        total: pager.total,
+      }"
+      v-model:selectedKeys="selectedKeys"
+      @page-change="handlePageChange"
+      @page-size-change="handlePageSizeChange"
+    >
+      <template #status="{ record }">
+        <a-tag v-if="record.status == 2" color="green">正常</a-tag>
+        <a-tag v-if="record.status == 1" color="red">关闭</a-tag>
+      </template>
+      <template #operTime="{ record }">
+        {{ parseTime(record.operTime) }}
+      </template>
+      <template #action="{ record }">
+        <a-popconfirm
+          content="是否删除当前数据?"
+          type="warning"
+          @ok="removeSysOperaLogInfo([record.id])"
+        >
+          <a-button type="text" status="danger">删除</a-button>
+        </a-popconfirm>
+      </template>
+    </a-table>
+  </div>
+</template>
+
+<script setup>
+import { ref, reactive, onMounted, getCurrentInstance } from 'vue';
+import { getSysOperaLog, removeSysOperaLog } from '@/api/admin/sys-opera-log';
+
+const { proxy } = getCurrentInstance();
+
+// 当前页
+const currentPage = ref(1);
+
+// 分页
+const pager = {
+  total: 0,
+  pageIndex: 1,
+  pageSize: 10,
+};
+
+//
+const selectedKeys = ref([]);
+
+const queryForm = reactive({});
+
+const tableData = ref([]);
+const columns = [
+  { title: '编号', dataIndex: 'id' },
+  { title: '请求信息', dataIndex: 'operUrl', width: 500 },
+  { title: '操作人员', dataIndex: 'operName' },
+  { title: '状态', dataIndex: 'status', slotName: 'status' },
+  { title: '操作日期', dataIndex: 'operTime', slotName: 'operTime' },
+  { title: '操作', slotName: 'action', slotName: 'action' },
+];
+
+// 查询
+const handleQuery = () => {
+  handlePageChange(1);
+};
+
+// 重置查询
+const handleResetQuery = () => {
+  proxy.$refs.queryFormRef.resetFields();
+  handlePageChange(1);
+};
+
+// 批量删除
+const handleBatchDelete = () => {
+  console.log(selectedKeys.value);
+  if (selectedKeys.value.length === 0) {
+    proxy.$message.error('请勾选需要删除的数据!');
+    return;
+  }
+
+  proxy.$modal.confirm({
+    title: '注意',
+    content: '是否批量删除选中?',
+    onOk: () => {
+      removeSysOperaLogInfo(selectedKeys.value);
+    },
+    onCancel: () => {
+      proxy.$message.info('已取消操作');
+    },
+  });
+};
+
+// 页码改变
+const handlePageChange = (page) => {
+  currentPage.value = page;
+
+  pager.pageIndex = page;
+  getSysOperaLogInfo({ ...pager, ...queryForm });
+};
+
+//
+const handlePageSizeChange = (pageSize) => {
+  pager.pageSize = pageSize;
+  getSysOperaLogInfo({ ...pager, ...queryForm });
+};
+
+// 获取操作日志
+const getSysOperaLogInfo = async (params = {}) => {
+  const res = await getSysOperaLog(params);
+  const { count, list, pageIndex, pageSize } = res.data;
+  tableData.value = list;
+
+  Object.assign(pager, { total: count, pageIndex, pageSize });
+};
+
+/**
+ * 删除操作日志
+ * @params {array} ids
+ */
+const removeSysOperaLogInfo = async (ids) => {
+  const res = await removeSysOperaLog({ ids });
+  proxy.$message.success(res.msg);
+
+  getSysOperaLogInfo();
+};
+
+onMounted(() => {
+  getSysOperaLogInfo();
+});
+</script>
+
+<style lang="scss">
+.action {
+  margin-bottom: 8px;
+}
+</style>

+ 272 - 0
src/views/admin/sys-post/index.vue

@@ -0,0 +1,272 @@
+<template>
+  <div class="app-container">
+    <a-form :model="queryForm" ref="queryFormRef" layout="inline">
+      <a-form-item field="postCode" label="岗位编码">
+        <a-input v-model="queryForm.postCode" placeholder="请输入岗位编码" />
+      </a-form-item>
+      <a-form-item field="postName" label="岗位名称">
+        <a-input v-model="queryForm.postName" placeholder="请输入岗位名称" />
+      </a-form-item>
+      <a-form-item field="status" label="岗位状态">
+        <a-select
+          v-model="queryForm.status"
+          placeholder="请选择岗位状态"
+          :style="{ width: '181px' }"
+        >
+          <a-option :value="2">正常</a-option>
+          <a-option :value="1">停用</a-option>
+        </a-select>
+      </a-form-item>
+      <a-form-item>
+        <a-space>
+          <a-button type="primary" @click="handleQuery"><icon-search /> 搜索</a-button>
+          <a-button @click="handleResetQuery"><icon-loop /> 重置</a-button>
+        </a-space>
+      </a-form-item>
+    </a-form>
+
+    <a-divider />
+
+    <!-- action -->
+    <div class="action">
+      <a-space>
+        <a-button type="primary" @click="handleAdd"><icon-plus /> 新增 </a-button>
+        <a-button type="primary" status="danger" @click="handleBatchDelete"><icon-delete /> 批量删除 </a-button>
+        <a-button type="primary" status="warning" disabled><icon-download /> 导出 </a-button>
+      </a-space>
+    </div>
+
+    <!-- table -->
+    <a-table
+      :columns="columns"
+      :data="tableData"
+      :row-selection="{ type: 'checkbox', showCheckedAll: true }"
+      row-key="postId"
+      :pagination="{
+        'show-total': true,
+        'show-jumper': true,
+        'show-page-size': true,
+        total: pager.total,
+      }"
+      @selection-change="selectionChange"
+    >
+      <template #createdAt="{ record }">
+        {{ parseTime(record.createdAt) }}
+      </template>
+      <template #status="{ record }">
+        <a-tag v-if="record.status == 2" color="green">正常</a-tag>
+        <a-tag v-else color="red">停用</a-tag>
+      </template>
+      <template #action="{ record }">
+        <a-space>
+          <a-button type="text" @click="handleUpdate(record)"><icon-edit /> 修改</a-button>
+          <a-popconfirm content="是否删除该岗位?" type="warning"  @ok="handleDelete(record)">
+            <a-button type="text"><icon-delete /> 删除</a-button>
+          </a-popconfirm>
+        </a-space>
+      </template>
+    </a-table>
+
+    <!-- Modal -->
+    <a-modal
+      v-model:visible="modalVisible"
+      :title="modalTitle"
+      title-align="start"
+      :on-before-ok="handleBeforeOk"
+      @close="handleResetModalForm"
+    >
+      <a-form :model="modalForm" :rules="rules" ref="modalFormRef">
+        <a-form-item field="postName" label="岗位名称">
+          <a-input v-model="modalForm.postName" placeholder="请输入岗位名称" />
+        </a-form-item>
+        <a-form-item field="postCode" label="岗位编码">
+          <a-input v-model="modalForm.postCode" placeholder="请输入岗位编码" />
+        </a-form-item>
+        <a-form-item field="sort" label="岗位排序">
+          <a-input-number
+            v-model="modalForm.sort"
+            mode="button"
+            :default-value="0"
+            :style="{ width: '150px' }"
+          />
+        </a-form-item>
+        <a-form-item field="status" label="岗位状态">
+          <a-radio-group v-model="modalForm.status">
+            <a-radio :value="2"> 正常 </a-radio>
+            <a-radio :value="1"> 停用 </a-radio>
+          </a-radio-group>
+        </a-form-item>
+        <a-form-item field="remark" label="备注">
+          <a-textarea v-model="modalForm.remark" placeholder="请输入备注内容" />
+        </a-form-item>
+      </a-form>
+    </a-modal>
+  </div>
+</template>
+
+<script setup>
+import { reactive, ref, getCurrentInstance, onMounted, nextTick } from 'vue';
+import { getPost, addPost, removePost, updatePost } from '@/api/admin/post';
+import { parseTime } from '@/utils/parseTime';
+
+const { proxy } = getCurrentInstance();
+
+// Pager
+const pager = {
+  total: 0,
+  pageIndex: 1,
+  pageSize: 10,
+};
+
+// form
+const queryForm = reactive({});
+const modalForm = reactive({
+  sort: 0,
+  status: 2,
+});
+
+// Rules
+const rules = {
+  postName: [{ required: true, message: '请输入岗位名称' }],
+  postCode: [{ required: true, message: '请输入岗位编码' }],
+  sort: [{ required: true, message: '请选择岗位排序' }],
+};
+
+// Modal
+const modalVisible = ref(false);
+const modalTitle = ref('默认标题');
+
+// Batch Del List
+let batchList = [];
+
+// Table Columns
+const columns = [
+  { title: '岗位编号', dataIndex: 'postId' },
+  { title: '岗位编码', dataIndex: 'postCode' },
+  { title: '岗位名称', dataIndex: 'postName' },
+  { title: '岗位排序', dataIndex: 'sort' },
+  { title: '状态', dataIndex: 'status', slotName: 'status' },
+  { title: '创建时间', dataIndex: 'createdAt', slotName: 'createdAt' },
+  { title: '操作', slotName: 'action' },
+];
+
+// Table Data
+const tableData = ref([]);
+
+// 新增
+const handleAdd = () => {
+  modalVisible.value = true;
+  modalTitle.value = '新增岗位';
+};
+
+// 修改
+const handleUpdate = async (record) => {
+  modalVisible.value = true;
+  modalTitle.value = '修改岗位';
+
+  await nextTick();
+  Object.assign(modalForm, record);
+};
+
+// 批量删除
+const handleBatchDelete = () => {
+  if (batchList.length !== 0) {
+    proxy.$modal.warning({
+      title: '提示',
+      content: '是否批量删除以下选中的数据?',
+      hideCancel: false,
+      onOk: async () => {
+        const res = await removePost({ ids: batchList });
+        proxy.$message.success(res.msg);
+        getPostInfo();
+      },
+      onCancel: () => {
+        proxy.$message.info('已取消批量删除数据');
+      },
+    });
+  } else {
+    proxy.$message.error('请勾选需要删除的数据!');
+  }
+};
+
+// 删除
+const handleDelete = async ({ postId }) => {
+  const res = await removePost({ ids: [postId] });
+  proxy.$message.success(res.msg);
+  getPostInfo();
+};
+
+// Modal ok
+// 异步关闭Modal需要调用 done()
+const handleBeforeOk = (done) => {
+  proxy.$refs.modalFormRef.validate(async (valid) => {
+    if (!valid) {
+      let res;
+      if (!modalForm.postId) {
+        res = await addPost(modalForm);
+        proxy.$message.success(res.msg);
+      } else {
+        res = await updatePost(modalForm, modalForm.postId);
+        proxy.$message.success(res.msg);
+      }
+      done();
+      getPostInfo();
+    } else {
+      proxy.$message.error('表单校验失败');
+      done(false);
+    }
+  });
+};
+
+// Table 勾选数据
+const selectionChange = (rowKeys) => {
+  batchList = rowKeys;
+};
+
+// 获取岗位信息
+const getPostInfo = async (params = {}) => {
+  const res = await getPost(params);
+  tableData.value = res.data.list;
+
+  // Pager
+  const { count, pageIndex, pageSize } = res.data;
+  pager.total = count;
+  pager.pageIndex = pageIndex;
+  pager.pageSize = pageSize;
+};
+
+// 查询岗位信息
+const handleQuery = async () => {
+  const params = {
+    pageIndex: pager.pageIndex,
+    pageSize: pager.pageSize,
+    ...queryForm,
+  };
+
+  getPostInfo(params);
+};
+
+// 重置搜索
+const handleResetQuery = () => {
+  proxy.$refs.queryFormRef.resetFields();
+
+  getPostInfo(queryForm);
+}
+
+// 重置ModalForm
+const handleResetModalForm = () => {
+  proxy.$refs.modalFormRef.resetFields();
+
+  modalForm.postId = null;
+}
+
+onMounted(() => {
+  getPostInfo(pager);
+});
+</script>
+
+<style lang="scss">
+.action {
+  margin-bottom: 12px;
+}
+</style>

+ 386 - 0
src/views/admin/sys-role/index.vue

@@ -0,0 +1,386 @@
+<template>
+  <div class="app-container">
+    <a-form :model="queryForm" ref="queryFormRef" layout="inline">
+      <a-form-item field="roleName" label="角色名称">
+        <a-input v-model="queryForm.roleName" placeholder="请输入角色名称" />
+      </a-form-item>
+      <a-form-item field="roleKey" label="权限字符">
+        <a-input v-model="queryForm.roleKey" placeholder="请输入权限字符" />
+      </a-form-item>
+      <a-form-item field="status" label="状态">
+        <a-select v-model="queryForm.status" placeholder="请选择角色状态">
+          <a-option :value="2">正常</a-option>
+          <a-option :value="1">停用</a-option>
+        </a-select>
+      </a-form-item>
+      <a-form-item>
+        <a-space>
+          <a-button type="primary" @click="handleQuery"><icon-search /> 搜索</a-button>
+          <a-button @click="handleResetQuery"><icon-loop /> 重置</a-button>
+        </a-space>
+      </a-form-item>
+    </a-form>
+
+    <a-divider />
+
+    <div class="action">
+      <a-space>
+        <a-button type="primary" @click="handleAdd"><icon-plus /> 新增</a-button>
+        <a-button type="primary" status="danger" @click="handleBatchDelete"><icon-delete /> 批量删除</a-button>
+        <a-button type="primary" status="warning" disabled><icon-download /> 导出</a-button>
+      </a-space>
+    </div>
+
+    <a-table
+      :columns="columns"
+      :data="tableData"
+      :pagination="{
+        'show-total': true,
+        'show-jumper': true,
+        'show-page-size': true,
+        total: pager.total,
+        current: currentPage,
+      }"
+      row-key="roleId"
+      :row-selection="{ type: 'checkbox', showCheckedAll: true }"
+      @select="handleSelect"
+      @page-change="handlePageChange"
+      @page-size-change="handlePageSizeChange"
+    >
+      <template #status="{ record }">
+        <a-tag v-if="record.status == 2" color="green">正常</a-tag>
+        <a-tag v-else color="red">停用</a-tag>
+      </template>
+      <template #createdAt="{ record }">
+        {{ parseTime(record.createdAt) }}
+      </template>
+      <template #action="{ record }">
+        <a-space>
+          <a-button type="text" @click="handleUpdate(record)"><icon-edit /> 修改</a-button>
+          <a-button type="text" @click="handleDataScope(record)"><icon-check-circle />  数据权限 </a-button>
+          <a-popconfirm content="是否确定删除该角色?" type="warning" position="lt" @ok="handleDelete(record)">
+            <a-button v-if="!record.admin" type="text"><icon-delete /> 删除</a-button>
+          </a-popconfirm>
+        </a-space>
+      </template>
+    </a-table>
+
+    <!-- Role Modal -->
+    <a-modal
+      v-model:visible="modalVisible"
+      :title="title"
+      title-align="start"
+      @before-ok="handleBeforeOk"
+      @close="handleResetModalForm"
+    >
+      <a-form :model="modalForm" :rules="rules" ref="modalFormRef">
+        <a-form-item field="roleName" label="角色名称">
+          <a-input v-model="modalForm.roleName" placeholder="请输入角色名称" />
+        </a-form-item>
+        <a-form-item field="roleKey" label="权限字符">
+          <a-input v-model="modalForm.roleKey" placeholder="请输入权限字符" />
+        </a-form-item>
+        <a-form-item field="roleSort" label="角色排序">
+          <a-input-number
+            v-model="modalForm.roleSort"
+            mode="button"
+            :default-value="0"
+            :style="{ width: '150px' }"
+          />
+        </a-form-item>
+        <a-form-item field="status" label="状态">
+          <a-radio-group v-model="modalForm.status">
+            <a-radio value="2">正常</a-radio>
+            <a-radio value="1">停用</a-radio>
+          </a-radio-group>
+        </a-form-item>
+        <a-form-item label="权限设置">
+          <a-tree
+            v-model:checked-keys="checkedKeys"
+            :checkable="true"
+            :check-strictly="false"
+            :data="treeData"
+            :default-expand-all="false"
+            :field-names="{ key: 'id', title: 'label' }"
+          />
+        </a-form-item>
+        <a-form-item field="remark" label="备注">
+          <a-textarea v-model="modalForm.remark" placeholder="请输入备注内容" />
+        </a-form-item>
+      </a-form>
+    </a-modal>
+
+    <!-- DataScope Modal -->
+    <a-modal
+      v-model:visible="scopedModalVisible"
+      :title="title"
+      title-align="start"
+      @before-ok="handleScopeBeforeOk"
+    >
+      <a-form :model="scopeForm">
+        <a-form-item field="roleName" label="角色名称">
+          <a-input v-model="scopeForm.roleName" disabled />
+        </a-form-item>
+        <a-form-item field="roleKey" label="权限字符">
+          <a-input v-model="scopeForm.roleKey" disabled />
+        </a-form-item>
+        <a-form-item field="dataScope" label="权限范围">
+          <a-select v-model="scopeForm.dataScope" placeholder="请选择权限范围">
+            <a-option
+              v-for="item in dataScopeOptions"
+              :key="item.value"
+              :value="item.value"
+              :label="item.label"
+            />
+          </a-select>
+        </a-form-item>
+      </a-form>
+    </a-modal>
+  </div>
+</template>
+
+<script setup>
+import { onMounted, reactive, ref, getCurrentInstance, nextTick } from 'vue';
+import {
+  getRole,
+  addRole,
+  updateRole,
+  removeRole,
+  updateRoleScoped,
+  getRoleMenuTree,
+} from '@/api/admin/role';
+
+const { proxy } = getCurrentInstance();
+
+const currentPage = ref(1);
+// Pager
+const pager = {
+  total: 0,
+  pageIndex: 1,
+  pageSize: 10,
+};
+
+// Batch Delete List
+const batchDeleteList = ref([]);
+
+// Form
+const queryForm = reactive({});
+const modalForm = reactive({
+  sort: 0,
+  status: '2',
+});
+const scopeForm = reactive({});
+
+// rules
+const rules = {
+  roleName: [{ required: true, message: '请输入角色名称' }],
+  roleKey: [{ required: true, message: '请输入权限字符' }],
+};
+
+// ScopeOption
+const dataScopeOptions = [
+  {
+    value: '1',
+    label: '全部数据权限',
+  },
+  {
+    value: '2',
+    label: '自定数据权限',
+  },
+  {
+    value: '3',
+    label: '本部门数据权限',
+  },
+  {
+    value: '4',
+    label: '本部门及以下数据权限',
+  },
+  {
+    value: '5',
+    label: '仅本人数据权限',
+  },
+];
+
+// Table Columns
+const columns = [
+  { title: '编号', dataIndex: 'roleId' },
+  { title: '角色名称', dataIndex: 'roleName' },
+  { title: '权限字符', dataIndex: 'roleKey' },
+  { title: '排序', dataIndex: 'roleSort' },
+  { title: '状态', dataIndex: 'status', slotName: 'status' },
+  { title: '创建时间', dataIndex: 'createdAt', slotName: 'createdAt' },
+  { title: '操作', slotName: 'action', width: 250 },
+];
+
+// Table Data;
+const tableData = ref([]);
+
+// Tree Data;
+const checkedKeys = ref([]);
+const treeData = ref([]);
+
+// Modal
+const modalVisible = ref(false);
+const scopedModalVisible = ref(false);
+const title = ref('默认标题');
+
+// Table Select
+const handleSelect = (rowKey) => {
+  batchDeleteList.value = rowKey;
+};
+
+// 查询
+const handleQuery = async () => {
+  const res = await getRole({ ...pager, ...queryForm });
+  const { count, list, pageIndex, pageSize } = res.data;
+
+  tableData.value = list;
+  pager.total = count;
+  pager.pageIndex = pageIndex;
+  pager.pageSize = pageSize;
+};
+
+// 重置查询
+const handleResetQuery = () => {
+  proxy.$refs.queryFormRef.resetFields();
+  getRoleInfo(queryForm);
+};
+
+// 创建
+const handleAdd = () => {
+  modalVisible.value = true;
+  title.value = '创建角色';
+};
+
+// 修改角色
+const handleUpdate = async (record) => {
+  modalVisible.value = true;
+  title.value = '修改角色';
+
+  await nextTick();
+  Object.assign(modalForm, record);
+
+  // 显示勾选的菜单,checkedKeys 传入id数组即可
+  const menuIdsChecked = [];
+  record.sysMenu.forEach((item) => {
+    menuIdsChecked.push(item.menuId);
+  });
+  checkedKeys.value = menuIdsChecked;
+};
+
+// 分配数据权限
+const handleDataScope = async (record) => {
+  scopedModalVisible.value = true;
+  title.value = '分配数据权限';
+
+  const { roleKey, roleName, dataScope, roleId } = record;
+  await nextTick();
+  Object.assign(scopeForm, { roleKey, roleName, dataScope, roleId });
+};
+
+// 单个删除
+const handleDelete = async ({ roleId }) => {
+  const res = await removeRole({ ids: [roleId] });
+  proxy.$message.success(res.msg);
+  getRoleInfo();
+};
+
+// 批量删除
+const handleBatchDelete = async () => {
+  if (batchDeleteList.value.length !== 0) {
+    proxy.$modal.warning({
+      title: '提示',
+      content: '是否删除选中的数据?',
+    });
+    const res = await removeRole({ ids: batchDeleteList.value });
+    proxy.$message.success(res.msg);
+    getRoleInfo();
+  } else {
+    proxy.$message.error('请勾选要删除的数据');
+  }
+};
+
+/**
+ * 分页改变
+ * @param {Number} [page]
+ */
+const handlePageChange = (page) => {
+  pager.pageIndex = page;
+
+  // 修改当前页码
+  currentPage.value = page;
+  getRoleInfo({ ...pager, ...queryForm });
+};
+
+// 每页数据量
+const handlePageSizeChange = (pageSize) => {
+  pager.pageSize = pageSize;
+  getRoleInfo({ ...pager, ...queryForm });
+};
+
+// 角色管理表单提交
+// 异步关闭表单需要使用 done() 回调函数
+const handleBeforeOk = (done) => {
+  proxy.$refs.modalFormRef.validate(async (valid) => {
+    // 如果 valid 为空则数据校验通过
+    if (!valid) {
+      modalForm.menuIds = checkedKeys.value;
+      let res;
+      if (modalForm.roleId) {
+        res = await updateRole(modalForm, modalForm.roleId);
+      } else {
+        res = await addRole(modalForm);
+      }
+      proxy.$message.success(res.msg);
+      getRoleInfo();
+      done();
+    } else {
+      proxy.$message.error('数据校验失败');
+      done(false);
+    }
+  });
+};
+
+// 数据权限表单提交
+const handleScopeBeforeOk = async (done) => {
+  const res = await updateRoleScoped(scopeForm);
+  proxy.$message.success(res.msg);
+  done();
+
+  getRoleInfo();
+};
+
+// 重置 Modal Form数据
+const handleResetModalForm = () => {
+  proxy.$refs.modalFormRef.resetFields();
+
+  checkedKeys.value = [];
+};
+
+// 获取角色信息
+const getRoleInfo = async (params = {}) => {
+  const res = await getRole(params);
+  const { count, list, pageIndex, pageSize } = res.data;
+  tableData.value = list;
+
+  Object.assign(pager, { total: count, pageIndex, pageSize });
+};
+
+// 获取角色菜单信息
+const getRoleMenuTreeInfo = async () => {
+  const res = await getRoleMenuTree({}, 0);
+  treeData.value = res.data.menus;
+  checkedKeys.value = res.data.checkedKeys;
+};
+
+onMounted(() => {
+  getRoleInfo();
+  getRoleMenuTreeInfo();
+});
+</script>
+
+<style setup>
+.action {
+  margin-bottom: 12px;
+}
+</style>

+ 77 - 0
src/views/admin/sys-set/index.vue

@@ -0,0 +1,77 @@
+<template>
+  <div class="app-container">
+    <a-tabs default-active-key="1" position="left" :style="{ textAlign: 'right' }">
+      <a-tab-pane key="1" title="系统内置">
+        <a-form :model="form" :style="{ width: '50%' }">
+          <a-form-item field="sys_app_name" label="系统名称">
+            <a-input v-model="form.sys_app_name"></a-input>
+          </a-form-item>
+          <a-form-item field="sys_app_logo" label="系统Logo">
+            <a-space>
+              <div class="upload-logo-preview">
+                <img width="150" height="150" />
+              </div>
+              <a-upload action="/">
+                <template #upload-button>
+                  <div class="upload-logo-card">
+                    <div class="upload-logo-card-text">
+                      <IconPlus />
+                    </div>
+                  </div>
+                </template>
+              </a-upload>
+            </a-space>
+          </a-form-item>
+          <a-form-item field="sys_user_initPassword" label="初始密码">
+            <a-input-password v-model="form.sys_user_initPassword"></a-input-password>
+          </a-form-item>
+          <a-form-item field="sys_index_skinName" label="皮肤样式">
+            <a-select v-model="form.sys_index_skinName">
+              <a-option>蓝色</a-option>
+            </a-select>
+          </a-form-item>
+          <a-form-item field="sys_index_sideTheme" label="侧边栏主题">
+            <a-select v-model="form.sys_index_sideTheme">
+              <a-option>深色主题</a-option>
+            </a-select>
+          </a-form-item>
+          <a-form-item>
+            <a-space>
+              <a-button type="primary">提交</a-button>
+              <a-button>重置</a-button>
+            </a-space>
+          </a-form-item>
+        </a-form>
+      </a-tab-pane>
+      <a-tab-pane key="2" title="其它">暂无内容</a-tab-pane>
+    </a-tabs>
+  </div>
+</template>
+
+<script setup>
+import { reactive } from 'vue';
+import { IconPlus } from '@arco-design/web-vue/es/icon'
+
+const form = reactive({});
+</script>
+
+<style lang="scss">
+.upload-logo-preview {
+  font-size: 0px;
+}
+
+.upload-logo-card {
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  width: 150px;
+  height: 150px;
+  box-sizing: border-box;
+  border: 1px dashed #c0ccda;
+  border-radius: 5px;
+  color: #4e5969;
+  &-text {
+    font-size: 24px;
+  }
+}
+</style>

+ 31 - 0
src/views/admin/sys-user/components/TreeDept.vue

@@ -0,0 +1,31 @@
+<template>
+  <div class="tree-container">
+    <a-tree
+      :data="props.data"
+      :field-names="{ key: 'deptId', title: 'deptName' }"
+      block-node
+      @select="handleTreeSelect"
+    />
+  </div>
+</template>
+
+<script setup>
+const props = defineProps({
+  data: {
+    type: Array,
+    default: () => [],
+  },
+});
+
+const emits = defineEmits(['nodeClick']);
+
+const handleTreeSelect = (selectKeys) => {
+  emits('nodeClick', { deptId: `/${selectKeys}/` });
+}
+</script>
+
+<style lang="scss">
+.tree-container {
+  padding-right: 20px;
+}
+</style>

+ 658 - 0
src/views/admin/sys-user/index.vue

@@ -0,0 +1,658 @@
+<template>
+  <div class="app-container">
+    <!-- Query -->
+    <a-form ref="queryFormRef" :model="queryForm" layout="inline">
+      <a-form-item field="deptName" label="部门名称">
+        <a-input v-model="queryForm.deptName" placeholder="请输入部门名称" />
+      </a-form-item>
+      <a-form-item field="username" label="用户名称">
+        <a-input v-model="queryForm.username" placeholder="请输入用户名称" />
+      </a-form-item>
+      <a-form-item field="phone" label="手机号码">
+        <a-input v-model="queryForm.phone" placeholder="请输入用户手机号" />
+      </a-form-item>
+      <a-form-item field="status" label="用户状态">
+        <a-select
+          v-model="queryForm.status"
+          placeholder="请选择用户状态"
+          :style="{ width: '205px' }"
+        >
+          <a-option value="2">正常</a-option>
+          <a-option value="1">停用</a-option>
+        </a-select>
+      </a-form-item>
+
+      <a-divider direction="vertical" :style="{ height: '30px' }" />
+      <a-form-item class="form-action">
+        <a-space size="medium">
+          <a-button type="primary" @click="handleQuery"><icon-search /> 搜索</a-button>
+          <a-button @click="handleResetQuery"><icon-loop /> 重置</a-button>
+        </a-space>
+      </a-form-item>
+    </a-form>
+
+    <!-- divider -->
+    <a-divider />
+
+    <!-- Table -->
+    <a-row>
+      <a-col :span="4">
+        <!-- tree组件只在组件第一次渲染时展开,此处等待数据加载完成再渲染组件 -->
+        <tree-dept v-if="treeDeptData" :data="treeDeptData" @node-click="getSysUserInfo" />
+      </a-col>
+      <a-col :span="20">
+        <!-- Action -->
+        <a-space class="action">
+          <a-button type="primary" @click="handleAdd" data-test="newUser"><icon-plus /> 新增</a-button>
+          <a-button type="primary" status="danger" @click="handleBatchDelete"><icon-delete /> 批量删除</a-button>
+        </a-space>
+
+        <!-- Table -->
+        <a-table
+          :columns="columns"
+          :data="tableData"
+          :bordered="false"
+          :row-selection="{ type: 'checkbox', showCheckedAll: true }"
+          row-key="userId"
+          :pagination="{
+            'show-total': true,
+            'show-jumper': true,
+            'show-page-size': true,
+          }"
+          @selection-change="selectionChange"
+          @page-change="handlePageChange"
+          @page-size-change="handlePageSizeChange"
+        >
+          <template #dept="{ record }">
+            {{ record.dept.deptName }}
+          </template>
+          <template #status="{ record }">
+            <a-switch
+              v-model="record.status"
+              checked-value="2"
+              unchecked-value="1"
+              @change="handleSwitchChange(record)"
+            />
+          </template>
+          <template #createdAt="{ record }">
+            {{ parseTime(record.createdAt) }}
+          </template>
+          <template #action="{ record }">
+            <a-button type="text" @click="handleUpdate(record)"><icon-edit /> 修改</a-button>
+            <a-popconfirm content="是否确认删除该用户?" type="warning" @ok="handleDelete([record.userId])">
+              <a-button type="text"><icon-delete /> 删除</a-button>
+            </a-popconfirm>
+            <a-button type="text" @click="handleReset(record.userId)"><icon-refresh /> 重置</a-button>
+          </template>
+        </a-table>
+      </a-col>
+    </a-row>
+
+    <!-- Modal -->
+    <a-modal
+      v-model:visible="modalVisible"
+      title-align="start"
+      :width="600"
+      @cancel="handleModalCancel('modalFormRef')"
+      @before-ok="handleBeforeOk"
+    >
+      <template #title>
+        {{ modalTitle }}
+      </template>
+      <a-form
+        ref="modalFormRef"
+        :model="modalForm"
+        :rules="rules"
+        auto-label-width
+      >
+        <a-row :gutter="16">
+          <a-col :span="12">
+            <a-form-item field="nickName" label="用户昵称">
+              <a-input
+                v-model="modalForm.nickName"
+                placeholder="请输入用户昵称"
+              />
+            </a-form-item>
+          </a-col>
+          <a-col :span="12">
+            <a-form-item field="deptId" label="所属部门">
+              <a-tree-select
+                v-model="modalForm.deptId"
+                :data="treeDeptData"
+                :field-names="{ key: 'deptId', title: 'deptName' }"
+                placeholder="请选择所属部门"
+              />
+            </a-form-item>
+          </a-col>
+          <a-col :span="12">
+            <a-form-item field="phone" label="手机号码">
+              <a-input v-model="modalForm.phone" placeholder="请输入手机号码" />
+            </a-form-item>
+          </a-col>
+          <a-col :span="12">
+            <a-form-item field="email" label="邮箱">
+              <a-input v-model="modalForm.email" placeholder="请输入邮箱" />
+            </a-form-item>
+          </a-col>
+          <a-col :span="12">
+            <a-form-item field="username" label="用户名称">
+              <a-input
+                v-model="modalForm.username"
+                placeholder="请输入用户名称"
+              />
+            </a-form-item>
+          </a-col>
+          <a-col v-if="!modalForm.userId" :span="12">
+            <a-form-item field="password" label="用户密码">
+              <a-input-password
+                v-model="modalForm.password"
+                placeholder="请输入用户密码"
+              />
+            </a-form-item>
+          </a-col>
+          <a-col :span="12">
+            <a-form-item field="sex" label="用户性别">
+              <a-select v-model="modalForm.sex" placeholder="请选择用户性别">
+                <a-option value="0"> 男 </a-option>
+                <a-option value="1"> 女 </a-option>
+              </a-select>
+            </a-form-item>
+          </a-col>
+          <a-col :span="12">
+            <a-form-item field="status" label="状态">
+              <a-radio-group v-model="modalForm.status">
+                <a-radio value="2"> 正常 </a-radio>
+                <a-radio value="1"> 停用 </a-radio>
+              </a-radio-group>
+            </a-form-item>
+          </a-col>
+          <a-col :span="12">
+            <a-form-item field="postId" label="岗位">
+              <a-select v-model="modalForm.postId" placeholder="请选择岗位">
+                <a-option
+                  v-for="item in postList"
+                  :key="item.postId"
+                  :value="item.postId"
+                  :label="item.postName"
+                />
+              </a-select>
+            </a-form-item>
+          </a-col>
+          <a-col :span="12">
+            <a-form-item field="roleId" label="角色">
+              <a-select v-model="modalForm.roleId" placeholder="请选择角色">
+                <a-option
+                  v-for="item in roleList"
+                  :key="item.roleId"
+                  :value="item.roleId"
+                  :label="item.roleName"
+                />
+              </a-select>
+            </a-form-item>
+          </a-col>
+        </a-row>
+        <a-form-item field="remark" label="备注">
+          <a-textarea placeholder="请输入备注" allow-clear />
+        </a-form-item>
+      </a-form>
+    </a-modal>
+
+    <a-modal
+      v-model:visible="resetPwdVisible"
+      title="重置密码"
+      @before-ok="handleResetPwd"
+      @cancel="$refs.resetPwdFormRef.resetFields()"
+    >
+      <a-form
+        ref="resetPwdFormRef"
+        :model="resetPwdForm"
+        :rules="resetPwdRules"
+        auto-label-width
+      >
+        <a-form-item field="password" label="新密码">
+          <a-input-password
+            v-model="resetPwdForm.password"
+            placeholder="请输入新密码"
+          />
+        </a-form-item>
+        <a-form-item field="repeatPwd" label="确认密码">
+          <a-input-password
+            v-model="resetPwdForm.repeatPwd"
+            placeholder="请输入确认密码"
+          />
+        </a-form-item>
+      </a-form>
+    </a-modal>
+  </div>
+</template>
+
+<script setup>
+import { reactive, ref, getCurrentInstance, onMounted } from 'vue';
+
+// Arco UI Icon
+import { IconSearch, IconLoop } from '@arco-design/web-vue/es/icon';
+
+// Components
+import TreeDept from './components/TreeDept.vue';
+
+// Api
+import {
+  getUser,
+  addUser,
+  updateUser,
+  removeUser,
+  updateUserStatus,
+  resetUserPwd,
+} from '@/api/admin/sys-user';
+import { getRole } from '@/api/admin/role';
+import { getPost } from '@/api/admin/post';
+import { getDept } from '@/api/admin/sys-dept';
+
+const { proxy } = getCurrentInstance();
+
+// Query
+const { queryForm, handleQuery, handleResetQuery } = useQueryData();
+// ApiInfo
+const { getSysPostInfo, getSysRoleInfo, getSysDeptTreeInfo, getSysUserInfo } =
+  useApiInfo();
+
+// Pager
+const pager = {
+  total: 0,
+  pageIndex: 1,
+  pageSize: 10
+}
+
+// Reset Pwd
+const {
+  resetPwdForm,
+  resetPwdVisible,
+  resetPwdRules,
+  handleReset,
+  handleResetPwd,
+} = useResetPwd();
+
+// Table Operate
+const {
+  columns,
+  tableData,
+  treeDeptData,
+  roleList,
+  postList,
+  selectionChange,
+  handlePageChange,
+  handlePageSizeChange,
+  handleBatchDelete,
+  handleSwitchChange,
+} = useTableList();
+
+// ModalForm Operate
+const {
+  rules,
+  modalForm,
+  modalVisible,
+  modalTitle,
+  handleAdd,
+  handleDelete,
+  handleUpdate,
+  handleBeforeOk,
+  handleModalCancel,
+} = useModalOperate();
+
+
+function useQueryData() {
+  const queryForm = reactive({});
+
+  // 查询
+  const handleQuery = () => {
+    getSysUserInfo(queryForm);
+  };
+
+  // 重置查询
+  const handleResetQuery = () => {
+    proxy.$refs.queryFormRef.resetFields();
+
+    getSysUserInfo(queryForm);
+  };
+
+  return { queryForm, handleQuery, handleResetQuery };
+}
+
+function useResetPwd() {
+  const resetPwdVisible = ref(false);
+  const resetPwdForm = reactive({});
+
+  // Rules
+  const resetPwdRules = {
+    password: [{ required: true, message: '请输入密码' }],
+    repeatPwd: [
+      {
+        required: true,
+        message: '请重复输入密码',
+      },
+      {
+        validator: (value, cb) => {
+          if (value !== resetPwdForm.password) {
+            cb('两次输入的密码不一致');
+          }
+        },
+      },
+    ],
+  };
+
+  // 用户重置密码API参数
+  const resetParams = {};
+
+  // 重置
+  const handleReset = (userId) => {
+    resetPwdVisible.value = true;
+    resetParams.userId = userId;
+  };
+
+  // 重置用户密码
+  const handleResetPwd = (done) => {
+    proxy.$refs.resetPwdFormRef.validate(async (err) => {
+      if (!err) {
+        resetParams.password = resetPwdForm.password;
+        const { msg } = await resetUserPwd(resetParams);
+        proxy.$message.success(msg);
+        done();
+        getSysUserInfo();
+      } else {
+        done(false);
+      }
+    });
+  };
+
+  return {
+    resetPwdForm,
+    resetPwdVisible,
+    resetPwdRules,
+    handleReset,
+    handleResetPwd,
+  };
+}
+
+function useApiInfo() {
+  // Pager
+  const pager = {
+    total: 0,
+    pageIndex: 1,
+    pageSize: 10,
+  };
+
+  /**
+   * 获取用户信息
+   * @param {*} [params]
+   */
+  const getSysUserInfo = async (params = {}) => {
+    const res = await getUser(params);
+    const { count, pageIndex, pageSize, list } = res.data;
+
+    tableData.value = list;
+    pager.total = count;
+    pager.pageIndex = pageIndex;
+    pager.pageSize = pageSize;
+  };
+
+  // 获取角色信息
+  const getSysRoleInfo = async () => {
+    const res = await getRole();
+    roleList.value = res.data.list;
+  };
+
+  // 获取岗位信息
+  const getSysPostInfo = async () => {
+    const res = await getPost();
+    postList.value = res.data.list;
+  };
+
+  // 获取部门树信息
+  const getSysDeptTreeInfo = async () => {
+    const res = await getDept();
+    treeDeptData.value = res.data;
+  };
+
+  return {
+    pager,
+    getSysPostInfo,
+    getSysRoleInfo,
+    getSysDeptTreeInfo,
+    getSysUserInfo,
+  };
+}
+
+function useTableList() {
+  // 部门数据
+  const treeDeptData = ref();
+  const roleList = ref([]);
+  const postList = ref([]);
+
+  // Table columns
+  const columns = [
+    {
+      title: '编号',
+      dataIndex: 'userId',
+    },
+    {
+      title: '登录名',
+      dataIndex: 'username',
+    },
+    {
+      title: '昵称',
+      dataIndex: 'nickName',
+    },
+    {
+      title: '部门',
+      dataIndex: 'deptName',
+      slotName: 'dept',
+    },
+    {
+      title: '手机号',
+      dataIndex: 'phone',
+    },
+    {
+      title: '状态',
+      dataIndex: 'status',
+      slotName: 'status',
+    },
+    {
+      title: '创建时间',
+      dataIndex: 'createdAt',
+      slotName: 'createdAt',
+    },
+    {
+      title: '操作',
+      slotName: 'action',
+    },
+  ];
+
+  // Table Data
+  const tableData = ref([]);
+
+  // Table Select Keys
+  let selectedKeys = ref([]);
+
+  // Table Selection Change
+  const selectionChange = (keys) => {
+    selectedKeys.value = keys;
+  };
+
+  /**
+   * 分页改变
+   * @param {Number} [page]
+   */
+  const handlePageChange = (page) => {
+    pager.pageIndex = page;
+
+    // 修改当前页码
+    currentPage.value = page;
+    getSysUserInfo({ ...pager, ...queryForm });
+  };
+
+  // 每页数据量
+  const handlePageSizeChange = (pageSize) => {
+    pager.pageSize = pageSize;
+    getSysUserInfo({ ...pager, ...queryForm });
+  };
+
+  // 用户状态快速切换
+  const handleSwitchChange = (record) => {
+    proxy.$modal.warning({
+      title: '注意',
+      content: `是否${record.status == 1 ? '停用' : '启用'} ${
+        record.username
+      } 用户?`,
+      hideCancel: false,
+      onOk: async () => {
+        const params = { userId: record.userId, status: record.status };
+        const res = await updateUserStatus(params);
+        proxy.$message.success(res.msg);
+      },
+      onCancel: () => {
+        record.status = record.status == '2' ? '1' : '2';
+      },
+    });
+  };
+
+  // 批量删除
+  const handleBatchDelete = () => {
+    if (selectedKeys.value.length === 0) {
+      proxy.$notification.error('请选择要删除的数据!');
+    } else {
+      proxy.$modal.warning({
+        title: '提示',
+        content: `是否删除当前编号为: ${selectedKeys.value} 数据?`,
+        hideCancel: false,
+        onOk: () => {
+          handleDelete(selectedKeys.value);
+        },
+      });
+    }
+  };
+
+  return {
+    treeDeptData,
+    roleList,
+    postList,
+    columns,
+    tableData,
+    selectionChange,
+    handlePageChange,
+    handlePageSizeChange,
+    handleBatchDelete,
+    handleSwitchChange,
+  };
+}
+
+function useModalOperate() {
+  const modalVisible = ref(false);
+  const modalTitle = ref('默认标题');
+
+  // Form
+  const modalForm = reactive({ status: '2' });
+
+  // AddRules
+  const rules = {
+    nickName: [{ required: true, message: '请输入用户昵称' }],
+    deptId: [{ required: true, message: '请选择所属部门' }],
+    phone: [{ required: true, message: '请输入手机号' }],
+    email: [
+      { required: true, message: '请输入邮箱' },
+      { type: 'email', message: '请输入正确的邮箱格式' },
+    ],
+    username: [{ required: true, message: '请输入用户名称' }],
+    password: [{ required: true, message: '请输入用户密码' }],
+  };
+
+  // Modal 取消后重置表单
+  const handleModalCancel = (formEl) => {
+    modalVisible.value = false;
+    resetForm(formEl);
+    modalForm.userId = null;
+  };
+
+  // 新增用户
+  const handleAdd = () => {
+    modalVisible.value = true;
+    modalTitle.value = '新增用户';
+  };
+
+  /**
+   * 修改用户
+   * @param {Object} val
+   */
+  const handleUpdate = (val) => {
+    modalVisible.value = true;
+    Object.assign(modalForm, val);
+  };
+
+  /**
+   * 删除用户
+   * @param {Array} ids
+   */
+  const handleDelete = async (ids) => {
+    const res = await removeUser({ ids });
+    proxy.$message.success(res.msg);
+    getSysUserInfo();
+  };
+
+  // 重置Form
+  const resetForm = (formEl) => {
+    if (!formEl) return;
+    proxy.$refs[formEl].resetFields();
+  };
+
+  // 表单提交
+  const handleBeforeOk = (done) => {
+    proxy.$refs.modalFormRef.validate(async (valid) => {
+      if (!valid) {
+        let res;
+        if (!modalForm.userId) {
+          res = await addUser(modalForm);
+        } else {
+          res = await updateUser(modalForm, modalForm.userId);
+        }
+        proxy.$message.success(res.msg);
+        done();
+        proxy.$refs.modalFormRef.resetFields();
+        getSysUserInfo();
+      } else {
+        proxy.$message.error('表单校验失败');
+        done(false);
+      }
+    });
+  };
+
+  return {
+    rules,
+    modalForm,
+    modalVisible,
+    modalTitle,
+    handleAdd,
+    handleDelete,
+    handleUpdate,
+    handleBeforeOk,
+    handleModalCancel,
+  };
+}
+
+onMounted(() => {
+  getSysUserInfo();
+  getSysDeptTreeInfo();
+  getSysRoleInfo();
+  getSysPostInfo();
+});
+</script>
+
+<style lang="scss">
+@media screen and (max-width: 1720px) {
+  .form-action {
+    margin-top: 12px;
+  }
+}
+
+.action {
+  margin-bottom: 8px;
+}
+</style>

+ 11 - 0
src/views/dev-tools/swagger/index.vue

@@ -0,0 +1,11 @@
+<template>
+  <iframe class="iframe-swagger" src="http://124.222.72.164:8000/swagger/index.html"></iframe>
+</template>
+
+<style lang="scss" scoped>
+.iframe-swagger {
+  width: 100%;
+  height: calc(100vh - 90px);
+  border: none
+}
+</style>

+ 25 - 0
src/views/error-page/404.vue

@@ -0,0 +1,25 @@
+<template>
+  <div class="app-container">
+    <a-result
+      status="404"
+      subtitle="抱歉,页面走丢了~"
+    >
+      <template #extra>
+        <a-space>
+          <a-button type="primary">
+            返回
+          </a-button>
+        </a-space>
+      </template>
+    </a-result>
+  </div>
+</template>
+
+<style lang="scss" scoped>
+.app-container {
+  display: flex;
+  flex-direction: column;
+  justify-content: center;
+  height: calc(100vh - 122px);
+}
+</style>

+ 3 - 0
src/views/index.vue

@@ -0,0 +1,3 @@
+<template>
+  <router-view></router-view>
+</template>

+ 284 - 0
src/views/login/index.vue

@@ -0,0 +1,284 @@
+<template>
+  <div class="account">
+    <div class="account-container">
+      <div class="account-wrap-login">
+        <div class="login-pic">
+          <div>
+            <img
+              src="https://image.akiraka.net/Quark/login/login_left_bg.jpeg"
+            />
+          </div>
+        </div>
+        <div class="login-form" style="padding: 3rem !important">
+          <div class="login-form-container">
+            <div class="account-top">
+              <div class="account-top-logo">
+                <img src="https://image.akiraka.net/Quark/login/logo.png" />
+                <span class="project-title">用户登录</span>
+              </div>
+              <div class="account-top-desc">Akiraka</div>
+            </div>
+            <!-- 登录表单 -->
+            <a-form
+              :model="loginForm"
+              :rules="loginRules"
+              ref="loginFormRef"
+              layout="vertical"
+              @keyup.enter="handleLogin"
+            >
+              <a-form-item field="userName" hide-asterisk>
+                <a-input
+                  v-model="loginForm.userName"
+                  placeholder="请输入用户名"
+                >
+                  <template #prefix>
+                    <icon-user />
+                  </template>
+                </a-input>
+              </a-form-item>
+              <a-form-item field="passWord" hide-asterisk>
+                <a-input-password
+                  v-model="loginForm.passWord"
+                  placeholder="请输入密码"
+                >
+                  <template #prefix>
+                    <icon-lock />
+                  </template>
+                </a-input-password>
+              </a-form-item>
+              <div style="display: flex">
+                <div style="width: 65%">
+                  <a-form-item field="code" hide-asterisk>
+                    <a-input
+                      v-model="loginForm.code"
+                      placeholder="请输入验证码"
+                    >
+                      <template #prefix>
+                        <icon-safe />
+                      </template>
+                    </a-input>
+                  </a-form-item>
+                </div>
+                <div style="width: 5%"></div>
+                <div style="width: 20%">
+                  <a-form-item field="code" hide-asterisk>
+                    <img
+                      :src="captchUrl"
+                      class="captcha"
+                      @click="loadCaptcha()"
+                    />
+                  </a-form-item>
+                </div>
+              </div>
+              <a-space direction="vertical" size="medium">
+                <a-checkbox>记住密码</a-checkbox>
+                <a-button
+                  size="large"
+                  type="primary"
+                  :loading="loading"
+                  long
+                  @click="handleLogin"
+                  >登录</a-button
+                >
+              </a-space>
+            </a-form>
+          </div>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script setup>
+import { ref, reactive, onMounted, getCurrentInstance } from 'vue';
+import { IconUser, IconLock, IconSafe } from '@arco-design/web-vue/es/icon';
+import { login, getCaptcha } from '@/api/admin/login';
+import { useUserStore } from '../../store/userInfo';
+
+const { proxy } = getCurrentInstance();
+const store = useUserStore();
+// form
+const loginForm = reactive({});
+// 验证码
+const captchUrl = ref(null);
+// 按钮loading
+const loading = ref(false);
+
+// rules
+const loginRules = {
+  userName: [{ required: true, message: '请输入用户名' }],
+  passWord: [{ required: true, message: '请输入密码' }],
+  code: [{ required: true, message: '请输入验证码' }],
+};
+
+// 获取验证码
+const loadCaptcha = async () => {
+  const res = await getCaptcha();
+  captchUrl.value = res.data;
+  loginForm.uuid = res.id;
+};
+
+// 登陆
+const handleLogin = () => {
+  loading.value = true;
+  // 如果 valid 为空则校验成功
+  proxy.$refs.loginFormRef.validate(async (valid) => {
+    if (!valid) {
+      try {
+        const res = await login(loginForm);
+        await store.setToken(res.token);
+        proxy.$message.success({
+          content: '登陆成功',
+          duration: 2000,
+        });
+        setTimeout(() => {
+          proxy.$router.push('/admin/sys-api');
+          loading.value = false;
+        }, 500);
+      } catch (err) {
+        proxy.$message.error(`登陆失败:${err}`);
+        loadCaptcha();
+        loginForm.code = '';
+        loading.value = false;
+      }
+    } else {
+      loading.value = false;
+      return false;
+    }
+  });
+};
+
+onMounted(async () => {
+  await loadCaptcha();
+});
+</script>
+
+<style lang="scss" scoped>
+.captcha {
+  width: 100px;
+  height: 32px;
+  cursor: pointer;
+}
+
+*,
+:before,
+:after {
+  box-sizing: border-box;
+  border-width: 0;
+  border-style: solid;
+  border-color: #e5e7eb;
+}
+
+// 输入框重写
+.arco-input-wrapper {
+  border-radius: 20px;
+  height: 40px;
+  border: 1px solid #ddd;
+  background: #fff;
+}
+// 输入框 重写框颜色
+.arco-input-wrapper:hover {
+  background: #fff;
+  border: 1px solid #1e6fff;
+}
+
+.account {
+  width: 100%;
+  margin: 0 auto;
+}
+.account-container {
+  width: 100%;
+  min-height: 100vh;
+  display: flex;
+  flex-wrap: wrap;
+  justify-content: center;
+  align-items: center;
+  padding: 15px;
+  background: #9053c7;
+  background: linear-gradient(-135deg, #c850c0, #4158d0);
+}
+.account-wrap-login {
+  width: 960px;
+  height: 554px;
+  // background: #fff;
+  border-radius: 10px;
+  overflow: hidden;
+  display: flex;
+  flex-wrap: wrap;
+  // justify-content:space-between;
+  // padding:30px 95px 33px
+}
+.account-wrap-login .login-pic {
+  background-color: #0259e6 !important;
+  display: flex;
+  align-items: center;
+  flex-direction: column;
+  justify-content: center;
+  width: 50%;
+}
+.account-wrap-login .login-pic img {
+  max-width: 100%;
+}
+.account-wrap-login .login-form {
+  width: 50%;
+  display: flex;
+  flex-direction: column;
+  background: #fff;
+}
+.account-wrap-login .login-form-container {
+  margin: auto;
+  width: 100%;
+}
+.account-wrap-login .login-form-title {
+  padding-bottom: 15px;
+  text-align: center;
+}
+@media (max-width: 991px) {
+  .account-wrap-login .login-pic {
+    display: none;
+  }
+  .account-wrap-login .login-form {
+    width: 100%;
+    margin: auto;
+  }
+}
+.account-wrap-login .account-top {
+  text-align: center;
+}
+.account-wrap-login .account-top-logo {
+  text-align: center;
+  margin-bottom: 10px;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+}
+.account-wrap-login .account-top-logo img {
+  width: 45px;
+}
+.account-wrap-login .account-top-logo .project-title {
+  background: linear-gradient(
+    92.06deg,
+    #33c2ff -17.9%,
+    #1e6fff 43.39%,
+    #1e6fff 99.4%
+  );
+  -webkit-background-clip: text;
+  -webkit-text-fill-color: transparent;
+  font-size: 24px;
+  line-height: 1.25;
+  font-weight: 500;
+  margin-left: 10px;
+}
+.account-wrap-login .account-top-desc {
+  font-size: 14px;
+  color: #808695;
+  margin-bottom: 20px;
+}
+@media (max-width: 640px) {
+  .account-wrap-login {
+    width: 100%;
+    padding: 30px;
+    height: auto;
+  }
+}
+</style>

+ 1 - 0
src/views/profile/api-management.svg

@@ -0,0 +1 @@
+<svg width="24" height="24" viewBox="0 0 48 48" fill="currentColor"><path fill-rule="evenodd" clip-rule="evenodd" d="M4 15a1 1 0 001 1h2a1 1 0 001-1V8h7a1 1 0 001-1V5a1 1 0 00-1-1H6a2 2 0 00-2 2v9zm4 18a1 1 0 00-1-1H5a1 1 0 00-1 1v9a2 2 0 002 2h9a1 1 0 001-1v-2a1 1 0 00-1-1H8v-7zm35-17a1 1 0 001-1V6a2 2 0 00-2-2h-9a1 1 0 00-1 1v2a1 1 0 001 1h7v7a1 1 0 001 1h2zm1 17a1 1 0 00-1-1h-2a1 1 0 00-1 1v7h-7a1 1 0 00-1 1v2a1 1 0 001 1h9a2 2 0 002-2v-9zM32.835 11h-6.108c-6.512 0-11.882 4.804-12.636 11h-1.992c-.382 0-.52.046-.66.134a.855.855 0 00-.325.378c-.074.162-.114.324-.114.77v1.436c0 .446.04.608.114.77.075.163.185.291.324.378.14.088.279.134.66.134h2.157c1.179 5.706 6.315 10 12.472 10h6.108c.405 0 .552-.041.7-.12a.819.819 0 00.344-.337c.079-.145.121-.29.121-.688V31h.901c.382 0 .52-.046.66-.134a.855.855 0 00.325-.378c.074-.162.114-.324.114-.77v-1.436c0-.446-.04-.608-.114-.77a.855.855 0 00-.325-.378c-.14-.088-.278-.134-.66-.134H34v-7h.901c.382 0 .52-.046.66-.134a.855.855 0 00.325-.378c.074-.162.114-.324.114-.77v-1.436c0-.446-.04-.608-.114-.77a.855.855 0 00-.325-.378c-.14-.088-.278-.134-.66-.134H34v-3.855c0-.398-.042-.543-.121-.688a.819.819 0 00-.344-.338c-.148-.078-.295-.119-.7-.119zm-2.744 3.571h-3.637c-5.02 0-9.09 3.998-9.09 8.929s4.07 8.929 9.09 8.929h3.637V14.57z" fill="currentColor"/></svg>

+ 181 - 0
src/views/profile/index.vue

@@ -0,0 +1,181 @@
+<template>
+  <div class="app-container">
+    <a-space>
+      <a-avatar :style="{ backgroundColor: '#3370ff' }" :size="100">{{
+        userInfoForm.nickName
+      }}</a-avatar>
+      <div class="userinfo-head">
+        <table class="userinfo-table">
+          <tbody>
+            <tr class="userinfo-row">
+              <td class="userinfo-item" colspan="1">
+                <div class="userinfo-item-label">用户名:</div>
+                <div class="userinfo-item-value">
+                  {{ userInfoForm.username }}
+                </div>
+              </td>
+              <td class="userinfo-item" colspan="1">
+                <div class="userinfo-item-label">手机号:</div>
+                <div class="userinfo-item-value">{{ userInfoForm.phone }}</div>
+              </td>
+            </tr>
+            <tr class="userinfo-row">
+              <td class="userinfo-item">
+                <div class="userinfo-item-label">用户邮箱:</div>
+                <div class="userinfo-item-value">{{ userInfoForm.email }}</div>
+              </td>
+              <td class="userinfo-item">
+                <div class="userinfo-item-label">所属部门:</div>
+                <div class="userinfo-item-value">Quark</div>
+              </td>
+            </tr>
+            <tr class="userinfo-row">
+              <td class="userinfo-item">
+                <div class="userinfo-item-label">所属角色:</div>
+                <div class="userinfo-item-value">系统管理员</div>
+              </td>
+              <td class="userinfo-item">
+                <div class="userinfo-item-label">创建时间:</div>
+                <div class="userinfo-item-value">
+                  {{ parseTime(userInfoForm.createdAt) }}
+                </div>
+              </td>
+            </tr>
+          </tbody>
+        </table>
+      </div>
+    </a-space>
+
+    <a-divider />
+
+    <a-tabs default-active-key="1" type="rounded">
+      <a-tab-pane key="1" title="基础信息">
+        <a-form
+          :model="userInfoForm"
+          :rules="rules"
+          class="userinfo-form"
+          ref="userInfoFormRef"
+        >
+          <a-form-item field="nickName" label="用户昵称">
+            <a-input
+              v-model="userInfoForm.nickName"
+              placeholder="请输入用户昵称"
+            ></a-input>
+          </a-form-item>
+          <a-form-item field="phone" label="手机号码">
+            <a-input
+              v-model="userInfoForm.phone"
+              placeholder="请输入手机号码"
+            ></a-input>
+          </a-form-item>
+          <a-form-item field="email" label="邮箱">
+            <a-input
+              v-model="userInfoForm.email"
+              placeholder="请输入邮箱"
+            ></a-input>
+          </a-form-item>
+          <a-form-item field="sex" label="性别">
+            <a-radio-group v-model="userInfoForm.sex">
+              <a-radio value="0">男</a-radio>
+              <a-radio value="1">女</a-radio>
+            </a-radio-group>
+          </a-form-item>
+          <a-form-item>
+            <a-space>
+              <a-button type="primary">保存</a-button>
+              <a-button @click="$refs.userInfoFormRef.resetFields()"
+                >重置</a-button
+              >
+            </a-space>
+          </a-form-item>
+        </a-form>
+      </a-tab-pane>
+      <a-tab-pane key="2" title="修改密码">
+        <a-form :model="userPwdForm" ref="userPwdFormRef" class="userinfo-form">
+          <a-form-item field="oldpwd" label="旧密码">
+            <a-input
+              v-model="userPwdForm.oldpwd"
+              placeholder="请输入旧密码"
+            ></a-input>
+          </a-form-item>
+          <a-form-item field="newpwd" label="新密码">
+            <a-input
+              v-model="userPwdForm.newpwd"
+              placeholder="请输入新密码"
+            ></a-input>
+          </a-form-item>
+          <a-form-item field="repeatpwd" label="确认密码">
+            <a-input
+              v-model="userPwdForm.repeatpwd"
+              placeholder="请输入确认密码"
+            ></a-input>
+          </a-form-item>
+          <a-form-item>
+            <a-space>
+              <a-button type="primary">保存</a-button>
+              <a-button @click="$refs.userPwdFormRef.resetFields()"
+                >重置</a-button
+              >
+            </a-space>
+          </a-form-item>
+        </a-form>
+      </a-tab-pane>
+    </a-tabs>
+  </div>
+</template>
+
+<script setup>
+import { onMounted, reactive } from 'vue';
+import { useUserStore } from '@/store/userInfo';
+import { getCurrentUser } from '@/api/admin/sys-user';
+
+// use Store
+const store = useUserStore();
+
+const userInfoForm = reactive({
+  sex: 0,
+});
+const userPwdForm = reactive({});
+
+// Rules
+const rules = {
+  nickName: [{ required: true, message: '请输入用户昵称' }],
+  phone: [{ required: true, message: '请输入手机号码' }],
+  email: [{ required: true, message: '请输入邮箱' }],
+};
+
+const getCurrentUserInfo = async () => {
+  const res = await getCurrentUser(store.uid);
+  Object.assign(userInfoForm, res.data);
+};
+
+onMounted(() => {
+  getCurrentUserInfo();
+});
+</script>
+
+<style lang="scss">
+.userinfo-row {
+  font-size: 14px;
+  line-height: 1.5;
+  .userinfo-item {
+    &-label {
+      width: 140px;
+      color: #4e5969;
+      text-align: right;
+    }
+    &-value {
+      width: 140px;
+      color: $primary-font-color;
+    }
+    div {
+      display: inline-block;
+    }
+  }
+}
+
+.userinfo-form {
+  width: 520px;
+  margin: 0 auto;
+}
+</style>

+ 266 - 0
src/views/schedule/index.vue

@@ -0,0 +1,266 @@
+<template>
+  <div class="app-container">
+    <a-form :model="queryForm" ref="queryFormRef" layout="inline">
+      <a-form-item field="jobName" label="任务名称">
+        <a-input
+          v-model="queryForm.jobName"
+          placeholder="请输入任务名称"
+        ></a-input>
+      </a-form-item>
+      <a-form-item field="jobGroup" label="任务分组">
+        <a-select v-model="queryForm.jobGroup" placeholder="请选择任务分组">
+          <a-option value="DEFAULT">默认</a-option>
+          <a-option value="SYSTEM">系统</a-option>
+        </a-select>
+      </a-form-item>
+      <a-form-item field="status" label="状态">
+        <a-select v-model="queryForm.status" placeholder="请选择任务状态">
+          <a-option :value="2">正常</a-option>
+          <a-option :value="1">关闭</a-option>
+        </a-select>
+      </a-form-item>
+      <a-form-item>
+        <a-space>
+          <a-button type="primary" @click="handleQuery">查询</a-button>
+          <a-button @click="handleResetQuery">重置</a-button>
+        </a-space>
+      </a-form-item>
+    </a-form>
+
+    <a-divider />
+
+    <div class="table-action">
+      <a-button type="primary" @click="handleAdd">新增定时任务</a-button>
+    </div>
+
+    <a-table :data="tableData" :columns="columns">
+      <template #status="{ record }">
+        <a-tag v-if="record.status == 2" color="green">正常</a-tag>
+        <a-tag v-if="record.status == 1" color="red">停用</a-tag>
+      </template>
+      <template #action="{ record }">
+        <a-button type="text" @click="handleUpdate(record)">修改</a-button>
+        <a-button type="text" status="success" v-if="record.entry_id == 0" @click="handleStart(record.jobId)">启动</a-button>
+        <a-button type="text" status="danger" v-if="record.entry_id !== 0" @click="handleStop(record.jobId)">停止</a-button>
+        <a-popconfirm
+          content="是否删除当前数据?"
+          type="warning"
+          @ok="handleDelete([record.jobId])"
+        >
+          <a-button type="text" status="danger">删除</a-button>
+        </a-popconfirm>
+      </template>
+    </a-table>
+
+    <a-modal
+      v-model:visible="modalVisible"
+      title-align="start"
+      :title="modalTitle"
+      :on-before-ok="onBeforeOk"
+      @ok="handleOk"
+      @cancel="$refs.modalFormRef.resetFields()"
+    >
+      <a-form
+        :model="modalForm"
+        :rules="rules"
+        ref="modalFormRef"
+        auto-label-width
+      >
+        <a-form-item field="jobName" label="任务名称">
+          <a-input
+            v-model="modalForm.jobName"
+            placeholder="请输入任务名称"
+          ></a-input>
+        </a-form-item>
+        <a-form-item field="jobGroup" label="任务分组">
+          <a-select v-model="modalForm.jobGroup" placeholder="请选择任务分组">
+            <a-option value="DEFAULT">默认</a-option>
+            <a-option value="SYSTEM">系统</a-option>
+          </a-select>
+        </a-form-item>
+        <a-form-item field="invokeTarget" label="调用目标">
+          <a-input
+            v-model="modalForm.invokeTarget"
+            placeholder="调用目标"
+          ></a-input>
+        </a-form-item>
+        <a-form-item field="args" label="目标参数">
+          <a-input v-model="modalForm.args" placeholder="目标参iuu数"></a-input>
+        </a-form-item>
+        <a-form-item field="cronExpression" label="Cron表达式">
+          <a-input
+            v-model="modalForm.cronExpression"
+            placeholder="Cron表达式"
+          ></a-input>
+        </a-form-item>
+        <a-form-item field="concurrent" label="是否并发">
+          <a-radio-group v-model="modalForm.concurrent" type="button">
+            <a-radio :value="0">允许</a-radio>
+            <a-radio :value="1">禁止</a-radio>
+          </a-radio-group>
+        </a-form-item>
+        <a-form-item field="jobType" label="调用类型">
+          <a-radio-group v-model="modalForm.jobType" type="button">
+            <a-radio :value="1">接口</a-radio>
+            <a-radio :value="2">函数</a-radio>
+          </a-radio-group>
+        </a-form-item>
+        <a-form-item field="misfirePolicy" label="执行策略">
+          <a-radio-group v-model="modalForm.misfirePolicy" type="button">
+            <a-radio :value="1">立即执行</a-radio>
+            <a-radio :value="2">执行一次</a-radio>
+            <a-radio :value="3">放弃执行</a-radio>
+          </a-radio-group>
+        </a-form-item>
+        <a-form-item field="status" label="状态">
+          <a-select v-model="modalForm.status">
+            <a-option :value="2">正常</a-option>
+            <a-option :value="1">停用</a-option>
+          </a-select>
+        </a-form-item>
+      </a-form>
+    </a-modal>
+  </div>
+</template>
+
+<script setup>
+import { reactive, ref, onMounted, getCurrentInstance } from 'vue';
+import {
+  listSysJob,
+  addSysJob,
+  updateSysJob,
+  delSysJob,
+  startJob,
+  removeJob
+} from '@/api/sys-job';
+
+const { proxy } = getCurrentInstance();
+
+const queryForm = reactive({});
+const modalForm = reactive({
+  concurrent: 1,
+  jobType: 1,
+  misfirePolicy: 1,
+  status: 2,
+});
+
+// 表单检验规则
+const rules = {
+  jobName: [{ required: true, message: '请输入任务名称' }],
+  jobGroup: [{ required: true, message: '请选择任务分组' }],
+  invokeTarget: [{ required: true, message: '请输入调用目标' }],
+  cronExpression: [{ required: true, message: '请输入Cron表达式' }],
+  status: [{ required: true, message: '请选择状态' }],
+};
+
+const modalVisible = ref(false);
+const modalTitle = ref('默认标题');
+
+const tableData = ref([]);
+const columns = [
+  { title: '编号', dataIndex: 'jobId' },
+  { title: '任务名称', dataIndex: 'jobName' },
+  { title: '任务分组', dataIndex: 'jobGroup' },
+  { title: '任务表达式', dataIndex: 'cronExpression' },
+  { title: '调用目标', dataIndex: 'invokeTarget' },
+  { title: '状态', dataIndex: 'status', slotName: 'status' },
+  { title: '操作', slotName: 'action' },
+];
+
+// 查询任务
+const handleQuery = () => {
+  getSysJobListInfo(queryForm);
+}
+
+// 重置查询
+const handleResetQuery = () => {
+  proxy.$refs.queryFormRef.resetFields();
+
+  getSysJobListInfo();
+}
+
+// 新增任务
+const handleAdd = () => {
+  modalVisible.value = true;
+  modalTitle.value = '新增任务';
+};
+
+// 修改任务
+const handleUpdate = (record) => {
+  modalVisible.value = true;
+  modalTitle.value = '修改任务';
+
+  Object.assign(modalForm, record);
+};
+
+/**
+ * 删除定时任务
+ * @params {Array} ids
+ */
+const handleDelete = async (ids) => {
+  const res = await delSysJob({ ids });
+  proxy.$message.success(res.msg);
+
+  getSysJobListInfo();
+};
+
+// 启动定时任务
+const handleStart = async (jobId) => {
+  await startJob(jobId);
+  proxy.$notification.success('启动任务成功!')
+
+  getSysJobListInfo();
+}
+
+// 关闭定时任务
+const handleStop = async (jobId) => {
+  await removeJob(jobId);
+  proxy.$notification.success('已停止任务!')
+
+  getSysJobListInfo();
+}
+
+// Modal 触发oK事件前
+const onBeforeOk = (done) => {
+  proxy.$refs.modalFormRef.validate((err) => {
+    if (!err) {
+      return done();
+    } else {
+      proxy.$message.error('表单校验失败');
+      return done(false);
+    }
+  });
+};
+
+// Modal 触发ok事件
+const handleOk = async () => {
+  let res;
+  if (modalForm.jobId) {
+    res = await updateSysJob(modalForm);
+  } else {
+    res = await addSysJob(modalForm);
+  }
+  proxy.$message.success(res.msg);
+
+  getSysJobListInfo();
+};
+
+// 获取系统任务信息
+const getSysJobListInfo = async (params = {}) => {
+  const res = await listSysJob(params);
+  const { count, list, pageIndex, pageSize } = res.data;
+
+  tableData.value = list;
+};
+
+
+onMounted(() => {
+  getSysJobListInfo();
+});
+</script>
+
+<style lang="scss" scoped>
+.table-action {
+  margin-bottom: 12px;
+}
+</style>

+ 160 - 0
src/views/sys-tools/monitor/index.vue

@@ -0,0 +1,160 @@
+<template>
+  <div class="card-wrapper">
+    <a-row :gutter="12">
+      <a-col :span="12">
+        <a-card title="服务器信息">
+          <div class="card-item card-item-server">
+            <span class="card-item-title">主机名称</span>
+            <span class="card-item-desc">{{ SystemInfo?.os?.hostName}}</span>
+          </div>
+          <div class="card-item card-item-server">
+            <span class="card-item-title">操作系统</span>
+            <span class="card-item-desc">{{ SystemInfo?.os?.goOs }}</span>
+          </div>
+          <div class="card-item card-item-server">
+            <span class="card-item-title">服务器IP</span>
+            <span class="card-item-desc">{{ SystemInfo?.os?.ip }}</span>
+          </div>
+          <div class="card-item card-item-server">
+            <span class="card-item-title">系统架构</span>
+            <span class="card-item-desc">{{ SystemInfo?.os?.arch }}</span>
+          </div>
+          <div class="card-item card-item-server">
+            <span class="card-item-title">CPU</span>
+            <span class="card-item-desc">{{ SystemInfo?.cpu?.cpuInfo[0]?.modelName }}</span>
+          </div>
+          <div class="card-item card-item-server">
+            <span class="card-item-title">当前时间</span>
+            <span class="card-item-desc">{{ SystemInfo?.os?.time }}</span>
+          </div>
+        </a-card>
+      </a-col>
+
+      <a-col :span="12">
+        <a-card :title="SystemInfo.location">
+          <a-row :gutter="12">
+            <a-col :span="12">
+              <div class="card-item">
+                <span class="card-item-title">系统</span>
+                <span class="card-item-desc">Linux</span>
+              </div>
+            </a-col>
+            <a-col :span="12">
+              <div class="card-item">
+                <span class="card-item-title">时间</span>
+                <span class="card-item-desc">2022-05-06 15:32:22</span>
+              </div>
+            </a-col>
+          </a-row>
+          <a-row :gutter="12">
+            <a-col :span="12">
+              <div class="card-item">
+                <span class="card-item-title">内存</span>
+                <span class="card-item-desc">129MB/2349MB</span>
+              </div>
+            </a-col>
+            <a-col :span="12">
+              <div class="card-item">
+                <span class="card-item-title">在线时间</span>
+                <span class="card-item-desc">{{ SystemInfo.bootTime }}分钟</span>
+              </div>
+            </a-col>
+          </a-row>
+          <a-row :gutter="12">
+            <a-col :span="12">
+              <div class="card-item">
+                <span class="card-item-title">交换</span>
+                <span class="card-item-desc">0/0</span>
+              </div>
+            </a-col>
+            <a-col :span="12">
+              <div class="card-item">
+                <span class="card-item-title">硬盘</span>
+                <span class="card-item-desc">{{ SystemInfo?.disk?.used }}GB/{{ SystemInfo?.disk?.total }}GB</span>
+              </div>
+            </a-col>
+          </a-row>
+          <a-row :gutter="12">
+            <a-col :span="12">
+              <div class="card-item">
+                <span class="card-item-title">下载</span>
+                <span class="card-item-desc">{{ SystemInfo?.net?.in }}KB</span>
+              </div>
+            </a-col>
+            <a-col :span="12">
+              <div class="card-item">
+                <span class="card-item-title">上传</span>
+                <span class="card-item-desc">{{ SystemInfo?.net?.out }}KB</span>
+              </div>
+            </a-col>
+          </a-row>
+
+          <!-- progress -->
+          <div class="progress-wrapper">
+            <span class="progress-title">CPU</span>
+            <a-progress status="success" :percent="SystemInfo?.cpu?.percent / 100" />
+          </div>
+          <div class="progress-wrapper">
+            <span class="progress-title">RAM</span>
+            <a-progress status="warning" :percent="SystemInfo?.mem?.percent / 100" />
+          </div>
+          <div class="progress-wrapper">
+            <span class="progress-title">硬盘</span>
+            <a-progress :percent="SystemInfo?.disk?.percent / 100" />
+          </div>
+        </a-card>
+      </a-col>
+    </a-row>
+  </div>
+</template>
+
+<script setup>
+import { onMounted, reactive } from 'vue';
+import { getServerMonitor } from '@/api/sys-tools/monitor';
+
+
+const SystemInfo = reactive({});
+
+const getServerMonitorInfo = async () => {
+  const res = await getServerMonitor();
+
+  Object.assign(SystemInfo, res)
+}
+
+onMounted(() => {
+  getServerMonitorInfo();
+});
+</script>
+
+<style lang="scss" scoped>
+.card-wrapper {
+  width: 100%;
+}
+
+.card-item {
+  display: flex;
+  justify-content: space-between;
+  padding: 15px 0;
+  border-bottom: 1px solid rgb(229, 230, 235);
+  &-title {
+    color: $primary-font-color;
+  }
+  &-desc {
+    color: $secondary-font-color;
+  }
+}
+
+.card-item-server:last-child {
+  border-bottom: none;
+}
+
+.progress-wrapper {
+  margin-top: 15px;
+  display: flex;
+  .progress-title {
+    font-weight: bold;
+    margin-right: 10px;
+    width: 50px;
+  }
+}
+</style>

+ 34 - 0
vite.config.js

@@ -0,0 +1,34 @@
+import { join } from 'path'
+import { defineConfig } from 'vite';
+import { viteMockServe } from 'vite-plugin-mock';
+import vue from '@vitejs/plugin-vue';
+
+// https://vitejs.dev/config/
+export default defineConfig({
+  plugins: [
+    vue(),
+    viteMockServe({
+      mockPath: '/mock',
+    }),
+  ],
+  resolve: {
+    alias: {
+      '@': join(__dirname, 'src'),
+    }
+  },
+  server: {
+    proxy: {
+      '/api/v1': {
+        target: 'http://127.0.0.1:8000',
+      }
+    }
+  },
+  // 引入全局scss变量
+  css: {
+    preprocessorOptions: {
+      scss: {
+        additionalData: `@import "@/style/variables.scss";`
+      }
+    }
+  }
+});