e 10 月之前
父節點
當前提交
83442fb740
共有 100 個文件被更改,包括 7351 次插入0 次删除
  1. 220 0
      vue3/my_Other/package-lock.json
  2. 1 0
      vue3/my_Other/package.json
  3. 二進制
      vue3/项目/vue3-project.zip
  4. 18 0
      vue3/项目/vue3-project/.eslintignore
  5. 43 0
      vue3/项目/vue3-project/.eslintrc.js
  6. 28 0
      vue3/项目/vue3-project/.gitignore
  7. 17 0
      vue3/项目/vue3-project/.prettierrc.js
  8. 21 0
      vue3/项目/vue3-project/LICENSE
  9. 15 0
      vue3/项目/vue3-project/README.md
  10. 2813 0
      vue3/项目/vue3-project/admin-api_OpenAPI.json
  11. 13 0
      vue3/项目/vue3-project/index.html
  12. 10 0
      vue3/项目/vue3-project/jsconfig.json
  13. 15 0
      vue3/项目/vue3-project/mock/_createProductionServer.js
  14. 39 0
      vue3/项目/vue3-project/mock/login.js
  15. 66 0
      vue3/项目/vue3-project/mock/menu.js
  16. 60 0
      vue3/项目/vue3-project/mock/test.js
  17. 76 0
      vue3/项目/vue3-project/package.json
  18. 5 0
      vue3/项目/vue3-project/postcss.config.js
  19. 二進制
      vue3/项目/vue3-project/public/favicon.ico
  20. 43 0
      vue3/项目/vue3-project/src/App.vue
  21. 45 0
      vue3/项目/vue3-project/src/api/brand.js
  22. 17 0
      vue3/项目/vue3-project/src/api/login.js
  23. 18 0
      vue3/项目/vue3-project/src/api/menu.js
  24. 65 0
      vue3/项目/vue3-project/src/api/product.js
  25. 15 0
      vue3/项目/vue3-project/src/api/productSpec.js
  26. 11 0
      vue3/项目/vue3-project/src/api/productUnit.js
  27. 37 0
      vue3/项目/vue3-project/src/api/sysMenu.js
  28. 62 0
      vue3/项目/vue3-project/src/api/sysRole.js
  29. 44 0
      vue3/项目/vue3-project/src/api/sysUser.js
  30. 18 0
      vue3/项目/vue3-project/src/api/test.js
  31. 1 0
      vue3/项目/vue3-project/src/assets/logo.svg
  32. 14 0
      vue3/项目/vue3-project/src/assets/style/element-variables.scss
  33. 18 0
      vue3/项目/vue3-project/src/assets/style/global-variables.scss
  34. 1 0
      vue3/项目/vue3-project/src/assets/svg/bug.svg
  35. 0 0
      vue3/项目/vue3-project/src/assets/svg/error-icons/403.svg
  36. 0 0
      vue3/项目/vue3-project/src/assets/svg/error-icons/404.svg
  37. 0 0
      vue3/项目/vue3-project/src/assets/svg/error-icons/500.svg
  38. 1 0
      vue3/项目/vue3-project/src/assets/svg/home.svg
  39. 1 0
      vue3/项目/vue3-project/src/assets/svg/icon-home.svg
  40. 0 0
      vue3/项目/vue3-project/src/assets/svg/language.svg
  41. 7 0
      vue3/项目/vue3-project/src/components/Avatar/hooks/useUserinfo.js
  42. 46 0
      vue3/项目/vue3-project/src/components/Avatar/index.vue
  43. 103 0
      vue3/项目/vue3-project/src/components/ErrorLog/index.vue
  44. 512 0
      vue3/项目/vue3-project/src/components/ProTable/index.vue
  45. 143 0
      vue3/项目/vue3-project/src/components/SelectTree/Select.vue
  46. 107 0
      vue3/项目/vue3-project/src/components/SelectTree/Tree.vue
  47. 118 0
      vue3/项目/vue3-project/src/components/SelectTree/index.vue
  48. 33 0
      vue3/项目/vue3-project/src/components/SvgIcon/index.vue
  49. 24 0
      vue3/项目/vue3-project/src/default-settings.js
  50. 17 0
      vue3/项目/vue3-project/src/directive/index.js
  51. 27 0
      vue3/项目/vue3-project/src/error-log.js
  52. 2 0
      vue3/项目/vue3-project/src/global-components.js
  53. 33 0
      vue3/项目/vue3-project/src/hooks/useCloseTag.js
  54. 19 0
      vue3/项目/vue3-project/src/i18n/index.js
  55. 6 0
      vue3/项目/vue3-project/src/i18n/locales/en/error.js
  56. 10 0
      vue3/项目/vue3-project/src/i18n/locales/en/login.js
  57. 16 0
      vue3/项目/vue3-project/src/i18n/locales/en/menu.js
  58. 22 0
      vue3/项目/vue3-project/src/i18n/locales/en/public.js
  59. 8 0
      vue3/项目/vue3-project/src/i18n/locales/en/tags.js
  60. 32 0
      vue3/项目/vue3-project/src/i18n/locales/en/test/list.js
  61. 21 0
      vue3/项目/vue3-project/src/i18n/locales/en/topbar.js
  62. 6 0
      vue3/项目/vue3-project/src/i18n/locales/zh-cn/error.js
  63. 12 0
      vue3/项目/vue3-project/src/i18n/locales/zh-cn/login.js
  64. 16 0
      vue3/项目/vue3-project/src/i18n/locales/zh-cn/menu.js
  65. 22 0
      vue3/项目/vue3-project/src/i18n/locales/zh-cn/public.js
  66. 8 0
      vue3/项目/vue3-project/src/i18n/locales/zh-cn/tags.js
  67. 32 0
      vue3/项目/vue3-project/src/i18n/locales/zh-cn/test/list.js
  68. 21 0
      vue3/项目/vue3-project/src/i18n/locales/zh-cn/topbar.js
  69. 16 0
      vue3/项目/vue3-project/src/i18n/useLang.js
  70. 26 0
      vue3/项目/vue3-project/src/layout/components/Content/index.vue
  71. 30 0
      vue3/项目/vue3-project/src/layout/components/Sidebar/Item.vue
  72. 42 0
      vue3/项目/vue3-project/src/layout/components/Sidebar/Logo.vue
  73. 124 0
      vue3/项目/vue3-project/src/layout/components/Sidebar/Menus.vue
  74. 36 0
      vue3/项目/vue3-project/src/layout/components/Sidebar/Submenu.vue
  75. 5 0
      vue3/项目/vue3-project/src/layout/components/Sidebar/config/menu.module.scss
  76. 80 0
      vue3/项目/vue3-project/src/layout/components/Sidebar/index.vue
  77. 103 0
      vue3/项目/vue3-project/src/layout/components/Tagsbar/hooks/useContextMenu.js
  78. 54 0
      vue3/项目/vue3-project/src/layout/components/Tagsbar/hooks/useScrollbar.js
  79. 103 0
      vue3/项目/vue3-project/src/layout/components/Tagsbar/hooks/useTags.js
  80. 180 0
      vue3/项目/vue3-project/src/layout/components/Tagsbar/index.vue
  81. 124 0
      vue3/项目/vue3-project/src/layout/components/Topbar/Breadcrumbs.vue
  82. 48 0
      vue3/项目/vue3-project/src/layout/components/Topbar/ChangeLang.vue
  83. 40 0
      vue3/项目/vue3-project/src/layout/components/Topbar/Hamburger.vue
  84. 88 0
      vue3/项目/vue3-project/src/layout/components/Topbar/LockModal.vue
  85. 80 0
      vue3/项目/vue3-project/src/layout/components/Topbar/Userinfo.vue
  86. 104 0
      vue3/项目/vue3-project/src/layout/components/Topbar/index.vue
  87. 34 0
      vue3/项目/vue3-project/src/layout/hooks/useResizeHandler.js
  88. 102 0
      vue3/项目/vue3-project/src/layout/index.vue
  89. 51 0
      vue3/项目/vue3-project/src/main.js
  90. 93 0
      vue3/项目/vue3-project/src/permission.js
  91. 5 0
      vue3/项目/vue3-project/src/pinia/index.js
  92. 23 0
      vue3/项目/vue3-project/src/pinia/modules/account.js
  93. 82 0
      vue3/项目/vue3-project/src/pinia/modules/app.js
  94. 18 0
      vue3/项目/vue3-project/src/pinia/modules/errorLog.js
  95. 15 0
      vue3/项目/vue3-project/src/pinia/modules/layoutSettings.js
  96. 101 0
      vue3/项目/vue3-project/src/pinia/modules/menu.js
  97. 102 0
      vue3/项目/vue3-project/src/pinia/modules/tags.js
  98. 41 0
      vue3/项目/vue3-project/src/router/index.js
  99. 81 0
      vue3/项目/vue3-project/src/router/modules/error.js
  100. 26 0
      vue3/项目/vue3-project/src/router/modules/home.js

+ 220 - 0
vue3/my_Other/package-lock.json

@@ -8,6 +8,7 @@
       "name": "my-other",
       "version": "0.0.0",
       "dependencies": {
+        "element-plus": "^2.7.5",
         "mock": "^0.1.1",
         "mockjs": "^1.1.0",
         "vue": "^3.4.21"
@@ -34,6 +35,22 @@
         "node": ">=6.0.0"
       }
     },
+    "node_modules/@ctrl/tinycolor": {
+      "version": "3.6.1",
+      "resolved": "https://registry.npmmirror.com/@ctrl/tinycolor/-/tinycolor-3.6.1.tgz",
+      "integrity": "sha512-SITSV6aIXsuVNV3f3O0f2n/cgyEDWoSqtZMYiAmcsYHydcKrOz3gUxB/iXd/Qf08+IZX4KpgNbvUdMBmWz+kcA==",
+      "engines": {
+        "node": ">=10"
+      }
+    },
+    "node_modules/@element-plus/icons-vue": {
+      "version": "2.3.1",
+      "resolved": "https://registry.npmmirror.com/@element-plus/icons-vue/-/icons-vue-2.3.1.tgz",
+      "integrity": "sha512-XxVUZv48RZAd87ucGS48jPf6pKu0yV5UCg9f4FFwtrYxXOwWuVJo6wOvSLKEoMQKjv8GsX/mhP6UsC1lRwbUWg==",
+      "peerDependencies": {
+        "vue": "^3.2.0"
+      }
+    },
     "node_modules/@esbuild/aix-ppc64": {
       "version": "0.20.2",
       "resolved": "https://registry.npmmirror.com/@esbuild/aix-ppc64/-/aix-ppc64-0.20.2.tgz",
@@ -402,11 +419,43 @@
         "node": ">=12"
       }
     },
+    "node_modules/@floating-ui/core": {
+      "version": "1.6.2",
+      "resolved": "https://registry.npmmirror.com/@floating-ui/core/-/core-1.6.2.tgz",
+      "integrity": "sha512-+2XpQV9LLZeanU4ZevzRnGFg2neDeKHgFLjP6YLW+tly0IvrhqT4u8enLGjLH3qeh85g19xY5rsAusfwTdn5lg==",
+      "dependencies": {
+        "@floating-ui/utils": "^0.2.0"
+      }
+    },
+    "node_modules/@floating-ui/dom": {
+      "version": "1.6.5",
+      "resolved": "https://registry.npmmirror.com/@floating-ui/dom/-/dom-1.6.5.tgz",
+      "integrity": "sha512-Nsdud2X65Dz+1RHjAIP0t8z5e2ff/IRbei6BqFrl1urT8sDVzM1HMQ+R0XcU5ceRfyO3I6ayeqIfh+6Wb8LGTw==",
+      "dependencies": {
+        "@floating-ui/core": "^1.0.0",
+        "@floating-ui/utils": "^0.2.0"
+      }
+    },
+    "node_modules/@floating-ui/utils": {
+      "version": "0.2.2",
+      "resolved": "https://registry.npmmirror.com/@floating-ui/utils/-/utils-0.2.2.tgz",
+      "integrity": "sha512-J4yDIIthosAsRZ5CPYP/jQvUAQtlZTTD/4suA08/FEnlxqW3sKS9iAhgsa9VYLZ6vDHn/ixJgIqRQPotoBjxIw=="
+    },
     "node_modules/@jridgewell/sourcemap-codec": {
       "version": "1.4.15",
       "resolved": "https://registry.npmmirror.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz",
       "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg=="
     },
+    "node_modules/@popperjs/core": {
+      "name": "@sxzz/popperjs-es",
+      "version": "2.11.7",
+      "resolved": "https://registry.npmmirror.com/@sxzz/popperjs-es/-/popperjs-es-2.11.7.tgz",
+      "integrity": "sha512-Ccy0NlLkzr0Ex2FKvh2X+OyERHXJ88XJ1MXtsI9y9fGexlaXaVTPzBCRBwIxFkORuOb+uBqeu+RqnpgYTEZRUQ==",
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/popperjs"
+      }
+    },
     "node_modules/@rollup/rollup-android-arm-eabi": {
       "version": "4.18.0",
       "resolved": "https://registry.npmmirror.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.18.0.tgz",
@@ -627,6 +676,19 @@
       "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==",
       "dev": true
     },
+    "node_modules/@types/lodash": {
+      "version": "4.17.5",
+      "resolved": "https://registry.npmmirror.com/@types/lodash/-/lodash-4.17.5.tgz",
+      "integrity": "sha512-MBIOHVZqVqgfro1euRDWX7OO0fBVUUMrN6Pwm8LQsz8cWhEpihlvR70ENj3f40j58TNxZaWv2ndSkInykNBBJw=="
+    },
+    "node_modules/@types/lodash-es": {
+      "version": "4.17.12",
+      "resolved": "https://registry.npmmirror.com/@types/lodash-es/-/lodash-es-4.17.12.tgz",
+      "integrity": "sha512-0NgftHUcV4v34VhXm8QBSftKVXtbkBG3ViCjs6+eJ5a6y6Mi/jiFGPc1sC7QK+9BFhWrURE3EOggmWaSxL9OzQ==",
+      "dependencies": {
+        "@types/lodash": "*"
+      }
+    },
     "node_modules/@types/node": {
       "version": "20.14.2",
       "resolved": "https://registry.npmmirror.com/@types/node/-/node-20.14.2.tgz",
@@ -636,6 +698,11 @@
         "undici-types": "~5.26.4"
       }
     },
+    "node_modules/@types/web-bluetooth": {
+      "version": "0.0.16",
+      "resolved": "https://registry.npmmirror.com/@types/web-bluetooth/-/web-bluetooth-0.0.16.tgz",
+      "integrity": "sha512-oh8q2Zc32S6gd/j50GowEjKLoOVOwHP/bWVjKJInBwQqdOYMdPrf1oVlelTlyfFK3CKxL1uahMDAr+vy8T7yMQ=="
+    },
     "node_modules/@vitejs/plugin-vue": {
       "version": "5.0.5",
       "resolved": "https://registry.npmmirror.com/@vitejs/plugin-vue/-/plugin-vue-5.0.5.tgz",
@@ -797,6 +864,89 @@
       "integrity": "sha512-VcZK7MvpjuTPx2w6blwnwZAu5/LgBUtejFOi3pPGQFXQN5Ela03FUtd2Qtg4yWGGissVL0dr6Ro1LfOFh+PCuQ==",
       "dev": true
     },
+    "node_modules/@vueuse/core": {
+      "version": "9.13.0",
+      "resolved": "https://registry.npmmirror.com/@vueuse/core/-/core-9.13.0.tgz",
+      "integrity": "sha512-pujnclbeHWxxPRqXWmdkKV5OX4Wk4YeK7wusHqRwU0Q7EFusHoqNA/aPhB6KCh9hEqJkLAJo7bb0Lh9b+OIVzw==",
+      "dependencies": {
+        "@types/web-bluetooth": "^0.0.16",
+        "@vueuse/metadata": "9.13.0",
+        "@vueuse/shared": "9.13.0",
+        "vue-demi": "*"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/antfu"
+      }
+    },
+    "node_modules/@vueuse/core/node_modules/vue-demi": {
+      "version": "0.14.8",
+      "resolved": "https://registry.npmmirror.com/vue-demi/-/vue-demi-0.14.8.tgz",
+      "integrity": "sha512-Uuqnk9YE9SsWeReYqK2alDI5YzciATE0r2SkA6iMAtuXvNTMNACJLJEXNXaEy94ECuBe4Sk6RzRU80kjdbIo1Q==",
+      "hasInstallScript": true,
+      "bin": {
+        "vue-demi-fix": "bin/vue-demi-fix.js",
+        "vue-demi-switch": "bin/vue-demi-switch.js"
+      },
+      "engines": {
+        "node": ">=12"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/antfu"
+      },
+      "peerDependencies": {
+        "@vue/composition-api": "^1.0.0-rc.1",
+        "vue": "^3.0.0-0 || ^2.6.0"
+      },
+      "peerDependenciesMeta": {
+        "@vue/composition-api": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/@vueuse/metadata": {
+      "version": "9.13.0",
+      "resolved": "https://registry.npmmirror.com/@vueuse/metadata/-/metadata-9.13.0.tgz",
+      "integrity": "sha512-gdU7TKNAUVlXXLbaF+ZCfte8BjRJQWPCa2J55+7/h+yDtzw3vOoGQDRXzI6pyKyo6bXFT5/QoPE4hAknExjRLQ==",
+      "funding": {
+        "url": "https://github.com/sponsors/antfu"
+      }
+    },
+    "node_modules/@vueuse/shared": {
+      "version": "9.13.0",
+      "resolved": "https://registry.npmmirror.com/@vueuse/shared/-/shared-9.13.0.tgz",
+      "integrity": "sha512-UrnhU+Cnufu4S6JLCPZnkWh0WwZGUp72ktOF2DFptMlOs3TOdVv8xJN53zhHGARmVOsz5KqOls09+J1NR6sBKw==",
+      "dependencies": {
+        "vue-demi": "*"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/antfu"
+      }
+    },
+    "node_modules/@vueuse/shared/node_modules/vue-demi": {
+      "version": "0.14.8",
+      "resolved": "https://registry.npmmirror.com/vue-demi/-/vue-demi-0.14.8.tgz",
+      "integrity": "sha512-Uuqnk9YE9SsWeReYqK2alDI5YzciATE0r2SkA6iMAtuXvNTMNACJLJEXNXaEy94ECuBe4Sk6RzRU80kjdbIo1Q==",
+      "hasInstallScript": true,
+      "bin": {
+        "vue-demi-fix": "bin/vue-demi-fix.js",
+        "vue-demi-switch": "bin/vue-demi-switch.js"
+      },
+      "engines": {
+        "node": ">=12"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/antfu"
+      },
+      "peerDependencies": {
+        "@vue/composition-api": "^1.0.0-rc.1",
+        "vue": "^3.0.0-0 || ^2.6.0"
+      },
+      "peerDependenciesMeta": {
+        "@vue/composition-api": {
+          "optional": true
+        }
+      }
+    },
     "node_modules/ansi-styles": {
       "version": "6.2.1",
       "resolved": "https://registry.npmmirror.com/ansi-styles/-/ansi-styles-6.2.1.tgz",
@@ -809,6 +959,11 @@
         "url": "https://github.com/chalk/ansi-styles?sponsor=1"
       }
     },
+    "node_modules/async-validator": {
+      "version": "4.2.5",
+      "resolved": "https://registry.npmmirror.com/async-validator/-/async-validator-4.2.5.tgz",
+      "integrity": "sha512-7HhHjtERjqlNbZtqNqy2rckN/SpOOlmDliet+lP7k+eKZEjPk3DgyeU9lIXLdeLz0uBbbVp+9Qdow9wJWgwwfg=="
+    },
     "node_modules/balanced-match": {
       "version": "1.0.2",
       "resolved": "https://registry.npmmirror.com/balanced-match/-/balanced-match-1.0.2.tgz",
@@ -857,12 +1012,42 @@
       "resolved": "https://registry.npmmirror.com/csstype/-/csstype-3.1.3.tgz",
       "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="
     },
+    "node_modules/dayjs": {
+      "version": "1.11.11",
+      "resolved": "https://registry.npmmirror.com/dayjs/-/dayjs-1.11.11.tgz",
+      "integrity": "sha512-okzr3f11N6WuqYtZSvm+F776mB41wRZMhKP+hc34YdW+KmtYYK9iqvHSwo2k9FEH3fhGXvOPV6yz2IcSrfRUDg=="
+    },
     "node_modules/de-indent": {
       "version": "1.0.2",
       "resolved": "https://registry.npmmirror.com/de-indent/-/de-indent-1.0.2.tgz",
       "integrity": "sha512-e/1zu3xH5MQryN2zdVaF0OrdNLUbvWxzMbi+iNA6Bky7l1RoP8a2fIbRocyHclXt/arDrrR6lL3TqFD9pMQTsg==",
       "dev": true
     },
+    "node_modules/element-plus": {
+      "version": "2.7.5",
+      "resolved": "https://registry.npmmirror.com/element-plus/-/element-plus-2.7.5.tgz",
+      "integrity": "sha512-e4oqhfRGBpdblgsjEBK+tA2+fg1H1KZ2Qinty1SaJl0X49FrMLK0lpXQNheWyBqI4V/pyjVOF9sRjz2hfyoctw==",
+      "dependencies": {
+        "@ctrl/tinycolor": "^3.4.1",
+        "@element-plus/icons-vue": "^2.3.1",
+        "@floating-ui/dom": "^1.0.1",
+        "@popperjs/core": "npm:@sxzz/popperjs-es@^2.11.7",
+        "@types/lodash": "^4.14.182",
+        "@types/lodash-es": "^4.17.6",
+        "@vueuse/core": "^9.1.0",
+        "async-validator": "^4.2.5",
+        "dayjs": "^1.11.3",
+        "escape-html": "^1.0.3",
+        "lodash": "^4.17.21",
+        "lodash-es": "^4.17.21",
+        "lodash-unified": "^1.0.2",
+        "memoize-one": "^6.0.0",
+        "normalize-wheel-es": "^1.2.0"
+      },
+      "peerDependencies": {
+        "vue": "^3.2.0"
+      }
+    },
     "node_modules/entities": {
       "version": "4.5.0",
       "resolved": "https://registry.npmmirror.com/entities/-/entities-4.5.0.tgz",
@@ -912,6 +1097,11 @@
         "@esbuild/win32-x64": "0.20.2"
       }
     },
+    "node_modules/escape-html": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmmirror.com/escape-html/-/escape-html-1.0.3.tgz",
+      "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow=="
+    },
     "node_modules/estree-walker": {
       "version": "2.0.2",
       "resolved": "https://registry.npmmirror.com/estree-walker/-/estree-walker-2.0.2.tgz",
@@ -955,6 +1145,26 @@
         "node": "^14.17.0 || ^16.13.0 || >=18.0.0"
       }
     },
+    "node_modules/lodash": {
+      "version": "4.17.21",
+      "resolved": "https://registry.npmmirror.com/lodash/-/lodash-4.17.21.tgz",
+      "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
+    },
+    "node_modules/lodash-es": {
+      "version": "4.17.21",
+      "resolved": "https://registry.npmmirror.com/lodash-es/-/lodash-es-4.17.21.tgz",
+      "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw=="
+    },
+    "node_modules/lodash-unified": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmmirror.com/lodash-unified/-/lodash-unified-1.0.3.tgz",
+      "integrity": "sha512-WK9qSozxXOD7ZJQlpSqOT+om2ZfcT4yO+03FuzAHD0wF6S0l0090LRPDx3vhTTLZ8cFKpBn+IOcVXK6qOcIlfQ==",
+      "peerDependencies": {
+        "@types/lodash-es": "*",
+        "lodash": "*",
+        "lodash-es": "*"
+      }
+    },
     "node_modules/magic-string": {
       "version": "0.30.10",
       "resolved": "https://registry.npmmirror.com/magic-string/-/magic-string-0.30.10.tgz",
@@ -963,6 +1173,11 @@
         "@jridgewell/sourcemap-codec": "^1.4.15"
       }
     },
+    "node_modules/memoize-one": {
+      "version": "6.0.0",
+      "resolved": "https://registry.npmmirror.com/memoize-one/-/memoize-one-6.0.0.tgz",
+      "integrity": "sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw=="
+    },
     "node_modules/memorystream": {
       "version": "0.3.1",
       "resolved": "https://registry.npmmirror.com/memorystream/-/memorystream-0.3.1.tgz",
@@ -1026,6 +1241,11 @@
         "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
       }
     },
