Ver Fonte

feat: dev_1.0.0

chenhao há 2 anos atrás
commit
01f3bfa3e9
66 ficheiros alterados com 6496 adições e 0 exclusões
  1. 1 0
      .env.development
  2. 1 0
      .env.production
  3. 1 0
      .env.test
  4. 24 0
      .gitignore
  5. 9 0
      .vscode/extensions.json
  6. 16 0
      README.md
  7. 35 0
      components.d.ts
  8. 13 0
      index.html
  9. 43 0
      package.json
  10. 3299 0
      pnpm-lock.yaml
  11. 15 0
      postcss.config.cjs
  12. 1 0
      public/vite.svg
  13. 13 0
      src/App.vue
  14. 23 0
      src/apis/common.ts
  15. 25 0
      src/apis/exam.ts
  16. 28 0
      src/apis/school.ts
  17. 65 0
      src/apis/struct.ts
  18. 74 0
      src/apis/user.ts
  19. 11 0
      src/assets/icons/add-icon.svg
  20. 11 0
      src/assets/icons/arrow-down-icon.svg
  21. 11 0
      src/assets/icons/download-icon.svg
  22. 12 0
      src/assets/icons/exam-icon.svg
  23. 13 0
      src/assets/icons/exit-icon.svg
  24. 17 0
      src/assets/icons/paper-icon.svg
  25. 13 0
      src/assets/icons/pwd-icon.svg
  26. 11 0
      src/assets/icons/school-icon.svg
  27. 11 0
      src/assets/icons/upload-icon.svg
  28. 12 0
      src/assets/icons/user-icon.svg
  29. BIN
      src/assets/images/layout/user-info-bg.png
  30. BIN
      src/assets/images/layout/web-logo.png
  31. BIN
      src/assets/images/login/login-banner.png
  32. 23 0
      src/assets/less/antd.modal.less
  33. 41 0
      src/assets/less/antd.table.less
  34. 1 0
      src/assets/less/antd.theme.less
  35. 41 0
      src/assets/less/global.less
  36. 23 0
      src/assets/less/var.less
  37. 25 0
      src/components/block/index.vue
  38. 26 0
      src/components/svg-icon/index.vue
  39. 8 0
      src/constants/storage.ts
  40. 33 0
      src/layout/index.vue
  41. 110 0
      src/layout/left-menu.vue
  42. 59 0
      src/layout/menu-item.vue
  43. 15 0
      src/main.ts
  44. 295 0
      src/pages/exam-manage/index.vue
  45. 136 0
      src/pages/login/index.vue
  46. 276 0
      src/pages/school-manage/index.vue
  47. 403 0
      src/pages/subjects-manage/index.vue
  48. 460 0
      src/pages/user-manage/index.vue
  49. 31 0
      src/plugins/configFilter.ts
  50. 73 0
      src/plugins/request.ts
  51. 15 0
      src/plugins/signToken.ts
  52. 55 0
      src/routes/index.ts
  53. 13 0
      src/store/main.ts
  54. 25 0
      src/utils/auth.ts
  55. 33 0
      src/utils/common.ts
  56. 9 0
      src/utils/error.ts
  57. 14 0
      src/utils/flexible.ts
  58. 67 0
      src/utils/storage.ts
  59. 15 0
      src/vite-env.d.ts
  60. 9 0
      tailwind.config.cjs
  61. 49 0
      tsconfig.json
  62. 9 0
      tsconfig.node.json
  63. 13 0
      types/axios.d.ts
  64. 214 0
      types/project.d.ts
  65. 7 0
      types/store.d.ts
  66. 62 0
      vite.config.ts

+ 1 - 0
.env.development

@@ -0,0 +1 @@
+VITE_APP_API_HOST = "/"

+ 1 - 0
.env.production

@@ -0,0 +1 @@
+VITE_APP_API_HOST = "http://192.168.10.39:7100"

+ 1 - 0
.env.test

@@ -0,0 +1 @@
+VITE_APP_API_HOST = "http://192.168.10.39:7100"

+ 24 - 0
.gitignore

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

+ 9 - 0
.vscode/extensions.json

@@ -0,0 +1,9 @@
+{
+  "recommendations": [
+    "Vue.volar",
+    "bradlc.vscode-tailwindcss",
+    "esbenp.prettier-vscode",
+    "phoenisx.cssvar",
+    "ionutvmi.path-autocomplete"
+  ]
+}

+ 16 - 0
README.md

