Quellcode durchsuchen

后台管理端

caojp vor 2 Wochen
Ursprung
Commit
f14e186ae2
73 geänderte Dateien mit 5022 neuen und 20 gelöschten Zeilen
  1. 24 0
      admin-vue/.gitignore
  2. 5 0
      admin-vue/README.md
  3. 13 0
      admin-vue/index.html
  4. 1742 0
      admin-vue/package-lock.json
  5. 22 0
      admin-vue/package.json
  6. 1 0
      admin-vue/public/vite.svg
  7. 9 0
      admin-vue/src/App.vue
  8. 15 0
      admin-vue/src/api/http.js
  9. 1 0
      admin-vue/src/assets/vue.svg
  10. 43 0
      admin-vue/src/components/HelloWorld.vue
  11. 82 0
      admin-vue/src/layouts/AdminLayout.vue
  12. 9 0
      admin-vue/src/main.js
  13. 28 0
      admin-vue/src/router/index.js
  14. 68 0
      admin-vue/src/style.css
  15. 187 0
      admin-vue/src/views/DishCategory.vue
  16. 474 0
      admin-vue/src/views/DishList.vue
  17. 187 0
      admin-vue/src/views/Flavor.vue
  18. 147 0
      admin-vue/src/views/OrdersList.vue
  19. 16 0
      admin-vue/vite.config.js
  20. 6 0
      oederFoodVue/package-lock.json
  21. 7 1
      pom.xml
  22. 2 2
      src/main/java/com/OrderFoodApplication.java
  23. 17 0
      src/main/java/com/bebind/config/CorsConfig.java
  24. 30 0
      src/main/java/com/bebind/config/MinioConfig.java
  25. 17 0
      src/main/java/com/bebind/config/MybatisPlusConfig.java
  26. 50 0
      src/main/java/com/bebind/controllor/DishCategoryControllor.java
  27. 113 0
      src/main/java/com/bebind/controllor/DishControllor.java
  28. 50 0
      src/main/java/com/bebind/controllor/FlaveControllor.java
  29. 44 0
      src/main/java/com/bebind/controllor/OrdersControllor.java
  30. 199 0
      src/main/java/com/bebind/domain/Dish.java
  31. 104 0
      src/main/java/com/bebind/domain/DishCategory.java
  32. 104 0
      src/main/java/com/bebind/domain/Flaver.java
  33. 109 0
      src/main/java/com/bebind/domain/OrderDetail.java
  34. 127 0
      src/main/java/com/bebind/domain/Orders.java
  35. 12 0
      src/main/java/com/bebind/dto/DishCatDto.java
  36. 9 0
      src/main/java/com/bebind/dto/DishCategoryDto.java
  37. 20 0
      src/main/java/com/bebind/dto/DishDto.java
  38. 11 0
      src/main/java/com/bebind/dto/FlaDto.java
  39. 10 0
      src/main/java/com/bebind/dto/FlaverDto.java
  40. 12 0
      src/main/java/com/bebind/dto/OrderDto.java
  41. 8 0
      src/main/java/com/bebind/dto/OrdersDto.java
  42. 10 0
      src/main/java/com/bebind/dto/PageDto.java
  43. 22 0
      src/main/java/com/bebind/mapper/DishCategoryMapper.java
  44. 22 0
      src/main/java/com/bebind/mapper/DishMapper.java
  45. 22 0
      src/main/java/com/bebind/mapper/FlaverMapper.java
  46. 22 0
      src/main/java/com/bebind/mapper/OrderDetailMapper.java
  47. 22 0
      src/main/java/com/bebind/mapper/OrdersMapper.java
  48. 21 0
      src/main/java/com/bebind/service/DishCategoryService.java
  49. 20 0
      src/main/java/com/bebind/service/DishService.java
  50. 21 0
      src/main/java/com/bebind/service/FlaverService.java
  51. 13 0
      src/main/java/com/bebind/service/OrderDetailService.java
  52. 27 0
      src/main/java/com/bebind/service/OrdersService.java
  53. 64 0
      src/main/java/com/bebind/service/impl/DishCategoryServiceImpl.java
  54. 111 0
      src/main/java/com/bebind/service/impl/DishServiceImpl.java
  55. 62 0
      src/main/java/com/bebind/service/impl/FlaverServiceImpl.java
  56. 22 0
      src/main/java/com/bebind/service/impl/OrderDetailServiceImpl.java
  57. 94 0
      src/main/java/com/bebind/service/impl/OrdersServiceImpl.java
  58. 77 0
      src/main/java/com/bebind/utils/MinioUploadUtil.java
  59. 12 0
      src/main/java/com/bebind/vo/MinioUploadResult.java
  60. 35 0
      src/main/java/com/bebind/vo/OrdersVo.java
  61. 47 0
      src/main/java/com/bebind/vo/ResultVo.java
  62. 1 1
      src/main/java/com/we/controller/DishController.java
  63. 1 1
      src/main/java/com/we/controller/OrderController.java
  64. 1 1
      src/main/java/com/we/controller/PaymentController.java
  65. 4 0
      src/main/java/com/we/service/DishService.java
  66. 7 0
      src/main/java/com/we/service/impl/DishServiceImpl.java
  67. 13 1
      src/main/resources/application.yml
  68. 19 0
      src/main/resources/com/bebind/mapper/DishCategoryMapper.xml
  69. 33 0
      src/main/resources/com/bebind/mapper/DishMapper.xml
  70. 19 0
      src/main/resources/com/bebind/mapper/FlaverMapper.xml
  71. 21 0
      src/main/resources/com/bebind/mapper/OrderDetailMapper.xml
  72. 23 0
      src/main/resources/com/bebind/mapper/OrdersMapper.xml
  73. 0 13
      src/test/java/com/we/OrderFoodApplicationTests.java

+ 24 - 0
admin-vue/.gitignore

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

+ 5 - 0
admin-vue/README.md