+    "node_modules/normalize-wheel-es": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmmirror.com/normalize-wheel-es/-/normalize-wheel-es-1.2.0.tgz",
+      "integrity": "sha512-Wj7+EJQ8mSuXr2iWfnujrimU35R2W4FAErEyTmJoJ7ucwTn2hOUSsRehMb5RSYkxXGTM7Y9QpvPmp++w5ftoJw=="
+    },
     "node_modules/npm-normalize-package-bin": {
       "version": "3.0.1",
       "resolved": "https://registry.npmmirror.com/npm-normalize-package-bin/-/npm-normalize-package-bin-3.0.1.tgz",

+ 1 - 0
vue3/my_Other/package.json

@@ -11,6 +11,7 @@
     "type-check": "vue-tsc --build --force"
   },
   "dependencies": {
+    "element-plus": "^2.7.5",
     "mock": "^0.1.1",
     "mockjs": "^1.1.0",
     "vue": "^3.4.21"

二進制
vue3/项目/vue3-project.zip


+ 18 - 0
vue3/项目/vue3-project/.eslintignore

@@ -0,0 +1,18 @@
+build/*.js
+src/assets
+mock
+
+*.sh
+node_modules
+*.md
+*.woff
+*.ttf
+.vscode
+.idea
+dist
+/public
+/docs
+.husky
+.local
+/bin
+Dockerfile

+ 43 - 0
vue3/项目/vue3-project/.eslintrc.js

@@ -0,0 +1,43 @@
+module.exports = {
+  root: true,
+  env: {
+    browser: true,
+    node: true,
+    es6: true,
+  },
+  extends: ['plugin:vue/vue3-essential', 'eslint:recommended', '@vue/prettier'],
+  parserOptions: {
+    parser: 'babel-eslint',
+    sourceType: 'module',
+    ecmaVersion: 2020,
+  },
+  rules: {
+    'no-console': 0,
+    'no-use-before-define': 'off',
+    'no-unused-vars': [
+      'warn',
+      {
+        // 允许声明未使用变量
+        vars: 'local',
+        // 参数不检查
+        args: 'none',
+      },
+    ],
+    'vue/no-unused-vars': 'warn',
+    'no-prototype-builtins': 'off',
+    'no-irregular-whitespace': 'off',
+    'space-before-function-paren': 'off',
+    'vue/custom-event-name-casing': 'off',
+    'vue/attributes-order': 'off',
+    'vue/one-component-per-file': 'off',
+    'vue/html-closing-bracket-newline': 'off',
+    'vue/max-attributes-per-line': 'off',
+    'vue/multiline-html-element-content-newline': 'off',
+    'vue/singleline-html-element-content-newline': 'off',
+    'vue/attribute-hyphenation': 'off',
+    'vue/require-default-prop': 'off',
+    'vue/no-unused-components': 'warn',
+    'vue/no-setup-props-destructure': 'off',
+    'vue/script-setup-uses-vars': 'off',
+  },
+}

+ 28 - 0
vue3/项目/vue3-project/.gitignore

@@ -0,0 +1,28 @@
+.DS_Store
+node_modules
+.DS_Store
+dist
+dist-ssr
+
+# local env files
+.env.local
+.env.*.local
+
+# Log files
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+pnpm-debug.log*
+
+
+# Editor directories and files
+.idea
+.vscode
+*.suo
+*.ntvs*
+*.njsproj
+*.sln
+*.sw?
+
+package-lock.json
+yarn.lock

+ 17 - 0
vue3/项目/vue3-project/.prettierrc.js

@@ -0,0 +1,17 @@
+module.exports = {
+  printWidth: 80, // 每行代码长度(默认80)
+  tabWidth: 2, // 每个tab相当于多少个空格(默认2)
+  useTabs: false, // 是否使用tab进行缩进(默认false)
+  singleQuote: true, // 使用单引号(默认false)
+  semi: false, // 声明结尾使用分号(默认true)
+  trailingComma: 'es5', // 多行使用拖尾逗号(默认none)
+  bracketSpacing: true, // 对象字面量的大括号间使用空格(默认true)
+  jsxBracketSameLine: false, // 多行JSX中的>放置在最后一行的结尾,而不是另起一行(默认false)
+  arrowParens: 'avoid', // 只有一个参数的箭头函数的参数是否带括号(默认avoid)
+  htmlWhitespaceSensitivity: 'ignore', // 空格不敏感。(开始标签、内容、结束标签各自单独一行)
+  // vueIndentScriptAndStyle: false, // 是否给vue中的 <script> and <style>标签加缩进
+  // embeddedLanguageFormatting: 'auto', // 是否格式化嵌入到JS中的html标记的代码段或者Markdown语法 auto-格式化 off-不格式化
+  // requirePragma: false, // 若为true,文件顶部加了 /*** @prettier */或/*** @format */的文件才会被格式化
+  // insertPragma: false, // 当requirePragma参数为true时,此参数为true将向@format标记后面添加一个换行符
+  // proseWrap: 'never', // 有效选项[always|never|preserve]。当Markdown文本超过printWidth时,是否换行,always-换行 never-不换行 preserve保持原样
+}

+ 21 - 0
vue3/项目/vue3-project/LICENSE

@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2021 ZSEN 
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.

+ 15 - 0
vue3/项目/vue3-project/README.md