@@ -0,0 +1,16 @@
+# Vue 3 + TypeScript + Vite
+
+This template should help get you started developing with Vue 3 and TypeScript 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=Vue.volar)
+
+## Type Support For `.vue` Imports in TS
+
+Since TypeScript cannot handle type information for `.vue` imports, they are shimmed to be a generic Vue component type by default. In most cases this is fine if you don't really care about component prop types outside of templates. However, if you wish to get actual prop types in `.vue` imports (for example to get props validation when using manual `h(...)` calls), you can enable Volar's Take Over mode by following these steps:
+
+1. Run `Extensions: Show Built-in Extensions` from VS Code's command palette, look for `TypeScript and JavaScript Language Features`, then right click and select `Disable (Workspace)`. By default, Take Over mode will enable itself if the default TypeScript extension is disabled.
+2. Reload the VS Code window by running `Developer: Reload Window` from the command palette.
+
+You can learn more about Take Over mode [here](https://github.com/johnsoncodehk/volar/discussions/471).

+ 35 - 0
components.d.ts

@@ -0,0 +1,35 @@
+// generated by unplugin-vue-components
+// We suggest you to commit this file into source control
+// Read more: https://github.com/vuejs/core/pull/3399
+import '@vue/runtime-core'
+
+export {}
+
+declare module '@vue/runtime-core' {
+  export interface GlobalComponents {
+    AButton: typeof import('ant-design-vue/es')['Button']
+    AButtonGroup: typeof import('ant-design-vue/es')['ButtonGroup']
+    ADropdownButton: typeof import('ant-design-vue/es')['DropdownButton']
+    AForm: typeof import('ant-design-vue/es')['Form']
+    AFormItem: typeof import('ant-design-vue/es')['FormItem']
+    AInput: typeof import('ant-design-vue/es')['Input']
+    AInputNumber: typeof import('ant-design-vue/es')['InputNumber']
+    AMenu: typeof import('ant-design-vue/es')['Menu']
+    AMenuItem: typeof import('ant-design-vue/es')['MenuItem']
+    AModal: typeof import('ant-design-vue/es')['Modal']
+    APopover: typeof import('ant-design-vue/es')['Popover']
+    ARadio: typeof import('ant-design-vue/es')['Radio']
+    ARadioGroup: typeof import('ant-design-vue/es')['RadioGroup']
+    ASelect: typeof import('ant-design-vue/es')['Select']
+    ASelectOption: typeof import('ant-design-vue/es')['SelectOption']
+    ATable: typeof import('ant-design-vue/es')['Table']
+    ATextarea: typeof import('ant-design-vue/es')['Textarea']
+    AUpload: typeof import('ant-design-vue/es')['Upload']
+    Block: typeof import('./src/components/block/index.vue')['default']
+    Button: typeof import('./src/components/button/index.vue')['default']
+    Form: typeof import('./src/components/form/index.vue')['default']
+    RouterLink: typeof import('vue-router')['RouterLink']
+    RouterView: typeof import('vue-router')['RouterView']
+    SvgIcon: typeof import('./src/components/svg-icon/index.vue')['default']
+  }
+}

+ 13 - 0
index.html

@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<html lang="en">
+  <head>
+    <meta charset="UTF-8" />
+    <link rel="icon" type="image/svg+xml" href="/vite.svg" />
+    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+    <title>Vite + Vue + TS</title>
+  </head>
+  <body>
+    <div id="app"></div>
+    <script type="module" src="/src/main.ts"></script>
+  </body>
+</html>

+ 43 - 0
package.json

@@ -0,0 +1,43 @@
+{
+  "name": "marking-paper-struct-web",
+  "private": true,
+  "version": "0.0.0",
+  "type": "module",
+  "scripts": {
+    "dev": "vite",
+    "build:test": "vue-tsc --noEmit && vite build --mode test",
+    "build": "vue-tsc --noEmit && vite build --mode production",
+    "preview": "vite preview"
+  },
+  "dependencies": {
+    "@ant-design/icons-vue": "^6.1.0",
+    "ant-design-vue": "^3.2.11",
+    "axios": "^0.27.2",
+    "axios-progress-bar": "^1.2.0",
+    "crypto-js": "^4.1.1",
+    "date-fns": "^2.29.1",
+    "lodash-es": "^4.17.21",
+    "pinia": "^2.0.18",
+    "qrcode": "^1.5.1",
+    "tailwindcss": "^3.1.8",
+    "vue": "^3.2.37",
+    "vue-qrcode": "^2.0.0",
+    "vue-router": "^4.1.3"
+  },
+  "devDependencies": {
+    "@types/crypto-js": "^4.1.1",
+    "@types/lodash-es": "^4.17.6",
+    "@types/node": "^18.7.6",
+    "@vitejs/plugin-vue": "^3.0.3",
+    "@vitejs/plugin-vue-jsx": "^2.0.0",
+    "autoprefixer": "^10.4.8",
+    "less": "^4.1.3",
+    "postcss-pxtorem": "^6.0.0",
+    "typescript": "^4.6.4",
+    "unplugin-vue-components": "^0.22.4",
+    "unplugin-vue-setup-extend-plus": "^0.3.2",
+    "vite": "^3.0.7",
+    "vite-plugin-svg-icons": "^2.0.1",
+    "vue-tsc": "^0.39.5"
+  }
+}

+ 3299 - 0
pnpm-lock.yaml

@@ -0,0 +1,3299 @@
+lockfileVersion: 5.4
+
+specifiers:
+  '@ant-design/icons-vue': ^6.1.0
+  '@types/crypto-js': ^4.1.1
+  '@types/lodash-es': ^4.17.6
+  '@types/node': ^18.7.6
+  '@vitejs/plugin-vue': ^3.0.3
+  '@vitejs/plugin-vue-jsx': ^2.0.0
+  ant-design-vue: ^3.2.11
+  autoprefixer: ^10.4.8
+  axios: ^0.27.2
+  axios-progress-bar: ^1.2.0
+  crypto-js: ^4.1.1
+  date-fns: ^2.29.1
+  less: ^4.1.3
+  lodash-es: ^4.17.21
+  pinia: ^2.0.18
+  postcss-pxtorem: ^6.0.0
+  qrcode: ^1.5.1
+  tailwindcss: ^3.1.8
+  typescript: ^4.6.4
+  unplugin-vue-components: ^0.22.4
+  unplugin-vue-setup-extend-plus: ^0.3.2
+  vite: ^3.0.7
+  vite-plugin-svg-icons: ^2.0.1
+  vue: ^3.2.37
+  vue-qrcode: ^2.0.0
+  vue-router: ^4.1.3
+  vue-tsc: ^0.39.5
+
+dependencies:
+  '@ant-design/icons-vue': 6.1.0_vue@3.2.37
+  ant-design-vue: 3.2.11_vue@3.2.37
+  axios: 0.27.2
+  axios-progress-bar: 1.2.0_axios@0.27.2
+  crypto-js: 4.1.1
+  date-fns: 2.29.1
+  lodash-es: 4.17.21
+  pinia: 2.0.18_j6bzmzd4ujpabbp5objtwxyjp4
+  qrcode: 1.5.1
+  tailwindcss: 3.1.8
+  vue: 3.2.37
+  vue-qrcode: 2.0.0_qrcode@1.5.1+vue@3.2.37
+  vue-router: 4.1.3_vue@3.2.37
+
+devDependencies:
+  '@types/crypto-js': 4.1.1
+  '@types/lodash-es': 4.17.6
+  '@types/node': 18.7.6
+  '@vitejs/plugin-vue': 3.0.3_vite@3.0.8+vue@3.2.37
+  '@vitejs/plugin-vue-jsx': 2.0.0_vite@3.0.8+vue@3.2.37
+  autoprefixer: 10.4.8
+  less: 4.1.3
+  postcss-pxtorem: 6.0.0
+  typescript: 4.7.4
+  unplugin-vue-components: 0.22.4_vite@3.0.8+vue@3.2.37
+  unplugin-vue-setup-extend-plus: 0.3.2_vite@3.0.8
+  vite: 3.0.8_less@4.1.3
+  vite-plugin-svg-icons: 2.0.1_vite@3.0.8
+  vue-tsc: 0.39.5_typescript@4.7.4
+
+packages:
+
+  /@ampproject/remapping/2.2.0:
+    resolution: {integrity: sha512-qRmjj8nj9qmLTQXXmaR1cck3UXSRMPrbsLJAasZpF+t3riI71BXed5ebIOYwQntykeZuhjsdweEc9BxH5Jc26w==}
+    engines: {node: '>=6.0.0'}
+    dependencies:
+      '@jridgewell/gen-mapping': 0.1.1
+      '@jridgewell/trace-mapping': 0.3.15
+    dev: true
+
+  /@ant-design/colors/6.0.0:
+    resolution: {integrity: sha512-qAZRvPzfdWHtfameEGP2Qvuf838NhergR35o+EuVyB5XvSA98xod5r4utvi4TJ3ywmevm290g9nsCG5MryrdWQ==}
+    dependencies:
+      '@ctrl/tinycolor': 3.4.1
+    dev: false
+
+  /@ant-design/icons-svg/4.2.1:
+    resolution: {integrity: sha512-EB0iwlKDGpG93hW8f85CTJTs4SvMX7tt5ceupvhALp1IF44SeUFOMhKUOYqpsoYWQKAOuTRDMqn75rEaKDp0Xw==}
+    dev: false
+
+  /@ant-design/icons-vue/6.1.0_vue@3.2.37:
+    resolution: {integrity: sha512-EX6bYm56V+ZrKN7+3MT/ubDkvJ5rK/O2t380WFRflDcVFgsvl3NLH7Wxeau6R8DbrO5jWR6DSTC3B6gYFp77AA==}
+    peerDependencies:
+      vue: '>=3.0.3'
+    dependencies:
+      '@ant-design/colors': 6.0.0
+      '@ant-design/icons-svg': 4.2.1
+      vue: 3.2.37
+    dev: false
+
+  /@antfu/utils/0.5.2:
+    resolution: {integrity: sha512-CQkeV+oJxUazwjlHD0/3ZD08QWKuGQkhnrKo3e6ly5pd48VUpXbb77q0xMU4+vc2CkJnDS02Eq/M9ugyX20XZA==}
+    dev: true
+
+  /@babel/code-frame/7.18.6:
+    resolution: {integrity: sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q==}
+    engines: {node: '>=6.9.0'}
+    dependencies:
+      '@babel/highlight': 7.18.6
+    dev: true
+
+  /@babel/compat-data/7.18.8:
+    resolution: {integrity: sha512-HSmX4WZPPK3FUxYp7g2T6EyO8j96HlZJlxmKPSh6KAcqwyDrfx7hKjXpAW/0FhFfTJsR0Yt4lAjLI2coMptIHQ==}
+    engines: {node: '>=6.9.0'}
+    dev: true
+
+  /@babel/core/7.18.10:
+    resolution: {integrity: sha512-JQM6k6ENcBFKVtWvLavlvi/mPcpYZ3+R+2EySDEMSMbp7Mn4FexlbbJVrx2R7Ijhr01T8gyqrOaABWIOgxeUyw==}
+    engines: {node: '>=6.9.0'}
+    dependencies:
+      '@ampproject/remapping': 2.2.0
+      '@babel/code-frame': 7.18.6
+      '@babel/generator': 7.18.12
+      '@babel/helper-compilation-targets': 7.18.9_@babel+core@7.18.10
+      '@babel/helper-module-transforms': 7.18.9
+      '@babel/helpers': 7.18.9
+      '@babel/parser': 7.18.11
+      '@babel/template': 7.18.10
+      '@babel/traverse': 7.18.11
+      '@babel/types': 7.18.10
+      convert-source-map: 1.8.0
+      debug: 4.3.4
+      gensync: 1.0.0-beta.2
+      json5: 2.2.1
+      semver: 6.3.0
+    transitivePeerDependencies:
+      - supports-color
+    dev: true
+
+  /@babel/generator/7.18.12:
+    resolution: {integrity: sha512-dfQ8ebCN98SvyL7IxNMCUtZQSq5R7kxgN+r8qYTGDmmSion1hX2C0zq2yo1bsCDhXixokv1SAWTZUMYbO/V5zg==}
+    engines: {node: '>=6.9.0'}
+    dependencies:
+      '@babel/types': 7.18.10
+      '@jridgewell/gen-mapping': 0.3.2
+      jsesc: 2.5.2
+    dev: true
+
+  /@babel/helper-annotate-as-pure/7.18.6:
+    resolution: {integrity: sha512-duORpUiYrEpzKIop6iNbjnwKLAKnJ47csTyRACyEmWj0QdUrm5aqNJGHSSEQSUAvNW0ojX0dOmK9dZduvkfeXA==}
+    engines: {node: '>=6.9.0'}
+    dependencies:
+      '@babel/types': 7.18.10
+    dev: true
+
+  /@babel/helper-compilation-targets/7.18.9_@babel+core@7.18.10:
+    resolution: {integrity: sha512-tzLCyVmqUiFlcFoAPLA/gL9TeYrF61VLNtb+hvkuVaB5SUjW7jcfrglBIX1vUIoT7CLP3bBlIMeyEsIl2eFQNg==}
+    engines: {node: '>=6.9.0'}
+    peerDependencies:
+      '@babel/core': ^7.0.0
+    dependencies:
+      '@babel/compat-data': 7.18.8
+      '@babel/core': 7.18.10
+      '@babel/helper-validator-option': 7.18.6
+      browserslist: 4.21.3
+      semver: 6.3.0
+    dev: true
+
+  /@babel/helper-create-class-features-plugin/7.18.9_@babel+core@7.18.10:
+    resolution: {integrity: sha512-WvypNAYaVh23QcjpMR24CwZY2Nz6hqdOcFdPbNpV56hL5H6KiFheO7Xm1aPdlLQ7d5emYZX7VZwPp9x3z+2opw==}
+    engines: {node: '>=6.9.0'}
+    peerDependencies:
+      '@babel/core': ^7.0.0
+    dependencies:
+      '@babel/core': 7.18.10
+      '@babel/helper-annotate-as-pure': 7.18.6
+      '@babel/helper-environment-visitor': 7.18.9
+      '@babel/helper-function-name': 7.18.9
+      '@babel/helper-member-expression-to-functions': 7.18.9
+      '@babel/helper-optimise-call-expression': 7.18.6
+      '@babel/helper-replace-supers': 7.18.9
+      '@babel/helper-split-export-declaration': 7.18.6
+    transitivePeerDependencies:
+      - supports-color
+    dev: true
+
+  /@babel/helper-environment-visitor/7.18.9:
+    resolution: {integrity: sha512-3r/aACDJ3fhQ/EVgFy0hpj8oHyHpQc+LPtJoY9SzTThAsStm4Ptegq92vqKoE3vD706ZVFWITnMnxucw+S9Ipg==}
+    engines: {node: '>=6.9.0'}
+    dev: true
+
+  /@babel/helper-function-name/7.18.9:
+    resolution: {integrity: sha512-fJgWlZt7nxGksJS9a0XdSaI4XvpExnNIgRP+rVefWh5U7BL8pPuir6SJUmFKRfjWQ51OtWSzwOxhaH/EBWWc0A==}
+    engines: {node: '>=6.9.0'}
+    dependencies:
+      '@babel/template': 7.18.10
+      '@babel/types': 7.18.10
+    dev: true
+
+  /@babel/helper-hoist-variables/7.18.6:
+    resolution: {integrity: sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q==}
+    engines: {node: '>=6.9.0'}
+    dependencies:
+      '@babel/types': 7.18.10
+    dev: true
+
+  /@babel/helper-member-expression-to-functions/7.18.9:
+    resolution: {integrity: sha512-RxifAh2ZoVU67PyKIO4AMi1wTenGfMR/O/ae0CCRqwgBAt5v7xjdtRw7UoSbsreKrQn5t7r89eruK/9JjYHuDg==}
+    engines: {node: '>=6.9.0'}
+    dependencies:
+      '@babel/types': 7.18.10
+    dev: true
+
+  /@babel/helper-module-imports/7.18.6:
+    resolution: {integrity: sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA==}
+    engines: {node: '>=6.9.0'}
+    dependencies:
+      '@babel/types': 7.18.10
+    dev: true
+
+  /@babel/helper-module-transforms/7.18.9:
+    resolution: {integrity: sha512-KYNqY0ICwfv19b31XzvmI/mfcylOzbLtowkw+mfvGPAQ3kfCnMLYbED3YecL5tPd8nAYFQFAd6JHp2LxZk/J1g==}
+    engines: {node: '>=6.9.0'}
+    dependencies:
+      '@babel/helper-environment-visitor': 7.18.9
+      '@babel/helper-module-imports': 7.18.6
+      '@babel/helper-simple-access': 7.18.6
+      '@babel/helper-split-export-declaration': 7.18.6
+      '@babel/helper-validator-identifier': 7.18.6
+      '@babel/template': 7.18.10
+      '@babel/traverse': 7.18.11
+      '@babel/types': 7.18.10
+    transitivePeerDependencies:
+      - supports-color
+    dev: true
+
+  /@babel/helper-optimise-call-expression/7.18.6:
+    resolution: {integrity: sha512-HP59oD9/fEHQkdcbgFCnbmgH5vIQTJbxh2yf+CdM89/glUNnuzr87Q8GIjGEnOktTROemO0Pe0iPAYbqZuOUiA==}
+    engines: {node: '>=6.9.0'}
+    dependencies:
+      '@babel/types': 7.18.10
+    dev: true
+
+  /@babel/helper-plugin-utils/7.18.9:
+    resolution: {integrity: sha512-aBXPT3bmtLryXaoJLyYPXPlSD4p1ld9aYeR+sJNOZjJJGiOpb+fKfh3NkcCu7J54nUJwCERPBExCCpyCOHnu/w==}
+    engines: {node: '>=6.9.0'}
+    dev: true
+
+  /@babel/helper-replace-supers/7.18.9:
+    resolution: {integrity: sha512-dNsWibVI4lNT6HiuOIBr1oyxo40HvIVmbwPUm3XZ7wMh4k2WxrxTqZwSqw/eEmXDS9np0ey5M2bz9tBmO9c+YQ==}
+    engines: {node: '>=6.9.0'}
+    dependencies:
+      '@babel/helper-environment-visitor': 7.18.9
+      '@babel/helper-member-expression-to-functions': 7.18.9
+      '@babel/helper-optimise-call-expression': 7.18.6
+      '@babel/traverse': 7.18.11
+      '@babel/types': 7.18.10
+    transitivePeerDependencies:
+      - supports-color
+    dev: true
+
+  /@babel/helper-simple-access/7.18.6:
+    resolution: {integrity: sha512-iNpIgTgyAvDQpDj76POqg+YEt8fPxx3yaNBg3S30dxNKm2SWfYhD0TGrK/Eu9wHpUW63VQU894TsTg+GLbUa1g==}
+    engines: {node: '>=6.9.0'}
+    dependencies:
+      '@babel/types': 7.18.10
+    dev: true
+
+  /@babel/helper-split-export-declaration/7.18.6:
+    resolution: {integrity: sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA==}
+    engines: {node: '>=6.9.0'}
+    dependencies:
+      '@babel/types': 7.18.10
+    dev: true
+
+  /@babel/helper-string-parser/7.18.10:
+    resolution: {integrity: sha512-XtIfWmeNY3i4t7t4D2t02q50HvqHybPqW2ki1kosnvWCwuCMeo81Jf0gwr85jy/neUdg5XDdeFE/80DXiO+njw==}
+    engines: {node: '>=6.9.0'}
+
+  /@babel/helper-validator-identifier/7.18.6:
+    resolution: {integrity: sha512-MmetCkz9ej86nJQV+sFCxoGGrUbU3q02kgLciwkrt9QqEB7cP39oKEY0PakknEO0Gu20SskMRi+AYZ3b1TpN9g==}
+    engines: {node: '>=6.9.0'}
+
+  /@babel/helper-validator-option/7.18.6:
+    resolution: {integrity: sha512-XO7gESt5ouv/LRJdrVjkShckw6STTaB7l9BrpBaAHDeF5YZT+01PCwmR0SJHnkW6i8OwW/EVWRShfi4j2x+KQw==}
+    engines: {node: '>=6.9.0'}
+    dev: true
+
+  /@babel/helpers/7.18.9:
+    resolution: {integrity: sha512-Jf5a+rbrLoR4eNdUmnFu8cN5eNJT6qdTdOg5IHIzq87WwyRw9PwguLFOWYgktN/60IP4fgDUawJvs7PjQIzELQ==}
+    engines: {node: '>=6.9.0'}
+    dependencies:
+      '@babel/template': 7.18.10
+      '@babel/traverse': 7.18.11
+      '@babel/types': 7.18.10
+    transitivePeerDependencies:
+      - supports-color
+    dev: true
+
+  /@babel/highlight/7.18.6:
+    resolution: {integrity: sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==}
+    engines: {node: '>=6.9.0'}
+    dependencies:
+      '@babel/helper-validator-identifier': 7.18.6
+      chalk: 2.4.2
+      js-tokens: 4.0.0
+    dev: true
+
+  /@babel/parser/7.18.11:
+    resolution: {integrity: sha512-9JKn5vN+hDt0Hdqn1PiJ2guflwP+B6Ga8qbDuoF0PzzVhrzsKIJo8yGqVk6CmMHiMei9w1C1Bp9IMJSIK+HPIQ==}
+    engines: {node: '>=6.0.0'}
+    hasBin: true
+    dependencies:
+      '@babel/types': 7.18.10
+
+  /@babel/plugin-syntax-import-meta/7.10.4_@babel+core@7.18.10:
+    resolution: {integrity: sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==}
+    peerDependencies:
+      '@babel/core': ^7.0.0-0
+    dependencies:
+      '@babel/core': 7.18.10
+      '@babel/helper-plugin-utils': 7.18.9
+    dev: true
+
+  /@babel/plugin-syntax-jsx/7.18.6_@babel+core@7.18.10:
+    resolution: {integrity: sha512-6mmljtAedFGTWu2p/8WIORGwy+61PLgOMPOdazc7YoJ9ZCWUyFy3A6CpPkRKLKD1ToAesxX8KGEViAiLo9N+7Q==}
+    engines: {node: '>=6.9.0'}
+    peerDependencies:
+      '@babel/core': ^7.0.0-0
+    dependencies:
+      '@babel/core': 7.18.10
+      '@babel/helper-plugin-utils': 7.18.9
+    dev: true
+
+  /@babel/plugin-syntax-typescript/7.18.6_@babel+core@7.18.10:
+    resolution: {integrity: sha512-mAWAuq4rvOepWCBid55JuRNvpTNf2UGVgoz4JV0fXEKolsVZDzsa4NqCef758WZJj/GDu0gVGItjKFiClTAmZA==}
+    engines: {node: '>=6.9.0'}
+    peerDependencies:
+      '@babel/core': ^7.0.0-0
+    dependencies:
+      '@babel/core': 7.18.10
+      '@babel/helper-plugin-utils': 7.18.9
+    dev: true
+
+  /@babel/plugin-transform-typescript/7.18.12_@babel+core@7.18.10:
+    resolution: {integrity: sha512-2vjjam0cum0miPkenUbQswKowuxs/NjMwIKEq0zwegRxXk12C9YOF9STXnaUptITOtOJHKHpzvvWYOjbm6tc0w==}
+    engines: {node: '>=6.9.0'}
+    peerDependencies:
+      '@babel/core': ^7.0.0-0
+    dependencies:
+      '@babel/core': 7.18.10
+      '@babel/helper-create-class-features-plugin': 7.18.9_@babel+core@7.18.10
+      '@babel/helper-plugin-utils': 7.18.9
+      '@babel/plugin-syntax-typescript': 7.18.6_@babel+core@7.18.10
+    transitivePeerDependencies:
+      - supports-color
+    dev: true
+
+  /@babel/runtime/7.18.9:
+    resolution: {integrity: sha512-lkqXDcvlFT5rvEjiu6+QYO+1GXrEHRo2LOtS7E4GtX5ESIZOgepqsZBVIj6Pv+a6zqsya9VCgiK1KAK4BvJDAw==}
+    engines: {node: '>=6.9.0'}
+    dependencies:
+      regenerator-runtime: 0.13.9
+    dev: false
+
+  /@babel/template/7.18.10:
+    resolution: {integrity: sha512-TI+rCtooWHr3QJ27kJxfjutghu44DLnasDMwpDqCXVTal9RLp3RSYNh4NdBrRP2cQAoG9A8juOQl6P6oZG4JxA==}
+    engines: {node: '>=6.9.0'}
+    dependencies:
+      '@babel/code-frame': 7.18.6
+      '@babel/parser': 7.18.11
+      '@babel/types': 7.18.10
+    dev: true
+
+  /@babel/traverse/7.18.11:
+    resolution: {integrity: sha512-TG9PiM2R/cWCAy6BPJKeHzNbu4lPzOSZpeMfeNErskGpTJx6trEvFaVCbDvpcxwy49BKWmEPwiW8mrysNiDvIQ==}
+    engines: {node: '>=6.9.0'}
+    dependencies:
+      '@babel/code-frame': 7.18.6
+      '@babel/generator': 7.18.12
+      '@babel/helper-environment-visitor': 7.18.9
+      '@babel/helper-function-name': 7.18.9
+      '@babel/helper-hoist-variables': 7.18.6
+      '@babel/helper-split-export-declaration': 7.18.6
+      '@babel/parser': 7.18.11
+      '@babel/types': 7.18.10
+      debug: 4.3.4
+      globals: 11.12.0
+    transitivePeerDependencies:
+      - supports-color
+    dev: true
+
+  /@babel/types/7.18.10:
+    resolution: {integrity: sha512-MJvnbEiiNkpjo+LknnmRrqbY1GPUUggjv+wQVjetM/AONoupqRALB7I6jGqNUAZsKcRIEu2J6FRFvsczljjsaQ==}
+    engines: {node: '>=6.9.0'}
+    dependencies:
+      '@babel/helper-string-parser': 7.18.10
+      '@babel/helper-validator-identifier': 7.18.6
+      to-fast-properties: 2.0.0
+
+  /@ctrl/tinycolor/3.4.1:
+    resolution: {integrity: sha512-ej5oVy6lykXsvieQtqZxCOaLT+xD4+QNarq78cIYISHmZXshCvROLudpQN3lfL8G0NL7plMSSK+zlyvCaIJ4Iw==}
+    engines: {node: '>=10'}
+    dev: false
+
+  /@esbuild/linux-loong64/0.14.54:
+    resolution: {integrity: sha512-bZBrLAIX1kpWelV0XemxBZllyRmM6vgFQQG2GdNb+r3Fkp0FOh1NJSvekXDs7jq70k4euu1cryLMfU+mTXlEpw==}
+    engines: {node: '>=12'}
+    cpu: [loong64]
+    os: [linux]
+    requiresBuild: true
+    dev: true
+    optional: true
+
+  /@jridgewell/gen-mapping/0.1.1:
+    resolution: {integrity: sha512-sQXCasFk+U8lWYEe66WxRDOE9PjVz4vSM51fTu3Hw+ClTpUSQb718772vH3pyS5pShp6lvQM7SxgIDXXXmOX7w==}
+    engines: {node: '>=6.0.0'}
+    dependencies:
+      '@jridgewell/set-array': 1.1.2
+      '@jridgewell/sourcemap-codec': 1.4.14
+    dev: true
+
+  /@jridgewell/gen-mapping/0.3.2:
+    resolution: {integrity: sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==}
+    engines: {node: '>=6.0.0'}
+    dependencies:
+      '@jridgewell/set-array': 1.1.2
+      '@jridgewell/sourcemap-codec': 1.4.14
+      '@jridgewell/trace-mapping': 0.3.15
+    dev: true
+
+  /@jridgewell/resolve-uri/3.1.0:
+    resolution: {integrity: sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==}
+    engines: {node: '>=6.0.0'}
+    dev: true
+
+  /@jridgewell/set-array/1.1.2:
+    resolution: {integrity: sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==}
+    engines: {node: '>=6.0.0'}
+    dev: true
+
+  /@jridgewell/sourcemap-codec/1.4.14:
+    resolution: {integrity: sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==}
+    dev: true
+
+  /@jridgewell/trace-mapping/0.3.15:
+    resolution: {integrity: sha512-oWZNOULl+UbhsgB51uuZzglikfIKSUBO/M9W2OfEjn7cmqoAiCgmv9lyACTUacZwBz0ITnJ2NqjU8Tx0DHL88g==}
+    dependencies:
+      '@jridgewell/resolve-uri': 3.1.0
+      '@jridgewell/sourcemap-codec': 1.4.14
+    dev: true
+
+  /@nodelib/fs.scandir/2.1.5:
+    resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==}
+    engines: {node: '>= 8'}
+    dependencies:
+      '@nodelib/fs.stat': 2.0.5
+      run-parallel: 1.2.0
+
+  /@nodelib/fs.stat/2.0.5:
+    resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==}
+    engines: {node: '>= 8'}
+
+  /@nodelib/fs.walk/1.2.8:
+    resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==}
+    engines: {node: '>= 8'}
+    dependencies:
+      '@nodelib/fs.scandir': 2.1.5
+      fastq: 1.13.0
+
+  /@rollup/pluginutils/4.2.1:
+    resolution: {integrity: sha512-iKnFXr7NkdZAIHiIWE+BX5ULi/ucVFYWD6TbAV+rZctiRTY2PL6tsIKhoIOaoskiWAkgu+VsbXgUVDNLHf+InQ==}
+    engines: {node: '>= 8.0.0'}
+    dependencies:
+      estree-walker: 2.0.2
+      picomatch: 2.3.1
+    dev: true
+
+  /@simonwep/pickr/1.8.2:
+    resolution: {integrity: sha512-/l5w8BIkrpP6n1xsetx9MWPWlU6OblN5YgZZphxan0Tq4BByTCETL6lyIeY8lagalS2Nbt4F2W034KHLIiunKA==}
+    dependencies:
+      core-js: 3.24.1
+      nanopop: 2.1.0
+    dev: false
+
+  /@trysound/sax/0.2.0:
+    resolution: {integrity: sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA==}
+    engines: {node: '>=10.13.0'}
+    dev: true
+
+  /@types/crypto-js/4.1.1:
+    resolution: {integrity: sha512-BG7fQKZ689HIoc5h+6D2Dgq1fABRa0RbBWKBd9SP/MVRVXROflpm5fhwyATX5duFmbStzyzyycPB8qUYKDH3NA==}
+    dev: true
+
+  /@types/lodash-es/4.17.6:
+    resolution: {integrity: sha512-R+zTeVUKDdfoRxpAryaQNRKk3105Rrgx2CFRClIgRGaqDTdjsm8h6IYA8ir584W3ePzkZfst5xIgDwYrlh9HLg==}
+    dependencies:
+      '@types/lodash': 4.14.183
+    dev: true
+
+  /@types/lodash/4.14.183:
+    resolution: {integrity: sha512-UXavyuxzXKMqJPEpFPri6Ku5F9af6ZJXUneHhvQJxavrEjuHkFp2YnDWHcxJiG7hk8ZkWqjcyNeW1s/smZv5cw==}
+    dev: true
+
+  /@types/node/18.7.6:
+    resolution: {integrity: sha512-EdxgKRXgYsNITy5mjjXjVE/CS8YENSdhiagGrLqjG0pvA2owgJ6i4l7wy/PFZGC0B1/H20lWKN7ONVDNYDZm7A==}
+    dev: true
+
+  /@types/svgo/2.6.4:
+    resolution: {integrity: sha512-l4cmyPEckf8moNYHdJ+4wkHvFxjyW6ulm9l4YGaOxeyBWPhBOT0gvni1InpFPdzx1dKf/2s62qGITwxNWnPQng==}
+    dependencies:
+      '@types/node': 18.7.6
+    dev: true
+
+  /@vitejs/plugin-vue-jsx/2.0.0_vite@3.0.8+vue@3.2.37:
+    resolution: {integrity: sha512-WF9ApZ/ivyyW3volQfu0Td0KNPhcccYEaRNzNY1NxRLVJQLSX0nFqquv3e2g7MF74p1XZK4bGtDL2y5i5O5+1A==}
+    engines: {node: '>=14.18.0'}
+    peerDependencies:
+      vite: ^3.0.0
+      vue: ^3.0.0
+    dependencies:
+      '@babel/core': 7.18.10
+      '@babel/plugin-syntax-import-meta': 7.10.4_@babel+core@7.18.10
+      '@babel/plugin-transform-typescript': 7.18.12_@babel+core@7.18.10
+      '@vue/babel-plugin-jsx': 1.1.1_@babel+core@7.18.10
+      vite: 3.0.8_less@4.1.3
+      vue: 3.2.37
+    transitivePeerDependencies:
+      - supports-color
+    dev: true
+
+  /@vitejs/plugin-vue/3.0.3_vite@3.0.8+vue@3.2.37:
+    resolution: {integrity: sha512-U4zNBlz9mg+TA+i+5QPc3N5lQvdUXENZLO2h0Wdzp56gI1MWhqJOv+6R+d4kOzoaSSq6TnGPBdZAXKOe4lXy6g==}
+    engines: {node: ^14.18.0 || >=16.0.0}
+    peerDependencies:
+      vite: ^3.0.0
+      vue: ^3.2.25
+    dependencies:
+      vite: 3.0.8_less@4.1.3
+      vue: 3.2.37
+    dev: true
+
+  /@volar/code-gen/0.39.5:
+    resolution: {integrity: sha512-vQr5VoCH8T2NHmqLc/AA1/4F8l41WB+24+I+VjxBaev/Hmwjye9K0GlmMHAOl84WB3hWGOqpHaPX6JkqzRNjJg==}
+    dependencies:
+      '@volar/source-map': 0.39.5
+    dev: true
+
+  /@volar/source-map/0.39.5:
+    resolution: {integrity: sha512-IVOX+v++Sr5Kok4/cLbDJp2vf1ia1rChpV7adgcnMle6uORBuGFEur234UzamK0iHRCcfFFRz7z+hSPf2CO23Q==}
+    dev: true
+
+  /@volar/typescript-faster/0.39.5:
+    resolution: {integrity: sha512-IzLqlxefmKkjNKXC/8aFiqPcTqnj6RG31D2f9cIWxmW9pvUYJxLED+y9phnOxNxq0OmeRtQ3Pfmvu85tUBoZsQ==}
+    dependencies:
+      semver: 7.3.7
+    dev: true
+
+  /@volar/vue-code-gen/0.39.5:
+    resolution: {integrity: sha512-y+QUV9MuuasiIuRoGKQl+gMhDaAX6XNhckAyJCvD1FZ8f2eJuPY2VtoFxmu/Z2bGWBdtUW/g98jaeKJ+j3wwOw==}
+    dependencies:
+      '@volar/code-gen': 0.39.5
+      '@volar/source-map': 0.39.5
+      '@vue/compiler-core': 3.2.37
+      '@vue/compiler-dom': 3.2.37
+      '@vue/shared': 3.2.37
+    dev: true
+
+  /@volar/vue-language-core/0.39.5:
+    resolution: {integrity: sha512-m+e1tYuL/WRPhSeC7hZ0NuSwHsfnnGJVxCBHLaP7jR0f6xcC0DAegP3QF+gfu9ZJFPGznpZYFKadngMjuhQS9Q==}
+    dependencies:
+      '@volar/code-gen': 0.39.5
+      '@volar/source-map': 0.39.5
+      '@volar/vue-code-gen': 0.39.5
+      '@vue/compiler-sfc': 3.2.37
+      '@vue/reactivity': 3.2.37
+    dev: true
+
+  /@volar/vue-typescript/0.39.5:
+    resolution: {integrity: sha512-ckhWD1xOi0OMr702XVkv/Npsb9FKAp5gvhxyLv0QqWekPdSo04t4KrZfwosJLGERIEcyr50SuB7HqBp8ndQmzA==}
+    dependencies:
+      '@volar/code-gen': 0.39.5
+      '@volar/typescript-faster': 0.39.5
+      '@volar/vue-language-core': 0.39.5
+    dev: true
+
+  /@vue/babel-helper-vue-transform-on/1.0.2:
+    resolution: {integrity: sha512-hz4R8tS5jMn8lDq6iD+yWL6XNB699pGIVLk7WSJnn1dbpjaazsjZQkieJoRX6gW5zpYSCFqQ7jUquPNY65tQYA==}
+    dev: true
+
+  /@vue/babel-plugin-jsx/1.1.1_@babel+core@7.18.10:
+    resolution: {integrity: sha512-j2uVfZjnB5+zkcbc/zsOc0fSNGCMMjaEXP52wdwdIfn0qjFfEYpYZBFKFg+HHnQeJCVrjOeO0YxgaL7DMrym9w==}
+    dependencies:
+      '@babel/helper-module-imports': 7.18.6
+      '@babel/plugin-syntax-jsx': 7.18.6_@babel+core@7.18.10
+      '@babel/template': 7.18.10
+      '@babel/traverse': 7.18.11
+      '@babel/types': 7.18.10
+      '@vue/babel-helper-vue-transform-on': 1.0.2
+      camelcase: 6.3.0
+      html-tags: 3.2.0
+      svg-tags: 1.0.0
+    transitivePeerDependencies:
+      - '@babel/core'
+      - supports-color
+    dev: true
+
+  /@vue/compiler-core/3.2.37:
+    resolution: {integrity: sha512-81KhEjo7YAOh0vQJoSmAD68wLfYqJvoiD4ulyedzF+OEk/bk6/hx3fTNVfuzugIIaTrOx4PGx6pAiBRe5e9Zmg==}
+    dependencies:
+      '@babel/parser': 7.18.11
+      '@vue/shared': 3.2.37
+      estree-walker: 2.0.2
+      source-map: 0.6.1
+
+  /@vue/compiler-dom/3.2.37:
+    resolution: {integrity: sha512-yxJLH167fucHKxaqXpYk7x8z7mMEnXOw3G2q62FTkmsvNxu4FQSu5+3UMb+L7fjKa26DEzhrmCxAgFLLIzVfqQ==}
+    dependencies:
+      '@vue/compiler-core': 3.2.37
+      '@vue/shared': 3.2.37
+
+  /@vue/compiler-sfc/3.2.37:
+    resolution: {integrity: sha512-+7i/2+9LYlpqDv+KTtWhOZH+pa8/HnX/905MdVmAcI/mPQOBwkHHIzrsEsucyOIZQYMkXUiTkmZq5am/NyXKkg==}
+    dependencies:
+      '@babel/parser': 7.18.11
+      '@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.9
+      postcss: 8.4.16
+      source-map: 0.6.1
+
+  /@vue/compiler-ssr/3.2.37:
+    resolution: {integrity: sha512-7mQJD7HdXxQjktmsWp/J67lThEIcxLemz1Vb5I6rYJHR5vI+lON3nPGOH3ubmbvYGt8xEUaAr1j7/tIFWiEOqw==}
+    dependencies:
+      '@vue/compiler-dom': 3.2.37
+      '@vue/shared': 3.2.37
+
+  /@vue/devtools-api/6.2.1:
+    resolution: {integrity: sha512-OEgAMeQXvCoJ+1x8WyQuVZzFo0wcyCmUR3baRVLmKBo1LmYZWMlRiXlux5jd0fqVJu6PfDbOrZItVqUEzLobeQ==}
+    dev: false
+
+  /@vue/reactivity-transform/3.2.37:
+    resolution: {integrity: sha512-IWopkKEb+8qpu/1eMKVeXrK0NLw9HicGviJzhJDEyfxTR9e1WtpnnbYkJWurX6WwoFP0sz10xQg8yL8lgskAZg==}
+    dependencies:
+      '@babel/parser': 7.18.11
+      '@vue/compiler-core': 3.2.37
+      '@vue/shared': 3.2.37
+      estree-walker: 2.0.2
+      magic-string: 0.25.9
+
+  /@vue/reactivity/3.2.37:
+    resolution: {integrity: sha512-/7WRafBOshOc6m3F7plwzPeCu/RCVv9uMpOwa/5PiY1Zz+WLVRWiy0MYKwmg19KBdGtFWsmZ4cD+LOdVPcs52A==}
+    dependencies:
+      '@vue/shared': 3.2.37
+
+  /@vue/runtime-core/3.2.37:
+    resolution: {integrity: sha512-JPcd9kFyEdXLl/i0ClS7lwgcs0QpUAWj+SKX2ZC3ANKi1U4DOtiEr6cRqFXsPwY5u1L9fAjkinIdB8Rz3FoYNQ==}
+    dependencies:
+      '@vue/reactivity': 3.2.37
+      '@vue/shared': 3.2.37
+
+  /@vue/runtime-dom/3.2.37:
+    resolution: {integrity: sha512-HimKdh9BepShW6YozwRKAYjYQWg9mQn63RGEiSswMbW+ssIht1MILYlVGkAGGQbkhSh31PCdoUcfiu4apXJoPw==}
+    dependencies:
+      '@vue/runtime-core': 3.2.37
+      '@vue/shared': 3.2.37
+      csstype: 2.6.20
+
+  /@vue/server-renderer/3.2.37_vue@3.2.37:
+    resolution: {integrity: sha512-kLITEJvaYgZQ2h47hIzPh2K3jG8c1zCVbp/o/bzQOyvzaKiCquKS7AaioPI28GNxIsE/zSx+EwWYsNxDCX95MA==}
+    peerDependencies:
+      vue: 3.2.37
+    dependencies:
+      '@vue/compiler-ssr': 3.2.37
+      '@vue/shared': 3.2.37
+      vue: 3.2.37
+
+  /@vue/shared/3.2.37:
+    resolution: {integrity: sha512-4rSJemR2NQIo9Klm1vabqWjD8rs/ZaJSzMxkMNeJS6lHiUjjUeYFbooN19NgFjztubEKh3WlZUeOLVdbbUWHsw==}
+
+  /acorn-node/1.8.2:
+    resolution: {integrity: sha512-8mt+fslDufLYntIoPAaIMUe/lrbrehIiwmR3t2k9LljIzoigEPF27eLk2hy8zSGzmR/ogr7zbRKINMo1u0yh5A==}
+    dependencies:
+      acorn: 7.4.1
+      acorn-walk: 7.2.0
+      xtend: 4.0.2
+    dev: false
+
+  /acorn-walk/7.2.0:
+    resolution: {integrity: sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==}
+    engines: {node: '>=0.4.0'}
+    dev: false
+
+  /acorn/7.4.1:
+    resolution: {integrity: sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==}
+    engines: {node: '>=0.4.0'}
+    hasBin: true
+    dev: false
+
+  /acorn/8.8.0:
+    resolution: {integrity: sha512-QOxyigPVrpZ2GXT+PFyZTl6TtOFc5egxHIP9IlQ+RbupQuX4RkT/Bee4/kQuC02Xkzg84JcT7oLYtDIQxp+v7w==}
+    engines: {node: '>=0.4.0'}
+    hasBin: true
+    dev: true
+
+  /ansi-regex/2.1.1:
+    resolution: {integrity: sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==}
+    engines: {node: '>=0.10.0'}
+    dev: true
+
+  /ansi-regex/5.0.1:
+    resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==}
+    engines: {node: '>=8'}
+    dev: false
+
+  /ansi-styles/2.2.1:
+    resolution: {integrity: sha512-kmCevFghRiWM7HB5zTPULl4r9bVFSWjz62MhqizDGUrq2NWuNMQyuv4tHHoKJHs69M/MF64lEcHdYIocrdWQYA==}
+    engines: {node: '>=0.10.0'}
+    dev: true
+
+  /ansi-styles/3.2.1:
+    resolution: {integrity: sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==}
+    engines: {node: '>=4'}
+    dependencies:
+      color-convert: 1.9.3
+    dev: true
+
+  /ansi-styles/4.3.0:
+    resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==}
+    engines: {node: '>=8'}
+    dependencies:
+      color-convert: 2.0.1
+    dev: false
+
+  /ant-design-vue/3.2.11_vue@3.2.37:
+    resolution: {integrity: sha512-QKCAcOY5EJF0PepiVGA4X5PzUetYUvG5qALmA+2TON40pc2+brOEiVTwr3kjF9N+f7q4MpyiLPu4pIErwoajOQ==}
+    engines: {node: '>=12.22.0'}
+    peerDependencies:
+      vue: '>=3.2.0'
+    dependencies:
+      '@ant-design/colors': 6.0.0
+      '@ant-design/icons-vue': 6.1.0_vue@3.2.37
+      '@babel/runtime': 7.18.9
+      '@ctrl/tinycolor': 3.4.1
+      '@simonwep/pickr': 1.8.2
+      array-tree-filter: 2.1.0
+      async-validator: 4.2.5
+      dayjs: 1.11.5
+      dom-align: 1.12.3
+      dom-scroll-into-view: 2.0.1
+      lodash: 4.17.21
+      lodash-es: 4.17.21
+      resize-observer-polyfill: 1.5.1
+      scroll-into-view-if-needed: 2.2.29
+      shallow-equal: 1.2.1
+      vue: 3.2.37
+      vue-types: 3.0.2_vue@3.2.37
+      warning: 4.0.3
+    dev: false
+
+  /anymatch/3.1.2:
+    resolution: {integrity: sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==}
+    engines: {node: '>= 8'}
+    dependencies:
+      normalize-path: 3.0.0
+      picomatch: 2.3.1
+
+  /arg/5.0.2:
+    resolution: {integrity: sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==}
+    dev: false
+
+  /arr-diff/4.0.0:
+    resolution: {integrity: sha512-YVIQ82gZPGBebQV/a8dar4AitzCQs0jjXwMPZllpXMaGjXPYVUawSxQrRsjhjupyVxEvbHgUmIhKVlND+j02kA==}
+    engines: {node: '>=0.10.0'}
+    dev: true
+
+  /arr-flatten/1.1.0:
+    resolution: {integrity: sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==}
+    engines: {node: '>=0.10.0'}
+    dev: true
+
+  /arr-union/3.1.0:
+    resolution: {integrity: sha512-sKpyeERZ02v1FeCZT8lrfJq5u6goHCtpTAzPwJYe7c8SPFOboNjNg1vz2L4VTn9T4PQxEx13TbXLmYUcS6Ug7Q==}
+    engines: {node: '>=0.10.0'}
+    dev: true
+
+  /array-tree-filter/2.1.0:
+    resolution: {integrity: sha512-4ROwICNlNw/Hqa9v+rk5h22KjmzB1JGTMVKP2AKJBOCgb0yL0ASf0+YvCcLNNwquOHNX48jkeZIJ3a+oOQqKcw==}
+    dev: false
+
+  /array-unique/0.3.2:
+    resolution: {integrity: sha512-SleRWjh9JUud2wH1hPs9rZBZ33H6T9HOiL0uwGnGx9FpE6wKGyfWugmbkEOIs6qWrZhg0LWeLziLrEwQJhs5mQ==}
+    engines: {node: '>=0.10.0'}
+    dev: true
+
+  /assign-symbols/1.0.0:
+    resolution: {integrity: sha512-Q+JC7Whu8HhmTdBph/Tq59IoRtoy6KAm5zzPv00WdujX82lbAL8K7WVjne7vdCsAmbF4AYaDOPyO3k0kl8qIrw==}
+    engines: {node: '>=0.10.0'}
+    dev: true
+
+  /async-validator/4.2.5:
+    resolution: {integrity: sha512-7HhHjtERjqlNbZtqNqy2rckN/SpOOlmDliet+lP7k+eKZEjPk3DgyeU9lIXLdeLz0uBbbVp+9Qdow9wJWgwwfg==}
+    dev: false
+
+  /asynckit/0.4.0:
+    resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==}
+    dev: false
+
+  /atob/2.1.2:
+    resolution: {integrity: sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==}
+    engines: {node: '>= 4.5.0'}
+    hasBin: true
+    dev: true
+
+  /autoprefixer/10.4.8:
+    resolution: {integrity: sha512-75Jr6Q/XpTqEf6D2ltS5uMewJIx5irCU1oBYJrWjFenq/m12WRRrz6g15L1EIoYvPLXTbEry7rDOwrcYNj77xw==}
+    engines: {node: ^10 || ^12 || >=14}
+    hasBin: true
+    peerDependencies:
+      postcss: ^8.1.0
+    dependencies:
+      browserslist: 4.21.3
+      caniuse-lite: 1.0.30001377
+      fraction.js: 4.2.0
+      normalize-range: 0.1.2
+      picocolors: 1.0.0
+      postcss-value-parser: 4.2.0
+    dev: true
+
+  /axios-progress-bar/1.2.0_axios@0.27.2:
+    resolution: {integrity: sha512-PEgWb/b2SMyHnKJ/cxA46OdCuNeVlo8eqL0HxXPtz+6G/Jtpyo49icPbW+jpO1wUeDEjbqpseMoCyWxESxf5pA==}
+    peerDependencies:
+      axios: 0.x
+    dependencies:
+      axios: 0.27.2
+    dev: false
+
+  /axios/0.27.2:
+    resolution: {integrity: sha512-t+yRIyySRTp/wua5xEr+z1q60QmLq8ABsS5O9Me1AsE5dfKqgnCFzwiCZZ/cGNd1lq4/7akDWMxdhVlucjmnOQ==}
+    dependencies:
+      follow-redirects: 1.15.1
+      form-data: 4.0.0
+    transitivePeerDependencies:
+      - debug
+    dev: false
+
+  /balanced-match/1.0.2:
+    resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
+    dev: true
+
+  /base/0.11.2:
+    resolution: {integrity: sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==}
+    engines: {node: '>=0.10.0'}
+    dependencies:
+      cache-base: 1.0.1
+      class-utils: 0.3.6
+      component-emitter: 1.3.0
+      define-property: 1.0.0
+      isobject: 3.0.1
+      mixin-deep: 1.3.2
+      pascalcase: 0.1.1
+    dev: true
+
+  /big.js/5.2.2:
+    resolution: {integrity: sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==}
+    dev: true
+
+  /binary-extensions/2.2.0:
+    resolution: {integrity: sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==}
+    engines: {node: '>=8'}
+
+  /bluebird/3.7.2:
+    resolution: {integrity: sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==}
+    dev: true
+
+  /boolbase/1.0.0:
+    resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==}
+    dev: true
+
+  /brace-expansion/2.0.1:
+    resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==}
+    dependencies:
+      balanced-match: 1.0.2
+    dev: true
+
+  /braces/2.3.2:
+    resolution: {integrity: sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==}
+    engines: {node: '>=0.10.0'}
+    dependencies:
+      arr-flatten: 1.1.0
+      array-unique: 0.3.2
+      extend-shallow: 2.0.1
+      fill-range: 4.0.0
+      isobject: 3.0.1
+      repeat-element: 1.1.4
+      snapdragon: 0.8.2
+      snapdragon-node: 2.1.1
+      split-string: 3.1.0
+      to-regex: 3.0.2
+    transitivePeerDependencies:
+      - supports-color
+    dev: true
+
+  /braces/3.0.2:
+    resolution: {integrity: sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==}
+    engines: {node: '>=8'}
+    dependencies:
+      fill-range: 7.0.1
+
+  /browserslist/4.21.3:
+    resolution: {integrity: sha512-898rgRXLAyRkM1GryrrBHGkqA5hlpkV5MhtZwg9QXeiyLUYs2k00Un05aX5l2/yJIOObYKOpS2JNo8nJDE7fWQ==}
+    engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7}
+    hasBin: true
+    dependencies:
+      caniuse-lite: 1.0.30001377
+      electron-to-chromium: 1.4.222
+      node-releases: 2.0.6
+      update-browserslist-db: 1.0.5_browserslist@4.21.3
+    dev: true
+
+  /cache-base/1.0.1:
+    resolution: {integrity: sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==}
+    engines: {node: '>=0.10.0'}
+    dependencies:
+      collection-visit: 1.0.0
+      component-emitter: 1.3.0
+      get-value: 2.0.6
+      has-value: 1.0.0
+      isobject: 3.0.1
+      set-value: 2.0.1
+      to-object-path: 0.3.0
+      union-value: 1.0.1
+      unset-value: 1.0.0
+    dev: true
+
+  /camelcase-css/2.0.1:
+    resolution: {integrity: sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==}
+    engines: {node: '>= 6'}
+    dev: false
+
+  /camelcase/5.3.1:
+    resolution: {integrity: sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==}
+    engines: {node: '>=6'}
+    dev: false
+
+  /camelcase/6.3.0:
+    resolution: {integrity: sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==}
+    engines: {node: '>=10'}
+    dev: true
+
+  /caniuse-lite/1.0.30001377:
+    resolution: {integrity: sha512-I5XeHI1x/mRSGl96LFOaSk528LA/yZG3m3iQgImGujjO8gotd/DL8QaI1R1h1dg5ATeI2jqPblMpKq4Tr5iKfQ==}
+    dev: true
+
+  /chalk/1.1.3:
+    resolution: {integrity: sha512-U3lRVLMSlsCfjqYPbLyVv11M9CPW4I728d6TCKMAOJueEeB9/8o+eSsMnxPJD+Q+K909sdESg7C+tIkoH6on1A==}
+    engines: {node: '>=0.10.0'}
+    dependencies:
+      ansi-styles: 2.2.1
+      escape-string-regexp: 1.0.5
+      has-ansi: 2.0.0
+      strip-ansi: 3.0.1
+      supports-color: 2.0.0
+    dev: true
+
+  /chalk/2.4.2:
+    resolution: {integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==}
+    engines: {node: '>=4'}
+    dependencies:
+      ansi-styles: 3.2.1
+      escape-string-regexp: 1.0.5
+      supports-color: 5.5.0
+    dev: true
+
+  /chokidar/3.5.3:
+    resolution: {integrity: sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==}
+    engines: {node: '>= 8.10.0'}
+    dependencies:
+      anymatch: 3.1.2
+      braces: 3.0.2
+      glob-parent: 5.1.2
+      is-binary-path: 2.1.0
+      is-glob: 4.0.3
+      normalize-path: 3.0.0
+      readdirp: 3.6.0
+    optionalDependencies:
+      fsevents: 2.3.2
+
+  /class-utils/0.3.6:
+    resolution: {integrity: sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==}
+    engines: {node: '>=0.10.0'}
+    dependencies:
+      arr-union: 3.1.0
+      define-property: 0.2.5
+      isobject: 3.0.1
+      static-extend: 0.1.2
+    dev: true
+
+  /cliui/6.0.0:
+    resolution: {integrity: sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==}
+    dependencies:
+      string-width: 4.2.3
+      strip-ansi: 6.0.1
+      wrap-ansi: 6.2.0
+    dev: false
+
+  /clone/2.1.2:
+    resolution: {integrity: sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w==}
+    engines: {node: '>=0.8'}
+    dev: true
+
+  /collection-visit/1.0.0:
+    resolution: {integrity: sha512-lNkKvzEeMBBjUGHZ+q6z9pSJla0KWAQPvtzhEV9+iGyQYG+pBpl7xKDhxoNSOZH2hhv0v5k0y2yAM4o4SjoSkw==}
+    engines: {node: '>=0.10.0'}
+    dependencies:
+      map-visit: 1.0.0
+      object-visit: 1.0.1
+    dev: true
+
+  /color-convert/1.9.3:
+    resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==}
+    dependencies:
+      color-name: 1.1.3
+    dev: true
+
+  /color-convert/2.0.1:
+    resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==}
+    engines: {node: '>=7.0.0'}
+    dependencies:
+      color-name: 1.1.4
+    dev: false
+
+  /color-name/1.1.3:
+    resolution: {integrity: sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==}
+    dev: true
+
+  /color-name/1.1.4:
+    resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==}
+    dev: false
+
+  /combined-stream/1.0.8:
+    resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==}
+    engines: {node: '>= 0.8'}
+    dependencies:
+      delayed-stream: 1.0.0
+    dev: false
+
+  /commander/7.2.0:
+    resolution: {integrity: sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==}
+    engines: {node: '>= 10'}
+    dev: true
+
+  /component-emitter/1.3.0:
+    resolution: {integrity: sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==}
+    dev: true
+
+  /compute-scroll-into-view/1.0.17:
+    resolution: {integrity: sha512-j4dx+Fb0URmzbwwMUrhqWM2BEWHdFGx+qZ9qqASHRPqvTYdqvWnHg0H1hIbcyLnvgnoNAVMlwkepyqM3DaIFUg==}
+    dev: false
+
+  /convert-source-map/1.8.0:
+    resolution: {integrity: sha512-+OQdjP49zViI/6i7nIJpA8rAl4sV/JdPfU9nZs3VqOwGIgizICvuN2ru6fMd+4llL0tar18UYJXfZ/TWtmhUjA==}
+    dependencies:
+      safe-buffer: 5.1.2
+    dev: true
+
+  /copy-anything/2.0.6:
+    resolution: {integrity: sha512-1j20GZTsvKNkc4BY3NpMOM8tt///wY3FpIzozTOFO2ffuZcV61nojHXVKIy3WM+7ADCy5FVhdZYHYDdgTU0yJw==}
+    dependencies:
+      is-what: 3.14.1
+    dev: true
+
+  /copy-descriptor/0.1.1:
+    resolution: {integrity: sha512-XgZ0pFcakEUlbwQEVNg3+QAis1FyTL3Qel9FYy8pSkQqoG3PNoT0bOCQtOXcOkur21r2Eq2kI+IE+gsmAEVlYw==}
+    engines: {node: '>=0.10.0'}
+    dev: true
+
+  /core-js/3.24.1:
+    resolution: {integrity: sha512-0QTBSYSUZ6Gq21utGzkfITDylE8jWC9Ne1D2MrhvlsZBI1x39OdDIVbzSqtgMndIy6BlHxBXpMGqzZmnztg2rg==}
+    requiresBuild: true
+    dev: false
+
+  /cors/2.8.5:
+    resolution: {integrity: sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==}
+    engines: {node: '>= 0.10'}
+    dependencies:
+      object-assign: 4.1.1
+      vary: 1.1.2
+    dev: true
+
+  /crypto-js/4.1.1:
+    resolution: {integrity: sha512-o2JlM7ydqd3Qk9CA0L4NL6mTzU2sdx96a+oOfPu8Mkl/PK51vSyoi8/rQ8NknZtk44vq15lmhAj9CIAGwgeWKw==}
+    dev: false
+
+  /css-select/4.3.0:
+    resolution: {integrity: sha512-wPpOYtnsVontu2mODhA19JrqWxNsfdatRKd64kmpRbQgh1KtItko5sTnEpPdpSaJszTOhEMlF/RPz28qj4HqhQ==}
+    dependencies:
+      boolbase: 1.0.0
+      css-what: 6.1.0
+      domhandler: 4.3.1
+      domutils: 2.8.0
+      nth-check: 2.1.1
+    dev: true
+
+  /css-tree/1.1.3:
+    resolution: {integrity: sha512-tRpdppF7TRazZrjJ6v3stzv93qxRcSsFmW6cX0Zm2NVKpxE1WV1HblnghVv9TreireHkqI/VDEsfolRF1p6y7Q==}
+    engines: {node: '>=8.0.0'}
+    dependencies:
+      mdn-data: 2.0.14
+      source-map: 0.6.1
+    dev: true
+
+  /css-what/6.1.0:
+    resolution: {integrity: sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==}
+    engines: {node: '>= 6'}
+    dev: true
+
+  /cssesc/3.0.0:
+    resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==}
+    engines: {node: '>=4'}
+    hasBin: true
+    dev: false
+
+  /csso/4.2.0:
+    resolution: {integrity: sha512-wvlcdIbf6pwKEk7vHj8/Bkc0B4ylXZruLvOgs9doS5eOsOpuodOV2zJChSpkp+pRpYQLQMeF04nr3Z68Sta9jA==}
+    engines: {node: '>=8.0.0'}
+    dependencies:
+      css-tree: 1.1.3
+    dev: true
+
+  /csstype/2.6.20:
+    resolution: {integrity: sha512-/WwNkdXfckNgw6S5R125rrW8ez139lBHWouiBvX8dfMFtcn6V81REDqnH7+CRpRipfYlyU1CmOnOxrmGcFOjeA==}
+
+  /date-fns/2.29.1:
+    resolution: {integrity: sha512-dlLD5rKaKxpFdnjrs+5azHDFOPEu4ANy/LTh04A1DTzMM7qoajmKCBc8pkKRFT41CNzw+4gQh79X5C+Jq27HAw==}
+    engines: {node: '>=0.11'}
+    dev: false
+
+  /dayjs/1.11.5:
+    resolution: {integrity: sha512-CAdX5Q3YW3Gclyo5Vpqkgpj8fSdLQcRuzfX6mC6Phy0nfJ0eGYOeS7m4mt2plDWLAtA4TqTakvbboHvUxfe4iA==}
+    dev: false
+
+  /debug/2.6.9:
+    resolution: {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==}
+    peerDependencies:
+      supports-color: '*'
+    peerDependenciesMeta:
+      supports-color:
+        optional: true
+    dependencies:
+      ms: 2.0.0
+    dev: true
+
+  /debug/3.2.7:
+    resolution: {integrity: sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==}
+    peerDependencies:
+      supports-color: '*'
+    peerDependenciesMeta:
+      supports-color:
+        optional: true
+    dependencies:
+      ms: 2.1.3
+    dev: true
+    optional: true
+
+  /debug/4.3.4:
+    resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==}
+    engines: {node: '>=6.0'}
+    peerDependencies:
+      supports-color: '*'
+    peerDependenciesMeta:
+      supports-color:
+        optional: true
+    dependencies:
+      ms: 2.1.2
+    dev: true
+
+  /decamelize/1.2.0:
+    resolution: {integrity: sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==}
+    engines: {node: '>=0.10.0'}
+    dev: false
+
+  /decode-uri-component/0.2.0:
+    resolution: {integrity: sha512-hjf+xovcEn31w/EUYdTXQh/8smFL/dzYjohQGEIgjyNavaJfBY2p5F527Bo1VPATxv0VYTUC2bOcXvqFwk78Og==}
+    engines: {node: '>=0.10'}
+    dev: true
+
+  /define-property/0.2.5:
+    resolution: {integrity: sha512-Rr7ADjQZenceVOAKop6ALkkRAmH1A4Gx9hV/7ZujPUN2rkATqFO0JZLZInbAjpZYoJ1gUx8MRMQVkYemcbMSTA==}
+    engines: {node: '>=0.10.0'}
+    dependencies:
+      is-descriptor: 0.1.6
+    dev: true
+
+  /define-property/1.0.0:
+    resolution: {integrity: sha512-cZTYKFWspt9jZsMscWo8sc/5lbPC9Q0N5nBLgb+Yd915iL3udB1uFgS3B8YCx66UVHq018DAVFoee7x+gxggeA==}
+    engines: {node: '>=0.10.0'}
+    dependencies:
+      is-descriptor: 1.0.2
+    dev: true
+
+  /define-property/2.0.2:
+    resolution: {integrity: sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==}
+    engines: {node: '>=0.10.0'}
+    dependencies:
+      is-descriptor: 1.0.2
+      isobject: 3.0.1
+    dev: true
+
+  /defined/1.0.0:
+    resolution: {integrity: sha512-Y2caI5+ZwS5c3RiNDJ6u53VhQHv+hHKwhkI1iHvceKUHw9Df6EK2zRLfjejRgMuCuxK7PfSWIMwWecceVvThjQ==}
+    dev: false
+
+  /delayed-stream/1.0.0:
+    resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==}
+    engines: {node: '>=0.4.0'}
+    dev: false
+
+  /detective/5.2.1:
+    resolution: {integrity: sha512-v9XE1zRnz1wRtgurGu0Bs8uHKFSTdteYZNbIPFVhUZ39L/S79ppMpdmVOZAnoz1jfEFodc48n6MX483Xo3t1yw==}
+    engines: {node: '>=0.8.0'}
+    hasBin: true
+    dependencies:
+      acorn-node: 1.8.2
+      defined: 1.0.0
+      minimist: 1.2.6
+    dev: false
+
+  /didyoumean/1.2.2:
+    resolution: {integrity: sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==}
+    dev: false
+
+  /dijkstrajs/1.0.2:
+    resolution: {integrity: sha512-QV6PMaHTCNmKSeP6QoXhVTw9snc9VD8MulTT0Bd99Pacp4SS1cjcrYPgBPmibqKVtMJJfqC6XvOXgPMEEPH/fg==}
+    dev: false
+
+  /dlv/1.1.3:
+    resolution: {integrity: sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==}
+    dev: false
+
+  /dom-align/1.12.3:
+    resolution: {integrity: sha512-Gj9hZN3a07cbR6zviMUBOMPdWxYhbMI+x+WS0NAIu2zFZmbK8ys9R79g+iG9qLnlCwpFoaB+fKy8Pdv470GsPA==}
+    dev: false
+
+  /dom-scroll-into-view/2.0.1:
+    resolution: {integrity: sha512-bvVTQe1lfaUr1oFzZX80ce9KLDlZ3iU+XGNE/bz9HnGdklTieqsbmsLHe+rT2XWqopvL0PckkYqN7ksmm5pe3w==}
+    dev: false
+
+  /dom-serializer/0.2.2:
+    resolution: {integrity: sha512-2/xPb3ORsQ42nHYiSunXkDjPLBaEj/xTwUO4B7XCZQTRk7EBtTOPaygh10YAAh2OI1Qrp6NWfpAhzswj0ydt9g==}
+    dependencies:
+      domelementtype: 2.3.0
+      entities: 2.2.0
+    dev: true
+
+  /dom-serializer/1.4.1:
+    resolution: {integrity: sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag==}
+    dependencies:
+      domelementtype: 2.3.0
+      domhandler: 4.3.1
+      entities: 2.2.0
+    dev: true
+
+  /domelementtype/1.3.1:
+    resolution: {integrity: sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w==}
+    dev: true
+
+  /domelementtype/2.3.0:
+    resolution: {integrity: sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==}
+    dev: true
+
+  /domhandler/2.4.2:
+    resolution: {integrity: sha512-JiK04h0Ht5u/80fdLMCEmV4zkNh2BcoMFBmZ/91WtYZ8qVXSKjiw7fXMgFPnHcSZgOo3XdinHvmnDUeMf5R4wA==}
+    dependencies:
+      domelementtype: 1.3.1
+    dev: true
+
+  /domhandler/4.3.1:
+    resolution: {integrity: sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ==}
+    engines: {node: '>= 4'}
+    dependencies:
+      domelementtype: 2.3.0
+    dev: true
+
+  /domutils/1.7.0:
+    resolution: {integrity: sha512-Lgd2XcJ/NjEw+7tFvfKxOzCYKZsdct5lczQ2ZaQY8Djz7pfAD3Gbp8ySJWtreII/vDlMVmxwa6pHmdxIYgttDg==}
+    dependencies:
+      dom-serializer: 0.2.2
+      domelementtype: 1.3.1
+    dev: true
+
+  /domutils/2.8.0:
+    resolution: {integrity: sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==}
+    dependencies:
+      dom-serializer: 1.4.1
+      domelementtype: 2.3.0
+      domhandler: 4.3.1
+    dev: true
+
+  /electron-to-chromium/1.4.222:
+    resolution: {integrity: sha512-gEM2awN5HZknWdLbngk4uQCVfhucFAfFzuchP3wM3NN6eow1eDU0dFy2kts43FB20ZfhVFF0jmFSTb1h5OhyIg==}
+    dev: true
+
+  /emoji-regex/8.0.0:
+    resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==}
+    dev: false
+
+  /emojis-list/3.0.0:
+    resolution: {integrity: sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==}
+    engines: {node: '>= 4'}
+    dev: true
+
+  /encode-utf8/1.0.3:
+    resolution: {integrity: sha512-ucAnuBEhUK4boH2HjVYG5Q2mQyPorvv0u/ocS+zhdw0S8AlHYY+GOFhP1Gio5z4icpP2ivFSvhtFjQi8+T9ppw==}
+    dev: false
+
+  /entities/1.1.2:
+    resolution: {integrity: sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w==}
+    dev: true
+
+  /entities/2.2.0:
+    resolution: {integrity: sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==}
+    dev: true
+
+  /errno/0.1.8:
+    resolution: {integrity: sha512-dJ6oBr5SQ1VSd9qkk7ByRgb/1SH4JZjCHSW/mr63/QcXO9zLVxvJ6Oy13nio03rxpSnVDDjFor75SjVeZWPW/A==}
+    hasBin: true
+    requiresBuild: true
+    dependencies:
+      prr: 1.0.1
+    dev: true
+    optional: true
+
+  /esbuild-android-64/0.14.54:
+    resolution: {integrity: sha512-Tz2++Aqqz0rJ7kYBfz+iqyE3QMycD4vk7LBRyWaAVFgFtQ/O8EJOnVmTOiDWYZ/uYzB4kvP+bqejYdVKzE5lAQ==}
+    engines: {node: '>=12'}
+    cpu: [x64]
+    os: [android]
+    requiresBuild: true
+    dev: true
+    optional: true
+
+  /esbuild-android-arm64/0.14.54:
+    resolution: {integrity: sha512-F9E+/QDi9sSkLaClO8SOV6etqPd+5DgJje1F9lOWoNncDdOBL2YF59IhsWATSt0TLZbYCf3pNlTHvVV5VfHdvg==}
+    engines: {node: '>=12'}
+    cpu: [arm64]
+    os: [android]
+    requiresBuild: true
+    dev: true
+    optional: true
+
+  /esbuild-darwin-64/0.14.54:
+    resolution: {integrity: sha512-jtdKWV3nBviOd5v4hOpkVmpxsBy90CGzebpbO9beiqUYVMBtSc0AL9zGftFuBon7PNDcdvNCEuQqw2x0wP9yug==}
+    engines: {node: '>=12'}
+    cpu: [x64]
+    os: [darwin]
+    requiresBuild: true
+    dev: true
+    optional: true
+
+  /esbuild-darwin-arm64/0.14.54:
+    resolution: {integrity: sha512-OPafJHD2oUPyvJMrsCvDGkRrVCar5aVyHfWGQzY1dWnzErjrDuSETxwA2HSsyg2jORLY8yBfzc1MIpUkXlctmw==}
+    engines: {node: '>=12'}
+    cpu: [arm64]
+    os: [darwin]
+    requiresBuild: true
+    dev: true
+    optional: true
+
+  /esbuild-freebsd-64/0.14.54:
+    resolution: {integrity: sha512-OKwd4gmwHqOTp4mOGZKe/XUlbDJ4Q9TjX0hMPIDBUWWu/kwhBAudJdBoxnjNf9ocIB6GN6CPowYpR/hRCbSYAg==}
+    engines: {node: '>=12'}
+    cpu: [x64]
+    os: [freebsd]
+    requiresBuild: true
+    dev: true
+    optional: true
+
+  /esbuild-freebsd-arm64/0.14.54:
+    resolution: {integrity: sha512-sFwueGr7OvIFiQT6WeG0jRLjkjdqWWSrfbVwZp8iMP+8UHEHRBvlaxL6IuKNDwAozNUmbb8nIMXa7oAOARGs1Q==}
+    engines: {node: '>=12'}
+    cpu: [arm64]
+    os: [freebsd]
+    requiresBuild: true
+    dev: true
+    optional: true
+
+  /esbuild-linux-32/0.14.54:
+    resolution: {integrity: sha512-1ZuY+JDI//WmklKlBgJnglpUL1owm2OX+8E1syCD6UAxcMM/XoWd76OHSjl/0MR0LisSAXDqgjT3uJqT67O3qw==}
+    engines: {node: '>=12'}
+    cpu: [ia32]
+    os: [linux]
+    requiresBuild: true
+    dev: true
+    optional: true
+
+  /esbuild-linux-64/0.14.54:
+    resolution: {integrity: sha512-EgjAgH5HwTbtNsTqQOXWApBaPVdDn7XcK+/PtJwZLT1UmpLoznPd8c5CxqsH2dQK3j05YsB3L17T8vE7cp4cCg==}
+    engines: {node: '>=12'}
+    cpu: [x64]
+    os: [linux]
+    requiresBuild: true
+    dev: true
+    optional: true
+
+  /esbuild-linux-arm/0.14.54:
+    resolution: {integrity: sha512-qqz/SjemQhVMTnvcLGoLOdFpCYbz4v4fUo+TfsWG+1aOu70/80RV6bgNpR2JCrppV2moUQkww+6bWxXRL9YMGw==}
+    engines: {node: '>=12'}
+    cpu: [arm]
+    os: [linux]
+    requiresBuild: true
+    dev: true
+    optional: true
+
+  /esbuild-linux-arm64/0.14.54:
+    resolution: {integrity: sha512-WL71L+0Rwv+Gv/HTmxTEmpv0UgmxYa5ftZILVi2QmZBgX3q7+tDeOQNqGtdXSdsL8TQi1vIaVFHUPDe0O0kdig==}
+    engines: {node: '>=12'}
+    cpu: [arm64]
+    os: [linux]
+    requiresBuild: true
+    dev: true
+    optional: true
+
+  /esbuild-linux-mips64le/0.14.54:
+    resolution: {integrity: sha512-qTHGQB8D1etd0u1+sB6p0ikLKRVuCWhYQhAHRPkO+OF3I/iSlTKNNS0Lh2Oc0g0UFGguaFZZiPJdJey3AGpAlw==}
+    engines: {node: '>=12'}
+    cpu: [mips64el]
+    os: [linux]
+    requiresBuild: true
+    dev: true
+    optional: true
+
+  /esbuild-linux-ppc64le/0.14.54:
+    resolution: {integrity: sha512-j3OMlzHiqwZBDPRCDFKcx595XVfOfOnv68Ax3U4UKZ3MTYQB5Yz3X1mn5GnodEVYzhtZgxEBidLWeIs8FDSfrQ==}
+    engines: {node: '>=12'}
+    cpu: [ppc64]
+    os: [linux]
+    requiresBuild: true
+    dev: true
+    optional: true
+
+  /esbuild-linux-riscv64/0.14.54:
+    resolution: {integrity: sha512-y7Vt7Wl9dkOGZjxQZnDAqqn+XOqFD7IMWiewY5SPlNlzMX39ocPQlOaoxvT4FllA5viyV26/QzHtvTjVNOxHZg==}
+    engines: {node: '>=12'}
+    cpu: [riscv64]
+    os: [linux]
+    requiresBuild: true
+    dev: true
+    optional: true
+
+  /esbuild-linux-s390x/0.14.54:
+    resolution: {integrity: sha512-zaHpW9dziAsi7lRcyV4r8dhfG1qBidQWUXweUjnw+lliChJqQr+6XD71K41oEIC3Mx1KStovEmlzm+MkGZHnHA==}
+    engines: {node: '>=12'}
+    cpu: [s390x]
+    os: [linux]
+    requiresBuild: true
+    dev: true
+    optional: true
+
+  /esbuild-netbsd-64/0.14.54:
+    resolution: {integrity: sha512-PR01lmIMnfJTgeU9VJTDY9ZerDWVFIUzAtJuDHwwceppW7cQWjBBqP48NdeRtoP04/AtO9a7w3viI+PIDr6d+w==}
+    engines: {node: '>=12'}
+    cpu: [x64]
+    os: [netbsd]
+    requiresBuild: true
+    dev: true
+    optional: true
+
+  /esbuild-openbsd-64/0.14.54:
+    resolution: {integrity: sha512-Qyk7ikT2o7Wu76UsvvDS5q0amJvmRzDyVlL0qf5VLsLchjCa1+IAvd8kTBgUxD7VBUUVgItLkk609ZHUc1oCaw==}
+    engines: {node: '>=12'}
+    cpu: [x64]
+    os: [openbsd]
+    requiresBuild: true
+    dev: true
+    optional: true
+
+  /esbuild-sunos-64/0.14.54:
+    resolution: {integrity: sha512-28GZ24KmMSeKi5ueWzMcco6EBHStL3B6ubM7M51RmPwXQGLe0teBGJocmWhgwccA1GeFXqxzILIxXpHbl9Q/Kw==}
+    engines: {node: '>=12'}
+    cpu: [x64]
+    os: [sunos]
+    requiresBuild: true
+    dev: true
+    optional: true
+
+  /esbuild-windows-32/0.14.54:
+    resolution: {integrity: sha512-T+rdZW19ql9MjS7pixmZYVObd9G7kcaZo+sETqNH4RCkuuYSuv9AGHUVnPoP9hhuE1WM1ZimHz1CIBHBboLU7w==}
+    engines: {node: '>=12'}
+    cpu: [ia32]
+    os: [win32]
+    requiresBuild: true
+    dev: true
+    optional: true
+
+  /esbuild-windows-64/0.14.54:
+    resolution: {integrity: sha512-AoHTRBUuYwXtZhjXZbA1pGfTo8cJo3vZIcWGLiUcTNgHpJJMC1rVA44ZereBHMJtotyN71S8Qw0npiCIkW96cQ==}
+    engines: {node: '>=12'}
+    cpu: [x64]
+    os: [win32]
+    requiresBuild: true
+    dev: true
+    optional: true
+
+  /esbuild-windows-arm64/0.14.54:
+    resolution: {integrity: sha512-M0kuUvXhot1zOISQGXwWn6YtS+Y/1RT9WrVIOywZnJHo3jCDyewAc79aKNQWFCQm+xNHVTq9h8dZKvygoXQQRg==}
+    engines: {node: '>=12'}
+    cpu: [arm64]
+    os: [win32]
+    requiresBuild: true
+    dev: true
+    optional: true
+
+  /esbuild/0.14.54:
+    resolution: {integrity: sha512-Cy9llcy8DvET5uznocPyqL3BFRrFXSVqbgpMJ9Wz8oVjZlh/zUSNbPRbov0VX7VxN2JH1Oa0uNxZ7eLRb62pJA==}
+    engines: {node: '>=12'}
+    hasBin: true
+    requiresBuild: true
+    optionalDependencies:
+      '@esbuild/linux-loong64': 0.14.54
+      esbuild-android-64: 0.14.54
+      esbuild-android-arm64: 0.14.54
+      esbuild-darwin-64: 0.14.54
+      esbuild-darwin-arm64: 0.14.54
+      esbuild-freebsd-64: 0.14.54
+      esbuild-freebsd-arm64: 0.14.54
+      esbuild-linux-32: 0.14.54
+      esbuild-linux-64: 0.14.54
+      esbuild-linux-arm: 0.14.54
+      esbuild-linux-arm64: 0.14.54
+      esbuild-linux-mips64le: 0.14.54
+      esbuild-linux-ppc64le: 0.14.54
+      esbuild-linux-riscv64: 0.14.54
+      esbuild-linux-s390x: 0.14.54
+      esbuild-netbsd-64: 0.14.54
+      esbuild-openbsd-64: 0.14.54
+      esbuild-sunos-64: 0.14.54
+      esbuild-windows-32: 0.14.54
+      esbuild-windows-64: 0.14.54
+      esbuild-windows-arm64: 0.14.54
+    dev: true
+
+  /escalade/3.1.1:
+    resolution: {integrity: sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==}
+    engines: {node: '>=6'}
+    dev: true
+
+  /escape-string-regexp/1.0.5:
+    resolution: {integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==}
+    engines: {node: '>=0.8.0'}
+    dev: true
+
+  /estree-walker/2.0.2:
+    resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==}
+
+  /etag/1.8.1:
+    resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==}
+    engines: {node: '>= 0.6'}
+    dev: true
+
+  /expand-brackets/2.1.4:
+    resolution: {integrity: sha512-w/ozOKR9Obk3qoWeY/WDi6MFta9AoMR+zud60mdnbniMcBxRuFJyDt2LdX/14A1UABeqk+Uk+LDfUpvoGKppZA==}
+    engines: {node: '>=0.10.0'}
+    dependencies:
+      debug: 2.6.9
+      define-property: 0.2.5
+      extend-shallow: 2.0.1
+      posix-character-classes: 0.1.1
+      regex-not: 1.0.2
+      snapdragon: 0.8.2
+      to-regex: 3.0.2
+    transitivePeerDependencies:
+      - supports-color
+    dev: true
+
+  /extend-shallow/2.0.1:
+    resolution: {integrity: sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==}
+    engines: {node: '>=0.10.0'}
+    dependencies:
+      is-extendable: 0.1.1
+    dev: true
+
+  /extend-shallow/3.0.2:
+    resolution: {integrity: sha512-BwY5b5Ql4+qZoefgMj2NUmx+tehVTH/Kf4k1ZEtOHNFcm2wSxMRo992l6X3TIgni2eZVTZ85xMOjF31fwZAj6Q==}
+    engines: {node: '>=0.10.0'}
+    dependencies:
+      assign-symbols: 1.0.0
+      is-extendable: 1.0.1
+    dev: true
+
+  /extglob/2.0.4:
+    resolution: {integrity: sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==}
+    engines: {node: '>=0.10.0'}
+    dependencies:
+      array-unique: 0.3.2
+      define-property: 1.0.0
+      expand-brackets: 2.1.4
+      extend-shallow: 2.0.1
+      fragment-cache: 0.2.1
+      regex-not: 1.0.2
+      snapdragon: 0.8.2
+      to-regex: 3.0.2
+    transitivePeerDependencies:
+      - supports-color
+    dev: true
+
+  /fast-glob/3.2.11:
+    resolution: {integrity: sha512-xrO3+1bxSo3ZVHAnqzyuewYT6aMFHRAd4Kcs92MAonjwQZLsK9d0SF1IyQ3k5PoirxTW0Oe/RqFgMQ6TcNE5Ew==}
+    engines: {node: '>=8.6.0'}
+    dependencies:
+      '@nodelib/fs.stat': 2.0.5
+      '@nodelib/fs.walk': 1.2.8
+      glob-parent: 5.1.2
+      merge2: 1.4.1
+      micromatch: 4.0.5
+
+  /fastq/1.13.0:
+    resolution: {integrity: sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw==}
+    dependencies:
+      reusify: 1.0.4
+
+  /fill-range/4.0.0:
+    resolution: {integrity: sha512-VcpLTWqWDiTerugjj8e3+esbg+skS3M9e54UuR3iCeIDMXCLTsAH8hTSzDQU/X6/6t3eYkOKoZSef2PlU6U1XQ==}
+    engines: {node: '>=0.10.0'}
+    dependencies:
+      extend-shallow: 2.0.1
+      is-number: 3.0.0
+      repeat-string: 1.6.1
+      to-regex-range: 2.1.1
+    dev: true
+
+  /fill-range/7.0.1:
+    resolution: {integrity: sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==}
+    engines: {node: '>=8'}
+    dependencies:
+      to-regex-range: 5.0.1
+
+  /find-up/4.1.0:
+    resolution: {integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==}
+    engines: {node: '>=8'}
+    dependencies:
+      locate-path: 5.0.0
+      path-exists: 4.0.0
+    dev: false
+
+  /follow-redirects/1.15.1:
+    resolution: {integrity: sha512-yLAMQs+k0b2m7cVxpS1VKJVvoz7SS9Td1zss3XRwXj+ZDH00RJgnuLx7E44wx02kQLrdM3aOOy+FpzS7+8OizA==}
+    engines: {node: '>=4.0'}
+    peerDependencies:
+      debug: '*'
+    peerDependenciesMeta:
+      debug:
+        optional: true
+    dev: false
+
+  /for-in/1.0.2:
+    resolution: {integrity: sha512-7EwmXrOjyL+ChxMhmG5lnW9MPt1aIeZEwKhQzoBUdTV0N3zuwWDZYVJatDvZ2OyzPUvdIAZDsCetk3coyMfcnQ==}
+    engines: {node: '>=0.10.0'}
+    dev: true
+
+  /form-data/4.0.0:
+    resolution: {integrity: sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==}
+    engines: {node: '>= 6'}
+    dependencies:
+      asynckit: 0.4.0
+      combined-stream: 1.0.8
+      mime-types: 2.1.35
+    dev: false
+
+  /fraction.js/4.2.0:
+    resolution: {integrity: sha512-MhLuK+2gUcnZe8ZHlaaINnQLl0xRIGRfcGk2yl8xoQAfHrSsL3rYu6FCmBdkdbhc9EPlwyGHewaRsvwRMJtAlA==}
+    dev: true
+
+  /fragment-cache/0.2.1:
+    resolution: {integrity: sha512-GMBAbW9antB8iZRHLoGw0b3HANt57diZYFO/HL1JGIC1MjKrdmhxvrJbupnVvpys0zsz7yBApXdQyfepKly2kA==}
+    engines: {node: '>=0.10.0'}
+    dependencies:
+      map-cache: 0.2.2
+    dev: true
+
+  /fs-extra/10.1.0:
+    resolution: {integrity: sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==}
+    engines: {node: '>=12'}
+    dependencies:
+      graceful-fs: 4.2.10
+      jsonfile: 6.1.0
+      universalify: 2.0.0
+    dev: true
+
+  /fsevents/2.3.2:
+    resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==}
+    engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
+    os: [darwin]
+    requiresBuild: true
+    optional: true
+
+  /function-bind/1.1.1:
+    resolution: {integrity: sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==}
+
+  /gensync/1.0.0-beta.2:
+    resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==}
+    engines: {node: '>=6.9.0'}
+    dev: true
+
+  /get-caller-file/2.0.5:
+    resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==}
+    engines: {node: 6.* || 8.* || >= 10.*}
+    dev: false
+
+  /get-value/2.0.6:
+    resolution: {integrity: sha512-Ln0UQDlxH1BapMu3GPtf7CuYNwRZf2gwCuPqbyG6pB8WfmFpzqcy4xtAaAMUhnNqjMKTiCPZG2oMT3YSx8U2NA==}
+    engines: {node: '>=0.10.0'}
+    dev: true
+
+  /glob-parent/5.1.2:
+    resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==}
+    engines: {node: '>= 6'}
+    dependencies:
+      is-glob: 4.0.3
+
+  /glob-parent/6.0.2:
+    resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==}
+    engines: {node: '>=10.13.0'}
+    dependencies:
+      is-glob: 4.0.3
+    dev: false
+
+  /globals/11.12.0:
+    resolution: {integrity: sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==}
+    engines: {node: '>=4'}
+    dev: true
+
+  /graceful-fs/4.2.10:
+    resolution: {integrity: sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==}
+    requiresBuild: true
+    dev: true
+
+  /has-ansi/2.0.0:
+    resolution: {integrity: sha512-C8vBJ8DwUCx19vhm7urhTuUsr4/IyP6l4VzNQDv+ryHQObW3TTTp9yB68WpYgRe2bbaGuZ/se74IqFeVnMnLZg==}
+    engines: {node: '>=0.10.0'}
+    dependencies:
+      ansi-regex: 2.1.1
+    dev: true
+
+  /has-flag/1.0.0:
+    resolution: {integrity: sha512-DyYHfIYwAJmjAjSSPKANxI8bFY9YtFrgkAfinBojQ8YJTOuOuav64tMUJv584SES4xl74PmuaevIyaLESHdTAA==}
+    engines: {node: '>=0.10.0'}
+    dev: true
+
+  /has-flag/3.0.0:
+    resolution: {integrity: sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==}
+    engines: {node: '>=4'}
+    dev: true
+
+  /has-value/0.3.1:
+    resolution: {integrity: sha512-gpG936j8/MzaeID5Yif+577c17TxaDmhuyVgSwtnL/q8UUTySg8Mecb+8Cf1otgLoD7DDH75axp86ER7LFsf3Q==}
+    engines: {node: '>=0.10.0'}
+    dependencies:
+      get-value: 2.0.6
+      has-values: 0.1.4
+      isobject: 2.1.0
+    dev: true
+
+  /has-value/1.0.0:
+    resolution: {integrity: sha512-IBXk4GTsLYdQ7Rvt+GRBrFSVEkmuOUy4re0Xjd9kJSUQpnTrWR4/y9RpfexN9vkAPMFuQoeWKwqzPozRTlasGw==}
+    engines: {node: '>=0.10.0'}
+    dependencies:
+      get-value: 2.0.6
+      has-values: 1.0.0
+      isobject: 3.0.1
+    dev: true
+
+  /has-values/0.1.4:
+    resolution: {integrity: sha512-J8S0cEdWuQbqD9//tlZxiMuMNmxB8PlEwvYwuxsTmR1G5RXUePEX/SJn7aD0GMLieuZYSwNH0cQuJGwnYunXRQ==}
+    engines: {node: '>=0.10.0'}
+    dev: true
+
+  /has-values/1.0.0:
+    resolution: {integrity: sha512-ODYZC64uqzmtfGMEAX/FvZiRyWLpAC3vYnNunURUnkGVTS+mI0smVsWaPydRBsE3g+ok7h960jChO8mFcWlHaQ==}
+    engines: {node: '>=0.10.0'}
+    dependencies:
+      is-number: 3.0.0
+      kind-of: 4.0.0
+    dev: true
+
+  /has/1.0.3:
+    resolution: {integrity: sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==}
+    engines: {node: '>= 0.4.0'}
+    dependencies:
+      function-bind: 1.1.1
+
+  /he/1.2.0:
+    resolution: {integrity: sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==}
+    hasBin: true
+    dev: true
+
+  /html-tags/3.2.0:
+    resolution: {integrity: sha512-vy7ClnArOZwCnqZgvv+ddgHgJiAFXe3Ge9ML5/mBctVJoUoYPCdxVucOywjDARn6CVoh3dRSFdPHy2sX80L0Wg==}
+    engines: {node: '>=8'}
+    dev: true
+
+  /htmlparser2/3.10.1:
+    resolution: {integrity: sha512-IgieNijUMbkDovyoKObU1DUhm1iwNYE/fuifEoEHfd1oZKZDaONBSkal7Y01shxsM49R4XaMdGez3WnF9UfiCQ==}
+    dependencies:
+      domelementtype: 1.3.1
+      domhandler: 2.4.2
+      domutils: 1.7.0
+      entities: 1.1.2
+      inherits: 2.0.4
+      readable-stream: 3.6.0
+    dev: true
+
+  /iconv-lite/0.6.3:
+    resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==}
+    engines: {node: '>=0.10.0'}
+    dependencies:
+      safer-buffer: 2.1.2
+    dev: true
+    optional: true
+
+  /image-size/0.5.5:
+    resolution: {integrity: sha512-6TDAlDPZxUFCv+fuOkIoXT/V/f3Qbq8e37p+YOiYrUv3v9cc3/6x78VdfPgFVaB9dZYeLUfKgHRebpkm/oP2VQ==}
+    engines: {node: '>=0.10.0'}
+    hasBin: true
+    requiresBuild: true
+    dev: true
+
+  /inherits/2.0.4:
+    resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==}
+    dev: true
+
+  /is-accessor-descriptor/0.1.6:
+    resolution: {integrity: sha512-e1BM1qnDbMRG3ll2U9dSK0UMHuWOs3pY3AtcFsmvwPtKL3MML/Q86i+GilLfvqEs4GW+ExB91tQ3Ig9noDIZ+A==}
+    engines: {node: '>=0.10.0'}
+    dependencies:
+      kind-of: 3.2.2
+    dev: true
+
+  /is-accessor-descriptor/1.0.0:
+    resolution: {integrity: sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==}
+    engines: {node: '>=0.10.0'}
+    dependencies:
+      kind-of: 6.0.3
+    dev: true
+
+  /is-binary-path/2.1.0:
+    resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==}
+    engines: {node: '>=8'}
+    dependencies:
+      binary-extensions: 2.2.0
+
+  /is-buffer/1.1.6:
+    resolution: {integrity: sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==}
+    dev: true
+
+  /is-core-module/2.10.0:
+    resolution: {integrity: sha512-Erxj2n/LDAZ7H8WNJXd9tw38GYM3dv8rk8Zcs+jJuxYTW7sozH+SS8NtrSjVL1/vpLvWi1hxy96IzjJ3EHTJJg==}
+    dependencies:
+      has: 1.0.3
+
+  /is-data-descriptor/0.1.4:
+    resolution: {integrity: sha512-+w9D5ulSoBNlmw9OHn3U2v51SyoCd0he+bB3xMl62oijhrspxowjU+AIcDY0N3iEJbUEkB15IlMASQsxYigvXg==}
+    engines: {node: '>=0.10.0'}
+    dependencies:
+      kind-of: 3.2.2
+    dev: true
+
+  /is-data-descriptor/1.0.0:
+    resolution: {integrity: sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==}
+    engines: {node: '>=0.10.0'}
+    dependencies:
+      kind-of: 6.0.3
+    dev: true
+
+  /is-descriptor/0.1.6:
+    resolution: {integrity: sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==}
+    engines: {node: '>=0.10.0'}
+    dependencies:
+      is-accessor-descriptor: 0.1.6
+      is-data-descriptor: 0.1.4
+      kind-of: 5.1.0
+    dev: true
+
+  /is-descriptor/1.0.2:
+    resolution: {integrity: sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==}
+    engines: {node: '>=0.10.0'}
+    dependencies:
+      is-accessor-descriptor: 1.0.0
+      is-data-descriptor: 1.0.0
+      kind-of: 6.0.3
+    dev: true
+
+  /is-extendable/0.1.1:
+    resolution: {integrity: sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==}
+    engines: {node: '>=0.10.0'}
+    dev: true
+
+  /is-extendable/1.0.1:
+    resolution: {integrity: sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==}
+    engines: {node: '>=0.10.0'}
+    dependencies:
+      is-plain-object: 2.0.4
+    dev: true
+
+  /is-extglob/2.1.1:
+    resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==}
+    engines: {node: '>=0.10.0'}
+
+  /is-fullwidth-code-point/3.0.0:
+    resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==}
+    engines: {node: '>=8'}
+    dev: false
+
+  /is-glob/4.0.3:
+    resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==}
+    engines: {node: '>=0.10.0'}
+    dependencies:
+      is-extglob: 2.1.1
+
+  /is-number/3.0.0:
+    resolution: {integrity: sha512-4cboCqIpliH+mAvFNegjZQ4kgKc3ZUhQVr3HvWbSh5q3WH2v82ct+T2Y1hdU5Gdtorx/cLifQjqCbL7bpznLTg==}
+    engines: {node: '>=0.10.0'}
+    dependencies:
+      kind-of: 3.2.2
+    dev: true
+
+  /is-number/7.0.0:
+    resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==}
+    engines: {node: '>=0.12.0'}
+
+  /is-plain-obj/1.1.0:
+    resolution: {integrity: sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg==}
+    engines: {node: '>=0.10.0'}
+    dev: true
+
+  /is-plain-object/2.0.4:
+    resolution: {integrity: sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==}
+    engines: {node: '>=0.10.0'}
+    dependencies:
+      isobject: 3.0.1
+    dev: true
+
+  /is-plain-object/3.0.1:
+    resolution: {integrity: sha512-Xnpx182SBMrr/aBik8y+GuR4U1L9FqMSojwDQwPMmxyC6bvEqly9UBCxhauBF5vNh2gwWJNX6oDV7O+OM4z34g==}
+    engines: {node: '>=0.10.0'}
+    dev: false
+
+  /is-what/3.14.1:
+    resolution: {integrity: sha512-sNxgpk9793nzSs7bA6JQJGeIuRBQhAaNGG77kzYQgMkrID+lS6SlK07K5LaptscDlSaIgH+GPFzf+d75FVxozA==}
+    dev: true
+
+  /is-windows/1.0.2:
+    resolution: {integrity: sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==}
+    engines: {node: '>=0.10.0'}
+    dev: true
+
+  /isarray/1.0.0:
+    resolution: {integrity: sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==}
+    dev: true
+
+  /isobject/2.1.0:
+    resolution: {integrity: sha512-+OUdGJlgjOBZDfxnDjYYG6zp487z0JGNQq3cYQYg5f5hKR+syHMsaztzGeml/4kGG55CSpKSpWTY+jYGgsHLgA==}
+    engines: {node: '>=0.10.0'}
+    dependencies:
+      isarray: 1.0.0
+    dev: true
+
+  /isobject/3.0.1:
+    resolution: {integrity: sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==}
+    engines: {node: '>=0.10.0'}
+    dev: true
+
+  /js-base64/2.6.4:
+    resolution: {integrity: sha512-pZe//GGmwJndub7ZghVHz7vjb2LgC1m8B07Au3eYqeqv9emhESByMXxaEgkUkEqJe87oBbSniGYoQNIBklc7IQ==}
+    dev: true
+
+  /js-tokens/4.0.0:
+    resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==}
+
+  /jsesc/2.5.2:
+    resolution: {integrity: sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==}
+    engines: {node: '>=4'}
+    hasBin: true
+    dev: true
+
+  /json5/1.0.1:
+    resolution: {integrity: sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==}
+    hasBin: true
+    dependencies:
+      minimist: 1.2.6
+    dev: true
+
+  /json5/2.2.1:
+    resolution: {integrity: sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==}
+    engines: {node: '>=6'}
+    hasBin: true
+    dev: true
+
+  /jsonfile/6.1.0:
+    resolution: {integrity: sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==}
+    dependencies:
+      universalify: 2.0.0
+    optionalDependencies:
+      graceful-fs: 4.2.10
+    dev: true
+
+  /kind-of/3.2.2:
+    resolution: {integrity: sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==}
+    engines: {node: '>=0.10.0'}
+    dependencies:
+      is-buffer: 1.1.6
+    dev: true
+
+  /kind-of/4.0.0:
+    resolution: {integrity: sha512-24XsCxmEbRwEDbz/qz3stgin8TTzZ1ESR56OMCN0ujYg+vRutNSiOj9bHH9u85DKgXguraugV5sFuvbD4FW/hw==}
+    engines: {node: '>=0.10.0'}
+    dependencies:
+      is-buffer: 1.1.6
+    dev: true
+
+  /kind-of/5.1.0:
+    resolution: {integrity: sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==}
+    engines: {node: '>=0.10.0'}
+    dev: true
+
+  /kind-of/6.0.3:
+    resolution: {integrity: sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==}
+    engines: {node: '>=0.10.0'}
+    dev: true
+
+  /less/4.1.3:
+    resolution: {integrity: sha512-w16Xk/Ta9Hhyei0Gpz9m7VS8F28nieJaL/VyShID7cYvP6IL5oHeL6p4TXSDJqZE/lNv0oJ2pGVjJsRkfwm5FA==}
+    engines: {node: '>=6'}
+    hasBin: true
+    dependencies:
+      copy-anything: 2.0.6
+      parse-node-version: 1.0.1
+      tslib: 2.4.0
+    optionalDependencies:
+      errno: 0.1.8
+      graceful-fs: 4.2.10
+      image-size: 0.5.5
+      make-dir: 2.1.0
+      mime: 1.6.0
+      needle: 3.1.0
+      source-map: 0.6.1
+    transitivePeerDependencies:
+      - supports-color
+    dev: true
+
+  /lilconfig/2.0.6:
+    resolution: {integrity: sha512-9JROoBW7pobfsx+Sq2JsASvCo6Pfo6WWoUW79HuB1BCoBXD4PLWJPqDF6fNj67pqBYTbAHkE57M1kS/+L1neOg==}
+    engines: {node: '>=10'}
+    dev: false
+
+  /loader-utils/1.4.0:
+    resolution: {integrity: sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA==}
+    engines: {node: '>=4.0.0'}
+    dependencies:
+      big.js: 5.2.2
+      emojis-list: 3.0.0
+      json5: 1.0.1
+    dev: true
+
+  /local-pkg/0.4.2:
+    resolution: {integrity: sha512-mlERgSPrbxU3BP4qBqAvvwlgW4MTg78iwJdGGnv7kibKjWcJksrG3t6LB5lXI93wXRDvG4NpUgJFmTG4T6rdrg==}
+    engines: {node: '>=14'}
+    dev: true
+
+  /locate-path/5.0.0:
+    resolution: {integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==}
+    engines: {node: '>=8'}
+    dependencies:
+      p-locate: 4.1.0
+    dev: false
+
+  /lodash-es/4.17.21:
+    resolution: {integrity: sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==}
+    dev: false
+
+  /lodash/4.17.21:
+    resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==}
+    dev: false
+
+  /loose-envify/1.4.0:
+    resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==}
+    hasBin: true
+    dependencies:
+      js-tokens: 4.0.0
+    dev: false
+
+  /lru-cache/6.0.0:
+    resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==}
+    engines: {node: '>=10'}
+    dependencies:
+      yallist: 4.0.0
+    dev: true
+
+  /magic-string/0.25.9:
+    resolution: {integrity: sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==}
+    dependencies:
+      sourcemap-codec: 1.4.8
+
+  /magic-string/0.26.2:
+    resolution: {integrity: sha512-NzzlXpclt5zAbmo6h6jNc8zl2gNRGHvmsZW4IvZhTC4W7k4OlLP+S5YLussa/r3ixNT66KOQfNORlXHSOy/X4A==}
+    engines: {node: '>=12'}
+    dependencies:
+      sourcemap-codec: 1.4.8
+    dev: true
+
+  /make-dir/2.1.0:
+    resolution: {integrity: sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==}
+    engines: {node: '>=6'}
+    requiresBuild: true
+    dependencies:
+      pify: 4.0.1
+      semver: 5.7.1
+    dev: true
+    optional: true
+
+  /map-cache/0.2.2:
+    resolution: {integrity: sha512-8y/eV9QQZCiyn1SprXSrCmqJN0yNRATe+PO8ztwqrvrbdRLA3eYJF0yaR0YayLWkMbsQSKWS9N2gPcGEc4UsZg==}
+    engines: {node: '>=0.10.0'}
+    dev: true
+
+  /map-visit/1.0.0:
+    resolution: {integrity: sha512-4y7uGv8bd2WdM9vpQsiQNo41Ln1NvhvDRuVt0k2JZQ+ezN2uaQes7lZeZ+QQUHOLQAtDaBJ+7wCbi+ab/KFs+w==}
+    engines: {node: '>=0.10.0'}
+    dependencies:
+      object-visit: 1.0.1
+    dev: true
+
+  /mdn-data/2.0.14:
+    resolution: {integrity: sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow==}
+    dev: true
+
+  /merge-options/1.0.1:
+    resolution: {integrity: sha512-iuPV41VWKWBIOpBsjoxjDZw8/GbSfZ2mk7N1453bwMrfzdrIk7EzBd+8UVR6rkw67th7xnk9Dytl3J+lHPdxvg==}
+    engines: {node: '>=4'}
+    dependencies:
+      is-plain-obj: 1.1.0
+    dev: true
+
+  /merge2/1.4.1:
+    resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==}
+    engines: {node: '>= 8'}
+
+  /micromatch/3.1.0:
+    resolution: {integrity: sha512-3StSelAE+hnRvMs8IdVW7Uhk8CVed5tp+kLLGlBP6WiRAXS21GPGu/Nat4WNPXj2Eoc24B02SaeoyozPMfj0/g==}
+    engines: {node: '>=0.10.0'}
+    dependencies:
+      arr-diff: 4.0.0
+      array-unique: 0.3.2
+      braces: 2.3.2
+      define-property: 1.0.0
+      extend-shallow: 2.0.1
+      extglob: 2.0.4
+      fragment-cache: 0.2.1
+      kind-of: 5.1.0
+      nanomatch: 1.2.13
+      object.pick: 1.3.0
+      regex-not: 1.0.2
+      snapdragon: 0.8.2
+      to-regex: 3.0.2
+    transitivePeerDependencies:
+      - supports-color
+    dev: true
+
+  /micromatch/4.0.5:
+    resolution: {integrity: sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==}
+    engines: {node: '>=8.6'}
+    dependencies:
+      braces: 3.0.2
+      picomatch: 2.3.1
+
+  /mime-db/1.52.0:
+    resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==}
+    engines: {node: '>= 0.6'}
+    dev: false
+
+  /mime-types/2.1.35:
+    resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==}
+    engines: {node: '>= 0.6'}
+    dependencies:
+      mime-db: 1.52.0
+    dev: false
+
+  /mime/1.6.0:
+    resolution: {integrity: sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==}
+    engines: {node: '>=4'}
+    hasBin: true
+    requiresBuild: true
+    dev: true
+    optional: true
+
+  /minimatch/5.1.0:
+    resolution: {integrity: sha512-9TPBGGak4nHfGZsPBohm9AWg6NoT7QTCehS3BIJABslyZbzxfV78QM2Y6+i741OPZIafFAaiiEMh5OyIrJPgtg==}
+    engines: {node: '>=10'}
+    dependencies:
+      brace-expansion: 2.0.1
+    dev: true
+
+  /minimist/1.2.6:
+    resolution: {integrity: sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==}
+
+  /mixin-deep/1.3.2:
+    resolution: {integrity: sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA==}
+    engines: {node: '>=0.10.0'}
+    dependencies:
+      for-in: 1.0.2
+      is-extendable: 1.0.1
+    dev: true
+
+  /ms/2.0.0:
+    resolution: {integrity: sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==}
+    dev: true
+
+  /ms/2.1.2:
+    resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==}
+    dev: true
+
+  /ms/2.1.3:
+    resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
+    dev: true
+    optional: true
+
+  /nanoid/3.3.4:
+    resolution: {integrity: sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==}
+    engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
+    hasBin: true
+
+  /nanomatch/1.2.13:
+    resolution: {integrity: sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==}
+    engines: {node: '>=0.10.0'}
+    dependencies:
+      arr-diff: 4.0.0
+      array-unique: 0.3.2
+      define-property: 2.0.2
+      extend-shallow: 3.0.2
+      fragment-cache: 0.2.1
+      is-windows: 1.0.2
+      kind-of: 6.0.3
+      object.pick: 1.3.0
+      regex-not: 1.0.2
+      snapdragon: 0.8.2
+      to-regex: 3.0.2
+    transitivePeerDependencies:
+      - supports-color
+    dev: true
+
+  /nanopop/2.1.0:
+    resolution: {integrity: sha512-jGTwpFRexSH+fxappnGQtN9dspgE2ipa1aOjtR24igG0pv6JCxImIAmrLRHX+zUF5+1wtsFVbKyfP51kIGAVNw==}
+    dev: false
+
+  /needle/3.1.0:
+    resolution: {integrity: sha512-gCE9weDhjVGCRqS8dwDR/D3GTAeyXLXuqp7I8EzH6DllZGXSUyxuqqLh+YX9rMAWaaTFyVAg6rHGL25dqvczKw==}
+    engines: {node: '>= 4.4.x'}
+    hasBin: true
+    requiresBuild: true
+    dependencies:
+      debug: 3.2.7
+      iconv-lite: 0.6.3
+      sax: 1.2.4
+    transitivePeerDependencies:
+      - supports-color
+    dev: true
+    optional: true
+
+  /node-releases/2.0.6:
+    resolution: {integrity: sha512-PiVXnNuFm5+iYkLBNeq5211hvO38y63T0i2KKh2KnUs3RpzJ+JtODFjkD8yjLwnDkTYF1eKXheUwdssR+NRZdg==}
+    dev: true
+
+  /normalize-path/3.0.0:
+    resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==}
+    engines: {node: '>=0.10.0'}
+
+  /normalize-range/0.1.2:
+    resolution: {integrity: sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==}
+    engines: {node: '>=0.10.0'}
+    dev: true
+
+  /nth-check/2.1.1:
+    resolution: {integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==}
+    dependencies:
+      boolbase: 1.0.0
+    dev: true
+
+  /object-assign/4.1.1:
+    resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==}
+    engines: {node: '>=0.10.0'}
+    dev: true
+
+  /object-copy/0.1.0:
+    resolution: {integrity: sha512-79LYn6VAb63zgtmAteVOWo9Vdj71ZVBy3Pbse+VqxDpEP83XuujMrGqHIwAXJ5I/aM0zU7dIyIAhifVTPrNItQ==}
+    engines: {node: '>=0.10.0'}
+    dependencies:
+      copy-descriptor: 0.1.1
+      define-property: 0.2.5
+      kind-of: 3.2.2
+    dev: true
+
+  /object-hash/3.0.0:
+    resolution: {integrity: sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==}
+    engines: {node: '>= 6'}
+    dev: false
+
+  /object-visit/1.0.1:
+    resolution: {integrity: sha512-GBaMwwAVK9qbQN3Scdo0OyvgPW7l3lnaVMj84uTOZlswkX0KpF6fyDBJhtTthf7pymztoN36/KEr1DyhF96zEA==}
+    engines: {node: '>=0.10.0'}
+    dependencies:
+      isobject: 3.0.1
+    dev: true
+
+  /object.pick/1.3.0:
+    resolution: {integrity: sha512-tqa/UMy/CCoYmj+H5qc07qvSL9dqcs/WZENZ1JbtWBlATP+iVOe778gE6MSijnyCnORzDuX6hU+LA4SZ09YjFQ==}
+    engines: {node: '>=0.10.0'}
+    dependencies:
+      isobject: 3.0.1
+    dev: true
+
+  /p-limit/2.3.0:
+    resolution: {integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==}
+    engines: {node: '>=6'}
+    dependencies:
+      p-try: 2.2.0
+    dev: false
+
+  /p-locate/4.1.0:
+    resolution: {integrity: sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==}
+    engines: {node: '>=8'}
+    dependencies:
+      p-limit: 2.3.0
+    dev: false
+
+  /p-try/2.2.0:
+    resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==}
+    engines: {node: '>=6'}
+    dev: false
+
+  /parse-node-version/1.0.1:
+    resolution: {integrity: sha512-3YHlOa/JgH6Mnpr05jP9eDG254US9ek25LyIxZlDItp2iJtwyaXQb57lBYLdT3MowkUFYEV2XXNAYIPlESvJlA==}
+    engines: {node: '>= 0.10'}
+    dev: true
+
+  /pascalcase/0.1.1:
+    resolution: {integrity: sha512-XHXfu/yOQRy9vYOtUDVMN60OEJjW013GoObG1o+xwQTpB9eYJX/BjXMsdW13ZDPruFhYYn0AG22w0xgQMwl3Nw==}
+    engines: {node: '>=0.10.0'}
+    dev: true
+
+  /path-exists/4.0.0:
+    resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==}
+    engines: {node: '>=8'}
+    dev: false
+
+  /path-parse/1.0.7:
+    resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==}
+
+  /pathe/0.2.0:
+    resolution: {integrity: sha512-sTitTPYnn23esFR3RlqYBWn4c45WGeLcsKzQiUpXJAyfcWkolvlYpV8FLo7JishK946oQwMFUCHXQ9AjGPKExw==}
+    dev: true
+
+  /picocolors/1.0.0:
+    resolution: {integrity: sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==}
+
+  /picomatch/2.3.1:
+    resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==}
+    engines: {node: '>=8.6'}
+
+  /pify/2.3.0:
+    resolution: {integrity: sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==}
+    engines: {node: '>=0.10.0'}
+    dev: false
+
+  /pify/4.0.1:
+    resolution: {integrity: sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==}
+    engines: {node: '>=6'}
+    dev: true
+    optional: true
+
+  /pinia/2.0.18_j6bzmzd4ujpabbp5objtwxyjp4:
+    resolution: {integrity: sha512-I5MW05UVX6a5Djka136oH3VzYFiZUgeOApBwFjMx6pL91eHtGVlE3adjNUKLgtwGnrxiBRuJ8+4R3LKJKwnyZg==}
+    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
+    dependencies:
+      '@vue/devtools-api': 6.2.1
+      typescript: 4.7.4
+      vue: 3.2.37
+      vue-demi: 0.13.8_vue@3.2.37
+    dev: false
+
+  /pngjs/5.0.0:
+    resolution: {integrity: sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw==}
+    engines: {node: '>=10.13.0'}
+    dev: false
+
+  /posix-character-classes/0.1.1:
+    resolution: {integrity: sha512-xTgYBc3fuo7Yt7JbiuFxSYGToMoz8fLoE6TC9Wx1P/u+LfeThMOAqmuyECnlBaaJb+u1m9hHiXUEtwW4OzfUJg==}
+    engines: {node: '>=0.10.0'}
+    dev: true
+
+  /postcss-import/14.1.0_postcss@8.4.16:
+    resolution: {integrity: sha512-flwI+Vgm4SElObFVPpTIT7SU7R3qk2L7PyduMcokiaVKuWv9d/U+Gm/QAd8NDLuykTWTkcrjOeD2Pp1rMeBTGw==}
+    engines: {node: '>=10.0.0'}
+    peerDependencies:
+      postcss: ^8.0.0
+    dependencies:
+      postcss: 8.4.16
+      postcss-value-parser: 4.2.0
+      read-cache: 1.0.0
+      resolve: 1.22.1
+    dev: false
+
+  /postcss-js/4.0.0_postcss@8.4.16:
+    resolution: {integrity: sha512-77QESFBwgX4irogGVPgQ5s07vLvFqWr228qZY+w6lW599cRlK/HmnlivnnVUxkjHnCu4J16PDMHcH+e+2HbvTQ==}
+    engines: {node: ^12 || ^14 || >= 16}
+    peerDependencies:
+      postcss: ^8.3.3
+    dependencies:
+      camelcase-css: 2.0.1
+      postcss: 8.4.16
+    dev: false
+
+  /postcss-load-config/3.1.4_postcss@8.4.16:
+    resolution: {integrity: sha512-6DiM4E7v4coTE4uzA8U//WhtPwyhiim3eyjEMFCnUpzbrkK9wJHgKDT2mR+HbtSrd/NubVaYTOpSpjUl8NQeRg==}
+    engines: {node: '>= 10'}
+    peerDependencies:
+      postcss: '>=8.0.9'
+      ts-node: '>=9.0.0'
+    peerDependenciesMeta:
+      postcss:
+        optional: true
+      ts-node:
+        optional: true
+    dependencies:
+      lilconfig: 2.0.6
+      postcss: 8.4.16
+      yaml: 1.10.2
+    dev: false
+
+  /postcss-nested/5.0.6_postcss@8.4.16:
+    resolution: {integrity: sha512-rKqm2Fk0KbA8Vt3AdGN0FB9OBOMDVajMG6ZCf/GoHgdxUJ4sBFp0A/uMIRm+MJUdo33YXEtjqIz8u7DAp8B7DA==}
+    engines: {node: '>=12.0'}
+    peerDependencies:
+      postcss: ^8.2.14
+    dependencies:
+      postcss: 8.4.16
+      postcss-selector-parser: 6.0.10
+    dev: false
+
+  /postcss-prefix-selector/1.16.0_postcss@5.2.18:
+    resolution: {integrity: sha512-rdVMIi7Q4B0XbXqNUEI+Z4E+pueiu/CS5E6vRCQommzdQ/sgsS4dK42U7GX8oJR+TJOtT+Qv3GkNo6iijUMp3Q==}
+    peerDependencies:
+      postcss: '>4 <9'
+    dependencies:
+      postcss: 5.2.18
+    dev: true
+
+  /postcss-pxtorem/6.0.0:
+    resolution: {integrity: sha512-ZRXrD7MLLjLk2RNGV6UA4f5Y7gy+a/j1EqjAfp9NdcNYVjUMvg5HTYduTjSkKBkRkfqbg/iKrjMO70V4g1LZeg==}
+    peerDependencies:
+      postcss: ^8.0.0
+    dev: true
+
+  /postcss-selector-parser/6.0.10:
+    resolution: {integrity: sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w==}
+    engines: {node: '>=4'}
+    dependencies:
+      cssesc: 3.0.0
+      util-deprecate: 1.0.2
+    dev: false
+
+  /postcss-value-parser/4.2.0:
+    resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==}
+
+  /postcss/5.2.18:
+    resolution: {integrity: sha512-zrUjRRe1bpXKsX1qAJNJjqZViErVuyEkMTRrwu4ud4sbTtIBRmtaYDrHmcGgmrbsW3MHfmtIf+vJumgQn+PrXg==}
+    engines: {node: '>=0.12'}
+    dependencies:
+      chalk: 1.1.3
+      js-base64: 2.6.4
+      source-map: 0.5.7
+      supports-color: 3.2.3
+    dev: true
+
+  /postcss/8.4.16:
+    resolution: {integrity: sha512-ipHE1XBvKzm5xI7hiHCZJCSugxvsdq2mPnsq5+UF+VHCjiBvtDrlxJfMBToWaP9D5XlgNmcFGqoHmUn0EYEaRQ==}
+    engines: {node: ^10 || ^12 || >=14}
+    dependencies:
+      nanoid: 3.3.4
+      picocolors: 1.0.0
+      source-map-js: 1.0.2
+
+  /posthtml-parser/0.2.1:
+    resolution: {integrity: sha512-nPC53YMqJnc/+1x4fRYFfm81KV2V+G9NZY+hTohpYg64Ay7NemWWcV4UWuy/SgMupqQ3kJ88M/iRfZmSnxT+pw==}
+    dependencies:
+      htmlparser2: 3.10.1
+      isobject: 2.1.0
+    dev: true
+
+  /posthtml-rename-id/1.0.12:
+    resolution: {integrity: sha512-UKXf9OF/no8WZo9edRzvuMenb6AD5hDLzIepJW+a4oJT+T/Lx7vfMYWT4aWlGNQh0WMhnUx1ipN9OkZ9q+ddEw==}
+    dependencies:
+      escape-string-regexp: 1.0.5
+    dev: true
+
+  /posthtml-render/1.4.0:
+    resolution: {integrity: sha512-W1779iVHGfq0Fvh2PROhCe2QhB8mEErgqzo1wpIt36tCgChafP+hbXIhLDOM8ePJrZcFs0vkNEtdibEWVqChqw==}
+    engines: {node: '>=10'}
+    dev: true
+
+  /posthtml-svg-mode/1.0.3:
+    resolution: {integrity: sha512-hEqw9NHZ9YgJ2/0G7CECOeuLQKZi8HjWLkBaSVtOWjygQ9ZD8P7tqeowYs7WrFdKsWEKG7o+IlsPY8jrr0CJpQ==}
+    dependencies:
+      merge-options: 1.0.1
+      posthtml: 0.9.2
+      posthtml-parser: 0.2.1
+      posthtml-render: 1.4.0
+    dev: true
+
+  /posthtml/0.9.2:
+    resolution: {integrity: sha512-spBB5sgC4cv2YcW03f/IAUN1pgDJWNWD8FzkyY4mArLUMJW+KlQhlmUdKAHQuPfb00Jl5xIfImeOsf6YL8QK7Q==}
+    engines: {node: '>=0.10.0'}
+    dependencies:
+      posthtml-parser: 0.2.1
+      posthtml-render: 1.4.0
+    dev: true
+
+  /prr/1.0.1:
+    resolution: {integrity: sha512-yPw4Sng1gWghHQWj0B3ZggWUm4qVbPwPFcRG8KyxiU7J2OHFSoEHKS+EZ3fv5l1t9CyCiop6l/ZYeWbrgoQejw==}
+    dev: true
+    optional: true
+
+  /qrcode/1.5.1:
+    resolution: {integrity: sha512-nS8NJ1Z3md8uTjKtP+SGGhfqmTCs5flU/xR623oI0JX+Wepz9R8UrRVCTBTJm3qGw3rH6jJ6MUHjkDx15cxSSg==}
+    engines: {node: '>=10.13.0'}
+    hasBin: true
+    dependencies:
+      dijkstrajs: 1.0.2
+      encode-utf8: 1.0.3
+      pngjs: 5.0.0
+      yargs: 15.4.1
+    dev: false
+
+  /query-string/4.3.4:
+    resolution: {integrity: sha512-O2XLNDBIg1DnTOa+2XrIwSiXEV8h2KImXUnjhhn2+UsvZ+Es2uyd5CCRTNQlDGbzUQOW3aYCBx9rVA6dzsiY7Q==}
+    engines: {node: '>=0.10.0'}
+    dependencies:
+      object-assign: 4.1.1
+      strict-uri-encode: 1.1.0
+    dev: true
+
+  /queue-microtask/1.2.3:
+    resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==}
+
+  /quick-lru/5.1.1:
+    resolution: {integrity: sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==}
+    engines: {node: '>=10'}
+    dev: false
+
+  /read-cache/1.0.0:
+    resolution: {integrity: sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==}
+    dependencies:
+      pify: 2.3.0
+    dev: false
+
+  /readable-stream/3.6.0:
+    resolution: {integrity: sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==}
+    engines: {node: '>= 6'}
+    dependencies:
+      inherits: 2.0.4
+      string_decoder: 1.3.0
+      util-deprecate: 1.0.2
+    dev: true
+
+  /readdirp/3.6.0:
+    resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==}
+    engines: {node: '>=8.10.0'}
+    dependencies:
+      picomatch: 2.3.1
+
+  /regenerator-runtime/0.13.9:
+    resolution: {integrity: sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA==}
+    dev: false
+
+  /regex-not/1.0.2:
+    resolution: {integrity: sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==}
+    engines: {node: '>=0.10.0'}
+    dependencies:
+      extend-shallow: 3.0.2
+      safe-regex: 1.1.0
+    dev: true
+
+  /repeat-element/1.1.4:
+    resolution: {integrity: sha512-LFiNfRcSu7KK3evMyYOuCzv3L10TW7yC1G2/+StMjK8Y6Vqd2MG7r/Qjw4ghtuCOjFvlnms/iMmLqpvW/ES/WQ==}
+    engines: {node: '>=0.10.0'}
+    dev: true
+
+  /repeat-string/1.6.1:
+    resolution: {integrity: sha512-PV0dzCYDNfRi1jCDbJzpW7jNNDRuCOG/jI5ctQcGKt/clZD+YcPS3yIlWuTJMmESC8aevCFmWJy5wjAFgNqN6w==}
+    engines: {node: '>=0.10'}
+    dev: true
+
+  /require-directory/2.1.1:
+    resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==}
+    engines: {node: '>=0.10.0'}
+    dev: false
+
+  /require-main-filename/2.0.0:
+    resolution: {integrity: sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==}
+    dev: false
+
+  /resize-observer-polyfill/1.5.1:
+    resolution: {integrity: sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg==}
+    dev: false
+
+  /resolve-url/0.2.1:
+    resolution: {integrity: sha512-ZuF55hVUQaaczgOIwqWzkEcEidmlD/xl44x1UZnhOXcYuFN2S6+rcxpG+C1N3So0wvNI3DmJICUFfu2SxhBmvg==}
+    deprecated: https://github.com/lydell/resolve-url#deprecated
+    dev: true
+
+  /resolve/1.22.1:
+    resolution: {integrity: sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==}
+    hasBin: true
+    dependencies:
+      is-core-module: 2.10.0
+      path-parse: 1.0.7
+      supports-preserve-symlinks-flag: 1.0.0
+
+  /ret/0.1.15:
+    resolution: {integrity: sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==}
+    engines: {node: '>=0.12'}
+    dev: true
+
+  /reusify/1.0.4:
+    resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==}
+    engines: {iojs: '>=1.0.0', node: '>=0.10.0'}
+
+  /rollup/2.77.3:
+    resolution: {integrity: sha512-/qxNTG7FbmefJWoeeYJFbHehJ2HNWnjkAFRKzWN/45eNBBF/r8lo992CwcJXEzyVxs5FmfId+vTSTQDb+bxA+g==}
+    engines: {node: '>=10.0.0'}
+    hasBin: true
+    optionalDependencies:
+      fsevents: 2.3.2
+    dev: true
+
+  /run-parallel/1.2.0:
+    resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==}
+    dependencies:
+      queue-microtask: 1.2.3
+
+  /safe-buffer/5.1.2:
+    resolution: {integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==}
+    dev: true
+
+  /safe-buffer/5.2.1:
+    resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==}
+    dev: true
+
+  /safe-regex/1.1.0:
+    resolution: {integrity: sha512-aJXcif4xnaNUzvUuC5gcb46oTS7zvg4jpMTnuqtrEPlR3vFr4pxtdTwaF1Qs3Enjn9HK+ZlwQui+a7z0SywIzg==}
+    dependencies:
+      ret: 0.1.15
+    dev: true
+
+  /safer-buffer/2.1.2:
+    resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==}
+    dev: true
+    optional: true
+
+  /sax/1.2.4:
+    resolution: {integrity: sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==}
+    dev: true
+    optional: true
+
+  /scroll-into-view-if-needed/2.2.29:
+    resolution: {integrity: sha512-hxpAR6AN+Gh53AdAimHM6C8oTN1ppwVZITihix+WqalywBeFcQ6LdQP5ABNl26nX8GTEL7VT+b8lKpdqq65wXg==}
+    dependencies:
+      compute-scroll-into-view: 1.0.17
+    dev: false
+
+  /semver/5.7.1:
+    resolution: {integrity: sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==}
+    hasBin: true
+    dev: true
+    optional: true
+
+  /semver/6.3.0:
+    resolution: {integrity: sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==}
+    hasBin: true
+    dev: true
+
+  /semver/7.3.7:
+    resolution: {integrity: sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==}
+    engines: {node: '>=10'}
+    hasBin: true
+    dependencies:
+      lru-cache: 6.0.0
+    dev: true
+
+  /set-blocking/2.0.0:
+    resolution: {integrity: sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==}
+    dev: false
+
+  /set-value/2.0.1:
+    resolution: {integrity: sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw==}
+    engines: {node: '>=0.10.0'}
+    dependencies:
+      extend-shallow: 2.0.1
+      is-extendable: 0.1.1
+      is-plain-object: 2.0.4
+      split-string: 3.1.0
+    dev: true
+
+  /shallow-equal/1.2.1:
+    resolution: {integrity: sha512-S4vJDjHHMBaiZuT9NPb616CSmLf618jawtv3sufLl6ivK8WocjAo58cXwbRV1cgqxH0Qbv+iUt6m05eqEa2IRA==}
+    dev: false
+
+  /snapdragon-node/2.1.1:
+    resolution: {integrity: sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==}
+    engines: {node: '>=0.10.0'}
+    dependencies:
+      define-property: 1.0.0
+      isobject: 3.0.1
+      snapdragon-util: 3.0.1
+    dev: true
+
+  /snapdragon-util/3.0.1:
+    resolution: {integrity: sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==}
+    engines: {node: '>=0.10.0'}
+    dependencies:
+      kind-of: 3.2.2
+    dev: true
+
+  /snapdragon/0.8.2:
+    resolution: {integrity: sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==}
+    engines: {node: '>=0.10.0'}
+    dependencies:
+      base: 0.11.2
+      debug: 2.6.9
+      define-property: 0.2.5
+      extend-shallow: 2.0.1
+      map-cache: 0.2.2
+      source-map: 0.5.7
+      source-map-resolve: 0.5.3
+      use: 3.1.1
+    transitivePeerDependencies:
+      - supports-color
+    dev: true
+
+  /source-map-js/1.0.2:
+    resolution: {integrity: sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==}
+    engines: {node: '>=0.10.0'}
+
+  /source-map-resolve/0.5.3:
+    resolution: {integrity: sha512-Htz+RnsXWk5+P2slx5Jh3Q66vhQj1Cllm0zvnaY98+NFx+Dv2CF/f5O/t8x+KaNdrdIAsruNzoh/KpialbqAnw==}
+    deprecated: See https://github.com/lydell/source-map-resolve#deprecated
+    dependencies:
+      atob: 2.1.2
+      decode-uri-component: 0.2.0
+      resolve-url: 0.2.1
+      source-map-url: 0.4.1
+      urix: 0.1.0
+    dev: true
+
+  /source-map-url/0.4.1:
+    resolution: {integrity: sha512-cPiFOTLUKvJFIg4SKVScy4ilPPW6rFgMgfuZJPNoDuMs3nC1HbMUycBoJw77xFIp6z1UJQJOfx6C9GMH80DiTw==}
+    deprecated: See https://github.com/lydell/source-map-url#deprecated
+    dev: true
+
+  /source-map/0.5.7:
+    resolution: {integrity: sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==}
+    engines: {node: '>=0.10.0'}
+    dev: true
+
+  /source-map/0.6.1:
+    resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==}
+    engines: {node: '>=0.10.0'}
+
+  /sourcemap-codec/1.4.8:
+    resolution: {integrity: sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==}
+
+  /split-string/3.1.0:
+    resolution: {integrity: sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==}
+    engines: {node: '>=0.10.0'}
+    dependencies:
+      extend-shallow: 3.0.2
+    dev: true
+
+  /stable/0.1.8:
+    resolution: {integrity: sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w==}
+    deprecated: 'Modern JS already guarantees Array#sort() is a stable sort, so this library is deprecated. See the compatibility table on MDN: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort#browser_compatibility'
+    dev: true
+
+  /static-extend/0.1.2:
+    resolution: {integrity: sha512-72E9+uLc27Mt718pMHt9VMNiAL4LMsmDbBva8mxWUCkT07fSzEGMYUCk0XWY6lp0j6RBAG4cJ3mWuZv2OE3s0g==}
+    engines: {node: '>=0.10.0'}
+    dependencies:
+      define-property: 0.2.5
+      object-copy: 0.1.0
+    dev: true
+
+  /strict-uri-encode/1.1.0:
+    resolution: {integrity: sha512-R3f198pcvnB+5IpnBlRkphuE9n46WyVl8I39W/ZUTZLz4nqSP/oLYUrcnJrw462Ds8he4YKMov2efsTIw1BDGQ==}
+    engines: {node: '>=0.10.0'}
+    dev: true
+
+  /string-width/4.2.3:
+    resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==}
+    engines: {node: '>=8'}
+    dependencies:
+      emoji-regex: 8.0.0
+      is-fullwidth-code-point: 3.0.0
+      strip-ansi: 6.0.1
+    dev: false
+
+  /string_decoder/1.3.0:
+    resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==}
+    dependencies:
+      safe-buffer: 5.2.1
+    dev: true
+
+  /strip-ansi/3.0.1:
+    resolution: {integrity: sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg==}
+    engines: {node: '>=0.10.0'}
+    dependencies:
+      ansi-regex: 2.1.1
+    dev: true
+
+  /strip-ansi/6.0.1:
+    resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==}
+    engines: {node: '>=8'}
+    dependencies:
+      ansi-regex: 5.0.1
+    dev: false
+
+  /supports-color/2.0.0:
+    resolution: {integrity: sha512-KKNVtd6pCYgPIKU4cp2733HWYCpplQhddZLBUryaAHou723x+FRzQ5Df824Fj+IyyuiQTRoub4SnIFfIcrp70g==}
+    engines: {node: '>=0.8.0'}
+    dev: true
+
+  /supports-color/3.2.3:
+    resolution: {integrity: sha512-Jds2VIYDrlp5ui7t8abHN2bjAu4LV/q4N2KivFPpGH0lrka0BMq/33AmECUXlKPcHigkNaqfXRENFju+rlcy+A==}
+    engines: {node: '>=0.8.0'}
+    dependencies:
+      has-flag: 1.0.0
+    dev: true
+
+  /supports-color/5.5.0:
+    resolution: {integrity: sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==}
+    engines: {node: '>=4'}
+    dependencies:
+      has-flag: 3.0.0
+    dev: true
+
+  /supports-preserve-symlinks-flag/1.0.0:
+    resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==}
+    engines: {node: '>= 0.4'}
+
+  /svg-baker/1.7.0:
+    resolution: {integrity: sha512-nibslMbkXOIkqKVrfcncwha45f97fGuAOn1G99YwnwTj8kF9YiM6XexPcUso97NxOm6GsP0SIvYVIosBis1xLg==}
+    dependencies:
+      bluebird: 3.7.2
+      clone: 2.1.2
+      he: 1.2.0
+      image-size: 0.5.5
+      loader-utils: 1.4.0
+      merge-options: 1.0.1
+      micromatch: 3.1.0
+      postcss: 5.2.18
+      postcss-prefix-selector: 1.16.0_postcss@5.2.18
+      posthtml-rename-id: 1.0.12
+      posthtml-svg-mode: 1.0.3
+      query-string: 4.3.4
+      traverse: 0.6.6
+    transitivePeerDependencies:
+      - supports-color
+    dev: true
+
+  /svg-tags/1.0.0:
+    resolution: {integrity: sha512-ovssysQTa+luh7A5Weu3Rta6FJlFBBbInjOh722LIt6klpU2/HtdUbszju/G4devcvk8PGt7FCLv5wftu3THUA==}
+    dev: true
+
+  /svgo/2.8.0:
+    resolution: {integrity: sha512-+N/Q9kV1+F+UeWYoSiULYo4xYSDQlTgb+ayMobAXPwMnLvop7oxKMo9OzIrX5x3eS4L4f2UHhc9axXwY8DpChg==}
+    engines: {node: '>=10.13.0'}
+    hasBin: true
+    dependencies:
+      '@trysound/sax': 0.2.0
+      commander: 7.2.0
+      css-select: 4.3.0
+      css-tree: 1.1.3
+      csso: 4.2.0
+      picocolors: 1.0.0
+      stable: 0.1.8
+    dev: true
+
+  /tailwindcss/3.1.8:
+    resolution: {integrity: sha512-YSneUCZSFDYMwk+TGq8qYFdCA3yfBRdBlS7txSq0LUmzyeqRe3a8fBQzbz9M3WS/iFT4BNf/nmw9mEzrnSaC0g==}
+    engines: {node: '>=12.13.0'}
+    hasBin: true
+    dependencies:
+      arg: 5.0.2
+      chokidar: 3.5.3
+      color-name: 1.1.4
+      detective: 5.2.1
+      didyoumean: 1.2.2
+      dlv: 1.1.3
+      fast-glob: 3.2.11
+      glob-parent: 6.0.2
+      is-glob: 4.0.3
+      lilconfig: 2.0.6
+      normalize-path: 3.0.0
+      object-hash: 3.0.0
+      picocolors: 1.0.0
+      postcss: 8.4.16
+      postcss-import: 14.1.0_postcss@8.4.16
+      postcss-js: 4.0.0_postcss@8.4.16
+      postcss-load-config: 3.1.4_postcss@8.4.16
+      postcss-nested: 5.0.6_postcss@8.4.16
+      postcss-selector-parser: 6.0.10
+      postcss-value-parser: 4.2.0
+      quick-lru: 5.1.1
+      resolve: 1.22.1
+    transitivePeerDependencies:
+      - ts-node
+    dev: false
+
+  /to-fast-properties/2.0.0:
+    resolution: {integrity: sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==}
+    engines: {node: '>=4'}
+
+  /to-object-path/0.3.0:
+    resolution: {integrity: sha512-9mWHdnGRuh3onocaHzukyvCZhzvr6tiflAy/JRFXcJX0TjgfWA9pk9t8CMbzmBE4Jfw58pXbkngtBtqYxzNEyg==}
+    engines: {node: '>=0.10.0'}
+    dependencies:
+      kind-of: 3.2.2
+    dev: true
+
+  /to-regex-range/2.1.1:
+    resolution: {integrity: sha512-ZZWNfCjUokXXDGXFpZehJIkZqq91BcULFq/Pi7M5i4JnxXdhMKAK682z8bCW3o8Hj1wuuzoKcW3DfVzaP6VuNg==}
+    engines: {node: '>=0.10.0'}
+    dependencies:
+      is-number: 3.0.0
+      repeat-string: 1.6.1
+    dev: true
+
+  /to-regex-range/5.0.1:
+    resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==}
+    engines: {node: '>=8.0'}
+    dependencies:
+      is-number: 7.0.0
+
+  /to-regex/3.0.2:
+    resolution: {integrity: sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==}
+    engines: {node: '>=0.10.0'}
+    dependencies:
+      define-property: 2.0.2
+      extend-shallow: 3.0.2
+      regex-not: 1.0.2
+      safe-regex: 1.1.0
+    dev: true
+
+  /traverse/0.6.6:
+    resolution: {integrity: sha512-kdf4JKs8lbARxWdp7RKdNzoJBhGUcIalSYibuGyHJbmk40pOysQ0+QPvlkCOICOivDWU2IJo2rkrxyTK2AH4fw==}
+    dev: true
+
+  /tslib/2.4.0:
+    resolution: {integrity: sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==}
+
+  /typescript/4.7.4:
+    resolution: {integrity: sha512-C0WQT0gezHuw6AdY1M2jxUO83Rjf0HP7Sk1DtXj6j1EwkQNZrHAg2XPWlq62oqEhYvONq5pkC2Y9oPljWToLmQ==}
+    engines: {node: '>=4.2.0'}
+    hasBin: true
+
+  /union-value/1.0.1:
+    resolution: {integrity: sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg==}
+    engines: {node: '>=0.10.0'}
+    dependencies:
+      arr-union: 3.1.0
+      get-value: 2.0.6
+      is-extendable: 0.1.1
+      set-value: 2.0.1
+    dev: true
+
+  /universalify/2.0.0:
+    resolution: {integrity: sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==}
+    engines: {node: '>= 10.0.0'}
+    dev: true
+
+  /unplugin-vue-components/0.22.4_vite@3.0.8+vue@3.2.37:
+    resolution: {integrity: sha512-2rRZcM9OnJGXnYxQNfaceEYuPeVACcWySIjy8WBwIiN3onr980TmA3XE5pRJFt8zoQrUA+c46oyIq96noLqrEQ==}
+    engines: {node: '>=14'}
+    peerDependencies:
+      '@babel/parser': ^7.15.8
+      vue: 2 || 3
+    peerDependenciesMeta:
+      '@babel/parser':
+        optional: true
+    dependencies:
+      '@antfu/utils': 0.5.2
+      '@rollup/pluginutils': 4.2.1
+      chokidar: 3.5.3
+      debug: 4.3.4
+      fast-glob: 3.2.11
+      local-pkg: 0.4.2
+      magic-string: 0.26.2
+      minimatch: 5.1.0
+      resolve: 1.22.1
+      unplugin: 0.9.2_vite@3.0.8
+      vue: 3.2.37
+    transitivePeerDependencies:
+      - esbuild
+      - rollup
+      - supports-color
+      - vite
+      - webpack
+    dev: true
+
+  /unplugin-vue-setup-extend-plus/0.3.2_vite@3.0.8:
+    resolution: {integrity: sha512-wvX/mgVriD6wviBEUSmX1S9uEcd4P3XWR5L6M++TFUS1rU/j0vvyld1D56FXWl7CT41Dll5S5WbD6sQNaMNNUA==}
+    dependencies:
+      '@vue/compiler-sfc': 3.2.37
+      magic-string: 0.26.2
+      unplugin: 0.6.3_vite@3.0.8
+    transitivePeerDependencies:
+      - esbuild
+      - rollup
+      - vite
+      - webpack
+    dev: true
+
+  /unplugin/0.6.3_vite@3.0.8:
+    resolution: {integrity: sha512-CoW88FQfCW/yabVc4bLrjikN9HC8dEvMU4O7B6K2jsYMPK0l6iAnd9dpJwqGcmXJKRCU9vwSsy653qg+RK0G6A==}
+    peerDependencies:
+      esbuild: '>=0.13'
+      rollup: ^2.50.0
+      vite: ^2.3.0
+      webpack: 4 || 5
+    peerDependenciesMeta:
+      esbuild:
+        optional: true
+      rollup:
+        optional: true
+      vite:
+        optional: true
+      webpack:
+        optional: true
+    dependencies:
+      chokidar: 3.5.3
+      vite: 3.0.8_less@4.1.3
+      webpack-sources: 3.2.3
+      webpack-virtual-modules: 0.4.4
+    dev: true
+
+  /unplugin/0.9.2_vite@3.0.8:
+    resolution: {integrity: sha512-Wo9lx9rA0O3AWhLYYNZ6DgnNhL5t5r7kV/Jg5BXjTQtY+DEWrD8VLFSaOmKN0tgqZCMqZ+XrzgOe/3DzIO4/SA==}
+    peerDependencies:
+      esbuild: '>=0.13'
+      rollup: ^2.50.0
+      vite: ^2.3.0 || ^3.0.0-0
+      webpack: 4 || 5
+    peerDependenciesMeta:
+      esbuild:
+        optional: true
+      rollup:
+        optional: true
+      vite:
+        optional: true
+      webpack:
+        optional: true
+    dependencies:
+      acorn: 8.8.0
+      chokidar: 3.5.3
+      vite: 3.0.8_less@4.1.3
+      webpack-sources: 3.2.3
+      webpack-virtual-modules: 0.4.4
+    dev: true
+
+  /unset-value/1.0.0:
+    resolution: {integrity: sha512-PcA2tsuGSF9cnySLHTLSh2qrQiJ70mn+r+Glzxv2TWZblxsxCC52BDlZoPCsz7STd9pN7EZetkWZBAvk4cgZdQ==}
+    engines: {node: '>=0.10.0'}
+    dependencies:
+      has-value: 0.3.1
+      isobject: 3.0.1
+    dev: true
+
+  /update-browserslist-db/1.0.5_browserslist@4.21.3:
+    resolution: {integrity: sha512-dteFFpCyvuDdr9S/ff1ISkKt/9YZxKjI9WlRR99c180GaztJtRa/fn18FdxGVKVsnPY7/a/FDN68mcvUmP4U7Q==}
+    hasBin: true
+    peerDependencies:
+      browserslist: '>= 4.21.0'
+    dependencies:
+      browserslist: 4.21.3
+      escalade: 3.1.1
+      picocolors: 1.0.0
+    dev: true
+
+  /urix/0.1.0:
+    resolution: {integrity: sha512-Am1ousAhSLBeB9cG/7k7r2R0zj50uDRlZHPGbazid5s9rlF1F/QKYObEKSIunSjIOkJZqwRRLpvewjEkM7pSqg==}
+    deprecated: Please see https://github.com/lydell/urix#deprecated
+    dev: true
+
+  /use/3.1.1:
+    resolution: {integrity: sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==}
+    engines: {node: '>=0.10.0'}
+    dev: true
+
+  /util-deprecate/1.0.2:
+    resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==}
+
+  /vary/1.1.2:
+    resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==}
+    engines: {node: '>= 0.8'}
+    dev: true
+
+  /vite-plugin-svg-icons/2.0.1_vite@3.0.8:
+    resolution: {integrity: sha512-6ktD+DhV6Rz3VtedYvBKKVA2eXF+sAQVaKkKLDSqGUfnhqXl3bj5PPkVTl3VexfTuZy66PmINi8Q6eFnVfRUmA==}
+    peerDependencies:
+      vite: '>=2.0.0'
+    dependencies:
+      '@types/svgo': 2.6.4
+      cors: 2.8.5
+      debug: 4.3.4
+      etag: 1.8.1
+      fs-extra: 10.1.0
+      pathe: 0.2.0
+      svg-baker: 1.7.0
+      svgo: 2.8.0
+      vite: 3.0.8_less@4.1.3
+    transitivePeerDependencies:
+      - supports-color
+    dev: true
+
+  /vite/3.0.8_less@4.1.3:
+    resolution: {integrity: sha512-AOZ4eN7mrkJiOLuw8IA7piS4IdOQyQCA81GxGsAQvAZzMRi9ZwGB3TOaYsj4uLAWK46T5L4AfQ6InNGlxX30IQ==}
+    engines: {node: ^14.18.0 || >=16.0.0}
+    hasBin: true
+    peerDependencies:
+      less: '*'
+      sass: '*'
+      stylus: '*'
+      terser: ^5.4.0
+    peerDependenciesMeta:
+      less:
+        optional: true
+      sass:
+        optional: true
+      stylus:
+        optional: true
+      terser:
+        optional: true
+    dependencies:
+      esbuild: 0.14.54
+      less: 4.1.3
+      postcss: 8.4.16
+      resolve: 1.22.1
+      rollup: 2.77.3
+    optionalDependencies:
+      fsevents: 2.3.2
+    dev: true
+
+  /vue-demi/0.13.8_vue@3.2.37:
+    resolution: {integrity: sha512-Vy1zbZhCOdsmvGR6tJhAvO5vhP7eiS8xkbYQSoVa7o6KlIy3W8Rc53ED4qI4qpeRDjv3mLfXSEpYU6Yq4pgXRg==}
+    engines: {node: '>=12'}
+    hasBin: true
+    requiresBuild: true
+    peerDependencies:
+      '@vue/composition-api': ^1.0.0-rc.1
+      vue: ^3.0.0-0 || ^2.6.0
+    peerDependenciesMeta:
+      '@vue/composition-api':
+        optional: true
+    dependencies:
+      vue: 3.2.37
+    dev: false
+
+  /vue-qrcode/2.0.0_qrcode@1.5.1+vue@3.2.37:
+    resolution: {integrity: sha512-j+kQQCWm0454zyPyAl1tyfUlo1TNih1nE05k+qepOQE91wdfH98I9mxbtNfVk4FZlVi5cjLjLkeVese7amxnMQ==}
+    peerDependencies:
+      qrcode: ^1.0.0
+      vue: ^2.7.0 || ^3.0.0
+    dependencies:
+      qrcode: 1.5.1
+      tslib: 2.4.0
+      vue: 3.2.37
+    dev: false
+
+  /vue-router/4.1.3_vue@3.2.37:
+    resolution: {integrity: sha512-XvK81bcYglKiayT7/vYAg/f36ExPC4t90R/HIpzrZ5x+17BOWptXLCrEPufGgZeuq68ww4ekSIMBZY1qdUdfjA==}
+    peerDependencies:
+      vue: ^3.2.0
+    dependencies:
+      '@vue/devtools-api': 6.2.1
+      vue: 3.2.37
+    dev: false
+
+  /vue-tsc/0.39.5_typescript@4.7.4:
+    resolution: {integrity: sha512-jhTsrKhZkafpIeN4Cbhr1K53hNfa/oesSrlh7hUaeHyCk55VhZT6oJkwJbtqN4MYkWZIwPrm3/xTwsELuf2ocg==}
+    hasBin: true
+    peerDependencies:
+      typescript: '*'
+    dependencies:
+      '@volar/vue-language-core': 0.39.5
+      '@volar/vue-typescript': 0.39.5
+      typescript: 4.7.4
+    dev: true
+
+  /vue-types/3.0.2_vue@3.2.37:
+    resolution: {integrity: sha512-IwUC0Aq2zwaXqy74h4WCvFCUtoV0iSWr0snWnE9TnU18S66GAQyqQbRf2qfJtUuiFsBf6qp0MEwdonlwznlcrw==}
+    engines: {node: '>=10.15.0'}
+    peerDependencies:
+      vue: ^3.0.0
+    dependencies:
+      is-plain-object: 3.0.1
+      vue: 3.2.37
+    dev: false
+
+  /vue/3.2.37:
+    resolution: {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@3.2.37
+      '@vue/shared': 3.2.37
+
+  /warning/4.0.3:
+    resolution: {integrity: sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==}
+    dependencies:
+      loose-envify: 1.4.0
+    dev: false
+
+  /webpack-sources/3.2.3:
+    resolution: {integrity: sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==}
+    engines: {node: '>=10.13.0'}
+    dev: true
+
+  /webpack-virtual-modules/0.4.4:
+    resolution: {integrity: sha512-h9atBP/bsZohWpHnr+2sic8Iecb60GxftXsWNLLLSqewgIsGzByd2gcIID4nXcG+3tNe4GQG3dLcff3kXupdRA==}
+    dev: true
+
+  /which-module/2.0.0:
+    resolution: {integrity: sha512-B+enWhmw6cjfVC7kS8Pj9pCrKSc5txArRyaYGe088shv/FGWH+0Rjx/xPgtsWfsUtS27FkP697E4DDhgrgoc0Q==}
+    dev: false
+
+  /wrap-ansi/6.2.0:
+    resolution: {integrity: sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==}
+    engines: {node: '>=8'}
+    dependencies:
+      ansi-styles: 4.3.0
+      string-width: 4.2.3
+      strip-ansi: 6.0.1
+    dev: false
+
+  /xtend/4.0.2:
+    resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==}
+    engines: {node: '>=0.4'}
+    dev: false
+
+  /y18n/4.0.3:
+    resolution: {integrity: sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==}
+    dev: false
+
+  /yallist/4.0.0:
+    resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==}
+    dev: true
+
+  /yaml/1.10.2:
+    resolution: {integrity: sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==}
+    engines: {node: '>= 6'}
+    dev: false
+
+  /yargs-parser/18.1.3:
+    resolution: {integrity: sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==}
+    engines: {node: '>=6'}
+    dependencies:
+      camelcase: 5.3.1
+      decamelize: 1.2.0
+    dev: false
+
+  /yargs/15.4.1:
+    resolution: {integrity: sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==}
+    engines: {node: '>=8'}
+    dependencies:
+      cliui: 6.0.0
+      decamelize: 1.2.0
+      find-up: 4.1.0
+      get-caller-file: 2.0.5
+      require-directory: 2.1.1
+      require-main-filename: 2.0.0
+      set-blocking: 2.0.0
+      string-width: 4.2.3
+      which-module: 2.0.0
+      y18n: 4.0.3
+      yargs-parser: 18.1.3
+    dev: false