@@ -0,0 +1,5 @@
+# Vue 3 + Vite
+
+This template should help get you started developing with Vue 3 in Vite. The template uses Vue 3 `<script setup>` SFCs, check out the [script setup docs](https://v3.vuejs.org/api/sfc-script-setup.html#sfc-script-setup) to learn more.
+
+Learn more about IDE Support for Vue in the [Vue Docs Scaling up Guide](https://vuejs.org/guide/scaling-up/tooling.html#ide-support).

+ 13 - 0
admin-vue/index.html

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

+ 1742 - 0
admin-vue/package-lock.json

@@ -0,0 +1,1742 @@
+{
+  "name": "admin-vue",
+  "version": "0.0.0",
+  "lockfileVersion": 3,
+  "requires": true,
+  "packages": {
+    "": {
+      "name": "admin-vue",
+      "version": "0.0.0",
+      "dependencies": {
+        "@element-plus/icons-vue": "^2.3.2",
+        "axios": "^1.13.5",
+        "element-plus": "^2.13.2",
+        "vue": "^3.5.25",
+        "vue-router": "^4.6.4"
+      },
+      "devDependencies": {
+        "@vitejs/plugin-vue": "^6.0.2",
+        "vite": "^7.3.1"
+      }
+    },
+    "node_modules/@babel/helper-string-parser": {
+      "version": "7.27.1",
+      "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz",
+      "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==",
+      "engines": {
+        "node": ">=6.9.0"
+      }
+    },
+    "node_modules/@babel/helper-validator-identifier": {
+      "version": "7.28.5",
+      "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz",
+      "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==",
+      "engines": {
+        "node": ">=6.9.0"
+      }
+    },
+    "node_modules/@babel/parser": {
+      "version": "7.29.0",
+      "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.0.tgz",
+      "integrity": "sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww==",
+      "dependencies": {
+        "@babel/types": "^7.29.0"
+      },
+      "bin": {
+        "parser": "bin/babel-parser.js"
+      },
+      "engines": {
+        "node": ">=6.0.0"
+      }
+    },
+    "node_modules/@babel/types": {
+      "version": "7.29.0",
+      "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz",
+      "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==",
+      "dependencies": {
+        "@babel/helper-string-parser": "^7.27.1",
+        "@babel/helper-validator-identifier": "^7.28.5"
+      },
+      "engines": {
+        "node": ">=6.9.0"
+      }
+    },
+    "node_modules/@ctrl/tinycolor": {
+      "version": "3.6.1",
+      "resolved": "https://registry.npmjs.org/@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.2",
+      "resolved": "https://registry.npmjs.org/@element-plus/icons-vue/-/icons-vue-2.3.2.tgz",
+      "integrity": "sha512-OzIuTaIfC8QXEPmJvB4Y4kw34rSXdCJzxcD1kFStBvr8bK6X1zQAYDo0CNMjojnfTqRQCJ0I7prlErcoRiET2A==",
+      "peerDependencies": {
+        "vue": "^3.2.0"
+      }
+    },
+    "node_modules/@esbuild/aix-ppc64": {
+      "version": "0.27.3",
+      "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.3.tgz",
+      "integrity": "sha512-9fJMTNFTWZMh5qwrBItuziu834eOCUcEqymSH7pY+zoMVEZg3gcPuBNxH1EvfVYe9h0x/Ptw8KBzv7qxb7l8dg==",
+      "cpu": [
+        "ppc64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "aix"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/android-arm": {
+      "version": "0.27.3",
+      "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.3.tgz",
+      "integrity": "sha512-i5D1hPY7GIQmXlXhs2w8AWHhenb00+GxjxRncS2ZM7YNVGNfaMxgzSGuO8o8SJzRc/oZwU2bcScvVERk03QhzA==",
+      "cpu": [
+        "arm"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "android"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/android-arm64": {
+      "version": "0.27.3",
+      "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.3.tgz",
+      "integrity": "sha512-YdghPYUmj/FX2SYKJ0OZxf+iaKgMsKHVPF1MAq/P8WirnSpCStzKJFjOjzsW0QQ7oIAiccHdcqjbHmJxRb/dmg==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "android"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/android-x64": {
+      "version": "0.27.3",
+      "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.3.tgz",
+      "integrity": "sha512-IN/0BNTkHtk8lkOM8JWAYFg4ORxBkZQf9zXiEOfERX/CzxW3Vg1ewAhU7QSWQpVIzTW+b8Xy+lGzdYXV6UZObQ==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "android"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/darwin-arm64": {
+      "version": "0.27.3",
+      "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.3.tgz",
+      "integrity": "sha512-Re491k7ByTVRy0t3EKWajdLIr0gz2kKKfzafkth4Q8A5n1xTHrkqZgLLjFEHVD+AXdUGgQMq+Godfq45mGpCKg==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "darwin"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/darwin-x64": {
+      "version": "0.27.3",
+      "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.3.tgz",
+      "integrity": "sha512-vHk/hA7/1AckjGzRqi6wbo+jaShzRowYip6rt6q7VYEDX4LEy1pZfDpdxCBnGtl+A5zq8iXDcyuxwtv3hNtHFg==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "darwin"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/freebsd-arm64": {
+      "version": "0.27.3",
+      "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.3.tgz",
+      "integrity": "sha512-ipTYM2fjt3kQAYOvo6vcxJx3nBYAzPjgTCk7QEgZG8AUO3ydUhvelmhrbOheMnGOlaSFUoHXB6un+A7q4ygY9w==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "freebsd"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/freebsd-x64": {
+      "version": "0.27.3",
+      "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.3.tgz",
+      "integrity": "sha512-dDk0X87T7mI6U3K9VjWtHOXqwAMJBNN2r7bejDsc+j03SEjtD9HrOl8gVFByeM0aJksoUuUVU9TBaZa2rgj0oA==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "freebsd"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/linux-arm": {
+      "version": "0.27.3",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.3.tgz",
+      "integrity": "sha512-s6nPv2QkSupJwLYyfS+gwdirm0ukyTFNl3KTgZEAiJDd+iHZcbTPPcWCcRYH+WlNbwChgH2QkE9NSlNrMT8Gfw==",
+      "cpu": [
+        "arm"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/linux-arm64": {
+      "version": "0.27.3",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.3.tgz",
+      "integrity": "sha512-sZOuFz/xWnZ4KH3YfFrKCf1WyPZHakVzTiqji3WDc0BCl2kBwiJLCXpzLzUBLgmp4veFZdvN5ChW4Eq/8Fc2Fg==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/linux-ia32": {
+      "version": "0.27.3",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.3.tgz",
+      "integrity": "sha512-yGlQYjdxtLdh0a3jHjuwOrxQjOZYD/C9PfdbgJJF3TIZWnm/tMd/RcNiLngiu4iwcBAOezdnSLAwQDPqTmtTYg==",
+      "cpu": [
+        "ia32"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/linux-loong64": {
+      "version": "0.27.3",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.3.tgz",
+      "integrity": "sha512-WO60Sn8ly3gtzhyjATDgieJNet/KqsDlX5nRC5Y3oTFcS1l0KWba+SEa9Ja1GfDqSF1z6hif/SkpQJbL63cgOA==",
+      "cpu": [
+        "loong64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/linux-mips64el": {
+      "version": "0.27.3",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.3.tgz",
+      "integrity": "sha512-APsymYA6sGcZ4pD6k+UxbDjOFSvPWyZhjaiPyl/f79xKxwTnrn5QUnXR5prvetuaSMsb4jgeHewIDCIWljrSxw==",
+      "cpu": [
+        "mips64el"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/linux-ppc64": {
+      "version": "0.27.3",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.3.tgz",
+      "integrity": "sha512-eizBnTeBefojtDb9nSh4vvVQ3V9Qf9Df01PfawPcRzJH4gFSgrObw+LveUyDoKU3kxi5+9RJTCWlj4FjYXVPEA==",
+      "cpu": [
+        "ppc64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/linux-riscv64": {
+      "version": "0.27.3",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.3.tgz",
+      "integrity": "sha512-3Emwh0r5wmfm3ssTWRQSyVhbOHvqegUDRd0WhmXKX2mkHJe1SFCMJhagUleMq+Uci34wLSipf8Lagt4LlpRFWQ==",
+      "cpu": [
+        "riscv64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/linux-s390x": {
+      "version": "0.27.3",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.3.tgz",
+      "integrity": "sha512-pBHUx9LzXWBc7MFIEEL0yD/ZVtNgLytvx60gES28GcWMqil8ElCYR4kvbV2BDqsHOvVDRrOxGySBM9Fcv744hw==",
+      "cpu": [
+        "s390x"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/linux-x64": {
+      "version": "0.27.3",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.3.tgz",
+      "integrity": "sha512-Czi8yzXUWIQYAtL/2y6vogER8pvcsOsk5cpwL4Gk5nJqH5UZiVByIY8Eorm5R13gq+DQKYg0+JyQoytLQas4dA==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/netbsd-arm64": {
+      "version": "0.27.3",
+      "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.3.tgz",
+      "integrity": "sha512-sDpk0RgmTCR/5HguIZa9n9u+HVKf40fbEUt+iTzSnCaGvY9kFP0YKBWZtJaraonFnqef5SlJ8/TiPAxzyS+UoA==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "netbsd"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/netbsd-x64": {
+      "version": "0.27.3",
+      "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.3.tgz",
+      "integrity": "sha512-P14lFKJl/DdaE00LItAukUdZO5iqNH7+PjoBm+fLQjtxfcfFE20Xf5CrLsmZdq5LFFZzb5JMZ9grUwvtVYzjiA==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "netbsd"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/openbsd-arm64": {
+      "version": "0.27.3",
+      "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.3.tgz",
+      "integrity": "sha512-AIcMP77AvirGbRl/UZFTq5hjXK+2wC7qFRGoHSDrZ5v5b8DK/GYpXW3CPRL53NkvDqb9D+alBiC/dV0Fb7eJcw==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "openbsd"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/openbsd-x64": {
+      "version": "0.27.3",
+      "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.3.tgz",
+      "integrity": "sha512-DnW2sRrBzA+YnE70LKqnM3P+z8vehfJWHXECbwBmH/CU51z6FiqTQTHFenPlHmo3a8UgpLyH3PT+87OViOh1AQ==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "openbsd"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/openharmony-arm64": {
+      "version": "0.27.3",
+      "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.3.tgz",
+      "integrity": "sha512-NinAEgr/etERPTsZJ7aEZQvvg/A6IsZG/LgZy+81wON2huV7SrK3e63dU0XhyZP4RKGyTm7aOgmQk0bGp0fy2g==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "openharmony"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/sunos-x64": {
+      "version": "0.27.3",
+      "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.3.tgz",
+      "integrity": "sha512-PanZ+nEz+eWoBJ8/f8HKxTTD172SKwdXebZ0ndd953gt1HRBbhMsaNqjTyYLGLPdoWHy4zLU7bDVJztF5f3BHA==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "sunos"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/win32-arm64": {
+      "version": "0.27.3",
+      "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.3.tgz",
+      "integrity": "sha512-B2t59lWWYrbRDw/tjiWOuzSsFh1Y/E95ofKz7rIVYSQkUYBjfSgf6oeYPNWHToFRr2zx52JKApIcAS/D5TUBnA==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "win32"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/win32-ia32": {
+      "version": "0.27.3",
+      "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.3.tgz",
+      "integrity": "sha512-QLKSFeXNS8+tHW7tZpMtjlNb7HKau0QDpwm49u0vUp9y1WOF+PEzkU84y9GqYaAVW8aH8f3GcBck26jh54cX4Q==",
+      "cpu": [
+        "ia32"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "win32"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/win32-x64": {
+      "version": "0.27.3",
+      "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.3.tgz",
+      "integrity": "sha512-4uJGhsxuptu3OcpVAzli+/gWusVGwZZHTlS63hh++ehExkVT8SgiEf7/uC/PclrPPkLhZqGgCTjd0VWLo6xMqA==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "win32"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@floating-ui/core": {
+      "version": "1.7.4",
+      "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.7.4.tgz",
+      "integrity": "sha512-C3HlIdsBxszvm5McXlB8PeOEWfBhcGBTZGkGlWc2U0KFY5IwG5OQEuQ8rq52DZmcHDlPLd+YFBK+cZcytwIFWg==",
+      "dependencies": {
+        "@floating-ui/utils": "^0.2.10"
+      }
+    },
+    "node_modules/@floating-ui/dom": {
+      "version": "1.7.5",
+      "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.7.5.tgz",
+      "integrity": "sha512-N0bD2kIPInNHUHehXhMke1rBGs1dwqvC9O9KYMyyjK7iXt7GAhnro7UlcuYcGdS/yYOlq0MAVgrow8IbWJwyqg==",
+      "dependencies": {
+        "@floating-ui/core": "^1.7.4",
+        "@floating-ui/utils": "^0.2.10"
+      }
+    },
+    "node_modules/@floating-ui/utils": {
+      "version": "0.2.10",
+      "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.10.tgz",
+      "integrity": "sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ=="
+    },
+    "node_modules/@jridgewell/sourcemap-codec": {
+      "version": "1.5.5",
+      "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
+      "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og=="
+    },
+    "node_modules/@popperjs/core": {
+      "name": "@sxzz/popperjs-es",
+      "version": "2.11.8",
+      "resolved": "https://registry.npmjs.org/@sxzz/popperjs-es/-/popperjs-es-2.11.8.tgz",
+      "integrity": "sha512-wOwESXvvED3S8xBmcPWHs2dUuzrE4XiZeFu7e1hROIJkm02a49N120pmOXxY33sBb6hArItm5W5tcg1cBtV+HQ==",
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/popperjs"
+      }
+    },
+    "node_modules/@rolldown/pluginutils": {
+      "version": "1.0.0-rc.2",
+      "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.2.tgz",
+      "integrity": "sha512-izyXV/v+cHiRfozX62W9htOAvwMo4/bXKDrQ+vom1L1qRuexPock/7VZDAhnpHCLNejd3NJ6hiab+tO0D44Rgw==",
+      "dev": true
+    },
+    "node_modules/@rollup/rollup-android-arm-eabi": {
+      "version": "4.57.1",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.57.1.tgz",
+      "integrity": "sha512-A6ehUVSiSaaliTxai040ZpZ2zTevHYbvu/lDoeAteHI8QnaosIzm4qwtezfRg1jOYaUmnzLX1AOD6Z+UJjtifg==",
+      "cpu": [
+        "arm"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "android"
+      ]
+    },
+    "node_modules/@rollup/rollup-android-arm64": {
+      "version": "4.57.1",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.57.1.tgz",
+      "integrity": "sha512-dQaAddCY9YgkFHZcFNS/606Exo8vcLHwArFZ7vxXq4rigo2bb494/xKMMwRRQW6ug7Js6yXmBZhSBRuBvCCQ3w==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "android"
+      ]
+    },
+    "node_modules/@rollup/rollup-darwin-arm64": {
+      "version": "4.57.1",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.57.1.tgz",
+      "integrity": "sha512-crNPrwJOrRxagUYeMn/DZwqN88SDmwaJ8Cvi/TN1HnWBU7GwknckyosC2gd0IqYRsHDEnXf328o9/HC6OkPgOg==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "darwin"
+      ]
+    },
+    "node_modules/@rollup/rollup-darwin-x64": {
+      "version": "4.57.1",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.57.1.tgz",
+      "integrity": "sha512-Ji8g8ChVbKrhFtig5QBV7iMaJrGtpHelkB3lsaKzadFBe58gmjfGXAOfI5FV0lYMH8wiqsxKQ1C9B0YTRXVy4w==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "darwin"
+      ]
+    },
+    "node_modules/@rollup/rollup-freebsd-arm64": {
+      "version": "4.57.1",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.57.1.tgz",
+      "integrity": "sha512-R+/WwhsjmwodAcz65guCGFRkMb4gKWTcIeLy60JJQbXrJ97BOXHxnkPFrP+YwFlaS0m+uWJTstrUA9o+UchFug==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "freebsd"
+      ]
+    },
+    "node_modules/@rollup/rollup-freebsd-x64": {
+      "version": "4.57.1",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.57.1.tgz",
+      "integrity": "sha512-IEQTCHeiTOnAUC3IDQdzRAGj3jOAYNr9kBguI7MQAAZK3caezRrg0GxAb6Hchg4lxdZEI5Oq3iov/w/hnFWY9Q==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "freebsd"
+      ]
+    },
+    "node_modules/@rollup/rollup-linux-arm-gnueabihf": {
+      "version": "4.57.1",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.57.1.tgz",
+      "integrity": "sha512-F8sWbhZ7tyuEfsmOxwc2giKDQzN3+kuBLPwwZGyVkLlKGdV1nvnNwYD0fKQ8+XS6hp9nY7B+ZeK01EBUE7aHaw==",
+      "cpu": [
+        "arm"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "linux"
+      ]
+    },
+    "node_modules/@rollup/rollup-linux-arm-musleabihf": {
+      "version": "4.57.1",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.57.1.tgz",
+      "integrity": "sha512-rGfNUfn0GIeXtBP1wL5MnzSj98+PZe/AXaGBCRmT0ts80lU5CATYGxXukeTX39XBKsxzFpEeK+Mrp9faXOlmrw==",
+      "cpu": [
+        "arm"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "linux"
+      ]
+    },
+    "node_modules/@rollup/rollup-linux-arm64-gnu": {
+      "version": "4.57.1",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.57.1.tgz",
+      "integrity": "sha512-MMtej3YHWeg/0klK2Qodf3yrNzz6CGjo2UntLvk2RSPlhzgLvYEB3frRvbEF2wRKh1Z2fDIg9KRPe1fawv7C+g==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "linux"
+      ]
+    },
+    "node_modules/@rollup/rollup-linux-arm64-musl": {
+      "version": "4.57.1",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.57.1.tgz",
+      "integrity": "sha512-1a/qhaaOXhqXGpMFMET9VqwZakkljWHLmZOX48R0I/YLbhdxr1m4gtG1Hq7++VhVUmf+L3sTAf9op4JlhQ5u1Q==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "linux"
+      ]
+    },
+    "node_modules/@rollup/rollup-linux-loong64-gnu": {
+      "version": "4.57.1",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.57.1.tgz",
+      "integrity": "sha512-QWO6RQTZ/cqYtJMtxhkRkidoNGXc7ERPbZN7dVW5SdURuLeVU7lwKMpo18XdcmpWYd0qsP1bwKPf7DNSUinhvA==",
+      "cpu": [
+        "loong64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "linux"
+      ]
+    },
+    "node_modules/@rollup/rollup-linux-loong64-musl": {
+      "version": "4.57.1",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.57.1.tgz",
+      "integrity": "sha512-xpObYIf+8gprgWaPP32xiN5RVTi/s5FCR+XMXSKmhfoJjrpRAjCuuqQXyxUa/eJTdAE6eJ+KDKaoEqjZQxh3Gw==",
+      "cpu": [
+        "loong64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "linux"
+      ]
+    },
+    "node_modules/@rollup/rollup-linux-ppc64-gnu": {
+      "version": "4.57.1",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.57.1.tgz",
+      "integrity": "sha512-4BrCgrpZo4hvzMDKRqEaW1zeecScDCR+2nZ86ATLhAoJ5FQ+lbHVD3ttKe74/c7tNT9c6F2viwB3ufwp01Oh2w==",
+      "cpu": [
+        "ppc64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "linux"
+      ]
+    },
+    "node_modules/@rollup/rollup-linux-ppc64-musl": {
+      "version": "4.57.1",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.57.1.tgz",
+      "integrity": "sha512-NOlUuzesGauESAyEYFSe3QTUguL+lvrN1HtwEEsU2rOwdUDeTMJdO5dUYl/2hKf9jWydJrO9OL/XSSf65R5+Xw==",
+      "cpu": [
+        "ppc64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "linux"
+      ]
+    },
+    "node_modules/@rollup/rollup-linux-riscv64-gnu": {
+      "version": "4.57.1",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.57.1.tgz",
+      "integrity": "sha512-ptA88htVp0AwUUqhVghwDIKlvJMD/fmL/wrQj99PRHFRAG6Z5nbWoWG4o81Nt9FT+IuqUQi+L31ZKAFeJ5Is+A==",
+      "cpu": [
+        "riscv64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "linux"
+      ]
+    },
+    "node_modules/@rollup/rollup-linux-riscv64-musl": {
+      "version": "4.57.1",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.57.1.tgz",
+      "integrity": "sha512-S51t7aMMTNdmAMPpBg7OOsTdn4tySRQvklmL3RpDRyknk87+Sp3xaumlatU+ppQ+5raY7sSTcC2beGgvhENfuw==",
+      "cpu": [
+        "riscv64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "linux"
+      ]
+    },
+    "node_modules/@rollup/rollup-linux-s390x-gnu": {
+      "version": "4.57.1",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.57.1.tgz",
+      "integrity": "sha512-Bl00OFnVFkL82FHbEqy3k5CUCKH6OEJL54KCyx2oqsmZnFTR8IoNqBF+mjQVcRCT5sB6yOvK8A37LNm/kPJiZg==",
+      "cpu": [
+        "s390x"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "linux"
+      ]
+    },
+    "node_modules/@rollup/rollup-linux-x64-gnu": {
+      "version": "4.57.1",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.57.1.tgz",
+      "integrity": "sha512-ABca4ceT4N+Tv/GtotnWAeXZUZuM/9AQyCyKYyKnpk4yoA7QIAuBt6Hkgpw8kActYlew2mvckXkvx0FfoInnLg==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "linux"
+      ]
+    },
+    "node_modules/@rollup/rollup-linux-x64-musl": {
+      "version": "4.57.1",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.57.1.tgz",
+      "integrity": "sha512-HFps0JeGtuOR2convgRRkHCekD7j+gdAuXM+/i6kGzQtFhlCtQkpwtNzkNj6QhCDp7DRJ7+qC/1Vg2jt5iSOFw==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "linux"
+      ]
+    },
+    "node_modules/@rollup/rollup-openbsd-x64": {
+      "version": "4.57.1",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.57.1.tgz",
+      "integrity": "sha512-H+hXEv9gdVQuDTgnqD+SQffoWoc0Of59AStSzTEj/feWTBAnSfSD3+Dql1ZruJQxmykT/JVY0dE8Ka7z0DH1hw==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "openbsd"
+      ]
+    },
+    "node_modules/@rollup/rollup-openharmony-arm64": {
+      "version": "4.57.1",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.57.1.tgz",
+      "integrity": "sha512-4wYoDpNg6o/oPximyc/NG+mYUejZrCU2q+2w6YZqrAs2UcNUChIZXjtafAiiZSUc7On8v5NyNj34Kzj/Ltk6dQ==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "openharmony"
+      ]
+    },
+    "node_modules/@rollup/rollup-win32-arm64-msvc": {
+      "version": "4.57.1",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.57.1.tgz",
+      "integrity": "sha512-O54mtsV/6LW3P8qdTcamQmuC990HDfR71lo44oZMZlXU4tzLrbvTii87Ni9opq60ds0YzuAlEr/GNwuNluZyMQ==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "win32"
+      ]
+    },
+    "node_modules/@rollup/rollup-win32-ia32-msvc": {
+      "version": "4.57.1",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.57.1.tgz",
+      "integrity": "sha512-P3dLS+IerxCT/7D2q2FYcRdWRl22dNbrbBEtxdWhXrfIMPP9lQhb5h4Du04mdl5Woq05jVCDPCMF7Ub0NAjIew==",
+      "cpu": [
+        "ia32"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "win32"
+      ]
+    },
+    "node_modules/@rollup/rollup-win32-x64-gnu": {
+      "version": "4.57.1",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.57.1.tgz",
+      "integrity": "sha512-VMBH2eOOaKGtIJYleXsi2B8CPVADrh+TyNxJ4mWPnKfLB/DBUmzW+5m1xUrcwWoMfSLagIRpjUFeW5CO5hyciQ==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "win32"
+      ]
+    },
+    "node_modules/@rollup/rollup-win32-x64-msvc": {
+      "version": "4.57.1",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.57.1.tgz",
+      "integrity": "sha512-mxRFDdHIWRxg3UfIIAwCm6NzvxG0jDX/wBN6KsQFTvKFqqg9vTrWUE68qEjHt19A5wwx5X5aUi2zuZT7YR0jrA==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "win32"
+      ]
+    },
+    "node_modules/@types/estree": {
+      "version": "1.0.8",
+      "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
+      "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==",
+      "dev": true
+    },
+    "node_modules/@types/lodash": {
+      "version": "4.17.23",
+      "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.23.tgz",
+      "integrity": "sha512-RDvF6wTulMPjrNdCoYRC8gNR880JNGT8uB+REUpC2Ns4pRqQJhGz90wh7rgdXDPpCczF3VGktDuFGVnz8zP7HA=="
+    },
+    "node_modules/@types/lodash-es": {
+      "version": "4.17.12",
+      "resolved": "https://registry.npmjs.org/@types/lodash-es/-/lodash-es-4.17.12.tgz",
+      "integrity": "sha512-0NgftHUcV4v34VhXm8QBSftKVXtbkBG3ViCjs6+eJ5a6y6Mi/jiFGPc1sC7QK+9BFhWrURE3EOggmWaSxL9OzQ==",
+      "dependencies": {
+        "@types/lodash": "*"
+      }
+    },
+    "node_modules/@types/web-bluetooth": {
+      "version": "0.0.20",
+      "resolved": "https://registry.npmjs.org/@types/web-bluetooth/-/web-bluetooth-0.0.20.tgz",
+      "integrity": "sha512-g9gZnnXVq7gM7v3tJCWV/qw7w+KeOlSHAhgF9RytFyifW6AF61hdT2ucrYhPq9hLs5JIryeupHV3qGk95dH9ow=="
+    },
+    "node_modules/@vitejs/plugin-vue": {
+      "version": "6.0.4",
+      "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-6.0.4.tgz",
+      "integrity": "sha512-uM5iXipgYIn13UUQCZNdWkYk+sysBeA97d5mHsAoAt1u/wpN3+zxOmsVJWosuzX+IMGRzeYUNytztrYznboIkQ==",
+      "dev": true,
+      "dependencies": {
+        "@rolldown/pluginutils": "1.0.0-rc.2"
+      },
+      "engines": {
+        "node": "^20.19.0 || >=22.12.0"
+      },
+      "peerDependencies": {
+        "vite": "^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0",
+        "vue": "^3.2.25"
+      }
+    },
+    "node_modules/@vue/compiler-core": {
+      "version": "3.5.28",
+      "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.28.tgz",
+      "integrity": "sha512-kviccYxTgoE8n6OCw96BNdYlBg2GOWfBuOW4Vqwrt7mSKWKwFVvI8egdTltqRgITGPsTFYtKYfxIG8ptX2PJHQ==",
+      "dependencies": {
+        "@babel/parser": "^7.29.0",
+        "@vue/shared": "3.5.28",
+        "entities": "^7.0.1",
+        "estree-walker": "^2.0.2",
+        "source-map-js": "^1.2.1"
+      }
+    },
+    "node_modules/@vue/compiler-dom": {
+      "version": "3.5.28",
+      "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.28.tgz",
+      "integrity": "sha512-/1ZepxAb159jKR1btkefDP+J2xuWL5V3WtleRmxaT+K2Aqiek/Ab/+Ebrw2pPj0sdHO8ViAyyJWfhXXOP/+LQA==",
+      "dependencies": {
+        "@vue/compiler-core": "3.5.28",
+        "@vue/shared": "3.5.28"
+      }
+    },
+    "node_modules/@vue/compiler-sfc": {
+      "version": "3.5.28",
+      "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.28.tgz",
+      "integrity": "sha512-6TnKMiNkd6u6VeVDhZn/07KhEZuBSn43Wd2No5zaP5s3xm8IqFTHBj84HJah4UepSUJTro5SoqqlOY22FKY96g==",
+      "dependencies": {
+        "@babel/parser": "^7.29.0",
+        "@vue/compiler-core": "3.5.28",
+        "@vue/compiler-dom": "3.5.28",
+        "@vue/compiler-ssr": "3.5.28",
+        "@vue/shared": "3.5.28",
+        "estree-walker": "^2.0.2",
+        "magic-string": "^0.30.21",
+        "postcss": "^8.5.6",
+        "source-map-js": "^1.2.1"
+      }
+    },
+    "node_modules/@vue/compiler-ssr": {
+      "version": "3.5.28",
+      "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.28.tgz",
+      "integrity": "sha512-JCq//9w1qmC6UGLWJX7RXzrGpKkroubey/ZFqTpvEIDJEKGgntuDMqkuWiZvzTzTA5h2qZvFBFHY7fAAa9475g==",
+      "dependencies": {
+        "@vue/compiler-dom": "3.5.28",
+        "@vue/shared": "3.5.28"
+      }
+    },
+    "node_modules/@vue/devtools-api": {
+      "version": "6.6.4",
+      "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-6.6.4.tgz",
+      "integrity": "sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g=="
+    },
+    "node_modules/@vue/reactivity": {
+      "version": "3.5.28",
+      "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.28.tgz",
+      "integrity": "sha512-gr5hEsxvn+RNyu9/9o1WtdYdwDjg5FgjUSBEkZWqgTKlo/fvwZ2+8W6AfKsc9YN2k/+iHYdS9vZYAhpi10kNaw==",
+      "dependencies": {
+        "@vue/shared": "3.5.28"
+      }
+    },
+    "node_modules/@vue/runtime-core": {
+      "version": "3.5.28",
+      "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.28.tgz",
+      "integrity": "sha512-POVHTdbgnrBBIpnbYU4y7pOMNlPn2QVxVzkvEA2pEgvzbelQq4ZOUxbp2oiyo+BOtiYlm8Q44wShHJoBvDPAjQ==",
+      "dependencies": {
+        "@vue/reactivity": "3.5.28",
+        "@vue/shared": "3.5.28"
+      }
+    },
+    "node_modules/@vue/runtime-dom": {
+      "version": "3.5.28",
+      "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.28.tgz",
+      "integrity": "sha512-4SXxSF8SXYMuhAIkT+eBRqOkWEfPu6nhccrzrkioA6l0boiq7sp18HCOov9qWJA5HML61kW8p/cB4MmBiG9dSA==",
+      "dependencies": {
+        "@vue/reactivity": "3.5.28",
+        "@vue/runtime-core": "3.5.28",
+        "@vue/shared": "3.5.28",
+        "csstype": "^3.2.3"
+      }
+    },
+    "node_modules/@vue/server-renderer": {
+      "version": "3.5.28",
+      "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.28.tgz",
+      "integrity": "sha512-pf+5ECKGj8fX95bNincbzJ6yp6nyzuLDhYZCeFxUNp8EBrQpPpQaLX3nNCp49+UbgbPun3CeVE+5CXVV1Xydfg==",
+      "dependencies": {
+        "@vue/compiler-ssr": "3.5.28",
+        "@vue/shared": "3.5.28"
+      },
+      "peerDependencies": {
+        "vue": "3.5.28"
+      }
+    },
+    "node_modules/@vue/shared": {
+      "version": "3.5.28",
+      "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.28.tgz",
+      "integrity": "sha512-cfWa1fCGBxrvaHRhvV3Is0MgmrbSCxYTXCSCau2I0a1Xw1N1pHAvkWCiXPRAqjvToILvguNyEwjevUqAuBQWvQ=="
+    },
+    "node_modules/@vueuse/core": {
+      "version": "10.11.1",
+      "resolved": "https://registry.npmjs.org/@vueuse/core/-/core-10.11.1.tgz",
+      "integrity": "sha512-guoy26JQktXPcz+0n3GukWIy/JDNKti9v6VEMu6kV2sYBsWuGiTU8OWdg+ADfUbHg3/3DlqySDe7JmdHrktiww==",
+      "dependencies": {
+        "@types/web-bluetooth": "^0.0.20",
+        "@vueuse/metadata": "10.11.1",
+        "@vueuse/shared": "10.11.1",
+        "vue-demi": ">=0.14.8"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/antfu"
+      }
+    },
+    "node_modules/@vueuse/core/node_modules/vue-demi": {
+      "version": "0.14.10",
+      "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.10.tgz",
+      "integrity": "sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==",
+      "hasInstallScript": true,
+      "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": "10.11.1",
+      "resolved": "https://registry.npmjs.org/@vueuse/metadata/-/metadata-10.11.1.tgz",
+      "integrity": "sha512-IGa5FXd003Ug1qAZmyE8wF3sJ81xGLSqTqtQ6jaVfkeZ4i5kS2mwQF61yhVqojRnenVew5PldLyRgvdl4YYuSw==",
+      "funding": {
+        "url": "https://github.com/sponsors/antfu"
+      }
+    },
+    "node_modules/@vueuse/shared": {
+      "version": "10.11.1",
+      "resolved": "https://registry.npmjs.org/@vueuse/shared/-/shared-10.11.1.tgz",
+      "integrity": "sha512-LHpC8711VFZlDaYUXEBbFBCQ7GS3dVU9mjOhhMhXP6txTV4EhYQg/KGnQuvt/sPAtoUKq7VVUnL6mVtFoL42sA==",
+      "dependencies": {
+        "vue-demi": ">=0.14.8"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/antfu"
+      }
+    },
+    "node_modules/@vueuse/shared/node_modules/vue-demi": {
+      "version": "0.14.10",
+      "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.10.tgz",
+      "integrity": "sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==",
+      "hasInstallScript": true,
+      "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/async-validator": {
+      "version": "4.2.5",
+      "resolved": "https://registry.npmjs.org/async-validator/-/async-validator-4.2.5.tgz",
+      "integrity": "sha512-7HhHjtERjqlNbZtqNqy2rckN/SpOOlmDliet+lP7k+eKZEjPk3DgyeU9lIXLdeLz0uBbbVp+9Qdow9wJWgwwfg=="
+    },
+    "node_modules/asynckit": {
+      "version": "0.4.0",
+      "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
+      "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="
+    },
+    "node_modules/axios": {
+      "version": "1.13.5",
+      "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.5.tgz",
+      "integrity": "sha512-cz4ur7Vb0xS4/KUN0tPWe44eqxrIu31me+fbang3ijiNscE129POzipJJA6zniq2C/Z6sJCjMimjS8Lc/GAs8Q==",
+      "dependencies": {
+        "follow-redirects": "^1.15.11",
+        "form-data": "^4.0.5",
+        "proxy-from-env": "^1.1.0"
+      }
+    },
+    "node_modules/call-bind-apply-helpers": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
+      "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
+      "dependencies": {
+        "es-errors": "^1.3.0",
+        "function-bind": "^1.1.2"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      }
+    },
+    "node_modules/combined-stream": {
+      "version": "1.0.8",
+      "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
+      "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
+      "dependencies": {
+        "delayed-stream": "~1.0.0"
+      },
+      "engines": {
+        "node": ">= 0.8"
+      }
+    },
+    "node_modules/csstype": {
+      "version": "3.2.3",
+      "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
+      "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ=="
+    },
+    "node_modules/dayjs": {
+      "version": "1.11.19",
+      "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.19.tgz",
+      "integrity": "sha512-t5EcLVS6QPBNqM2z8fakk/NKel+Xzshgt8FFKAn+qwlD1pzZWxh0nVCrvFK7ZDb6XucZeF9z8C7CBWTRIVApAw=="
+    },
+    "node_modules/delayed-stream": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
+      "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
+      "engines": {
+        "node": ">=0.4.0"
+      }
+    },
+    "node_modules/dunder-proto": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
+      "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
+      "dependencies": {
+        "call-bind-apply-helpers": "^1.0.1",
+        "es-errors": "^1.3.0",
+        "gopd": "^1.2.0"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      }
+    },
+    "node_modules/element-plus": {
+      "version": "2.13.2",
+      "resolved": "https://registry.npmjs.org/element-plus/-/element-plus-2.13.2.tgz",
+      "integrity": "sha512-Zjzm1NnFXGhV4LYZ6Ze9skPlYi2B4KAmN18FL63A3PZcjhDfroHwhtM6RE8BonlOPHXUnPQynH0BgaoEfvhrGw==",
+      "dependencies": {
+        "@ctrl/tinycolor": "^3.4.1",
+        "@element-plus/icons-vue": "^2.3.2",
+        "@floating-ui/dom": "^1.0.1",
+        "@popperjs/core": "npm:@sxzz/popperjs-es@^2.11.7",
+        "@types/lodash": "^4.17.20",
+        "@types/lodash-es": "^4.17.12",
+        "@vueuse/core": "^10.11.0",
+        "async-validator": "^4.2.5",
+        "dayjs": "^1.11.19",
+        "lodash": "^4.17.23",
+        "lodash-es": "^4.17.23",
+        "lodash-unified": "^1.0.3",
+        "memoize-one": "^6.0.0",
+        "normalize-wheel-es": "^1.2.0"
+      },
+      "peerDependencies": {
+        "vue": "^3.3.0"
+      }
+    },
+    "node_modules/entities": {
+      "version": "7.0.1",
+      "resolved": "https://registry.npmjs.org/entities/-/entities-7.0.1.tgz",
+      "integrity": "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==",
+      "engines": {
+        "node": ">=0.12"
+      },
+      "funding": {
+        "url": "https://github.com/fb55/entities?sponsor=1"
+      }
+    },
+    "node_modules/es-define-property": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
+      "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
+      "engines": {
+        "node": ">= 0.4"
+      }
+    },
+    "node_modules/es-errors": {
+      "version": "1.3.0",
+      "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
+      "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
+      "engines": {
+        "node": ">= 0.4"
+      }
+    },
+    "node_modules/es-object-atoms": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
+      "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
+      "dependencies": {
+        "es-errors": "^1.3.0"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      }
+    },
+    "node_modules/es-set-tostringtag": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
+      "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
+      "dependencies": {
+        "es-errors": "^1.3.0",
+        "get-intrinsic": "^1.2.6",
+        "has-tostringtag": "^1.0.2",
+        "hasown": "^2.0.2"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      }
+    },
+    "node_modules/esbuild": {
+      "version": "0.27.3",
+      "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.3.tgz",
+      "integrity": "sha512-8VwMnyGCONIs6cWue2IdpHxHnAjzxnw2Zr7MkVxB2vjmQ2ivqGFb4LEG3SMnv0Gb2F/G/2yA8zUaiL1gywDCCg==",
+      "dev": true,
+      "hasInstallScript": true,
+      "bin": {
+        "esbuild": "bin/esbuild"
+      },
+      "engines": {
+        "node": ">=18"
+      },
+      "optionalDependencies": {
+        "@esbuild/aix-ppc64": "0.27.3",
+        "@esbuild/android-arm": "0.27.3",
+        "@esbuild/android-arm64": "0.27.3",
+        "@esbuild/android-x64": "0.27.3",
+        "@esbuild/darwin-arm64": "0.27.3",
+        "@esbuild/darwin-x64": "0.27.3",
+        "@esbuild/freebsd-arm64": "0.27.3",
+        "@esbuild/freebsd-x64": "0.27.3",
+        "@esbuild/linux-arm": "0.27.3",
+        "@esbuild/linux-arm64": "0.27.3",
+        "@esbuild/linux-ia32": "0.27.3",
+        "@esbuild/linux-loong64": "0.27.3",
+        "@esbuild/linux-mips64el": "0.27.3",
+        "@esbuild/linux-ppc64": "0.27.3",
+        "@esbuild/linux-riscv64": "0.27.3",
+        "@esbuild/linux-s390x": "0.27.3",
+        "@esbuild/linux-x64": "0.27.3",
+        "@esbuild/netbsd-arm64": "0.27.3",
+        "@esbuild/netbsd-x64": "0.27.3",
+        "@esbuild/openbsd-arm64": "0.27.3",
+        "@esbuild/openbsd-x64": "0.27.3",
+        "@esbuild/openharmony-arm64": "0.27.3",
+        "@esbuild/sunos-x64": "0.27.3",
+        "@esbuild/win32-arm64": "0.27.3",
+        "@esbuild/win32-ia32": "0.27.3",
+        "@esbuild/win32-x64": "0.27.3"
+      }
+    },
+    "node_modules/estree-walker": {
+      "version": "2.0.2",
+      "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz",
+      "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="
+    },
+    "node_modules/fdir": {
+      "version": "6.5.0",
+      "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
+      "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==",
+      "dev": true,
+      "engines": {
+        "node": ">=12.0.0"
+      },
+      "peerDependencies": {
+        "picomatch": "^3 || ^4"
+      },
+      "peerDependenciesMeta": {
+        "picomatch": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/follow-redirects": {
+      "version": "1.15.11",
+      "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz",
+      "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==",
+      "funding": [
+        {
+          "type": "individual",
+          "url": "https://github.com/sponsors/RubenVerborgh"
+        }
+      ],
+      "engines": {
+        "node": ">=4.0"
+      },
+      "peerDependenciesMeta": {
+        "debug": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/form-data": {
+      "version": "4.0.5",
+      "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz",
+      "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==",
+      "dependencies": {
+        "asynckit": "^0.4.0",
+        "combined-stream": "^1.0.8",
+        "es-set-tostringtag": "^2.1.0",
+        "hasown": "^2.0.2",
+        "mime-types": "^2.1.12"
+      },
+      "engines": {
+        "node": ">= 6"
+      }
+    },
+    "node_modules/fsevents": {
+      "version": "2.3.3",
+      "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
+      "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
+      "dev": true,
+      "hasInstallScript": true,
+      "optional": true,
+      "os": [
+        "darwin"
+      ],
+      "engines": {
+        "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
+      }
+    },
+    "node_modules/function-bind": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
+      "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/get-intrinsic": {
+      "version": "1.3.0",
+      "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
+      "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
+      "dependencies": {
+        "call-bind-apply-helpers": "^1.0.2",
+        "es-define-property": "^1.0.1",
+        "es-errors": "^1.3.0",
+        "es-object-atoms": "^1.1.1",
+        "function-bind": "^1.1.2",
+        "get-proto": "^1.0.1",
+        "gopd": "^1.2.0",
+        "has-symbols": "^1.1.0",
+        "hasown": "^2.0.2",
+        "math-intrinsics": "^1.1.0"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/get-proto": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
+      "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
+      "dependencies": {
+        "dunder-proto": "^1.0.1",
+        "es-object-atoms": "^1.0.0"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      }
+    },
+    "node_modules/gopd": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
+      "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
+      "engines": {
+        "node": ">= 0.4"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/has-symbols": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
+      "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
+      "engines": {
+        "node": ">= 0.4"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/has-tostringtag": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
+      "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
+      "dependencies": {
+        "has-symbols": "^1.0.3"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/hasown": {
+      "version": "2.0.2",
+      "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
+      "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
+      "dependencies": {
+        "function-bind": "^1.1.2"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      }
+    },
+    "node_modules/lodash": {
+      "version": "4.17.23",
+      "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz",
+      "integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w=="
+    },
+    "node_modules/lodash-es": {
+      "version": "4.17.23",
+      "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.23.tgz",
+      "integrity": "sha512-kVI48u3PZr38HdYz98UmfPnXl2DXrpdctLrFLCd3kOx1xUkOmpFPx7gCWWM5MPkL/fD8zb+Ph0QzjGFs4+hHWg=="
+    },
+    "node_modules/lodash-unified": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/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.21",
+      "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz",
+      "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==",
+      "dependencies": {
+        "@jridgewell/sourcemap-codec": "^1.5.5"
+      }
+    },
+    "node_modules/math-intrinsics": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
+      "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
+      "engines": {
+        "node": ">= 0.4"
+      }
+    },
+    "node_modules/memoize-one": {
+      "version": "6.0.0",
+      "resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-6.0.0.tgz",
+      "integrity": "sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw=="
+    },
+    "node_modules/mime-db": {
+      "version": "1.52.0",
+      "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
+      "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/mime-types": {
+      "version": "2.1.35",
+      "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
+      "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
+      "dependencies": {
+        "mime-db": "1.52.0"
+      },
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/nanoid": {
+      "version": "3.3.11",
+      "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
+      "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==",
+      "funding": [
+        {
+          "type": "github",
+          "url": "https://github.com/sponsors/ai"
+        }
+      ],
+      "bin": {
+        "nanoid": "bin/nanoid.cjs"
+      },
+      "engines": {
+        "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
+      }
+    },
+    "node_modules/normalize-wheel-es": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/normalize-wheel-es/-/normalize-wheel-es-1.2.0.tgz",
+      "integrity": "sha512-Wj7+EJQ8mSuXr2iWfnujrimU35R2W4FAErEyTmJoJ7ucwTn2hOUSsRehMb5RSYkxXGTM7Y9QpvPmp++w5ftoJw=="
+    },
+    "node_modules/picocolors": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
+      "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="
+    },
+    "node_modules/picomatch": {
+      "version": "4.0.3",
+      "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
+      "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
+      "dev": true,
+      "engines": {
+        "node": ">=12"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/jonschlinkert"
+      }
+    },
+    "node_modules/postcss": {
+      "version": "8.5.6",
+      "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz",
+      "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==",
+      "funding": [
+        {
+          "type": "opencollective",
+          "url": "https://opencollective.com/postcss/"
+        },
+        {
+          "type": "tidelift",
+          "url": "https://tidelift.com/funding/github/npm/postcss"
+        },
+        {
+          "type": "github",
+          "url": "https://github.com/sponsors/ai"
+        }
+      ],
+      "dependencies": {
+        "nanoid": "^3.3.11",
+        "picocolors": "^1.1.1",
+        "source-map-js": "^1.2.1"
+      },
+      "engines": {
+        "node": "^10 || ^12 || >=14"
+      }
+    },
+    "node_modules/proxy-from-env": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
+      "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="
+    },
+    "node_modules/rollup": {
+      "version": "4.57.1",
+      "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.57.1.tgz",
+      "integrity": "sha512-oQL6lgK3e2QZeQ7gcgIkS2YZPg5slw37hYufJ3edKlfQSGGm8ICoxswK15ntSzF/a8+h7ekRy7k7oWc3BQ7y8A==",
+      "dev": true,
+      "dependencies": {
+        "@types/estree": "1.0.8"
+      },
+      "bin": {
+        "rollup": "dist/bin/rollup"
+      },
+      "engines": {
+        "node": ">=18.0.0",
+        "npm": ">=8.0.0"
+      },
+      "optionalDependencies": {
+        "@rollup/rollup-android-arm-eabi": "4.57.1",
+        "@rollup/rollup-android-arm64": "4.57.1",
+        "@rollup/rollup-darwin-arm64": "4.57.1",
+        "@rollup/rollup-darwin-x64": "4.57.1",
+        "@rollup/rollup-freebsd-arm64": "4.57.1",
+        "@rollup/rollup-freebsd-x64": "4.57.1",
+        "@rollup/rollup-linux-arm-gnueabihf": "4.57.1",
+        "@rollup/rollup-linux-arm-musleabihf": "4.57.1",
+        "@rollup/rollup-linux-arm64-gnu": "4.57.1",
+        "@rollup/rollup-linux-arm64-musl": "4.57.1",
+        "@rollup/rollup-linux-loong64-gnu": "4.57.1",
+        "@rollup/rollup-linux-loong64-musl": "4.57.1",
+        "@rollup/rollup-linux-ppc64-gnu": "4.57.1",
+        "@rollup/rollup-linux-ppc64-musl": "4.57.1",
+        "@rollup/rollup-linux-riscv64-gnu": "4.57.1",
+        "@rollup/rollup-linux-riscv64-musl": "4.57.1",
+        "@rollup/rollup-linux-s390x-gnu": "4.57.1",
+        "@rollup/rollup-linux-x64-gnu": "4.57.1",
+        "@rollup/rollup-linux-x64-musl": "4.57.1",
+        "@rollup/rollup-openbsd-x64": "4.57.1",
+        "@rollup/rollup-openharmony-arm64": "4.57.1",
+        "@rollup/rollup-win32-arm64-msvc": "4.57.1",
+        "@rollup/rollup-win32-ia32-msvc": "4.57.1",
+        "@rollup/rollup-win32-x64-gnu": "4.57.1",
+        "@rollup/rollup-win32-x64-msvc": "4.57.1",
+        "fsevents": "~2.3.2"
+      }
+    },
+    "node_modules/source-map-js": {
+      "version": "1.2.1",
+      "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
+      "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/tinyglobby": {
+      "version": "0.2.15",
+      "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz",
+      "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==",
+      "dev": true,
+      "dependencies": {
+        "fdir": "^6.5.0",
+        "picomatch": "^4.0.3"
+      },
+      "engines": {
+        "node": ">=12.0.0"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/SuperchupuDev"
+      }
+    },
+    "node_modules/vite": {
+      "version": "7.3.1",
+      "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.1.tgz",
+      "integrity": "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==",
+      "dev": true,
+      "dependencies": {
+        "esbuild": "^0.27.0",
+        "fdir": "^6.5.0",
+        "picomatch": "^4.0.3",
+        "postcss": "^8.5.6",
+        "rollup": "^4.43.0",
+        "tinyglobby": "^0.2.15"
+      },
+      "bin": {
+        "vite": "bin/vite.js"
+      },
+      "engines": {
+        "node": "^20.19.0 || >=22.12.0"
+      },
+      "funding": {
+        "url": "https://github.com/vitejs/vite?sponsor=1"
+      },
+      "optionalDependencies": {
+        "fsevents": "~2.3.3"
+      },
+      "peerDependencies": {
+        "@types/node": "^20.19.0 || >=22.12.0",
+        "jiti": ">=1.21.0",
+        "less": "^4.0.0",
+        "lightningcss": "^1.21.0",
+        "sass": "^1.70.0",
+        "sass-embedded": "^1.70.0",
+        "stylus": ">=0.54.8",
+        "sugarss": "^5.0.0",
+        "terser": "^5.16.0",
+        "tsx": "^4.8.1",
+        "yaml": "^2.4.2"
+      },
+      "peerDependenciesMeta": {
+        "@types/node": {
+          "optional": true
+        },
+        "jiti": {
+          "optional": true
+        },
+        "less": {
+          "optional": true
+        },
+        "lightningcss": {
+          "optional": true
+        },
+        "sass": {
+          "optional": true
+        },
+        "sass-embedded": {
+          "optional": true
+        },
+        "stylus": {
+          "optional": true
+        },
+        "sugarss": {
+          "optional": true
+        },
+        "terser": {
+          "optional": true
+        },
+        "tsx": {
+          "optional": true
+        },
+        "yaml": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/vue": {
+      "version": "3.5.28",
+      "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.28.tgz",
+      "integrity": "sha512-BRdrNfeoccSoIZeIhyPBfvWSLFP4q8J3u8Ju8Ug5vu3LdD+yTM13Sg4sKtljxozbnuMu1NB1X5HBHRYUzFocKg==",
+      "dependencies": {
+        "@vue/compiler-dom": "3.5.28",
+        "@vue/compiler-sfc": "3.5.28",
+        "@vue/runtime-dom": "3.5.28",
+        "@vue/server-renderer": "3.5.28",
+        "@vue/shared": "3.5.28"
+      },
+      "peerDependencies": {
+        "typescript": "*"
+      },
+      "peerDependenciesMeta": {
+        "typescript": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/vue-router": {
+      "version": "4.6.4",
+      "resolved": "https://registry.npmjs.org/vue-router/-/vue-router-4.6.4.tgz",
+      "integrity": "sha512-Hz9q5sa33Yhduglwz6g9skT8OBPii+4bFn88w6J+J4MfEo4KRRpmiNG/hHHkdbRFlLBOqxN8y8gf2Fb0MTUgVg==",
+      "dependencies": {
+        "@vue/devtools-api": "^6.6.4"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/posva"
+      },
+      "peerDependencies": {
+        "vue": "^3.5.0"
+      }
+    }
+  }
+}

+ 22 - 0
admin-vue/package.json

@@ -0,0 +1,22 @@
+{
+  "name": "admin-vue",
+  "private": true,
+  "version": "0.0.0",
+  "type": "module",
+  "scripts": {
+    "dev": "vite",
+    "build": "vite build",
+    "preview": "vite preview"
+  },
+  "dependencies": {
+    "@element-plus/icons-vue": "^2.3.2",
+    "axios": "^1.13.5",
+    "element-plus": "^2.13.2",
+    "vue": "^3.5.25",
+    "vue-router": "^4.6.4"
+  },
+  "devDependencies": {
+    "@vitejs/plugin-vue": "^6.0.2",
+    "vite": "^7.3.1"
+  }
+}

+ 1 - 0
admin-vue/public/vite.svg

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

+ 9 - 0
admin-vue/src/App.vue

@@ -0,0 +1,9 @@
+<script setup>
+</script>
+
+<template>
+  <router-view />
+</template>
+
+<style scoped>
+</style>

+ 15 - 0
admin-vue/src/api/http.js

@@ -0,0 +1,15 @@
+import axios from 'axios'
+
+const http = axios.create({
+  baseURL: '/api',
+  timeout: 15000,
+})
+
+http.interceptors.response.use(
+  (resp) => resp.data,
+  (error) => {
+    return Promise.reject(error)
+  },
+)
+
+export default http

+ 1 - 0
admin-vue/src/assets/vue.svg

@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="37.07" height="36" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 198"><path fill="#41B883" d="M204.8 0H256L128 220.8L0 0h97.92L128 51.2L157.44 0h47.36Z"></path><path fill="#41B883" d="m0 0l128 220.8L256 0h-51.2L128 132.48L50.56 0H0Z"></path><path fill="#35495E" d="M50.56 0L128 133.12L204.8 0h-47.36L128 51.2L97.92 0H50.56Z"></path></svg>

+ 43 - 0
admin-vue/src/components/HelloWorld.vue

@@ -0,0 +1,43 @@
+<script setup>
+import { ref } from 'vue'
+
+defineProps({
+  msg: String,
+})
+
+const count = ref(0)
+</script>
+
+<template>
+  <h1>{{ msg }}</h1>
+
+  <div class="card">
+    <button type="button" @click="count++">count is {{ count }}</button>
+    <p>
+      Edit
+      <code>components/HelloWorld.vue</code> to test HMR
+    </p>
+  </div>
+
+  <p>
+    Check out
+    <a href="https://vuejs.org/guide/quick-start.html#local" target="_blank"
+      >create-vue</a
+    >, the official Vue + Vite starter
+  </p>
+  <p>
+    Learn more about IDE Support for Vue in the
+    <a
+      href="https://vuejs.org/guide/scaling-up/tooling.html#ide-support"
+      target="_blank"
+      >Vue Docs Scaling up Guide</a
+    >.
+  </p>
+  <p class="read-the-docs">Click on the Vite and Vue logos to learn more</p>
+</template>
+
+<style scoped>
+.read-the-docs {
+  color: #888;
+}
+</style>

+ 82 - 0
admin-vue/src/layouts/AdminLayout.vue

@@ -0,0 +1,82 @@
+<script setup>
+import { computed } from 'vue'
+import { useRoute, useRouter } from 'vue-router'
+import {
+  Document,
+  Food,
+  Menu,
+  Sugar,
+} from '@element-plus/icons-vue'
+
+const route = useRoute()
+const router = useRouter()
+
+const active = computed(() => route.path)
+
+function onSelect(index) {
+  if (index && index !== route.path) router.push(index)
+}
+</script>
+
+<template>
+  <el-container class="layout">
+    <el-aside width="220px" class="aside">
+      <div class="brand">点餐后台</div>
+      <el-menu :default-active="active" router class="menu" @select="onSelect">
+        <el-menu-item index="/orders">
+          <el-icon><Document /></el-icon>
+          <span>订单管理</span>
+        </el-menu-item>
+        <el-menu-item index="/dish">
+          <el-icon><Food /></el-icon>
+          <span>菜品管理</span>
+        </el-menu-item>
+        <el-menu-item index="/category">
+          <el-icon><Menu /></el-icon>
+          <span>分类管理</span>
+        </el-menu-item>
+        <el-menu-item index="/flavor">
+          <el-icon><Sugar /></el-icon>
+          <span>口味管理</span>
+        </el-menu-item>
+      </el-menu>
+    </el-aside>
+
+    <el-container>
+      <el-header class="header">
+        <div class="title">后台管理系统</div>
+      </el-header>
+      <el-main class="main">
+        <router-view />
+      </el-main>
+    </el-container>
+  </el-container>
+</template>
+
+<style scoped>
+.layout {
+  height: 100vh;
+}
+.aside {
+  border-right: 1px solid var(--el-border-color);
+  background: var(--el-bg-color);
+}
+.brand {
+  height: 56px;
+  display: flex;
+  align-items: center;
+  padding: 0 16px;
+  font-weight: 700;
+}
+.header {
+  display: flex;
+  align-items: center;
+  border-bottom: 1px solid var(--el-border-color);
+}
+.main {
+  background: #f5f7fb;
+}
+.menu {
+  border-right: 0;
+}
+</style>

+ 9 - 0
admin-vue/src/main.js

@@ -0,0 +1,9 @@
+import { createApp } from 'vue'
+import ElementPlus from 'element-plus'
+import 'element-plus/dist/index.css'
+import './style.css'
+import App from './App.vue'
+
+import router from './router'
+
+createApp(App).use(router).use(ElementPlus).mount('#app')

+ 28 - 0
admin-vue/src/router/index.js

@@ -0,0 +1,28 @@
+import { createRouter, createWebHistory } from 'vue-router'
+
+import AdminLayout from '../layouts/AdminLayout.vue'
+import OrdersList from '../views/OrdersList.vue'
+import DishList from '../views/DishList.vue'
+import DishCategory from '../views/DishCategory.vue'
+import Flavor from '../views/Flavor.vue'
+
+const routes = [
+  {
+    path: '/',
+    component: AdminLayout,
+    redirect: '/orders',
+    children: [
+      { path: '/orders', name: 'orders', component: OrdersList },
+      { path: '/dish', name: 'dish', component: DishList },
+      { path: '/category', name: 'category', component: DishCategory },
+      { path: '/flavor', name: 'flavor', component: Flavor },
+    ],
+  },
+]
+
+const router = createRouter({
+  history: createWebHistory(),
+  routes,
+})
+
+export default router

+ 68 - 0
admin-vue/src/style.css

@@ -0,0 +1,68 @@
+:root {
+  font-family: system-ui, Avenir, Helvetica, Arial, sans-serif;
+  line-height: 1.5;
+  font-weight: 400;
+
+  color-scheme: light;
+  color: #213547;
+  background-color: #ffffff;
+
+  font-synthesis: none;
+  text-rendering: optimizeLegibility;
+  -webkit-font-smoothing: antialiased;
+  -moz-osx-font-smoothing: grayscale;
+}
+
+a {
+  font-weight: 500;
+  color: #646cff;
+  text-decoration: inherit;
+}
+a:hover {
+  color: #535bf2;
+}
+
+body {
+  margin: 0;
+  min-width: 320px;
+  min-height: 100vh;
+}
+
+html,
+body,
+#app {
+  height: 100%;
+}
+
+h1 {
+  font-size: 3.2em;
+  line-height: 1.1;
+}
+
+button {
+  border-radius: 8px;
+  border: 1px solid transparent;
+  padding: 0.6em 1.2em;
+  font-size: 1em;
+  font-weight: 500;
+  font-family: inherit;
+  background-color: #f9f9f9;
+  cursor: pointer;
+  transition: border-color 0.25s;
+}
+button:hover {
+  border-color: #646cff;
+}
+button:focus,
+button:focus-visible {
+  outline: 4px auto -webkit-focus-ring-color;
+}
+
+.card {
+  padding: 2em;
+}
+
+#app {
+  width: 100%;
+  min-height: 100vh;
+}

+ 187 - 0
admin-vue/src/views/DishCategory.vue

@@ -0,0 +1,187 @@
+<script setup>
+import { onMounted, reactive, ref } from 'vue'
+import { ElMessage, ElMessageBox } from 'element-plus'
+import http from '../api/http'
+
+const loading = ref(false)
+const list = ref([])
+const selectedRows = ref([])
+
+// 弹窗相关
+const dialogVisible = ref(false)
+const dialogTitle = ref('')
+const isEdit = ref(false)
+const formRef = ref(null)
+const form = reactive({
+  id: null,
+  categoryName: '',
+  sort: 0,
+  status: 0,
+})
+
+const rules = {
+  categoryName: [{ required: true, message: '请输入分类名称', trigger: 'blur' }],
+  sort: [{ required: true, message: '请输入排序', trigger: 'blur' }],
+}
+
+async function fetchList() {
+  loading.value = true
+  try {
+    const res = await http.get('/dishcate/list')
+    list.value = res?.data || res
+  } finally {
+    loading.value = false
+  }
+}
+
+function onSelectionChange(rows) {
+  selectedRows.value = rows
+}
+
+// 新增
+function onAdd() {
+  isEdit.value = false
+  dialogTitle.value = '新增分类'
+  resetForm()
+  dialogVisible.value = true
+}
+
+// 编辑
+function onEdit(row) {
+  isEdit.value = true
+  dialogTitle.value = '编辑分类'
+  Object.assign(form, row)
+  dialogVisible.value = true
+}
+
+// 保存
+async function onSave() {
+  const valid = await formRef.value?.validate().catch(() => false)
+  if (!valid) return
+
+  const url = isEdit.value ? '/dishcate/upd' : '/dishcate/add'
+  const res = await http.post(url, form)
+  
+  if (res.code === 200 || res.code === 0 || !res.code) {
+    ElMessage.success(isEdit.value ? '修改成功' : '新增成功')
+    dialogVisible.value = false
+    fetchList()
+  } else {
+    ElMessage.error(res.msg || '操作失败')
+  }
+}
+
+// 批量删除
+async function onBatchDelete() {
+  if (selectedRows.value.length === 0) {
+    ElMessage.warning('请选择要删除的分类')
+    return
+  }
+  
+  try {
+    await ElMessageBox.confirm(
+      `确定删除选中的 ${selectedRows.value.length} 个分类吗?`,
+      '确认删除',
+      { type: 'warning' }
+    )
+    
+    const ids = selectedRows.value.map(r => r.id)
+    const res = await http.post('/dishcate/del', ids)
+    
+    if (res.code === 200 || res.code === 0 || !res.code) {
+      ElMessage.success('删除成功')
+      fetchList()
+    } else {
+      ElMessage.error(res.msg || '删除失败')
+    }
+  } catch {
+    // 取消
+  }
+}
+
+// 单行删除
+async function onDelete(row) {
+  try {
+    await ElMessageBox.confirm('确定删除该分类吗?', '确认删除', { type: 'warning' })
+    const res = await http.post('/dishcate/del', [row.id])
+    
+    if (res.code === 200 || res.code === 0 || !res.code) {
+      ElMessage.success('删除成功')
+      fetchList()
+    } else {
+      ElMessage.error(res.msg || '删除失败')
+    }
+  } catch {
+    // 取消
+  }
+}
+
+function resetForm() {
+  Object.assign(form, {
+    id: null,
+    categoryName: '',
+    sort: 0,
+    status: 0,
+  })
+  formRef.value?.resetFields()
+}
+
+onMounted(fetchList)
+</script>
+
+<template>
+  <el-card>
+    <template #header>
+      <div style="display:flex;align-items:center;justify-content:space-between;gap:12px;flex-wrap:wrap;">
+        <div style="font-weight:700;">分类管理</div>
+        <div style="display:flex;gap:8px;">
+          <el-button type="success" @click="onAdd">新增</el-button>
+          <el-button type="danger" :disabled="selectedRows.length === 0" @click="onBatchDelete">
+            批量删除
+          </el-button>
+        </div>
+      </div>
+    </template>
+
+    <el-table :data="list" v-loading="loading" style="width: 100%" @selection-change="onSelectionChange">
+      <el-table-column type="selection" width="55" />
+      <el-table-column prop="categoryName" label="分类名称" min-width="200" />
+      <el-table-column prop="sort" label="排序" width="100" />
+      <el-table-column label="状态" width="80">
+        <template #default="scope">
+          <el-tag :type="scope.row.status === 0 ? 'success' : 'info'">
+            {{ scope.row.status === 0 ? '启用' : '禁用' }}
+          </el-tag>
+        </template>
+      </el-table-column>
+      <el-table-column label="操作" width="150" fixed="right">
+        <template #default="scope">
+          <el-button size="small" @click="onEdit(scope.row)">编辑</el-button>
+          <el-button size="small" type="danger" @click="onDelete(scope.row)">删除</el-button>
+        </template>
+      </el-table-column>
+    </el-table>
+
+    <!-- 新增/编辑弹窗 -->
+    <el-dialog v-model="dialogVisible" :title="dialogTitle" width="500px" :close-on-click-modal="false">
+      <el-form ref="formRef" :model="form" :rules="rules" label-width="100px">
+        <el-form-item label="分类名称" prop="categoryName">
+          <el-input v-model="form.categoryName" />
+        </el-form-item>
+        <el-form-item label="排序" prop="sort">
+          <el-input-number v-model="form.sort" :min="0" style="width:100%" />
+        </el-form-item>
+        <el-form-item label="状态" prop="status">
+          <el-select v-model="form.status" style="width:100%">
+            <el-option label="启用" :value="0" />
+            <el-option label="禁用" :value="1" />
+          </el-select>
+        </el-form-item>
+      </el-form>
+      <template #footer>
+        <el-button @click="dialogVisible = false">取消</el-button>
+        <el-button type="primary" @click="onSave">保存</el-button>
+      </template>
+    </el-dialog>
+  </el-card>
+</template>

+ 474 - 0
admin-vue/src/views/DishList.vue

@@ -0,0 +1,474 @@
+<script setup>
+import { onMounted, reactive, ref } from 'vue'
+import { ElMessage, ElMessageBox } from 'element-plus'
+import { Plus } from '@element-plus/icons-vue'
+import http from '../api/http'
+
+const loading = ref(false)
+const tableData = ref([])
+const total = ref(0)
+const selectedRows = ref([])
+
+const categories = ref([])
+const flavors = ref([])
+const categoryMap = ref({})
+const flavorMap = ref({})
+
+const query = reactive({
+  pageNum: 1,
+  pageSize: 10,
+  dishName: '',
+  categoryId: null,
+  flavorId: null,
+  status: null,
+})
+
+// 弹窗相关
+const dialogVisible = ref(false)
+const dialogTitle = ref('')
+const isEdit = ref(false)
+const formRef = ref(null)
+const form = reactive({
+  id: null,
+  dishName: '',
+  categoryId: '',
+  flavorId: '',
+  coreMaterial: '',
+  price: null,
+  costPrice: null,
+  stock: null,
+  sales: null,
+  tasteFeature: '',
+  adaptScene: '',
+  coverImg: '',
+  description: '',
+  status: 1,
+})
+
+const rules = {
+  dishName: [{ required: true, message: '请输入菜品名', trigger: 'blur' }],
+  categoryId: [{ required: true, message: '请选择所属分类', trigger: 'change' }],
+  flavorId: [{ required: true, message: '请选择口味', trigger: 'change' }],
+  price: [{ required: true, message: '请输入价格', trigger: 'blur' }],
+  status: [{ required: true, message: '请选择状态', trigger: 'change' }],
+}
+
+const coverImgFile = ref(null)
+const coverImgPreview = ref('')
+
+async function fetchDicts() {
+  const [catRes, flaRes] = await Promise.all([
+    http.get('/dishcate/list'),
+    http.get('/fla/list'),
+  ])
+
+  const catList = catRes?.data || catRes || []
+  const flaList = flaRes?.data || flaRes || []
+
+  // 强制将 id 转为字符串,避免 JS 精度丢失
+  categories.value = Array.isArray(catList) ? catList.map(c => ({...c, id: String(c.id)})) : []
+  flavors.value = Array.isArray(flaList) ? flaList.map(f => ({...f, id: String(f.id)})) : []
+
+  const cm = {}
+  for (const c of categories.value) cm[c.id] = c.categoryName
+  categoryMap.value = cm
+
+  const fm = {}
+  for (const f of flavors.value) fm[f.id] = f.flavorName
+  flavorMap.value = fm
+}
+
+async function fetchList() {
+  loading.value = true
+  try {
+    const res = await http.post('/dis/list', query)
+    const page = res?.data || res
+    tableData.value = page?.records || []
+    total.value = page?.total || 0
+  } finally {
+    loading.value = false
+  }
+}
+
+function onSearch() {
+  query.pageNum = 1
+  fetchList()
+}
+
+function onSizeChange(size) {
+  query.pageSize = size
+  query.pageNum = 1
+  fetchList()
+}
+
+function onCurrentChange(page) {
+  query.pageNum = page
+  fetchList()
+}
+
+function onSelectionChange(rows) {
+  selectedRows.value = rows
+}
+
+// 新增
+function onAdd() {
+  isEdit.value = false
+  dialogTitle.value = '新增菜品'
+  resetForm()
+  dialogVisible.value = true
+}
+
+// 编辑
+function onEdit(row) {
+  isEdit.value = true
+  dialogTitle.value = '编辑菜品'
+  Object.assign(form, row)
+  // 设置已存在的图片预览
+  coverImgPreview.value = row.coverImg || ''
+  coverImgFile.value = null
+  dialogVisible.value = true
+}
+
+// 保存
+async function onSave() {
+  const valid = await formRef.value?.validate().catch(() => false)
+  if (!valid) return
+
+  const url = isEdit.value ? '/dis/upd' : '/dis/add'
+  
+  // 使用 FormData 支持文件上传
+  const formData = new FormData()
+  if (isEdit.value && form.id) {
+    formData.append('id', form.id)
+  }
+  formData.append('dishName', form.dishName)
+  formData.append('categoryId', form.categoryId)
+  formData.append('flavorId', form.flavorId)
+  if (form.coreMaterial) formData.append('coreMaterial', form.coreMaterial)
+  formData.append('price', form.price)
+  if (form.costPrice) formData.append('costPrice', form.costPrice)
+  if (form.stock !== null && form.stock !== undefined) formData.append('stock', form.stock)
+  if (form.sales !== null && form.sales !== undefined) formData.append('sales', form.sales)
+  if (form.tasteFeature) formData.append('tasteFeature', form.tasteFeature)
+  if (form.adaptScene) formData.append('adaptScene', form.adaptScene)
+  if (form.description) formData.append('description', form.description)
+  if (form.coverImg) formData.append('coverImg', form.coverImg)
+  formData.append('status', form.status)
+  
+  // 如果有新上传的文件
+  if (coverImgFile.value) {
+    formData.append('multipartFile', coverImgFile.value)
+  }
+  
+  const res = await http.post(url, formData, {
+    headers: { 'Content-Type': 'multipart/form-data' }
+  })
+  
+  if (res.code === 200 || res.code === 0 || !res.code) {
+    ElMessage.success(isEdit.value ? '修改成功' : '新增成功')
+    dialogVisible.value = false
+    coverImgFile.value = null
+    coverImgPreview.value = ''
+    fetchList()
+  } else {
+    ElMessage.error(res.msg || '操作失败')
+  }
+}
+
+// 处理图片选择变化
+function onCoverImgChange(file) {
+  const maxSize = 10 * 1024 * 1024 // 10MB
+  if (file.raw.size > maxSize) {
+    ElMessage.warning('图片大小不能超过10MB')
+    return
+  }
+  coverImgFile.value = file.raw
+  coverImgPreview.value = URL.createObjectURL(file.raw)
+}
+
+// 移除已选图片
+function onCoverImgRemove() {
+  coverImgFile.value = null
+  coverImgPreview.value = ''
+  form.coverImg = ''
+}
+
+// 批量删除
+async function onBatchDelete() {
+  if (selectedRows.value.length === 0) {
+    ElMessage.warning('请选择要删除的菜品')
+    return
+  }
+  
+  try {
+    await ElMessageBox.confirm(
+      `确定删除选中的 ${selectedRows.value.length} 个菜品吗?`,
+      '确认删除',
+      { type: 'warning' }
+    )
+    
+    const ids = selectedRows.value.map(r => r.id)
+    const res = await http.post('/dis/del', ids)
+    
+    if (res.code === 200 || res.code === 0 || !res.code) {
+      ElMessage.success('删除成功')
+      fetchList()
+    } else {
+      ElMessage.error(res.msg || '删除失败')
+    }
+  } catch {
+    // 取消
+  }
+}
+
+// 单行删除
+async function onDelete(row) {
+  try {
+    await ElMessageBox.confirm('确定删除该菜品吗?', '确认删除', { type: 'warning' })
+    const res = await http.post('/dis/del', [row.id])
+    
+    if (res.code === 200 || res.code === 0 || !res.code) {
+      ElMessage.success('删除成功')
+      fetchList()
+    } else {
+      ElMessage.error(res.msg || '删除失败')
+    }
+  } catch {
+    // 取消
+  }
+}
+
+function resetForm() {
+  Object.assign(form, {
+    id: null,
+    dishName: '',
+    categoryId: '',
+    flavorId: '',
+    coreMaterial: '',
+    price: null,
+    costPrice: null,
+    stock: null,
+    sales: null,
+    tasteFeature: '',
+    adaptScene: '',
+    coverImg: '',
+    description: '',
+    status: 1,
+  })
+  coverImgFile.value = null
+  coverImgPreview.value = ''
+  formRef.value?.resetFields()
+}
+
+onMounted(async () => {
+  await fetchDicts()
+  await fetchList()
+})
+</script>
+
+<template>
+  <el-card>
+    <template #header>
+      <div style="display:flex;align-items:center;justify-content:space-between;gap:12px;flex-wrap:wrap;">
+        <div style="font-weight:700;">菜品列表</div>
+        <div style="display:flex;gap:8px;flex-wrap:wrap;">
+          <el-input v-model="query.dishName" placeholder="菜品名" style="width:200px" clearable />
+          <el-select v-model="query.categoryId" placeholder="所属分类" style="width:160px" clearable>
+            <el-option
+              v-for="c in categories"
+              :key="c.id"
+              :label="c.categoryName"
+              :value="c.id"
+            />
+          </el-select>
+          <el-select v-model="query.flavorId" placeholder="口味" style="width:160px" clearable>
+            <el-option
+              v-for="f in flavors"
+              :key="f.id"
+              :label="f.flavorName"
+              :value="f.id"
+            />
+          </el-select>
+          <el-select v-model="query.status" placeholder="状态" style="width:140px" clearable>
+            <el-option label="下架" :value="0" />
+            <el-option label="上架" :value="1" />
+          </el-select>
+          <el-button type="primary" @click="onSearch">查询</el-button>
+          <el-button type="success" @click="onAdd">新增</el-button>
+          <el-button type="danger" :disabled="selectedRows.length === 0" @click="onBatchDelete">批量删除</el-button>
+        </div>
+      </div>
+    </template>
+
+    <el-table :data="tableData" v-loading="loading" style="width: 100%" @selection-change="onSelectionChange">
+      <el-table-column type="selection" width="55" />
+      <el-table-column label="封面图" width="110">
+        <template #default="scope">
+          <el-image
+            v-if="scope.row.coverImg"
+            :src="scope.row.coverImg"
+            :preview-src-list="[scope.row.coverImg]"
+            fit="cover"
+            style="width: 72px; height: 48px; border-radius: 6px;"
+          />
+          <span v-else>-</span>
+        </template>
+      </el-table-column>
+      <el-table-column prop="id" label="ID" width="90" />
+      <el-table-column prop="dishName" label="菜品名" min-width="140" />
+      <el-table-column label="所属分类" min-width="140">
+        <template #default="scope">
+          {{ categoryMap[scope.row.categoryId] || scope.row.categoryId || '-' }}
+        </template>
+      </el-table-column>
+      <el-table-column label="口味" min-width="140">
+        <template #default="scope">
+          {{ flavorMap[scope.row.flavorId] || scope.row.flavorId || '-' }}
+        </template>
+      </el-table-column>
+      <el-table-column prop="coreMaterial" label="核心食材" min-width="160" show-overflow-tooltip />
+      <el-table-column prop="price" label="价格" width="110" />
+      <el-table-column prop="costPrice" label="成本价" width="110" />
+      <el-table-column prop="stock" label="库存" width="90" />
+      <el-table-column prop="sales" label="销量" width="90" />
+      <el-table-column prop="tasteFeature" label="口味特点" min-width="160" show-overflow-tooltip />
+      <el-table-column prop="adaptScene" label="适配场景" min-width="160" show-overflow-tooltip />
+      <el-table-column prop="description" label="菜品描述" min-width="200" show-overflow-tooltip />
+      <el-table-column label="状态" width="90">
+        <template #default="scope">
+          <el-tag :type="scope.row.status === 1 ? 'success' : 'info'">
+            {{ scope.row.status === 1 ? '上架' : '下架' }}
+          </el-tag>
+        </template>
+      </el-table-column>
+      <el-table-column label="操作" width="150" fixed="right">
+        <template #default="scope">
+          <el-button size="small" @click="onEdit(scope.row)">编辑</el-button>
+          <el-button size="small" type="danger" @click="onDelete(scope.row)">删除</el-button>
+        </template>
+      </el-table-column>
+    </el-table>
+
+    <div style="display:flex;justify-content:flex-end;margin-top:12px;">
+      <el-pagination
+        background
+        layout="total, sizes, prev, pager, next"
+        :total="total"
+        :page-size="query.pageSize"
+        :current-page="query.pageNum"
+        @size-change="onSizeChange"
+        @current-change="onCurrentChange"
+      />
+    </div>
+
+    <!-- 新增/编辑弹窗 -->
+    <el-dialog v-model="dialogVisible" :title="dialogTitle" width="700px" :close-on-click-modal="false">
+      <el-form ref="formRef" :model="form" :rules="rules" label-width="100px">
+        <el-row :gutter="20">
+          <el-col :span="12">
+            <el-form-item label="菜品名" prop="dishName">
+              <el-input v-model="form.dishName" />
+            </el-form-item>
+          </el-col>
+          <el-col :span="12">
+            <el-form-item label="所属分类" prop="categoryId">
+              <el-select v-model="form.categoryId" style="width:100%">
+                <el-option
+                  v-for="c in categories"
+                  :key="c.id"
+                  :label="c.categoryName"
+                  :value="c.id"
+                />
+              </el-select>
+            </el-form-item>
+          </el-col>
+        </el-row>
+        <el-row :gutter="20">
+          <el-col :span="12">
+            <el-form-item label="口味" prop="flavorId">
+              <el-select v-model="form.flavorId" style="width:100%">
+                <el-option
+                  v-for="f in flavors"
+                  :key="f.id"
+                  :label="f.flavorName"
+                  :value="f.id"
+                />
+              </el-select>
+            </el-form-item>
+          </el-col>
+          <el-col :span="12">
+            <el-form-item label="状态" prop="status">
+              <el-select v-model="form.status" style="width:100%">
+                <el-option label="下架" :value="0" />
+                <el-option label="上架" :value="1" />
+              </el-select>
+            </el-form-item>
+          </el-col>
+        </el-row>
+        <el-row :gutter="20">
+          <el-col :span="12">
+            <el-form-item label="价格" prop="price">
+              <el-input-number v-model="form.price" :min="0" :precision="2" style="width:100%" />
+            </el-form-item>
+          </el-col>
+          <el-col :span="12">
+            <el-form-item label="成本价" prop="costPrice">
+              <el-input-number v-model="form.costPrice" :min="0" :precision="2" style="width:100%" />
+            </el-form-item>
+          </el-col>
+        </el-row>
+        <el-row :gutter="20">
+          <el-col :span="12">
+            <el-form-item label="库存">
+              <el-input-number v-model="form.stock" :min="-1" style="width:100%" />
+            </el-form-item>
+          </el-col>
+          <el-col :span="12">
+            <el-form-item label="销量">
+              <el-input-number v-model="form.sales" :min="0" style="width:100%" />
+            </el-form-item>
+          </el-col>
+        </el-row>
+        <el-form-item label="核心食材">
+          <el-input v-model="form.coreMaterial" />
+        </el-form-item>
+        <el-form-item label="口味特点">
+          <el-input v-model="form.tasteFeature" />
+        </el-form-item>
+        <el-form-item label="适配场景">
+          <el-input v-model="form.adaptScene" />
+        </el-form-item>
+        <el-form-item label="封面图">
+          <div style="display:flex;align-items:flex-start;gap:12px;">
+            <el-upload
+              class="cover-uploader"
+              action="#"
+              :auto-upload="false"
+              :show-file-list="false"
+              :on-change="onCoverImgChange"
+              :on-remove="onCoverImgRemove"
+              accept="image/*"
+            >
+              <img v-if="coverImgPreview" :src="coverImgPreview" class="cover-preview" style="width:120px;height:80px;object-fit:cover;border-radius:6px;" />
+              <div v-else style="width:120px;height:80px;border:1px dashed #ccc;border-radius:6px;display:flex;flex-direction:column;align-items:center;justify-content:center;cursor:pointer;">
+                <el-icon style="font-size:28px;color:#999;"><Plus /></el-icon>
+                <span style="font-size:12px;color:#999;margin-top:4px;">上传图片</span>
+              </div>
+            </el-upload>
+            <el-button v-if="coverImgPreview" size="small" type="danger" @click="onCoverImgRemove">删除</el-button>
+          </div>
+          <div v-if="form.coverImg && !coverImgFile" style="margin-top:8px;font-size:12px;color:#666;">
+            当前图片: {{ form.coverImg }}
+          </div>
+        </el-form-item>
+        <el-form-item label="菜品描述">
+          <el-input v-model="form.description" type="textarea" :rows="3" />
+        </el-form-item>
+      </el-form>
+      <template #footer>
+        <el-button @click="dialogVisible = false">取消</el-button>
+        <el-button type="primary" @click="onSave">保存</el-button>
+      </template>
+    </el-dialog>
+  </el-card>
+</template>

+ 187 - 0
admin-vue/src/views/Flavor.vue

@@ -0,0 +1,187 @@
+<script setup>
+import { onMounted, reactive, ref } from 'vue'
+import { ElMessage, ElMessageBox } from 'element-plus'
+import http from '../api/http'
+
+const loading = ref(false)
+const list = ref([])
+const selectedRows = ref([])
+
+// 弹窗相关
+const dialogVisible = ref(false)
+const dialogTitle = ref('')
+const isEdit = ref(false)
+const formRef = ref(null)
+const form = reactive({
+  id: null,
+  flavorName: '',
+  sort: 0,
+  status: 0,
+})
+
+const rules = {
+  flavorName: [{ required: true, message: '请输入口味名称', trigger: 'blur' }],
+  sort: [{ required: true, message: '请输入排序', trigger: 'blur' }],
+}
+
+async function fetchList() {
+  loading.value = true
+  try {
+    const res = await http.get('/fla/list')
+    list.value = res?.data || res
+  } finally {
+    loading.value = false
+  }
+}
+
+function onSelectionChange(rows) {
+  selectedRows.value = rows
+}
+
+// 新增
+function onAdd() {
+  isEdit.value = false
+  dialogTitle.value = '新增口味'
+  resetForm()
+  dialogVisible.value = true
+}
+
+// 编辑
+function onEdit(row) {
+  isEdit.value = true
+  dialogTitle.value = '编辑口味'
+  Object.assign(form, row)
+  dialogVisible.value = true
+}
+
+// 保存
+async function onSave() {
+  const valid = await formRef.value?.validate().catch(() => false)
+  if (!valid) return
+
+  const url = isEdit.value ? '/fla/upd' : '/fla/add'
+  const res = await http.post(url, form)
+  
+  if (res.code === 200 || res.code === 0 || !res.code) {
+    ElMessage.success(isEdit.value ? '修改成功' : '新增成功')
+    dialogVisible.value = false
+    fetchList()
+  } else {
+    ElMessage.error(res.msg || '操作失败')
+  }
+}
+
+// 批量删除
+async function onBatchDelete() {
+  if (selectedRows.value.length === 0) {
+    ElMessage.warning('请选择要删除的口味')
+    return
+  }
+  
+  try {
+    await ElMessageBox.confirm(
+      `确定删除选中的 ${selectedRows.value.length} 个口味吗?`,
+      '确认删除',
+      { type: 'warning' }
+    )
+    
+    const ids = selectedRows.value.map(r => r.id)
+    const res = await http.post('/fla/del', ids)
+    
+    if (res.code === 200 || res.code === 0 || !res.code) {
+      ElMessage.success('删除成功')
+      fetchList()
+    } else {
+      ElMessage.error(res.msg || '删除失败')
+    }
+  } catch {
+    // 取消
+  }
+}
+
+// 单行删除
+async function onDelete(row) {
+  try {
+    await ElMessageBox.confirm('确定删除该口味吗?', '确认删除', { type: 'warning' })
+    const res = await http.post('/fla/del', [row.id])
+    
+    if (res.code === 200 || res.code === 0 || !res.code) {
+      ElMessage.success('删除成功')
+      fetchList()
+    } else {
+      ElMessage.error(res.msg || '删除失败')
+    }
+  } catch {
+    // 取消
+  }
+}
+
+function resetForm() {
+  Object.assign(form, {
+    id: null,
+    flavorName: '',
+    sort: 0,
+    status: 0,
+  })
+  formRef.value?.resetFields()
+}
+
+onMounted(fetchList)
+</script>
+
+<template>
+  <el-card>
+    <template #header>
+      <div style="display:flex;align-items:center;justify-content:space-between;gap:12px;flex-wrap:wrap;">
+        <div style="font-weight:700;">口味管理</div>
+        <div style="display:flex;gap:8px;">
+          <el-button type="success" @click="onAdd">新增</el-button>
+          <el-button type="danger" :disabled="selectedRows.length === 0" @click="onBatchDelete">
+            批量删除
+          </el-button>
+        </div>
+      </div>
+    </template>
+
+    <el-table :data="list" v-loading="loading" style="width: 100%" @selection-change="onSelectionChange">
+      <el-table-column type="selection" width="55" />
+      <el-table-column prop="flavorName" label="口味名称" min-width="200" />
+      <el-table-column prop="sort" label="排序" width="100" />
+      <el-table-column label="状态" width="80">
+        <template #default="scope">
+          <el-tag :type="scope.row.status === 0 ? 'success' : 'info'">
+            {{ scope.row.status === 0 ? '启用' : '禁用' }}
+          </el-tag>
+        </template>
+      </el-table-column>
+      <el-table-column label="操作" width="150" fixed="right">
+        <template #default="scope">
+          <el-button size="small" @click="onEdit(scope.row)">编辑</el-button>
+          <el-button size="small" type="danger" @click="onDelete(scope.row)">删除</el-button>
+        </template>
+      </el-table-column>
+    </el-table>
+
+    <!-- 新增/编辑弹窗 -->
+    <el-dialog v-model="dialogVisible" :title="dialogTitle" width="500px" :close-on-click-modal="false">
+      <el-form ref="formRef" :model="form" :rules="rules" label-width="100px">
+        <el-form-item label="口味名称" prop="flavorName">
+          <el-input v-model="form.flavorName" />
+        </el-form-item>
+        <el-form-item label="排序" prop="sort">
+          <el-input-number v-model="form.sort" :min="0" style="width:100%" />
+        </el-form-item>
+        <el-form-item label="状态" prop="status">
+          <el-select v-model="form.status" style="width:100%">
+            <el-option label="启用" :value="0" />
+            <el-option label="禁用" :value="1" />
+          </el-select>
+        </el-form-item>
+      </el-form>
+      <template #footer>
+        <el-button @click="dialogVisible = false">取消</el-button>
+        <el-button type="primary" @click="onSave">保存</el-button>
+      </template>
+    </el-dialog>
+  </el-card>
+</template>

+ 147 - 0
admin-vue/src/views/OrdersList.vue

@@ -0,0 +1,147 @@
+<script setup>
+import { onMounted, reactive, ref } from 'vue'
+import { ElMessage, ElMessageBox } from 'element-plus'
+import http from '../api/http'
+
+const loading = ref(false)
+const tableData = ref([])
+const total = ref(0)
+
+// 状态修改弹窗
+const statusDialogVisible = ref(false)
+const currentOrder = ref(null)
+const newStatus = ref(null)
+
+const query = reactive({
+  pageNum: 1,
+  pageSize: 10,
+  status: null,
+  orderNumber: '',
+})
+
+async function fetchList() {
+  loading.value = true
+  try {
+    const res = await http.post('/horders/list', query)
+    const page = res?.data || res
+    tableData.value = page?.records || []
+    total.value = page?.total || 0
+  } finally {
+    loading.value = false
+  }
+}
+
+function onSearch() {
+  query.pageNum = 1
+  fetchList()
+}
+
+function onSizeChange(size) {
+  query.pageSize = size
+  query.pageNum = 1
+  fetchList()
+}
+
+function onCurrentChange(page) {
+  query.pageNum = page
+  fetchList()
+}
+
+// 打开状态修改弹窗
+function onChangeStatus(row) {
+  currentOrder.value = row
+  newStatus.value = row.status
+  statusDialogVisible.value = true
+}
+
+// 确认修改状态
+async function onConfirmStatusChange() {
+  if (newStatus.value === null || newStatus.value === undefined) {
+    ElMessage.warning('请选择状态')
+    return
+  }
+  try {
+    const res = await http.post('/horders/updateStatus', {
+      id: currentOrder.value.id,
+      status: newStatus.value
+    })
+    if (res.code === 200 || res.code === 0 || !res.code) {
+      ElMessage.success('状态修改成功')
+      statusDialogVisible.value = false
+      fetchList()
+    } else {
+      ElMessage.error(res.msg || '状态修改失败')
+    }
+  } catch (err) {
+    ElMessage.error('状态修改失败')
+  }
+}
+
+onMounted(fetchList)
+</script>
+
+<template>
+  <el-card>
+    <template #header>
+      <div style="display:flex;align-items:center;justify-content:space-between;gap:12px;flex-wrap:wrap;">
+        <div style="font-weight:700;">订单列表</div>
+        <div style="display:flex;gap:8px;flex-wrap:wrap;">
+          <el-input v-model="query.orderNumber" placeholder="订单号" style="width:200px" clearable />
+          <el-select v-model="query.status" placeholder="状态" style="width:140px" clearable>
+            <el-option label="待支付" :value="0" />
+            <el-option label="已支付" :value="1" />
+            <el-option label="已取消" :value="2" />
+          </el-select>
+          <el-button type="primary" @click="onSearch">查询</el-button>
+        </div>
+      </div>
+    </template>
+
+    <el-table :data="tableData" v-loading="loading" style="width: 100%">
+      <el-table-column prop="orderNumber" label="订单号" min-width="160" />
+      <el-table-column label="状态" width="100">
+        <template #default="scope">
+          <el-tag v-if="scope.row.status === 0" type="danger">待支付</el-tag>
+          <el-tag v-else-if="scope.row.status === 1" type="success">已支付</el-tag>
+          <el-tag v-else-if="scope.row.status === 2" type="info">已取消</el-tag>
+          <span v-else>{{ scope.row.status }}</span>
+        </template>
+      </el-table-column>
+      <el-table-column prop="totalAmount" label="商品总价" width="120" />
+      <el-table-column prop="finalAmount" label="订单总额" width="120" />
+      <el-table-column prop="createTime" label="创建时间" min-width="180" />
+      <el-table-column label="操作" width="120" fixed="right">
+        <template #default="scope">
+          <el-button size="small" type="primary" @click="onChangeStatus(scope.row)">修改状态</el-button>
+        </template>
+      </el-table-column>
+    </el-table>
+
+    <div style="display:flex;justify-content:flex-end;margin-top:12px;">
+      <el-pagination
+        background
+        layout="total, sizes, prev, pager, next"
+        :total="total"
+        :page-size="query.pageSize"
+        :current-page="query.pageNum"
+        @size-change="onSizeChange"
+        @current-change="onCurrentChange"
+      />
+    </div>
+
+    <!-- 状态修改弹窗 -->
+    <el-dialog v-model="statusDialogVisible" title="修改订单状态" width="400px" :close-on-click-modal="false">
+      <div style="padding:20px 0;">
+        <el-radio-group v-model="newStatus">
+          <el-radio-button :label="0">待支付</el-radio-button>
+          <el-radio-button :label="1">已支付</el-radio-button>
+          <el-radio-button :label="2">已取消</el-radio-button>
+        </el-radio-group>
+      </div>
+      <template #footer>
+        <el-button @click="statusDialogVisible = false">取消</el-button>
+        <el-button type="primary" @click="onConfirmStatusChange">确定</el-button>
+      </template>
+    </el-dialog>
+  </el-card>
+</template>

+ 16 - 0
admin-vue/vite.config.js

@@ -0,0 +1,16 @@
+import { defineConfig } from 'vite'
+import vue from '@vitejs/plugin-vue'
+
+// https://vite.dev/config/
+export default defineConfig({
+  plugins: [vue()],
+  server: {
+    proxy: {
+      '/api': {
+        target: 'http://localhost:8086',
+        changeOrigin: true,
+        rewrite: (path) => path.replace(/^\/api/, ''),
+      },
+    },
+  },
+})

+ 6 - 0
oederFoodVue/package-lock.json

@@ -0,0 +1,6 @@
+{
+  "name": "oederFoodVue",
+  "lockfileVersion": 3,
+  "requires": true,
+  "packages": {}
+}

+ 7 - 1
pom.xml

@@ -59,6 +59,12 @@
             <artifactId>mysql-connector-j</artifactId>
             <scope>runtime</scope>
         </dependency>
+        <!-- MinIO Java客户端 -->
+        <dependency>
+            <groupId>io.minio</groupId>
+            <artifactId>minio</artifactId>
+            <version>8.5.7</version>
+        </dependency>
         <dependency>
             <groupId>io.jsonwebtoken</groupId>
             <artifactId>jjwt</artifactId>
@@ -164,7 +170,7 @@
                 <artifactId>spring-boot-maven-plugin</artifactId>
                 <version>${spring-boot.version}</version>
                 <configuration>
-                    <mainClass>com.we.OrderFoodApplication</mainClass>
+                    <mainClass>com.OrderFoodApplication</mainClass>
                     <skip>true</skip>
                 </configuration>
                 <executions>

+ 2 - 2
src/main/java/com/we/OrderFoodApplication.java → src/main/java/com/OrderFoodApplication.java

@@ -1,11 +1,11 @@
-package com.we;
+package com;
 
 import org.mybatis.spring.annotation.MapperScan;
 import org.springframework.boot.SpringApplication;
 import org.springframework.boot.autoconfigure.SpringBootApplication;
 
 @SpringBootApplication
-@MapperScan("com.we.mapper")
+@MapperScan({"com.we.mapper,com.bebind.mapper"})
 public class OrderFoodApplication {
 
     public static void main(String[] args) {

+ 17 - 0
src/main/java/com/bebind/config/CorsConfig.java

@@ -0,0 +1,17 @@
+package com.bebind.config;
+
+import org.springframework.context.annotation.Configuration;
+import org.springframework.web.servlet.config.annotation.CorsRegistry;
+import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
+
+@Configuration
+public class CorsConfig implements WebMvcConfigurer {
+    @Override
+    public void addCorsMappings(CorsRegistry registry) {
+        registry.addMapping("/**")
+                .allowedOriginPatterns("*")
+                .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
+                .allowedHeaders("*")
+                .allowCredentials(true);
+    }
+}

+ 30 - 0
src/main/java/com/bebind/config/MinioConfig.java

@@ -0,0 +1,30 @@
+package com.bebind.config;
+
+import io.minio.MinioClient;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+@Configuration
+public class MinioConfig {
+
+    @Value("${minio.endpoint}")
+    private String endpoint;
+
+    @Value("${minio.accessKey}")
+    private String accessKey;
+
+    @Value("${minio.secretKey}")
+    private String secretKey;
+
+    /**
+     * 初始化MinIO客户端
+     */
+    @Bean
+    public MinioClient minioClient() {
+        return MinioClient.builder()
+                .endpoint(endpoint)
+                .credentials(accessKey, secretKey)
+                .build();
+    }
+}

+ 17 - 0
src/main/java/com/bebind/config/MybatisPlusConfig.java

@@ -0,0 +1,17 @@
+package com.bebind.config;
+
+import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
+import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+@Configuration
+public class MybatisPlusConfig {
+
+    @Bean
+    public MybatisPlusInterceptor mybatisPlusInterceptor() {
+        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
+        interceptor.addInnerInterceptor(new PaginationInnerInterceptor());
+        return interceptor;
+    }
+}

+ 50 - 0
src/main/java/com/bebind/controllor/DishCategoryControllor.java

@@ -0,0 +1,50 @@
+package com.bebind.controllor;
+
+import com.bebind.domain.DishCategory;
+import com.bebind.dto.DishCatDto;
+import com.bebind.dto.DishCategoryDto;
+import com.bebind.service.DishCategoryService;
+import com.bebind.vo.ResultVo;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+@RestController
+@RequestMapping("/dishcate")
+public class DishCategoryControllor {
+    @Autowired
+    DishCategoryService dishCategoryService;
+    @GetMapping("/list")
+    public ResultVo list() {
+        List<DishCategory> list = dishCategoryService.list();
+        return ResultVo.success(list);
+    }
+    @PostMapping("/add")
+    public ResultVo add(@RequestBody DishCategoryDto dishCategory) {
+        boolean save = dishCategoryService.saveDishCategory(dishCategory);
+        if (save){
+            return ResultVo.success("添加成功");
+        }else {
+            return ResultVo.fail("添加失败");
+        }
+    }
+    @PostMapping("/upd")
+    public ResultVo upd(@RequestBody DishCatDto dto){
+        boolean save = dishCategoryService.updateDishCategory(dto);
+        if (save){
+            return ResultVo.success("修改成功");
+        }else {
+            return ResultVo.fail("修改失败");
+        }
+    }
+    @PostMapping("/del")
+    public ResultVo del(@RequestBody List<Long> id){
+        boolean save = dishCategoryService.deleteDishCategory(id);
+        if (save){
+            return ResultVo.success("删除成功");
+        }else {
+            return ResultVo.fail("删除失败");
+        }
+    }
+}

+ 113 - 0
src/main/java/com/bebind/controllor/DishControllor.java

@@ -0,0 +1,113 @@
+package com.bebind.controllor;
+
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.bebind.domain.Dish;
+import com.bebind.dto.DishDto;
+import com.bebind.service.DishService;
+import com.bebind.vo.ResultVo;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.util.List;
+
+@RestController
+@RequestMapping("/dis")
+public class DishControllor {
+    @Autowired
+    private DishService dishService;
+    @PostMapping("/list")
+    public ResultVo list(@RequestBody DishDto dto) {
+        Page<Dish> page = new Page<>(dto.getPageNum(), dto.getPageSize());
+        IPage<Dish> selectAll = dishService.selectAll(page, dto);
+        return ResultVo.success(selectAll);
+    }
+    @PostMapping(value = "/add", consumes = "multipart/form-data")
+    public ResultVo jia(
+            @RequestParam("dishName") String dishName,
+            @RequestParam("categoryId") Long categoryId,
+            @RequestParam("flavorId") Long flavorId,
+            @RequestParam(value = "coreMaterial", required = false) String coreMaterial,
+            @RequestParam("price") java.math.BigDecimal price,
+            @RequestParam(value = "costPrice", required = false) java.math.BigDecimal costPrice,
+            @RequestParam(value = "stock", required = false) Integer stock,
+            @RequestParam(value = "sales", required = false) Integer sales,
+            @RequestParam(value = "tasteFeature", required = false) String tasteFeature,
+            @RequestParam(value = "adaptScene", required = false) String adaptScene,
+            @RequestParam(value = "description", required = false) String description,
+            @RequestParam(value = "status") Integer status,
+            @RequestParam(value = "multipartFile", required = false) MultipartFile multipartFile
+    ) throws Exception {
+        Dish dto = new Dish();
+        dto.setDishName(dishName);
+        dto.setCategoryId(categoryId);
+        dto.setFlavorId(flavorId);
+        dto.setCoreMaterial(coreMaterial);
+        dto.setPrice(price);
+        dto.setCostPrice(costPrice);
+        dto.setStock(stock);
+        dto.setSales(sales);
+        dto.setTasteFeature(tasteFeature);
+        dto.setAdaptScene(adaptScene);
+        dto.setDescription(description);
+        dto.setStatus(status);
+        dto.setMultipartFile(multipartFile);
+        boolean savedish = dishService.savedish(dto);
+        if (savedish) {
+            return ResultVo.success("添加成功");
+        }else {
+            return ResultVo.fail("添加失败");
+        }
+    }
+    @PostMapping(value = "/upd", consumes = "multipart/form-data")
+    public ResultVo upd(
+            @RequestParam("id") Long id,
+            @RequestParam("dishName") String dishName,
+            @RequestParam("categoryId") Long categoryId,
+            @RequestParam("flavorId") Long flavorId,
+            @RequestParam(value = "coreMaterial", required = false) String coreMaterial,
+            @RequestParam("price") java.math.BigDecimal price,
+            @RequestParam(value = "costPrice", required = false) java.math.BigDecimal costPrice,
+            @RequestParam(value = "stock", required = false) Integer stock,
+            @RequestParam(value = "sales", required = false) Integer sales,
+            @RequestParam(value = "tasteFeature", required = false) String tasteFeature,
+            @RequestParam(value = "adaptScene", required = false) String adaptScene,
+            @RequestParam(value = "coverImg", required = false) String coverImg,
+            @RequestParam(value = "description", required = false) String description,
+            @RequestParam(value = "status") Integer status,
+            @RequestParam(value = "multipartFile", required = false) MultipartFile multipartFile
+    ) throws Exception {
+        Dish dto = new Dish();
+        dto.setId(id);
+        dto.setDishName(dishName);
+        dto.setCategoryId(categoryId);
+        dto.setFlavorId(flavorId);
+        dto.setCoreMaterial(coreMaterial);
+        dto.setPrice(price);
+        dto.setCostPrice(costPrice);
+        dto.setStock(stock);
+        dto.setSales(sales);
+        dto.setTasteFeature(tasteFeature);
+        dto.setAdaptScene(adaptScene);
+        dto.setCoverImg(coverImg);
+        dto.setDescription(description);
+        dto.setStatus(status);
+        dto.setMultipartFile(multipartFile);
+        boolean savedish = dishService.updatedish(dto);
+        if (savedish) {
+            return ResultVo.success("修改成功");
+        }else {
+            return ResultVo.fail("修改失败");
+        }
+    }
+    @PostMapping("/del")
+    public ResultVo del(@RequestBody List<Long> id){
+        boolean deletedish = dishService.deletedish(id);
+        if (deletedish) {
+            return ResultVo.success("删除成功");
+        }else {
+            return ResultVo.fail("删除失败");
+        }
+    }
+}

+ 50 - 0
src/main/java/com/bebind/controllor/FlaveControllor.java

@@ -0,0 +1,50 @@
+package com.bebind.controllor;
+
+import com.bebind.domain.Flaver;
+import com.bebind.dto.FlaDto;
+import com.bebind.dto.FlaverDto;
+import com.bebind.service.FlaverService;
+import com.bebind.vo.ResultVo;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+@RestController
+@RequestMapping("/fla")
+public class FlaveControllor {
+    @Autowired
+    private FlaverService flaverService;
+    @GetMapping("/list")
+    public ResultVo list(){
+        List<Flaver> flaver = flaverService.getFlaver();
+        return ResultVo.success(flaver);
+    }
+    @PostMapping("/add")
+    public ResultVo add(@RequestBody FlaverDto flaver) {
+        Boolean b = flaverService.saveFlaver(flaver);
+        if (b) {
+            return ResultVo.success("添加成功");
+        }else {
+            return ResultVo.fail("添加失败");
+        }
+    }
+    @PostMapping("/upd")
+    public ResultVo upd(@RequestBody FlaDto dto){
+        Boolean b = flaverService.updateFlaver(dto);
+        if (b) {
+            return ResultVo.success("修改成功");
+        }else {
+            return ResultVo.fail("修改失败");
+        }
+    }
+    @PostMapping("/del")
+    public ResultVo del(@RequestBody List<Long> id){
+        Boolean b = flaverService.deleteFlaver(id);
+        if (b) {
+            return ResultVo.success("删除成功");
+        }else {
+            return ResultVo.fail("删除失败");
+        }
+    }
+}

+ 44 - 0
src/main/java/com/bebind/controllor/OrdersControllor.java

@@ -0,0 +1,44 @@
+package com.bebind.controllor;
+
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.bebind.domain.Orders;
+import com.bebind.dto.OrderDto;
+import com.bebind.service.OrdersService;
+import com.bebind.vo.ResultVo;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+@RestController
+@RequestMapping("/horders")
+public class OrdersControllor {
+    @Autowired
+    private OrdersService ordersService;
+    @PostMapping("/list")
+    public ResultVo list(@RequestBody OrderDto orderDto){
+        Page<Orders> page = new Page<>(orderDto.getPageNum(), orderDto.getPageSize());
+        IPage<Orders> list = ordersService.getOrdersList(page, orderDto);
+        return ResultVo.success(list);
+    }
+    @PostMapping("/del")
+    public ResultVo Del(@RequestBody List<String> id){
+        Boolean b = ordersService.deleteOrders(id);
+        if (b){
+            return ResultVo.success("删除成功");
+        }else {
+            return ResultVo.fail("删除失败");
+        }
+    }
+
+    @PostMapping("/updateStatus")
+    public ResultVo updateStatus(@RequestBody Orders orders){
+        Boolean b = ordersService.updateOrderStatus(String.valueOf(orders.getId()), orders.getStatus());
+        if (b){
+            return ResultVo.success("状态修改成功");
+        }else {
+            return ResultVo.fail("状态修改失败");
+        }
+    }
+}

+ 199 - 0
src/main/java/com/bebind/domain/Dish.java

@@ -0,0 +1,199 @@
+package com.bebind.domain;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import java.io.Serializable;
+import java.math.BigDecimal;
+import java.util.Date;
+import lombok.Data;
+import org.springframework.web.multipart.MultipartFile;
+import com.fasterxml.jackson.databind.annotation.JsonSerialize;
+import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
+
+/**
+ * 菜品核心表
+ * @TableName dish
+ */
+@TableName(value ="dish")
+@Data
+public class Dish implements Serializable {
+    /**
+     * 菜品ID(主键)
+     */
+    @TableId
+    @JsonSerialize(using = ToStringSerializer.class)
+    private Long id;
+
+    /**
+     * 菜品名称(唯一)
+     */
+    private String dishName;
+
+    /**
+     * 所属分类ID,关联dish_category.id
+     */
+    @JsonSerialize(using = ToStringSerializer.class)
+    private Long categoryId;
+
+    /**
+     * 口味ID,关联flavor.id
+     */
+    @JsonSerialize(using = ToStringSerializer.class)
+    private Long flavorId;
+
+    /**
+     * 核心食材(如黄瓜、蒜末;番茄、鸡蛋)
+     */
+    private String coreMaterial;
+
+    /**
+     * 菜品单价,保留2位小数
+     */
+    private BigDecimal price;
+
+    /**
+     * 菜品成本价,用于核算利润
+     */
+    private BigDecimal costPrice;
+
+    /**
+     * 库存数量(0为无库存,-1为无限库存)
+     */
+    private Integer stock;
+
+    /**
+     * 销量,自动累计
+     */
+    private Integer sales;
+
+    /**
+     * 口味特点(补充,如酸辣爽口、脆嫩入味)
+     */
+    private String tasteFeature;
+
+    /**
+     * 适配场景(如日常佐餐、家庭小聚、下酒)
+     */
+    private String adaptScene;
+
+    /**
+     * 菜品封面图URL,用于前端展示
+     */
+    private String coverImg;
+
+    /**
+     * 菜品描述(如做法贴士、食材亮点)
+     */
+    private String description;
+
+    /**
+     * 状态:1-上架,0-下架
+     */
+    private Integer status;
+
+    /**
+     * 软删除:0-未删,1-已删
+     */
+    private Integer isDelete;
+
+    /**
+     * 创建时间
+     */
+    private Date createTime;
+
+    /**
+     * 更新时间
+     */
+    private Date updateTime;
+
+    @TableField(exist = false)
+    private MultipartFile multipartFile;
+
+    @TableField(exist = false)
+    private static final long serialVersionUID = 1L;
+
+    @Override
+    public boolean equals(Object that) {
+        if (this == that) {
+            return true;
+        }
+        if (that == null) {
+            return false;
+        }
+        if (getClass() != that.getClass()) {
+            return false;
+        }
+        Dish other = (Dish) that;
+        return (this.getId() == null ? other.getId() == null : this.getId().equals(other.getId()))
+            && (this.getDishName() == null ? other.getDishName() == null : this.getDishName().equals(other.getDishName()))
+            && (this.getCategoryId() == null ? other.getCategoryId() == null : this.getCategoryId().equals(other.getCategoryId()))
+            && (this.getFlavorId() == null ? other.getFlavorId() == null : this.getFlavorId().equals(other.getFlavorId()))
+            && (this.getCoreMaterial() == null ? other.getCoreMaterial() == null : this.getCoreMaterial().equals(other.getCoreMaterial()))
+            && (this.getPrice() == null ? other.getPrice() == null : this.getPrice().equals(other.getPrice()))
+            && (this.getCostPrice() == null ? other.getCostPrice() == null : this.getCostPrice().equals(other.getCostPrice()))
+            && (this.getStock() == null ? other.getStock() == null : this.getStock().equals(other.getStock()))
+            && (this.getSales() == null ? other.getSales() == null : this.getSales().equals(other.getSales()))
+            && (this.getTasteFeature() == null ? other.getTasteFeature() == null : this.getTasteFeature().equals(other.getTasteFeature()))
+            && (this.getAdaptScene() == null ? other.getAdaptScene() == null : this.getAdaptScene().equals(other.getAdaptScene()))
+            && (this.getCoverImg() == null ? other.getCoverImg() == null : this.getCoverImg().equals(other.getCoverImg()))
+            && (this.getDescription() == null ? other.getDescription() == null : this.getDescription().equals(other.getDescription()))
+            && (this.getStatus() == null ? other.getStatus() == null : this.getStatus().equals(other.getStatus()))
+            && (this.getIsDelete() == null ? other.getIsDelete() == null : this.getIsDelete().equals(other.getIsDelete()))
+            && (this.getCreateTime() == null ? other.getCreateTime() == null : this.getCreateTime().equals(other.getCreateTime()))
+            && (this.getUpdateTime() == null ? other.getUpdateTime() == null : this.getUpdateTime().equals(other.getUpdateTime()));
+    }
+
+    @Override
+    public int hashCode() {
+        final int prime = 31;
+        int result = 1;
+        result = prime * result + ((getId() == null) ? 0 : getId().hashCode());
+        result = prime * result + ((getDishName() == null) ? 0 : getDishName().hashCode());
+        result = prime * result + ((getCategoryId() == null) ? 0 : getCategoryId().hashCode());
+        result = prime * result + ((getFlavorId() == null) ? 0 : getFlavorId().hashCode());
+        result = prime * result + ((getCoreMaterial() == null) ? 0 : getCoreMaterial().hashCode());
+        result = prime * result + ((getPrice() == null) ? 0 : getPrice().hashCode());
+        result = prime * result + ((getCostPrice() == null) ? 0 : getCostPrice().hashCode());
+        result = prime * result + ((getStock() == null) ? 0 : getStock().hashCode());
+        result = prime * result + ((getSales() == null) ? 0 : getSales().hashCode());
+        result = prime * result + ((getTasteFeature() == null) ? 0 : getTasteFeature().hashCode());
+        result = prime * result + ((getAdaptScene() == null) ? 0 : getAdaptScene().hashCode());
+        result = prime * result + ((getCoverImg() == null) ? 0 : getCoverImg().hashCode());
+        result = prime * result + ((getDescription() == null) ? 0 : getDescription().hashCode());
+        result = prime * result + ((getStatus() == null) ? 0 : getStatus().hashCode());
+        result = prime * result + ((getIsDelete() == null) ? 0 : getIsDelete().hashCode());
+        result = prime * result + ((getCreateTime() == null) ? 0 : getCreateTime().hashCode());
+        result = prime * result + ((getUpdateTime() == null) ? 0 : getUpdateTime().hashCode());
+        return result;
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder sb = new StringBuilder();
+        sb.append(getClass().getSimpleName());
+        sb.append(" [");
+        sb.append("Hash = ").append(hashCode());
+        sb.append(", id=").append(id);
+        sb.append(", dishName=").append(dishName);
+        sb.append(", categoryId=").append(categoryId);
+        sb.append(", flavorId=").append(flavorId);
+        sb.append(", coreMaterial=").append(coreMaterial);
+        sb.append(", price=").append(price);
+        sb.append(", costPrice=").append(costPrice);
+        sb.append(", stock=").append(stock);
+        sb.append(", sales=").append(sales);
+        sb.append(", tasteFeature=").append(tasteFeature);
+        sb.append(", adaptScene=").append(adaptScene);
+        sb.append(", coverImg=").append(coverImg);
+        sb.append(", description=").append(description);
+        sb.append(", status=").append(status);
+        sb.append(", isDelete=").append(isDelete);
+        sb.append(", createTime=").append(createTime);
+        sb.append(", updateTime=").append(updateTime);
+        sb.append(", serialVersionUID=").append(serialVersionUID);
+        sb.append("]");
+        return sb.toString();
+    }
+}

+ 104 - 0
src/main/java/com/bebind/domain/DishCategory.java

@@ -0,0 +1,104 @@
+package com.bebind.domain;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import java.io.Serializable;
+import java.util.Date;
+import lombok.Data;
+import com.fasterxml.jackson.databind.annotation.JsonSerialize;
+import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
+
+/**
+ * 
+ * @TableName dish_category
+ */
+@TableName(value ="dish_category")
+@Data
+public class DishCategory implements Serializable {
+    /**
+     * 雪花id
+     */
+    @TableId
+    @JsonSerialize(using = ToStringSerializer.class)
+    private Long id;
+
+    /**
+     * 分类名称
+     */
+    private String categoryName;
+
+    /**
+     * 排序
+     */
+    private Integer sort;
+
+    /**
+     * 状态 0:启用 1:禁用
+     */
+    private Integer status;
+
+    /**
+     * 创建时间
+     */
+    private Date createTime;
+
+    /**
+     * 修改时间
+     */
+    private Date updateTime;
+
+    @TableField(exist = false)
+    private static final long serialVersionUID = 1L;
+
+    @Override
+    public boolean equals(Object that) {
+        if (this == that) {
+            return true;
+        }
+        if (that == null) {
+            return false;
+        }
+        if (getClass() != that.getClass()) {
+            return false;
+        }
+        DishCategory other = (DishCategory) that;
+        return (this.getId() == null ? other.getId() == null : this.getId().equals(other.getId()))
+            && (this.getCategoryName() == null ? other.getCategoryName() == null : this.getCategoryName().equals(other.getCategoryName()))
+            && (this.getSort() == null ? other.getSort() == null : this.getSort().equals(other.getSort()))
+            && (this.getStatus() == null ? other.getStatus() == null : this.getStatus().equals(other.getStatus()))
+            && (this.getCreateTime() == null ? other.getCreateTime() == null : this.getCreateTime().equals(other.getCreateTime()))
+            && (this.getUpdateTime() == null ? other.getUpdateTime() == null : this.getUpdateTime().equals(other.getUpdateTime()));
+    }
+
+    @Override
+    public int hashCode() {
+        final int prime = 31;
+        int result = 1;
+        result = prime * result + ((getId() == null) ? 0 : getId().hashCode());
+        result = prime * result + ((getCategoryName() == null) ? 0 : getCategoryName().hashCode());
+        result = prime * result + ((getSort() == null) ? 0 : getSort().hashCode());
+        result = prime * result + ((getStatus() == null) ? 0 : getStatus().hashCode());
+        result = prime * result + ((getCreateTime() == null) ? 0 : getCreateTime().hashCode());
+        result = prime * result + ((getUpdateTime() == null) ? 0 : getUpdateTime().hashCode());
+        return result;
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder sb = new StringBuilder();
+        sb.append(getClass().getSimpleName());
+        sb.append(" [");
+        sb.append("Hash = ").append(hashCode());
+        sb.append(", id=").append(id);
+        sb.append(", categoryName=").append(categoryName);
+        sb.append(", sort=").append(sort);
+        sb.append(", status=").append(status);
+        sb.append(", createTime=").append(createTime);
+        sb.append(", updateTime=").append(updateTime);
+        sb.append(", serialVersionUID=").append(serialVersionUID);
+        sb.append("]");
+        return sb.toString();
+    }
+}

+ 104 - 0
src/main/java/com/bebind/domain/Flaver.java

@@ -0,0 +1,104 @@
+package com.bebind.domain;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import java.io.Serializable;
+import java.util.Date;
+import lombok.Data;
+import com.fasterxml.jackson.databind.annotation.JsonSerialize;
+import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
+
+/**
+ * 
+ * @TableName flaver
+ */
+@TableName(value ="flaver")
+@Data
+public class Flaver implements Serializable {
+    /**
+     * 口味id
+     */
+    @TableId
+    @JsonSerialize(using = ToStringSerializer.class)
+    private Long id;
+
+    /**
+     * 口味名称
+     */
+    private String flavorName;
+
+    /**
+     * 排序
+     */
+    private Integer sort;
+
+    /**
+     * 状态 0:启用 1:禁用
+     */
+    private Integer status;
+
+    /**
+     * 创建时间
+     */
+    private Date createTime;
+
+    /**
+     * 修改时间
+     */
+    private Date updateTime;
+
+    @TableField(exist = false)
+    private static final long serialVersionUID = 1L;
+
+    @Override
+    public boolean equals(Object that) {
+        if (this == that) {
+            return true;
+        }
+        if (that == null) {
+            return false;
+        }
+        if (getClass() != that.getClass()) {
+            return false;
+        }
+        Flaver other = (Flaver) that;
+        return (this.getId() == null ? other.getId() == null : this.getId().equals(other.getId()))
+            && (this.getFlavorName() == null ? other.getFlavorName() == null : this.getFlavorName().equals(other.getFlavorName()))
+            && (this.getSort() == null ? other.getSort() == null : this.getSort().equals(other.getSort()))
+            && (this.getStatus() == null ? other.getStatus() == null : this.getStatus().equals(other.getStatus()))
+            && (this.getCreateTime() == null ? other.getCreateTime() == null : this.getCreateTime().equals(other.getCreateTime()))
+            && (this.getUpdateTime() == null ? other.getUpdateTime() == null : this.getUpdateTime().equals(other.getUpdateTime()));
+    }
+
+    @Override
+    public int hashCode() {
+        final int prime = 31;
+        int result = 1;
+        result = prime * result + ((getId() == null) ? 0 : getId().hashCode());
+        result = prime * result + ((getFlavorName() == null) ? 0 : getFlavorName().hashCode());
+        result = prime * result + ((getSort() == null) ? 0 : getSort().hashCode());
+        result = prime * result + ((getStatus() == null) ? 0 : getStatus().hashCode());
+        result = prime * result + ((getCreateTime() == null) ? 0 : getCreateTime().hashCode());
+        result = prime * result + ((getUpdateTime() == null) ? 0 : getUpdateTime().hashCode());
+        return result;
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder sb = new StringBuilder();
+        sb.append(getClass().getSimpleName());
+        sb.append(" [");
+        sb.append("Hash = ").append(hashCode());
+        sb.append(", id=").append(id);
+        sb.append(", flavorName=").append(flavorName);
+        sb.append(", sort=").append(sort);
+        sb.append(", status=").append(status);
+        sb.append(", createTime=").append(createTime);
+        sb.append(", updateTime=").append(updateTime);
+        sb.append(", serialVersionUID=").append(serialVersionUID);
+        sb.append("]");
+        return sb.toString();
+    }
+}

+ 109 - 0
src/main/java/com/bebind/domain/OrderDetail.java

@@ -0,0 +1,109 @@
+package com.bebind.domain;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import java.io.Serializable;
+import java.math.BigDecimal;
+import lombok.Data;
+
+/**
+ * 订单详情表
+ * @TableName order_detail
+ */
+@TableName(value ="order_detail")
+@Data
+public class OrderDetail implements Serializable {
+    /**
+     * 详情ID
+     */
+    @TableId
+    private Long id;
+
+    /**
+     * 订单ID
+     */
+    private Long orderId;
+
+    /**
+     * 菜品ID
+     */
+    private Long dishId;
+
+    /**
+     * 菜品名称
+     */
+    private String dishName;
+
+    /**
+     * 单价
+     */
+    private BigDecimal price;
+
+    /**
+     * 数量
+     */
+    private Integer quantity;
+
+    /**
+     * 小计
+     */
+    private BigDecimal subtotal;
+
+    @TableField(exist = false)
+    private static final long serialVersionUID = 1L;
+
+    @Override
+    public boolean equals(Object that) {
+        if (this == that) {
+            return true;
+        }
+        if (that == null) {
+            return false;
+        }
+        if (getClass() != that.getClass()) {
+            return false;
+        }
+        OrderDetail other = (OrderDetail) that;
+        return (this.getId() == null ? other.getId() == null : this.getId().equals(other.getId()))
+            && (this.getOrderId() == null ? other.getOrderId() == null : this.getOrderId().equals(other.getOrderId()))
+            && (this.getDishId() == null ? other.getDishId() == null : this.getDishId().equals(other.getDishId()))
+            && (this.getDishName() == null ? other.getDishName() == null : this.getDishName().equals(other.getDishName()))
+            && (this.getPrice() == null ? other.getPrice() == null : this.getPrice().equals(other.getPrice()))
+            && (this.getQuantity() == null ? other.getQuantity() == null : this.getQuantity().equals(other.getQuantity()))
+            && (this.getSubtotal() == null ? other.getSubtotal() == null : this.getSubtotal().equals(other.getSubtotal()));
+    }
+
+    @Override
+    public int hashCode() {
+        final int prime = 31;
+        int result = 1;
+        result = prime * result + ((getId() == null) ? 0 : getId().hashCode());
+        result = prime * result + ((getOrderId() == null) ? 0 : getOrderId().hashCode());
+        result = prime * result + ((getDishId() == null) ? 0 : getDishId().hashCode());
+        result = prime * result + ((getDishName() == null) ? 0 : getDishName().hashCode());
+        result = prime * result + ((getPrice() == null) ? 0 : getPrice().hashCode());
+        result = prime * result + ((getQuantity() == null) ? 0 : getQuantity().hashCode());
+        result = prime * result + ((getSubtotal() == null) ? 0 : getSubtotal().hashCode());
+        return result;
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder sb = new StringBuilder();
+        sb.append(getClass().getSimpleName());
+        sb.append(" [");
+        sb.append("Hash = ").append(hashCode());
+        sb.append(", id=").append(id);
+        sb.append(", orderId=").append(orderId);
+        sb.append(", dishId=").append(dishId);
+        sb.append(", dishName=").append(dishName);
+        sb.append(", price=").append(price);
+        sb.append(", quantity=").append(quantity);
+        sb.append(", subtotal=").append(subtotal);
+        sb.append(", serialVersionUID=").append(serialVersionUID);
+        sb.append("]");
+        return sb.toString();
+    }
+}

+ 127 - 0
src/main/java/com/bebind/domain/Orders.java

@@ -0,0 +1,127 @@
+package com.bebind.domain;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import java.io.Serializable;
+import java.math.BigDecimal;
+import java.util.Date;
+import lombok.Data;
+
+/**
+ * 订单表
+ * @TableName orders
+ */
+@TableName(value ="orders")
+@Data
+public class Orders implements Serializable {
+    /**
+     * 订单ID
+     */
+    @TableId
+    private Long id;
+
+    /**
+     * 订单号
+     */
+    private String orderNumber;
+
+    /**
+     * 商品总价
+     */
+    private BigDecimal totalAmount;
+
+    /**
+     * 配送费
+     */
+    private BigDecimal deliveryFee;
+
+    /**
+     * 订单总额
+     */
+    private BigDecimal finalAmount;
+
+    /**
+     * 订单状态 0-待支付 1-已支付 2-已取消
+     */
+    private Integer status;
+
+    /**
+     * 备注
+     */
+    private String remark;
+
+    /**
+     * 创建时间
+     */
+    private Date createTime;
+
+    /**
+     * 更新时间
+     */
+    private Date updateTime;
+    private Integer isDel;
+
+    @TableField(exist = false)
+    private static final long serialVersionUID = 1L;
+
+    @Override
+    public boolean equals(Object that) {
+        if (this == that) {
+            return true;
+        }
+        if (that == null) {
+            return false;
+        }
+        if (getClass() != that.getClass()) {
+            return false;
+        }
+        Orders other = (Orders) that;
+        return (this.getId() == null ? other.getId() == null : this.getId().equals(other.getId()))
+            && (this.getOrderNumber() == null ? other.getOrderNumber() == null : this.getOrderNumber().equals(other.getOrderNumber()))
+            && (this.getTotalAmount() == null ? other.getTotalAmount() == null : this.getTotalAmount().equals(other.getTotalAmount()))
+            && (this.getDeliveryFee() == null ? other.getDeliveryFee() == null : this.getDeliveryFee().equals(other.getDeliveryFee()))
+            && (this.getFinalAmount() == null ? other.getFinalAmount() == null : this.getFinalAmount().equals(other.getFinalAmount()))
+            && (this.getStatus() == null ? other.getStatus() == null : this.getStatus().equals(other.getStatus()))
+            && (this.getRemark() == null ? other.getRemark() == null : this.getRemark().equals(other.getRemark()))
+            && (this.getCreateTime() == null ? other.getCreateTime() == null : this.getCreateTime().equals(other.getCreateTime()))
+            && (this.getUpdateTime() == null ? other.getUpdateTime() == null : this.getUpdateTime().equals(other.getUpdateTime()));
+    }
+
+    @Override
+    public int hashCode() {
+        final int prime = 31;
+        int result = 1;
+        result = prime * result + ((getId() == null) ? 0 : getId().hashCode());
+        result = prime * result + ((getOrderNumber() == null) ? 0 : getOrderNumber().hashCode());
+        result = prime * result + ((getTotalAmount() == null) ? 0 : getTotalAmount().hashCode());
+        result = prime * result + ((getDeliveryFee() == null) ? 0 : getDeliveryFee().hashCode());
+        result = prime * result + ((getFinalAmount() == null) ? 0 : getFinalAmount().hashCode());
+        result = prime * result + ((getStatus() == null) ? 0 : getStatus().hashCode());
+        result = prime * result + ((getRemark() == null) ? 0 : getRemark().hashCode());
+        result = prime * result + ((getCreateTime() == null) ? 0 : getCreateTime().hashCode());
+        result = prime * result + ((getUpdateTime() == null) ? 0 : getUpdateTime().hashCode());
+        return result;
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder sb = new StringBuilder();
+        sb.append(getClass().getSimpleName());
+        sb.append(" [");
+        sb.append("Hash = ").append(hashCode());
+        sb.append(", id=").append(id);
+        sb.append(", orderNumber=").append(orderNumber);
+        sb.append(", totalAmount=").append(totalAmount);
+        sb.append(", deliveryFee=").append(deliveryFee);
+        sb.append(", finalAmount=").append(finalAmount);
+        sb.append(", status=").append(status);
+        sb.append(", remark=").append(remark);
+        sb.append(", createTime=").append(createTime);
+        sb.append(", updateTime=").append(updateTime);
+        sb.append(", serialVersionUID=").append(serialVersionUID);
+        sb.append("]");
+        return sb.toString();
+    }
+}

+ 12 - 0
src/main/java/com/bebind/dto/DishCatDto.java

@@ -0,0 +1,12 @@
+package com.bebind.dto;
+
+
+import lombok.Data;
+
+@Data
+public class DishCatDto {
+    private Long id;
+    private String categoryName;
+    private Integer sort;
+    private Integer status;
+}

+ 9 - 0
src/main/java/com/bebind/dto/DishCategoryDto.java

@@ -0,0 +1,9 @@
+package com.bebind.dto;
+
+import lombok.Data;
+
+@Data
+public class DishCategoryDto {
+    private String categoryName;
+    private Integer sort;
+}

+ 20 - 0
src/main/java/com/bebind/dto/DishDto.java

@@ -0,0 +1,20 @@
+package com.bebind.dto;
+
+import lombok.Data;
+
+@Data
+public class DishDto extends PageDto{
+    private String dishName;
+    /**
+     * 所属分类ID,关联dish_category.id
+     */
+    private Long categoryId;
+    /**
+     * 口味ID,关联flavor.id
+     */
+    private Long flavorId;
+    /**
+     * 状态:1-上架,0-下架
+     */
+    private Integer status;
+}

+ 11 - 0
src/main/java/com/bebind/dto/FlaDto.java

@@ -0,0 +1,11 @@
+package com.bebind.dto;
+
+import lombok.Data;
+
+@Data
+public class FlaDto {
+    private Long id;
+    private String flavorName;
+    private Integer sort;
+    private Integer status;
+}

+ 10 - 0
src/main/java/com/bebind/dto/FlaverDto.java

@@ -0,0 +1,10 @@
+package com.bebind.dto;
+
+import io.swagger.v3.oas.models.security.SecurityScheme;
+import lombok.Data;
+
+@Data
+public class FlaverDto {
+    private String flavorName;
+    private Integer sort;
+}

+ 12 - 0
src/main/java/com/bebind/dto/OrderDto.java

@@ -0,0 +1,12 @@
+package com.bebind.dto;
+
+import lombok.Data;
+
+@Data
+public class OrderDto extends PageDto{
+    private Integer status;
+    /**
+     * 订单号
+     */
+    private String orderNumber;
+}

+ 8 - 0
src/main/java/com/bebind/dto/OrdersDto.java

@@ -0,0 +1,8 @@
+package com.bebind.dto;
+
+import lombok.Data;
+
+@Data
+public class OrdersDto{
+    private String orderNumber;
+}

+ 10 - 0
src/main/java/com/bebind/dto/PageDto.java

@@ -0,0 +1,10 @@
+package com.bebind.dto;
+
+import io.swagger.v3.oas.models.security.SecurityScheme;
+import lombok.Data;
+
+@Data
+public class PageDto {
+    private Integer pageNum;
+    private Integer pageSize;
+}

+ 22 - 0
src/main/java/com/bebind/mapper/DishCategoryMapper.java

@@ -0,0 +1,22 @@
+package com.bebind.mapper;
+
+import com.bebind.domain.DishCategory;
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import org.apache.ibatis.annotations.Mapper;
+import org.springframework.stereotype.Repository;
+
+/**
+* @author 联想
+* @description 针对表【dish_category】的数据库操作Mapper
+* @createDate 2026-02-09 19:42:24
+* @Entity com.bebind.domain.DishCategory
+*/
+@Mapper
+@Repository("bebindDishCategoryMapper")
+public interface DishCategoryMapper extends BaseMapper<DishCategory> {
+
+}
+
+
+
+

+ 22 - 0
src/main/java/com/bebind/mapper/DishMapper.java

@@ -0,0 +1,22 @@
+package com.bebind.mapper;
+
+import com.bebind.domain.Dish;
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import org.apache.ibatis.annotations.Mapper;
+import org.springframework.stereotype.Repository;
+
+/**
+* @author 联想
+* @description 针对表【dish(菜品核心表)】的数据库操作Mapper
+* @createDate 2026-02-09 19:42:24
+* @Entity com.bebind.domain.Dish
+*/
+@Mapper
+@Repository("bebindDishMapper")
+public interface DishMapper extends BaseMapper<Dish> {
+
+}
+
+
+
+

+ 22 - 0
src/main/java/com/bebind/mapper/FlaverMapper.java

@@ -0,0 +1,22 @@
+package com.bebind.mapper;
+
+import com.bebind.domain.Flaver;
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import org.apache.ibatis.annotations.Mapper;
+import org.springframework.stereotype.Repository;
+
+/**
+* @author 联想
+* @description 针对表【flaver】的数据库操作Mapper
+* @createDate 2026-02-09 19:42:24
+* @Entity com.bebind.domain.Flaver
+*/
+@Mapper
+@Repository("bebindFlaverMapper")
+public interface FlaverMapper extends BaseMapper<Flaver> {
+
+}
+
+
+
+

+ 22 - 0
src/main/java/com/bebind/mapper/OrderDetailMapper.java

@@ -0,0 +1,22 @@
+package com.bebind.mapper;
+
+import com.bebind.domain.OrderDetail;
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import org.apache.ibatis.annotations.Mapper;
+import org.springframework.stereotype.Repository;
+
+/**
+* @author 联想
+* @description 针对表【order_detail(订单详情表)】的数据库操作Mapper
+* @createDate 2026-02-09 19:42:24
+* @Entity com.bebind.domain.OrderDetail
+*/
+@Mapper
+@Repository("bebindOrderDetailMapper")
+public interface OrderDetailMapper extends BaseMapper<OrderDetail> {
+
+}
+
+
+
+

+ 22 - 0
src/main/java/com/bebind/mapper/OrdersMapper.java

@@ -0,0 +1,22 @@
+package com.bebind.mapper;
+
+import com.bebind.domain.Orders;
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import org.apache.ibatis.annotations.Mapper;
+import org.springframework.stereotype.Repository;
+
+/**
+* @author 联想
+* @description 针对表【orders(订单表)】的数据库操作Mapper
+* @createDate 2026-02-09 19:42:24
+* @Entity com.bebind.domain.Orders
+*/
+@Mapper
+@Repository("bebindOrderMapper")
+public interface OrdersMapper extends BaseMapper<Orders> {
+
+}
+
+
+
+

+ 21 - 0
src/main/java/com/bebind/service/DishCategoryService.java

@@ -0,0 +1,21 @@
+package com.bebind.service;
+
+import com.bebind.domain.DishCategory;
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.bebind.dto.DishCatDto;
+import com.bebind.dto.DishCategoryDto;
+
+import java.util.List;
+
+/**
+* @author 联想
+* @description 针对表【dish_category】的数据库操作Service
+* @createDate 2026-02-09 19:42:24
+*/
+public interface DishCategoryService extends IService<DishCategory> {
+    List<DishCategory> findDishCategoryByDishId();
+    Boolean saveDishCategory(DishCategoryDto dishCategory);
+    Boolean updateDishCategory(DishCatDto dishCategory);
+    Boolean deleteDishCategory(List<Long> dishId);
+
+}

+ 20 - 0
src/main/java/com/bebind/service/DishService.java

@@ -0,0 +1,20 @@
+package com.bebind.service;
+import com.bebind.domain.Dish;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.bebind.dto.DishDto;
+
+import java.util.List;
+
+/**
+* @author 联想
+* @description 针对表【dish(菜品核心表)】的数据库操作Service
+* @createDate 2026-02-09 19:42:24
+*/
+public interface DishService extends IService<Dish> {
+    IPage<Dish> selectAll(Page<Dish> page,DishDto dish);
+    boolean savedish(Dish dish) throws Exception;
+    boolean updatedish(Dish dish) throws Exception;
+    boolean deletedish(List<Long> ids);
+}

+ 21 - 0
src/main/java/com/bebind/service/FlaverService.java

@@ -0,0 +1,21 @@
+package com.bebind.service;
+
+import com.bebind.domain.Flaver;
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.bebind.dto.FlaDto;
+import com.bebind.dto.FlaverDto;
+
+import java.util.List;
+
+/**
+* @author 联想
+* @description 针对表【flaver】的数据库操作Service
+* @createDate 2026-02-09 19:42:24
+*/
+public interface FlaverService extends IService<Flaver> {
+    List<Flaver> getFlaver();
+    Boolean saveFlaver(FlaverDto flaver);
+    Boolean updateFlaver(FlaDto flaver);
+    Boolean deleteFlaver(List<Long> ids);
+
+}

+ 13 - 0
src/main/java/com/bebind/service/OrderDetailService.java

@@ -0,0 +1,13 @@
+package com.bebind.service;
+
+import com.bebind.domain.OrderDetail;
+import com.baomidou.mybatisplus.extension.service.IService;
+
+/**
+* @author 联想
+* @description 针对表【order_detail(订单详情表)】的数据库操作Service
+* @createDate 2026-02-09 19:42:24
+*/
+public interface OrderDetailService extends IService<OrderDetail> {
+
+}

+ 27 - 0
src/main/java/com/bebind/service/OrdersService.java

@@ -0,0 +1,27 @@
+package com.bebind.service;
+
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.bebind.domain.Orders;
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.bebind.dto.OrderDto;
+import com.bebind.dto.OrdersDto;
+import com.bebind.vo.OrdersVo;
+
+import java.util.List;
+
+/**
+* @author 联想
+* @description 针对表【orders(订单表)】的数据库操作Service
+* @createDate 2026-02-09 19:42:24
+*/
+public interface OrdersService extends IService<Orders> {
+    IPage<Orders> getOrdersList(Page<Orders> page,OrderDto orderDto);
+//    删除
+    Boolean deleteOrders(List<String> ids);
+//    订单详情
+    List<OrdersVo> getByNumber(OrdersDto dto);
+//    更新订单状态
+    Boolean updateOrderStatus(String id, Integer status);
+
+}

+ 64 - 0
src/main/java/com/bebind/service/impl/DishCategoryServiceImpl.java

@@ -0,0 +1,64 @@
+package com.bebind.service.impl;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.bebind.domain.DishCategory;
+import com.bebind.dto.DishCatDto;
+import com.bebind.dto.DishCategoryDto;
+import com.bebind.service.DishCategoryService;
+import com.bebind.mapper.DishCategoryMapper;
+import org.springframework.beans.BeanUtils;
+import org.springframework.stereotype.Service;
+
+import java.util.Date;
+import java.util.List;
+
+/**
+* @author 联想
+* @description 针对表【dish_category】的数据库操作Service实现
+* @createDate 2026-02-09 19:42:24
+*/
+@Service("bebindDishCategoryServiceImpl")
+public class DishCategoryServiceImpl extends ServiceImpl<DishCategoryMapper, DishCategory>
+    implements DishCategoryService{
+
+    @Override
+    public List<DishCategory> findDishCategoryByDishId() {
+        LambdaQueryWrapper<DishCategory> wrapper = new LambdaQueryWrapper<>();
+        wrapper.orderByDesc(DishCategory::getUpdateTime)
+                .orderByDesc(DishCategory::getSort);
+        List<DishCategory> list = this.list(wrapper);
+        return list;
+    }
+
+    @Override
+    public Boolean saveDishCategory(DishCategoryDto dishCategory) {
+        DishCategory dishCategoryEntity = new DishCategory();
+        BeanUtils.copyProperties(dishCategory, dishCategoryEntity);
+        dishCategoryEntity.setCreateTime(new Date());
+        dishCategoryEntity.setUpdateTime(new Date());
+        boolean save = this.save(dishCategoryEntity);
+        return save;
+    }
+
+    @Override
+    public Boolean updateDishCategory(DishCatDto dishCategory) {
+        DishCategory dishCategoryEntity = new DishCategory();
+        BeanUtils.copyProperties(dishCategory, dishCategoryEntity);
+        dishCategoryEntity.setUpdateTime(new Date());
+        boolean update = this.updateById(dishCategoryEntity);
+        return update;
+    }
+
+    @Override
+    public Boolean deleteDishCategory(List<Long> dishId) {
+        LambdaQueryWrapper<DishCategory> wrapper = new LambdaQueryWrapper<>();
+        wrapper.eq(DishCategory::getId, dishId);
+        boolean b = removeBatchByIds(dishId);
+        return b;
+    }
+}
+
+
+
+

+ 111 - 0
src/main/java/com/bebind/service/impl/DishServiceImpl.java

@@ -0,0 +1,111 @@
+package com.bebind.service.impl;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.bebind.domain.Dish;
+import com.bebind.dto.DishDto;
+import com.bebind.service.DishService;
+import com.bebind.mapper.DishMapper;
+import com.bebind.utils.MinioUploadUtil;
+import com.bebind.vo.MinioUploadResult;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.util.List;
+
+/**
+* @author 联想
+* @description 针对表【dish(菜品核心表)】的数据库操作Service实现
+* @createDate 2026-02-09 19:42:24
+*/
+@Service("bebindDishServiceImpl")
+public class DishServiceImpl extends ServiceImpl<DishMapper, Dish>
+    implements DishService{
+    @Autowired
+    MinioUploadUtil minioUploadUtil;
+    @Override
+    public IPage<Dish> selectAll(Page<Dish> page,DishDto dish) {
+        LambdaQueryWrapper<Dish> wrapper = new LambdaQueryWrapper<>();
+        wrapper.eq(Dish::getIsDelete,0)
+                .orderByDesc(Dish::getUpdateTime);
+        if (dish.getDishName() != null && !"".equals(dish.getDishName())) {
+            wrapper.like(Dish::getDishName, dish.getDishName());
+        }
+        if (dish.getStatus() != null) {
+            wrapper.eq(Dish::getStatus, dish.getStatus());
+        }
+        if (dish.getCategoryId() != null) {
+            wrapper.eq(Dish::getCategoryId, dish.getCategoryId());
+        }
+        if (dish.getFlavorId() != null) {
+            wrapper.eq(Dish::getFlavorId, dish.getFlavorId());
+        }
+        Page<Dish> page1 = this.page(page, wrapper);
+        return page1;
+    }
+
+    @Override
+    public boolean savedish(Dish d) throws Exception {
+        Dish dish1 = new Dish();
+        // 只有上传了文件才处理图片
+        if (d.getMultipartFile() != null && !d.getMultipartFile().isEmpty()) {
+            MinioUploadResult minioUploadResult = minioUploadUtil.uploadImage(d.getMultipartFile());
+            dish1.setCoverImg(minioUploadResult.getImageUrl());
+        }
+        dish1.setDishName(d.getDishName());
+        dish1.setCategoryId(d.getCategoryId());
+        dish1.setFlavorId(d.getFlavorId());
+        dish1.setCoreMaterial(d.getCoreMaterial());
+        dish1.setPrice(d.getPrice());
+        dish1.setCostPrice(d.getCostPrice());
+        dish1.setTasteFeature(d.getTasteFeature());
+        dish1.setAdaptScene(d.getAdaptScene());
+        dish1.setDescription(d.getDescription());
+        boolean save = save(dish1);
+        return save;
+    }
+
+    @Override
+    public boolean updatedish(Dish d) throws Exception {
+        Dish dish1 = new Dish();
+        // 只有上传了新文件才处理图片,否则保留原图片
+        if (d.getMultipartFile() != null && !d.getMultipartFile().isEmpty()) {
+            MinioUploadResult minioUploadResult = minioUploadUtil.uploadImage(d.getMultipartFile());
+            dish1.setCoverImg(minioUploadResult.getImageUrl());
+        }
+        dish1.setDishName(d.getDishName());
+        dish1.setCategoryId(d.getCategoryId());
+        dish1.setFlavorId(d.getFlavorId());
+        dish1.setCoreMaterial(d.getCoreMaterial());
+        dish1.setPrice(d.getPrice());
+        dish1.setCostPrice(d.getCostPrice());
+        dish1.setTasteFeature(d.getTasteFeature());
+        dish1.setAdaptScene(d.getAdaptScene());
+        dish1.setDescription(d.getDescription());
+        dish1.setStock(d.getStock());
+        dish1.setSales(d.getSales());
+        dish1.setIsDelete(d.getIsDelete());
+        dish1.setStatus(d.getStatus());
+        LambdaQueryWrapper<Dish> wrapper = new LambdaQueryWrapper<>();
+        wrapper.eq(Dish::getId,d.getId());
+        boolean update = update(dish1, wrapper);
+        return update;
+    }
+
+    @Override
+    public boolean deletedish(List<Long> ids) {
+        LambdaUpdateWrapper<Dish> dishLambdaUpdateWrapper = new LambdaUpdateWrapper<>();
+        dishLambdaUpdateWrapper.in(Dish::getId,ids);
+        dishLambdaUpdateWrapper.set(Dish::getIsDelete,1);
+        boolean update = update(dishLambdaUpdateWrapper);
+        return update;
+    }
+
+}
+
+
+
+

+ 62 - 0
src/main/java/com/bebind/service/impl/FlaverServiceImpl.java

@@ -0,0 +1,62 @@
+package com.bebind.service.impl;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.bebind.domain.Flaver;
+import com.bebind.dto.FlaDto;
+import com.bebind.dto.FlaverDto;
+import com.bebind.service.FlaverService;
+import com.bebind.mapper.FlaverMapper;
+import org.springframework.beans.BeanUtils;
+import org.springframework.stereotype.Service;
+
+import java.util.Date;
+import java.util.List;
+
+/**
+* @author 联想
+* @description 针对表【flaver】的数据库操作Service实现
+* @createDate 2026-02-09 19:42:24
+*/
+@Service("bebindFlaverServiceImpl")
+public class FlaverServiceImpl extends ServiceImpl<FlaverMapper, Flaver>
+    implements FlaverService{
+
+    @Override
+    public List<Flaver> getFlaver() {
+        LambdaQueryWrapper<Flaver> wrapper = new LambdaQueryWrapper<>();
+        wrapper.orderByAsc(Flaver::getUpdateTime)
+                .orderByDesc(Flaver::getSort);
+        List<Flaver> list = this.list(wrapper);
+        return list;
+    }
+
+    @Override
+    public Boolean saveFlaver(FlaverDto flaver) {
+        Flaver flaver1 = new Flaver();
+        BeanUtils.copyProperties(flaver, flaver1);
+        flaver1.setCreateTime(new Date());
+        flaver1.setUpdateTime(new Date());
+        boolean save = this.save(flaver1);
+        return save;
+    }
+
+    @Override
+    public Boolean updateFlaver(FlaDto flaver) {
+        Flaver flaver1 = new Flaver();
+        BeanUtils.copyProperties(flaver, flaver1);
+        flaver1.setUpdateTime(new Date());
+        boolean update = this.updateById(flaver1);
+        return update;
+    }
+
+    @Override
+    public Boolean deleteFlaver(List<Long> ids) {
+        boolean b = removeBatchByIds(ids);
+        return b;
+    }
+}
+
+
+
+

+ 22 - 0
src/main/java/com/bebind/service/impl/OrderDetailServiceImpl.java

@@ -0,0 +1,22 @@
+package com.bebind.service.impl;
+
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.bebind.domain.OrderDetail;
+import com.bebind.service.OrderDetailService;
+import com.bebind.mapper.OrderDetailMapper;
+import org.springframework.stereotype.Service;
+
+/**
+* @author 联想
+* @description 针对表【order_detail(订单详情表)】的数据库操作Service实现
+* @createDate 2026-02-09 19:42:24
+*/
+@Service("bebindOrderDetailServiceImpl")
+public class OrderDetailServiceImpl extends ServiceImpl<OrderDetailMapper, OrderDetail>
+    implements OrderDetailService{
+
+}
+
+
+
+

+ 94 - 0
src/main/java/com/bebind/service/impl/OrdersServiceImpl.java

@@ -0,0 +1,94 @@
+package com.bebind.service.impl;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.bebind.domain.Dish;
+import com.bebind.domain.OrderDetail;
+import com.bebind.domain.Orders;
+import com.bebind.dto.OrderDto;
+import com.bebind.dto.OrdersDto;
+import com.bebind.service.DishService;
+import com.bebind.service.OrdersService;
+import com.bebind.mapper.OrdersMapper;
+import com.bebind.vo.OrdersVo;
+import com.bebind.service.OrderDetailService;
+import org.springframework.beans.BeanUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+/**
+* @author 联想
+* @description 针对表【orders(订单表)】的数据库操作Service实现
+* @createDate 2026-02-09 19:42:24
+*/
+@Service("bebindOrdersServiceImpl")
+public class OrdersServiceImpl extends ServiceImpl<OrdersMapper, Orders>
+    implements OrdersService{
+    @Autowired
+    private OrderDetailService orderDetailService;
+    @Autowired
+    private DishService dishService;
+
+    @Override
+    public IPage<Orders> getOrdersList(Page<Orders> page, OrderDto orderDto) {
+        LambdaQueryWrapper<Orders> wrapper = new LambdaQueryWrapper<>();
+        wrapper.orderByDesc(Orders::getCreateTime)
+                .eq(Orders::getIsDel,0);
+        if(orderDto.getOrderNumber()!=null){
+            wrapper.like(Orders::getOrderNumber,orderDto.getOrderNumber());
+        }
+        if (orderDto.getStatus() != null) {
+            wrapper.eq(Orders::getStatus,orderDto.getStatus());
+        }
+        Page<Orders> page1 = this.page(page, wrapper);
+        return page1;
+    }
+
+
+    @Override
+    public Boolean deleteOrders(List<String> ids) {
+        if (ids != null && ids.size() > 0) {
+            boolean b = this.removeByIds(ids);
+            return b;
+        }
+        return false;
+    }
+
+    @Override
+    public List<OrdersVo> getByNumber(OrdersDto dto) {
+        ArrayList<OrdersVo> ordersVo = new ArrayList<>();
+        String orderNumber = dto.getOrderNumber();
+        LambdaQueryWrapper<OrderDetail> wrapper = new LambdaQueryWrapper<>();
+        wrapper.eq(OrderDetail::getOrderId,orderNumber);
+        List<OrderDetail> list = orderDetailService.list(wrapper);
+        List<Long> dishlist = list.stream().map(OrderDetail::getDishId).collect(Collectors.toList());
+        List<Dish> dishes = dishService.listByIds(dishlist);
+        for (Dish dish : dishes) {
+            BeanUtils.copyProperties(dish,ordersVo);
+        }
+        List<Long> collect = dishes.stream().map(Dish::getFlavorId).collect(Collectors.toList());
+        for (Long l : collect) {
+            BeanUtils.copyProperties(dto,ordersVo);
+        }
+        return ordersVo;
+    }
+
+    @Override
+    public Boolean updateOrderStatus(String id, Integer status) {
+        Orders orders = new Orders();
+        orders.setId(Long.valueOf(id));
+        orders.setStatus(status);
+        return this.updateById(orders);
+    }
+}
+
+
+
+

+ 77 - 0
src/main/java/com/bebind/utils/MinioUploadUtil.java

@@ -0,0 +1,77 @@
+package com.bebind.utils;
+
+import cn.hutool.core.util.IdUtil;
+import com.bebind.vo.MinioUploadResult;
+import io.minio.BucketExistsArgs;
+import io.minio.MakeBucketArgs;
+import io.minio.PutObjectArgs;
+import io.minio.MinioClient;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Component;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.io.InputStream;
+
+@Component
+public class MinioUploadUtil {
+
+    @Value("${minio.bucketName}")
+    private String bucketName;
+
+    @Value("${minio.previewUrl}")
+    private String previewUrl;
+
+    private final MinioClient minioClient;
+
+    // 构造器注入MinioClient
+    public MinioUploadUtil(MinioClient minioClient) {
+        this.minioClient = minioClient;
+    }
+
+    /**
+     * 上传图片到MinIO(返回访问链接+MinIO路径)
+     * @param file 前端上传的图片文件
+     * @return 上传结果(含链接和路径)
+     * @throws Exception 上传异常
+     */
+    public MinioUploadResult uploadImage(MultipartFile file) throws Exception {
+        // 1. 校验文件是否为空
+        if (file.isEmpty()) {
+            throw new IllegalArgumentException("上传的图片文件不能为空");
+        }
+
+        // 2. 获取文件原始名称和后缀
+        String originalFilename = file.getOriginalFilename();
+        assert originalFilename != null;
+        String suffix = originalFilename.substring(originalFilename.lastIndexOf("."));
+
+        // 3. 生成唯一文件名(避免重复)
+        String fileName = IdUtil.simpleUUID() + suffix;
+
+        // 4. 检查桶是否存在,不存在则创建
+        if (!minioClient.bucketExists(BucketExistsArgs.builder().bucket(bucketName).build())) {
+            minioClient.makeBucket(MakeBucketArgs.builder().bucket(bucketName).build());
+        }
+
+        // 5. 获取文件输入流
+        try (InputStream inputStream = file.getInputStream()) {
+            // 6. 上传文件到MinIO
+            minioClient.putObject(
+                    PutObjectArgs.builder()
+                            .bucket(bucketName)
+                            .object(fileName)  // MinIO中存储的文件名
+                            .stream(inputStream, file.getSize(), -1)  // 文件流和大小
+                            .contentType(file.getContentType())  // 文件类型(如image/jpeg)
+                            .build()
+            );
+        }
+
+        // 7. 拼接图片可访问链接
+        String imageUrl = previewUrl + bucketName + "/" + fileName;
+        // 8. 拼接MinIO存储路径(桶名/文件名)
+        String minioPath = bucketName + "/" + fileName;
+
+        // 返回封装后的结果
+        return new MinioUploadResult(imageUrl, minioPath);
+    }
+}

+ 12 - 0
src/main/java/com/bebind/vo/MinioUploadResult.java

@@ -0,0 +1,12 @@
+package com.bebind.vo;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+/**
+ * MinIO上传结果封装类
+ */
+@Data
+@AllArgsConstructor
+public class MinioUploadResult {
+    private String imageUrl;  // 图片访问链接
+    private String minioPath; // 图片在MinIO中的存储路径(桶名/文件名)
+}

+ 35 - 0
src/main/java/com/bebind/vo/OrdersVo.java

@@ -0,0 +1,35 @@
+package com.bebind.vo;
+
+import com.bebind.dto.PageDto;
+import lombok.Data;
+
+import java.io.Serializable;
+import java.math.BigDecimal;
+
+@Data
+public class OrdersVo  implements Serializable{
+    /**
+     * 菜品名称(唯一)
+     */
+    private String dishName;
+    /**
+     * 菜品单价,保留2位小数
+     */
+    private BigDecimal price;
+    /**
+     * 口味特点(补充,如酸辣爽口、脆嫩入味)
+     */
+    private String tasteFeature;
+    /**
+     * 菜品封面图URL,用于前端展示
+     */
+    private String coverImg;
+    /**
+     * 分类名称
+     */
+    private String categoryName;
+    /**
+     * 口味名称
+     */
+    private String flavorName;
+}

+ 47 - 0
src/main/java/com/bebind/vo/ResultVo.java

@@ -0,0 +1,47 @@
+package com.bebind.vo;
+
+import cn.hutool.http.HttpStatus;
+import lombok.Data;
+
+@Data
+public class ResultVo<T> {
+    private Integer code;
+
+    private String msg;
+
+    private T data;//相当于Object
+
+    public static ResultVo success(){
+        ResultVo success=new ResultVo();
+        success.setCode(HttpStatus.HTTP_OK);
+        success.setMsg("成功");
+        return success;
+    }
+    public static ResultVo success(Object data){
+        ResultVo success=new ResultVo();
+        success.setCode(HttpStatus.HTTP_OK);
+        success.setMsg("成功");
+        success.setData(data);
+        return success;
+    }
+    public static ResultVo fail(Integer code,String msg){
+        ResultVo fail=new ResultVo();
+        fail.setCode(code);
+        fail.setMsg(msg);
+        return fail;
+    }
+    public static ResultVo fail(Integer code,String msg,Object obj){
+        ResultVo fail=new ResultVo();
+        fail.setCode(code);
+        fail.setMsg(msg);
+        fail.setData(obj);
+        return fail;
+    }
+    public static ResultVo fail(String msg){
+        ResultVo fail=new ResultVo();
+        fail.setMsg(msg);
+        return fail;
+    }
+
+
+}

+ 1 - 1
src/main/java/com/we/controller/DishController.java

@@ -24,7 +24,7 @@ import java.util.stream.Collectors;
  */
 @RestController
 @RequestMapping("/api")
-@CrossOrigin(origins = "*")
+@CrossOrigin(originPatterns = "*")
 public class DishController {
 
     @Autowired

+ 1 - 1
src/main/java/com/we/controller/OrderController.java

@@ -12,7 +12,7 @@ import org.springframework.web.bind.annotation.*;
  */
 @RestController
 @RequestMapping("/api/orders")
-@CrossOrigin(origins = "*")
+@CrossOrigin(originPatterns = "*")
 public class OrderController {
     
     @Autowired

+ 1 - 1
src/main/java/com/we/controller/PaymentController.java

@@ -12,7 +12,7 @@ import java.util.concurrent.ConcurrentHashMap;
  */
 @RestController
 @RequestMapping("/api/payment")
-@CrossOrigin(origins = "*")
+@CrossOrigin(originPatterns = "*")
 public class PaymentController {
     
     // 模拟支付状态存储(实际应该用Redis或数据库)

+ 4 - 0
src/main/java/com/we/service/DishService.java

@@ -1,8 +1,12 @@
 package com.we.service;
 
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.bebind.dto.DishDto;
 import com.we.domain.Dish;
 import com.baomidou.mybatisplus.extension.service.IService;
 
+import java.util.List;
+
 /**
 * @author Lenovo
 * @description 针对表【dish(菜品核心表)】的数据库操作Service

+ 7 - 0
src/main/java/com/we/service/impl/DishServiceImpl.java

@@ -1,11 +1,17 @@
 package com.we.service.impl;
 
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.bebind.dto.DishDto;
 import com.we.domain.Dish;
 import com.we.service.DishService;
 import com.we.mapper.DishMapper;
 import org.springframework.stereotype.Service;
 
+import java.util.List;
+
 /**
 * @author Lenovo
 * @description 针对表【dish(菜品核心表)】的数据库操作Service实现
@@ -15,6 +21,7 @@ import org.springframework.stereotype.Service;
 public class DishServiceImpl extends ServiceImpl<DishMapper, Dish>
     implements DishService{
 
+
 }
 
 

+ 13 - 1
src/main/resources/application.yml

@@ -1,5 +1,9 @@
 server:
   port: 8086
+  servlet:
+    multipart:
+      max-file-size: 10MB
+      max-request-size: 10MB
 
 spring:
   datasource:
@@ -14,4 +18,12 @@ mybatis-plus:
   type-handlers-package: com.zhentao.utils
   configuration:
     map-underscore-to-camel-case: true  # 下划线转驼峰
-    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl  # 打印SQL日志
+    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl  # 打印SQL日志
+# MinIO配置
+minio:
+  endpoint: http://120.26.175.13:9000  # MinIO API端口(不是控制台的9001)
+  accessKey: minioadmin  # 你的MinIO账号
+  secretKey: minioadmin  # 你的MinIO密码
+  bucketName: fandain  # 存储图片的桶名
+  # 图片访问前缀(MinIO控制台访问地址,用于拼接可访问链接)
+  previewUrl: http://120.26.175.13:9000/

+ 19 - 0
src/main/resources/com/bebind/mapper/DishCategoryMapper.xml

@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE mapper
+        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
+        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.bebind.mapper.DishCategoryMapper">
+
+    <resultMap id="BaseResultMap" type="com.bebind.domain.DishCategory">
+            <id property="id" column="id" />
+            <result property="categoryName" column="category_name" />
+            <result property="sort" column="sort" />
+            <result property="status" column="status" />
+            <result property="createTime" column="create_time" />
+            <result property="updateTime" column="update_time" />
+    </resultMap>
+
+    <sql id="Base_Column_List">
+        id,category_name,sort,status,create_time,update_time
+    </sql>
+</mapper>

+ 33 - 0
src/main/resources/com/bebind/mapper/DishMapper.xml

@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE mapper
+        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
+        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.bebind.mapper.DishMapper">
+
+    <resultMap id="BaseResultMap" type="com.bebind.domain.Dish">
+            <id property="id" column="id" />
+            <result property="dishName" column="dish_name" />
+            <result property="categoryId" column="category_id" />
+            <result property="flavorId" column="flavor_id" />
+            <result property="coreMaterial" column="core_material" />
+            <result property="price" column="price" />
+            <result property="costPrice" column="cost_price" />
+            <result property="stock" column="stock" />
+            <result property="sales" column="sales" />
+            <result property="tasteFeature" column="taste_feature" />
+            <result property="adaptScene" column="adapt_scene" />
+            <result property="coverImg" column="cover_img" />
+            <result property="description" column="description" />
+            <result property="status" column="status" />
+            <result property="isDelete" column="is_delete" />
+            <result property="createTime" column="create_time" />
+            <result property="updateTime" column="update_time" />
+    </resultMap>
+
+    <sql id="Base_Column_List">
+        id,dish_name,category_id,flavor_id,core_material,price,
+        cost_price,stock,sales,taste_feature,adapt_scene,
+        cover_img,description,status,is_delete,create_time,
+        update_time
+    </sql>
+</mapper>

+ 19 - 0
src/main/resources/com/bebind/mapper/FlaverMapper.xml

@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE mapper
+        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
+        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.bebind.mapper.FlaverMapper">
+
+    <resultMap id="BaseResultMap" type="com.bebind.domain.Flaver">
+            <id property="id" column="id" />
+            <result property="flavorName" column="flavor_name" />
+            <result property="sort" column="sort" />
+            <result property="status" column="status" />
+            <result property="createTime" column="create_time" />
+            <result property="updateTime" column="update_time" />
+    </resultMap>
+
+    <sql id="Base_Column_List">
+        id,flavor_name,sort,status,create_time,update_time
+    </sql>
+</mapper>

+ 21 - 0
src/main/resources/com/bebind/mapper/OrderDetailMapper.xml

@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE mapper
+        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
+        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.bebind.mapper.OrderDetailMapper">
+
+    <resultMap id="BaseResultMap" type="com.bebind.domain.OrderDetail">
+            <id property="id" column="id" />
+            <result property="orderId" column="order_id" />
+            <result property="dishId" column="dish_id" />
+            <result property="dishName" column="dish_name" />
+            <result property="price" column="price" />
+            <result property="quantity" column="quantity" />
+            <result property="subtotal" column="subtotal" />
+    </resultMap>
+
+    <sql id="Base_Column_List">
+        id,order_id,dish_id,dish_name,price,quantity,
+        subtotal
+    </sql>
+</mapper>

+ 23 - 0
src/main/resources/com/bebind/mapper/OrdersMapper.xml

@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE mapper
+        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
+        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.bebind.mapper.OrdersMapper">
+
+    <resultMap id="BaseResultMap" type="com.bebind.domain.Orders">
+            <id property="id" column="id" />
+            <result property="orderNumber" column="order_number" />
+            <result property="totalAmount" column="total_amount" />
+            <result property="deliveryFee" column="delivery_fee" />
+            <result property="finalAmount" column="final_amount" />
+            <result property="status" column="status" />
+            <result property="remark" column="remark" />
+            <result property="createTime" column="create_time" />
+            <result property="updateTime" column="update_time" />
+    </resultMap>
+
+    <sql id="Base_Column_List">
+        id,order_number,total_amount,delivery_fee,final_amount,status,
+        remark,create_time,update_time
+    </sql>
+</mapper>

+ 0 - 13
src/test/java/com/we/OrderFoodApplicationTests.java

@@ -1,13 +0,0 @@
-package com.we;
-
-import org.junit.jupiter.api.Test;
-import org.springframework.boot.test.context.SpringBootTest;
-
-@SpringBootTest
-class OrderFoodApplicationTests {
-
-    @Test
-    void contextLoads() {
-    }
-
-}