@@ -0,0 +1,15 @@
+# 安装依赖
+npm install
+
+# 建议不要直接使用 cnpm 安装依赖,会有各种诡异的 bug。可以通过如下操作解决 npm 下载速度慢的问题
+npm install --registry=https://registry.npm.taobao.org
+
+# 启动服务
+npm start
+```
+
+## 打包发布
+
+```bash
+npm run build
+```

+ 2813 - 0
vue3/项目/vue3-project/admin-api_OpenAPI.json

@@ -0,0 +1,2813 @@
+{
+  "openapi": "3.0.1",
+  "info": {
+    "title": "小小egAPI接口文档",
+    "description": "小小egAPI接口文档",
+    "contact": {
+      "name": "xxeg"
+    },
+    "version": "1.0"
+  },
+  "servers": [
+    {
+      "url": "http://localhost:8501",
+      "description": "Generated server url"
+    }
+  ],
+  "paths": {
+    "/admin/system/sysUser/updateSysUser": {
+      "put": {
+        "tags": [
+          "sys-user-controller"
+        ],
+        "operationId": "updateSysUser",
+        "requestBody": {
+          "content": {
+            "application/json": {
+              "schema": {
+                "$ref": "#/components/schemas/SysUser"
+              }
+            }
+          },
+          "required": true
+        },
+        "responses": {
+          "200": {
+            "description": "OK",
+            "content": {
+              "*/*": {
+                "schema": {
+                  "$ref": "#/components/schemas/Result"
+                }
+              }
+            }
+          }
+        }
+      }
+    },
+    "/admin/system/sysRole/updateSysRole": {
+      "put": {
+        "tags": [
+          "sys-role-controller"
+        ],
+        "operationId": "updateSysRole",
+        "requestBody": {
+          "content": {
+            "application/json": {
+              "schema": {
+                "$ref": "#/components/schemas/SysRole"
+              }
+            }
+          },
+          "required": true
+        },
+        "responses": {
+          "200": {
+            "description": "OK",
+            "content": {
+              "*/*": {
+                "schema": {
+                  "$ref": "#/components/schemas/Result"
+                }
+              }
+            }
+          }
+        }
+      }
+    },
+    "/admin/system/sysMenu/updateById": {
+      "put": {
+        "tags": [
+          "sys-menu-controller"
+        ],
+        "operationId": "updateById",
+        "requestBody": {
+          "content": {
+            "application/json": {
+              "schema": {
+                "$ref": "#/components/schemas/SysMenu"
+              }
+            }
+          },
+          "required": true
+        },
+        "responses": {
+          "200": {
+            "description": "OK",
+            "content": {
+              "*/*": {
+                "schema": {
+                  "$ref": "#/components/schemas/Result"
+                }
+              }
+            }
+          }
+        }
+      }
+    },
+    "/admin/product/productSpec/updateById": {
+      "put": {
+        "tags": [
+          "product-spec-controller"
+        ],
+        "operationId": "updateById_1",
+        "requestBody": {
+          "content": {
+            "application/json": {
+              "schema": {
+                "$ref": "#/components/schemas/ProductSpec"
+              }
+            }
+          },
+          "required": true
+        },
+        "responses": {
+          "200": {
+            "description": "OK",
+            "content": {
+              "*/*": {
+                "schema": {
+                  "$ref": "#/components/schemas/Result"
+                }
+              }
+            }
+          }
+        }
+      }
+    },
+    "/admin/product/product/updateById": {
+      "put": {
+        "tags": [
+          "product-controller"
+        ],
+        "operationId": "updateById_2",
+        "requestBody": {
+          "content": {
+            "application/json": {
+              "schema": {
+                "$ref": "#/components/schemas/Product"
+              }
+            }
+          },
+          "required": true
+        },
+        "responses": {
+          "200": {
+            "description": "OK",
+            "content": {
+              "*/*": {
+                "schema": {
+                  "$ref": "#/components/schemas/Result"
+                }
+              }
+            }
+          }
+        }
+      }
+    },
+    "/admin/product/categoryBrand/updateById": {
+      "put": {
+        "tags": [
+          "category-brand-controller"
+        ],
+        "operationId": "updateById_3",
+        "requestBody": {
+          "content": {
+            "application/json": {
+              "schema": {
+                "$ref": "#/components/schemas/CategoryBrand"
+              }
+            }
+          },
+          "required": true
+        },
+        "responses": {
+          "200": {
+            "description": "OK",
+            "content": {
+              "*/*": {
+                "schema": {
+                  "$ref": "#/components/schemas/Result"
+                }
+              }
+            }
+          }
+        }
+      }
+    },
+    "/admin/product/brand/updateById": {
+      "put": {
+        "tags": [
+          "brand-controller"
+        ],
+        "operationId": "updateById_4",
+        "requestBody": {
+          "content": {
+            "application/json": {
+              "schema": {
+                "$ref": "#/components/schemas/Brand"
+              }
+            }
+          },
+          "required": true
+        },
+        "responses": {
+          "200": {
+            "description": "OK",
+            "content": {
+              "*/*": {
+                "schema": {
+                  "$ref": "#/components/schemas/Result"
+                }
+              }
+            }
+          }
+        }
+      }
+    },
+    "/admin/system/sysUser/saveSysUser": {
+      "post": {
+        "tags": [
+          "sys-user-controller"
+        ],
+        "operationId": "saveSysUser",
+        "requestBody": {
+          "content": {
+            "application/json": {
+              "schema": {
+                "$ref": "#/components/schemas/SysUser"
+              }
+            }
+          },
+          "required": true
+        },
+        "responses": {
+          "200": {
+            "description": "OK",
+            "content": {
+              "*/*": {
+                "schema": {
+                  "$ref": "#/components/schemas/Result"
+                }
+              }
+            }
+          }
+        }
+      }
+    },
+    "/admin/system/sysRoleUser/doAssign": {
+      "post": {
+        "tags": [
+          "sys-role-user-controller"
+        ],
+        "operationId": "doAssign",
+        "requestBody": {
+          "content": {
+            "application/json": {
+              "schema": {
+                "$ref": "#/components/schemas/AssginRoleDto"
+              }
+            }
+          },
+          "required": true
+        },
+        "responses": {
+          "200": {
+            "description": "OK",
+            "content": {
+              "*/*": {
+                "schema": {
+                  "$ref": "#/components/schemas/Result"
+                }
+              }
+            }
+          }
+        }
+      }
+    },
+    "/admin/system/sysRoleMenu/doAssign": {
+      "post": {
+        "tags": [
+          "sys-role-menu-controller"
+        ],
+        "operationId": "doAssign_1",
+        "requestBody": {
+          "content": {
+            "application/json": {
+              "schema": {
+                "$ref": "#/components/schemas/AssginMenuDto"
+              }
+            }
+          },
+          "required": true
+        },
+        "responses": {
+          "200": {
+            "description": "OK",
+            "content": {
+              "*/*": {
+                "schema": {
+                  "$ref": "#/components/schemas/Result"
+                }
+              }
+            }
+          }
+        }
+      }
+    },
+    "/admin/system/sysRole/saveSysRole": {
+      "post": {
+        "tags": [
+          "sys-role-controller"
+        ],
+        "operationId": "saveSysRole",
+        "requestBody": {
+          "content": {
+            "application/json": {
+              "schema": {
+                "$ref": "#/components/schemas/SysRole"
+              }
+            }
+          },
+          "required": true
+        },
+        "responses": {
+          "200": {
+            "description": "OK",
+            "content": {
+              "*/*": {
+                "schema": {
+                  "$ref": "#/components/schemas/Result"
+                }
+              }
+            }
+          }
+        }
+      }
+    },
+    "/admin/system/sysRole/findByPage/{pageNum}/{pageSize}": {
+      "post": {
+        "tags": [
+          "sys-role-controller"
+        ],
+        "operationId": "findByPage",
+        "parameters": [
+          {
+            "name": "sysRoleDto",
+            "in": "query",
+            "required": true,
+            "schema": {
+              "$ref": "#/components/schemas/SysRoleDto"
+            }
+          },
+          {
+            "name": "pageNum",
+            "in": "path",
+            "required": true,
+            "schema": {
+              "type": "integer",
+              "format": "int32"
+            }
+          },
+          {
+            "name": "pageSize",
+            "in": "path",
+            "required": true,
+            "schema": {
+              "type": "integer",
+              "format": "int32"
+            }
+          }
+        ],
+        "responses": {
+          "200": {
+            "description": "OK",
+            "content": {
+              "*/*": {
+                "schema": {
+                  "$ref": "#/components/schemas/ResultPageInfoSysRole"
+                }
+              }
+            }
+          }
+        }
+      }
+    },
+    "/admin/system/sysMenu/save": {
+      "post": {
+        "tags": [
+          "sys-menu-controller"
+        ],
+        "operationId": "save",
+        "requestBody": {
+          "content": {
+            "application/json": {
+              "schema": {
+                "$ref": "#/components/schemas/SysMenu"
+              }
+            }
+          },
+          "required": true
+        },
+        "responses": {
+          "200": {
+            "description": "OK",
+            "content": {
+              "*/*": {
+                "schema": {
+                  "$ref": "#/components/schemas/Result"
+                }
+              }
+            }
+          }
+        }
+      }
+    },
+    "/admin/system/index/login": {
+      "post": {
+        "tags": [
+          "用户接口"
+        ],
+        "summary": "登录接口",
+        "operationId": "login",
+        "requestBody": {
+          "content": {
+            "application/json": {
+              "schema": {
+                "$ref": "#/components/schemas/LoginDto"
+              }
+            }
+          },
+          "required": true
+        },
+        "responses": {
+          "200": {
+            "description": "OK",
+            "content": {
+              "*/*": {
+                "schema": {
+                  "$ref": "#/components/schemas/ResultLoginVo"
+                }
+              }
+            }
+          }
+        }
+      }
+    },
+    "/admin/system/fileUpload": {
+      "post": {
+        "tags": [
+          "file-upload-controller"
+        ],
+        "operationId": "fileUploadService",
+        "requestBody": {
+          "content": {
+            "application/json": {
+              "schema": {
+                "required": [
+                  "file"
+                ],
+                "type": "object",
+                "properties": {
+                  "file": {
+                    "type": "string",
+                    "format": "binary"
+                  }
+                }
+              }
+            }
+          }
+        },
+        "responses": {
+          "200": {
+            "description": "OK",
+            "content": {
+              "*/*": {
+                "schema": {
+                  "$ref": "#/components/schemas/ResultString"
+                }
+              }
+            }
+          }
+        }
+      }
+    },
+    "/admin/product/productSpec/save": {
+      "post": {
+        "tags": [
+          "product-spec-controller"
+        ],
+        "operationId": "save_1",
+        "requestBody": {
+          "content": {
+            "application/json": {
+              "schema": {
+                "$ref": "#/components/schemas/ProductSpec"
+              }
+            }
+          },
+          "required": true
+        },
+        "responses": {
+          "200": {
+            "description": "OK",
+            "content": {
+              "*/*": {
+                "schema": {
+                  "$ref": "#/components/schemas/Result"
+                }
+              }
+            }
+          }
+        }
+      }
+    },
+    "/admin/product/product/save": {
+      "post": {
+        "tags": [
+          "product-controller"
+        ],
+        "operationId": "save_2",
+        "requestBody": {
+          "content": {
+            "application/json": {
+              "schema": {
+                "$ref": "#/components/schemas/Product"
+              }
+            }
+          },
+          "required": true
+        },
+        "responses": {
+          "200": {
+            "description": "OK",
+            "content": {
+              "*/*": {
+                "schema": {
+                  "$ref": "#/components/schemas/Result"
+                }
+              }
+            }
+          }
+        }
+      }
+    },
+    "/admin/product/categoryBrand/save": {
+      "post": {
+        "tags": [
+          "category-brand-controller"
+        ],
+        "operationId": "save_3",
+        "requestBody": {
+          "content": {
+            "application/json": {
+              "schema": {
+                "$ref": "#/components/schemas/CategoryBrand"
+              }
+            }
+          },
+          "required": true
+        },
+        "responses": {
+          "200": {
+            "description": "OK",
+            "content": {
+              "*/*": {
+                "schema": {
+                  "$ref": "#/components/schemas/Result"
+                }
+              }
+            }
+          }
+        }
+      }
+    },
+    "/admin/product/category/importData": {
+      "post": {
+        "tags": [
+          "category-controller"
+        ],
+        "operationId": "importData",
+        "parameters": [
+          {
+            "name": "file",
+            "in": "query",
+            "required": true,
+            "schema": {
+              "type": "string",
+              "format": "binary"
+            }
+          }
+        ],
+        "responses": {
+          "200": {
+            "description": "OK",
+            "content": {
+              "*/*": {
+                "schema": {
+                  "$ref": "#/components/schemas/Result"
+                }
+              }
+            }
+          }
+        }
+      }
+    },
+    "/admin/product/brand/save": {
+      "post": {
+        "tags": [
+          "brand-controller"
+        ],
+        "operationId": "save_4",
+        "requestBody": {
+          "content": {
+            "application/json": {
+              "schema": {
+                "$ref": "#/components/schemas/Brand"
+              }
+            }
+          },
+          "required": true
+        },
+        "responses": {
+          "200": {
+            "description": "OK",
+            "content": {
+              "*/*": {
+                "schema": {
+                  "$ref": "#/components/schemas/Result"
+                }
+              }
+            }
+          }
+        }
+      }
+    },
+    "/admin/system/sysUser/findByPage/{pageNum}/{pageSize}": {
+      "get": {
+        "tags": [
+          "sys-user-controller"
+        ],
+        "operationId": "findByPage_1",
+        "parameters": [
+          {
+            "name": "sysUserDto",
+            "in": "query",
+            "required": true,
+            "schema": {
+              "$ref": "#/components/schemas/SysUserDto"
+            }
+          },
+          {
+            "name": "pageNum",
+            "in": "path",
+            "required": true,
+            "schema": {
+              "type": "integer",
+              "format": "int32"
+            }
+          },
+          {
+            "name": "pageSize",
+            "in": "path",
+            "required": true,
+            "schema": {
+              "type": "integer",
+              "format": "int32"
+            }
+          }
+        ],
+        "responses": {
+          "200": {
+            "description": "OK",
+            "content": {
+              "*/*": {
+                "schema": {
+                  "$ref": "#/components/schemas/ResultPageInfoSysRole"
+                }
+              }
+            }
+          }
+        }
+      }
+    },
+    "/admin/system/sysRoleMenu/findSysRoleMenuByRoleId/{roleId}": {
+      "get": {
+        "tags": [
+          "sys-role-menu-controller"
+        ],
+        "operationId": "findSysRoleMenuByRoleId",
+        "parameters": [
+          {
+            "name": "roleId",
+            "in": "path",
+            "required": true,
+            "schema": {
+              "type": "integer",
+              "format": "int64"
+            }
+          }
+        ],
+        "responses": {
+          "200": {
+            "description": "OK",
+            "content": {
+              "*/*": {
+                "schema": {
+                  "$ref": "#/components/schemas/ResultMapStringObject"
+                }
+              }
+            }
+          }
+        }
+      }
+    },
+    "/admin/system/sysRole/findAllRoles": {
+      "get": {
+        "tags": [
+          "sys-role-controller"
+        ],
+        "operationId": "findAllRoles",
+        "responses": {
+          "200": {
+            "description": "OK",
+            "content": {
+              "*/*": {
+                "schema": {
+                  "$ref": "#/components/schemas/ResultMapStringObject"
+                }
+              }
+            }
+          }
+        }
+      }
+    },
+    "/admin/system/sysRole/findAllRoles/{userId}": {
+      "get": {
+        "tags": [
+          "sys-role-controller"
+        ],
+        "operationId": "findAllRoles_1",
+        "parameters": [
+          {
+            "name": "userId",
+            "in": "path",
+            "required": true,
+            "schema": {
+              "type": "integer",
+              "format": "int64"
+            }
+          }
+        ],
+        "responses": {
+          "200": {
+            "description": "OK",
+            "content": {
+              "*/*": {
+                "schema": {
+                  "$ref": "#/components/schemas/ResultMapStringObject"
+                }
+              }
+            }
+          }
+        }
+      }
+    },
+    "/admin/system/sysMenu/findNodes": {
+      "get": {
+        "tags": [
+          "sys-menu-controller"
+        ],
+        "operationId": "findNodes",
+        "responses": {
+          "200": {
+            "description": "OK",
+            "content": {
+              "*/*": {
+                "schema": {
+                  "$ref": "#/components/schemas/ResultListSysMenu"
+                }
+              }
+            }
+          }
+        }
+      }
+    },
+    "/admin/system/index/menus": {
+      "get": {
+        "tags": [
+          "用户接口"
+        ],
+        "operationId": "menus",
+        "responses": {
+          "200": {
+            "description": "OK",
+            "content": {
+              "*/*": {
+                "schema": {
+                  "$ref": "#/components/schemas/Result"
+                }
+              }
+            }
+          }
+        }
+      }
+    },
+    "/admin/system/index/logout": {
+      "get": {
+        "tags": [
+          "用户接口"
+        ],
+        "operationId": "logout",
+        "parameters": [
+          {
+            "name": "token",
+            "in": "header",
+            "required": true,
+            "schema": {
+              "type": "string"
+            }
+          }
+        ],
+        "responses": {
+          "200": {
+            "description": "OK",
+            "content": {
+              "*/*": {
+                "schema": {
+                  "$ref": "#/components/schemas/Result"
+                }
+              }
+            }
+          }
+        }
+      }
+    },
+    "/admin/system/index/getUserInfo": {
+      "get": {
+        "tags": [
+          "用户接口"
+        ],
+        "operationId": "getUserInfo",
+        "parameters": [
+          {
+            "name": "token",
+            "in": "header",
+            "required": true,
+            "schema": {
+              "type": "string"
+            }
+          }
+        ],
+        "responses": {
+          "200": {
+            "description": "OK",
+            "content": {
+              "*/*": {
+                "schema": {
+                  "$ref": "#/components/schemas/ResultSysUser"
+                }
+              }
+            }
+          }
+        }
+      }
+    },
+    "/admin/system/index/generateValidateCode": {
+      "get": {
+        "tags": [
+          "用户接口"
+        ],
+        "operationId": "generateValidateCode",
+        "responses": {
+          "200": {
+            "description": "OK",
+            "content": {
+              "*/*": {
+                "schema": {
+                  "$ref": "#/components/schemas/ResultValidateCodeVo"
+                }
+              }
+            }
+          }
+        }
+      }
+    },
+    "/admin/product/productUnit/findAll": {
+      "get": {
+        "tags": [
+          "product-unit-controller"
+        ],
+        "operationId": "findAll",
+        "responses": {
+          "200": {
+            "description": "OK",
+            "content": {
+              "*/*": {
+                "schema": {
+                  "$ref": "#/components/schemas/ResultListProductUnit"
+                }
+              }
+            }
+          }
+        }
+      }
+    },
+    "/admin/product/productSpec/{page}/{limit}": {
+      "get": {
+        "tags": [
+          "product-spec-controller"
+        ],
+        "operationId": "findByPage_2",
+        "parameters": [
+          {
+            "name": "page",
+            "in": "path",
+            "required": true,
+            "schema": {
+              "type": "integer",
+              "format": "int32"
+            }
+          },
+          {
+            "name": "limit",
+            "in": "path",
+            "required": true,
+            "schema": {
+              "type": "integer",
+              "format": "int32"
+            }
+          }
+        ],
+        "responses": {
+          "200": {
+            "description": "OK",
+            "content": {
+              "*/*": {
+                "schema": {
+                  "$ref": "#/components/schemas/ResultPageInfoProductSpec"
+                }
+              }
+            }
+          }
+        }
+      }
+    },
+    "/admin/product/productSpec/findAll": {
+      "get": {
+        "tags": [
+          "product-spec-controller"
+        ],
+        "operationId": "findAll_1",
+        "responses": {
+          "200": {
+            "description": "OK",
+            "content": {
+              "*/*": {
+                "schema": {
+                  "$ref": "#/components/schemas/Result"
+                }
+              }
+            }
+          }
+        }
+      }
+    },
+    "/admin/product/product/{page}/{limit}": {
+      "get": {
+        "tags": [
+          "product-controller"
+        ],
+        "operationId": "findByPage_3",
+        "parameters": [
+          {
+            "name": "page",
+            "in": "path",
+            "required": true,
+            "schema": {
+              "type": "integer",
+              "format": "int32"
+            }
+          },
+          {
+            "name": "limit",
+            "in": "path",
+            "required": true,
+            "schema": {
+              "type": "integer",
+              "format": "int32"
+            }
+          },
+          {
+            "name": "productDto",
+            "in": "query",
+            "required": true,
+            "schema": {
+              "$ref": "#/components/schemas/ProductDto"
+            }
+          }
+        ],
+        "responses": {
+          "200": {
+            "description": "OK",
+            "content": {
+              "*/*": {
+                "schema": {
+                  "$ref": "#/components/schemas/ResultPageInfoProduct"
+                }
+              }
+            }
+          }
+        }
+      }
+    },
+    "/admin/product/product/updateStatus/{id}/{status}": {
+      "get": {
+        "tags": [
+          "product-controller"
+        ],
+        "operationId": "updateStatus",
+        "parameters": [
+          {
+            "name": "id",
+            "in": "path",
+            "required": true,
+            "schema": {
+              "type": "integer",
+              "format": "int64"
+            }
+          },
+          {
+            "name": "status",
+            "in": "path",
+            "required": true,
+            "schema": {
+              "type": "integer",
+              "format": "int32"
+            }
+          }
+        ],
+        "responses": {
+          "200": {
+            "description": "OK",
+            "content": {
+              "*/*": {
+                "schema": {
+                  "$ref": "#/components/schemas/Result"
+                }
+              }
+            }
+          }
+        }
+      }
+    },
+    "/admin/product/product/updateAuditStatus/{id}/{auditStatus}": {
+      "get": {
+        "tags": [
+          "product-controller"
+        ],
+        "operationId": "updateAuditStatus",
+        "parameters": [
+          {
+            "name": "id",
+            "in": "path",
+            "required": true,
+            "schema": {
+              "type": "integer",
+              "format": "int64"
+            }
+          },
+          {
+            "name": "auditStatus",
+            "in": "path",
+            "required": true,
+            "schema": {
+              "type": "integer",
+              "format": "int32"
+            }
+          }
+        ],
+        "responses": {
+          "200": {
+            "description": "OK",
+            "content": {
+              "*/*": {
+                "schema": {
+                  "$ref": "#/components/schemas/Result"
+                }
+              }
+            }
+          }
+        }
+      }
+    },
+    "/admin/product/product/getById/{id}": {
+      "get": {
+        "tags": [
+          "product-controller"
+        ],
+        "operationId": "getById",
+        "parameters": [
+          {
+            "name": "id",
+            "in": "path",
+            "required": true,
+            "schema": {
+              "type": "integer",
+              "format": "int64"
+            }
+          }
+        ],
+        "responses": {
+          "200": {
+            "description": "OK",
+            "content": {
+              "*/*": {
+                "schema": {
+                  "$ref": "#/components/schemas/ResultProduct"
+                }
+              }
+            }
+          }
+        }
+      }
+    },
+    "/admin/product/categoryBrand/{page}/{limit}": {
+      "get": {
+        "tags": [
+          "category-brand-controller"
+        ],
+        "operationId": "findByPage_4",
+        "parameters": [
+          {
+            "name": "page",
+            "in": "path",
+            "required": true,
+            "schema": {
+              "type": "integer",
+              "format": "int32"
+            }
+          },
+          {
+            "name": "limit",
+            "in": "path",
+            "required": true,
+            "schema": {
+              "type": "integer",
+              "format": "int32"
+            }
+          },
+          {
+            "name": "CategoryBrandDto",
+            "in": "query",
+            "required": true,
+            "schema": {
+              "$ref": "#/components/schemas/CategoryBrandDto"
+            }
+          }
+        ],
+        "responses": {
+          "200": {
+            "description": "OK",
+            "content": {
+              "*/*": {
+                "schema": {
+                  "$ref": "#/components/schemas/ResultPageInfoCategoryBrand"
+                }
+              }
+            }
+          }
+        }
+      }
+    },
+    "/admin/product/categoryBrand/findBrandByCategoryId/{categoryId}": {
+      "get": {
+        "tags": [
+          "category-brand-controller"
+        ],
+        "operationId": "findBrandByCategoryId",
+        "parameters": [
+          {
+            "name": "categoryId",
+            "in": "path",
+            "required": true,
+            "schema": {
+              "type": "integer",
+              "format": "int64"
+            }
+          }
+        ],
+        "responses": {
+          "200": {
+            "description": "OK",
+            "content": {
+              "*/*": {
+                "schema": {
+                  "$ref": "#/components/schemas/Result"
+                }
+              }
+            }
+          }
+        }
+      }
+    },
+    "/admin/product/category/findByParentId/{parentId}": {
+      "get": {
+        "tags": [
+          "category-controller"
+        ],
+        "summary": "根据parentId获取下级节点",
+        "operationId": "findByParentId",
+        "parameters": [
+          {
+            "name": "parentId",
+            "in": "path",
+            "required": true,
+            "schema": {
+              "type": "integer",
+              "format": "int64"
+            }
+          }
+        ],
+        "responses": {
+          "200": {
+            "description": "OK",
+            "content": {
+              "*/*": {
+                "schema": {
+                  "$ref": "#/components/schemas/ResultListCategory"
+                }
+              }
+            }
+          }
+        }
+      }
+    },
+    "/admin/product/category/exportData": {
+      "get": {
+        "tags": [
+          "category-controller"
+        ],
+        "operationId": "exportData",
+        "responses": {
+          "200": {
+            "description": "OK"
+          }
+        }
+      }
+    },
+    "/admin/product/brand/{page}/{limit}": {
+      "get": {
+        "tags": [
+          "brand-controller"
+        ],
+        "operationId": "findByPage_5",
+        "parameters": [
+          {
+            "name": "page",
+            "in": "path",
+            "required": true,
+            "schema": {
+              "type": "integer",
+              "format": "int32"
+            }
+          },
+          {
+            "name": "limit",
+            "in": "path",
+            "required": true,
+            "schema": {
+              "type": "integer",
+              "format": "int32"
+            }
+          }
+        ],
+        "responses": {
+          "200": {
+            "description": "OK",
+            "content": {
+              "*/*": {
+                "schema": {
+                  "$ref": "#/components/schemas/ResultPageInfoBrand"
+                }
+              }
+            }
+          }
+        }
+      }
+    },
+    "/admin/product/brand/findAll": {
+      "get": {
+        "tags": [
+          "brand-controller"
+        ],
+        "operationId": "findAll_2",
+        "responses": {
+          "200": {
+            "description": "OK",
+            "content": {
+              "*/*": {
+                "schema": {
+                  "$ref": "#/components/schemas/Result"
+                }
+              }
+            }
+          }
+        }
+      }
+    },
+    "/admin/order/orderInfo/getOrderStatisticsData": {
+      "get": {
+        "tags": [
+          "order-info-controller"
+        ],
+        "operationId": "getOrderStatisticsData",
+        "parameters": [
+          {
+            "name": "orderStatisticsDto",
+            "in": "query",
+            "required": true,
+            "schema": {
+              "$ref": "#/components/schemas/OrderStatisticsDto"
+            }
+          }
+        ],
+        "responses": {
+          "200": {
+            "description": "OK",
+            "content": {
+              "*/*": {
+                "schema": {
+                  "$ref": "#/components/schemas/ResultOrderStatisticsVo"
+                }
+              }
+            }
+          }
+        }
+      }
+    },
+    "/admin/system/sysUser/deleteById/{userId}": {
+      "delete": {
+        "tags": [
+          "sys-user-controller"
+        ],
+        "operationId": "deleteById",
+        "parameters": [
+          {
+            "name": "userId",
+            "in": "path",
+            "required": true,
+            "schema": {
+              "type": "integer",
+              "format": "int64"
+            }
+          }
+        ],
+        "responses": {
+          "200": {
+            "description": "OK",
+            "content": {
+              "*/*": {
+                "schema": {
+                  "$ref": "#/components/schemas/Result"
+                }
+              }
+            }
+          }
+        }
+      }
+    },
+    "/admin/system/sysRole/deleteById/{roleId}": {
+      "delete": {
+        "tags": [
+          "sys-role-controller"
+        ],
+        "operationId": "deleteById_1",
+        "parameters": [
+          {
+            "name": "roleId",
+            "in": "path",
+            "required": true,
+            "schema": {
+              "type": "integer",
+              "format": "int64"
+            }
+          }
+        ],
+        "responses": {
+          "200": {
+            "description": "OK",
+            "content": {
+              "*/*": {
+                "schema": {
+                  "$ref": "#/components/schemas/Result"
+                }
+              }
+            }
+          }
+        }
+      }
+    },
+    "/admin/system/sysMenu/removeById/{id}": {
+      "delete": {
+        "tags": [
+          "sys-menu-controller"
+        ],
+        "operationId": "removeById",
+        "parameters": [
+          {
+            "name": "id",
+            "in": "path",
+            "required": true,
+            "schema": {
+              "type": "integer",
+              "format": "int64"
+            }
+          }
+        ],
+        "responses": {
+          "200": {
+            "description": "OK",
+            "content": {
+              "*/*": {
+                "schema": {
+                  "$ref": "#/components/schemas/Result"
+                }
+              }
+            }
+          }
+        }
+      }
+    },
+    "/admin/product/productSpec/deleteById/{id}": {
+      "delete": {
+        "tags": [
+          "product-spec-controller"
+        ],
+        "operationId": "removeById_1",
+        "parameters": [
+          {
+            "name": "id",
+            "in": "path",
+            "required": true,
+            "schema": {
+              "type": "integer",
+              "format": "int64"
+            }
+          }
+        ],
+        "responses": {
+          "200": {
+            "description": "OK",
+            "content": {
+              "*/*": {
+                "schema": {
+                  "$ref": "#/components/schemas/Result"
+                }
+              }
+            }
+          }
+        }
+      }
+    },
+    "/admin/product/product/deleteById/{id}": {
+      "delete": {
+        "tags": [
+          "product-controller"
+        ],
+        "operationId": "deleteById_2",
+        "parameters": [
+          {
+            "name": "id",
+            "in": "path",
+            "description": "商品id",
+            "required": true,
+            "schema": {
+              "type": "integer",
+              "format": "int64"
+            }
+          }
+        ],
+        "responses": {
+          "200": {
+            "description": "OK",
+            "content": {
+              "*/*": {
+                "schema": {
+                  "$ref": "#/components/schemas/Result"
+                }
+              }
+            }
+          }
+        }
+      }
+    },
+    "/admin/product/categoryBrand/deleteById/{id}": {
+      "delete": {
+        "tags": [
+          "category-brand-controller"
+        ],
+        "operationId": "deleteById_3",
+        "parameters": [
+          {
+            "name": "id",
+            "in": "path",
+            "required": true,
+            "schema": {
+              "type": "integer",
+              "format": "int64"
+            }
+          }
+        ],
+        "responses": {
+          "200": {
+            "description": "OK",
+            "content": {
+              "*/*": {
+                "schema": {
+                  "$ref": "#/components/schemas/Result"
+                }
+              }
+            }
+          }
+        }
+      }
+    },
+    "/admin/product/brand/deleteById/{id}": {
+      "delete": {
+        "tags": [
+          "brand-controller"
+        ],
+        "operationId": "deleteById_4",
+        "parameters": [
+          {
+            "name": "id",
+            "in": "path",
+            "required": true,
+            "schema": {
+              "type": "integer",
+              "format": "int64"
+            }
+          }
+        ],
+        "responses": {
+          "200": {
+            "description": "OK",
+            "content": {
+              "*/*": {
+                "schema": {
+                  "$ref": "#/components/schemas/Result"
+                }
+              }
+            }
+          }
+        }
+      }
+    }
+  },
+  "components": {
+    "schemas": {
+      "SysUser": {
+        "type": "object",
+        "properties": {
+          "id": {
+            "type": "integer",
+            "format": "int64"
+          },
+          "createTime": {
+            "type": "string",
+            "format": "date-time"
+          },
+          "updateTime": {
+            "type": "string",
+            "format": "date-time"
+          },
+          "isDeleted": {
+            "type": "integer",
+            "format": "int32"
+          },
+          "userName": {
+            "type": "string"
+          },
+          "password": {
+            "type": "string"
+          },
+          "name": {
+            "type": "string"
+          },
+          "phone": {
+            "type": "string"
+          },
+          "avatar": {
+            "type": "string"
+          },
+          "description": {
+            "type": "string"
+          },
+          "status": {
+            "type": "integer",
+            "format": "int32"
+          }
+        }
+      },
+      "Result": {
+        "type": "object",
+        "properties": {
+          "code": {
+            "type": "integer",
+            "format": "int32"
+          },
+          "message": {
+            "type": "string"
+          },
+          "data": {
+            "type": "object"
+          }
+        }
+      },
+      "SysRole": {
+        "type": "object",
+        "properties": {
+          "id": {
+            "type": "integer",
+            "format": "int64"
+          },
+          "createTime": {
+            "type": "string",
+            "format": "date-time"
+          },
+          "updateTime": {
+            "type": "string",
+            "format": "date-time"
+          },
+          "isDeleted": {
+            "type": "integer",
+            "format": "int32"
+          },
+          "roleName": {
+            "type": "string",
+            "description": "角色名称"
+          },
+          "roleCode": {
+            "type": "string",
+            "description": "角色编码"
+          },
+          "description": {
+            "type": "string",
+            "description": "描述"
+          }
+        },
+        "description": "SysRole"
+      },
+      "SysMenu": {
+        "type": "object",
+        "properties": {
+          "id": {
+            "type": "integer",
+            "format": "int64"
+          },
+          "createTime": {
+            "type": "string",
+            "format": "date-time"
+          },
+          "updateTime": {
+            "type": "string",
+            "format": "date-time"
+          },
+          "isDeleted": {
+            "type": "integer",
+            "format": "int32"
+          },
+          "parentId": {
+            "type": "integer",
+            "format": "int64"
+          },
+          "title": {
+            "type": "string"
+          },
+          "component": {
+            "type": "string"
+          },
+          "sortValue": {
+            "type": "integer",
+            "format": "int32"
+          },
+          "status": {
+            "type": "integer",
+            "format": "int32"
+          },
+          "children": {
+            "type": "array",
+            "items": {
+              "$ref": "#/components/schemas/SysMenu"
+            }
+          }
+        }
+      },
+      "ProductSpec": {
+        "type": "object",
+        "properties": {
+          "id": {
+            "type": "integer",
+            "format": "int64"
+          },
+          "createTime": {
+            "type": "string",
+            "format": "date-time"
+          },
+          "updateTime": {
+            "type": "string",
+            "format": "date-time"
+          },
+          "isDeleted": {
+            "type": "integer",
+            "format": "int32"
+          },
+          "specName": {
+            "type": "string"
+          },
+          "specValue": {
+            "type": "string"
+          }
+        }
+      },
+      "Product": {
+        "type": "object",
+        "properties": {
+          "id": {
+            "type": "integer",
+            "format": "int64"
+          },
+          "createTime": {
+            "type": "string",
+            "format": "date-time"
+          },
+          "updateTime": {
+            "type": "string",
+            "format": "date-time"
+          },
+          "isDeleted": {
+            "type": "integer",
+            "format": "int32"
+          },
+          "name": {
+            "type": "string"
+          },
+          "brandId": {
+            "type": "integer",
+            "format": "int64"
+          },
+          "category1Id": {
+            "type": "integer",
+            "format": "int64"
+          },
+          "category2Id": {
+            "type": "integer",
+            "format": "int64"
+          },
+          "category3Id": {
+            "type": "integer",
+            "format": "int64"
+          },
+          "unitName": {
+            "type": "string"
+          },
+          "sliderUrls": {
+            "type": "string"
+          },
+          "specValue": {
+            "type": "string"
+          },
+          "status": {
+            "type": "integer",
+            "format": "int32"
+          },
+          "auditStatus": {
+            "type": "integer",
+            "format": "int32"
+          },
+          "auditMessage": {
+            "type": "string"
+          },
+          "brandName": {
+            "type": "string"
+          },
+          "category1Name": {
+            "type": "string"
+          },
+          "category2Name": {
+            "type": "string"
+          },
+          "category3Name": {
+            "type": "string"
+          },
+          "productSkuList": {
+            "type": "array",
+            "items": {
+              "$ref": "#/components/schemas/ProductSku"
+            }
+          },
+          "detailsImageUrls": {
+            "type": "string"
+          }
+        },
+        "description": "请求参数实体类"
+      },
+      "ProductSku": {
+        "type": "object",
+        "properties": {
+          "id": {
+            "type": "integer",
+            "format": "int64"
+          },
+          "createTime": {
+            "type": "string",
+            "format": "date-time"
+          },
+          "updateTime": {
+            "type": "string",
+            "format": "date-time"
+          },
+          "isDeleted": {
+            "type": "integer",
+            "format": "int32"
+          },
+          "skuCode": {
+            "type": "string"
+          },
+          "skuName": {
+            "type": "string"
+          },
+          "productId": {
+            "type": "integer",
+            "format": "int64"
+          },
+          "thumbImg": {
+            "type": "string"
+          },
+          "salePrice": {
+            "type": "number"
+          },
+          "marketPrice": {
+            "type": "number"
+          },
+          "costPrice": {
+            "type": "number"
+          },
+          "stockNum": {
+            "type": "integer",
+            "format": "int32"
+          },
+          "saleNum": {
+            "type": "integer",
+            "format": "int32"
+          },
+          "skuSpec": {
+            "type": "string"
+          },
+          "weight": {
+            "type": "string"
+          },
+          "volume": {
+            "type": "string"
+          },
+          "status": {
+            "type": "integer",
+            "format": "int32"
+          }
+        }
+      },
+      "CategoryBrand": {
+        "type": "object",
+        "properties": {
+          "id": {
+            "type": "integer",
+            "format": "int64"
+          },
+          "createTime": {
+            "type": "string",
+            "format": "date-time"
+          },
+          "updateTime": {
+            "type": "string",
+            "format": "date-time"
+          },
+          "isDeleted": {
+            "type": "integer",
+            "format": "int32"
+          },
+          "brandId": {
+            "type": "integer",
+            "format": "int64"
+          },
+          "categoryId": {
+            "type": "integer",
+            "format": "int64"
+          },
+          "categoryName": {
+            "type": "string"
+          },
+          "brandName": {
+            "type": "string"
+          },
+          "logo": {
+            "type": "string"
+          }
+        }
+      },
+      "Brand": {
+        "type": "object",
+        "properties": {
+          "id": {
+            "type": "integer",
+            "format": "int64"
+          },
+          "createTime": {
+            "type": "string",
+            "format": "date-time"
+          },
+          "updateTime": {
+            "type": "string",
+            "format": "date-time"
+          },
+          "isDeleted": {
+            "type": "integer",
+            "format": "int32"
+          },
+          "name": {
+            "type": "string"
+          },
+          "logo": {
+            "type": "string"
+          }
+        }
+      },
+      "AssginRoleDto": {
+        "type": "object",
+        "properties": {
+          "userId": {
+            "type": "integer",
+            "format": "int64"
+          },
+          "roleIdList": {
+            "type": "array",
+            "items": {
+              "type": "integer",
+              "format": "int64"
+            }
+          }
+        }
+      },
+      "AssginMenuDto": {
+        "type": "object",
+        "properties": {
+          "roleId": {
+            "type": "integer",
+            "format": "int64"
+          },
+          "menuIdList": {
+            "type": "array",
+            "items": {
+              "type": "object",
+              "additionalProperties": {
+                "type": "number"
+              }
+            }
+          }
+        }
+      },
+      "SysRoleDto": {
+        "type": "object",
+        "properties": {
+          "roleName": {
+            "type": "string"
+          }
+        }
+      },
+      "PageInfoSysRole": {
+        "type": "object",
+        "properties": {
+          "total": {
+            "type": "integer",
+            "format": "int64"
+          },
+          "list": {
+            "type": "array",
+            "items": {
+              "$ref": "#/components/schemas/SysRole"
+            }
+          },
+          "pageNum": {
+            "type": "integer",
+            "format": "int32"
+          },
+          "pageSize": {
+            "type": "integer",
+            "format": "int32"
+          },
+          "size": {
+            "type": "integer",
+            "format": "int32"
+          },
+          "startRow": {
+            "type": "integer",
+            "format": "int64"
+          },
+          "endRow": {
+            "type": "integer",
+            "format": "int64"
+          },
+          "pages": {
+            "type": "integer",
+            "format": "int32"
+          },
+          "prePage": {
+            "type": "integer",
+            "format": "int32"
+          },
+          "nextPage": {
+            "type": "integer",
+            "format": "int32"
+          },
+          "isFirstPage": {
+            "type": "boolean"
+          },
+          "isLastPage": {
+            "type": "boolean"
+          },
+          "hasPreviousPage": {
+            "type": "boolean"
+          },
+          "hasNextPage": {
+            "type": "boolean"
+          },
+          "navigatePages": {
+            "type": "integer",
+            "format": "int32"
+          },
+          "navigatepageNums": {
+            "type": "array",
+            "items": {
+              "type": "integer",
+              "format": "int32"
+            }
+          },
+          "navigateFirstPage": {
+            "type": "integer",
+            "format": "int32"
+          },
+          "navigateLastPage": {
+            "type": "integer",
+            "format": "int32"
+          }
+        }
+      },
+      "ResultPageInfoSysRole": {
+        "type": "object",
+        "properties": {
+          "code": {
+            "type": "integer",
+            "format": "int32"
+          },
+          "message": {
+            "type": "string"
+          },
+          "data": {
+            "$ref": "#/components/schemas/PageInfoSysRole"
+          }
+        }
+      },
+      "LoginDto": {
+        "type": "object",
+        "properties": {
+          "userName": {
+            "type": "string"
+          },
+          "password": {
+            "type": "string"
+          },
+          "captcha": {
+            "type": "string"
+          },
+          "codeKey": {
+            "type": "string"
+          }
+        }
+      },
+      "LoginVo": {
+        "type": "object",
+        "properties": {
+          "token": {
+            "type": "string"
+          },
+          "refresh_token": {
+            "type": "string"
+          }
+        }
+      },
+      "ResultLoginVo": {
+        "type": "object",
+        "properties": {
+          "code": {
+            "type": "integer",
+            "format": "int32"
+          },
+          "message": {
+            "type": "string"
+          },
+          "data": {
+            "$ref": "#/components/schemas/LoginVo"
+          }
+        }
+      },
+      "ResultString": {
+        "type": "object",
+        "properties": {
+          "code": {
+            "type": "integer",
+            "format": "int32"
+          },
+          "message": {
+            "type": "string"
+          },
+          "data": {
+            "type": "string"
+          }
+        }
+      },
+      "SysUserDto": {
+        "type": "object",
+        "properties": {
+          "keyword": {
+            "type": "string"
+          },
+          "createTimeBegin": {
+            "type": "string"
+          },
+          "createTimeEnd": {
+            "type": "string"
+          }
+        }
+      },
+      "ResultMapStringObject": {
+        "type": "object",
+        "properties": {
+          "code": {
+            "type": "integer",
+            "format": "int32"
+          },
+          "message": {
+            "type": "string"
+          },
+          "data": {
+            "type": "object",
+            "additionalProperties": {
+              "type": "object"
+            }
+          }
+        }
+      },
+      "ResultListSysMenu": {
+        "type": "object",
+        "properties": {
+          "code": {
+            "type": "integer",
+            "format": "int32"
+          },
+          "message": {
+            "type": "string"
+          },
+          "data": {
+            "type": "array",
+            "items": {
+              "$ref": "#/components/schemas/SysMenu"
+            }
+          }
+        }
+      },
+      "ResultSysUser": {
+        "type": "object",
+        "properties": {
+          "code": {
+            "type": "integer",
+            "format": "int32"
+          },
+          "message": {
+            "type": "string"
+          },
+          "data": {
+            "$ref": "#/components/schemas/SysUser"
+          }
+        }
+      },
+      "ResultValidateCodeVo": {
+        "type": "object",
+        "properties": {
+          "code": {
+            "type": "integer",
+            "format": "int32"
+          },
+          "message": {
+            "type": "string"
+          },
+          "data": {
+            "$ref": "#/components/schemas/ValidateCodeVo"
+          }
+        }
+      },
+      "ValidateCodeVo": {
+        "type": "object",
+        "properties": {
+          "codeKey": {
+            "type": "string"
+          },
+          "codeValue": {
+            "type": "string"
+          }
+        }
+      },
+      "ProductUnit": {
+        "type": "object",
+        "properties": {
+          "id": {
+            "type": "integer",
+            "format": "int64"
+          },
+          "createTime": {
+            "type": "string",
+            "format": "date-time"
+          },
+          "updateTime": {
+            "type": "string",
+            "format": "date-time"
+          },
+          "isDeleted": {
+            "type": "integer",
+            "format": "int32"
+          },
+          "name": {
+            "type": "string"
+          }
+        }
+      },
+      "ResultListProductUnit": {
+        "type": "object",
+        "properties": {
+          "code": {
+            "type": "integer",
+            "format": "int32"
+          },
+          "message": {
+            "type": "string"
+          },
+          "data": {
+            "type": "array",
+            "items": {
+              "$ref": "#/components/schemas/ProductUnit"
+            }
+          }
+        }
+      },
+      "PageInfoProductSpec": {
+        "type": "object",
+        "properties": {
+          "total": {
+            "type": "integer",
+            "format": "int64"
+          },
+          "list": {
+            "type": "array",
+            "items": {
+              "$ref": "#/components/schemas/ProductSpec"
+            }
+          },
+          "pageNum": {
+            "type": "integer",
+            "format": "int32"
+          },
+          "pageSize": {
+            "type": "integer",
+            "format": "int32"
+          },
+          "size": {
+            "type": "integer",
+            "format": "int32"
+          },
+          "startRow": {
+            "type": "integer",
+            "format": "int64"
+          },
+          "endRow": {
+            "type": "integer",
+            "format": "int64"
+          },
+          "pages": {
+            "type": "integer",
+            "format": "int32"
+          },
+          "prePage": {
+            "type": "integer",
+            "format": "int32"
+          },
+          "nextPage": {
+            "type": "integer",
+            "format": "int32"
+          },
+          "isFirstPage": {
+            "type": "boolean"
+          },
+          "isLastPage": {
+            "type": "boolean"
+          },
+          "hasPreviousPage": {
+            "type": "boolean"
+          },
+          "hasNextPage": {
+            "type": "boolean"
+          },
+          "navigatePages": {
+            "type": "integer",
+            "format": "int32"
+          },
+          "navigatepageNums": {
+            "type": "array",
+            "items": {
+              "type": "integer",
+              "format": "int32"
+            }
+          },
+          "navigateFirstPage": {
+            "type": "integer",
+            "format": "int32"
+          },
+          "navigateLastPage": {
+            "type": "integer",
+            "format": "int32"
+          }
+        }
+      },
+      "ResultPageInfoProductSpec": {
+        "type": "object",
+        "properties": {
+          "code": {
+            "type": "integer",
+            "format": "int32"
+          },
+          "message": {
+            "type": "string"
+          },
+          "data": {
+            "$ref": "#/components/schemas/PageInfoProductSpec"
+          }
+        }
+      },
+      "ProductDto": {
+        "type": "object",
+        "properties": {
+          "id": {
+            "type": "integer",
+            "format": "int64"
+          },
+          "createTime": {
+            "type": "string",
+            "format": "date-time"
+          },
+          "updateTime": {
+            "type": "string",
+            "format": "date-time"
+          },
+          "isDeleted": {
+            "type": "integer",
+            "format": "int32"
+          },
+          "brandId": {
+            "type": "integer",
+            "format": "int64"
+          },
+          "category1Id": {
+            "type": "integer",
+            "format": "int64"
+          },
+          "category2Id": {
+            "type": "integer",
+            "format": "int64"
+          },
+          "category3Id": {
+            "type": "integer",
+            "format": "int64"
+          }
+        }
+      },
+      "PageInfoProduct": {
+        "type": "object",
+        "properties": {
+          "total": {
+            "type": "integer",
+            "format": "int64"
+          },
+          "list": {
+            "type": "array",
+            "items": {
+              "$ref": "#/components/schemas/Product"
+            }
+          },
+          "pageNum": {
+            "type": "integer",
+            "format": "int32"
+          },
+          "pageSize": {
+            "type": "integer",
+            "format": "int32"
+          },
+          "size": {
+            "type": "integer",
+            "format": "int32"
+          },
+          "startRow": {
+            "type": "integer",
+            "format": "int64"
+          },
+          "endRow": {
+            "type": "integer",
+            "format": "int64"
+          },
+          "pages": {
+            "type": "integer",
+            "format": "int32"
+          },
+          "prePage": {
+            "type": "integer",
+            "format": "int32"
+          },
+          "nextPage": {
+            "type": "integer",
+            "format": "int32"
+          },
+          "isFirstPage": {
+            "type": "boolean"
+          },
+          "isLastPage": {
+            "type": "boolean"
+          },
+          "hasPreviousPage": {
+            "type": "boolean"
+          },
+          "hasNextPage": {
+            "type": "boolean"
+          },
+          "navigatePages": {
+            "type": "integer",
+            "format": "int32"
+          },
+          "navigatepageNums": {
+            "type": "array",
+            "items": {
+              "type": "integer",
+              "format": "int32"
+            }
+          },
+          "navigateFirstPage": {
+            "type": "integer",
+            "format": "int32"
+          },
+          "navigateLastPage": {
+            "type": "integer",
+            "format": "int32"
+          }
+        }
+      },
+      "ResultPageInfoProduct": {
+        "type": "object",
+        "properties": {
+          "code": {
+            "type": "integer",
+            "format": "int32"
+          },
+          "message": {
+            "type": "string"
+          },
+          "data": {
+            "$ref": "#/components/schemas/PageInfoProduct"
+          }
+        }
+      },
+      "ResultProduct": {
+        "type": "object",
+        "properties": {
+          "code": {
+            "type": "integer",
+            "format": "int32"
+          },
+          "message": {
+            "type": "string"
+          },
+          "data": {
+            "$ref": "#/components/schemas/Product"
+          }
+        }
+      },
+      "CategoryBrandDto": {
+        "type": "object",
+        "properties": {
+          "brandId": {
+            "type": "integer",
+            "format": "int64"
+          },
+          "categoryId": {
+            "type": "integer",
+            "format": "int64"
+          }
+        }
+      },
+      "PageInfoCategoryBrand": {
+        "type": "object",
+        "properties": {
+          "total": {
+            "type": "integer",
+            "format": "int64"
+          },
+          "list": {
+            "type": "array",
+            "items": {
+              "$ref": "#/components/schemas/CategoryBrand"
+            }
+          },
+          "pageNum": {
+            "type": "integer",
+            "format": "int32"
+          },
+          "pageSize": {
+            "type": "integer",
+            "format": "int32"
+          },
+          "size": {
+            "type": "integer",
+            "format": "int32"
+          },
+          "startRow": {
+            "type": "integer",
+            "format": "int64"
+          },
+          "endRow": {
+            "type": "integer",
+            "format": "int64"
+          },
+          "pages": {
+            "type": "integer",
+            "format": "int32"
+          },
+          "prePage": {
+            "type": "integer",
+            "format": "int32"
+          },
+          "nextPage": {
+            "type": "integer",
+            "format": "int32"
+          },
+          "isFirstPage": {
+            "type": "boolean"
+          },
+          "isLastPage": {
+            "type": "boolean"
+          },
+          "hasPreviousPage": {
+            "type": "boolean"
+          },
+          "hasNextPage": {
+            "type": "boolean"
+          },
+          "navigatePages": {
+            "type": "integer",
+            "format": "int32"
+          },
+          "navigatepageNums": {
+            "type": "array",
+            "items": {
+              "type": "integer",
+              "format": "int32"
+            }
+          },
+          "navigateFirstPage": {
+            "type": "integer",
+            "format": "int32"
+          },
+          "navigateLastPage": {
+            "type": "integer",
+            "format": "int32"
+          }
+        }
+      },
+      "ResultPageInfoCategoryBrand": {
+        "type": "object",
+        "properties": {
+          "code": {
+            "type": "integer",
+            "format": "int32"
+          },
+          "message": {
+            "type": "string"
+          },
+          "data": {
+            "$ref": "#/components/schemas/PageInfoCategoryBrand"
+          }
+        }
+      },
+      "Category": {
+        "type": "object",
+        "properties": {
+          "id": {
+            "type": "integer",
+            "format": "int64"
+          },
+          "createTime": {
+            "type": "string",
+            "format": "date-time"
+          },
+          "updateTime": {
+            "type": "string",
+            "format": "date-time"
+          },
+          "isDeleted": {
+            "type": "integer",
+            "format": "int32"
+          },
+          "name": {
+            "type": "string"
+          },
+          "imageUrl": {
+            "type": "string"
+          },
+          "parentId": {
+            "type": "integer",
+            "format": "int64"
+          },
+          "status": {
+            "type": "integer",
+            "format": "int32"
+          },
+          "orderNum": {
+            "type": "integer",
+            "format": "int32"
+          },
+          "hasChildren": {
+            "type": "boolean"
+          }
+        }
+      },
+      "ResultListCategory": {
+        "type": "object",
+        "properties": {
+          "code": {
+            "type": "integer",
+            "format": "int32"
+          },
+          "message": {
+            "type": "string"
+          },
+          "data": {
+            "type": "array",
+            "items": {
+              "$ref": "#/components/schemas/Category"
+            }
+          }
+        }
+      },
+      "PageInfoBrand": {
+        "type": "object",
+        "properties": {
+          "total": {
+            "type": "integer",
+            "format": "int64"
+          },
+          "list": {
+            "type": "array",
+            "items": {
+              "$ref": "#/components/schemas/Brand"
+            }
+          },
+          "pageNum": {
+            "type": "integer",
+            "format": "int32"
+          },
+          "pageSize": {
+            "type": "integer",
+            "format": "int32"
+          },
+          "size": {
+            "type": "integer",
+            "format": "int32"
+          },
+          "startRow": {
+            "type": "integer",
+            "format": "int64"
+          },
+          "endRow": {
+            "type": "integer",
+            "format": "int64"
+          },
+          "pages": {
+            "type": "integer",
+            "format": "int32"
+          },
+          "prePage": {
+            "type": "integer",
+            "format": "int32"
+          },
+          "nextPage": {
+            "type": "integer",
+            "format": "int32"
+          },
+          "isFirstPage": {
+            "type": "boolean"
+          },
+          "isLastPage": {
+            "type": "boolean"
+          },
+          "hasPreviousPage": {
+            "type": "boolean"
+          },
+          "hasNextPage": {
+            "type": "boolean"
+          },
+          "navigatePages": {
+            "type": "integer",
+            "format": "int32"
+          },
+          "navigatepageNums": {
+            "type": "array",
+            "items": {
+              "type": "integer",
+              "format": "int32"
+            }
+          },
+          "navigateFirstPage": {
+            "type": "integer",
+            "format": "int32"
+          },
+          "navigateLastPage": {
+            "type": "integer",
+            "format": "int32"
+          }
+        }
+      },
+      "ResultPageInfoBrand": {
+        "type": "object",
+        "properties": {
+          "code": {
+            "type": "integer",
+            "format": "int32"
+          },
+          "message": {
+            "type": "string"
+          },
+          "data": {
+            "$ref": "#/components/schemas/PageInfoBrand"
+          }
+        }
+      },
+      "OrderStatisticsDto": {
+        "type": "object",
+        "properties": {
+          "createTimeBegin": {
+            "type": "string"
+          },
+          "createTimeEnd": {
+            "type": "string"
+          }
+        }
+      },
+      "OrderStatisticsVo": {
+        "type": "object",
+        "properties": {
+          "dateList": {
+            "type": "array",
+            "items": {
+              "type": "string"
+            }
+          },
+          "amountList": {
+            "type": "array",
+            "items": {
+              "type": "number"
+            }
+          }
+        }
+      },
+      "ResultOrderStatisticsVo": {
+        "type": "object",
+        "properties": {
+          "code": {
+            "type": "integer",
+            "format": "int32"
+          },
+          "message": {
+            "type": "string"
+          },
+          "data": {
+            "$ref": "#/components/schemas/OrderStatisticsVo"
+          }
+        }
+      }
+    }
+  }
+}

+ 13 - 0
vue3/项目/vue3-project/index.html

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

+ 10 - 0
vue3/项目/vue3-project/jsconfig.json

@@ -0,0 +1,10 @@
+{
+  "compilerOptions": {
+    "baseUrl": "./",
+    "paths": {
+      "@/*": ["src/*"],
+    }
+  },
+  "exclude": ["node_modules", "dist"],
+  "include": ["src/**/*"]
+}

+ 15 - 0
vue3/项目/vue3-project/mock/_createProductionServer.js

@@ -0,0 +1,15 @@
+import { createProdMockServer } from 'vite-plugin-mock/es/createProdMockServer'
+
+const modules = import.meta.globEager('./**/*.js')
+
+const mockModules = []
+Object.keys(modules).forEach(key => {
+  mockModules.push(...modules[key].default)
+})
+
+/**
+ * Used in a production environment. Need to manually import all modules
+ */
+export function setupProdMockServer() {
+  createProdMockServer(mockModules)
+}

+ 39 - 0
vue3/项目/vue3-project/mock/login.js

@@ -0,0 +1,39 @@
+export default [
+  {
+    url: '/api/login',
+    method: 'post',
+    timeout: 1000,
+    statusCode: 200,
+    response: ({ body }) => {
+      // 响应内容
+      return +body.password === 123456
+        ? {
+            code: 200,
+            message: '登录成功',
+            data: {
+              token: '@word(50, 100)', // @word()是mockjs的语法
+              refresh_token: '@word(50, 100)', // refresh_token是用来重新生成token的
+            },
+          }
+        : {
+            code: 400,
+            message: '密码错误,请输入123456',
+          }
+    },
+  },
+  {
+    url: '/api/userinfo',
+    method: 'get',
+    timeout: 100,
+    response: {
+      code: 200,
+      message: '获取用户信息成功',
+      data: {
+        id: 1,
+        name: 'zhangsan',
+        'role|1': ['admin', 'visitor'], // 随机返回一个角色admin或visitor
+        avatar: "@image('48x48', '#fb0a2a')",
+      },
+    },
+  },
+]

+ 66 - 0
vue3/项目/vue3-project/mock/menu.js

@@ -0,0 +1,66 @@
+export default [
+  {
+    url: '/api/menus',
+    method: 'get',
+    timeout: 100,
+    response: ({ query }) => {
+      // 响应内容
+      const childs = [
+        {
+          name: 'testList',
+          title: '列表',
+        },
+        {
+          name: 'testAdd',
+          title: '添加',
+        },
+        {
+          name: 'testEdit',
+          title: '编辑',
+        },
+        // {
+        //   name: 'testAuth',
+        //   title: '权限测试',
+        // },
+        {
+          name: 'test-cache',
+          title: '该页面可缓存',
+        },
+        {
+          name: 'test-no-cache',
+          title: '该页面不可缓存',
+        },
+        {
+          name: 'nest',
+          title: '二级菜单',
+          children: [
+            {
+              name: 'nestPage1',
+              title: 'page1',
+            },
+            {
+              name: 'nestPage2',
+              title: 'page2',
+            },
+          ],
+        },
+        {
+          name: 'test-error-log',
+          title: '测试错误日志',
+        },
+      ]
+
+      return {
+        code: 200,
+        message: '获取菜单成功',
+        data: [
+          {
+            name: 'test',
+            title: '测试页面',
+            children: childs,
+          },
+        ],
+      }
+    },
+  },
+]

+ 60 - 0
vue3/项目/vue3-project/mock/test.js

@@ -0,0 +1,60 @@
+export default [
+  {
+    url: '/api/get', // 请求地址
+    method: 'get', // 请求方法
+    response: ({ query }) => {
+      // 响应内容
+      return {
+        code: 0,
+        data: {
+          name: 'hello world',
+        },
+      }
+    },
+  },
+  {
+    url: '/api/post',
+    method: 'post',
+    timeout: 2000,
+    response: {
+      code: 0,
+      data: {
+        name: 'hello world',
+      },
+    },
+  },
+  {
+    url: '/api/500',
+    method: 'get',
+    statusCode: 500,
+    response: {
+      code: 500,
+      message: '内部错误',
+      data: null,
+    },
+  },
+  // 请求用户列表
+  {
+    url: '/api/test/users',
+    method: 'post',
+    timeout: 1000,
+    response: () => {
+      // 响应内容
+      return {
+        code: 200,
+        message: '获取成功',
+        data: {
+          'list|10': [
+            {
+              'id|+1': 1,
+              nickName: '@cname()',
+              userEmail: '@email()',
+              'status|1': [0, 1],
+            },
+          ],
+          'total|50-1000': 1,
+        },
+      }
+    },
+  },
+]

+ 76 - 0
vue3/项目/vue3-project/package.json

@@ -0,0 +1,76 @@
+{
+  "name": "sifu",
+  "version": "3.0.0",
+  "author": {
+    "name": "huzhushan",
+    "email": "huzhushan@126.com",
+    "url": "https://github.com/huzhushan"
+  },
+  "scripts": {
+    "start": "npm run dev:mock",
+    "dev": "vite",
+    "dev:mock": "vite --mode mock",
+    "build": "vite build",
+    "build:mock": "vite build --mode mock",
+    "serve": "vite preview",
+    "lint": "eslint --fix --ext .js,.vue src"
+  },
+  "browserslist": [
+    "> 1%",
+    "last 2 versions"
+  ],
+  "dependencies": {
+    "axios": "^0.21.1",
+    "echarts": "^5.5.0",
+    "pinia": "^2.0.14",
+    "typescript": "^5.4.5",
+    "vue": "3.2.33",
+    "vue-router": "^4.0.5",
+    "vuex": "^4.0.0"
+  },
+  "devDependencies": {
+    "@ehutch79/vite-eslint": "0.0.1",
+    "@vitejs/plugin-vue": "^1.2.3",
+    "@vue/compiler-sfc": "^3.1.2",
+    "@vue/eslint-config-prettier": "^6.0.0",
+    "autoprefixer": "^10.2.5",
+    "babel-eslint": "^10.1.0",
+    "crypto-js": "^4.0.0",
+    "element-plus": "^2.2.13",
+    "eslint": "^6.7.2",
+    "eslint-plugin-prettier": "^3.1.3",
+    "eslint-plugin-vue": "^7.0.0-0",
+    "husky": "^1.3.1",
+    "lint-staged": "^9.5.0",
+    "mockjs": "^1.1.0",
+    "prettier": "^1.19.1",
+    "sass": "^1.41.1",
+    "vite": "^2.3.7",
+    "vite-plugin-mock": "^2.3.0",
+    "vite-plugin-svg-icons": "^0.4.0",
+    "vue-i18n": "^9.0.0"
+  },
+  "lint-staged": {
+    "src/**/*.{js,vue}": [
+      "npm run lint",
+      "git add"
+    ]
+  },
+  "husky": {
+    "hooks": {
+      "pre-commit": "lint-staged"
+    }
+  },
+  "repository": {
+    "type": "git",
+    "url": "git+https://github.com/huzhushan/vue3-element-admin.git"
+  },
+  "license": "MIT",
+  "bugs": {
+    "url": "https://github.com/huzhushan/vue3-element-admin/issues"
+  },
+  "rules":{
+    "no-unused-vars": "off"
+  },
+  "homepage": "https://github.com/huzhushan/vue3-element-admin"
+}

+ 5 - 0
vue3/项目/vue3-project/postcss.config.js

@@ -0,0 +1,5 @@
+module.exports = {
+  plugins: {
+    autoprefixer: {},
+  },
+};

二進制
vue3/项目/vue3-project/public/favicon.ico


+ 43 - 0
vue3/项目/vue3-project/src/App.vue

@@ -0,0 +1,43 @@
+<template>
+  <el-config-provider :locale="locales[lang]">
+    <router-view />
+  </el-config-provider>
+</template>
+
+<script>
+import { defineComponent } from 'vue'
+import { ElConfigProvider } from 'element-plus'
+import localeZH from 'element-plus/lib/locale/lang/zh-cn'
+import localeEN from 'element-plus/lib/locale/lang/en'
+import useLang from '@/i18n/useLang'
+
+export default defineComponent({
+  components: {
+    [ElConfigProvider.name]: ElConfigProvider,
+  },
+  setup() {
+    const { lang } = useLang()
+    return {
+      lang,
+      locales: {
+        'zh-cn': localeZH,
+        en: localeEN,
+      },
+    }
+  },
+})
+</script>
+
+<style lang="scss">
+html,
+body,
+#app {
+  width: 100%;
+  height: 100%;
+  margin: 0;
+  padding: 0;
+  * {
+    outline: none;
+  }
+}
+</style>

+ 45 - 0
vue3/项目/vue3-project/src/api/brand.js

@@ -0,0 +1,45 @@
+import request from '@/utils/request'
+
+const api_name = '/admin/product/brand'
+
+// 分页列表
+export const GetBrandPageList = (page, limit) => {
+  return request({
+    url: `${api_name}/${page}/${limit}`,
+    method: 'get'
+  })
+}
+
+// 保存品牌
+export const SaveBrand = brand => {
+  return request({
+    url: `${api_name}/saveBrand`,
+    method: 'post',
+    data: brand,
+  })
+}
+
+// 修改信息
+export const UpdateBrandById = brand => {
+  return request({
+    url: `${api_name}/updateById`,
+    method: 'put',
+    data: brand,
+  })
+}
+
+// 根据id删除品牌
+export const DeleteBrandById = id => {
+  return request({
+    url: `${api_name}/deleteById/${id}`,
+    method: 'delete',
+  })
+}
+
+// 查询所有的品牌数据
+export const FindAllBrand = () => {
+  return request({
+    url: `${api_name}/findAll`,
+    method: 'get',
+  })
+}

+ 17 - 0
vue3/项目/vue3-project/src/api/login.js

@@ -0,0 +1,17 @@
+import request from '@/utils/request'
+
+// 获取登录用户信息
+export const GetUserinfo = () => {
+  return request({
+    url: '/admin/system/index/getUserInfo',
+    method: 'get',
+  })
+}
+
+//退出
+export const Logout = () => {
+  return request({
+    url: '/admin/system/index/logout',
+    method: 'get',
+  })
+}

+ 18 - 0
vue3/项目/vue3-project/src/api/menu.js

@@ -0,0 +1,18 @@
+import request from '@/utils/request'
+
+// 获取菜单
+// export const GetMenus = params => {
+//   return request({
+//     url: '/api/menus',
+//     method: 'get',
+//     params,
+//   })
+// }
+// 获取菜单
+export const GetMenus = params => {
+  return request({
+    url: '/admin/system/index/menus',
+    method: 'get',
+    params,
+  })
+}

+ 65 - 0
vue3/项目/vue3-project/src/api/product.js

@@ -0,0 +1,65 @@
+import request from '@/utils/request'
+
+const api_name = '/admin/product/product'
+
+// 分页列表
+export const GetProductPageList = (page, limit, queryDto) => {
+  return request({
+    url: `${api_name}/${page}/${limit}`,
+    method: 'get',
+    params: queryDto,
+  })
+}
+
+
+// 保存信息
+export const SaveProduct = product => {
+  return request({
+    url: `${api_name}/save`,
+    method: 'post',
+    data: product,
+  })
+}
+
+// 修改信息
+export const UpdateProductById = product => {
+  return request({
+    url: `${api_name}/updateById`,
+    method: 'put',
+    data: product,
+  })
+}
+
+// 根据id获取信息
+export const GetProductById = id => {
+  return request({
+    url: `${api_name}/getById/${id}`,
+    method: 'get',
+  })
+}
+
+
+// 根据id删除商品
+export const DeleteProductById = id => {
+  return request({
+    url: `${api_name}/deleteById/${id}`,
+    method: 'delete',
+  })
+}
+
+
+//审核
+export const UpdateProductAuditStatus = (id, auditStatus) => {
+  return request({
+    url: `${api_name}/updateAuditStatus/${id}/${auditStatus}`,
+    method: 'get',
+  })
+}
+
+//上下架
+export const UpdateProductStatus = (id, status) => {
+  return request({
+    url: `${api_name}/updateStatus/${id}/${status}`,
+    method: 'get',
+  })
+}

+ 15 - 0
vue3/项目/vue3-project/src/api/productSpec.js

@@ -0,0 +1,15 @@
+import request from '@/utils/request'
+
+const api_name = '/admin/product/productSpec'
+// 查询所有的产品规格数据
+export const FindAllProductSpec = () => {
+  return request({
+    url: `${api_name}/findAll`,
+    method: 'get',
+  })
+}
+
+export const UpdateProductSpecById = () =>{}
+export const DeleteProductSpecById = () =>{}
+export const GetProductSpecPageList = () =>{}
+export const SaveProductSpec = () =>{}

+ 11 - 0
vue3/项目/vue3-project/src/api/productUnit.js

@@ -0,0 +1,11 @@
+import request from '@/utils/request'
+
+const api_name = '/admin/product/productUnit'
+
+// 获取全部信息
+export const FindAllProductUnit = () => {
+  return request({
+    url: `${api_name}/findAll`,
+    method: 'get',
+  })
+}

+ 37 - 0
vue3/项目/vue3-project/src/api/sysMenu.js

@@ -0,0 +1,37 @@
+import request from '@/utils/request'
+
+const api_name = '/admin/system/sysMenu'
+// 分页列表
+export const FindNodes = () => {
+  return request({
+    url: `${api_name}/findNodes`,
+    method: 'get',
+  })
+}
+
+// 保存信息
+export const SaveMenu = sysMenu => {
+  return request({
+    url: `${api_name}/save`,
+    method: 'post',
+    data: sysMenu,
+  })
+}
+
+// 修改信息
+export const UpdateSysMenuById = sysMenu => {
+  return request({
+    url: `${api_name}/updateById`,
+    method: 'put',
+    data: sysMenu,
+  })
+}
+
+// 根据id删除数据
+export const RemoveSysMenuById = id => {
+  return request({
+    url: `${api_name}/removeById/${id}`,
+    method: 'delete',
+  })
+}
+

+ 62 - 0
vue3/项目/vue3-project/src/api/sysRole.js

@@ -0,0 +1,62 @@
+import request from '@/utils/request'
+
+// 分页查询角色数据
+export const GetSysRoleListByPage = (pageNum , pageSize , queryDto) => {
+  return request({
+    url: `/admin/system/sysRole/findByPage/${pageNum}/${pageSize}`,
+    method: 'post',
+    params: queryDto
+  })
+}
+
+//添加
+export const SaveSysRole = (sysRole) => {
+  return request({
+    url: `/admin/system/sysRole/saveSysRole`,
+    method: 'post',
+    data: sysRole
+  })
+}
+
+//EditSysRole
+export const UpdateSysRole = (sysRole) => {
+  return request({
+    url: `/admin/system/sysRole/updateSysRole`,
+    method: 'put',
+    data: sysRole
+  })
+}
+
+//DeleteSysRoleById
+
+export const DeleteSysRoleById = (id) => {
+  return request({
+    url: `/admin/system/sysRole/deleteById/${id}`,
+    method: 'delete'
+  })
+}
+
+//
+export const FindAllRoles = (userId) => {
+  return request({
+    url: `/admin/system/sysRole/findAllRoles/${userId}`,
+    method: 'get'
+  })
+}
+
+// 查询指定角色所对应的菜单id
+export const GetSysRoleMenuIds = (roleId) => {
+  return request({
+    url: "/admin/system/sysRoleMenu/findSysRoleMenuByRoleId/"+ roleId,
+    method: 'get'
+  })
+}
+
+// 根据角色分配菜单请求方法
+export const DoAssignMenuIdToSysRole = (assignMenuDto) => {
+  return request({
+    url: "/admin/system/sysRoleMenu/doAssign",
+    method: 'post',
+    data: assignMenuDto
+  })
+}

+ 44 - 0
vue3/项目/vue3-project/src/api/sysUser.js

@@ -0,0 +1,44 @@
+import request from '@/utils/request'
+
+// 分页查询
+export const GetSysUserListByPage = (pageNum , pageSize , queryDto) => {
+  return request({
+    url: "/admin/system/sysUser/findByPage/" + pageNum + "/" + pageSize,
+    method: 'get',
+    params: queryDto
+  })
+}
+// 新增用户的方法
+export const SaveSysUser = (data) => {
+  return request({
+    url: "/admin/system/sysUser/saveSysUser",
+    method: "post",
+    data
+  })
+}
+// 修改用户数据的方法
+export const UpdateSysUser = (sysUser) => {
+  return request({
+    url: "/admin/system/sysUser/updateSysUser",
+    method: "put",
+    data: sysUser
+  })
+}
+
+// 根据id删除用户
+export const DeleteSysUserById = (userId) => {
+  return request({
+    url: "/admin/system/sysUser/deleteById/" + userId,
+    method: 'delete'
+  })
+}
+
+
+//分配 DoAssignRoleToUser
+export const DoAssignRoleToUser = (vo) => {
+  return request({
+    url: "/admin/system/sysRoleUser/doAssign",
+    method: 'post',
+    data: vo
+  })
+}

+ 18 - 0
vue3/项目/vue3-project/src/api/test.js

@@ -0,0 +1,18 @@
+import request from '@/utils/request'
+
+// 测试
+export const TestError = () => {
+  return request({
+    url: '/api/500',
+    method: 'get',
+  })
+}
+
+// 用户列表
+export const getUsers = data => {
+  return request({
+    url: '/api/test/users',
+    method: 'post',
+    data,
+  })
+}

+ 1 - 0
vue3/项目/vue3-project/src/assets/logo.svg

@@ -0,0 +1 @@
+<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1618380288923" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1997" data-spm-anchor-id="a313x.7781069.0.i9" width="200" height="200" xmlns:xlink="http://www.w3.org/1999/xlink"><defs><style type="text/css"></style></defs><path d="M651.6 85l-137 137L372.9 84.7c-10-9.7-3.2-26.6 10.7-26.7l256.4-0.8c14.6 0 21.9 17.5 11.6 27.8z" fill="#e81c56" p-id="1998" data-spm-anchor-id="a313x.7781069.0.i10" class=""></path><path d="M844.1 71.2L717.8 197.5v364.3c0 2.1-0.9 4.2-2.4 5.7L522.7 760.1c14.8 14 72.2 62.7 157.4 67.4l204.3-204.3c7.6-7.6 11.8-17.8 11.8-28.5V92.8c0.1-27.3-32.8-40.9-52.1-21.6z" fill="#41b883" p-id="1999" data-spm-anchor-id="a313x.7781069.0.i5" class=""></path><path d="M522.7 760.1l-2.1 2.1-202.7-202.7V202.8c0-8.3-3.3-16.3-9.2-22.2L194.5 67.3c-20.7-20.5-55.9-5.9-55.9 23.3V638l334 334c17.4 17.4 45.6 17.4 63 0l144.5-144.5c-85.2-4.7-142.5-53.3-157.4-67.4z" fill="#1296db" p-id="2000" data-spm-anchor-id="a313x.7781069.0.i6" class=""></path></svg>

+ 14 - 0
vue3/项目/vue3-project/src/assets/style/element-variables.scss

@@ -0,0 +1,14 @@
+/**如果需要修改其它变量,可以在以下文件中查找
+ * https://github.com/element-plus/element-plus/blob/dev/packages/theme-chalk/src/common/var.scss
+ */
+
+@forward 'element-plus/theme-chalk/src/common/var.scss' with (
+  $colors: (
+    'primary': (
+      'base': $mainColor,
+    ),
+  ),
+);
+
+@use "element-plus/theme-chalk/src/reset.scss" as *;
+@use "element-plus/theme-chalk/src/index.scss" as *;

+ 18 - 0
vue3/项目/vue3-project/src/assets/style/global-variables.scss

@@ -0,0 +1,18 @@
+// 该文件中的变量是全局变量,在css文件和vue组件中可以直接使用
+
+$mainColor: #409eff; // 网站主题色
+
+// 侧边栏
+$menuBg: #304156; // 菜单背景颜色
+$menuTextColor: #fff; // 菜单文字颜色
+$menuActiveTextColor: $mainColor; // 已选中菜单文字颜色
+$menuActiveBg: none; // 已选中菜单背景颜色
+$menuHover: #263445; // 鼠标经过菜单时的背景颜色
+$subMenuBg: #1f2d3d; // 子菜单背景颜色
+$subMenuHover: #001528; // 鼠标经过子菜单时的背景颜色
+$collapseMenuActiveBg: #1f2d3d; // 菜单宽度折叠后,已选中菜单的背景颜色
+$collapseMenuActiveColor: $menuTextColor; // 菜单宽度折叠后,已选中菜单的文字颜色
+$collapseMenuActiveBorderColor: $mainColor; // 菜单宽度折叠后,已选中菜单的边框颜色
+$collapseMenuActiveBorderWidth: 2px; // 菜单宽度折叠后,已选中菜单的边框宽度
+$arrowColor: #909399; // 展开/收起箭头颜色
+$horizontalMenuHeight: 40px; // 菜单栏水平排列时候的高度

+ 1 - 0
vue3/项目/vue3-project/src/assets/svg/bug.svg

@@ -0,0 +1 @@
+<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M127.88 73.143c0 1.412-.506 2.635-1.518 3.669-1.011 1.033-2.209 1.55-3.592 1.55h-17.887c0 9.296-1.783 17.178-5.35 23.645l16.609 17.044c1.011 1.034 1.517 2.257 1.517 3.67 0 1.412-.506 2.635-1.517 3.668-.958 1.033-2.155 1.55-3.593 1.55-1.438 0-2.635-.517-3.593-1.55l-15.811-16.063a15.49 15.49 0 0 1-1.196 1.06c-.532.434-1.65 1.208-3.353 2.322a50.104 50.104 0 0 1-5.192 2.974c-1.758.87-3.94 1.658-6.546 2.364-2.607.706-5.189 1.06-7.748 1.06V47.044H58.89v73.062c-2.716 0-5.417-.367-8.106-1.102-2.688-.734-5.003-1.631-6.945-2.692a66.769 66.769 0 0 1-5.268-3.179c-1.571-1.057-2.73-1.94-3.476-2.65L33.9 109.34l-14.611 16.877c-1.066 1.14-2.344 1.711-3.833 1.711-1.277 0-2.422-.434-3.434-1.304-1.012-.978-1.557-2.187-1.635-3.627-.079-1.44.333-2.705 1.236-3.794l16.129-18.51c-3.087-6.197-4.63-13.644-4.63-22.342H5.235c-1.383 0-2.58-.517-3.592-1.55S.125 74.545.125 73.132c0-1.412.506-2.635 1.518-3.668 1.012-1.034 2.21-1.55 3.592-1.55h17.887V43.939L9.308 29.833c-1.012-1.033-1.517-2.256-1.517-3.669 0-1.412.505-2.635 1.517-3.668 1.012-1.034 2.21-1.55 3.593-1.55s2.58.516 3.593 1.55l13.813 14.106h67.396l13.814-14.106c1.012-1.034 2.21-1.55 3.592-1.55 1.384 0 2.581.516 3.593 1.55 1.012 1.033 1.518 2.256 1.518 3.668 0 1.413-.506 2.636-1.518 3.67l-13.814 14.105v23.975h17.887c1.383 0 2.58.516 3.593 1.55 1.011 1.033 1.517 2.256 1.517 3.668l-.005.01zM89.552 26.175H38.448c0-7.23 2.489-13.386 7.466-18.469C50.892 2.623 56.92.082 64 .082c7.08 0 13.108 2.541 18.086 7.624 4.977 5.083 7.466 11.24 7.466 18.469z"/></svg>

文件差異過大導致無法顯示
+ 0 - 0
vue3/项目/vue3-project/src/assets/svg/error-icons/403.svg


文件差異過大導致無法顯示
+ 0 - 0
vue3/项目/vue3-project/src/assets/svg/error-icons/404.svg


文件差異過大導致無法顯示
+ 0 - 0
vue3/项目/vue3-project/src/assets/svg/error-icons/500.svg


+ 1 - 0
vue3/项目/vue3-project/src/assets/svg/home.svg

@@ -0,0 +1 @@
+<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg class="icon" width="200px" height="200.00px" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg"><path d="M510.72 41.856l435.2 361.024a32 32 0 0 1-36.16 52.48l-4.736-3.2-41.024-34.048V832c0 48.832-32.64 90.752-76.864 95.552l-8.448 0.448H245.312c-45.696 0-80.96-39.04-84.928-86.912L160 832V407.808l-55.04 44.608a32 32 0 0 1-40.96-0.64l-4.032-4.16a32 32 0 0 1 0.64-40.96l4.096-4.032L510.784 41.856z m-0.512 82.688L223.232 356.672a31.808 31.808 0 0 1 0.256 1.152l0.512 5.76V832c0 16.768 8.64 28.992 17.92 31.552l3.392 0.448h533.376c9.216 0 18.88-10.432 20.928-25.92L800 832V364.992L510.208 124.544z" /></svg>

+ 1 - 0
vue3/项目/vue3-project/src/assets/svg/icon-home.svg

@@ -0,0 +1 @@
+<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg class="icon" width="200px" height="200.00px" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg"><path d="M510.72 41.856l435.2 361.024a32 32 0 0 1-36.16 52.48l-4.736-3.2-41.024-34.048V832c0 48.832-32.64 90.752-76.864 95.552l-8.448 0.448H245.312c-45.696 0-80.96-39.04-84.928-86.912L160 832V407.808l-55.04 44.608a32 32 0 0 1-40.96-0.64l-4.032-4.16a32 32 0 0 1 0.64-40.96l4.096-4.032L510.784 41.856z m-0.512 82.688L223.232 356.672a31.808 31.808 0 0 1 0.256 1.152l0.512 5.76V832c0 16.768 8.64 28.992 17.92 31.552l3.392 0.448h533.376c9.216 0 18.88-10.432 20.928-25.92L800 832V364.992L510.208 124.544z" /></svg>

文件差異過大導致無法顯示
+ 0 - 0
vue3/项目/vue3-project/src/assets/svg/language.svg


+ 7 - 0
vue3/项目/vue3-project/src/components/Avatar/hooks/useUserinfo.js

@@ -0,0 +1,7 @@
+import { storeToRefs } from 'pinia'
+import { useAccount } from '@/pinia/modules/account'
+
+export const useUserinfo = () => {
+  const { userinfo } = storeToRefs(useAccount())
+  return { userinfo }
+}

+ 46 - 0
vue3/项目/vue3-project/src/components/Avatar/index.vue

@@ -0,0 +1,46 @@
+<template>
+  <div class="userinfo">
+    <template v-if="!userinfo">
+      <i class="el-icon-user" />
+      <h3>admin</h3>
+    </template>
+    <template v-else>
+      <img class="avatar" :src="userinfo.avatar" />
+      <h3>{{ userinfo.name }}</h3>
+    </template>
+  </div>
+</template>
+
+<script>
+import { defineComponent } from 'vue'
+import { useUserinfo } from './hooks/useUserinfo'
+
+export default defineComponent({
+  setup() {
+    const { userinfo } = useUserinfo()
+
+    return { userinfo }
+  },
+})
+</script>
+<style lang="scss" scoped>
+.userinfo {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  i {
+    font-size: 48px;
+    color: $mainColor;
+  }
+  h3 {
+    font-size: 14px;
+    font-weight: normal;
+    margin: 8px 0;
+  }
+  .avatar {
+    width: 64px;
+    height: 64px;
+    border-radius: 50%;
+  }
+}
+</style>

+ 103 - 0
vue3/项目/vue3-project/src/components/ErrorLog/index.vue

@@ -0,0 +1,103 @@
+<template>
+  <div v-if="errorLogs.length > 0" class="errLog-container">
+    <el-badge :is-dot="true" @click="dialogTableVisible = true">
+      <el-button style="padding: 8px 10px;" size="small" type="danger">
+        <svg-icon name="bug" />
+      </el-button>
+    </el-badge>
+
+    <el-dialog v-model="dialogTableVisible" width="80%" append-to-body>
+      <template #title>
+        <span style="padding-right: 10px;">错误日志</span>
+        <el-button
+          size="mini"
+          type="primary"
+          icon="el-icon-delete"
+          @click="clearAll"
+        >
+          Clear All
+        </el-button>
+      </template>
+      <el-table :data="errorLogs" border>
+        <el-table-column label="Message">
+          <template #default="{ row }">
+            <div style="margin-bottom:10px">
+              <span class="message-title" style="padding-right: 16px;">
+                页面:
+              </span>
+              <el-tag type="success">
+                {{ row.url }}
+              </el-tag>
+            </div>
+
+            <div style="margin-bottom:10px">
+              <span class="message-title">事件源:</span>
+              <el-tag type="primary">
+                {{ row.info && row.info }}
+              </el-tag>
+            </div>
+
+            <div style="margin-bottom:10px">
+              <span class="message-title">错误提示:</span>
+              <el-tag type="danger">
+                {{ row.err && row.err.message }}
+              </el-tag>
+            </div>
+
+            <div v-if="row.err && row.err.config">
+              <span class="message-title" style="padding-right: 16px;">
+                接口地址:
+              </span>
+              <el-tag type="info">
+                {{ row.err && row.err.config && row.err.config.url }}
+              </el-tag>
+            </div>
+          </template>
+        </el-table-column>
+        <el-table-column label="Stack">
+          <template #default="{ row }">
+            {{ row.err && row.err.stack }}
+          </template>
+        </el-table-column>
+      </el-table>
+    </el-dialog>
+  </div>
+</template>
+
+<script>
+import { defineComponent, ref } from 'vue'
+import { useErrorlog } from '@/pinia/modules/errorLog'
+import { storeToRefs } from 'pinia'
+
+export default defineComponent({
+  name: 'ErrorLog',
+  setup() {
+    const dialogTableVisible = ref(false)
+    const errorStore = useErrorlog()
+    const { logs: errorLogs } = storeToRefs(errorStore)
+    const { clearErrorLog } = errorStore
+    const clearAll = () => {
+      dialogTableVisible.value = false
+      clearErrorLog()
+    }
+
+    return {
+      dialogTableVisible,
+      errorLogs,
+      clearAll,
+    }
+  },
+})
+</script>
+
+<style scoped>
+.errLog-container {
+  margin-right: 10px;
+}
+.message-title {
+  font-size: 16px;
+  color: #333;
+  font-weight: bold;
+  padding-right: 8px;
+}
+</style>

+ 512 - 0
vue3/项目/vue3-project/src/components/ProTable/index.vue

@@ -0,0 +1,512 @@
+<template>
+  <div class="page-box">
+    <!-- 搜索选项 -->
+
+    <el-form
+      v-if="!!search"
+      class="search"
+      :model="searchModel"
+      :inline="true"
+      label-position="right"
+      :label-width="search.labelWidth"
+      ref="searchForm"
+    >
+      <el-form-item
+        v-for="item in search.fields"
+        :key="item.name"
+        :label="$t(item.label)"
+        :prop="item.name"
+      >
+        <slot v-if="item.type === 'custom'" :name="item.slot" />
+        <el-select
+          v-else-if="item.type === 'select'"
+          v-model="searchModel[item.name]"
+          :filterable="!!item.filterable"
+          :multiple="!!item.multiple"
+          clearable
+          :placeholder="$t(item.label)"
+          :style="{ width: search.inputWidth, ...item.style }"
+        >
+          <el-option
+            v-for="option of item.options"
+            :key="option.value"
+            :label="$t(option.name)"
+            :value="option.value"
+          ></el-option>
+        </el-select>
+        <el-radio-group
+          v-model="searchModel[item.name]"
+          v-else-if="item.type === 'radio'"
+          :style="{ width: search.inputWidth, ...item.style }"
+        >
+          <el-radio
+            v-for="option of item.options"
+            :key="option.value"
+            :label="option.value"
+          >
+            {{ $t(option.name) }}
+          </el-radio>
+        </el-radio-group>
+        <el-radio-group
+          v-model="searchModel[item.name]"
+          v-else-if="item.type === 'radio-button'"
+          :style="{ width: search.inputWidth, ...item.style }"
+        >
+          <el-radio-button
+            v-for="option of item.options"
+            :key="option.value"
+            :label="option.value"
+          >
+            {{ $t(option.name) }}
+          </el-radio-button>
+        </el-radio-group>
+        <el-checkbox-group
+          v-model="searchModel[item.name]"
+          v-else-if="item.type === 'checkbox'"
+          :style="{ width: search.inputWidth, ...item.style }"
+        >
+          <el-checkbox
+            v-for="option of item.options"
+            :key="option.value"
+            :label="option.value"
+          >
+            {{ $t(option.name) }}
+          </el-checkbox>
+        </el-checkbox-group>
+        <el-checkbox-group
+          v-model="searchModel[item.name]"
+          v-else-if="item.type === 'checkbox-button'"
+          :style="{ width: search.inputWidth, ...item.style }"
+        >
+          <el-checkbox-button
+            v-for="option of item.options"
+            :key="option.value"
+            :label="option.value"
+          >
+            {{ $t(option.name) }}
+          </el-checkbox-button>
+        </el-checkbox-group>
+        <el-date-picker
+          v-else-if="item.type === 'date'"
+          v-model="searchModel[item.name]"
+          type="date"
+          format="YYYY-MM-DD"
+          clearable
+          @change="handleDateChange($event, item, 'YYYY-MM-DD')"
+          :placeholder="$t(item.label)"
+          :style="{ width: search.inputWidth, ...item.style }"
+        ></el-date-picker>
+        <el-date-picker
+          v-else-if="item.type === 'datetime'"
+          v-model="searchModel[item.name]"
+          type="datetime"
+          clearable
+          @change="handleDateChange($event, item, 'YYYY-MM-DD HH:mm:ss')"
+          format="YYYY-MM-DD HH:mm:ss"
+          :placeholder="$t(item.label)"
+          :style="{ width: search.inputWidth, ...item.style }"
+        ></el-date-picker>
+        <el-date-picker
+          v-else-if="item.type === 'daterange'"
+          v-model="searchModel[item.name]"
+          type="daterange"
+          format="YYYY-MM-DD"
+          range-separator="-"
+          :start-placeholder="$t('public.startdate')"
+          :end-placeholder="$t('public.enddate')"
+          clearable
+          @change="handleRangeChange($event, item, 'YYYY-MM-DD')"
+          :style="{ ...item.style }"
+        ></el-date-picker>
+        <el-date-picker
+          v-else-if="item.type === 'datetimerange'"
+          v-model="searchModel[item.name]"
+          type="datetimerange"
+          format="YYYY-MM-DD HH:mm:ss"
+          range-separator="-"
+          :start-placeholder="$t('public.starttime')"
+          :end-placeholder="$t('public.endtime')"
+          clearable
+          @change="handleRangeChange($event, item, 'YYYY-MM-DD HH:mm:ss')"
+          :style="{ ...item.style }"
+        ></el-date-picker>
+        <el-input-number
+          v-else-if="item.type === 'number'"
+          v-model="searchModel[item.name]"
+          :placeholder="$t(item.label)"
+          controls-position="right"
+          :min="item.min"
+          :max="item.max"
+          :style="{ width: search.inputWidth, ...item.style }"
+        />
+        <el-input
+          v-else-if="item.type === 'textarea'"
+          :maxlength="item.maxlength"
+          type="textarea"
+          clearable
+          v-model="searchModel[item.name]"
+          :placeholder="$t(item.label)"
+          :style="{ width: search.inputWidth, ...item.style }"
+        ></el-input>
+        <el-input
+          v-else
+          :maxlength="item.maxlength"
+          v-model="searchModel[item.name]"
+          clearable
+          :placeholder="$t(item.label)"
+          :style="{ width: search.inputWidth, ...item.style }"
+        ></el-input>
+      </el-form-item>
+      <el-form-item class="search-btn">
+        <el-button type="primary" icon="Search" @click="handleSearch">
+          {{ $t('public.search') }}
+        </el-button>
+        <el-button @click="handleReset" icon="RefreshRight">
+          {{ $t('public.reset') }}
+        </el-button>
+      </el-form-item>
+    </el-form>
+
+    <!-- title 和 工具栏 -->
+    <div class="head" v-if="!hideTitleBar">
+      <slot name="title">
+        <span class="title">{{ title }}</span>
+      </slot>
+      <div class="toolbar">
+        <slot name="toolbar"></slot>
+      </div>
+    </div>
+    <!-- table表格栏 -->
+    <div class="table">
+      <el-table
+        v-loading="loading"
+        :data="tableData"
+        :row-key="rowKey"
+        tooltip-effect="dark"
+        stripe
+        :border="border"
+        @selection-change="handleSelectionChange"
+      >
+        <el-table-column
+          v-for="item in columns"
+          :key="item.label"
+          :filter-method="item.filters && filterHandler"
+          :show-overflow-tooltip="!item.wrap"
+          v-bind="item"
+          :label="item.label ? $t(item.label) : ''"
+        >
+          <template #header="scope" v-if="!!item.labelSlot">
+            <slot :name="item.labelSlot" v-bind="scope"></slot>
+          </template>
+          <template #default="scope" v-if="!!item.tdSlot">
+            <slot :name="item.tdSlot" v-bind="scope"></slot>
+          </template>
+        </el-table-column>
+      </el-table>
+    </div>
+    <!-- 分页 -->
+    <el-pagination
+      v-if="paginationConfig.show && total > 0"
+      class="pagination"
+      :style="paginationConfig.style"
+      @size-change="handleSizeChange"
+      v-model:currentPage="pageNum"
+      @current-change="handleCurrentChange"
+      :page-sizes="paginationConfig.pageSizes"
+      v-model:pageSize="pageSize"
+      :layout="paginationConfig.layout"
+      :total="total"
+    ></el-pagination>
+  </div>
+</template>
+<script>
+import { defineComponent, reactive, toRefs, onBeforeMount, watch } from 'vue'
+const formatDate = (date, format) => {
+  var obj = {
+    'M+': date.getMonth() + 1,
+    'D+': date.getDate(),
+    'H+': date.getHours(),
+    'm+': date.getMinutes(),
+    's+': date.getSeconds(),
+    'q+': Math.floor((date.getMonth() + 3) / 3),
+    'S+': date.getMilliseconds(),
+  }
+  if (/(y+)/i.test(format)) {
+    format = format.replace(
+      RegExp.$1,
+      (date.getFullYear() + '').substr(4 - RegExp.$1.length)
+    )
+  }
+  for (var k in obj) {
+    if (new RegExp('(' + k + ')').test(format)) {
+      format = format.replace(
+        RegExp.$1,
+        RegExp.$1.length == 1
+          ? obj[k]
+          : ('00' + obj[k]).substr(('' + obj[k]).length)
+      )
+    }
+  }
+  return format
+}
+const getSearchModel = search => {
+  const searchModel = {}
+  if (search && search.fields) {
+    search.fields.forEach(item => {
+      switch (item.type) {
+        case 'checkbox':
+        case 'checkbox-button':
+          searchModel[item.name] = []
+          break
+        default:
+          break
+      }
+      if (item.defaultValue !== undefined) {
+        searchModel[item.name] = item.defaultValue
+        // 日期范围和时间范围真实变量默认值
+        if (
+          (item.type === 'daterange' || item.type === 'datetimerange') &&
+          !!item.trueNames &&
+          Array.isArray(item.defaultValue)
+        ) {
+          item.defaultValue.forEach((val, index) => {
+            searchModel[item.trueNames[index]] = val
+          })
+        }
+      }
+    })
+  }
+  return searchModel
+}
+export default defineComponent({
+  props: {
+    // 请求数据的方法
+    request: {
+      type: Function,
+    },
+    // 表格标题
+    title: {
+      type: String,
+      default: '',
+    },
+    // 是否隐藏标题栏
+    hideTitleBar: {
+      type: Boolean,
+      default: false,
+    },
+    // 是否隐藏按钮操作
+    hideToolbar: {
+      type: Boolean,
+      default: false,
+    },
+    // 搜索表单配置,false表示不显示搜索表单
+    search: {
+      type: [Boolean, Object],
+      default: false,
+    },
+    border: {
+      type: Boolean,
+      default: false,
+    },
+    // 表头配置
+    columns: {
+      type: Array,
+      default: () => [],
+    },
+    // 行数据的Key,同elementUI的table组件的row-key
+    rowKey: {
+      type: [String, Function],
+      default: () => {},
+    },
+    // 分页配置,false表示不显示分页
+    pagination: {
+      type: [Boolean, Object],
+      default: () => ({}),
+    },
+  },
+  setup(props, { emit }) {
+    // 优化搜索字段,
+    // 1、如果搜索配置有transform处理函数,执行transform
+    // 2、删除日期范围默认的name字段
+    const optimizeFields = search => {
+      const searchModel = JSON.parse(JSON.stringify(state.searchModel))
+      if (search && search.fields) {
+        search.fields.forEach(item => {
+          if (!searchModel.hasOwnProperty(item.name)) {
+            return
+          }
+          if (item.transform) {
+            searchModel[item.name] = item.transform(searchModel[item.name])
+          }
+          if (
+            (item.type === 'daterange' || item.type === 'datetimerange') &&
+            !!item.trueNames
+          ) {
+            delete searchModel[item.name]
+          }
+        })
+      }
+      return searchModel
+    }
+
+    // 请求列表数据
+    const getTableData = async () => {
+      state.loading = true
+      const searchModel = optimizeFields(props.search)
+      const { data, total } = await props.request({
+        current: state.pageNum,
+        size: state.pageSize,
+        ...searchModel,
+      })
+      state.loading = false
+      state.tableData = data
+      state.total = total
+    }
+
+    const state = reactive({
+      searchModel: getSearchModel(props.search),
+      loading: false,
+      tableData: [],
+      total: 0,
+      pageNum: 1,
+      pageSize: (!!props.pagination && props.pagination.pageSize) || 10,
+      paginationConfig: {
+        show: false,
+      },
+      // 搜索
+      handleSearch() {
+        state.pageNum = 1
+        getTableData()
+      },
+      // 重置函数
+      handleReset() {
+        if (JSON.stringify(state.searchModel) === '{}') {
+          return
+        }
+        state.pageNum = 1
+        state.searchModel = getSearchModel(props.search)
+        getTableData()
+      },
+      // 刷新
+      refresh() {
+        getTableData()
+      },
+
+      // 当前页变化
+      handleCurrentChange() {
+        getTableData()
+      },
+      // 改变每页size数量
+      handleSizeChange() {
+        state.pageNum = 1
+        getTableData()
+      },
+      // 全选
+      handleSelectionChange(arr) {
+        emit('selectionChange', arr)
+      },
+      // 过滤方法
+      filterHandler(value, row, column) {
+        const property = column['property']
+        return row[property] === value
+      },
+      // 日期范围
+      handleDateChange(date, item, format) {
+        state.searchModel[item.name] = date ? formatDate(date, format) : ''
+      },
+      handleRangeChange(date, item, format) {
+        const arr = !!date && date.map(d => formatDate(d, format))
+        state.searchModel[item.name] = arr ? arr : []
+
+        if (!item.trueNames) {
+          return
+        }
+
+        if (arr) {
+          arr.forEach((val, index) => {
+            state.searchModel[item.trueNames[index]] = val
+          })
+        } else {
+          item.trueNames.forEach(key => {
+            delete state.searchModel[key]
+          })
+        }
+      },
+    })
+
+    if (typeof props.pagination === 'object') {
+      const { layout, pageSizes, style } = props.pagination
+      state.paginationConfig = {
+        show: true,
+        layout: layout || 'total, sizes, prev, pager, next, jumper',
+        pageSizes: pageSizes || [10, 20, 30, 40, 50, 100],
+        style: style || {},
+      }
+    }
+
+    watch(
+      () => props.search,
+      val => {
+        state.searchModel = getSearchModel(val)
+      },
+      { deep: true }
+    )
+
+    onBeforeMount(() => {
+      getTableData()
+    })
+
+    return {
+      ...toRefs(state),
+    }
+  },
+})
+</script>
+<style lang="scss" scoped>
+.page-box {
+  width: 100%;
+  box-sizing: border-box;
+  .search {
+    padding: 20px 20px 0;
+    background: #fff;
+    margin-bottom: 10px;
+    display: flex;
+    flex-wrap: wrap;
+    .el-form-item {
+      margin-bottom: 20px;
+    }
+    .search-btn {
+      margin-left: auto;
+    }
+    :deep(.el-input-number .el-input__inner) {
+      text-align: left;
+    }
+    :deep(.el-range-editor.el-input__wrapper) {
+      box-sizing: border-box;
+    }
+  }
+
+  .head {
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    padding: 20px 20px 0;
+    background: #fff;
+    .title {
+      font-size: 16px;
+    }
+  }
+  .table {
+    padding: 20px;
+    background: #fff;
+  }
+  .pagination {
+    padding: 0 20px 20px;
+    background: #fff;
+    justify-content: flex-end;
+    :last-child {
+      margin-right: 0;
+    }
+  }
+}
+</style>

+ 143 - 0
vue3/项目/vue3-project/src/components/SelectTree/Select.vue

@@ -0,0 +1,143 @@
+<template>
+  <el-select
+    ref="select"
+    class="select"
+    style="width:100%"
+    clearable
+    :multiple="multiple"
+    v-model="selectValue"
+    :value-key="treeProps.label"
+    @remove-tag="handleRemove"
+    @clear="handleClear"
+    popper-class="select-popover"
+    :popper-append-to-body="false"
+    collapse-tags
+    :placeholder="placeholder"
+  >
+    <el-option
+      v-for="(item, index) in selectOptions"
+      :key="index"
+      :label="item[treeProps.label]"
+      :value="item"
+    />
+  </el-select>
+</template>
+<script>
+import {
+  defineComponent,
+  onBeforeMount,
+  reactive,
+  ref,
+  watch,
+  toRefs,
+  inject,
+} from 'vue'
+
+export default defineComponent({
+  props: {
+    value: {},
+    data: {
+      type: Array,
+      default() {
+        return []
+      },
+    },
+    multiple: {
+      type: Boolean,
+      default() {
+        return false
+      },
+    },
+    treeProps: {
+      type: Object,
+      default() {
+        return {}
+      },
+    },
+  },
+  setup(props, { emit }) {
+    const {
+      multiple,
+      treeProps: { label, children, nodeKey },
+    } = props
+    const state = reactive({
+      select: ref(null),
+      newValue: null,
+      selectValue: '',
+      selectOptions: [],
+      placeholder: inject('placeholder'),
+      updateSelectValue(values) {
+        if (!values || state.selectOptions.length <= 0) {
+          return
+        }
+        state.selectValue = multiple
+          ? state.selectOptions.filter(item => values.includes(item[nodeKey]))
+          : state.selectOptions.find(item => values === item[nodeKey]) || {}
+      },
+      updateSelectOptions(data) {
+        state.selectOptions = state.flatTree(data)
+      },
+      flatTree(tree) {
+        const res = []
+        tree.forEach(item => {
+          res.push({
+            [nodeKey]: item[nodeKey],
+            [label]: item[label],
+          })
+          if (item[children] && item[children].length > 0) {
+            res.push(...state.flatTree(item[children]))
+          }
+        })
+        return res
+      },
+      handleRemove() {
+        emit(
+          'select-change',
+          multiple
+            ? state.selectValue.map(item => item[nodeKey])
+            : state.selectValue[nodeKey]
+        )
+      },
+      handleClear() {
+        emit('select-change', multiple ? [] : '')
+      },
+    })
+
+    watch(
+      () => props.data,
+      v => {
+        state.updateSelectOptions(v)
+        state.updateSelectValue(state.newValue)
+      },
+      {
+        immediate: true,
+      }
+    )
+
+    watch(
+      () => props.value,
+      v => {
+        state.newValue = v
+        state.updateSelectValue(state.newValue)
+      },
+      {
+        immediate: true,
+      }
+    )
+
+    onBeforeMount(() => {
+      state.updateSelectOptions(props.data)
+    })
+
+    return toRefs(state)
+  },
+})
+</script>
+
+<style lang="scss" scoped>
+.select {
+  :deep(.select-popover) {
+    display: none;
+  }
+}
+</style>

+ 107 - 0
vue3/项目/vue3-project/src/components/SelectTree/Tree.vue

@@ -0,0 +1,107 @@
+<template>
+  <div v-if="data.length <= 0" style="text-align:center;padding: 16px">
+    暂无数据
+  </div>
+  <el-tree
+    v-else
+    class="common-tree"
+    ref="tree"
+    :data="data"
+    :node-key="nodeKey"
+    :show-checkbox="multiple"
+    :check-strictly="treeProps['check-strictly'] || true"
+    :expand-on-click-node="false"
+    :check-on-click-node="true"
+    :default-expanded-keys="treeProps['default-expanded-keys'] || value"
+    :default-checked-keys="value"
+    @node-click="handleNodeClick"
+    @check-change="handleCheckChange"
+    v-bind="treeProps"
+  ></el-tree>
+</template>
+<script>
+import { defineComponent, reactive, ref, toRefs, watch } from 'vue'
+
+export default defineComponent({
+  props: {
+    value: {
+      type: Array,
+      default: () => [],
+    },
+    data: {
+      type: Array,
+      default() {
+        return []
+      },
+    },
+    multiple: {
+      type: Boolean,
+      default() {
+        return false
+      },
+    },
+    treeProps: {
+      type: Object,
+      default() {
+        return {}
+      },
+    },
+  },
+  setup(props, { emit }) {
+    const { multiple, treeProps } = props
+    const state = reactive({
+      nodeKey: treeProps['node-key'] || 'id',
+      tree: ref(null),
+      handleNodeClick(node) {
+        if (!multiple) {
+          state.tree.setCheckedKeys([node[state.nodeKey]])
+          state.hanelEmit()
+          emit('close')
+        }
+      },
+      handleCheckChange() {
+        if (!multiple) {
+          return
+        }
+        state.hanelEmit()
+      },
+      hanelEmit() {
+        const res = state.tree.getCheckedKeys()
+        emit('tree-change', multiple ? res : res[0] || '')
+      },
+      updateValue(value) {
+        if (state.tree) {
+          state.tree.setCheckedKeys(value)
+          // state.hanelEmit()
+        }
+      },
+    })
+
+    watch(
+      () => props.value,
+      v => {
+        state.updateValue(v)
+      },
+      {
+        immediate: true,
+      }
+    )
+
+    return toRefs(state)
+  },
+})
+</script>
+<style lang="scss" scoped>
+.common-tree {
+  overflow: auto;
+  :deep {
+    .el-tree-node {
+      &.is-checked {
+        > .el-tree-node__content {
+          color: $mainColor;
+        }
+      }
+    }
+  }
+}
+</style>

+ 118 - 0
vue3/项目/vue3-project/src/components/SelectTree/index.vue

@@ -0,0 +1,118 @@
+<template>
+  <el-popover
+    ref="popover"
+    placement="bottom-start"
+    trigger="click"
+    :popper-options="{ boundariesElement: 'viewport' }"
+    v-model:visible="visible"
+    :disabled="disabled"
+  >
+    <tree
+      ref="tree"
+      :value="is_multiple ? modelValue : modelValue ? [modelValue] : []"
+      :data="data || treeProps.data"
+      :multiple="is_multiple"
+      :tree-props="treeProps"
+      @tree-change="handleTreeChange"
+      @close="visible = false"
+    />
+
+    <template #reference>
+      <custom-select
+        ref="select"
+        :value="modelValue"
+        :data="data || treeProps.data"
+        :multiple="is_multiple"
+        :tree-props="{ children, label, nodeKey }"
+        @select-change="handleSelectChange"
+      />
+    </template>
+  </el-popover>
+</template>
+
+<script>
+import { defineComponent, reactive, ref, toRefs, onMounted, provide } from 'vue'
+
+import Tree from './Tree.vue'
+import CustomSelect from './Select.vue'
+
+export default defineComponent({
+  name: 'el-select-tree',
+  components: {
+    Tree,
+    CustomSelect,
+  },
+  props: {
+    modelValue: {},
+    disabled: {
+      type: Boolean,
+      default: false,
+    },
+    placeholder: {
+      type: String,
+      default: '请选择',
+    },
+    data: {
+      type: Array,
+      default() {
+        return []
+      },
+    },
+    multiple: {
+      type: Boolean,
+      default() {
+        return false
+      },
+    },
+    treeProps: {
+      type: Object,
+      default() {
+        return {}
+      },
+    },
+  },
+  setup(props, { emit }) {
+    provide('placeholder', props.placeholder)
+    const { treeProps, multiple } = props
+    const state = reactive({
+      popover: ref(null),
+      visible: false,
+      select: ref(null),
+      tree: ref(null),
+      is_multiple: multiple || treeProps['show-checkbox'],
+      label:
+        treeProps.props && treeProps.props.label
+          ? treeProps.props.label
+          : 'label',
+      children:
+        treeProps.props && treeProps.props.children
+          ? treeProps.props.children
+          : 'children',
+      nodeKey: treeProps['node-key'] || 'id',
+      handleTreeChange(value) {
+        emit('update:modelValue', value)
+        emit('change', value)
+      },
+      handleSelectChange(value) {
+        // state.tree.updateValue(value)
+        emit('update:modelValue', value)
+        emit('change', value)
+      },
+      initPopoverStyle(width) {
+        state.popover.popperRef.style.minWidth = width
+        state.popover.popperRef.style.boxSizing = 'border-box'
+        state.popover.popperRef.style.maxHeight = '400px'
+        state.popover.popperRef.style.overflow = 'auto'
+      },
+    })
+
+    onMounted(() => {
+      state.initPopoverStyle(
+        state.select.select.selectWrapper.offsetWidth + 'px'
+      )
+    })
+
+    return toRefs(state)
+  },
+})
+</script>

+ 33 - 0
vue3/项目/vue3-project/src/components/SvgIcon/index.vue

@@ -0,0 +1,33 @@
+<template>
+  <svg class="icon" aria-hidden="true">
+    <use :xlink:href="symbolId" />
+  </svg>
+</template>
+
+<script>
+import { defineComponent, computed } from 'vue'
+
+export default defineComponent({
+  name: 'SvgIcon',
+  props: {
+    name: {
+      type: String,
+      required: true,
+    },
+  },
+  setup(props) {
+    const symbolId = computed(() => `#icon-${props.name}`)
+    return { symbolId }
+  },
+})
+</script>
+
+<style lang="scss" scoped>
+.icon {
+  width: 1em;
+  height: 1em;
+  vertical-align: -0.15em;
+  fill: currentColor;
+  overflow: hidden;
+}
+</style>