+ 15 - 0
postcss.config.cjs

@@ -0,0 +1,15 @@
+module.exports = {
+  plugins: {
+    tailwindcss: {},
+    autoprefixer: {},
+    "postcss-pxtorem": {
+      rootValue: 12.8,
+      unitPrecision: 5,
+      propList: ['*'],
+      selectorBlackList: [],
+      replace: true,
+      mediaQuery: false,
+      minPixelValue: 0
+    }
+  },
+}

+ 1 - 0
public/vite.svg

@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>

+ 13 - 0
src/App.vue

@@ -0,0 +1,13 @@
+<template>
+  <router-view></router-view>
+</template>
+
+<script setup lang="ts" name="App">
+import { getUserInfoHttp } from "./apis/user";
+import { getLoginResult } from "./utils/auth";
+if (getLoginResult()) {
+  getUserInfoHttp();
+}
+</script>
+
+<style scoped></style>

+ 23 - 0
src/apis/common.ts

@@ -0,0 +1,23 @@
+import request from "@/plugins/request";
+import { cacheLoginResult } from "@/utils/auth";
+import { getUserInfoHttp } from "./user";
+
+/**
+ * @description 登录
+ */
+export const loginHttp = (data: LoginModel) => {
+  return request
+    .post<LoginModel, LoginResult>("/api/auth/login/admin", data, {
+      noAuth: true,
+    })
+    .then(cacheLoginResult)
+    .then((response) => (getUserInfoHttp(), response));
+};
+
+/**
+ * @description 退出登录
+ */
+
+export const loginOutHttp = () => {
+  return request.post("/api/auth/logout", null, { noToast: true });
+};

