From d6451cf188c255851d889c16e1cf29956f1ed9fc Mon Sep 17 00:00:00 2001 From: wangziqi Date: Tue, 10 Feb 2026 15:14:23 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E9=87=8D=E6=9E=84=E5=90=8E=E5=8F=B0?= =?UTF-8?q?=E8=B7=AF=E7=94=B1=E5=B9=B6=E4=BD=BF=E7=94=A8=E5=B5=8C=E5=A5=97?= =?UTF-8?q?=E8=B7=AF=E7=94=B1=EF=BC=9B=E6=B7=BB=E5=8A=A0=E5=9B=BE=E7=89=87?= =?UTF-8?q?=E4=B8=8A=E4=BC=A0=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 拆分 AdminView 为多个子页面组件,使用嵌套路由 - 拆分 MerchantView 为多个子页面组件,使用嵌套路由 - 创建 AdminLayout 和 MerchantLayout 布局组件 - 修复编译错误:IconSend 重复导入、IconDatabase 不存在 - 修复 HomeView 缺失 onMounted 导入 - 添加文件上传后端接口和静态资源映射 - 为商品和轮播图添加图片上传功能(支持预览、清除) --- .../com/maternalmall/config/WebMvcConfig.java | 14 +- .../controller/FileUploadController.java | 73 ++ frontend/src/api/index.js | 18 +- frontend/src/router/index.js | 68 +- frontend/src/views/AdminView.vue | 928 ++++++++++++++---- frontend/src/views/CartView.vue | 302 +++++- frontend/src/views/HomeView.vue | 504 ++++++++-- frontend/src/views/MerchantView.vue | 595 ++++++++--- frontend/src/views/OrdersView.vue | 443 +++++++-- frontend/src/views/admin/AdminAudit.vue | 106 ++ frontend/src/views/admin/AdminBanners.vue | 231 +++++ frontend/src/views/admin/AdminInventory.vue | 52 + frontend/src/views/admin/AdminLayout.vue | 150 +++ frontend/src/views/admin/AdminLogistics.vue | 52 + frontend/src/views/admin/AdminOrders.vue | 134 +++ frontend/src/views/admin/AdminOverview.vue | 153 +++ frontend/src/views/admin/AdminProducts.vue | 291 ++++++ frontend/src/views/admin/AdminProfile.vue | 84 ++ frontend/src/views/admin/AdminReviews.vue | 62 ++ frontend/src/views/admin/AdminRisk.vue | 65 ++ frontend/src/views/admin/AdminUsers.vue | 153 +++ .../src/views/merchant/MerchantInventory.vue | 63 ++ .../src/views/merchant/MerchantLayout.vue | 126 +++ .../src/views/merchant/MerchantLogistics.vue | 36 + .../src/views/merchant/MerchantOrders.vue | 123 +++ .../src/views/merchant/MerchantOverview.vue | 209 ++++ .../src/views/merchant/MerchantProducts.vue | 256 +++++ .../src/views/merchant/MerchantProfile.vue | 84 ++ .../src/views/merchant/MerchantReviews.vue | 52 + 29 files changed, 4948 insertions(+), 479 deletions(-) create mode 100644 backend/src/main/java/com/maternalmall/controller/FileUploadController.java create mode 100644 frontend/src/views/admin/AdminAudit.vue create mode 100644 frontend/src/views/admin/AdminBanners.vue create mode 100644 frontend/src/views/admin/AdminInventory.vue create mode 100644 frontend/src/views/admin/AdminLayout.vue create mode 100644 frontend/src/views/admin/AdminLogistics.vue create mode 100644 frontend/src/views/admin/AdminOrders.vue create mode 100644 frontend/src/views/admin/AdminOverview.vue create mode 100644 frontend/src/views/admin/AdminProducts.vue create mode 100644 frontend/src/views/admin/AdminProfile.vue create mode 100644 frontend/src/views/admin/AdminReviews.vue create mode 100644 frontend/src/views/admin/AdminRisk.vue create mode 100644 frontend/src/views/admin/AdminUsers.vue create mode 100644 frontend/src/views/merchant/MerchantInventory.vue create mode 100644 frontend/src/views/merchant/MerchantLayout.vue create mode 100644 frontend/src/views/merchant/MerchantLogistics.vue create mode 100644 frontend/src/views/merchant/MerchantOrders.vue create mode 100644 frontend/src/views/merchant/MerchantOverview.vue create mode 100644 frontend/src/views/merchant/MerchantProducts.vue create mode 100644 frontend/src/views/merchant/MerchantProfile.vue create mode 100644 frontend/src/views/merchant/MerchantReviews.vue diff --git a/backend/src/main/java/com/maternalmall/config/WebMvcConfig.java b/backend/src/main/java/com/maternalmall/config/WebMvcConfig.java index f0c8f17..bfe1a00 100644 --- a/backend/src/main/java/com/maternalmall/config/WebMvcConfig.java +++ b/backend/src/main/java/com/maternalmall/config/WebMvcConfig.java @@ -1,25 +1,37 @@ package com.maternalmall.config; +import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.CorsRegistry; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; +import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; @Configuration public class WebMvcConfig implements WebMvcConfigurer { private final AuthInterceptor authInterceptor; + @Value("${app.upload-path:./uploads}") + private String uploadPath; + public WebMvcConfig(AuthInterceptor authInterceptor) { this.authInterceptor = authInterceptor; } @Override public void addInterceptors(InterceptorRegistry registry) { - registry.addInterceptor(authInterceptor).addPathPatterns("/**"); + registry.addInterceptor(authInterceptor).addPathPatterns("/**") + .excludePathPatterns("/uploads/**"); } @Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/**").allowedOrigins("*").allowedMethods("*").allowedHeaders("*"); } + + @Override + public void addResourceHandlers(ResourceHandlerRegistry registry) { + registry.addResourceHandler("/uploads/**") + .addResourceLocations("file:" + uploadPath + "/"); + } } diff --git a/backend/src/main/java/com/maternalmall/controller/FileUploadController.java b/backend/src/main/java/com/maternalmall/controller/FileUploadController.java new file mode 100644 index 0000000..10f3c90 --- /dev/null +++ b/backend/src/main/java/com/maternalmall/controller/FileUploadController.java @@ -0,0 +1,73 @@ +package com.maternalmall.controller; + +import com.maternalmall.common.ApiResponse; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.UUID; + +@RestController +@RequestMapping("/api") +public class FileUploadController { + + @Value("${app.upload-path:./uploads}") + private String uploadPath; + + @Value("${app.upload-url-prefix:/uploads}") + private String uploadUrlPrefix; + + @PostMapping("/upload") + public ApiResponse uploadFile(@RequestParam("file") MultipartFile file) { + if (file.isEmpty()) { + return ApiResponse.error("请选择要上传的文件"); + } + + try { + // 创建上传目录 + Path uploadDir = Paths.get(uploadPath); + if (!Files.exists(uploadDir)) { + Files.createDirectories(uploadDir); + } + + // 生成唯一文件名 + String originalFilename = file.getOriginalFilename(); + String extension = ""; + if (originalFilename != null && originalFilename.contains(".")) { + extension = originalFilename.substring(originalFilename.lastIndexOf(".")); + } + String newFilename = UUID.randomUUID().toString().replace("-", "") + extension; + + // 保存文件 + Path filePath = uploadDir.resolve(newFilename); + file.transferTo(filePath.toFile()); + + // 返回文件URL + String fileUrl = uploadUrlPrefix + "/" + newFilename; + return ApiResponse.success(fileUrl); + + } catch (IOException e) { + return ApiResponse.error("文件上传失败: " + e.getMessage()); + } + } + + @PostMapping("/upload/image") + public ApiResponse uploadImage(@RequestParam("file") MultipartFile file) { + if (file.isEmpty()) { + return ApiResponse.error("请选择要上传的图片"); + } + + // 检查文件类型 + String contentType = file.getContentType(); + if (contentType == null || !contentType.startsWith("image/")) { + return ApiResponse.error("只能上传图片文件"); + } + + return uploadFile(file); + } +} \ No newline at end of file diff --git a/frontend/src/api/index.js b/frontend/src/api/index.js index cc692ea..cd732c8 100644 --- a/frontend/src/api/index.js +++ b/frontend/src/api/index.js @@ -61,5 +61,21 @@ export const api = { adminDeleteProduct: (id) => http.delete(`/api/admin/products/${id}`), adminReviews: () => http.get('/api/admin/reviews'), adminLogistics: () => http.get('/api/admin/logistics'), - adminInventory: () => http.get('/api/admin/inventory') + adminInventory: () => http.get('/api/admin/inventory'), + + // File Upload + uploadImage: (file) => { + const formData = new FormData() + formData.append('file', file) + return http.post('/api/upload/image', formData, { + headers: { 'Content-Type': 'multipart/form-data' } + }) + }, + uploadFile: (file) => { + const formData = new FormData() + formData.append('file', file) + return http.post('/api/upload', formData, { + headers: { 'Content-Type': 'multipart/form-data' } + }) + } } diff --git a/frontend/src/router/index.js b/frontend/src/router/index.js index 7400f4d..eb107c5 100644 --- a/frontend/src/router/index.js +++ b/frontend/src/router/index.js @@ -5,8 +5,30 @@ import CartView from '../views/CartView.vue' import OrdersView from '../views/OrdersView.vue' import FavoritesView from '../views/FavoritesView.vue' import ProfileView from '../views/ProfileView.vue' -import AdminView from '../views/AdminView.vue' -import MerchantView from '../views/MerchantView.vue' + +// Admin Layout & Pages +import AdminLayout from '../views/admin/AdminLayout.vue' +import AdminOverview from '../views/admin/AdminOverview.vue' +import AdminOrders from '../views/admin/AdminOrders.vue' +import AdminRisk from '../views/admin/AdminRisk.vue' +import AdminProducts from '../views/admin/AdminProducts.vue' +import AdminAudit from '../views/admin/AdminAudit.vue' +import AdminUsers from '../views/admin/AdminUsers.vue' +import AdminBanners from '../views/admin/AdminBanners.vue' +import AdminReviews from '../views/admin/AdminReviews.vue' +import AdminLogistics from '../views/admin/AdminLogistics.vue' +import AdminInventory from '../views/admin/AdminInventory.vue' +import AdminProfile from '../views/admin/AdminProfile.vue' + +// Merchant Layout & Pages +import MerchantLayout from '../views/merchant/MerchantLayout.vue' +import MerchantOverview from '../views/merchant/MerchantOverview.vue' +import MerchantProducts from '../views/merchant/MerchantProducts.vue' +import MerchantOrders from '../views/merchant/MerchantOrders.vue' +import MerchantReviews from '../views/merchant/MerchantReviews.vue' +import MerchantLogistics from '../views/merchant/MerchantLogistics.vue' +import MerchantInventory from '../views/merchant/MerchantInventory.vue' +import MerchantProfile from '../views/merchant/MerchantProfile.vue' const routes = [ { path: '/', redirect: '/products' }, @@ -16,9 +38,43 @@ const routes = [ { path: '/favorites', component: FavoritesView }, { path: '/profile', component: ProfileView }, { path: '/login', component: LoginView }, - { path: '/admin', component: AdminView }, - { path: '/merchant', component: MerchantView }, - { path: '/customer', redirect: '/products' } + { path: '/customer', redirect: '/products' }, + + // Admin Routes + { + path: '/admin', + component: AdminLayout, + redirect: '/admin/overview', + children: [ + { path: 'overview', name: 'admin-overview', component: AdminOverview }, + { path: 'orders', name: 'admin-orders', component: AdminOrders }, + { path: 'risk', name: 'admin-risk', component: AdminRisk }, + { path: 'products', name: 'admin-products', component: AdminProducts }, + { path: 'audit', name: 'admin-audit', component: AdminAudit }, + { path: 'users', name: 'admin-users', component: AdminUsers }, + { path: 'banners', name: 'admin-banners', component: AdminBanners }, + { path: 'reviews', name: 'admin-reviews', component: AdminReviews }, + { path: 'logistics', name: 'admin-logistics', component: AdminLogistics }, + { path: 'inventory', name: 'admin-inventory', component: AdminInventory }, + { path: 'profile', name: 'admin-profile', component: AdminProfile } + ] + }, + + // Merchant Routes + { + path: '/merchant', + component: MerchantLayout, + redirect: '/merchant/overview', + children: [ + { path: 'overview', name: 'merchant-overview', component: MerchantOverview }, + { path: 'products', name: 'merchant-products', component: MerchantProducts }, + { path: 'orders', name: 'merchant-orders', component: MerchantOrders }, + { path: 'reviews', name: 'merchant-reviews', component: MerchantReviews }, + { path: 'logistics', name: 'merchant-logistics', component: MerchantLogistics }, + { path: 'inventory', name: 'merchant-inventory', component: MerchantInventory }, + { path: 'profile', name: 'merchant-profile', component: MerchantProfile } + ] + } ] const router = createRouter({ @@ -26,4 +82,4 @@ const router = createRouter({ routes }) -export default router +export default router \ No newline at end of file diff --git a/frontend/src/views/AdminView.vue b/frontend/src/views/AdminView.vue index 24de449..5de92d4 100644 --- a/frontend/src/views/AdminView.vue +++ b/frontend/src/views/AdminView.vue @@ -1,55 +1,151 @@