+ 24 - 0
vue3/项目/vue3-project/src/default-settings.js

@@ -0,0 +1,24 @@
+export default {
+  menus: {
+    // 菜单栏是否显示
+    isShow: true,
+    // 菜单栏排列方式
+    mode: 'vertical', // horizontal: 水平排列   vertical: 垂直排列
+  },
+  tagsbar: {
+    // 标签栏是否显示
+    isShow: true,
+  },
+  breadcrumbs: {
+    // 面包屑导航是否显示
+    isShow: true,
+  },
+  topbar: {
+    // 顶栏是否固定
+    isFixed: true,
+  },
+  layout: {
+    // 是否流式布局
+    isFluid: true,
+  },
+}

+ 17 - 0
vue3/项目/vue3-project/src/directive/index.js

@@ -0,0 +1,17 @@
+import { useAccount } from '@/pinia/modules/account'
+
+export const Permission = app => {
+  app.directive('permission', {
+    mounted: function(el, binding) {
+      const { permissionList } = useAccount()
+
+      if (
+        binding.value &&
+        permissionList.every(item => item !== binding.value)
+      ) {
+        // 移除组件
+        el.parentNode.removeChild(el)
+      }
+    },
+  })
+}

+ 27 - 0
vue3/项目/vue3-project/src/error-log.js