+ 25 - 0
src/apis/exam.ts

@@ -0,0 +1,25 @@
+import request from "@/plugins/request";
+
+/**
+ * @description 查询考试列表
+ */
+export const getExamListHttp = (data: FetchExamListQuery) => {
+  return request.post<FetchExamListQuery, MultiplePageData<ExamListInfo>>(
+    "/api/exam/page",
+    data
+  );
+};
+
+/**
+ * @description 新增/修改考试信息
+ */
+export const editExamInfoHttp = (data: BaseExamInfo) => {
+  return request.post<BaseExamInfo, null>("/api/exam/save", data);
+};
+
+/**
+ * @description 考试数据同步
+ */
+export const syncExamDataHttp = (data: { schoolId: string }) => {
+  return request.post<unknown, null>("/api/exam/sync", data);
+};

+ 28 - 0
src/apis/school.ts

@@ -0,0 +1,28 @@
+import request from "@/plugins/request";
+
+/**
+ * @description 查询学校列表
+ */
+export const getSchoolListHttp = (data: FetchSchoolListQuery) => {
+  return request.post<FetchSchoolListQuery, MultiplePageData<SchoolListInfo>>(
+    "/api/school/page",
+    data
+  );
+};
+
+/**
+ * @description 新增/修改学校信息
+ */
+export const editSchoolInfoHttp = (data: BaseSchoolInfo) => {
+  return request.post<BaseSchoolInfo, null>("/api/school/save", data);
+};
+
+/**
+ * @description 启用/禁用学校
+ */
+export const updateSchoolStatusHttp = (data: {
+  enable: boolean;
+  ids: number[];
+}) => {
+  return request.post<unknown, null>("/api/school/toggle", data);
+};