@@ -0,0 +1,27 @@
+import { nextTick } from 'vue'
+import { useErrorlog } from './pinia/modules/errorLog'
+// import store from '@/store'
+
+// 判断环境,决定是否开启错误监控
+//   - import.meta.env.DEV 布尔值,代表开发环境
+//   - import.meta.env.PROD 布尔值,代表生产环境
+
+// const flag =  import.meta.env.PROD  // 生产环境才进行错误监控
+const flag = true // 为了演示,默认开启错误监控。如果你的项目不需要错误监控,请设为false
+
+export default app => {
+  if (flag) {
+    app.config.errorHandler = function(err, vm, info) {
+      nextTick(() => {
+        useErrorlog().addErrorLog({
+          err,
+          // vm, // 这里不保存vm,否则渲染错误日志的时候控制台会有警告
+          info,
+          url: window.location.href,
+          id: Date.now(),
+        })
+        console.error(err, info)
+      })
+    }
+  }
+}

+ 2 - 0
vue3/项目/vue3-project/src/global-components.js

@@ -0,0 +1,2 @@
+export { default as SvgIcon } from '@/components/SvgIcon/index.vue'
+export { default as ProTable } from '@/components/ProTable/index.vue'

+ 33 - 0
vue3/项目/vue3-project/src/hooks/useCloseTag.js