+ 65 - 0
src/apis/struct.ts

@@ -0,0 +1,65 @@
+import request from "@/plugins/request";
+
+/**
+ * @description 查询科目列表
+ */
+export const getSubjectsListHttp = (data: FetchSubjectsListQuery) => {
+  return request.post<
+    FetchSubjectsListQuery,
+    MultiplePageData<SubjectsListInfo>
+  >("/api/paper/page", data);
+};
+
+/**
+ * @description 导入科目
+ */
+export const importSubjectsHttp = (data: { examId: string; file: File }) => {
+  /** multipart/form-data */
+  return request.post<unknown, null>("/api/paper/import-course", data, {
+    headers: {
+      "content-type": "multipart/form-data",
+    },
+  });
+};
+
+/**
+ * @description 导入试卷结构
+ */
+
+export const importPaperStructHttp = (data: { examId: string; file: File }) => {
+  return request.post<unknown, null>("/api/paper/import-struct-subject", data, {
+    headers: {
+      "content-type": "multipart/form-data",
+    },
+  });
+};
+
+/**
+ * @description 导出试卷结构
+ */
+
+export const downloadPaperStructHttp = (data: FetchSubjectsListQuery) => {
+  return request.post<Blob>("/api/paper/export-subjective", data, {
+    download: true,
+  });
+};
+
+/**
+ * @description 下载科目导入模板
+ */
+export const downloadSubjectTemplateHttp = () => {
+  return request.post<Blob>("/api/paper/template-course", null, {
+    responseType: "blob",
+    download: true,
+  });
+};
+
+/**
+ * @description 下载试卷结构导入模板
+ */
+export const downloadPaperStructTemplateHttp = () => {
+  return request.post<Blob>("/api/paper/template-struct", null, {
+    responseType: "blob",
+    download: true,
+  });
+};

+ 74 - 0
src/apis/user.ts

@@ -0,0 +1,74 @@
+import request from "@/plugins/request";
+import { getUserInfo, cacheUserInfo } from "@/utils/auth";
+
+/**
+ * @description 获取当前用户信息
+ */
+export const getUserInfoHttp = (): Promise<SystemUserInfo> => {
+  const cachedUserInfo = getUserInfo();
+
+  return cachedUserInfo
+    ? Promise.resolve(cacheUserInfo(cachedUserInfo))
+    : request
+        .post<null, SystemUserInfo>("/api/user/my/info")
+        .then(cacheUserInfo);
+};
+
+/**
+ * @description 获取用户列表
+ */
+
+export const getUserListHttp = (data: FetchUserListQuery) => {
+  return request.post<FetchUserListQuery, MultiplePageData<UserInfo>>(
+    "/api/user/page",
+    data
+  );
+};
+
+/**
+ * @description 新增/修改用户信息
+ */
+export const editUserInfoHttp = (data: EditUserInfo) => {
+  return request.post<BaseSchoolInfo, null>("/api/user/save", data);
+};
+
+/**
+ * @description 启用/禁用用户
+ */
+export const updateUserStatusHttp = (data: {
+  enable: boolean;
+  ids: number[];
+}) => {
+  return request.post<unknown, null>("/api/user/toggle", data);
+};
+
+/**
+ * @description 重置用户密码
+ */
+export const resetUserPwdHttp = (data: { passwd: string; userId: string }) => {
+  return request.post<unknown, null>("/api/user/reset-passwd", data);
+};
+
+/**
+ * @description 导入用户
+ */
+
+export const importUserHttp = (data: FormData) => {
+  /** multipart/form-data */
+  return request.post<unknown, null>("/api/user/import", data, {
+    headers: {
+      "Content-Type": "multipart/form-data",
+    },
+  });
+};
+
+/**
+ * @description 下载导入模板
+ */
+
+export const downloadImportUserHttp = () => {
+  return request.post<Blob>("/api/user/template", null, {
+    responseType: "blob",
+    download: true,
+  });
+};

+ 11 - 0
src/assets/icons/add-icon.svg

@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="12px" height="12px" viewBox="0 0 12 12" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+    <title>新增</title>
+    <g id="页面-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
+        <g id="icon" transform="translate(-27.000000, -122.000000)" stroke="#FFFFFF">
+            <g transform="translate(27.000000, 122.000000)" id="新增">
+                <path d=""></path>
+            </g>
+        </g>
+    </g>
+</svg>

+ 11 - 0
src/assets/icons/arrow-down-icon.svg

@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="8px" height="5px" viewBox="0 0 8 5" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+    <title>下拉</title>
+    <g id="页面-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" stroke-linecap="round" stroke-linejoin="round">
+        <g id="icon" transform="translate(-111.000000, -126.000000)" stroke="#687C9E" stroke-width="1.5">
+            <g id="下拉" transform="translate(111.000000, 126.000000)">
+                <polyline id="矩形" points="7 1 4 4 1 1"></polyline>
+            </g>
+        </g>
+    </g>
+</svg>

+ 11 - 0
src/assets/icons/download-icon.svg

@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="12px" height="10px" viewBox="0 0 12 10" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+    <title>导出</title>
+    <g id="页面-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
+        <g id="icon" transform="translate(-83.000000, -123.000000)" fill="#FFFFFF">
+            <g transform="translate(83.000000, 123.000000)" id="导出">
+                <path d="M0.5,10 C0.223857625,10 3.38176876e-17,9.77614237 0,9.5 L0,6.5 C-3.38176876e-17,6.22385763 0.223857625,6 0.5,6 C0.776142375,6 1,6.22385763 1,6.5 L1,9 L11,9 L11,6.5 C11,6.22385763 11.2238576,6 11.5,6 C11.7761424,6 12,6.22385763 12,6.5 L12,9.5 L12,9.5 C12,9.74545989 11.8231248,9.94960837 11.5898756,9.99194433 L11.5,10 L11.5,10 L0.5,10 Z M6,0 L9,4 L7,4 L7,8 L5,8 L5,4 L3,4 L6,0 Z"></path>
+            </g>
+        </g>
+    </g>
+</svg>

+ 12 - 0
src/assets/icons/exam-icon.svg

@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="16px" height="16px" viewBox="0 0 16 16" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+    <title>考试批次-默认</title>
+    <g id="页面-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
+        <g id="icon" transform="translate(-61.000000, -28.000000)" stroke="#5D7092" stroke-width="2">
+            <g transform="translate(61.000000, 28.000000)" id="矩形">
+                <path d="M14,1 C14.2761424,1 14.5261424,1.11192881 14.7071068,1.29289322 C14.8880712,1.47385763 15,1.72385763 15,2 L15,2 L15,11 C15,11.2761424 14.8880712,11.5261424 14.7071068,11.7071068 C14.5261424,11.8880712 14.2761424,12 14,12 L14,12 L5,12 C4.72385763,12 4.47385763,11.8880712 4.29289322,11.7071068 C4.11192881,11.5261424 4,11.2761424 4,11 L4,11 L4,2 C4,1.72385763 4.11192881,1.47385763 4.29289322,1.29289322 C4.47385763,1.11192881 4.72385763,1 5,1 L5,1 Z" fill="#CED4DE"></path>
+                <path d="M11,4 C11.2761424,4 11.5261424,4.11192881 11.7071068,4.29289322 C11.8880712,4.47385763 12,4.72385763 12,5 L12,5 L12,14 C12,14.2761424 11.8880712,14.5261424 11.7071068,14.7071068 C11.5261424,14.8880712 11.2761424,15 11,15 L11,15 L2,15 C1.72385763,15 1.47385763,14.8880712 1.29289322,14.7071068 C1.11192881,14.5261424 1,14.2761424 1,14 L1,14 L1,5 C1,4.72385763 1.11192881,4.47385763 1.29289322,4.29289322 C1.47385763,4.11192881 1.72385763,4 2,4 L2,4 Z" fill="#FFFFFF"></path>
+            </g>
+        </g>
+    </g>
+</svg>

+ 13 - 0
src/assets/icons/exit-icon.svg

@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="16px" height="16px" viewBox="0 0 16 16" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+    <title>退出登录</title>
+    <g id="页面-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
+        <g id="icon" transform="translate(-157.000000, -59.000000)">
+            <g transform="translate(157.000000, 59.000000)" id="矩形">
+                <path d="M8,1 C9.93299662,1 11.6829966,1.78350169 12.9497475,3.05025253 C14.2164983,4.31700338 15,6.06700338 15,8 C15,9.93299662 14.2164983,11.6829966 12.9497475,12.9497475 C11.6829966,14.2164983 9.93299662,15 8,15 C6.06700338,15 4.31700338,14.2164983 3.05025253,12.9497475 C1.78350169,11.6829966 1,9.93299662 1,8 C1,6.06700338 1.78350169,4.31700338 3.05025253,3.05025253 C4.31700338,1.78350169 6.06700338,1 8,1 Z" stroke="currentColor" stroke-width="2"></path>
+                <rect fill="currentColor" x="4" y="7" width="4" height="2"></rect>
+                <polygon fill="currentColor" points="8 5 12 8 8 11"></polygon>
+            </g>
+        </g>
+    </g>
+</svg>

+ 17 - 0
src/assets/icons/paper-icon.svg

@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="16px" height="16px" viewBox="0 0 16 16" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+    <title>试卷结构-默认</title>
+    <g id="页面-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" stroke-linejoin="round">
+        <g id="icon" transform="translate(-93.000000, -28.000000)" stroke-width="2">
+            <g transform="translate(93.000000, 28.000000)" id="编组-6">
+                <g transform="translate(1.000000, 1.000000)">
+                    <polygon id="矩形备份-10" stroke="#CED4DE" points="0 3 7 -1.42053036e-13 7 8 0 11"></polygon>
+                    <polygon id="矩形备份-9" stroke="#CED4DE" points="7 -1.42053036e-13 14 3 14 11 7 8"></polygon>
+                    <polygon id="矩形" stroke="currentColor" points="7 6 14 3 14 11 7 14"></polygon>
+                    <polygon id="矩形备份-9" stroke="currentColor" points="0 3 7 6 7 14 0 11"></polygon>
+                    <polygon id="矩形" stroke="currentColor" points="0 3 7 -1.42053036e-13 14 3 7 6"></polygon>
+                </g>
+            </g>
+        </g>
+    </g>
+</svg>

+ 13 - 0
src/assets/icons/pwd-icon.svg

@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="16px" height="16px" viewBox="0 0 16 16" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+    <title>密码</title>
+    <g id="页面-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
+        <g id="icon" transform="translate(-157.000000, -28.000000)">
+            <g transform="translate(157.000000, 28.000000)" id="矩形">
+                <path d="M9,1 C9.55228475,1 10.0522847,1.22385763 10.4142136,1.58578644 C10.7761424,1.94771525 11,2.44771525 11,3 L11,3 L11,5 L5,5 L5,3 C5,2.44771525 5.22385763,1.94771525 5.58578644,1.58578644 C5.94771525,1.22385763 6.44771525,1 7,1 L7,1 Z" stroke="currentColor" stroke-width="2"></path>
+                <rect stroke="currentColor" stroke-width="2" x="1" y="5" width="14" height="10" rx="4"></rect>
+                <rect fill="currentColor" x="9" y="10" width="3" height="3" rx="1.5"></rect>
+            </g>
+        </g>
+    </g>
+</svg>

+ 11 - 0
src/assets/icons/school-icon.svg

@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="16px" height="14px" viewBox="0 0 16 14" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+    <title>学校管理-默认</title>
+    <g id="页面-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
+        <g id="管理端-D1" transform="translate(-32.000000, -108.000000)" fill="currentColor" fill-rule="nonzero">
+            <g id="icon" transform="translate(32.000000, 108.000000)">
+                <path d="M7.86019018,10.2175831 C7.94859933,10.2601719 8.05140067,10.2601719 8.14186584,10.2175831 L12.9550244,7.85390524 L12.9550244,11.239714 C12.9550244,11.3696099 12.8851195,11.4888585 12.772038,11.5484828 L8.14803392,13.9632672 C8.05551272,14.0122443 7.94654331,14.0122443 7.85196608,13.9632672 L1.99845798,11.5506122 C1.88537651,11.4931173 1.8154716,11.3738687 1.8154716,11.2418435 L1.8154716,7.85390524 L7.86019018,10.2175831 Z M15.002827,3.98897255 L15.002827,13.3606358 C15.002827,13.7119933 14.7273195,13.9973382 14.388075,13.9973382 C14.0488306,13.9973382 13.7733231,13.7119933 13.7733231,13.3606358 L13.7733231,4.25302304 L12.9570804,4.45106092 L12.9570804,6.80196213 L8.00205603,9.23378204 L1.8154716,6.80196213 L1.8154716,4.37227166 L0.211770753,3.8356529 C0.0842970959,3.78454635 0,3.65677998 0,3.51623698 C0,3.37569397 0.0842970959,3.2479276 0.211770753,3.19682105 L7.87869442,0.0239561944 C7.95682344,-0.00798539813 8.04317656,-0.00798539813 8.12130558,0.0239561944 L15.7902853,3.19682105 C15.9157029,3.25005704 16,3.37569397 16,3.51623698 C16,3.65677998 15.9177589,3.78454635 15.7902853,3.8356529 L15.002827,3.98897255 Z" id="学校管理"></path>
+            </g>
+        </g>
+    </g>
+</svg>

+ 11 - 0
src/assets/icons/upload-icon.svg

@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="12px" height="10px" viewBox="0 0 12 10" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+    <title>导入</title>
+    <g id="页面-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
+        <g id="icon" transform="translate(-55.000000, -123.000000)" fill="#FFFFFF">
+            <g transform="translate(55.000000, 123.000000)" id="导入">
+                <path d="M0.5,10 C0.223857625,10 3.38176876e-17,9.77614237 0,9.5 L0,6.5 C-3.38176876e-17,6.22385763 0.223857625,6 0.5,6 C0.776142375,6 1,6.22385763 1,6.5 L1,9 L11,9 L11,6.5 C11,6.22385763 11.2238576,6 11.5,6 C11.7761424,6 12,6.22385763 12,6.5 L12,9.5 L12,9.5 C12,9.74545989 11.8231248,9.94960837 11.5898756,9.99194433 L11.5,10 L11.5,10 L0.5,10 Z M7,0 L7,4 L9,4 L6,8 L3,4 L5,4 L5,0 L7,0 Z"></path>
+            </g>
+        </g>
+    </g>
+</svg>

+ 12 - 0
src/assets/icons/user-icon.svg

@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="16px" height="16px" viewBox="0 0 16 16" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+    <title>用户管理-默认</title>
+    <g id="页面-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
+        <g id="icon" transform="translate(-125.000000, -28.000000)">
+            <g transform="translate(125.000000, 28.000000)">
+                <path d="M8,10 C9.0543618,10 9.91816512,10.8158778 9.99451426,11.8507377 L10,12 L6,12 C6,10.8954305 6.8954305,10 8,10 Z M8,4 C9.65685425,4 11,5.34314575 11,7 C11,8.65685425 9.65685425,10 8,10 C6.34314575,10 5,8.65685425 5,7 C5,5.34314575 6.34314575,4 8,4 Z" id="形状结合" fill="currentColor"></path>
+                <path d="M8,1 C9.93299662,1 11.6829966,1.78350169 12.9497475,3.05025253 C14.2164983,4.31700338 15,6.06700338 15,8 C15,9.93299662 14.2164983,11.6829966 12.9497475,12.9497475 C11.6829966,14.2164983 9.93299662,15 8,15 C6.06700338,15 4.31700338,14.2164983 3.05025253,12.9497475 C1.78350169,11.6829966 1,9.93299662 1,8 C1,6.06700338 1.78350169,4.31700338 3.05025253,3.05025253 C4.31700338,1.78350169 6.06700338,1 8,1 Z" id="矩形" stroke="currentColor" stroke-width="2"></path>
+            </g>
+        </g>
+    </g>
+</svg>

BIN
src/assets/images/layout/user-info-bg.png


BIN
src/assets/images/layout/web-logo.png


BIN
src/assets/images/login/login-banner.png


+ 23 - 0
src/assets/less/antd.modal.less

@@ -0,0 +1,23 @@
+.ant-modal {
+  .ant-modal-header {
+    padding: 12.5px 24px;
+  }
+  .ant-modal-body {
+    padding: 24px;
+    padding-bottom: 0;
+  }
+  .ant-modal-footer {
+    border: none;
+    padding: 24px;
+    .ant-btn {
+      width: 80px;
+      height: 36px;
+    }
+  }
+  .operation-group {
+    padding: 24px 0;
+    .ant-btn ~ .ant-btn {
+      margin-left: 12px;
+    }
+  }
+}

+ 41 - 0
src/assets/less/antd.table.less

@@ -0,0 +1,41 @@
+.ant-table {
+  .ant-table-container {
+    .ant-table-thead {
+      tr:first-child {
+        .ant-table-cell {
+          border: none;
+          &:before {
+            display: none;
+          }
+          &:first-child {
+            border-radius: @border-radius-base 0 0 @border-radius-base;
+          }
+          &:last-child {
+            border-radius: 0 @border-radius-base @border-radius-base 0;
+          }
+        }
+      }
+    }
+    .ant-table-tbody {
+      .ant-table-row {
+        .ant-table-cell {
+          border: none;
+          font-weight: bold;
+          color: @font-color;
+          padding: 6px 15px;
+          &:first-child {
+            border-radius: @border-radius-base 0 0 @border-radius-base;
+          }
+          &:last-child {
+            border-radius: 0 @border-radius-base @border-radius-base 0;
+          }
+        }
+        &.table-striped {
+          .ant-table-cell {
+            background-color: @bg-color;
+          }
+        }
+      }
+    }
+  }
+}

+ 1 - 0
src/assets/less/antd.theme.less

@@ -0,0 +1 @@
+@import "ant-design-vue/dist/antd.less";

+ 41 - 0
src/assets/less/global.less

@@ -0,0 +1,41 @@
+@import "./antd.table.less";
+@import "./antd.modal.less";
+
+body {
+  font-size: @font-size-base;
+  color: @font-color;
+  background-color: @bg-color;
+  line-height: 1;
+  min-height: 100vh;
+}
+
+#app {
+  height: 100%;
+}
+
+a {
+  color: @font-color;
+}
+
+svg {
+  vertical-align: initial;
+}
+
+::-webkit-scrollbar{
+  width:10px;
+  height:10px;
+}
+::-webkit-scrollbar-track{
+  background: rgb(239, 239, 239);
+  border-radius:2px;
+}
+::-webkit-scrollbar-thumb{
+  background: #bfbfbf;
+  border-radius:10px;
+}
+::-webkit-scrollbar-thumb:hover{
+  background: #333;
+}
+::-webkit-scrollbar-corner{
+  background: #179a16;
+}

+ 23 - 0
src/assets/less/var.less

@@ -0,0 +1,23 @@
+/** antd */
+@table-header-bg: @bg-color;
+@table-header-color: @font-color;
+@table-font-size: @font-size-small;
+@select-background: @bg-color;
+@border-radius-base: 8px;
+@font-size-base: 14px;
+@font-size-small: 12px;
+@height-base: 32px;
+@input-bg: @bg-color;
+
+@white: #fff;
+@black: #363e4d;
+
+@bg-color: #ebeff5;
+
+@primary-color: #5b8ff9;
+
+@font-color: #5d7092;
+
+@font-light-color: #3273f9;
+
+@border-color: #eef1f7;

+ 25 - 0
src/components/block/index.vue

@@ -0,0 +1,25 @@
+<template>
+  <div :class="['common-section', { gray: props.theme !== 'white' }]">
+    <slot />
+  </div>
+</template>
+
+<script setup lang="ts" name="ComBlock">
+const props = withDefaults(defineProps<{ theme?: "white" | "gray" }>(), {
+  theme: "white",
+});
+</script>
+
+<style scoped lang="less">
+.common-section {
+  border-radius: @border-radius-base;
+  background-color: @white;
+  padding: 16px;
+  &.gray {
+    background-color: @bg-color;
+  }
+  & ~ .common-section {
+    margin-top: 16px;
+  }
+}
+</style>

+ 26 - 0
src/components/svg-icon/index.vue

@@ -0,0 +1,26 @@
+<template>
+  <svg aria-hidden="true">
+    <use :xlink:href="symbolId" :fill="color" />
+  </svg>
+</template>
+
+<script setup lang="ts" name="SvgIcon">
+import { computed } from "vue";
+
+const props = defineProps({
+  prefix: {
+    type: String,
+    default: "icon",
+  },
+  name: {
+    type: String,
+    required: true,
+  },
+  color: {
+    type: String,
+    default: "#5D7092",
+  },
+});
+
+const symbolId = computed(() => `#${props.prefix}-${props.name}`);
+</script>

+ 8 - 0
src/constants/storage.ts

@@ -0,0 +1,8 @@
+export enum LOCAL_STORAGE_KEYS {}
+
+export enum SESSION_STORAGE_KEYS {
+  /** 登录返回结果 */
+  LOGIN_RESULT = "login-result",
+  /** 用户信息 */
+  USER_INFO = "user-info"
+}

+ 33 - 0
src/layout/index.vue

@@ -0,0 +1,33 @@
+<template>
+  <div class="layout">
+    <div class="layout-left">
+      <LeftMenu />
+    </div>
+    <div class="layout-content">
+      <router-view></router-view>
+    </div>
+  </div>
+</template>
+
+<script setup lang="ts" name="Layout">
+import LeftMenu from "./left-menu.vue";
+</script>
+
+<style scoped lang="less">
+.layout {
+  width: 100vw;
+  height: 100vh;
+  display: flex;
+  .layout-left {
+    width: 200px;
+    height: 100%;
+    background: @white;
+  }
+  .layout-content {
+    flex: 1 1 0;
+    height: 100%;
+    overflow: auto;
+    padding: 24px;
+  }
+}
+</style>

+ 110 - 0
src/layout/left-menu.vue

@@ -0,0 +1,110 @@
+<template>
+  <div class="menu">
+    <div class="menu-header">
+      <img class="web-logo" src="@imgs/layout/web-logo.png" alt="" />
+    </div>
+    <div class="menu-content">
+      <MenuItem
+        class="root-menu-item"
+        v-for="menuItem in menu"
+        :key="menuItem.label"
+        :menu-item="menuItem"
+      />
+    </div>
+    <div class="menu-footer">
+      <div class="user-info">
+        <div class="user-name">{{mainStore.systemUserInfo?.name}}</div>
+        <div class="exit-login" @click="handleExitLogin">
+          <SvgIcon class="exit-icon" name="exit-icon" />
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script setup lang="ts" name="LayoutLeftMenu">
+import MenuItem from "./menu-item.vue";
+import { exitLogin } from "@/utils/common";
+import useMainStore from "@/store/main";
+
+const mainStore = useMainStore()
+const menu = [
+  { path: "/school", label: "学校管理", iconName: "school-icon" },
+  {
+    path: "",
+    label: "考试管理",
+    children: [
+      { path: "/exam", label: "考试批次管理", iconName: "exam-icon" },
+      { path: "/subjects", label: "科目试卷结构", iconName: "paper-icon" },
+    ],
+  },
+  { path: "/user", label: "用户管理", iconName: "user-icon" },
+];
+
+const handleExitLogin = () => {
+  exitLogin();
+};
+</script>
+
+<style scoped lang="less">
+.menu {
+  width: 100%;
+  height: 100%;
+  display: flex;
+  flex-direction: column;
+  .menu-header {
+    padding: 0 16px;
+    height: 86px;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    .web-logo {
+      height: 22px;
+      max-width: 100%;
+      object-fit: contain;
+    }
+  }
+  .menu-content {
+    flex: 1 1 0;
+    padding: 0 32px;
+    .root-menu-item {
+      padding: 25px 0;
+      border-bottom: 1px solid @border-color;
+      &:first-child {
+        border-top: 1px solid @border-color;
+      }
+    }
+  }
+  .menu-footer {
+    padding: 16px;
+    .user-info {
+      display: flex;
+      align-items: center;
+      justify-content: space-between;
+      height: 48px;
+      padding: 0 16px;
+      background-color: @bg-color;
+      border-radius: @border-radius-base;
+      background-image: url("@imgs/layout/user-info-bg.png");
+      background-repeat: no-repeat;
+      background-position: right top;
+      background-size: contain;
+      .user-name {
+        font-size: @font-size-small;
+      }
+      .exit-login {
+        color: @font-color;
+        padding: 6px;
+        cursor: pointer;
+        .exit-icon {
+          width: 16px;
+          height: 16px;
+        }
+        &:hover {
+          color: @font-light-color;
+        }
+      }
+    }
+  }
+}
+</style>

+ 59 - 0
src/layout/menu-item.vue

@@ -0,0 +1,59 @@
+<template>
+  <div class="menu-item">
+    <component
+      :is="props.menuItem.path ? RouterLink : 'div'"
+      :to="props.menuItem.path"
+      :class="['menu-row', { 'has-child': props.menuItem.children?.length }]"
+    >
+      <div class="menu-icon" v-if="props.menuItem.iconName">
+        <SvgIcon class="svg-icon" :name="props.menuItem.iconName" />
+      </div>
+      <div class="menu-label">
+        {{ props.menuItem.label }}
+      </div>
+    </component>
+    <menu-item
+      v-for="child in props.menuItem.children || []"
+      :key="child.label"
+      :menu-item="child"
+    ></menu-item>
+  </div>
+</template>
+
+<script setup lang="ts" name="MenuItem">
+import { RouterLink } from "vue-router";
+
+const props = withDefaults(defineProps<{ menuItem: MenuItem }>(), {});
+</script>
+
+<style scoped lang="less">
+.menu-item {
+  .menu-row {
+    display: flex;
+    align-items: center;
+    font-size: @font-size-base;
+    font-weight: bold;
+    color: @font-color;
+    padding: 8px 0;
+    &.router-link-active {
+      color: @font-light-color;
+    }
+    &.has-child {
+      font-weight: normal;
+      font-size: @font-size-small;
+      & ~ div {
+        margin-top: 4px;
+      }
+    }
+    .menu-icon {
+      width: 16px;
+      height: 16px;
+      margin-right: 12px;
+      .svg-icon {
+        width: 100%;
+        height: 100%;
+      }
+    }
+  }
+}
+</style>

+ 15 - 0
src/main.ts

@@ -0,0 +1,15 @@
+import { createApp } from "vue";
+import { createPinia } from 'pinia'
+import router from "@/routes";
+import App from "./App.vue";
+import flexible from './utils/flexible';
+import 'virtual:svg-icons-register'
+
+import "tailwindcss/tailwind.css";
+import "ant-design-vue/es/message/style/css";
+import "@/assets/less/antd.theme.less";
+import "@/assets/less/global.less";
+
+
+flexible()
+createApp(App).use(router).use(createPinia()).mount("#app");

+ 295 - 0
src/pages/exam-manage/index.vue