@@ -0,0 +1,33 @@
+import { useTags } from '@/pinia/modules/tags'
+import { reactive, toRefs, getCurrentInstance } from 'vue'
+import { useRoute, useRouter } from 'vue-router'
+
+// 关闭当前标签
+export default () => {
+  const instance = getCurrentInstance()
+  const router = useRouter()
+  const route = useRoute()
+  const { delTag } = useTags()
+  const state = reactive({
+    /**
+     * @param {String} fullPath 要跳转到那个页面的地址
+     * @param {Boolean} reload 是否在跳转后重新渲染页面组件
+     * @param {Boolean} f5 是否在跳转后刷新页面
+     * @return {*}
+     */
+    closeTag({ fullPath, reload, f5 } = {}) {
+      delTag(route)
+      fullPath ? router.push(fullPath) : router.back()
+      reload &&
+        setTimeout(() => {
+          instance.appContext.config.globalProperties.$tagsbar.refreshSelectedTag(
+            route
+          )
+        }, 500)
+
+      f5 && setTimeout(() => window.location.reload(), 500)
+    },
+  })
+
+  return toRefs(state)
+}

+ 19 - 0
vue3/项目/vue3-project/src/i18n/index.js

@@ -0,0 +1,19 @@
+import { createI18n } from 'vue-i18n'
+
+const getMessage = modules => {
+  return Object.entries(modules).reduce((module, [path, mod]) => {
+    const moduleName = path.replace(/^\.\/locales\/[\w-]+\/(.*)\.\w+$/, '$1')
+    module[moduleName] = mod.default
+    return module
+  }, {})
+}
+
+export default createI18n({
+  locale: localStorage.getItem('__VEA__lang') || 'zh-cn',
+  messages: {
+    'zh-cn': getMessage(import.meta.globEager('./locales/zh-cn/**/*.js')),
+    en: getMessage(import.meta.globEager('./locales/en/**/*.js')),
+  },
+  legacy: false,
+  globalInjection: true,
+})

+ 6 - 0
vue3/项目/vue3-project/src/i18n/locales/en/error.js

@@ -0,0 +1,6 @@
+export default {
+  noauth: 'No access',
+  servererror: 'Server error',
+  notfound: 'Page not found',
+  backhome: 'Back to Home',
+}

+ 10 - 0
vue3/项目/vue3-project/src/i18n/locales/en/login.js

@@ -0,0 +1,10 @@
+export default {
+  username: 'Username',
+  password: 'Password',
+  login: 'Login',
+  logining: 'Login...',
+  loginsuccess: 'Success',
+  'rules-username': 'Please input username',
+  'rules-password': 'Please input password',
+  'rules-regpassword': '6 to 12 characters in length',
+}

+ 16 - 0
vue3/项目/vue3-project/src/i18n/locales/en/menu.js

@@ -0,0 +1,16 @@
+export default {
+  homepage: 'Homepage',
+  dashboard: 'Dashboard',
+  test: 'Test page',
+  testList: 'List',
+  testAdd: 'Add',
+  testEdit: 'Edit',
+  testAuth: 'Auth',
+  testNoAuth: 'No auth',
+  'test-cache': 'Cache',
+  'test-no-cache': 'No Cache',
+  nest: 'Nest page',
+  nestPage1: 'Page1',
+  nestPage2: 'Page2',
+  'test-error-log': 'Error log',
+}

+ 22 - 0
vue3/项目/vue3-project/src/i18n/locales/en/public.js

@@ -0,0 +1,22 @@
+export default {
+  sure: 'Sure',
+  search: 'Search',
+  reset: 'Reset',
+  edit: 'Edit',
+  add: 'Add',
+  delete: 'Delete',
+  save: 'Save',
+  cancel: 'Cancel',
+  yes: 'Yes',
+  no: 'No',
+  status: 'Status',
+  operate: 'Operate',
+  enabled: 'Enabled',
+  disabled: 'Disabled',
+  male: 'Male',
+  female: 'Female',
+  startdate: 'From',
+  enddate: 'To',
+  starttime: 'From',
+  endtime: 'To',
+}

+ 8 - 0
vue3/项目/vue3-project/src/i18n/locales/en/tags.js

@@ -0,0 +1,8 @@
+export default {
+  refresh: 'Refresh',
+  close: 'Close',
+  other: 'Close other',
+  left: 'Close left',
+  right: 'Close right',
+  all: 'Close all',
+}

+ 32 - 0
vue3/项目/vue3-project/src/i18n/locales/en/test/list.js

@@ -0,0 +1,32 @@
+export default {
+  title: 'List',
+  batchDelete: 'Batch delete',
+  add: 'Add one',
+  refresh: 'Refresh',
+  index: 'Index',
+  name: 'Nickname',
+  email: 'Email',
+  desc: 'Description',
+  publish: 'Published',
+  nopublish: 'Unpublished',
+  gender: 'Gender',
+  city: 'City',
+  bj: 'Beijing',
+  sh: 'Shanghai',
+  gz: 'Guangzhou',
+  sz: 'Shenzhen',
+  hobby: 'Hobby',
+  eat: 'Eat',
+  sleep: 'Sleep',
+  bit: 'Beat',
+  fruit: 'Fruit',
+  apple: 'Apple',
+  banana: 'Banana',
+  orange: 'Orange',
+  grape: 'Grape',
+  date: 'Date',
+  daterange: 'Date range',
+  time: 'Time',
+  timerange: 'Time range',
+  num: 'Number',
+}

+ 21 - 0
vue3/项目/vue3-project/src/i18n/locales/en/topbar.js

@@ -0,0 +1,21 @@
+export default {
+  center: 'User center',
+  password: 'Modify password',
+  logout: 'Logout',
+  'lock-title': 'Lock screen',
+  'lock-password': 'Password',
+  'lock-rules-password': 'Please input Screen password',
+  'lock-locked': 'Screen Locked',
+  'lock-lock': 'Unlock',
+  'lock-relogin': 'Re-login',
+  'lock-rules-password2': 'Screen password or User password',
+  'lock-rules-password3': 'Password error',
+  'lock-error': 'Your account has been logged out, please log in directly',
+  'lock-week0': 'Sunday',
+  'lock-week1': 'Monday',
+  'lock-week2': 'Tuesday',
+  'lock-week3': 'Wednesday',
+  'lock-week4': 'Thursday',
+  'lock-week5': 'Friday',
+  'lock-week6': 'Saturday',
+}

+ 6 - 0
vue3/项目/vue3-project/src/i18n/locales/zh-cn/error.js

@@ -0,0 +1,6 @@
+export default {
+  noauth: '您无权访问此页面',
+  servererror: '服务器出错了',
+  notfound: '您访问的页面不存在',
+  backhome: '返回首页',
+}

+ 12 - 0
vue3/项目/vue3-project/src/i18n/locales/zh-cn/login.js

@@ -0,0 +1,12 @@
+export default {
+  username: '用户名',
+  password: '密码',
+  login: '登录',
+  logining: '登录中...',
+  loginsuccess: '登录成功',
+  captcha:'验证码',
+  'rules-username': '请输入用户名',
+  'rules-password': '请输入密码',
+  'rules-regpassword': '长度在 6 到 12 个字符',
+  'rules-captcha': '请输入验证码',
+}

+ 16 - 0
vue3/项目/vue3-project/src/i18n/locales/zh-cn/menu.js

@@ -0,0 +1,16 @@
+export default {
+  homepage: '首页',
+  dashboard: '工作台',
+  test: '测试页面',
+  testList: '列表',
+  testAdd: '添加',
+  testEdit: '编辑',
+  testAuth: '权限测试',
+  testNoAuth: '权限页面',
+  'test-cache': '该页面可缓存',
+  'test-no-cache': '该页面不缓存',
+  nest: '二级页面',
+  nestPage1: 'Page1',
+  nestPage2: 'Page2',
+  'test-error-log': '测试错误日志',
+}

+ 22 - 0
vue3/项目/vue3-project/src/i18n/locales/zh-cn/public.js

@@ -0,0 +1,22 @@
+export default {
+  sure: '确定',
+  search: '搜索',
+  reset: '重置',
+  edit: '编辑',
+  add: '新增',
+  delete: '删除',
+  save: '保存',
+  cancel: '取消',
+  yes: '是',
+  no: '否',
+  status: '状态',
+  operate: '操作',
+  enabled: '启用',
+  disabled: '禁用',
+  male: '男',
+  female: '女',
+  startdate: '开始日期',
+  enddate: '结束日期',
+  starttime: '开始时间',
+  endtime: '结束时间',
+}

+ 8 - 0
vue3/项目/vue3-project/src/i18n/locales/zh-cn/tags.js

@@ -0,0 +1,8 @@
+export default {
+  refresh: '刷新',
+  close: '关闭',
+  other: '关闭其它',
+  left: '关闭左侧',
+  right: '关闭右侧',
+  all: '关闭全部',
+}

+ 32 - 0
vue3/项目/vue3-project/src/i18n/locales/zh-cn/test/list.js

@@ -0,0 +1,32 @@
+export default {
+  title: '列表',
+  batchDelete: '批量删除',
+  add: '添加一条',
+  refresh: '刷新',
+  index: '序号',
+  name: '昵称',
+  email: '邮箱',
+  desc: '描述',
+  publish: '已发布',
+  nopublish: '未发布',
+  gender: '性别',
+  city: '城市',
+  bj: '北京',
+  sh: '上海',
+  gz: '广州',
+  sz: '深圳',
+  hobby: '爱好',
+  eat: '吃饭',
+  sleep: '睡觉',
+  bit: '打豆豆',
+  fruit: '水果',
+  apple: '苹果',
+  banana: '香蕉',
+  orange: '橘子',
+  grape: '葡萄',
+  date: '日期',
+  daterange: '日期范围',
+  time: '时间',
+  timerange: '时间范围',
+  num: '数量',
+}

+ 21 - 0
vue3/项目/vue3-project/src/i18n/locales/zh-cn/topbar.js

@@ -0,0 +1,21 @@
+export default {
+  center: '个人中心',
+  password: '修改密码',
+  logout: '退出登录',
+  'lock-title': '锁定屏幕',
+  'lock-password': '锁屏密码',
+  'lock-rules-password': '请输入锁屏密码',
+  'lock-locked': '屏幕已锁定',
+  'lock-lock': '解锁',
+  'lock-relogin': '重新登录',
+  'lock-rules-password2': '请输入锁屏密码或登录密码',
+  'lock-rules-password3': '密码错误',
+  'lock-error': '您的账号已退出,请直接登录',
+  'lock-week0': '星期日',
+  'lock-week1': '星期一',
+  'lock-week2': '星期二',
+  'lock-week3': '星期三',
+  'lock-week4': '星期四',
+  'lock-week5': '星期五',
+  'lock-week6': '星期六',
+}

+ 16 - 0
vue3/项目/vue3-project/src/i18n/useLang.js

@@ -0,0 +1,16 @@
+import { computed } from 'vue'
+import { useI18n } from 'vue-i18n'
+
+export default function useLang() {
+  const i18n = useI18n()
+  const lang = computed(() => i18n.locale.value)
+  const changeLang = value => {
+    i18n.locale.value = value
+    localStorage.setItem('__VEA__lang', value)
+  }
+  return {
+    i18n,
+    lang,
+    changeLang,
+  }
+}

+ 26 - 0
vue3/项目/vue3-project/src/layout/components/Content/index.vue

@@ -0,0 +1,26 @@
+<template>
+  <router-view v-slot="{ Component }">
+    <keep-alive :include="cacheList">
+      <component :is="Component" :key="key" />
+    </keep-alive>
+  </router-view>
+</template>
+<script>
+import { storeToRefs } from 'pinia'
+import { computed, defineComponent } from 'vue'
+import { useRoute } from 'vue-router'
+import { useTags } from '@/pinia/modules/tags'
+
+export default defineComponent({
+  setup() {
+    const route = useRoute()
+    const { cacheList } = storeToRefs(useTags())
+    const key = computed(() => route.fullPath)
+
+    return {
+      cacheList,
+      key,
+    }
+  },
+})
+</script>

+ 30 - 0
vue3/项目/vue3-project/src/layout/components/Sidebar/Item.vue

@@ -0,0 +1,30 @@
+<template>
+  <svg-icon class="icon" v-if="isCustomSvg" :name="icon" />
+  <component :is="icon" v-else-if="!!icon" class="icon" />
+  <span>{{ $t(title) }}</span>
+</template>
+
+<script>
+import { computed, defineComponent } from 'vue'
+
+export default defineComponent({
+  props: ['title', 'icon'],
+  setup({ icon }) {
+    const isCustomSvg = computed(() => icon && icon.startsWith('icon-'))
+
+    return {
+      isCustomSvg,
+    }
+  },
+})
+</script>
+<style lang="scss" scoped>
+.icon {
+  margin-right: 10px;
+  width: 16px !important;
+  height: 16px !important;
+  font-size: 16px;
+  text-align: center;
+  color: currentColor;
+}
+</style>

+ 42 - 0
vue3/项目/vue3-project/src/layout/components/Sidebar/Logo.vue

@@ -0,0 +1,42 @@
+<template>
+  <div class="brand">
+    <img class="logo" src="~@/assets/logo.svg" @click="goHome" />
+    <div class="title">Vue3 Element Admin</div>
+  </div>
+</template>
+<script>
+import { defineComponent } from 'vue'
+import { useRouter } from 'vue-router'
+
+export default defineComponent({
+  setup() {
+    const router = useRouter()
+    const goHome = () => {
+      router.push('/')
+    }
+    return { goHome }
+  },
+})
+</script>
+<style lang="scss" scoped>
+.brand {
+  height: 48px;
+  padding: 0 8px;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  .logo {
+    cursor: pointer;
+    max-width: 32px;
+    max-height: 32px;
+  }
+  .title {
+    color: #fff;
+    font-size: 14px;
+    font-weight: 700;
+    white-space: nowrap;
+    margin-left: 8px;
+    transition: all 0.5s;
+  }
+}
+</style>

+ 124 - 0
vue3/项目/vue3-project/src/layout/components/Sidebar/Menus.vue

@@ -0,0 +1,124 @@
+<template>
+  <el-scrollbar class="scroll">
+    <el-menu
+      class="menu"
+      :mode="mode"
+      :collapse="collapse"
+      :uniqueOpened="true"
+      :router="true"
+      :default-active="activePath"
+      :background-color="variables.menuBg"
+      :text-color="variables.menuTextColor"
+      :active-text-color="variables.menuActiveTextColor"
+    >
+      <submenu v-for="menu in menus" :key="menu.url" :menu="menu" />
+    </el-menu>
+  </el-scrollbar>
+</template>
+<script>
+import { computed, defineComponent } from 'vue'
+import Submenu from './Submenu.vue'
+import { useRoute } from 'vue-router'
+import config from './config/menu.module.scss'
+import { storeToRefs } from 'pinia'
+import { useMenus } from '@/pinia/modules/menu'
+
+export default defineComponent({
+  components: {
+    Submenu,
+  },
+  props: {
+    collapse: {
+      type: Boolean,
+      default: false,
+    },
+    mode: {
+      type: String,
+      default: 'vertical',
+    },
+  },
+  setup() {
+    const route = useRoute()
+    const { menus } = storeToRefs(useMenus())
+
+    return {
+      menus,
+      activePath: computed(() => route.path),
+      variables: computed(() => config),
+    }
+  },
+})
+</script>
+<style lang="scss">
+// menu hover
+.el-menu-item,
+.el-sub-menu__title {
+  &:hover {
+    background-color: $menuHover !important;
+  }
+}
+
+.el-sub-menu {
+  .el-menu-item,
+  .el-sub-menu .el-sub-menu__title {
+    background-color: $subMenuBg !important;
+
+    &:hover {
+      background-color: $subMenuHover !important;
+    }
+  }
+}
+.el-menu-item.is-active {
+  background-color: $menuActiveBg !important;
+  &:hover {
+    background-color: $menuActiveBg !important;
+  }
+}
+
+.el-menu--collapse {
+  .el-menu-item.is-active,
+  .el-sub-menu.is-active > .el-sub-menu__title {
+    position: relative;
+    background-color: $collapseMenuActiveBg !important;
+    color: $collapseMenuActiveColor !important;
+    &::before {
+      content: '';
+      position: absolute;
+      left: 0;
+      top: 0;
+      width: $collapseMenuActiveBorderWidth;
+      height: 100%;
+      background-color: $collapseMenuActiveBorderColor;
+    }
+  }
+}
+
+.el-sub-menu__title i {
+  color: $arrowColor;
+}
+
+// 水平菜单
+.el-menu--horizontal {
+  .el-menu-item,
+  .el-sub-menu .el-sub-menu__title {
+    height: $horizontalMenuHeight;
+    line-height: $horizontalMenuHeight;
+    border-bottom: none;
+  }
+  .el-menu-item.is-active,
+  .el-sub-menu.is-active .el-sub-menu__title {
+    border: none;
+  }
+}
+</style>
+<style lang="scss" scoped>
+.scroll {
+  height: auto;
+  flex: 1;
+  overflow-x: hidden;
+  overflow-y: auto;
+  .menu {
+    border: none;
+  }
+}
+</style>