@@ -0,0 +1,295 @@
+<template>
+  <div class="exam-manage">
+    <Block class="header-block tw-flex tw-items-center tw-justify-between">
+      <a-form layout="inline">
+        <a-form-item label="学校名称">
+          <a-select
+            v-model:value="query.schoolId"
+            show-search
+            :filterOption="false"
+            @search="querySchoolList"
+            placeholder="学校名称"
+          >
+            <a-select-option
+              v-for="school in schoolTableData.result"
+              :key="school.id"
+              :value="school.id"
+              >{{ school.name }}</a-select-option
+            >
+          </a-select>
+        </a-form-item>
+        <a-form-item label="考试批次">
+          <a-input v-model:value="query.name"></a-input>
+        </a-form-item>
+        <a-form-item>
+          <a-button class="search-button" type="primary" @click="queryExamList"
+            >查询</a-button
+          >
+        </a-form-item>
+      </a-form>
+      <a-button
+        type="primary"
+        class="tw-flex tw-items-center operation-button"
+        @click="syncExamData"
+      >
+        <template #icon>
+          <CloudSyncOutlined />
+        </template>
+        同步
+      </a-button>
+      <a-button
+        type="primary"
+        class="tw-flex tw-items-center operation-button"
+        @click="toggleAddExamModal"
+      >
+        <template #icon>
+          <PlusCircleOutlined />
+        </template>
+        新增
+      </a-button>
+    </Block>
+    <Block class="exam-table">
+      <a-table
+        :columns="columns"
+        :data-source="examTableData.result"
+        emptyText="暂无考试信息"
+        :row-class-name="
+          (_:any, index:number) => (index % 2 === 1 ? 'table-striped' : null)
+        "
+      >
+        <template #bodyCell="{ column, record, index }">
+          <template v-if="column.dataIndex === 'index'">
+            {{ index + 1 }}
+          </template>
+          <template v-else-if="column.dataIndex === 'examStatus'">
+            {{
+              record.examStatus === "EDIT"
+                ? "开放上报"
+                : record.examStatus === "FINISH"
+                ? "停止上报"
+                : "关闭卡松是"
+            }}
+          </template>
+          <template v-else-if="column.dataIndex === 'operation'">
+            <div class="tw-flex tw-items-center">
+              <span
+                class="tw-cursor-pointer tw-p-2 tw-ml-1"
+                @click="onEdit(record)"
+                >编辑</span
+              >
+            </div>
+          </template>
+        </template>
+      </a-table>
+    </Block>
+    <a-modal
+      v-model:visible="showModal"
+      title="新增考试"
+      okText="确定"
+      cancelText="取消"
+      :maskClosable="false"
+      @ok="onAddNewExam"
+      :afterClose="resetExamInfo"
+    >
+      <a-form :labelCol="{ span: 6 }">
+        <a-form-item label="学校名称" v-bind="validateInfos.schoolId">
+          <a-select
+            v-model:value="examInfo.schoolId"
+            show-search
+            @search="querySchoolList"
+            :filterOption="false"
+            placeholder="学校名称"
+          >
+            <a-select-option
+              v-for="school in schoolTableData.result"
+              :key="school.id"
+              :value="school.id"
+              >{{ school.name }}</a-select-option
+            >
+          </a-select>
+        </a-form-item>
+        <a-form-item label="考试批次" v-bind="validateInfos.name">
+          <a-input v-model:value="examInfo.name"></a-input>
+        </a-form-item>
+        <a-form-item label="状态" v-bind="validateInfos.examStatus">
+          <a-select v-model:value="examInfo.examStatus">
+            <a-select-option value="EDIT">开放上报</a-select-option>
+            <a-select-option value="FINISH">停止上报</a-select-option>
+            <a-select-option value="CLOSE">关闭考试</a-select-option>
+          </a-select>
+        </a-form-item>
+      </a-form>
+    </a-modal>
+  </div>
+</template>
+
+<script setup lang="ts" name="PageExam">
+import { reactive, ref, watch } from "vue";
+import {
+  PlusCircleOutlined,
+  CloudSyncOutlined,
+} from "@ant-design/icons-vue";
+import { getSchoolListHttp } from "@/apis/school";
+import {
+  getExamListHttp,
+  editExamInfoHttp,
+  syncExamDataHttp,
+} from "@/apis/exam";
+import Block from "@/components/block/index.vue";
+import { message, TableColumnType } from "ant-design-vue";
+import { Form } from "ant-design-vue";
+
+import { throttle } from "lodash-es";
+
+const showModal = ref(false);
+
+const examInfo = ref<BaseExamInfo>({
+  examStatus: "",
+  name: "",
+  schoolId: "",
+  id: void 0,
+});
+
+const examRules = {
+  name: [{ required: true, message: "请填写考试批次" }],
+  examStatus: [{ required: true, message: "请选择考试状态" }],
+  schoolId: [{ required: true, message: "请选择学校" }],
+};
+
+const { validate, validateInfos } = Form.useForm(examInfo.value, examRules);
+
+/** 请求参数 */
+const query = reactive<FetchExamListQuery>({
+  name: "",
+  schoolId: "",
+  pageNumber: 1,
+  pageSize: 10,
+});
+
+/** table配置 */
+const columns: TableColumnType[] = [
+  { title: "序号", dataIndex: "index", align: "center" },
+  { title: "考试ID", dataIndex: "id" },
+  { title: "考试批次", dataIndex: "name" },
+  { title: "状态", dataIndex: "examStatus", align: "center" },
+  { title: "科目数量", dataIndex: "paperCount" },
+  { title: "操作", dataIndex: "operation" },
+];
+
+/** 学校列表信息 */
+const schoolTableData = reactive<MultiplePageData<SchoolListInfo>>({
+  totalCount: 0,
+  result: [],
+});
+
+/** 考试列表信息 */
+const examTableData = reactive<MultiplePageData<ExamListInfo>>({
+  totalCount: 0,
+  result: [],
+});
+
+/** 查询学校列表 */
+const querySchoolList = throttle(async (name: string = "") => {
+  try {
+    const { result = [], totalCount } = await getSchoolListHttp({
+      pageNumber: 1,
+      pageSize: 10,
+      name,
+    });
+    Object.assign(schoolTableData, { result, totalCount });
+  } catch (error) {
+    console.error(error);
+  }
+}, 100);
+
+/** 显示新增考试弹窗 */
+const toggleAddExamModal = (show: boolean = true) => {
+  showModal.value = show;
+};
+
+/** 查询考试列表 */
+const queryExamList = async () => {
+  try {
+    const { result = [], totalCount } = await getExamListHttp(query);
+    Object.assign(examTableData, { result, totalCount });
+  } catch (error) {
+    console.error(error);
+  }
+};
+
+watch(() => query.pageNumber, queryExamList);
+
+/** 编辑考试 */
+
+const onEdit = (record: ExamListInfo) => {
+  examInfo.value = Object.assign(examInfo.value, { ...record });
+};
+
+/** 新增考试 */
+const onAddNewExam = () => {
+  validate().then((valid) => {
+    if (valid) {
+      editExamInfoHttp(examInfo.value).then(() => {
+        message.success(`${examInfo.value.id ? "修改" : "添加"}成功`);
+        toggleAddExamModal(false);
+        query.schoolId && queryExamList();
+      });
+    }
+  });
+};
+
+/** 初始化examInfo */
+const resetExamInfo = () => {
+  examInfo.value = Object.assign(examInfo.value, {
+    examStatus: "",
+    name: "",
+    schoolId: "",
+    id: void 0,
+  });
+};
+
+/** 同步考试数据 */
+const syncExamData = () => {
+  if (query.schoolId) {
+    syncExamDataHttp({ schoolId: query.schoolId }).then(queryExamList);
+  }
+};
+
+querySchoolList();
+
+/** effect */
+if (query.schoolId) {
+  queryExamList();
+}
+</script>
+
+<style scoped lang="less">
+.exam-manage {
+  .header-block {
+    :deep(.ant-select-selector) {
+      width: 160px;
+    }
+    .search-button {
+      background-color: @font-color;
+      color: @white;
+      border: none;
+      width: 56px;
+      padding: 0;
+      &:after {
+        display: none;
+        opacity: 0;
+      }
+    }
+    .operation-button {
+      width: 72px;
+      padding: 0;
+      margin-left: auto;
+      & ~ .operation-button {
+        margin-left: 8px;
+      }
+    }
+  }
+  .exam-table {
+  }
+}
+</style>

+ 136 - 0
src/pages/login/index.vue

@@ -0,0 +1,136 @@
+<template>
+  <div class="login-view tw-grid tw-place-content-center">
+    <div class="login-content tw-flex tw-items-center">
+      <img
+        class="login-banner"
+        draggable="false"
+        src="@imgs/login/login-banner.png"
+        alt=""
+      />
+      <a-form class="login-form" @submit="onLogin">
+        <div class="login-title">试卷结构管理</div>
+        <a-form-item v-bind="validateInfos.loginName">
+          <a-input
+            class="login-input login-input-name"
+            v-model:value="loginModel.loginName"
+            placeholder="请输入用户名"
+          >
+            <template #prefix>
+              <SvgIcon class="input-icon" name="user-icon" />
+            </template>
+          </a-input>
+        </a-form-item>
+        <a-form-item v-bind="validateInfos.password">
+          <a-input
+            class="login-input login-input-pwd"
+            type="password"
+            v-model:value="loginModel.password"
+            placeholder="请输入密码"
+          >
+            <template #prefix>
+              <SvgIcon class="input-icon" name="pwd-icon" />
+            </template>
+          </a-input>
+        </a-form-item>
+        <a-button
+          class="login-button"
+          :loading="loading"
+          type="primary"
+          html-type="submit"
+          >{{ loading ? "登录中..." : "立即登录" }}</a-button
+        >
+      </a-form>
+    </div>
+  </div>
+</template>
+
+<script setup lang="ts" name="PageLogin">
+import { reactive, ref } from "vue";
+import { useRouter } from "vue-router";
+import { Form, message } from "ant-design-vue";
+import { loginHttp } from "@api/common";
+import SvgIcon from "@/components/svg-icon/index.vue";
+
+const router = useRouter();
+
+const loading = ref(false);
+
+const loginModel: LoginModel = reactive({
+  loginName: "",
+  password: "",
+});
+
+const loginRules = {
+  loginName: [{ required: true, message: "请输入用户名" }],
+  password: [{ required: true, message: "请输入密码" }],
+};
+
+const { validate, validateInfos } = Form.useForm(loginModel, loginRules);
+
+const onLogin = () => {
+  validate().then(() => {
+    loading.value = true;
+    doLogin()
+      .then(() => {
+        message.success({ content: "登录成功", key: "success" });
+        setTimeout(() => {
+          message.destroy("success");
+          router.replace({ path: "/" });
+        }, 500);
+      })
+      .finally(() => {
+        loading.value = false;
+      });
+  });
+};
+
+const doLogin = () => {
+  return loginHttp(loginModel);
+};
+</script>
+
+<style scoped lang="less">
+.login-view {
+  width: 100%;
+  height: 100%;
+  background-color: @white;
+  .login-content {
+    .login-banner {
+      width: 480px;
+    }
+    .login-form {
+      background-color: @bg-color;
+      width: 428px;
+      height: 420px;
+      border-radius: 8px;
+      margin-left: 64px;
+      padding: 64px;
+      .login-title {
+        margin-bottom: 40px;
+        color: @black;
+        font-size: 20px;
+        font-weight: bold;
+      }
+      .login-input {
+        height: 56px;
+        background-color: @white;
+
+        :deep(.ant-input) {
+          background-color: @white;
+        }
+
+        .input-icon {
+          width: 16px;
+          height: 16px;
+        }
+      }
+      .login-button {
+        width: 160px;
+        height: 56px;
+        font-weight: bold;
+        margin-top: 32px;
+      }
+    }
+  }
+}
+</style>

+ 276 - 0
src/pages/school-manage/index.vue

@@ -0,0 +1,276 @@
+<template>
+  <div class="school-manage">
+    <Block class="header-block tw-flex tw-items-center tw-justify-between">
+      <a-form layout="inline">
+        <a-form-item label="学校名称">
+          <a-input v-model:value="query.name"></a-input>
+        </a-form-item>
+        <a-form-item>
+          <a-button
+            class="search-button"
+            type="primary"
+            @click="querySchoolList"
+            >查询</a-button
+          >
+        </a-form-item>
+      </a-form>
+      <a-button
+        type="primary"
+        class="tw-flex tw-items-center operation-button"
+        @click="toggleAddSchoolModal"
+      >
+        <template #icon>
+          <PlusCircleOutlined />
+        </template>
+        新增
+      </a-button>
+    </Block>
+    <Block class="school-table">
+      <a-table
+        :columns="columns"
+        :data-source="schoolTableData.result"
+        emptyText="暂无学校信息"
+        @change="currentPageChange"
+        :pagination="{
+          total: schoolTableData.totalCount,
+          pageSize: query.pageSize,
+        }"
+        :row-class-name="
+          (_:any, index:number) => (index % 2 === 1 ? 'table-striped' : null)
+        "
+      >
+        <template #bodyCell="{ column, record, index }">
+          <template v-if="column.dataIndex === 'index'">
+            {{ index + 1 }}
+          </template>
+          <template v-else-if="column.dataIndex === 'enable'">
+            <template v-if="record.enable">
+              <CheckCircleFilled style="color: #30bf78" />
+            </template>
+            <template v-else>
+              <CloseCircleFilled style="color: #f4664a" />
+            </template>
+          </template>
+          <template v-else-if="column.dataIndex === 'operation'">
+            <div class="tw-flex tw-items-center">
+              <span
+                class="tw-cursor-pointer tw-p-2"
+                @click="updateSchoolStatus(record)"
+                >{{ record.enable ? "禁用" : "启用" }}</span
+              >
+              <span
+                class="tw-cursor-pointer tw-p-2 tw-ml-1"
+                @click="onEdit(record)"
+                >编辑</span
+              >
+              <a-popover trigger="click">
+                <template #content>
+                  <VueQrCode
+                    type="image/png"
+                    :margin="0"
+                    :color="{}"
+                    :quality="0.9"
+                    :value="record.qrCode"
+                  />
+                </template>
+                <span class="tw-cursor-pointer tw-p-2 tw-ml-1">二维码</span>
+              </a-popover>
+            </div>
+          </template>
+        </template>
+      </a-table>
+    </Block>
+    <a-modal
+      v-model:visible="showModal"
+      :title="`${!schoolInfo.id ? '新增' : '编辑'}学校`"
+      okText="确定"
+      cancelText="取消"
+      :maskClosable="false"
+      @ok="onAddNewSchool"
+      :after-close="resetSchoolInfo"
+    >
+      <a-form :labelCol="{ span: 6 }">
+        <a-form-item label="学校编码" v-bind="validateInfos.code">
+          <a-input v-model:value="schoolInfo.code"></a-input>
+        </a-form-item>
+        <a-form-item label="学校名称" v-bind="validateInfos.name">
+          <a-input v-model:value="schoolInfo.name"></a-input>
+        </a-form-item>
+        <a-form-item label="负责人" v-bind="validateInfos.contacts">
+          <a-input v-model:value="schoolInfo.contacts"></a-input>
+        </a-form-item>
+        <a-form-item label="联系方式" v-bind="validateInfos.telephone">
+          <a-input
+            v-model:value="schoolInfo.telephone"
+            maxlength="11"
+          ></a-input>
+        </a-form-item>
+        <a-form-item label="地区" v-bind="validateInfos.region">
+          <a-input v-model:value="schoolInfo.region"></a-input>
+        </a-form-item>
+        <a-form-item label="状态">
+          <a-radio-group v-model:value="schoolInfo.enable">
+            <a-radio :value="true">启用</a-radio>
+            <a-radio :value="false">禁用</a-radio>
+          </a-radio-group>
+        </a-form-item>
+      </a-form>
+    </a-modal>
+  </div>
+</template>
+
+<script setup lang="ts" name="PageSchool">
+import { reactive, ref, watch } from "vue";
+import {
+  PlusCircleOutlined,
+  CheckCircleFilled,
+  CloseCircleFilled,
+} from "@ant-design/icons-vue";
+import {
+  getSchoolListHttp,
+  updateSchoolStatusHttp,
+  editSchoolInfoHttp,
+} from "@/apis/school";
+import Block from "@/components/block/index.vue";
+import { message, TableColumnType } from "ant-design-vue";
+import { Form } from "ant-design-vue";
+import VueQrCode from "vue-qrcode";
+
+const showModal = ref(false);
+
+const schoolInfo = ref<BaseSchoolInfo>({
+  code: "",
+  contacts: "",
+  name: "",
+  region: "",
+  telephone: "",
+  enable: true,
+  id: void 0,
+});
+
+const schoolRules = {
+  code: [{ required: true, message: "请填写学校编码" }],
+  name: [{ required: true, message: "请填写学校名称" }],
+  contacts: [{ required: true, message: "请填写负责人" }],
+  region: [{ required: true, message: "请填写学校地区" }],
+  telephone: [
+    { required: true, message: "请填写联系方式" },
+    { pattern: /\d{11}/, message: "请填写正确联系方式" },
+  ],
+};
+
+const { validate, validateInfos } = Form.useForm(schoolInfo.value, schoolRules);
+
+/** 请求参数 */
+const query = reactive<FetchSchoolListQuery>({
+  name: "",
+  pageNumber: 1,
+  pageSize: 10,
+});
+
+/** table配置 */
+const columns: TableColumnType[] = [
+  { title: "序号", dataIndex: "index", align: "center" },
+  { title: "学校ID", dataIndex: "id" },
+  { title: "学校名称", dataIndex: "name" },
+  { title: "地区", dataIndex: "region" },
+  { title: "状态", dataIndex: "enable", align: "center" },
+  { title: "负责人", dataIndex: "contacts" },
+  { title: "联系方式", dataIndex: "telephone" },
+  { title: "更新时间", dataIndex: "updateTime" },
+  { title: "操作", dataIndex: "operation" },
+];
+
+/** 学校列表信息 */
+const schoolTableData = reactive<MultiplePageData<SchoolListInfo>>({
+  totalCount: 0,
+  result: [],
+});
+
+/** 显示新增学校弹窗 */
+const toggleAddSchoolModal = (show: boolean = true) => {
+  showModal.value = show;
+};
+
+const currentPageChange = ({ current }: { current: number }) => {
+  console.log("page change", current);
+  query.pageNumber = current;
+};
+
+/** 查询学校列表 */
+const querySchoolList = async () => {
+  try {
+    const { result = [], totalCount } = await getSchoolListHttp(query);
+    Object.assign(schoolTableData, { result, totalCount });
+  } catch (error) {
+    console.error(error);
+  }
+};
+
+watch(() => query.pageNumber, querySchoolList);
+
+/* 启用/禁用 */
+const updateSchoolStatus = (record: SchoolListInfo) => {
+  updateSchoolStatusHttp({ enable: !record.enable, ids: [record.id] }).then(
+    querySchoolList
+  );
+};
+
+/** 编辑学校 */
+const onEdit = (record: SchoolListInfo) => {
+  schoolInfo.value = Object.assign(schoolInfo.value, { ...record });
+  toggleAddSchoolModal(true);
+};
+
+/** 新增学校 */
+const onAddNewSchool = () => {
+  validate().then((valid) => {
+    if (valid) {
+      editSchoolInfoHttp(schoolInfo.value).then(() => {
+        message.success(`${schoolInfo.value.code ? "修改" : "添加"}成功`);
+        toggleAddSchoolModal(false);
+        querySchoolList();
+      });
+    }
+  });
+};
+
+/** 初始化schoolInfo */
+const resetSchoolInfo = () => {
+  schoolInfo.value = {
+    contacts: "",
+    name: "",
+    region: "",
+    telephone: "",
+    enable: true,
+  };
+};
+
+/** effect */
+querySchoolList();
+</script>
+
+<style scoped lang="less">
+.school-manage {
+  .header-block {
+    .search-button {
+      background-color: @font-color;
+      color: @white;
+      border: none;
+      width: 56px;
+      padding: 0;
+      &:after {
+        display: none;
+        opacity: 0;
+      }
+    }
+
+    .operation-button {
+      width: 72px;
+      padding: 0;
+    }
+  }
+  .school-table {
+  }
+}
+</style>

+ 403 - 0
src/pages/subjects-manage/index.vue

@@ -0,0 +1,403 @@
+<template>
+  <div class="subjects-manage">
+    <Block class="header-block tw-flex tw-items-center">
+      <a-form layout="inline" class="tw-flex-1">
+        <a-form-item label="学校名称">
+          <a-select
+            v-model:value="query.schoolId"
+            show-search
+            :filterOption="false"
+            @search="querySchoolList"
+            placeholder="学校名称"
+          >
+            <a-select-option
+              v-for="school in schoolTableData.result"
+              :key="school.id"
+              :value="school.id"
+              >{{ school.name }}</a-select-option
+            >
+          </a-select>
+        </a-form-item>
+        <a-form-item label="考试批次">
+          <a-select
+            v-model:value="query.examId"
+            :filterOption="false"
+            show-search
+            @search="queryExamList"
+            placeholder="考试批次"
+          >
+            <a-select-option
+              v-for="exam in examTableData.result"
+              :key="exam.id"
+              :value="exam.id"
+              >{{ exam.name }}</a-select-option
+            >
+          </a-select>
+        </a-form-item>
+        <a-form-item label="分组状态">
+          <a-select v-model:value="query.groupFinish" placeholder="考试批次">
+            <a-select-option :value="0">全部</a-select-option>
+            <a-select-option :value="1">已完成</a-select-option>
+            <a-select-option :value="2">未完成</a-select-option>
+          </a-select>
+        </a-form-item>
+        <a-form-item label="科目代码">
+          <a-input v-model:value="query.courseCode"></a-input>
+        </a-form-item>
+        <a-form-item label="科目名称">
+          <a-input v-model:value="query.courseName"></a-input>
+        </a-form-item>
+        <a-form-item label="试卷总分" class="range-item">
+          <a-input v-model:value="query.totalScoreMin"></a-input>
+          至
+          <a-input v-model:value="query.totalScoreMax"></a-input>
+        </a-form-item>
+        <a-form-item>
+          <a-button
+            class="search-button"
+            type="primary"
+            @click="querySubjectsList"
+            >查询</a-button
+          >
+        </a-form-item>
+      </a-form>
+      <a-dropdown-button type="primary">
+        <template #overlay>
+          <a-menu>
+            <a-menu-item key="1" @click="showImportModalType('subject')"
+              >导入科目</a-menu-item
+            >
+            <a-menu-item key="2" @click="showImportModalType('struct')"
+              >导入主观题</a-menu-item
+            >
+          </a-menu>
+        </template>
+        <template #icon><DownOutlined /></template>
+        导入
+      </a-dropdown-button>
+      <a-button
+        type="primary"
+        class="tw-flex tw-items-center operation-button"
+        @click="downloadPaperStruct"
+      >
+        <template #icon>
+          <UploadOutlined />
+        </template>
+        导出
+      </a-button>
+    </Block>
+    <Block class="subjects-table">
+      <a-table
+        :columns="columns"
+        :data-source="subjectsTableData.result"
+        :row-class-name="
+          (_:any, index:number) => (index % 2 === 1 ? 'table-striped' : null)
+        "
+      >
+        <template #bodyCell="{ column, record, index }">
+          <template v-if="column.dataIndex === 'index'">
+            {{ index + 1 }}
+          </template>
+          <template v-else-if="column.dataIndex === 'enable'">
+            <template v-if="record.enable">
+              <CheckCircleFilled style="color: #30bf78" />
+            </template>
+            <template v-else>
+              <CloseCircleFilled style="color: #f4664a" />
+            </template>
+          </template>
+        </template>
+      </a-table>
+    </Block>
+    <a-modal
+      v-model:visible="showImportModal"
+      :title="`导入${importType === 'subject' ? '科目' : '试卷结构'}`"
+      :footer="false"
+      :maskClosable="false"
+      @close="clearFileList"
+    >
+      <a-upload
+        :file-list="fileList"
+        :before-upload="beforeUpload"
+        @remove="handleRemove"
+        :max-count="1"
+        type="primary"
+      >
+        <a-button>
+          <upload-outlined></upload-outlined>
+          选择文件
+        </a-button>
+      </a-upload>
+      <div class="operation-group">
+        <a-button type="primary" @click="downloadTemplate">下载模板</a-button>
+        <a-button type="primary" @click="clearFileList">清空上传文件</a-button>
+        <a-button type="primary" @click="onImport">确认上传</a-button>
+      </div>
+    </a-modal>
+  </div>
+</template>
+
+<script setup lang="ts" name="PageSubjects">
+import { reactive, ref, watch } from "vue";
+import {
+  UploadOutlined,
+  CheckCircleFilled,
+  CloseCircleFilled,
+  DownOutlined,
+} from "@ant-design/icons-vue";
+
+import Block from "@/components/block/index.vue";
+import type { TableColumnType, UploadProps } from "ant-design-vue";
+import {
+  getSubjectsListHttp,
+  importSubjectsHttp,
+  importPaperStructHttp,
+  downloadPaperStructHttp,
+  downloadSubjectTemplateHttp,
+  downloadPaperStructTemplateHttp,
+} from "@/apis/struct";
+import { getSchoolListHttp } from "@/apis/school";
+import { getExamListHttp } from "@/apis/exam";
+import { download, extractFileName } from "@/utils/common";
+import { AxiosResponse } from "axios";
+
+const showImportModal = ref(false);
+const importType = ref("subject");
+
+const fileList = ref<UploadProps["fileList"]>([]);
+
+/** 请求参数 */
+const query = reactive<
+  Omit<FetchSubjectsListQuery, "groupFinish"> & { groupFinish: number }
+>({
+  /** 科目代码 */
+  courseCode: "",
+  courseName: "",
+  /** 考试id */
+  examId: "",
+  /** 	分组状态 */
+  groupFinish: 0,
+  /** 学校id */
+  schoolId: "",
+  /** 总分截止值 */
+  totalScoreMax: "",
+  /** 总分起始值 */
+  totalScoreMin: "",
+  pageSize: 10,
+  pageNumber: 1,
+});
+
+/** table配置 */
+const columns: TableColumnType[] = [
+  { title: "序号", dataIndex: "index", align: "center" },
+  { title: "科目代码", dataIndex: "courseCode" },
+  { title: "科目名称", dataIndex: "courseName" },
+  { title: "主观总分", dataIndex: "subjectiveScore" },
+  { title: "试卷总分", dataIndex: "totalScore" },
+  { title: "分组数", dataIndex: "groupCount" },
+];
+
+/** 学校列表信息 */
+const schoolTableData = reactive<MultiplePageData<SchoolListInfo>>({
+  totalCount: 0,
+  result: [],
+});
+
+/** 考试列表信息 */
+const examTableData = reactive<MultiplePageData<ExamListInfo>>({
+  totalCount: 0,
+  result: [],
+});
+
+/** 科目列表信息 */
+const subjectsTableData = reactive<MultiplePageData<SubjectsListInfo>>({
+  totalCount: 0,
+  result: [],
+});
+
+/** 查询学校列表 */
+const querySchoolList = async (name?: string) => {
+  try {
+    const { result = [], totalCount } = await getSchoolListHttp({
+      name,
+      pageNumber: 1,
+      pageSize: 10,
+    });
+    Object.assign(schoolTableData, { result, totalCount });
+  } catch (error) {
+    console.error(error);
+  }
+};
+
+/** 查询考试列表 */
+const queryExamList = async (name?: string) => {
+  try {
+    const { result = [], totalCount } = await getExamListHttp({
+      pageNumber: 1,
+      pageSize: 10,
+      name,
+      schoolId: query.schoolId,
+    });
+    Object.assign(examTableData, { result, totalCount });
+  } catch (error) {
+    console.error(error);
+  }
+};
+
+/** 查询科目列表 */
+const querySubjectsList = async () => {
+  try {
+    const { result = [], totalCount } = await getSubjectsListHttp({
+      ...query,
+      groupFinish: [void 0, true, false][query.groupFinish],
+    });
+    Object.assign(subjectsTableData, { result, totalCount });
+  } catch (error) {
+    console.error(error);
+  }
+};
+
+watch(() => query.pageNumber, querySubjectsList);
+watch(
+  () => query.schoolId,
+  () => {
+    query.examId = "";
+    Object.assign(examTableData, { result: [], totalCount: 0 });
+    queryExamList();
+  }
+);
+
+/** 导入科目 */
+const importSubject = async () => {
+  try {
+    const formData = new FormData();
+    formData.append("schoolId", query.schoolId);
+    fileList.value?.forEach((file: any) => {
+      formData.append("file", file);
+    });
+    await importSubjectsHttp({
+      examId: query.examId,
+      file: new File([], "a"),
+    }).then(querySubjectsList);
+  } catch (error) {
+    console.error(error);
+  }
+};
+
+/** 导入试卷结构 */
+const importPaperStruct = async () => {
+  try {
+    await importPaperStructHttp({
+      examId: query.examId,
+      file: new File([], "a"),
+    }).then(querySubjectsList);
+  } catch (error) {
+    console.error(error);
+  }
+};
+
+/** 导出试卷结构 */
+
+const downloadPaperStruct = async () => {
+  try {
+    const result = await downloadPaperStructHttp({
+      ...query,
+      groupFinish: [void 0, true, false][query.groupFinish],
+    });
+    if (result) {
+      const fileName = extractFileName(result.headers["content-disposition"]);
+      result && download(result.data, fileName);
+    }
+  } catch (error) {
+    console.error(error);
+  }
+};
+
+const handleRemove: UploadProps["onRemove"] = (file) => {
+  const index = fileList.value!.indexOf(file);
+  const newFileList = fileList.value!.slice();
+  newFileList.splice(index, 1);
+  fileList.value = newFileList;
+};
+
+const beforeUpload: UploadProps["beforeUpload"] = (file) => {
+  fileList.value = [file];
+  return false;
+};
+
+const clearFileList = () => {
+  fileList.value = [];
+};
+
+const downloadTemplate = async () => {
+  try {
+    let result: AxiosResponse<Blob>;
+    if (importType.value === "subject") {
+      result = await downloadSubjectTemplateHttp();
+    } else {
+      result = await downloadPaperStructTemplateHttp();
+    }
+    if (result) {
+      const fileName = extractFileName(result.headers["content-disposition"]);
+      result && download(result.data, fileName);
+    }
+  } catch (error) {
+    console.error(error);
+  }
+};
+
+const showImportModalType = (type: "subject" | "struct") => {
+  importType.value = type;
+  showImportModal.value = true
+};
+
+const onImport = () => {
+  if (importType.value === "subject") {
+    importSubject();
+  } else {
+    importPaperStruct;
+  }
+};
+
+querySchoolList();
+</script>
+
+<style scoped lang="less">
+.subjects-manage {
+  .header-block {
+    .ant-input {
+      width: 160px;
+    }
+    :deep(.ant-select-selector) {
+      width: 160px;
+    }
+    :deep(.ant-form-item) {
+      margin-bottom: 8px;
+    }
+    .range-item {
+      .ant-input {
+        width: 69px;
+      }
+    }
+    .search-button {
+      background-color: @font-color;
+      color: @white;
+      border: none;
+      width: 56px;
+      padding: 0;
+      &:after {
+        display: none;
+        opacity: 0;
+      }
+    }
+
+    .operation-button {
+      width: 72px;
+      padding: 0;
+      margin-left: 8px;
+    }
+  }
+  .subjects-table {
+  }
+}
+</style>

+ 460 - 0
src/pages/user-manage/index.vue