+ 36 - 0
vue3/项目/vue3-project/src/layout/components/Sidebar/Submenu.vue

@@ -0,0 +1,36 @@
+<template>
+  <el-menu-item v-if="!menu.children" :index="menu.url">
+    <item :icon="menu.icon" :title="menu.title" />
+  </el-menu-item>
+  <el-sub-menu v-else :index="menu.url">
+    <template #title>
+      <item :icon="menu.icon" :title="menu.title" />
+    </template>
+    <submenu
+      v-for="submenu in menu.children"
+      :key="submenu.url"
+      :is-nest="true"
+      :menu="submenu"
+    />
+  </el-sub-menu>
+</template>
+<script>
+import { defineComponent } from 'vue'
+import Item from './Item.vue'
+export default defineComponent({
+  name: 'Submenu',
+  components: {
+    Item,
+  },
+  props: {
+    menu: {
+      type: Object,
+      required: true,
+    },
+    isNest: {
+      type: Boolean,
+      default: false,
+    },
+  },
+})
+</script>

+ 5 - 0
vue3/项目/vue3-project/src/layout/components/Sidebar/config/menu.module.scss

@@ -0,0 +1,5 @@
+:export {
+  menuBg: $menuBg;
+  menuTextColor: $menuTextColor;
+  menuActiveTextColor: $menuActiveTextColor;
+}

+ 80 - 0
vue3/项目/vue3-project/src/layout/components/Sidebar/index.vue

@@ -0,0 +1,80 @@
+<template>
+  <div
+    class="left"
+    :class="{ collapse: collapse, mobile: device === 'mobile' }"
+  >
+    <logo />
+    <menus :collapse="collapse" />
+  </div>
+  <div class="mask" @click="closeSidebar"></div>
+</template>
+
+<script>
+import { useApp } from '@/pinia/modules/app'
+import { storeToRefs } from 'pinia'
+import { computed, defineComponent } from 'vue'
+import Logo from './Logo.vue'
+import Menus from './Menus.vue'
+
+export default defineComponent({
+  components: {
+    Logo,
+    Menus,
+  },
+  setup() {
+    const appStore = useApp()
+    const { sidebar, device } = storeToRefs(appStore)
+    const { setCollapse } = appStore
+    const collapse = computed(() => sidebar.value.collapse)
+
+    const closeSidebar = () => {
+      setCollapse(1)
+    }
+
+    return {
+      collapse,
+      device,
+      closeSidebar,
+    }
+  },
+})
+</script>
+
+<style lang="scss" scoped>
+.left {
+  width: 210px;
+  background: $menuBg;
+  transition: all 0.3s;
+  overflow: hidden;
+  display: flex;
+  flex-direction: column;
+  &.collapse {
+    width: 64px;
+    ::v-deep(.brand .title) {
+      display: none;
+    }
+  }
+  &.mobile {
+    height: 100%;
+    position: fixed;
+    left: 0;
+    top: 0;
+    z-index: 10;
+    & + .mask {
+      position: fixed;
+      left: 0;
+      top: 0;
+      right: 0;
+      bottom: 0;
+      background: rgba(0, 0, 0, 0.3);
+      z-index: 9;
+    }
+    &.collapse {
+      transform: translateX(-100%);
+      & + .mask {
+        display: none;
+      }
+    }
+  }
+}
+</style>

+ 103 - 0
vue3/项目/vue3-project/src/layout/components/Tagsbar/hooks/useContextMenu.js

@@ -0,0 +1,103 @@
+import { useTags } from '@/pinia/modules/tags'
+import { onMounted, onBeforeUnmount, reactive, toRefs, nextTick } from 'vue'
+import { useRoute, useRouter } from 'vue-router'
+import { isAffix } from './useTags'
+
+export const useContextMenu = tagList => {
+  const router = useRouter()
+  const route = useRoute()
+
+  const tagsStore = useTags()
+
+  const state = reactive({
+    visible: false,
+    top: 0,
+    left: 0,
+    selectedTag: {},
+    openMenu(tag, e) {
+      state.visible = true
+      state.left = e.clientX
+      state.top = e.clientY
+      state.selectedTag = tag
+    },
+    closeMenu() {
+      state.visible = false
+    },
+    refreshSelectedTag(tag) {
+      tagsStore.deCacheList(tag)
+      const { fullPath } = tag
+      nextTick(() => {
+        router.replace({
+          path: '/redirect' + fullPath,
+        })
+      })
+    },
+    closeTag(tag) {
+      if (isAffix(tag)) return
+
+      const closedTagIndex = tagList.value.findIndex(
+        item => item.fullPath === tag.fullPath
+      )
+      tagsStore.delTag(tag)
+      if (isActive(tag)) {
+        toLastTag(closedTagIndex - 1)
+      }
+    },
+    closeOtherTags() {
+      tagsStore.delOtherTags(state.selectedTag)
+      router.push(state.selectedTag)
+    },
+    closeLeftTags() {
+      state.closeSomeTags('left')
+    },
+    closeRightTags() {
+      state.closeSomeTags('right')
+    },
+    closeSomeTags(direction) {
+      const index = tagList.value.findIndex(
+        item => item.fullPath === state.selectedTag.fullPath
+      )
+
+      if (
+        (direction === 'left' && index <= 0) ||
+        (direction === 'right' && index >= tagList.value.length - 1)
+      ) {
+        return
+      }
+
+      const needToClose =
+        direction === 'left'
+          ? tagList.value.slice(0, index)
+          : tagList.value.slice(index + 1)
+      tagsStore.delSomeTags(needToClose)
+      router.push(state.selectedTag)
+    },
+    closeAllTags() {
+      tagsStore.delAllTags()
+      router.push('/')
+    },
+  })
+
+  const isActive = tag => {
+    return tag.fullPath === route.fullPath
+  }
+
+  const toLastTag = lastTagIndex => {
+    const lastTag = tagList.value[lastTagIndex]
+    if (lastTag) {
+      router.push(lastTag.fullPath)
+    } else {
+      router.push('/')
+    }
+  }
+
+  onMounted(() => {
+    document.addEventListener('click', state.closeMenu)
+  })
+
+  onBeforeUnmount(() => {
+    document.removeEventListener('click', state.closeMenu)
+  })
+
+  return toRefs(state)
+}

+ 54 - 0
vue3/项目/vue3-project/src/layout/components/Tagsbar/hooks/useScrollbar.js

@@ -0,0 +1,54 @@
+import { ref } from 'vue'
+
+export const useScrollbar = tagsItem => {
+  const scrollContainer = ref(null)
+  const scrollLeft = ref(0)
+
+  const doScroll = val => {
+    scrollLeft.value = val
+    scrollContainer.value.setScrollLeft(scrollLeft.value)
+  }
+
+  const handleScroll = e => {
+    const $wrap = scrollContainer.value.wrap$
+    if ($wrap.offsetWidth + scrollLeft.value > $wrap.children[0].scrollWidth) {
+      doScroll($wrap.children[0].scrollWidth - $wrap.offsetWidth)
+      return
+    } else if (scrollLeft.value < 0) {
+      doScroll(0)
+      return
+    }
+    const eventDelta = e.wheelDelta || -e.deltaY
+    doScroll(scrollLeft.value - eventDelta / 4)
+  }
+
+  const moveToTarget = currentTag => {
+    const $wrap = scrollContainer.value.wrap$
+    const tagList = tagsItem.value
+
+    let firstTag = null
+    let lastTag = null
+
+    if (tagList.length > 0) {
+      firstTag = tagList[0]
+      lastTag = tagList[tagList.length - 1]
+    }
+    if (firstTag === currentTag) {
+      doScroll(0)
+    } else if (lastTag === currentTag) {
+      doScroll($wrap.children[0].scrollWidth - $wrap.offsetWidth)
+    } else {
+      const el = currentTag.$el.nextElementSibling
+
+      el.offsetLeft + el.offsetWidth > $wrap.offsetWidth
+        ? doScroll(el.offsetLeft - el.offsetWidth)
+        : doScroll(0)
+    }
+  }
+
+  return {
+    scrollContainer,
+    handleScroll,
+    moveToTarget,
+  }
+}

+ 103 - 0
vue3/项目/vue3-project/src/layout/components/Tagsbar/hooks/useTags.js

@@ -0,0 +1,103 @@
+import { storeToRefs } from 'pinia'
+import { useTags as useTagsbar } from '@/pinia/modules/tags'
+import { useScrollbar } from './useScrollbar'
+import { watch, computed, ref, nextTick, onBeforeMount } from 'vue'
+import { useRouter } from 'vue-router'
+
+export const isAffix = tag => {
+  return !!tag.meta && !!tag.meta.affix
+}
+
+export const useTags = () => {
+  const tagStore = useTagsbar()
+  const { tagList } = storeToRefs(tagStore)
+  const { addTag, delTag, saveActivePosition, updateTagList } = tagStore
+  const router = useRouter()
+  const route = router.currentRoute
+  const routes = computed(() => router.getRoutes())
+
+  const tagsItem = ref([])
+
+  const setItemRef = (i, el) => {
+    tagsItem.value[i] = el
+  }
+
+  const scrollbar = useScrollbar(tagsItem)
+
+  watch(
+    () => tagList.value.length,
+    () => {
+      tagsItem.value = []
+    }
+  )
+
+  const filterAffixTags = routes => {
+    return routes.filter(route => isAffix(route))
+  }
+
+  const initTags = () => {
+    const affixTags = filterAffixTags(routes.value)
+
+    for (const tag of affixTags) {
+      if (tag.name) {
+        addTag(tag)
+      }
+    }
+    // 不在路由中的所有标签,需要删除
+    const noUseTags = tagList.value.filter(tag =>
+      routes.value.every(route => route.name !== tag.name)
+    )
+    noUseTags.forEach(tag => {
+      delTag(tag)
+    })
+  }
+
+  const addTagList = () => {
+    const tag = route.value
+    if (!!tag.name && tag.matched[0].components.default.name === 'layout') {
+      addTag(tag)
+    }
+  }
+
+  const saveTagPosition = tag => {
+    const index = tagList.value.findIndex(
+      item => item.fullPath === tag.fullPath
+    )
+
+    saveActivePosition(Math.max(0, index))
+  }
+
+  const moveToCurrentTag = () => {
+    nextTick(() => {
+      for (const tag of tagsItem.value) {
+        if (!!tag && tag.to.path === route.value.path) {
+          scrollbar.moveToTarget(tag)
+
+          if (tag.to.fullPath !== route.value.fullPath) {
+            updateTagList(route.value)
+          }
+          break
+        }
+      }
+    })
+  }
+
+  onBeforeMount(() => {
+    initTags()
+    addTagList()
+    moveToCurrentTag()
+  })
+
+  watch(route, (newRoute, oldRoute) => {
+    saveTagPosition(oldRoute) // 保存标签的位置
+    addTagList()
+    moveToCurrentTag()
+  })
+
+  return {
+    tagList,
+    setItemRef,
+    isAffix,
+    ...scrollbar,
+  }
+}

+ 180 - 0
vue3/项目/vue3-project/src/layout/components/Tagsbar/index.vue

@@ -0,0 +1,180 @@
+<template>
+  <div class="tags-container" :class="{ hide: !isTagsbarShow }">
+    <el-scrollbar
+      ref="scrollContainer"
+      :vertical="false"
+      class="scroll-container"
+      @wheel.prevent="onScroll"
+    >
+      <router-link
+        v-for="(tag, i) in tagList"
+        :key="tag.fullPath"
+        :to="tag"
+        :ref="el => setItemRef(i, el)"
+        custom
+        v-slot="{ navigate, isExactActive }"
+      >
+        <div
+          class="tags-item"
+          :class="isExactActive ? 'active' : ''"
+          @click="navigate"
+          @click.middle="closeTag(tag)"
+          @contextmenu.prevent="openMenu(tag, $event)"
+        >
+          <span class="title">{{ $t(tag.title) }}</span>
+
+          <el-icon
+            v-if="!isAffix(tag)"
+            class="el-icon-close"
+            @click.prevent.stop="closeTag(tag)"
+          >
+            <Close />
+          </el-icon>
+        </div>
+      </router-link>
+    </el-scrollbar>
+  </div>
+  <ul
+    v-show="visible"
+    :style="{ left: left + 'px', top: top + 'px' }"
+    class="contextmenu"
+  >
+    <li @click="refreshSelectedTag(selectedTag)">{{ $t('tags.refresh') }}</li>
+    <li v-if="!isAffix(selectedTag)" @click="closeTag(selectedTag)">
+      {{ $t('tags.close') }}
+    </li>
+    <li @click="closeOtherTags">{{ $t('tags.other') }}</li>
+    <li @click="closeLeftTags">{{ $t('tags.left') }}</li>
+    <li @click="closeRightTags">{{ $t('tags.right') }}</li>
+    <li @click="closeAllTags">{{ $t('tags.all') }}</li>
+  </ul>
+</template>
+
+<script>
+import { defineComponent, computed, getCurrentInstance } from 'vue'
+import { useTags } from './hooks/useTags'
+import { useContextMenu } from './hooks/useContextMenu'
+import { useLayoutsettings } from '@/pinia/modules/layoutSettings'
+
+export default defineComponent({
+  name: 'Tagsbar',
+  mounted() {
+    const instance = getCurrentInstance()
+    instance.appContext.config.globalProperties.$tagsbar = this
+  },
+  setup() {
+    const defaultSettings = useLayoutsettings()
+    const isTagsbarShow = computed(() => defaultSettings.tagsbar.isShow)
+
+    const tags = useTags()
+    const contextMenu = useContextMenu(tags.tagList)
+
+    const onScroll = e => {
+      tags.handleScroll(e)
+      contextMenu.closeMenu.value()
+    }
+
+    return {
+      isTagsbarShow,
+      onScroll,
+      ...tags,
+      ...contextMenu,
+    }
+  },
+})
+</script>
+
+<style lang="scss" scoped>
+.tags-container {
+  height: 32px;
+  width: 100%;
+  background: #fff;
+  border-bottom: 1px solid #e0e4ef;
+  &.hide {
+    display: none;
+  }
+  .scroll-container {
+    white-space: nowrap;
+    overflow: hidden;
+    ::v-deep(.el-scrollbar__bar) {
+      bottom: 0px;
+    }
+  }
+
+  .tags-item {
+    display: inline-block;
+    height: 32px;
+    line-height: 32px;
+    box-sizing: border-box;
+    border-left: 1px solid #e6e6e6;
+    border-right: 1px solid #e6e6e6;
+    color: #5c5c5c;
+    background: #fff;
+    padding: 0 8px;
+    font-size: 12px;
+    margin-left: -1px;
+    vertical-align: bottom;
+    cursor: pointer;
+    &:first-of-type {
+      margin-left: 15px;
+    }
+    &:last-of-type {
+      margin-right: 15px;
+    }
+    &.active {
+      color: #303133;
+      background: #f5f5f5;
+    }
+    .title {
+      display: inline-block;
+      vertical-align: top;
+      max-width: 200px;
+      overflow: hidden;
+      white-space: nowrap;
+      text-overflow: ellipsis;
+    }
+    .el-icon-close {
+      color: #5c5c5c;
+      margin-left: 8px;
+      width: 16px;
+      height: 16px;
+      vertical-align: -2px;
+      border-radius: 50%;
+      text-align: center;
+      transition: all 0.3s cubic-bezier(0.645, 0.045, 0.355, 1);
+      transform-origin: 100% 50%;
+      &:before {
+        transform: scale(0.8);
+        display: inline-block;
+        vertical-align: -2px;
+      }
+      &:hover {
+        background-color: #333;
+        color: #fff;
+      }
+    }
+  }
+}
+.contextmenu {
+  margin: 0;
+  background: #fff;
+  z-index: 3000;
+  position: fixed;
+  list-style-type: none;
+  padding: 5px 0;
+  border-radius: 4px;
+  font-size: 12px;
+  font-weight: 400;
+  color: #333;
+  box-shadow: 2px 2px 3px 0 rgba(0, 0, 0, 0.3);
+  white-space: nowrap;
+  li {
+    margin: 0;
+    padding: 8px 16px;
+    cursor: pointer;
+    &:hover {
+      background: #eee;
+    }
+  }
+}
+</style>

+ 124 - 0
vue3/项目/vue3-project/src/layout/components/Topbar/Breadcrumbs.vue

@@ -0,0 +1,124 @@
+<template>
+  <el-breadcrumb
+    separator-class="el-icon-arrow-right"
+    class="breadcrumb"
+    :class="{
+      mobile: device === 'mobile',
+      show: isHorizontalMenu,
+      hide: breadcrumbs.length <= 1,
+    }"
+  >
+    <el-breadcrumb-item
+      v-for="(item, index) in breadcrumbs"
+      :key="index"
+      :class="{ no_link: index === breadcrumbs.length - 1 }"
+      :to="index < breadcrumbs.length - 1 ? item.path : ''"
+    >
+      {{ $t(item.meta.title) }}
+    </el-breadcrumb-item>
+  </el-breadcrumb>
+</template>
+<script>
+import { useApp } from '@/pinia/modules/app'
+import { useLayoutsettings } from '@/pinia/modules/layoutSettings'
+import { storeToRefs } from 'pinia'
+import {
+  defineComponent,
+  computed,
+  ref,
+  onBeforeMount,
+  watch,
+  getCurrentInstance,
+} from 'vue'
+import { useRouter } from 'vue-router'
+
+export default defineComponent({
+  setup(props, { emit }) {
+    const { proxy } = getCurrentInstance()
+    const { device } = storeToRefs(useApp())
+    const router = useRouter()
+    const route = router.currentRoute // 这里不使用useRoute获取当前路由,否则下面watch监听路由的时候会有警告
+    const breadcrumbs = ref([])
+    const defaultSettings = useLayoutsettings()
+    const isHorizontalMenu = computed(
+      () => defaultSettings.menus.mode === 'horizontal'
+    )
+
+    const getBreadcrumbs = route => {
+      const home = [{ path: '/', meta: { title: proxy.$t('menu.homepage') } }]
+      if (route.name === 'home') {
+        return home
+      } else {
+        const matched = route.matched.filter(
+          item => !!item.meta && !!item.meta.title
+        )
+
+        return [...home, ...matched]
+      }
+    }
+
+    onBeforeMount(() => {
+      breadcrumbs.value = getBreadcrumbs(route.value)
+    })
+
+    watch(
+      route,
+      newRoute => {
+        route.value.meta.truetitle = proxy.$t(route.value.meta.title)
+        breadcrumbs.value = getBreadcrumbs(newRoute)
+        emit('on-breadcrumbs-change', breadcrumbs.value.length > 1)
+      },
+      {
+        immediate: true,
+      }
+    )
+
+    return {
+      device,
+      breadcrumbs,
+      isHorizontalMenu,
+    }
+  },
+})
+</script>
+
+<style lang="scss" scoped>
+.breadcrumb {
+  margin-left: 10px;
+  white-space: nowrap;
+  overflow: hidden;
+  text-overflow: ellipsis;
+  ::v-deep(a),
+  ::v-deep(.is-link) {
+    font-weight: normal;
+  }
+  // ::v-deep(.el-breadcrumb__item) {
+  //   float: none;
+  // }
+  .no_link {
+    ::v-deep(.el-breadcrumb__inner) {
+      color: #97a8be !important;
+    }
+  }
+  &.mobile {
+    display: none;
+  }
+  &.show {
+    display: block;
+    margin: 0;
+    padding: 16px;
+    background: #f5f5f5;
+  }
+  &.hide {
+    display: none;
+  }
+}
+</style>
+<style lang="scss">
+.el-breadcrumb__inner {
+  &.is-link,
+  a {
+    color: #5c5c5c;
+  }
+}
+</style>

+ 48 - 0
vue3/项目/vue3-project/src/layout/components/Topbar/ChangeLang.vue

@@ -0,0 +1,48 @@
+<template>
+  <el-dropdown trigger="hover">
+    <div class="change-lang">
+      <svg-icon name="language" class="icon"></svg-icon>
+    </div>
+    <template #dropdown>
+      <el-dropdown-menu>
+        <el-dropdown-item
+          @click="changeLang(item.value)"
+          v-for="item in langlist"
+          :key="item.value"
+        >
+          {{ item.name }}
+        </el-dropdown-item>
+      </el-dropdown-menu>
+    </template>
+  </el-dropdown>
+</template>
+<script setup>
+import useLang from '@/i18n/useLang'
+const langlist = [
+  {
+    name: '简体中文',
+    value: 'zh-cn',
+  },
+  {
+    name: 'English',
+    value: 'en',
+  },
+]
+const { changeLang } = useLang()
+</script>
+
+<style lang="scss" scoped>
+.change-lang {
+  padding: 0 16px;
+  height: 48px;
+  cursor: pointer;
+  display: flex;
+  align-items: center;
+  &:hover {
+    background: #f5f5f5;
+  }
+  .icon {
+    font-size: 18px;
+  }
+}
+</style>

+ 40 - 0
vue3/项目/vue3-project/src/layout/components/Topbar/Hamburger.vue

@@ -0,0 +1,40 @@
+<template>
+  <el-icon
+    :size="20"
+    class="fold-btn"
+    :class="{ collapse: collapse }"
+    @click="handleToggleMenu"
+  >
+    <Fold />
+  </el-icon>
+</template>
+<script>
+import { useApp } from '@/pinia/modules/app'
+import { storeToRefs } from 'pinia'
+import { computed, defineComponent } from 'vue'
+
+export default defineComponent({
+  setup() {
+    const appStore = useApp()
+    const { sidebar } = storeToRefs(appStore)
+    const { setCollapse } = appStore
+    const handleToggleMenu = () => {
+      setCollapse(+!sidebar.value.collapse)
+    }
+    return {
+      collapse: computed(() => sidebar.value.collapse),
+      handleToggleMenu,
+    }
+  },
+})
+</script>
+<style lang="scss" scoped>
+.fold-btn {
+  line-height: 48px;
+  padding: 0 10px;
+  cursor: pointer;
+  &.collapse {
+    transform: scale(-1, 1);
+  }
+}
+</style>

+ 88 - 0
vue3/项目/vue3-project/src/layout/components/Topbar/LockModal.vue

@@ -0,0 +1,88 @@
+<template>
+  <el-dropdown-item @click="dialogVisible = true">
+    {{ $t('topbar.lock-title') }}
+  </el-dropdown-item>
+  <el-dialog
+    :title="$t('topbar.lock-title')"
+    v-model="dialogVisible"
+    width="640px"
+    custom-class="lock-modal"
+    append-to-body
+  >
+    <Avatar />
+    <el-form
+      :model="lockModel"
+      :rules="lockRules"
+      ref="lockForm"
+      label-width="90px"
+    >
+      <el-form-item :label="$t('topbar.lock-password')" prop="password">
+        <el-input
+          type="password"
+          v-model.trim="lockModel.password"
+          autocomplete="off"
+          @keyup.enter="submitForm"
+        ></el-input>
+      </el-form-item>
+      <el-form-item>
+        <el-button class="submit-btn" type="primary" @click="submitForm">
+          {{ $t('topbar.lock-title') }}
+        </el-button>
+      </el-form-item>
+    </el-form>
+  </el-dialog>
+</template>
+
+<script>
+import { defineComponent, getCurrentInstance, reactive, ref } from 'vue'
+import Avatar from '@/components/Avatar/index.vue'
+import { useRouter } from 'vue-router'
+import { useApp } from '@/pinia/modules/app'
+
+export default defineComponent({
+  components: {
+    Avatar,
+  },
+  setup() {
+    const { proxy } = getCurrentInstance()
+    const router = useRouter()
+    const dialogVisible = ref(false)
+    const lockForm = ref(null)
+    const lockModel = reactive({
+      password: '',
+    })
+    const lockRules = reactive({
+      password: [
+        { required: true, message: proxy.$t('topbar.lock-rules-password') },
+      ],
+    })
+    const submitForm = () => {
+      lockForm.value.validate(valid => {
+        if (!valid) {
+          return false
+        }
+
+        // 对密码加密并跟token保存在一起
+        useApp().setScreenCode(lockModel.password)
+
+        // 跳转到锁屏页面
+        router.push('/lock?redirect=' + router.currentRoute.value.fullPath)
+      })
+    }
+
+    return {
+      dialogVisible,
+      lockForm,
+      lockModel,
+      lockRules,
+      submitForm,
+    }
+  },
+})
+</script>
+
+<style lang="scss">
+.lock-modal[aria-modal] {
+  max-width: 90%;
+}
+</style>

+ 80 - 0
vue3/项目/vue3-project/src/layout/components/Topbar/Userinfo.vue