@@ -0,0 +1,460 @@
+<template>
+  <div class="user-manage">
+    <Block class="header-block tw-flex tw-items-center">
+      <a-form layout="inline">
+        <a-form-item label="学校名称">
+          <a-select
+            v-model:value="query.schoolId"
+            show-search
+            :filterOption="false"
+            @search="querySchoolList"
+            placeholder="学校名称"
+          >
+            <a-select-option
+              v-for="school in schoolTableData.result"
+              :key="school.id"
+              :value="school.id"
+              >{{ school.name }}</a-select-option
+            >
+          </a-select>
+        </a-form-item>
+        <a-form-item label="登录名">
+          <a-input v-model:value="query.loginName"></a-input>
+        </a-form-item>
+        <a-form-item label="角色">
+          <a-select v-model:value="query.role">
+            <a-select-option value="SCHOOL_ADMIN">学校管理员</a-select-option>
+            <a-select-option value="SECTION_LEADER">科组长</a-select-option>
+          </a-select>
+        </a-form-item>
+        <a-form-item>
+          <a-button class="search-button" type="primary" @click="queryUserList"
+            >查询</a-button
+          >
+        </a-form-item>
+      </a-form>
+      <a-button
+        type="primary"
+        class="tw-flex tw-items-center operation-button"
+        @click="toggleAddUserModal"
+      >
+        <template #icon>
+          <PlusCircleOutlined />
+        </template>
+        新增
+      </a-button>
+      <a-button
+        type="primary"
+        class="tw-flex tw-items-center operation-button"
+        @click="importUserList"
+      >
+        <template #icon>
+          <DownloadOutlined />
+        </template>
+        导入
+      </a-button>
+    </Block>
+    <Block class="user-table">
+      <a-table
+        :columns="columns"
+        :data-source="userTableData.result"
+        :row-class-name="
+          (_:any, index:number) => (index % 2 === 1 ? 'table-striped' : null)
+        "
+      >
+        <template #bodyCell="{ column, record, index }">
+          <template v-if="column.dataIndex === 'index'">
+            {{ index + 1 }}
+          </template>
+          <template v-else-if="column.dataIndex === 'enable'">
+            <template v-if="record.enable">
+              <CheckCircleFilled style="color: #30bf78" />
+            </template>
+            <template v-else>
+              <CloseCircleFilled style="color: #f4664a" />
+            </template>
+          </template>
+          <template v-else-if="column.dataIndex === 'operation'">
+            <div class="tw-flex tw-items-center">
+              <span
+                class="tw-cursor-pointer tw-p-2"
+                @click="updateUserStatus(record)"
+                >{{ record.enable ? "禁用" : "启用" }}</span
+              >
+              <span
+                class="tw-cursor-pointer tw-p-2 tw-ml-1"
+                @click="onEdit(record)"
+                >编辑</span
+              >
+              <span
+                class="tw-cursor-pointer tw-p-2 tw-ml-1"
+                @click="onResetPwd(record)"
+                >重置密码
+              </span>
+            </div>
+          </template>
+        </template>
+      </a-table>
+    </Block>
+    <a-modal
+      v-model:visible="showModal"
+      title="新增用户"
+      okText="确定"
+      cancelText="取消"
+      :maskClosable="false"
+      @ok="onAddNewUser"
+      @close="resetUserInfo"
+    >
+      <a-form :labelCol="{ span: 6 }">
+        <a-form-item label="学校" v-bind="validateInfos.schoolId">
+          <a-select
+            v-model:value="userInfo.schoolId"
+            show-search
+            :filterOption="false"
+            @search="querySchoolList"
+            placeholder="学校名称"
+          >
+            <a-select-option
+              v-for="school in schoolTableData.result"
+              :key="school.id"
+              :value="school.id"
+              >{{ school.name }}</a-select-option
+            >
+          </a-select>
+        </a-form-item>
+        <a-form-item label="姓名" v-bind="validateInfos.name">
+          <a-input v-model:value="userInfo.name"></a-input>
+        </a-form-item>
+        <a-form-item label="登录名" v-bind="validateInfos.loginName">
+          <a-input v-model:value="userInfo.loginName"></a-input>
+        </a-form-item>
+        <a-form-item label="密码" v-bind="validateInfos.passwd">
+          <a-input v-model:value="userInfo.passwd"></a-input>
+        </a-form-item>
+        <a-form-item label="角色" v-bind="validateInfos.role">
+          <a-select v-model:value="userInfo.role">
+            <a-select-option value="SCHOOL_ADMIN">学校管理员</a-select-option>
+            <a-select-option value="SECTION_LEADER">科组长</a-select-option>
+          </a-select>
+        </a-form-item>
+        <a-form-item label="所属科目" v-bind="validateInfos.course">
+          <a-textarea v-model:value="userInfo.course"></a-textarea>
+        </a-form-item>
+      </a-form>
+    </a-modal>
+
+    <a-modal
+      v-model:visible="showResetPwdModal"
+      title="密码设置"
+      okText="确定"
+      cancelText="取消"
+      :maskClosable="false"
+      @ok="onUpdateUserPwd"
+      @close="clearFileList"
+    >
+      <a-form :labelCol="{ span: 3 }">
+        <a-form-item label="密码" v-bind="validatePwdInfos.passwd">
+          <a-input v-model:value="resetPwd.passwd"></a-input>
+        </a-form-item>
+      </a-form>
+    </a-modal>
+
+    <a-modal
+      v-model:visible="showImportModal"
+      title="导入用户"
+      :footer="false"
+      :maskClosable="false"
+      @close="resetPwdInfo"
+    >
+      <a-upload
+        :file-list="fileList"
+        :before-upload="beforeUpload"
+        @remove="handleRemove"
+        :max-count="1"
+        type="primary"
+      >
+        <a-button>
+          <upload-outlined></upload-outlined>
+          选择文件
+        </a-button>
+      </a-upload>
+      <div class="operation-group">
+        <a-button type="primary" @click="downloadTemplate">下载模板</a-button>
+        <a-button type="primary" @click="clearFileList">清空上传文件</a-button>
+        <a-button type="primary" @click="onImportUserList">确认上传</a-button>
+      </div>
+    </a-modal>
+  </div>
+</template>
+
+<script setup lang="ts" name="PageUsers">
+import { reactive, ref } from "vue";
+import {
+  PlusCircleOutlined,
+  CheckCircleFilled,
+  CloseCircleFilled,
+  DownloadOutlined,
+} from "@ant-design/icons-vue";
+import {
+  getUserListHttp,
+  updateUserStatusHttp,
+  editUserInfoHttp,
+  resetUserPwdHttp,
+  importUserHttp,
+  downloadImportUserHttp
+} from "@/apis/user";
+import Block from "@/components/block/index.vue";
+import { message } from "ant-design-vue";
+import { Form } from "ant-design-vue";
+import { getSchoolListHttp } from "@/apis/school";
+import { download, extractFileName } from "@/utils/common";
+import type { UploadProps, TableColumnType } from "ant-design-vue";
+
+const showModal = ref(false);
+const showResetPwdModal = ref(false);
+const showImportModal = ref(false);
+
+const fileList = ref<UploadProps["fileList"]>([]);
+
+const userInfo = ref<EditUserInfo>({
+  schoolId: "",
+  name: "",
+  loginName: "",
+  role: void 0,
+  course: "",
+});
+
+const resetPwd = ref({
+  passwd: "",
+  userId: "",
+});
+
+const userRules = {
+  schoolId: [{ required: true, message: "请选择用户所属学校" }],
+  name: [{ required: true, message: "请填写用户姓名" }],
+  loginName: [{ required: true, message: "请填写登录名称" }],
+  passwd: [{ required: true, message: "请填写登录密码" }],
+  role: [{ required: true, message: "请选择用户角色" }],
+};
+
+const pwdRules = {
+  passwd: [
+    { required: true, message: "请选择用户所属学校" },
+    { pattern: /s/, message: "请选择用户所属学校" },
+  ],
+};
+
+const { validate, validateInfos } = Form.useForm(userInfo.value, userRules);
+const { validate: validatePwd, validateInfos: validatePwdInfos } = Form.useForm(
+  resetPwd.value,
+  pwdRules
+);
+
+/** 请求参数 */
+const query = reactive<FetchUserListQuery>({
+  loginName: "",
+  schoolId: "",
+  role: void 0,
+  pageNumber: 1,
+  pageSize: 10,
+});
+
+/** table配置 */
+const columns: TableColumnType[] = [
+  { title: "序号", dataIndex: "index", align: "center" },
+  { title: "ID", dataIndex: "id" },
+  { title: "姓名", dataIndex: "name" },
+  { title: "登录名", dataIndex: "loginName" },
+  { title: "学校", dataIndex: "schoolName", align: "center" },
+  { title: "角色", dataIndex: "roleId" },
+  { title: "更新时间", dataIndex: "updateTime" },
+  { title: "状态", dataIndex: "enable" },
+  { title: "操作", dataIndex: "operation" },
+];
+
+/** 用户列表信息 */
+const userTableData = reactive<MultiplePageData<UserInfo>>({
+  totalCount: 0,
+  result: [],
+});
+
+/** 学校列表信息 */
+const schoolTableData = reactive<MultiplePageData<SchoolListInfo>>({
+  totalCount: 0,
+  result: [],
+});
+
+/** 查询学校列表 */
+const querySchoolList = async (name?: string) => {
+  try {
+    const { result = [], totalCount } = await getSchoolListHttp({
+      name,
+      pageNumber: 1,
+      pageSize: 10,
+    });
+    Object.assign(schoolTableData, { result, totalCount });
+  } catch (error) {
+    console.error(error);
+  }
+};
+
+/** 显示新增用户弹窗 */
+const toggleAddUserModal = (show: boolean = true) => {
+  showModal.value = show;
+};
+
+/** 查询用户列表 */
+const queryUserList = async () => {
+  try {
+    const { result = [], totalCount } = await getUserListHttp(query);
+    Object.assign(userTableData, { result, totalCount });
+  } catch (error) {
+    console.error(error);
+  }
+};
+
+/* 启用/禁用 */
+const updateUserStatus = (record: UserInfo) => {
+  updateUserStatusHttp({ enable: !record.enable, ids: [record.id] }).then(
+    queryUserList
+  );
+};
+
+/** 编辑用户 */
+const onEdit = (record: UserInfo) => {
+  userInfo.value = { ...record, course: record.courseCodes.join(",") };
+};
+
+/** 重置密码 */
+const onResetPwd = (record: UserInfo) => {
+  resetPwd.value = { passwd: "", userId: `${record.id}` };
+  showResetPwdModal.value = true;
+};
+
+/** 新增用户 */
+const onAddNewUser = () => {
+  validate().then((valid) => {
+    if (valid) {
+      editUserInfoHttp(userInfo.value).then(() => {
+        message.success(`${userInfo.value.id ? "修改" : "添加"}成功`);
+        toggleAddUserModal(false);
+      });
+    }
+  });
+};
+
+/** 导入用户 */
+const importUserList = () => {
+  if (!query.schoolId) {
+    return message.error("请选择学校");
+  }
+  showImportModal.value = true;
+};
+
+const handleRemove: UploadProps["onRemove"] = (file) => {
+  const index = fileList.value!.indexOf(file);
+  const newFileList = fileList.value!.slice();
+  newFileList.splice(index, 1);
+  fileList.value = newFileList;
+};
+
+const beforeUpload: UploadProps["beforeUpload"] = (file) => {
+  fileList.value = [file];
+  return false;
+};
+
+const clearFileList = () => {
+  fileList.value = [];
+};
+
+const downloadTemplate = () => {
+  downloadImportUserHttp().then((d) => {
+    const fileName = extractFileName(d.headers["content-disposition"]);
+    d && download(d.data, fileName);
+  });
+};
+
+const onImportUserList = () => {
+  if (!query.schoolId) {
+    return;
+  }
+  const formData = new FormData();
+  formData.append("schoolId", query.schoolId);
+  fileList.value?.forEach((file: any) => {
+    formData.append("file", file);
+  });
+  importUserHttp(formData);
+};
+
+/** 修改密码 */
+const onUpdateUserPwd = () => {
+  validatePwd().then((valid) => {
+    if (valid) {
+      resetUserPwdHttp(resetPwd.value).then(() => {
+        message.success(`重置成功`);
+        showResetPwdModal.value = false;
+      });
+    }
+  });
+};
+
+/** 初始化userInfo */
+const resetUserInfo = () => {
+  userInfo.value = {
+    schoolId: "",
+    name: "",
+    loginName: "",
+    role: void 0,
+    course: "",
+  };
+};
+
+const resetPwdInfo = () => {
+  resetPwd.value = {
+    passwd: "",
+    userId: "",
+  };
+};
+
+querySchoolList();
+
+/** effect */
+if (query.schoolId) {
+  queryUserList();
+}
+</script>
+
+<style scoped lang="less">
+.user-manage {
+  .header-block {
+    .ant-input {
+      width: 160px;
+    }
+    :deep(.ant-select-selector) {
+      width: 160px;
+    }
+    .search-button {
+      background-color: @font-color;
+      color: @white;
+      border: none;
+      width: 56px;
+      padding: 0;
+      &:after {
+        display: none;
+        opacity: 0;
+      }
+    }
+
+    .operation-button {
+      width: 72px;
+      padding: 0;
+      margin-left: auto;
+      & ~ .operation-button {
+        margin-left: 8px;
+      }
+    }
+  }
+  .user-table {
+  }
+}
+</style>

+ 31 - 0
src/plugins/configFilter.ts

@@ -0,0 +1,31 @@
+import { AxiosRequestConfig } from "axios";
+
+function isDefine(val: any) {
+  return val !== void 0 && val !== null;
+}
+
+function isObject(val: any) {
+  return Object.prototype.toString.call(val) === "[object Object]";
+}
+
+const filterEmpty = (data:Record<string, any>) => {
+  if (isObject(data)) {
+    data = Object.entries(data).reduce<Record<string, any>>(
+      (d, [k, v]) => {
+        if (isDefine(v)) {
+          d[k] = v;
+        }
+        return d;
+      },
+      {}
+    );
+  }
+  return data
+}
+
+export const filterConfigEmpty = (config: AxiosRequestConfig) => {
+  config.data = filterEmpty(config.data)
+  config.params = filterEmpty(config.params)
+  return config
+};
+

+ 73 - 0
src/plugins/request.ts

@@ -0,0 +1,73 @@
+import axios, { AxiosError } from "axios";
+import { message } from "ant-design-vue";
+import { loadProgressBar } from "axios-progress-bar";
+import { signToken } from "./signToken";
+import { filterConfigEmpty } from "./configFilter";
+import router from "@/routes";
+
+const request = axios.create({
+  baseURL: import.meta.env.VITE_APP_API_HOST,
+  withCredentials: true,
+  timeout: 20000,
+  method: "post",
+  transformRequest: [(data) => new URLSearchParams(data)],
+});
+
+request.interceptors.request.use(
+  (config) => {
+    if (!config.url) {
+      throw new AxiosError(
+        "request must should a url parameter",
+        "REQUEST_PARAMETER_ERROR",
+        config
+      );
+    }
+    config.headers = config.headers || {}
+    if (!config.noAuth) {
+      const { token, timestamp } = signToken(config.url, config.method);
+      config.headers["Authorization"] = token;
+      config.headers["time"] = timestamp;
+    }
+    filterConfigEmpty(config);
+    return config;
+  },
+  (error: AxiosError) => {
+    if (!error.config.noToast) {
+      message.error(error.message || error.name || error.code);
+    }
+    return Promise.reject(error);
+  }
+);
+
+request.interceptors.response.use(
+  (response) => {
+    if (response.config.download) {
+      return response;
+    }
+    return response.data;
+  },
+  (error: AxiosError<any>) => {
+    if (error.isAxiosError && !error.config.noToast) {
+      const msg =
+        error?.response?.data?.message ||
+        error.message ||
+        error.name ||
+        error.code;
+      message.error(msg);
+    } else if (!error.isAxiosError) {
+      error.message && message.error(error.message);
+    }
+    if (
+      error.response &&
+      ([401, 403].includes(error.response.status) ||
+        error.response.data?.code?.toString()?.startsWith("401"))
+    ) {
+      router.push({ name: "login" });
+    }
+    return Promise.reject(error);
+  }
+);
+
+loadProgressBar(null, request);
+
+export default request;

+ 15 - 0
src/plugins/signToken.ts

@@ -0,0 +1,15 @@
+import { getLoginResult } from "@/utils/auth";
+import { SHA1, enc } from "crypto-js";
+
+export const signToken = (url: string, method: string = "post") => {
+  const timestamp = Date.now();
+  const loginResult = getLoginResult();
+  const token = `Token ${loginResult?.sessionId}:${enc.Base64.stringify(
+    SHA1(
+      `${method?.toLowerCase() || "post"}&${url}&${timestamp}&${
+        loginResult?.accessToken
+      }`
+    )
+  )}`;
+  return { token, timestamp };
+};

+ 55 - 0
src/routes/index.ts

@@ -0,0 +1,55 @@
+import { createRouter, createWebHistory } from "vue-router";
+
+import LayOut from "@/layout/index.vue";
+import { sessionStorageTool } from "@/utils/storage";
+import { SESSION_STORAGE_KEYS } from "@/constants/storage";
+
+const router = createRouter({
+  routes: [
+    {
+      path: "/login",
+      name: "login",
+      component: () => import("@/pages/login/index.vue"),
+    },
+    {
+      path: "/",
+      component: LayOut,
+      children: [
+        {
+          path: "exam",
+          name: "exam",
+          component: () => import("@/pages/exam-manage/index.vue"),
+        },
+        {
+          path: "subjects",
+          name: "subjects",
+          component: () => import("@/pages/subjects-manage/index.vue"),
+        },
+        {
+          path: "user",
+          name: "user",
+          component: () => import("@/pages/user-manage/index.vue"),
+        },
+        {
+          path: "/|school",
+          name: "school",
+          component: () => import("@/pages/school-manage/index.vue"),
+        },
+      ],
+    },
+  ],
+  history: createWebHistory(),
+});
+
+router.beforeEach((to, from, next) => {
+  if (to.name === "login") {
+    sessionStorageTool.remove(SESSION_STORAGE_KEYS.LOGIN_RESULT);
+    return next();
+  }
+  if (!sessionStorageTool.get(SESSION_STORAGE_KEYS.LOGIN_RESULT)) {
+    return next({ name: "login" });
+  }
+  next();
+});
+
+export default router;

+ 13 - 0
src/store/main.ts

@@ -0,0 +1,13 @@
+import { defineStore } from "pinia";
+
+const useMainStore = defineStore<"main", Store.MainStoreState>("main", {
+  state() {
+    return {
+      loginResult: null,
+      systemUserInfo: null,
+    };
+  },
+});
+
+
+export default useMainStore

+ 25 - 0
src/utils/auth.ts

@@ -0,0 +1,25 @@
+import { sessionStorageTool } from "@/utils/storage";
+import { SESSION_STORAGE_KEYS } from "@/constants/storage";
+import useMainStore from "@/store/main";
+
+export const getLoginResult = (): LoginResult | null => {
+  return sessionStorageTool.get(SESSION_STORAGE_KEYS.LOGIN_RESULT);
+};
+
+export const cacheLoginResult = (loginResult: LoginResult): LoginResult => {
+  const store = useMainStore();
+  store.loginResult = loginResult;
+  sessionStorageTool.set(SESSION_STORAGE_KEYS.LOGIN_RESULT, loginResult);
+  return loginResult;
+};
+
+export const getUserInfo = (): SystemUserInfo | null => {
+  return sessionStorageTool.get(SESSION_STORAGE_KEYS.USER_INFO);
+};
+
+export const cacheUserInfo = (userInfo: SystemUserInfo): SystemUserInfo => {
+  const store = useMainStore();
+  store.systemUserInfo = userInfo;
+  sessionStorageTool.set(SESSION_STORAGE_KEYS.USER_INFO, userInfo);
+  return userInfo;
+};

+ 33 - 0
src/utils/common.ts

@@ -0,0 +1,33 @@
+import { sessionStorageTool } from "./storage";
+import { SESSION_STORAGE_KEYS } from "@/constants/storage";
+import { loginOutHttp } from "@/apis/common";
+import router from "@/routes";
+
+/** 退出登录 */
+export const exitLogin = () => {
+  loginOutHttp();
+  sessionStorageTool.remove(SESSION_STORAGE_KEYS.LOGIN_RESULT);
+  sessionStorageTool.remove(SESSION_STORAGE_KEYS.USER_INFO);
+  router.replace({ name: "login" });
+};
+
+
+/** 提取文件名 */
+export const extractFileName = (str: string) => {
+  if(/fileName=([^;\s]*)/gi.test(str)){
+    return decodeURIComponent(RegExp.$1)
+  }
+  return ''
+}
+
+/** 下载文件 */
+export const download = (blob: Blob, filename: string = "") => {
+  const a = document.createElement("a");
+  a.download = filename;
+  const blobUrl = URL.createObjectURL(blob);
+  a.href = blobUrl;
+  document.body.appendChild(a);
+  a.click();
+  a.remove();
+  URL.revokeObjectURL(blobUrl);
+};

+ 9 - 0
src/utils/error.ts

@@ -0,0 +1,9 @@
+
+export default class CustomError extends Error {
+  code: string | undefined;
+  constructor(opt: { message?: string; name?: string; code?: string } = {}) {
+    super(opt?.message);
+    this.name = opt?.name ?? "Error";
+    this.code = opt?.code ?? "UNKNOWN";
+  }
+}

+ 14 - 0
src/utils/flexible.ts

@@ -0,0 +1,14 @@
+import { debounce } from "lodash-es";
+
+const flexible = debounce((designWidth: number = 1280) => {
+  const ratio = document.documentElement.clientWidth / designWidth;
+  document.documentElement.style.fontSize = (ratio * designWidth) / 100 + "px";
+
+  addEventListener("resize", () => {
+    flexible();
+  });
+}, 30);
+
+export default () => {
+  flexible(1280);
+};

+ 67 - 0
src/utils/storage.ts

@@ -0,0 +1,67 @@
+import {
+  LOCAL_STORAGE_KEYS,
+  SESSION_STORAGE_KEYS,
+} from "src/constants/storage";
+import { isAfter } from "date-fns";
+
+interface StorageData {
+  cache?: string;
+  data: any;
+}
+
+class StorageTool<T extends "local" | "session"> {
+  storage: Storage;
+
+  constructor(storageType: "local" | "session" = "local") {
+    this.storage =
+      storageType === "local" ? window.localStorage : window.sessionStorage;
+  }
+
+  get(key: T extends "local" ? LOCAL_STORAGE_KEYS : SESSION_STORAGE_KEYS) {
+    const value = this.storage.getItem(key as string);
+    try {
+      const data: StorageData = value ? JSON.parse(value) : value;
+      if (data !== null) {
+        if (data.cache) {
+          if (isAfter(new Date(data.cache), Date.now())) {
+            return data.data;
+          }
+          this.remove(key);
+          return null;
+        }
+        return data.data;
+      }
+      return data;
+    } catch (error) {
+      return value;
+    }
+  }
+
+  set(
+    key: T extends "local" ? LOCAL_STORAGE_KEYS : SESSION_STORAGE_KEYS,
+    data: any,
+    option?: { expires: string | Date }
+  ) {
+    let cache: string | Date = "";
+    if (option?.expires) {
+      try {
+        cache = new Date(option.expires);
+      } catch (error) {
+        console.warn(`set storage ${key} expires error`);
+      }
+    }
+    return this.storage.setItem(key as string, JSON.stringify({ data, cache }));
+  }
+
+  remove(key: T extends "local" ? LOCAL_STORAGE_KEYS : SESSION_STORAGE_KEYS) {
+    return this.storage.removeItem(key as string);
+  }
+
+  clear() {
+    return this.storage.clear();
+  }
+}
+
+export const localStorageTool = new StorageTool<"local">("local");
+
+export const sessionStorageTool = new StorageTool<"session">("session");

+ 15 - 0
src/vite-env.d.ts

@@ -0,0 +1,15 @@
+/// <reference types="vite/client" />
+
+declare module '*.vue' {
+  import type { DefineComponent } from 'vue'
+  const component: DefineComponent<{}, {}, any>
+  export default component
+}
+
+interface ImportMetaEnv {
+  readonly VITE_APP_API_HOST: string
+}
+
+interface ImportMeta {
+  readonly env: ImportMetaEnv
+}

+ 9 - 0
tailwind.config.cjs

@@ -0,0 +1,9 @@
+/** @type {import('tailwindcss').Config} */
+module.exports = {
+  content: ["./index.html", "./src/**/*.{vue,js,ts,jsx,tsx}"],
+  prefix: "tw-",
+  theme: {
+    extend: {},
+  },
+  plugins: [],
+};

+ 49 - 0
tsconfig.json

@@ -0,0 +1,49 @@
+{
+  "compilerOptions": {
+    "target": "ESNext",
+    "useDefineForClassFields": true,
+    "module": "ESNext",
+    "moduleResolution": "Node",
+    "strict": true,
+    "jsx": "preserve",
+    "sourceMap": true,
+    "resolveJsonModule": true,
+    "isolatedModules": true,
+    "esModuleInterop": true,
+    "lib": [
+      "ESNext",
+      "DOM"
+    ],
+    "skipLibCheck": true,
+    "baseUrl": ".",
+    "paths": {
+      "@/*": [
+        "src/*"
+      ],
+      "@api/*": [
+        "src/apis/*"
+      ],
+      "@imgs/*": [
+        "src/assets/images/*"
+      ],
+      "@hooks/*": [
+        "src/hooks/*"
+      ]
+    },
+    "types": [
+      "vite-plugin-svg-icons/client",
+    ]
+  },
+  "include": [
+    "src/**/*.ts",
+    "src/**/*.d.ts",
+    "src/**/*.tsx",
+    "src/**/*.vue",
+    "types/**/*.d.ts"
+  ],
+  "references": [
+    {
+      "path": "./tsconfig.node.json"
+    }
+  ]
+}

+ 9 - 0
tsconfig.node.json

@@ -0,0 +1,9 @@
+{
+  "compilerOptions": {
+    "composite": true,
+    "module": "ESNext",
+    "moduleResolution": "Node",
+    "allowSyntheticDefaultImports": true
+  },
+  "include": ["vite.config.ts"]
+}

+ 13 - 0
types/axios.d.ts

@@ -0,0 +1,13 @@
+import axios from "axios";
+
+declare module "axios" {
+  interface AxiosRequestConfig {
+    noToast?: boolean;
+    noAuth?: boolean;
+    download?: boolean
+  }
+  interface AxiosRequestHeaders {
+    Authorization: string;
+    time: number;
+  }
+}

+ 214 - 0
types/project.d.ts

@@ -0,0 +1,214 @@
+declare module "axios-progress-bar";
+
+interface MenuItem {
+  path?: string;
+  label: string;
+  iconName?: string;
+  children?: MenuItem[];
+}
+
+/** 分页数据 */
+type MultiplePageData<T> = {
+  pageCount?: number;
+  pageNumber?: number;
+  pageSize?: number;
+  totalCount: number;
+  result: T[];
+};
+
+interface LoginModel {
+  loginName: string;
+  password: string;
+}
+
+/** 登录结果 */
+type LoginResult = {
+  accessToken: string;
+  name: string;
+  role: string;
+  schoolId: number;
+  schoolName: string;
+  sessionId: string;
+};
+
+/** 用户信息 */
+type SystemUserInfo = {
+  /** 科目代码集合 */
+  courseCodes: string[];
+  /** 科目名称 */
+  courseNames: string[];
+  createTime: string;
+  /** 启用/禁用 */
+  enable: boolean;
+  /** 用户ID */
+  id: number;
+  /** 登录名 */
+  loginName: string;
+  /** 姓名 */
+  name: string;
+  /** 角色ID */
+  roleId: number;
+  /** 学校ID */
+  schoolId: number;
+  /** 学校名称 */
+  schoolName: string;
+  updateTime: string;
+};
+
+interface BaseMutPageQuery {
+  pageNumber: number;
+  pageSize: number;
+}
+
+/** 查询学校列表参数 */
+interface FetchSchoolListQuery extends BaseMutPageQuery {
+  /** 学校名称 */
+  name?: string;
+}
+
+interface BaseSchoolInfo {
+  /** 学校编码 */
+  code?: string;
+  /** 负责人 */
+  contacts: string;
+  id?: number;
+  /** 学校名称 */
+  name: string;
+  /** 区域 */
+  region: string;
+  /** 联系方式 */
+  telephone: string;
+  /** 启用/禁用 */
+  enable: boolean;
+}
+
+/** 学校列表信息 */
+interface SchoolListInfo extends Required<BaseSchoolInfo> {
+  createTime: string;
+  creatorId: number;
+  /** 二维码 */
+  qrCode: string;
+  updateTime: string;
+  updaterId: number;
+}
+
+/** 查询用户列表参数 */
+interface FetchUserListQuery extends BaseMutPageQuery {
+  /** 用户名称 */
+  loginName?: string;
+  /** 用户角色 */
+  role?: "SUPER_ADMIN" | "SCHOOL_ADMIN" | "SECTION_LEADER";
+  /** 学校ID */
+  schoolId?: string;
+}
+
+interface BaseUserInfo {
+  /** 学校ID */
+  schoolId: string;
+  /** 启用/禁用 */
+  enable?: boolean;
+  /** 用户ID */
+  id?: number;
+  /** 登录名 */
+  loginName: string;
+  /** 姓名 */
+  name: string;
+  /** 角色ID */
+  roleId: string;
+}
+
+/** 用户信息 */
+interface UserInfo extends Required<BaseUserInfo> {
+  /** 科目代码集合 */
+  courseCodes: string[];
+  /** 科目名称 */
+  courseNames: string[];
+  createTime: string;
+  /** 学校名称 */
+  schoolName: string;
+  updateTime: string;
+}
+
+type EditUserInfo = Omit<BaseUserInfo, "roleId"> & {
+  /** 用户角色 */
+  role?: "SUPER_ADMIN" | "SCHOOL_ADMIN" | "SECTION_LEADER";
+  /** 用户密码 */
+  passwd?: string;
+  /** 科目代码集合 */
+  course: string;
+};
+
+interface FetchExamListQuery extends BaseMutPageQuery {
+  /** 考试名称 */
+  name?: string;
+  /** 学校ID */
+  schoolId?: string;
+}
+
+interface BaseExamInfo {
+  /** 考试状态,可用值:EDIT,FINISH,CLOSE */
+  examStatus?: string;
+  /** 考试ID */
+  id?: number;
+  /** 考试名称 */
+  name?: string;
+  /** 学校ID */
+  schoolId?: string;
+}
+
+/** 考试列表信息 */
+
+interface ExamListInfo extends Required<BaseExamInfo> {
+  /** 考试编码 */
+  code: string;
+  createTime: string;
+  /** 科目数量 */
+  paperCount: number;
+  updateTime: string;
+}
+
+/** 科目查询参数 */
+interface FetchSubjectsListQuery extends BaseMutPageQuery {
+  /** 科目代码 */
+  courseCode: string;
+  courseName: string;
+  /** 考试id */
+  examId: string;
+  /** 	分组状态 */
+  groupFinish?: boolean;
+  /** 学校id */
+  schoolId: string;
+  /** 总分截止值 */
+  totalScoreMax: string;
+  /** 总分起始值 */
+  totalScoreMin: string;
+}
+
+/** 科目列表信息 */
+interface SubjectsListInfo {
+  /** 科目代码 */
+  courseCode: string;
+  /** 科目ID */
+  courseId: number;
+  /** 科目名称 */
+  courseName: string;
+  createTime: string;
+  /** 考试ID */
+  examId: number;
+  /** 考试名称 */
+  examName: string;
+  /** 分组数量 */
+  groupCount: number;
+  /** 分组是否完成 */
+  groupFinish: boolean;
+  id: number;
+  /** 学校ID */
+  schoolId: number;
+  /** 试卷结构是否提交 */
+  structFinish: boolean;
+  /** 主观题总分 */
+  subjectiveScore: number;
+  /** 试卷总分 */
+  totalScore: number;
+  updateTime: string;
+}

+ 7 - 0
types/store.d.ts

@@ -0,0 +1,7 @@
+
+namespace Store {
+  interface MainStoreState {
+    systemUserInfo: SystemUserInfo | null;
+    loginResult: LoginResult | null;
+  }
+}

+ 62 - 0
vite.config.ts

@@ -0,0 +1,62 @@
+import path from "path";
+import { defineConfig } from "vite";
+import vue from "@vitejs/plugin-vue";
+import vueJsx from "@vitejs/plugin-vue-jsx";
+import Components from "unplugin-vue-components/vite";
+import { AntDesignVueResolver } from "unplugin-vue-components/resolvers";
+import vueSetupExtend from 'unplugin-vue-setup-extend-plus/vite'
+import { createSvgIconsPlugin } from "vite-plugin-svg-icons";
+
+const resolvePath = (...args: any[]) => {
+  return path.resolve(__dirname, ...args);
+};
+
+// https://vitejs.dev/config/
+export default defineConfig({
+  plugins: [
+    vue({ reactivityTransform: true }),
+    vueJsx({}),
+    Components({ resolvers: [AntDesignVueResolver({ importStyle: false })] }),
+    createSvgIconsPlugin({
+      iconDirs: [resolvePath("src/assets/icons")],
+      symbolId: "icon-[dir]-[name]",
+      customDomId: "__svg__icons__dom__",
+    }),
+    vueSetupExtend()
+  ],
+  resolve: {
+    extensions: [".vue", ".js", ".ts", ".jsx", ".tsx", ".json"],
+    alias: [
+      { find: "@", replacement: resolvePath("src") },
+      { find: "@api", replacement: resolvePath("src/apis") },
+      { find: "@imgs", replacement: resolvePath("src/assets/images") },
+      { find: "@hooks", replacement: resolvePath("src/hooks") },
+    ],
+  },
+  css: {
+    preprocessorOptions: {
+      less: {
+        additionalData(content, context) {
+          if (context.includes("antd.theme.less")) {
+            return (
+              content +
+              `\r\n@import "${resolvePath("src/assets/less/var.less")}";`
+            );
+          }
+          return (
+            `@import "${resolvePath("src/assets/less/var.less")}";\r\n` +
+            content
+          );
+        },
+        javascriptEnabled: true,
+      },
+    },
+  },
+  server: {
+    proxy: {
+      '^/api': {
+        target: 'http://192.168.10.39:7100'
+      }
+    }
+  }
+});