@@ -0,0 +1,80 @@
+<template>
+  <el-dropdown trigger="hover">
+    <div class="userinfo">
+      <template v-if="!userinfo">
+        <i class="el-icon-user" />
+        admin
+      </template>
+      <template v-else>
+        <img class="avatar" :src="userinfo.avatar" />
+        {{ userinfo.name }}
+      </template>
+    </div>
+    <template #dropdown>
+      <el-dropdown-menu>
+        <el-dropdown-item>{{ $t('topbar.center') }}</el-dropdown-item>
+        <el-dropdown-item>{{ $t('topbar.password') }}</el-dropdown-item>
+        <lock-modal />
+        <el-dropdown-item @click="logout">
+          {{ $t('topbar.logout') }}
+        </el-dropdown-item>
+      </el-dropdown-menu>
+    </template>
+  </el-dropdown>
+</template>
+<script>
+import { defineComponent } from 'vue'
+import { useRouter } from 'vue-router'
+import { useUserinfo } from '@/components/Avatar/hooks/useUserinfo'
+import LockModal from './LockModal.vue'
+import { useApp } from '@/pinia/modules/app'
+import { Logout } from '@/api/login'
+
+export default defineComponent({
+  components: {
+    LockModal,
+  },
+  setup: function() {
+    const router = useRouter();
+
+    const { userinfo } = useUserinfo();
+
+    // 退出
+    const logout = async () => {
+      //发送请求到后端
+      await Logout();
+      // 清除token
+      useApp().clearToken();
+      router.push("/login");
+    };
+
+    return {
+      userinfo,
+      logout
+    };
+  }
+})
+</script>
+
+<style lang="scss" scoped>
+.userinfo {
+  padding: 0 16px;
+  line-height: 48px;
+  cursor: pointer;
+  display: flex;
+  align-items: center;
+  &:hover {
+    background: #f5f5f5;
+  }
+  .el-icon-user {
+    font-size: 20px;
+    margin-right: 8px;
+  }
+  .avatar {
+    margin-right: 8px;
+    width: 32px;
+    height: 32px;
+    border-radius: 50%;
+  }
+}
+</style>

+ 104 - 0
vue3/项目/vue3-project/src/layout/components/Topbar/index.vue

@@ -0,0 +1,104 @@
+<template>
+  <div class="header" :class="{ 'no-border': isHorizontalMenu }">
+    <div class="navigation">
+      <logo
+        v-if="isShowLogo"
+        class="mobile"
+        :class="{ 'show-title': isHorizontalMenu }"
+      />
+      <hamburger v-if="isShowHamburger" />
+      <breadcrumbs v-if="isShowBreadcrumbs" />
+    </div>
+    <div class="action">
+      <error-log />
+      <userinfo />
+      <change-lang />
+    </div>
+  </div>
+</template>
+<script>
+import { defineComponent, computed } from 'vue'
+import Logo from '@/layout/components/Sidebar/Logo.vue'
+import Hamburger from './Hamburger.vue'
+import Breadcrumbs from './Breadcrumbs.vue'
+import Userinfo from './Userinfo.vue'
+import ChangeLang from './ChangeLang.vue'
+import ErrorLog from '@/components/ErrorLog/index.vue'
+import { useLayoutsettings } from '@/pinia/modules/layoutSettings'
+import { storeToRefs } from 'pinia'
+import { useApp } from '@/pinia/modules/app'
+
+export default defineComponent({
+  components: {
+    Logo,
+    Hamburger,
+    Breadcrumbs,
+    Userinfo,
+    ChangeLang,
+    ErrorLog,
+  },
+  setup() {
+    const defaultSettings = useLayoutsettings()
+
+    const { device } = storeToRefs(useApp())
+
+    const isHorizontalMenu = computed(
+      () => defaultSettings.menus.mode === 'horizontal'
+    )
+
+    const isShowLogo = computed(
+      () => isHorizontalMenu.value || device.value === 'mobile'
+    )
+
+    const isShowHamburger = computed(() => !isHorizontalMenu.value)
+
+    const isShowBreadcrumbs = computed(
+      () => defaultSettings.breadcrumbs.isShow && !isHorizontalMenu.value
+    )
+
+    return {
+      device,
+      isHorizontalMenu,
+      isShowLogo,
+      isShowHamburger,
+      isShowBreadcrumbs,
+    }
+  },
+})
+</script>
+<style lang="scss" scoped>
+.header {
+  height: 48px;
+  border-bottom: 1px solid #e0e4ef;
+  display: flex;
+  justify-content: space-between;
+  &.no-border {
+    border: none;
+  }
+  .navigation {
+    display: flex;
+    align-items: center;
+    overflow: hidden;
+  }
+  .action {
+    display: flex;
+    align-items: center;
+  }
+}
+.mobile {
+  padding-right: 0;
+  ::v-deep(.logo) {
+    max-width: 24px;
+    max-height: 24px;
+  }
+  ::v-deep(.title) {
+    display: none;
+  }
+}
+.show-title {
+  ::v-deep(.title) {
+    display: block;
+    color: #333;
+  }
+}
+</style>

+ 34 - 0
vue3/项目/vue3-project/src/layout/hooks/useResizeHandler.js

@@ -0,0 +1,34 @@
+import { storeToRefs } from 'pinia'
+import { useApp } from '@/pinia/modules/app'
+import { onBeforeMount, onBeforeUnmount, computed } from 'vue'
+
+const WIDTH = 768
+export const useResizeHandler = () => {
+  const appStore = useApp()
+  const { sidebar } = storeToRefs(appStore)
+  const { setDevice, setCollapse } = appStore
+  const collapse = computed(() => sidebar.value.collapse)
+
+  const isMobile = () => {
+    return window.innerWidth < WIDTH
+  }
+
+  const resizeHandler = () => {
+    if (isMobile()) {
+      setDevice('mobile')
+      setCollapse(1)
+    } else {
+      setDevice('desktop')
+      setCollapse(collapse.value)
+    }
+  }
+
+  onBeforeMount(() => {
+    resizeHandler()
+    window.addEventListener('resize', resizeHandler)
+  })
+
+  onBeforeUnmount(() => {
+    window.removeEventListener('resize', resizeHandler)
+  })
+}

+ 102 - 0
vue3/项目/vue3-project/src/layout/index.vue

@@ -0,0 +1,102 @@
+<template>
+  <div class="wrapper" :class="{ fluid: isFluid }">
+    <sidebar v-if="isMenusShow && !isHorizontalMenu" />
+    <div class="right" :class="{ flex: isTopbarFixed }">
+      <div class="top">
+        <topbar />
+        <menus mode="horizontal" v-if="isMenusShow && isHorizontalMenu" />
+        <tagsbar />
+        <breadcrumbs
+          v-if="isBreadcrumbsShow"
+          @on-breadcrumbs-change="handleBreadcrumbsChange"
+        />
+      </div>
+      <div class="main" :class="{ pt0: isBreadcrumbsShow && paddingFlag }">
+        <Content />
+      </div>
+    </div>
+  </div>
+</template>
+<script>
+import { defineComponent, ref, computed } from 'vue'
+import Sidebar from './components/Sidebar/index.vue'
+import Topbar from './components/Topbar/index.vue'
+import Menus from './components/Sidebar/Menus.vue'
+import Tagsbar from './components/Tagsbar/index.vue'
+import Breadcrumbs from './components/Topbar/Breadcrumbs.vue'
+import Content from './components/Content/index.vue'
+import { useResizeHandler } from './hooks/useResizeHandler'
+import { storeToRefs } from 'pinia'
+import { useLayoutsettings } from '@/pinia/modules/layoutSettings'
+
+export default defineComponent({
+  name: 'layout',
+  components: {
+    Sidebar,
+    Topbar,
+    Menus,
+    Tagsbar,
+    Breadcrumbs,
+    Content,
+  },
+  setup() {
+    useResizeHandler()
+    const defaultSettings = useLayoutsettings()
+    const isFluid = defaultSettings.layout.isFluid
+    const isTopbarFixed = defaultSettings.topbar.isFixed
+    const isMenusShow = defaultSettings.menus.isShow
+    const isHorizontalMenu = defaultSettings.menus.mode === 'horizontal'
+    const isBreadcrumbsShow = computed(
+      () => isHorizontalMenu && defaultSettings.breadcrumbs.isShow
+    )
+    const paddingFlag = ref(true)
+    const handleBreadcrumbsChange = boo => {
+      paddingFlag.value = boo
+    }
+
+    return {
+      isFluid,
+      isTopbarFixed,
+      isMenusShow,
+      isHorizontalMenu,
+      isBreadcrumbsShow,
+      paddingFlag,
+      handleBreadcrumbsChange,
+    }
+  },
+})
+</script>
+
+<style lang="scss" scoped>
+.wrapper {
+  display: flex;
+  margin: 0 auto;
+  width: 1440px;
+  height: 100%;
+  &.fluid {
+    width: 100%;
+  }
+
+  .right {
+    flex: 1;
+    overflow: auto;
+    &.flex {
+      overflow: hidden;
+      display: flex;
+      flex-direction: column;
+    }
+    .top {
+      background: #fff;
+    }
+    .main {
+      flex: 1;
+      background: #f5f5f5;
+      padding: 16px;
+      overflow: auto;
+      &.pt0 {
+        padding-top: 0;
+      }
+    }
+  }
+}
+</style>

+ 51 - 0
vue3/项目/vue3-project/src/main.js

@@ -0,0 +1,51 @@
+import { createApp } from 'vue'
+import App from './App.vue'
+
+const app = createApp(App)
+
+// 引入element-plus
+import ElementPlus from 'element-plus'
+import './assets/style/element-variables.scss'
+
+// 国际化
+import i18n from '@/i18n'
+
+// 全局注册element-plus/icons-vue
+import * as ICONS from '@element-plus/icons-vue'
+Object.entries(ICONS).forEach(([key, component]) => {
+  // app.component(key === 'PieChart' ? 'PieChartIcon' : key, component)
+  app.component(key, component)
+})
+
+// 引入路由
+import router from './router'
+
+// 引入pinia
+import pinia from './pinia'
+
+// 权限控制
+import './permission'
+
+// 引入svg图标注册脚本
+import 'vite-plugin-svg-icons/register'
+
+// 注册全局组件
+import * as Components from './global-components'
+Object.entries(Components).forEach(([key, component]) => {
+  app.component(key, component)
+})
+
+// 注册自定义指令
+import * as Directives from '@/directive'
+Object.values(Directives).forEach(fn => fn(app))
+
+// 错误日志
+import useErrorHandler from './error-log'
+useErrorHandler(app)
+
+app
+  .use(i18n)
+  .use(ElementPlus)
+  .use(pinia)
+  .use(router)
+  .mount('#app')

+ 93 - 0
vue3/项目/vue3-project/src/permission.js

@@ -0,0 +1,93 @@
+import { ElLoading } from 'element-plus'
+import router from '@/router'
+// import store from '@/store'
+// import { TOKEN } from '@/store/modules/app' // TOKEN变量名
+import { TOKEN } from '@/pinia/modules/app' // TOKEN变量名
+import { nextTick } from 'vue'
+import { useApp } from './pinia/modules/app'
+import { useAccount } from './pinia/modules/account'
+import { useMenus } from './pinia/modules/menu'
+
+const getPageTitle = title => {
+  const { title: appTitle } = useApp()
+  if (title) {
+    return `${title} - ${appTitle}`
+  }
+  return appTitle
+}
+
+// 白名单,里面是路由对象的name
+const WhiteList = ['login', 'lock']
+
+let loadingInstance = null
+
+// vue-router4的路由守卫不再是通过next放行,而是通过return返回true或false或者一个路由地址
+router.beforeEach(async to => {
+  loadingInstance = ElLoading.service({
+    lock: true,
+    // text: '正在加载数据,请稍候~',
+    background: 'rgba(0, 0, 0, 0.7)',
+  })
+
+  if (WhiteList.includes(to.name)) {
+    return true
+  }
+  if (!window.localStorage[TOKEN]) {
+    return {
+      name: 'login',
+      query: {
+        redirect: to.fullPath, // redirect是指登录之后可以跳回到redirect指定的页面
+      },
+      replace: true,
+    }
+  } else {
+    const { userinfo, getUserinfo } = useAccount()
+    // 获取用户角色信息,根据角色判断权限
+    if (!userinfo) {
+      try {
+        // 获取用户信息
+        await getUserinfo()
+      } catch (err) {
+        loadingInstance.close()
+        return false
+      }
+
+      return to.fullPath
+    }
+
+    // 生成菜单(如果你的项目有动态菜单,在此处会添加动态路由)
+    const { menus, generateMenus } = useMenus()
+    if (menus.length <= 0) {
+      try {
+        await generateMenus()
+        return to.fullPath // 添加动态路由后,必须加这一句触发重定向,否则会404
+      } catch (err) {
+        loadingInstance.close()
+        return false
+      }
+    }
+
+    // 判断是否处于锁屏状态
+    if (to.name !== 'lock') {
+      const { authorization } = useApp()
+      if (!!authorization && !!authorization.screenCode) {
+        return {
+          name: 'lock',
+          query: {
+            redirect: to.path,
+          },
+          replace: true,
+        }
+      }
+    }
+  }
+})
+
+router.afterEach(to => {
+  loadingInstance.close()
+  if (router.currentRoute.value.name === to.name) {
+    nextTick(() => {
+      document.title = getPageTitle(!!to.meta && to.meta.truetitle)
+    })
+  }
+})

+ 5 - 0
vue3/项目/vue3-project/src/pinia/index.js

@@ -0,0 +1,5 @@
+import { createPinia } from 'pinia'
+
+const pinia = createPinia()
+
+export default pinia

+ 23 - 0
vue3/项目/vue3-project/src/pinia/modules/account.js

@@ -0,0 +1,23 @@
+import { defineStore } from 'pinia'
+import { GetUserinfo } from '@/api/login'
+
+export const useAccount = defineStore('account', {
+  state: () => ({
+    userinfo: null,
+    permissionList: [],
+  }),
+  actions: {
+    // 清除用户信息
+    clearUserinfo() {
+      this.userinfo = null
+    },
+    // 获取用户信息
+    async getUserinfo() {
+      const { code, data } = await GetUserinfo()
+      if (+code === 200) {
+        this.userinfo = data
+        return Promise.resolve(data)
+      }
+    },
+  },
+})

+ 82 - 0
vue3/项目/vue3-project/src/pinia/modules/app.js

@@ -0,0 +1,82 @@
+import { defineStore } from 'pinia'
+import { getItem, setItem, removeItem } from '@/utils/storage' //getItem和setItem是封装的操作localStorage的方法
+import { AesEncryption } from '@/utils/encrypt'
+import { toRaw } from 'vue'
+import { useAccount } from './account'
+import { useTags } from './tags'
+import { useMenus } from './menu'
+export const TOKEN = 'VEA-TOKEN'
+const COLLAPSE = 'VEA-COLLAPSE'
+
+export const useApp = defineStore('app', {
+  state: () => ({
+    title: 'Vue3 Element Admin',
+    authorization: getItem(TOKEN),
+    sidebar: {
+      collapse: getItem(COLLAPSE),
+    },
+    device: 'desktop',
+  }),
+  actions: {
+    setCollapse(data) {
+      this.sidebar.collapse = data
+      // 保存到localStorage
+      setItem(COLLAPSE, data)
+    },
+    clearCollapse() {
+      this.sidebar.collapse = ''
+      removeItem(COLLAPSE)
+    },
+    setDevice(device) {
+      this.device = device
+    },
+    setToken(data) {
+      this.authorization = data
+      // 保存到localStorage
+      setItem(TOKEN, data)
+    },
+    initToken(data) {
+      this.clearToken()
+      this.setToken(data)
+    },
+    clearToken() {
+      // 清除token
+      this.authorization = ''
+      removeItem(TOKEN)
+      // 清除用户信息
+      useAccount().clearUserinfo()
+      // 清除标签栏
+      useTags().clearAllTags()
+      // 清空menus
+      useMenus().setMenus([])
+    },
+    setScreenCode(password) {
+      const authorization = toRaw(this.authorization)
+
+      if (!password) {
+        try {
+          delete authorization.screenCode
+        } catch (err) {
+          console.log(err)
+        }
+
+        this.authorization = authorization
+        // 保存到localStorage
+        setItem(TOKEN, authorization)
+
+        return
+      }
+
+      // 对密码加密
+      const screenCode = new AesEncryption().encryptByAES(password)
+
+      const res = {
+        ...authorization,
+        screenCode,
+      }
+      this.authorization = res
+      // 保存到localStorage
+      setItem(TOKEN, res)
+    },
+  },
+})

+ 18 - 0
vue3/项目/vue3-project/src/pinia/modules/errorLog.js

@@ -0,0 +1,18 @@
+import { defineStore } from 'pinia'
+
+export const useErrorlog = defineStore('errorLog', {
+  state: () => ({
+    logs: [],
+  }),
+  actions: {
+    addErrorLog(log) {
+      // 可以根据需要将错误上报给服务器
+      // ....code.......
+
+      this.logs.push(log)
+    },
+    clearErrorLog() {
+      this.logs.splice(0)
+    },
+  },
+})

+ 15 - 0
vue3/项目/vue3-project/src/pinia/modules/layoutSettings.js

@@ -0,0 +1,15 @@
+import { defineStore } from 'pinia'
+import { getItem, setItem } from '@/utils/storage' //getItem和setItem是封装的操作localStorage的方法
+import defaultSettings from '@/default-settings'
+
+export const useLayoutsettings = defineStore('layoutSettings', {
+  state: () => getItem('defaultSettings') || defaultSettings,
+  actions: {
+    saveSettings(data) {
+      Object.entries(data).forEach(([key, value]) => {
+        this[key] = value
+      })
+      setItem('defaultSettings', data)
+    },
+  },
+})

+ 101 - 0
vue3/项目/vue3-project/src/pinia/modules/menu.js

@@ -0,0 +1,101 @@
+import { defineStore } from 'pinia'
+import { fixedRoutes, asyncRoutes } from '@/router'
+import { GetMenus } from '@/api/menu'
+import router from '@/router'
+import { ref } from 'vue'
+
+export const useMenus = defineStore('menu', () => {
+  const generateUrl = (path, parentPath) => {
+    return path.startsWith('/')
+      ? path
+      : path
+      ? `${parentPath}/${path}`
+      : parentPath
+  }
+
+  const getFilterRoutes = (targetRoutes, ajaxRoutes) => {
+    const filterRoutes = []
+
+    ajaxRoutes.forEach(item => {
+      const target = targetRoutes.find(target => target.name === item.name)
+
+      if (target) {
+        const { children: targetChildren, ...rest } = target
+        const route = {
+          ...rest,
+        }
+
+        if (item.children) {
+          route.children = getFilterRoutes(targetChildren, item.children)
+        }
+
+        filterRoutes.push(route)
+      }
+    })
+
+    return filterRoutes
+  }
+
+  const getFilterMenus = (arr, parentPath = '') => {
+    const menus = []
+
+    arr.forEach(item => {
+      if (!item.hidden) {
+        const menu = {
+          url: generateUrl(item.path, parentPath),
+          title: item.meta.title,
+          icon: item.icon,
+        }
+        if (item.children) {
+          if (item.children.filter(child => !child.hidden).length <= 1) {
+            menu.url = generateUrl(item.children[0].path, menu.url)
+          } else {
+            menu.children = getFilterMenus(item.children, menu.url)
+          }
+        }
+        menus.push(menu)
+      }
+    })
+
+    return menus
+  }
+
+  const menus = ref([])
+  const setMenus = data => {
+    menus.value = data
+  }
+  const generateMenus = async () => {
+    // // 方式一:只有固定菜单
+    //const menus = getFilterMenus(fixedRoutes)
+    //commit('SET_MENUS', menus)
+    //console.log("/////////////////////////////////")
+    //console.log(menus)
+    //setMenus(menus)
+
+    //方式二:有动态菜单
+    //从后台获取菜单
+    const { code, data } = await GetMenus()
+
+    if (+code === 200) {
+      // 添加路由之前先删除所有动态路由
+      asyncRoutes.forEach(item => {
+        router.removeRoute(item.name)
+      })
+      // 过滤出需要添加的动态路由
+      const filterRoutes = getFilterRoutes(asyncRoutes, data)
+
+      filterRoutes.forEach(route => router.addRoute(route))
+      console.log(JSON.stringify(filterRoutes))
+      console.log(JSON.stringify(fixedRoutes))
+      // 生成菜单
+      const menus = getFilterMenus([...fixedRoutes, ...filterRoutes])
+
+      setMenus(menus)
+    }
+  }
+  return {
+    menus,
+    setMenus,
+    generateMenus,
+  }
+})

+ 102 - 0
vue3/项目/vue3-project/src/pinia/modules/tags.js

@@ -0,0 +1,102 @@
+import { defineStore } from 'pinia'
+import { getItem, setItem, removeItem } from '@/utils/storage' //getItem和setItem是封装的操作localStorage的方法
+const TAGLIST = 'VEA-TAGLIST'
+
+export const useTags = defineStore('tags', {
+  state: () => ({
+    tagList: getItem(TAGLIST) || [],
+    cacheList: [],
+    activePosition: -1,
+  }),
+  actions: {
+    saveActivePosition(index) {
+      this.activePosition = index
+    },
+    addTag({ path, fullPath, name, meta, params, query }) {
+      if (this.tagList.some(v => v.path === path)) return false
+      // 添加tagList
+      const target = Object.assign(
+        {},
+        { path, fullPath, name, meta, params, query },
+        {
+          title: meta.title || '未命名',
+          fullPath: fullPath || path,
+        }
+      )
+      if (this.activePosition === -1) {
+        if (name === 'home') {
+          this.tagList.unshift(target)
+        } else {
+          this.tagList.push(target)
+        }
+      } else {
+        this.tagList.splice(this.activePosition + 1, 0, target)
+      }
+      // 保存到localStorage
+      setItem(TAGLIST, this.tagList)
+
+      // 添加cacheList
+      if (this.cacheList.includes(name)) return
+      if (!meta.noCache) {
+        this.cacheList.push(name)
+      }
+    },
+    deTagList(tag) {
+      // 删除tagList
+      this.tagList = this.tagList.filter(v => v.path !== tag.path)
+      // 保存到localStorage
+      setItem(TAGLIST, this.tagList)
+    },
+    deCacheList(tag) {
+      // 删除cacheList
+      this.cacheList = this.cacheList.filter(v => v !== tag.name)
+    },
+    delTag(tag) {
+      // 删除tagList
+      this.deTagList(tag)
+
+      // 删除cacheList
+      this.deCacheList(tag)
+    },
+    delOtherTags(tag) {
+      this.tagList = this.tagList.filter(
+        v => !!v.meta.affix || v.path === tag.path
+      )
+      // 保存到localStorage
+      setItem(TAGLIST, this.tagList)
+
+      this.cacheList = this.cacheList.filter(v => v === tag.name)
+    },
+    delSomeTags(tags) {
+      this.tagList = this.tagList.filter(
+        v => !!v.meta.affix || tags.every(tag => tag.path !== v.path)
+      )
+      // 保存到localStorage
+      setItem(TAGLIST, this.tagList)
+
+      this.cacheList = this.cacheList.filter(v =>
+        tags.every(tag => tag.name !== v)
+      )
+    },
+    delAllTags() {
+      this.tagList = this.tagList.filter(v => !!v.meta.affix)
+      // 保存到localStorage
+      removeItem(TAGLIST)
+      this.cacheList = []
+    },
+    updateTagList(tag) {
+      const index = this.tagList.findIndex(v => v.path === tag.path)
+      if (index > -1) {
+        this.tagList[index] = Object.assign({}, this.tagList[index], tag)
+        // 保存到localStorage
+        setItem(TAGLIST, this.tagList)
+      }
+    },
+    clearAllTags() {
+      this.cacheList = []
+      this.tagList = []
+      // 保存到localStorage
+      removeItem(TAGLIST)
+    },
+  },
+})

+ 41 - 0
vue3/项目/vue3-project/src/router/index.js

@@ -0,0 +1,41 @@
+import { createRouter, createWebHashHistory } from 'vue-router'
+
+import redirect from './modules/redirect'
+import error from './modules/error'
+import login from './modules/login'
+import lock from './modules/lock'
+import home from './modules/home'
+import test from './modules/test'
+import system from './modules/system'
+import product from './modules/product'
+import order from './modules/order'
+
+/* 菜单栏的路由 */
+// 固定菜单
+export const fixedRoutes = [...home]
+// 动态菜单
+export const asyncRoutes = [...system, ...product, ...order]
+
+const router = createRouter({
+  history: createWebHashHistory(),
+  routes: [
+    {
+      path: '/',
+      redirect: '/home',
+    },
+    ...redirect, // 统一的重定向配置
+    ...login,
+    ...lock,
+    ...fixedRoutes,
+    ...error,
+  ],
+  scrollBehavior(to, from, savedPosition) {
+    if (savedPosition) {
+      return savedPosition
+    } else {
+      return { left: 0, top: 0 }
+    }
+  },
+})
+
+export default router

+ 81 - 0
vue3/项目/vue3-project/src/router/modules/error.js

@@ -0,0 +1,81 @@
+import { useAccount } from '@/pinia/modules/account'
+
+const checkUserinfo = (code, fullPath) => {
+  const { userinfo } = useAccount()
+  if (userinfo) {
+    return `/error/${code === '404' ? fullPath : code}`
+  }
+  return true
+}
+
+const Layout = () => import('@/layout/index.vue')
+const Error = () => import('@/views/error/index.vue')
+
+export default [
+  {
+    path: '/error',
+    component: Layout,
+    children: [
+      {
+        path: '403',
+        name: 'error-forbidden',
+        component: Error,
+        meta: { title: '403' },
+        props: {
+          error: '403',
+        },
+      },
+      {
+        path: '500',
+        name: 'error-server-error',
+        component: Error,
+        meta: { title: '500' },
+        props: {
+          error: '500',
+        },
+      },
+      {
+        path: ':pathMatch(.*)',
+        name: 'error-not-found',
+        component: Error,
+        meta: { title: '404' },
+        props: {
+          error: '404',
+        },
+      },
+    ],
+  },
+  {
+    path: '/403',
+    name: 'forbidden',
+    component: Error,
+    props: {
+      error: '403',
+    },
+    beforeEnter() {
+      return checkUserinfo('403')
+    },
+  },
+  {
+    path: '/500',
+    name: 'server-error',
+    component: Error,
+    props: {
+      error: '500',
+    },
+    beforeEnter() {
+      return checkUserinfo('500')
+    },
+  },
+  {
+    path: '/:pathMatch(.*)',
+    name: 'not-found',
+    component: Error,
+    props: {
+      error: '404',
+    },
+    beforeEnter(to) {
+      return checkUserinfo('404', to.fullPath.replace('/', ''))
+    },
+  },
+]

+ 26 - 0
vue3/项目/vue3-project/src/router/modules/home.js

@@ -0,0 +1,26 @@
+// home.js
+const Layout = () => import('@/layout/index.vue')
+const Home = () => import('@/views/home/index.vue')
+
+export default [
+  {
+    path: '/home',
+    component: Layout,
+    name: 'Dashboard',
+    meta: {
+      title: 'menu.dashboard',
+    },
+    icon: 'icon-home',
+    children: [
+      {
+        path: '',
+        name: 'home',
+        component: Home,
+        meta: {
+          title: 'menu.homepage',
+          affix: true,
+        },
+      },
+    ],
+  },
+]

部分文件因文件數量過多而無法顯示