commit a7ce0a089eb2fb47a85ba976be3d1059a675e8c2 Author: wangziqi Date: Mon Feb 9 09:51:14 2026 -0800 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d92744d --- /dev/null +++ b/.gitignore @@ -0,0 +1,48 @@ +# Node.js +node_modules/ +npm-debug.log +yarn-error.log +yarn-debug.log +.pnpm-debug.log +.npm +.yarn + +# Build outputs +dist/ +build/ +out/ +.next/ +.nuxt/ +.cache/ + +# Environment variables +.env +.env.local +.env.development.local +.env.test.local +.env.production.local + +# IDE and editors +.vscode/ +.idea/ +*.swp +*.swo +.DS_Store +*.sublime-project +*.sublime-workspace + +# Logs +logs +*.log + +# Testing +coverage/ +.nyc_output/ + +# Dependency directories +jspm_packages/ +bower_components/ + +# Misc +.DS_Store +Thumbs.db \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..5d494ab --- /dev/null +++ b/README.md @@ -0,0 +1,79 @@ +# 萌贝母婴商城(Spring Boot + Vue + Arco Design + MySQL) + +基于《崔梦雪-开题报告修改版》实现的三端基础版母婴商城,包含顾客端、商家端、管理员端核心功能。 + +## 1. 技术栈 + +- 后端: Spring Boot 3 + Spring Web + Spring Data JPA +- 前端: Vue3 + Vite + Arco Design +- 数据库: MySQL 8 + +## 2. 功能覆盖 + +### 顾客 +- 登录注册 +- 搜索/浏览商品 +- 购物车管理(加购、移除、结算) +- 下单购买 +- 收藏管理 +- 评价商品 +- 订单管理(退款申请、查看物流、修改地址、删除订单) +- 个人信息修改 + +### 商家 +- 数据概览(订单量、销售额、热销商品) +- 商品管理(增删改查) +- 订单管理(查看、发货、退款处理) +- 评价查看 +- 物流查看 +- 库存记录查看/删除 +- 个人信息修改(通过 `/api/auth/me`) + +### 管理员 +- 数据概览 +- 订单管理 +- 审核管理(商家入驻审核) +- 用户管理 +- 轮播图管理 +- 个人信息修改(通过 `/api/auth/me`) + +## 3. 目录结构 + +- `/Users/apple/code/bs/mying/backend` 后端工程 +- `/Users/apple/code/bs/mying/frontend` 前端工程 +- `/Users/apple/code/bs/mying/sql/init.sql` 数据库初始化脚本 + +## 4. 启动步骤 + +1. 创建并初始化数据库: + - 执行 `/Users/apple/code/bs/mying/sql/init.sql` +2. 修改后端数据库配置: + - 文件: `/Users/apple/code/bs/mying/backend/src/main/resources/application.yml` +3. 启动后端: +```bash +cd /Users/apple/code/bs/mying/backend +mvn spring-boot:run +``` +4. 启动前端: +```bash +cd /Users/apple/code/bs/mying/frontend +npm install +npm run dev +``` +5. 访问前端: + - `http://localhost:5173` + +## 5. 默认账号 + +- 管理员: `admin / 123456` +- 商家: `merchant1 / 123456` +- 顾客: `customer1 / 123456` + +## 6. 主要接口前缀 + +- `/api/auth` 登录注册与个人资料 +- `/api/public` 商品与轮播公开接口 +- `/api/customer` 顾客接口 +- `/api/merchant` 商家接口 +- `/api/admin` 管理员接口 + diff --git a/backend/pom.xml b/backend/pom.xml new file mode 100644 index 0000000..4eeaa4e --- /dev/null +++ b/backend/pom.xml @@ -0,0 +1,59 @@ + + 4.0.0 + com.maternalmall + backend + 1.0.0 + maternal-mall-backend + Maternal mall backend + + + org.springframework.boot + spring-boot-starter-parent + 3.3.5 + + + + + 17 + + + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-data-jpa + + + com.mysql + mysql-connector-j + runtime + + + org.projectlombok + lombok + true + + + org.springframework.boot + spring-boot-starter-validation + + + org.springframework.boot + spring-boot-starter-test + test + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + diff --git a/backend/src/main/java/com/maternalmall/MaternalMallApplication.java b/backend/src/main/java/com/maternalmall/MaternalMallApplication.java new file mode 100644 index 0000000..3de676b --- /dev/null +++ b/backend/src/main/java/com/maternalmall/MaternalMallApplication.java @@ -0,0 +1,11 @@ +package com.maternalmall; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class MaternalMallApplication { + public static void main(String[] args) { + SpringApplication.run(MaternalMallApplication.class, args); + } +} diff --git a/backend/src/main/java/com/maternalmall/common/ApiResponse.java b/backend/src/main/java/com/maternalmall/common/ApiResponse.java new file mode 100644 index 0000000..f24a2de --- /dev/null +++ b/backend/src/main/java/com/maternalmall/common/ApiResponse.java @@ -0,0 +1,26 @@ +package com.maternalmall.common; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@NoArgsConstructor +@AllArgsConstructor +public class ApiResponse { + private int code; + private String message; + private T data; + + public static ApiResponse ok(T data) { + return new ApiResponse<>(0, "ok", data); + } + + public static ApiResponse ok() { + return new ApiResponse<>(0, "ok", null); + } + + public static ApiResponse fail(String message) { + return new ApiResponse<>(-1, message, null); + } +} diff --git a/backend/src/main/java/com/maternalmall/common/BizException.java b/backend/src/main/java/com/maternalmall/common/BizException.java new file mode 100644 index 0000000..5afd58b --- /dev/null +++ b/backend/src/main/java/com/maternalmall/common/BizException.java @@ -0,0 +1,7 @@ +package com.maternalmall.common; + +public class BizException extends RuntimeException { + public BizException(String message) { + super(message); + } +} diff --git a/backend/src/main/java/com/maternalmall/common/GlobalExceptionHandler.java b/backend/src/main/java/com/maternalmall/common/GlobalExceptionHandler.java new file mode 100644 index 0000000..3f8a86e --- /dev/null +++ b/backend/src/main/java/com/maternalmall/common/GlobalExceptionHandler.java @@ -0,0 +1,32 @@ +package com.maternalmall.common; + +import jakarta.validation.ConstraintViolationException; +import org.springframework.web.bind.MethodArgumentNotValidException; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.RestControllerAdvice; + +@RestControllerAdvice +public class GlobalExceptionHandler { + + @ExceptionHandler(BizException.class) + public ApiResponse handleBiz(BizException ex) { + return ApiResponse.fail(ex.getMessage()); + } + + @ExceptionHandler(MethodArgumentNotValidException.class) + public ApiResponse handleValid(MethodArgumentNotValidException ex) { + String msg = ex.getBindingResult().getFieldErrors().stream().findFirst() + .map(e -> e.getField() + ":" + e.getDefaultMessage()).orElse("参数错误"); + return ApiResponse.fail(msg); + } + + @ExceptionHandler(ConstraintViolationException.class) + public ApiResponse handleConstraint(ConstraintViolationException ex) { + return ApiResponse.fail(ex.getMessage()); + } + + @ExceptionHandler(Exception.class) + public ApiResponse handleUnknown(Exception ex) { + return ApiResponse.fail(ex.getMessage()); + } +} diff --git a/backend/src/main/java/com/maternalmall/config/AuthContext.java b/backend/src/main/java/com/maternalmall/config/AuthContext.java new file mode 100644 index 0000000..7693194 --- /dev/null +++ b/backend/src/main/java/com/maternalmall/config/AuthContext.java @@ -0,0 +1,11 @@ +package com.maternalmall.config; + +import com.maternalmall.domain.User; + +public class AuthContext { + public static final String CURRENT_USER = "CURRENT_USER"; + + public static User getUser(jakarta.servlet.http.HttpServletRequest request) { + return (User) request.getAttribute(CURRENT_USER); + } +} diff --git a/backend/src/main/java/com/maternalmall/config/AuthInterceptor.java b/backend/src/main/java/com/maternalmall/config/AuthInterceptor.java new file mode 100644 index 0000000..5986a89 --- /dev/null +++ b/backend/src/main/java/com/maternalmall/config/AuthInterceptor.java @@ -0,0 +1,58 @@ +package com.maternalmall.config; + +import com.maternalmall.domain.User; +import com.maternalmall.repository.UserRepository; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.HttpMethod; +import org.springframework.stereotype.Component; +import org.springframework.web.servlet.HandlerInterceptor; + +import java.util.Set; + +@Component +public class AuthInterceptor implements HandlerInterceptor { + private final UserRepository userRepository; + + @Value("${app.token-header:X-Token}") + private String tokenHeader; + + private static final Set PUBLIC_PREFIX = Set.of( + "/api/auth/login", + "/api/auth/register", + "/api/public" + ); + + public AuthInterceptor(UserRepository userRepository) { + this.userRepository = userRepository; + } + + @Override + public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { + // Let browser CORS preflight pass without auth token. + if (HttpMethod.OPTIONS.matches(request.getMethod())) { + response.setStatus(HttpServletResponse.SC_OK); + return true; + } + + String uri = request.getRequestURI(); + if (uri.equals("/") || uri.startsWith("/error") || PUBLIC_PREFIX.stream().anyMatch(uri::startsWith)) { + return true; + } + String token = request.getHeader(tokenHeader); + if (token == null || token.isBlank()) { + response.setStatus(401); + response.getWriter().write("{\"code\":401,\"message\":\"请先登录\",\"data\":null}"); + return false; + } + User user = userRepository.findByToken(token).orElse(null); + if (user == null || !Boolean.TRUE.equals(user.getEnabled())) { + response.setStatus(401); + response.getWriter().write("{\"code\":401,\"message\":\"登录状态无效\",\"data\":null}"); + return false; + } + request.setAttribute(AuthContext.CURRENT_USER, user); + return true; + } +} diff --git a/backend/src/main/java/com/maternalmall/config/WebMvcConfig.java b/backend/src/main/java/com/maternalmall/config/WebMvcConfig.java new file mode 100644 index 0000000..f0c8f17 --- /dev/null +++ b/backend/src/main/java/com/maternalmall/config/WebMvcConfig.java @@ -0,0 +1,25 @@ +package com.maternalmall.config; + +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.WebMvcConfigurer; + +@Configuration +public class WebMvcConfig implements WebMvcConfigurer { + private final AuthInterceptor authInterceptor; + + public WebMvcConfig(AuthInterceptor authInterceptor) { + this.authInterceptor = authInterceptor; + } + + @Override + public void addInterceptors(InterceptorRegistry registry) { + registry.addInterceptor(authInterceptor).addPathPatterns("/**"); + } + + @Override + public void addCorsMappings(CorsRegistry registry) { + registry.addMapping("/**").allowedOrigins("*").allowedMethods("*").allowedHeaders("*"); + } +} diff --git a/backend/src/main/java/com/maternalmall/controller/AdminController.java b/backend/src/main/java/com/maternalmall/controller/AdminController.java new file mode 100644 index 0000000..3bcfea1 --- /dev/null +++ b/backend/src/main/java/com/maternalmall/controller/AdminController.java @@ -0,0 +1,165 @@ +package com.maternalmall.controller; + +import com.maternalmall.common.ApiResponse; +import com.maternalmall.config.AuthContext; +import com.maternalmall.domain.*; +import com.maternalmall.service.MallService; +import jakarta.servlet.http.HttpServletRequest; +import org.springframework.web.bind.annotation.*; + +import java.util.List; +import java.util.Map; + +@RestController +@RequestMapping("/api/admin") +public class AdminController { + private final MallService mallService; + + public AdminController(MallService mallService) { + this.mallService = mallService; + } + + private void check(User user) { + mallService.requireRole(user, UserRole.ADMIN); + } + + @GetMapping("/overview") + public ApiResponse> overview(HttpServletRequest request) { + check(AuthContext.getUser(request)); + return ApiResponse.ok(mallService.adminOverview()); + } + + @GetMapping("/orders") + public ApiResponse> orders(HttpServletRequest request) { + check(AuthContext.getUser(request)); + return ApiResponse.ok(mallService.adminOrders()); + } + + @PutMapping("/orders/{id}") + public ApiResponse updateOrder(HttpServletRequest request, @PathVariable Long id, @RequestBody Map body) { + check(AuthContext.getUser(request)); + return ApiResponse.ok(mallService.adminUpdateOrder(id, body.get("status"), body.getOrDefault("logisticsInfo", ""))); + } + + @GetMapping("/orders/risk") + public ApiResponse>> riskOrders(HttpServletRequest request) { + check(AuthContext.getUser(request)); + return ApiResponse.ok(mallService.adminRiskOrders()); + } + + @PutMapping("/orders/{id}/refund-audit") + public ApiResponse auditRefund(HttpServletRequest request, @PathVariable Long id, @RequestBody Map body) { + check(AuthContext.getUser(request)); + boolean approve = Boolean.parseBoolean(String.valueOf(body.getOrDefault("approve", false))); + String remark = String.valueOf(body.getOrDefault("remark", "")); + return ApiResponse.ok(mallService.adminAuditRefund(id, approve, remark)); + } + + @PutMapping("/orders/{id}/ship-audit") + public ApiResponse auditShipment(HttpServletRequest request, @PathVariable Long id, @RequestBody Map body) { + check(AuthContext.getUser(request)); + boolean approve = Boolean.parseBoolean(String.valueOf(body.getOrDefault("approve", true))); + String remark = String.valueOf(body.getOrDefault("remark", "")); + return ApiResponse.ok(mallService.adminAuditShipment(id, approve, remark)); + } + + @GetMapping("/merchant-applications") + public ApiResponse>> merchantApplications(HttpServletRequest request) { + check(AuthContext.getUser(request)); + return ApiResponse.ok(mallService.adminMerchantApplications()); + } + + @PutMapping("/merchant-applications/{id}") + public ApiResponse auditMerchant(HttpServletRequest request, @PathVariable Long id, @RequestBody Map body) { + check(AuthContext.getUser(request)); + return ApiResponse.ok(mallService.adminAuditApplication(id, body.get("status"), body.getOrDefault("remark", ""))); + } + + @GetMapping("/users") + public ApiResponse> users(HttpServletRequest request) { + check(AuthContext.getUser(request)); + return ApiResponse.ok(mallService.adminUsers()); + } + + @PostMapping("/users") + public ApiResponse saveUser(HttpServletRequest request, @RequestBody User user) { + check(AuthContext.getUser(request)); + return ApiResponse.ok(mallService.adminSaveUser(user)); + } + + @DeleteMapping("/users/{id}") + public ApiResponse deleteUser(HttpServletRequest request, @PathVariable Long id) { + check(AuthContext.getUser(request)); + mallService.adminDeleteUser(id); + return ApiResponse.ok(); + } + + @GetMapping("/banners") + public ApiResponse> banners(HttpServletRequest request) { + check(AuthContext.getUser(request)); + return ApiResponse.ok(mallService.adminBanners()); + } + + @PostMapping("/banners") + public ApiResponse saveBanner(HttpServletRequest request, @RequestBody Banner banner) { + check(AuthContext.getUser(request)); + return ApiResponse.ok(mallService.adminSaveBanner(banner)); + } + + @DeleteMapping("/banners/{id}") + public ApiResponse deleteBanner(HttpServletRequest request, @PathVariable Long id) { + check(AuthContext.getUser(request)); + mallService.adminDeleteBanner(id); + return ApiResponse.ok(); + } + + @GetMapping("/products") + public ApiResponse> products(HttpServletRequest request) { + check(AuthContext.getUser(request)); + return ApiResponse.ok(mallService.adminProducts()); + } + + @GetMapping("/products/views") + public ApiResponse>> productViews(HttpServletRequest request) { + check(AuthContext.getUser(request)); + return ApiResponse.ok(mallService.adminProductViews()); + } + + @PostMapping("/products") + public ApiResponse saveProduct(HttpServletRequest request, @RequestBody Product product) { + check(AuthContext.getUser(request)); + return ApiResponse.ok(mallService.adminSaveProduct(product)); + } + + @PutMapping("/products/{id}/approve") + public ApiResponse approveProduct(HttpServletRequest request, @PathVariable Long id, @RequestBody Map body) { + check(AuthContext.getUser(request)); + boolean approved = Boolean.parseBoolean(String.valueOf(body.getOrDefault("approved", true))); + return ApiResponse.ok(mallService.adminApproveProduct(id, approved)); + } + + @DeleteMapping("/products/{id}") + public ApiResponse deleteProduct(HttpServletRequest request, @PathVariable Long id) { + check(AuthContext.getUser(request)); + mallService.adminDeleteProduct(id); + return ApiResponse.ok(); + } + + @GetMapping("/reviews") + public ApiResponse>> reviews(HttpServletRequest request) { + check(AuthContext.getUser(request)); + return ApiResponse.ok(mallService.adminReviews()); + } + + @GetMapping("/logistics") + public ApiResponse>> logistics(HttpServletRequest request) { + check(AuthContext.getUser(request)); + return ApiResponse.ok(mallService.adminLogistics()); + } + + @GetMapping("/inventory") + public ApiResponse>> inventory(HttpServletRequest request) { + check(AuthContext.getUser(request)); + return ApiResponse.ok(mallService.adminInventory()); + } +} diff --git a/backend/src/main/java/com/maternalmall/controller/AuthController.java b/backend/src/main/java/com/maternalmall/controller/AuthController.java new file mode 100644 index 0000000..cdd00b1 --- /dev/null +++ b/backend/src/main/java/com/maternalmall/controller/AuthController.java @@ -0,0 +1,47 @@ +package com.maternalmall.controller; + +import com.maternalmall.common.ApiResponse; +import com.maternalmall.config.AuthContext; +import com.maternalmall.domain.User; +import com.maternalmall.repository.UserRepository; +import com.maternalmall.service.AuthService; +import jakarta.servlet.http.HttpServletRequest; +import org.springframework.web.bind.annotation.*; + +import java.util.Map; + +@RestController +@RequestMapping("/api/auth") +public class AuthController { + private final AuthService authService; + private final UserRepository userRepository; + + public AuthController(AuthService authService, UserRepository userRepository) { + this.authService = authService; + this.userRepository = userRepository; + } + + @PostMapping("/register") + public ApiResponse> register(@RequestBody Map body) { + return ApiResponse.ok(authService.register(body.get("username"), body.get("password"), body.getOrDefault("role", "CUSTOMER"))); + } + + @PostMapping("/login") + public ApiResponse> login(@RequestBody Map body) { + return ApiResponse.ok(authService.login(body.get("username"), body.get("password"))); + } + + @GetMapping("/me") + public ApiResponse me(HttpServletRequest request) { + return ApiResponse.ok(AuthContext.getUser(request)); + } + + @PutMapping("/me") + public ApiResponse updateMe(HttpServletRequest request, @RequestBody Map body) { + User user = AuthContext.getUser(request); + user.setNickname(body.getOrDefault("nickname", user.getNickname())); + user.setPhone(body.getOrDefault("phone", user.getPhone())); + user.setAddress(body.getOrDefault("address", user.getAddress())); + return ApiResponse.ok(userRepository.save(user)); + } +} diff --git a/backend/src/main/java/com/maternalmall/controller/CustomerController.java b/backend/src/main/java/com/maternalmall/controller/CustomerController.java new file mode 100644 index 0000000..f3c7558 --- /dev/null +++ b/backend/src/main/java/com/maternalmall/controller/CustomerController.java @@ -0,0 +1,157 @@ +package com.maternalmall.controller; + +import com.maternalmall.common.ApiResponse; +import com.maternalmall.config.AuthContext; +import com.maternalmall.domain.*; +import com.maternalmall.service.MallService; +import jakarta.servlet.http.HttpServletRequest; +import org.springframework.web.bind.annotation.*; + +import java.util.List; +import java.util.Map; + +@RestController +@RequestMapping("/api/customer") +public class CustomerController { + private final MallService mallService; + + public CustomerController(MallService mallService) { + this.mallService = mallService; + } + + @GetMapping("/cart") + public ApiResponse> cart(HttpServletRequest request) { + User user = AuthContext.getUser(request); + mallService.requireRole(user, UserRole.CUSTOMER); + return ApiResponse.ok(mallService.cartList(user.getId())); + } + + @GetMapping("/cart/views") + public ApiResponse>> cartViews(HttpServletRequest request) { + User user = AuthContext.getUser(request); + mallService.requireRole(user, UserRole.CUSTOMER); + return ApiResponse.ok(mallService.cartViews(user.getId())); + } + + @PostMapping("/cart") + public ApiResponse addCart(HttpServletRequest request, @RequestBody Map body) { + User user = AuthContext.getUser(request); + mallService.requireRole(user, UserRole.CUSTOMER); + return ApiResponse.ok(mallService.addCart(user.getId(), Long.valueOf(body.get("productId").toString()), Integer.valueOf(body.get("quantity").toString()))); + } + + @DeleteMapping("/cart/{productId}") + public ApiResponse removeCart(HttpServletRequest request, @PathVariable Long productId) { + User user = AuthContext.getUser(request); + mallService.requireRole(user, UserRole.CUSTOMER); + mallService.removeCart(user.getId(), productId); + return ApiResponse.ok(); + } + + @GetMapping("/favorites") + public ApiResponse> favorites(HttpServletRequest request) { + User user = AuthContext.getUser(request); + mallService.requireRole(user, UserRole.CUSTOMER); + return ApiResponse.ok(mallService.favoriteList(user.getId())); + } + + @GetMapping("/favorites/views") + public ApiResponse>> favoriteViews(HttpServletRequest request) { + User user = AuthContext.getUser(request); + mallService.requireRole(user, UserRole.CUSTOMER); + return ApiResponse.ok(mallService.favoriteViews(user.getId())); + } + + @PostMapping("/favorites") + public ApiResponse addFavorite(HttpServletRequest request, @RequestBody Map body) { + User user = AuthContext.getUser(request); + mallService.requireRole(user, UserRole.CUSTOMER); + return ApiResponse.ok(mallService.addFavorite(user.getId(), Long.valueOf(body.get("productId").toString()))); + } + + @DeleteMapping("/favorites/{productId}") + public ApiResponse removeFavorite(HttpServletRequest request, @PathVariable Long productId) { + User user = AuthContext.getUser(request); + mallService.requireRole(user, UserRole.CUSTOMER); + mallService.removeFavorite(user.getId(), productId); + return ApiResponse.ok(); + } + + @PostMapping("/orders/checkout") + public ApiResponse checkout(HttpServletRequest request, @RequestBody Map body) { + User user = AuthContext.getUser(request); + mallService.requireRole(user, UserRole.CUSTOMER); + return ApiResponse.ok(mallService.checkout(user.getId(), body.get("address"))); + } + + @PostMapping("/orders/buy-now") + public ApiResponse buyNow(HttpServletRequest request, @RequestBody Map body) { + User user = AuthContext.getUser(request); + mallService.requireRole(user, UserRole.CUSTOMER); + Long productId = Long.valueOf(body.get("productId").toString()); + Integer quantity = Integer.valueOf(String.valueOf(body.getOrDefault("quantity", 1))); + String address = String.valueOf(body.getOrDefault("address", "")); + return ApiResponse.ok(mallService.buyNow(user.getId(), productId, quantity, address)); + } + + @GetMapping("/orders") + public ApiResponse> orders(HttpServletRequest request) { + User user = AuthContext.getUser(request); + mallService.requireRole(user, UserRole.CUSTOMER); + return ApiResponse.ok(mallService.customerOrders(user.getId())); + } + + @GetMapping("/orders/{orderId}/items") + public ApiResponse> orderItems(HttpServletRequest request, @PathVariable Long orderId) { + User user = AuthContext.getUser(request); + mallService.requireRole(user, UserRole.CUSTOMER); + return ApiResponse.ok(mallService.customerOrderItems(user.getId(), orderId)); + } + + @PutMapping("/orders/{orderId}/address") + public ApiResponse updateAddress(HttpServletRequest request, @PathVariable Long orderId, @RequestBody Map body) { + User user = AuthContext.getUser(request); + mallService.requireRole(user, UserRole.CUSTOMER); + return ApiResponse.ok(mallService.customerUpdateAddress(user.getId(), orderId, body.get("address"))); + } + + @PutMapping("/orders/{orderId}/refund") + public ApiResponse refund(HttpServletRequest request, @PathVariable Long orderId, @RequestBody Map body) { + User user = AuthContext.getUser(request); + mallService.requireRole(user, UserRole.CUSTOMER); + return ApiResponse.ok(mallService.customerRefund(user.getId(), orderId, body.get("reason"))); + } + + @DeleteMapping("/orders/{orderId}") + public ApiResponse deleteOrder(HttpServletRequest request, @PathVariable Long orderId) { + User user = AuthContext.getUser(request); + mallService.requireRole(user, UserRole.CUSTOMER); + mallService.customerDeleteOrder(user.getId(), orderId); + return ApiResponse.ok(); + } + + @GetMapping("/orders/{orderId}/logistics") + public ApiResponse> logistics(HttpServletRequest request, @PathVariable Long orderId) { + User user = AuthContext.getUser(request); + mallService.requireRole(user, UserRole.CUSTOMER); + return ApiResponse.ok(mallService.logisticsOfOrder(user.getId(), orderId)); + } + + @PostMapping("/reviews") + public ApiResponse review(HttpServletRequest request, @RequestBody Map body) { + User user = AuthContext.getUser(request); + mallService.requireRole(user, UserRole.CUSTOMER); + return ApiResponse.ok(mallService.addReview( + user.getId(), + Long.valueOf(body.get("orderId").toString()), + Long.valueOf(body.get("productId").toString()), + Integer.valueOf(body.get("rating").toString()), + body.get("content").toString())); + } + + @PostMapping("/merchant-applications") + public ApiResponse applyMerchant(HttpServletRequest request, @RequestBody Map body) { + User user = AuthContext.getUser(request); + return ApiResponse.ok(mallService.applyMerchant(user.getId(), body.getOrDefault("qualification", ""))); + } +} diff --git a/backend/src/main/java/com/maternalmall/controller/MerchantController.java b/backend/src/main/java/com/maternalmall/controller/MerchantController.java new file mode 100644 index 0000000..acf873e --- /dev/null +++ b/backend/src/main/java/com/maternalmall/controller/MerchantController.java @@ -0,0 +1,100 @@ +package com.maternalmall.controller; + +import com.maternalmall.common.ApiResponse; +import com.maternalmall.config.AuthContext; +import com.maternalmall.domain.*; +import com.maternalmall.service.MallService; +import jakarta.servlet.http.HttpServletRequest; +import org.springframework.web.bind.annotation.*; + +import java.util.List; +import java.util.Map; + +@RestController +@RequestMapping("/api/merchant") +public class MerchantController { + private final MallService mallService; + + public MerchantController(MallService mallService) { + this.mallService = mallService; + } + + @GetMapping("/overview") + public ApiResponse> overview(HttpServletRequest request) { + User user = AuthContext.getUser(request); + mallService.requireRole(user, UserRole.MERCHANT); + return ApiResponse.ok(mallService.merchantOverview(user.getId())); + } + + @GetMapping("/products") + public ApiResponse> products(HttpServletRequest request) { + User user = AuthContext.getUser(request); + mallService.requireRole(user, UserRole.MERCHANT); + return ApiResponse.ok(mallService.merchantProducts(user.getId())); + } + + @PostMapping("/products") + public ApiResponse saveProduct(HttpServletRequest request, @RequestBody Product product) { + User user = AuthContext.getUser(request); + mallService.requireRole(user, UserRole.MERCHANT); + return ApiResponse.ok(mallService.merchantSaveProduct(user.getId(), product)); + } + + @DeleteMapping("/products/{id}") + public ApiResponse deleteProduct(HttpServletRequest request, @PathVariable Long id) { + User user = AuthContext.getUser(request); + mallService.requireRole(user, UserRole.MERCHANT); + mallService.merchantDeleteProduct(user.getId(), id); + return ApiResponse.ok(); + } + + @GetMapping("/orders") + public ApiResponse> orders(HttpServletRequest request) { + User user = AuthContext.getUser(request); + mallService.requireRole(user, UserRole.MERCHANT); + return ApiResponse.ok(mallService.merchantOrders(user.getId())); + } + + @PutMapping("/orders/{id}/ship") + public ApiResponse ship(HttpServletRequest request, @PathVariable Long id, @RequestBody Map body) { + User user = AuthContext.getUser(request); + mallService.requireRole(user, UserRole.MERCHANT); + return ApiResponse.ok(mallService.merchantShip(user.getId(), id, body.getOrDefault("note", "已发货"))); + } + + @PutMapping("/orders/{id}/refund") + public ApiResponse refund(HttpServletRequest request, @PathVariable Long id, @RequestBody Map body) { + User user = AuthContext.getUser(request); + mallService.requireRole(user, UserRole.MERCHANT); + return ApiResponse.ok(mallService.merchantProcessRefund(user.getId(), id, Boolean.parseBoolean(body.get("agree").toString()))); + } + + @GetMapping("/reviews") + public ApiResponse>> reviews(HttpServletRequest request) { + User user = AuthContext.getUser(request); + mallService.requireRole(user, UserRole.MERCHANT); + return ApiResponse.ok(mallService.merchantReviews(user.getId())); + } + + @GetMapping("/logistics") + public ApiResponse>> logistics(HttpServletRequest request) { + User user = AuthContext.getUser(request); + mallService.requireRole(user, UserRole.MERCHANT); + return ApiResponse.ok(mallService.merchantLogistics(user.getId())); + } + + @GetMapping("/inventory") + public ApiResponse>> inventory(HttpServletRequest request) { + User user = AuthContext.getUser(request); + mallService.requireRole(user, UserRole.MERCHANT); + return ApiResponse.ok(mallService.merchantInventory(user.getId())); + } + + @DeleteMapping("/inventory/{id}") + public ApiResponse deleteInventory(HttpServletRequest request, @PathVariable Long id) { + User user = AuthContext.getUser(request); + mallService.requireRole(user, UserRole.MERCHANT); + mallService.merchantDeleteInventory(user.getId(), id); + return ApiResponse.ok(); + } +} diff --git a/backend/src/main/java/com/maternalmall/controller/PublicController.java b/backend/src/main/java/com/maternalmall/controller/PublicController.java new file mode 100644 index 0000000..882bd7a --- /dev/null +++ b/backend/src/main/java/com/maternalmall/controller/PublicController.java @@ -0,0 +1,43 @@ +package com.maternalmall.controller; + +import com.maternalmall.common.ApiResponse; +import com.maternalmall.domain.Banner; +import com.maternalmall.domain.Product; +import com.maternalmall.domain.Review; +import com.maternalmall.repository.ReviewRepository; +import com.maternalmall.service.MallService; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +@RestController +@RequestMapping("/api/public") +public class PublicController { + private final MallService mallService; + private final ReviewRepository reviewRepository; + + public PublicController(MallService mallService, ReviewRepository reviewRepository) { + this.mallService = mallService; + this.reviewRepository = reviewRepository; + } + + @GetMapping("/products") + public ApiResponse> products(@RequestParam(required = false) String keyword) { + return ApiResponse.ok(mallService.searchProducts(keyword)); + } + + @GetMapping("/products/{id}") + public ApiResponse product(@PathVariable Long id) { + return ApiResponse.ok(mallService.getProduct(id)); + } + + @GetMapping("/products/{id}/reviews") + public ApiResponse> reviews(@PathVariable Long id) { + return ApiResponse.ok(reviewRepository.findByProductId(id)); + } + + @GetMapping("/banners") + public ApiResponse> banners() { + return ApiResponse.ok(mallService.banners()); + } +} diff --git a/backend/src/main/java/com/maternalmall/domain/Banner.java b/backend/src/main/java/com/maternalmall/domain/Banner.java new file mode 100644 index 0000000..a74691e --- /dev/null +++ b/backend/src/main/java/com/maternalmall/domain/Banner.java @@ -0,0 +1,25 @@ +package com.maternalmall.domain; + +import jakarta.persistence.*; +import lombok.Data; + +@Data +@Entity +@Table(name = "banner") +public class Banner { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(nullable = false, length = 255) + private String imageUrl; + + @Column(length = 255) + private String linkUrl; + + @Column(nullable = false) + private Integer sortNo = 0; + + @Column(nullable = false) + private Boolean enabled = true; +} diff --git a/backend/src/main/java/com/maternalmall/domain/CartItem.java b/backend/src/main/java/com/maternalmall/domain/CartItem.java new file mode 100644 index 0000000..3975d5f --- /dev/null +++ b/backend/src/main/java/com/maternalmall/domain/CartItem.java @@ -0,0 +1,22 @@ +package com.maternalmall.domain; + +import jakarta.persistence.*; +import lombok.Data; + +@Data +@Entity +@Table(name = "cart_item", uniqueConstraints = @UniqueConstraint(columnNames = {"customerId", "productId"})) +public class CartItem { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(nullable = false) + private Long customerId; + + @Column(nullable = false) + private Long productId; + + @Column(nullable = false) + private Integer quantity; +} diff --git a/backend/src/main/java/com/maternalmall/domain/Favorite.java b/backend/src/main/java/com/maternalmall/domain/Favorite.java new file mode 100644 index 0000000..6b57b12 --- /dev/null +++ b/backend/src/main/java/com/maternalmall/domain/Favorite.java @@ -0,0 +1,19 @@ +package com.maternalmall.domain; + +import jakarta.persistence.*; +import lombok.Data; + +@Data +@Entity +@Table(name = "favorite", uniqueConstraints = @UniqueConstraint(columnNames = {"customerId", "productId"})) +public class Favorite { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(nullable = false) + private Long customerId; + + @Column(nullable = false) + private Long productId; +} diff --git a/backend/src/main/java/com/maternalmall/domain/InventoryRecord.java b/backend/src/main/java/com/maternalmall/domain/InventoryRecord.java new file mode 100644 index 0000000..ac407b6 --- /dev/null +++ b/backend/src/main/java/com/maternalmall/domain/InventoryRecord.java @@ -0,0 +1,31 @@ +package com.maternalmall.domain; + +import jakarta.persistence.*; +import lombok.Data; +import org.hibernate.annotations.CreationTimestamp; + +import java.time.LocalDateTime; + +@Data +@Entity +@Table(name = "inventory_record") +public class InventoryRecord { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(nullable = false) + private Long productId; + + @Column(nullable = false) + private Long merchantId; + + @Column(nullable = false) + private Integer changeQty; + + @Column(length = 255) + private String note; + + @CreationTimestamp + private LocalDateTime createdAt; +} diff --git a/backend/src/main/java/com/maternalmall/domain/LogisticsRecord.java b/backend/src/main/java/com/maternalmall/domain/LogisticsRecord.java new file mode 100644 index 0000000..90f6b3f --- /dev/null +++ b/backend/src/main/java/com/maternalmall/domain/LogisticsRecord.java @@ -0,0 +1,31 @@ +package com.maternalmall.domain; + +import jakarta.persistence.*; +import lombok.Data; +import org.hibernate.annotations.CreationTimestamp; + +import java.time.LocalDateTime; + +@Data +@Entity +@Table(name = "logistics_record") +public class LogisticsRecord { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(nullable = false) + private Long orderId; + + @Column(nullable = false) + private Long merchantId; + + @Column(length = 255, nullable = false) + private String status; + + @Column(length = 255) + private String note; + + @CreationTimestamp + private LocalDateTime createdAt; +} diff --git a/backend/src/main/java/com/maternalmall/domain/MerchantApplication.java b/backend/src/main/java/com/maternalmall/domain/MerchantApplication.java new file mode 100644 index 0000000..2744bc5 --- /dev/null +++ b/backend/src/main/java/com/maternalmall/domain/MerchantApplication.java @@ -0,0 +1,35 @@ +package com.maternalmall.domain; + +import jakarta.persistence.*; +import lombok.Data; +import org.hibernate.annotations.CreationTimestamp; +import org.hibernate.annotations.UpdateTimestamp; + +import java.time.LocalDateTime; + +@Data +@Entity +@Table(name = "merchant_application") +public class MerchantApplication { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(nullable = false) + private Long userId; + + @Column(length = 255) + private String qualification; + + @Column(length = 30, nullable = false) + private String status = "PENDING"; + + @Column(length = 255) + private String remark; + + @CreationTimestamp + private LocalDateTime createdAt; + + @UpdateTimestamp + private LocalDateTime updatedAt; +} diff --git a/backend/src/main/java/com/maternalmall/domain/OrderItem.java b/backend/src/main/java/com/maternalmall/domain/OrderItem.java new file mode 100644 index 0000000..000bd5c --- /dev/null +++ b/backend/src/main/java/com/maternalmall/domain/OrderItem.java @@ -0,0 +1,30 @@ +package com.maternalmall.domain; + +import jakarta.persistence.*; +import lombok.Data; + +import java.math.BigDecimal; + +@Data +@Entity +@Table(name = "order_item") +public class OrderItem { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(nullable = false) + private Long orderId; + + @Column(nullable = false) + private Long productId; + + @Column(nullable = false, length = 120) + private String productName; + + @Column(nullable = false) + private Integer quantity; + + @Column(nullable = false, precision = 10, scale = 2) + private BigDecimal unitPrice; +} diff --git a/backend/src/main/java/com/maternalmall/domain/OrderStatus.java b/backend/src/main/java/com/maternalmall/domain/OrderStatus.java new file mode 100644 index 0000000..660c1ca --- /dev/null +++ b/backend/src/main/java/com/maternalmall/domain/OrderStatus.java @@ -0,0 +1,11 @@ +package com.maternalmall.domain; + +public enum OrderStatus { + PENDING_PAYMENT, + PAID, + SHIPPED, + COMPLETED, + REFUND_REQUESTED, + REFUNDED, + CANCELLED +} diff --git a/backend/src/main/java/com/maternalmall/domain/Orders.java b/backend/src/main/java/com/maternalmall/domain/Orders.java new file mode 100644 index 0000000..4b95569 --- /dev/null +++ b/backend/src/main/java/com/maternalmall/domain/Orders.java @@ -0,0 +1,49 @@ +package com.maternalmall.domain; + +import jakarta.persistence.*; +import lombok.Data; +import org.hibernate.annotations.CreationTimestamp; +import org.hibernate.annotations.UpdateTimestamp; + +import java.math.BigDecimal; +import java.time.LocalDateTime; + +@Data +@Entity +@Table(name = "orders") +public class Orders { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(nullable = false, unique = true, length = 50) + private String orderNo; + + @Column(nullable = false) + private Long customerId; + + @Column(nullable = false) + private Long merchantId; + + @Column(nullable = false, precision = 10, scale = 2) + private BigDecimal totalAmount; + + @Enumerated(EnumType.STRING) + @Column(nullable = false, length = 30) + private OrderStatus status; + + @Column(length = 255) + private String address; + + @Column(length = 255) + private String logisticsInfo; + + @Column(length = 255) + private String refundReason; + + @CreationTimestamp + private LocalDateTime createdAt; + + @UpdateTimestamp + private LocalDateTime updatedAt; +} diff --git a/backend/src/main/java/com/maternalmall/domain/Product.java b/backend/src/main/java/com/maternalmall/domain/Product.java new file mode 100644 index 0000000..652e4ed --- /dev/null +++ b/backend/src/main/java/com/maternalmall/domain/Product.java @@ -0,0 +1,48 @@ +package com.maternalmall.domain; + +import jakarta.persistence.*; +import lombok.Data; +import org.hibernate.annotations.CreationTimestamp; +import org.hibernate.annotations.UpdateTimestamp; + +import java.math.BigDecimal; +import java.time.LocalDateTime; + +@Data +@Entity +@Table(name = "product") +public class Product { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(nullable = false, length = 120) + private String name; + + @Column(length = 50) + private String category; + + @Column(length = 1000) + private String description; + + @Column(nullable = false, precision = 10, scale = 2) + private BigDecimal price; + + @Column(nullable = false) + private Integer stock; + + @Column(length = 255) + private String imageUrl; + + @Column(nullable = false) + private Long merchantId; + + @Column(nullable = false) + private Boolean approved = false; + + @CreationTimestamp + private LocalDateTime createdAt; + + @UpdateTimestamp + private LocalDateTime updatedAt; +} diff --git a/backend/src/main/java/com/maternalmall/domain/Review.java b/backend/src/main/java/com/maternalmall/domain/Review.java new file mode 100644 index 0000000..9b19ff7 --- /dev/null +++ b/backend/src/main/java/com/maternalmall/domain/Review.java @@ -0,0 +1,34 @@ +package com.maternalmall.domain; + +import jakarta.persistence.*; +import lombok.Data; +import org.hibernate.annotations.CreationTimestamp; + +import java.time.LocalDateTime; + +@Data +@Entity +@Table(name = "review") +public class Review { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(nullable = false) + private Long orderId; + + @Column(nullable = false) + private Long productId; + + @Column(nullable = false) + private Long customerId; + + @Column(nullable = false) + private Integer rating; + + @Column(length = 1000) + private String content; + + @CreationTimestamp + private LocalDateTime createdAt; +} diff --git a/backend/src/main/java/com/maternalmall/domain/User.java b/backend/src/main/java/com/maternalmall/domain/User.java new file mode 100644 index 0000000..5413967 --- /dev/null +++ b/backend/src/main/java/com/maternalmall/domain/User.java @@ -0,0 +1,48 @@ +package com.maternalmall.domain; + +import jakarta.persistence.*; +import lombok.Data; +import org.hibernate.annotations.CreationTimestamp; +import org.hibernate.annotations.UpdateTimestamp; + +import java.time.LocalDateTime; + +@Data +@Entity +@Table(name = "users") +public class User { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(unique = true, nullable = false, length = 50) + private String username; + + @Column(nullable = false, length = 100) + private String password; + + @Enumerated(EnumType.STRING) + @Column(nullable = false, length = 20) + private UserRole role; + + @Column(length = 50) + private String nickname; + + @Column(length = 20) + private String phone; + + @Column(length = 255) + private String address; + + @Column(length = 100) + private String token; + + @Column(nullable = false) + private Boolean enabled = true; + + @CreationTimestamp + private LocalDateTime createdAt; + + @UpdateTimestamp + private LocalDateTime updatedAt; +} diff --git a/backend/src/main/java/com/maternalmall/domain/UserRole.java b/backend/src/main/java/com/maternalmall/domain/UserRole.java new file mode 100644 index 0000000..e9efabd --- /dev/null +++ b/backend/src/main/java/com/maternalmall/domain/UserRole.java @@ -0,0 +1,7 @@ +package com.maternalmall.domain; + +public enum UserRole { + CUSTOMER, + MERCHANT, + ADMIN +} diff --git a/backend/src/main/java/com/maternalmall/repository/BannerRepository.java b/backend/src/main/java/com/maternalmall/repository/BannerRepository.java new file mode 100644 index 0000000..6524a1f --- /dev/null +++ b/backend/src/main/java/com/maternalmall/repository/BannerRepository.java @@ -0,0 +1,10 @@ +package com.maternalmall.repository; + +import com.maternalmall.domain.Banner; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.List; + +public interface BannerRepository extends JpaRepository { + List findByEnabledTrueOrderBySortNoAsc(); +} diff --git a/backend/src/main/java/com/maternalmall/repository/CartItemRepository.java b/backend/src/main/java/com/maternalmall/repository/CartItemRepository.java new file mode 100644 index 0000000..8aa6c5a --- /dev/null +++ b/backend/src/main/java/com/maternalmall/repository/CartItemRepository.java @@ -0,0 +1,22 @@ +package com.maternalmall.repository; + +import com.maternalmall.domain.CartItem; +import org.springframework.data.jpa.repository.Modifying; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; +import java.util.Optional; + +public interface CartItemRepository extends JpaRepository { + List findByCustomerId(Long customerId); + Optional findByCustomerIdAndProductId(Long customerId, Long productId); + + @Modifying + @Transactional + void deleteByCustomerIdAndProductId(Long customerId, Long productId); + + @Modifying + @Transactional + void deleteByCustomerId(Long customerId); +} diff --git a/backend/src/main/java/com/maternalmall/repository/FavoriteRepository.java b/backend/src/main/java/com/maternalmall/repository/FavoriteRepository.java new file mode 100644 index 0000000..d51e5c1 --- /dev/null +++ b/backend/src/main/java/com/maternalmall/repository/FavoriteRepository.java @@ -0,0 +1,18 @@ +package com.maternalmall.repository; + +import com.maternalmall.domain.Favorite; +import org.springframework.data.jpa.repository.Modifying; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; +import java.util.Optional; + +public interface FavoriteRepository extends JpaRepository { + List findByCustomerId(Long customerId); + Optional findByCustomerIdAndProductId(Long customerId, Long productId); + + @Modifying + @Transactional + void deleteByCustomerIdAndProductId(Long customerId, Long productId); +} diff --git a/backend/src/main/java/com/maternalmall/repository/InventoryRecordRepository.java b/backend/src/main/java/com/maternalmall/repository/InventoryRecordRepository.java new file mode 100644 index 0000000..7f2a9ac --- /dev/null +++ b/backend/src/main/java/com/maternalmall/repository/InventoryRecordRepository.java @@ -0,0 +1,11 @@ +package com.maternalmall.repository; + +import com.maternalmall.domain.InventoryRecord; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.List; + +public interface InventoryRecordRepository extends JpaRepository { + List findByMerchantId(Long merchantId); + List findByProductId(Long productId); +} diff --git a/backend/src/main/java/com/maternalmall/repository/LogisticsRecordRepository.java b/backend/src/main/java/com/maternalmall/repository/LogisticsRecordRepository.java new file mode 100644 index 0000000..2a01074 --- /dev/null +++ b/backend/src/main/java/com/maternalmall/repository/LogisticsRecordRepository.java @@ -0,0 +1,11 @@ +package com.maternalmall.repository; + +import com.maternalmall.domain.LogisticsRecord; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.List; + +public interface LogisticsRecordRepository extends JpaRepository { + List findByOrderId(Long orderId); + List findByMerchantId(Long merchantId); +} diff --git a/backend/src/main/java/com/maternalmall/repository/MerchantApplicationRepository.java b/backend/src/main/java/com/maternalmall/repository/MerchantApplicationRepository.java new file mode 100644 index 0000000..52ead6a --- /dev/null +++ b/backend/src/main/java/com/maternalmall/repository/MerchantApplicationRepository.java @@ -0,0 +1,11 @@ +package com.maternalmall.repository; + +import com.maternalmall.domain.MerchantApplication; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.List; + +public interface MerchantApplicationRepository extends JpaRepository { + List findByStatus(String status); + boolean existsByUserIdAndStatus(Long userId, String status); +} diff --git a/backend/src/main/java/com/maternalmall/repository/OrderItemRepository.java b/backend/src/main/java/com/maternalmall/repository/OrderItemRepository.java new file mode 100644 index 0000000..4ae771b --- /dev/null +++ b/backend/src/main/java/com/maternalmall/repository/OrderItemRepository.java @@ -0,0 +1,10 @@ +package com.maternalmall.repository; + +import com.maternalmall.domain.OrderItem; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.List; + +public interface OrderItemRepository extends JpaRepository { + List findByOrderId(Long orderId); +} diff --git a/backend/src/main/java/com/maternalmall/repository/OrdersRepository.java b/backend/src/main/java/com/maternalmall/repository/OrdersRepository.java new file mode 100644 index 0000000..62a7a78 --- /dev/null +++ b/backend/src/main/java/com/maternalmall/repository/OrdersRepository.java @@ -0,0 +1,13 @@ +package com.maternalmall.repository; + +import com.maternalmall.domain.OrderStatus; +import com.maternalmall.domain.Orders; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.List; + +public interface OrdersRepository extends JpaRepository { + List findByCustomerId(Long customerId); + List findByMerchantId(Long merchantId); + List findByStatus(OrderStatus status); +} diff --git a/backend/src/main/java/com/maternalmall/repository/ProductRepository.java b/backend/src/main/java/com/maternalmall/repository/ProductRepository.java new file mode 100644 index 0000000..e9524a5 --- /dev/null +++ b/backend/src/main/java/com/maternalmall/repository/ProductRepository.java @@ -0,0 +1,11 @@ +package com.maternalmall.repository; + +import com.maternalmall.domain.Product; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.List; + +public interface ProductRepository extends JpaRepository { + List findByApprovedTrueAndNameContainingIgnoreCase(String name); + List findByMerchantId(Long merchantId); +} diff --git a/backend/src/main/java/com/maternalmall/repository/ReviewRepository.java b/backend/src/main/java/com/maternalmall/repository/ReviewRepository.java new file mode 100644 index 0000000..5eb8550 --- /dev/null +++ b/backend/src/main/java/com/maternalmall/repository/ReviewRepository.java @@ -0,0 +1,12 @@ +package com.maternalmall.repository; + +import com.maternalmall.domain.Review; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.List; + +public interface ReviewRepository extends JpaRepository { + List findByProductId(Long productId); + List findByCustomerId(Long customerId); + boolean existsByOrderIdAndProductIdAndCustomerId(Long orderId, Long productId, Long customerId); +} diff --git a/backend/src/main/java/com/maternalmall/repository/UserRepository.java b/backend/src/main/java/com/maternalmall/repository/UserRepository.java new file mode 100644 index 0000000..e5d9dff --- /dev/null +++ b/backend/src/main/java/com/maternalmall/repository/UserRepository.java @@ -0,0 +1,11 @@ +package com.maternalmall.repository; + +import com.maternalmall.domain.User; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.Optional; + +public interface UserRepository extends JpaRepository { + Optional findByUsername(String username); + Optional findByToken(String token); +} diff --git a/backend/src/main/java/com/maternalmall/service/AuthService.java b/backend/src/main/java/com/maternalmall/service/AuthService.java new file mode 100644 index 0000000..faae71a --- /dev/null +++ b/backend/src/main/java/com/maternalmall/service/AuthService.java @@ -0,0 +1,65 @@ +package com.maternalmall.service; + +import com.maternalmall.common.BizException; +import com.maternalmall.domain.User; +import com.maternalmall.domain.UserRole; +import com.maternalmall.repository.UserRepository; +import org.springframework.stereotype.Service; + +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + +@Service +public class AuthService { + private final UserRepository userRepository; + + public AuthService(UserRepository userRepository) { + this.userRepository = userRepository; + } + + public Map register(String username, String password, String role) { + username = username == null ? "" : username.trim(); + password = password == null ? "" : password.trim(); + if (username.isEmpty()) { + throw new BizException("用户名不能为空"); + } + if (password.isEmpty()) { + throw new BizException("密码不能为空"); + } + userRepository.findByUsername(username).ifPresent(u -> { + throw new BizException("账号已存在"); + }); + if (role != null && !role.isBlank() && !"CUSTOMER".equalsIgnoreCase(role)) { + throw new BizException("仅支持注册顾客账号,商家需提交入驻申请"); + } + User user = new User(); + user.setUsername(username); + user.setPassword(password); + user.setRole(UserRole.CUSTOMER); + user.setNickname(username); + userRepository.save(user); + return Map.of("userId", user.getId()); + } + + public Map login(String username, String password) { + User user = userRepository.findByUsername(username).orElseThrow(() -> new BizException("用户不存在")); + if (!user.getPassword().equals(password)) { + throw new BizException("密码错误"); + } + if (!Boolean.TRUE.equals(user.getEnabled())) { + throw new BizException("账号已被禁用,请联系管理员"); + } + String token = UUID.randomUUID().toString().replace("-", ""); + user.setToken(token); + userRepository.save(user); + + Map data = new HashMap<>(); + data.put("token", token); + data.put("id", user.getId()); + data.put("username", user.getUsername()); + data.put("role", user.getRole()); + data.put("nickname", user.getNickname()); + return data; + } +} diff --git a/backend/src/main/java/com/maternalmall/service/MallService.java b/backend/src/main/java/com/maternalmall/service/MallService.java new file mode 100644 index 0000000..1a7a97f --- /dev/null +++ b/backend/src/main/java/com/maternalmall/service/MallService.java @@ -0,0 +1,887 @@ +package com.maternalmall.service; + +import com.maternalmall.common.BizException; +import com.maternalmall.domain.*; +import com.maternalmall.repository.*; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.math.BigDecimal; +import java.time.LocalDateTime; +import java.util.*; + +@Service +public class MallService { + private final ProductRepository productRepository; + private final CartItemRepository cartItemRepository; + private final FavoriteRepository favoriteRepository; + private final OrdersRepository ordersRepository; + private final OrderItemRepository orderItemRepository; + private final ReviewRepository reviewRepository; + private final LogisticsRecordRepository logisticsRecordRepository; + private final InventoryRecordRepository inventoryRecordRepository; + private final MerchantApplicationRepository merchantApplicationRepository; + private final BannerRepository bannerRepository; + private final UserRepository userRepository; + + public MallService(ProductRepository productRepository, + CartItemRepository cartItemRepository, + FavoriteRepository favoriteRepository, + OrdersRepository ordersRepository, + OrderItemRepository orderItemRepository, + ReviewRepository reviewRepository, + LogisticsRecordRepository logisticsRecordRepository, + InventoryRecordRepository inventoryRecordRepository, + MerchantApplicationRepository merchantApplicationRepository, + BannerRepository bannerRepository, + UserRepository userRepository) { + this.productRepository = productRepository; + this.cartItemRepository = cartItemRepository; + this.favoriteRepository = favoriteRepository; + this.ordersRepository = ordersRepository; + this.orderItemRepository = orderItemRepository; + this.reviewRepository = reviewRepository; + this.logisticsRecordRepository = logisticsRecordRepository; + this.inventoryRecordRepository = inventoryRecordRepository; + this.merchantApplicationRepository = merchantApplicationRepository; + this.bannerRepository = bannerRepository; + this.userRepository = userRepository; + } + + public void requireRole(User user, UserRole role) { + if (user.getRole() != role) { + throw new BizException("无权限操作"); + } + } + + public List searchProducts(String keyword) { + return productRepository.findByApprovedTrueAndNameContainingIgnoreCase(keyword == null ? "" : keyword); + } + + public Product getProduct(Long id) { + return productRepository.findById(id).orElseThrow(() -> new BizException("商品不存在")); + } + + public List banners() { + return bannerRepository.findByEnabledTrueOrderBySortNoAsc(); + } + + public List cartList(Long customerId) { + return cartItemRepository.findByCustomerId(customerId); + } + + public List> cartViews(Long customerId) { + List items = cartItemRepository.findByCustomerId(customerId); + List> views = new ArrayList<>(); + for (CartItem item : items) { + Product p = getProduct(item.getProductId()); + Map row = new HashMap<>(); + row.put("productId", p.getId()); + row.put("productName", p.getName()); + row.put("category", p.getCategory()); + row.put("imageUrl", p.getImageUrl()); + row.put("unitPrice", p.getPrice()); + row.put("quantity", item.getQuantity()); + row.put("subtotal", p.getPrice().multiply(BigDecimal.valueOf(item.getQuantity()))); + row.put("stock", p.getStock()); + views.add(row); + } + return views; + } + + public CartItem addCart(Long customerId, Long productId, Integer quantity) { + Product product = getProduct(productId); + if (!product.getApproved()) { + throw new BizException("商品未上架"); + } + CartItem item = cartItemRepository.findByCustomerIdAndProductId(customerId, productId).orElseGet(CartItem::new); + item.setCustomerId(customerId); + item.setProductId(productId); + item.setQuantity((item.getQuantity() == null ? 0 : item.getQuantity()) + quantity); + return cartItemRepository.save(item); + } + + public void removeCart(Long customerId, Long productId) { + cartItemRepository.deleteByCustomerIdAndProductId(customerId, productId); + } + + public List favoriteList(Long customerId) { + return favoriteRepository.findByCustomerId(customerId); + } + + public List> favoriteViews(Long customerId) { + List favorites = favoriteRepository.findByCustomerId(customerId); + List> views = new ArrayList<>(); + for (Favorite favorite : favorites) { + Product p = getProduct(favorite.getProductId()); + Map row = new HashMap<>(); + row.put("productId", p.getId()); + row.put("productName", p.getName()); + row.put("category", p.getCategory()); + row.put("imageUrl", p.getImageUrl()); + row.put("unitPrice", p.getPrice()); + row.put("stock", p.getStock()); + views.add(row); + } + return views; + } + + public Favorite addFavorite(Long customerId, Long productId) { + favoriteRepository.findByCustomerIdAndProductId(customerId, productId).ifPresent(f -> { + throw new BizException("已收藏"); + }); + Favorite favorite = new Favorite(); + favorite.setCustomerId(customerId); + favorite.setProductId(productId); + return favoriteRepository.save(favorite); + } + + public void removeFavorite(Long customerId, Long productId) { + favoriteRepository.deleteByCustomerIdAndProductId(customerId, productId); + } + + @Transactional + public Orders checkout(Long customerId, String address) { + List cartItems = cartItemRepository.findByCustomerId(customerId); + if (cartItems.isEmpty()) { + throw new BizException("购物车为空"); + } + Long merchantId = null; + BigDecimal total = BigDecimal.ZERO; + Orders order = new Orders(); + order.setOrderNo("MM" + System.currentTimeMillis()); + order.setCustomerId(customerId); + order.setStatus(OrderStatus.PAID); + order.setAddress(address); + + for (CartItem cartItem : cartItems) { + Product product = getProduct(cartItem.getProductId()); + if (product.getStock() < cartItem.getQuantity()) { + throw new BizException("库存不足: " + product.getName()); + } + if (merchantId == null) { + merchantId = product.getMerchantId(); + } else if (!merchantId.equals(product.getMerchantId())) { + throw new BizException("当前基础版本仅支持单商家结算"); + } + product.setStock(product.getStock() - cartItem.getQuantity()); + productRepository.save(product); + + InventoryRecord record = new InventoryRecord(); + record.setMerchantId(product.getMerchantId()); + record.setProductId(product.getId()); + record.setChangeQty(-cartItem.getQuantity()); + record.setNote("订单扣减库存"); + inventoryRecordRepository.save(record); + + BigDecimal line = product.getPrice().multiply(BigDecimal.valueOf(cartItem.getQuantity())); + total = total.add(line); + } + order.setMerchantId(merchantId); + order.setTotalAmount(total); + ordersRepository.save(order); + + for (CartItem cartItem : cartItems) { + Product product = getProduct(cartItem.getProductId()); + OrderItem item = new OrderItem(); + item.setOrderId(order.getId()); + item.setProductId(product.getId()); + item.setProductName(product.getName()); + item.setQuantity(cartItem.getQuantity()); + item.setUnitPrice(product.getPrice()); + orderItemRepository.save(item); + } + cartItemRepository.deleteByCustomerId(customerId); + return order; + } + + @Transactional + public Orders buyNow(Long customerId, Long productId, Integer quantity, String address) { + if (quantity == null || quantity <= 0) { + throw new BizException("购买数量必须大于0"); + } + Product product = getProduct(productId); + if (!Boolean.TRUE.equals(product.getApproved())) { + throw new BizException("商品未上架"); + } + if (product.getStock() < quantity) { + throw new BizException("库存不足: " + product.getName()); + } + + product.setStock(product.getStock() - quantity); + productRepository.save(product); + + InventoryRecord record = new InventoryRecord(); + record.setMerchantId(product.getMerchantId()); + record.setProductId(product.getId()); + record.setChangeQty(-quantity); + record.setNote("立即购买扣减库存"); + inventoryRecordRepository.save(record); + + Orders order = new Orders(); + order.setOrderNo("MM" + System.currentTimeMillis()); + order.setCustomerId(customerId); + order.setMerchantId(product.getMerchantId()); + order.setStatus(OrderStatus.PAID); + order.setAddress(address); + order.setTotalAmount(product.getPrice().multiply(BigDecimal.valueOf(quantity))); + ordersRepository.save(order); + + OrderItem item = new OrderItem(); + item.setOrderId(order.getId()); + item.setProductId(product.getId()); + item.setProductName(product.getName()); + item.setQuantity(quantity); + item.setUnitPrice(product.getPrice()); + orderItemRepository.save(item); + + return order; + } + + public List customerOrders(Long customerId) { + return ordersRepository.findByCustomerId(customerId); + } + + public List customerOrderItems(Long customerId, Long orderId) { + Orders order = ordersRepository.findById(orderId).orElseThrow(() -> new BizException("订单不存在")); + if (!order.getCustomerId().equals(customerId)) { + throw new BizException("无权限操作"); + } + return orderItemRepository.findByOrderId(orderId); + } + + public Orders customerUpdateAddress(Long customerId, Long orderId, String address) { + Orders order = ordersRepository.findById(orderId).orElseThrow(() -> new BizException("订单不存在")); + if (!order.getCustomerId().equals(customerId)) { + throw new BizException("无权限操作"); + } + if (order.getStatus() == OrderStatus.SHIPPED || order.getStatus() == OrderStatus.COMPLETED) { + throw new BizException("该状态不可修改地址"); + } + order.setAddress(address); + return ordersRepository.save(order); + } + + public Orders customerRefund(Long customerId, Long orderId, String reason) { + Orders order = ordersRepository.findById(orderId).orElseThrow(() -> new BizException("订单不存在")); + if (!order.getCustomerId().equals(customerId)) { + throw new BizException("无权限操作"); + } + order.setStatus(OrderStatus.REFUND_REQUESTED); + order.setRefundReason(reason); + return ordersRepository.save(order); + } + + public void customerDeleteOrder(Long customerId, Long orderId) { + Orders order = ordersRepository.findById(orderId).orElseThrow(() -> new BizException("订单不存在")); + if (!order.getCustomerId().equals(customerId)) { + throw new BizException("无权限操作"); + } + if (!(order.getStatus() == OrderStatus.COMPLETED || order.getStatus() == OrderStatus.REFUNDED || order.getStatus() == OrderStatus.CANCELLED)) { + throw new BizException("当前状态不可删除"); + } + ordersRepository.delete(order); + } + + public List logisticsOfOrder(Long customerId, Long orderId) { + Orders order = ordersRepository.findById(orderId).orElseThrow(() -> new BizException("订单不存在")); + if (!order.getCustomerId().equals(customerId)) { + throw new BizException("无权限操作"); + } + return logisticsRecordRepository.findByOrderId(orderId); + } + + public Review addReview(Long customerId, Long orderId, Long productId, Integer rating, String content) { + Orders order = ordersRepository.findById(orderId).orElseThrow(() -> new BizException("订单不存在")); + if (!order.getCustomerId().equals(customerId)) { + throw new BizException("无权限操作"); + } + if (rating == null || rating < 1 || rating > 5) { + throw new BizException("评分范围为1-5分"); + } + if (!(order.getStatus() == OrderStatus.PAID + || order.getStatus() == OrderStatus.SHIPPED + || order.getStatus() == OrderStatus.COMPLETED + || order.getStatus() == OrderStatus.REFUND_REQUESTED + || order.getStatus() == OrderStatus.REFUNDED)) { + throw new BizException("仅已购买商品可评价"); + } + boolean ordered = orderItemRepository.findByOrderId(orderId) + .stream() + .anyMatch(i -> i.getProductId().equals(productId)); + if (!ordered) { + throw new BizException("该商品不在订单内"); + } + if (reviewRepository.existsByOrderIdAndProductIdAndCustomerId(orderId, productId, customerId)) { + throw new BizException("该商品已评价"); + } + Review review = new Review(); + review.setOrderId(orderId); + review.setProductId(productId); + review.setCustomerId(customerId); + review.setRating(rating); + review.setContent(content); + return reviewRepository.save(review); + } + + public Map merchantOverview(Long merchantId) { + List orders = ordersRepository.findByMerchantId(merchantId); + BigDecimal sales = orders.stream() + .filter(o -> o.getStatus() == OrderStatus.PAID || o.getStatus() == OrderStatus.SHIPPED || o.getStatus() == OrderStatus.COMPLETED) + .map(Orders::getTotalAmount) + .reduce(BigDecimal.ZERO, BigDecimal::add); + + Map hotMap = new HashMap<>(); + Map categoryCount = new HashMap<>(); + for (Orders order : orders) { + for (OrderItem item : orderItemRepository.findByOrderId(order.getId())) { + hotMap.put(item.getProductId(), hotMap.getOrDefault(item.getProductId(), 0) + item.getQuantity()); + } + } + for (Product p : productRepository.findByMerchantId(merchantId)) { + String category = p.getCategory() == null ? "未分类" : p.getCategory(); + categoryCount.put(category, categoryCount.getOrDefault(category, 0L) + 1); + } + return Map.of( + "orderCount", orders.size(), + "salesAmount", sales, + "hotProducts", hotMap, + "categoryRatio", categoryCount, + "notifications", List.of("请及时处理退款申请", "注意库存预警") + ); + } + + public Product merchantSaveProduct(Long merchantId, Product body) { + Product product = body; + if (product.getId() != null) { + Product old = productRepository.findById(product.getId()).orElseThrow(() -> new BizException("商品不存在")); + if (!old.getMerchantId().equals(merchantId)) { + throw new BizException("无权限操作"); + } + } + product.setMerchantId(merchantId); + if (product.getApproved() == null) { + product.setApproved(false); + } + Product saved = productRepository.save(product); + InventoryRecord record = new InventoryRecord(); + record.setMerchantId(merchantId); + record.setProductId(saved.getId()); + record.setChangeQty(saved.getStock()); + record.setNote("商品上架或更新库存"); + inventoryRecordRepository.save(record); + return saved; + } + + public List merchantProducts(Long merchantId) { + return productRepository.findByMerchantId(merchantId); + } + + public void merchantDeleteProduct(Long merchantId, Long productId) { + Product product = getProduct(productId); + if (!product.getMerchantId().equals(merchantId)) { + throw new BizException("无权限操作"); + } + productRepository.delete(product); + } + + public List merchantOrders(Long merchantId) { + return ordersRepository.findByMerchantId(merchantId); + } + + public Orders merchantShip(Long merchantId, Long orderId, String note) { + Orders order = ordersRepository.findById(orderId).orElseThrow(() -> new BizException("订单不存在")); + if (!order.getMerchantId().equals(merchantId)) { + throw new BizException("无权限操作"); + } + order.setStatus(OrderStatus.SHIPPED); + order.setLogisticsInfo(note); + ordersRepository.save(order); + + LogisticsRecord record = new LogisticsRecord(); + record.setOrderId(orderId); + record.setMerchantId(merchantId); + record.setStatus("SHIPPED"); + record.setNote(note); + logisticsRecordRepository.save(record); + return order; + } + + public Orders merchantProcessRefund(Long merchantId, Long orderId, boolean agree) { + Orders order = ordersRepository.findById(orderId).orElseThrow(() -> new BizException("订单不存在")); + if (!order.getMerchantId().equals(merchantId)) { + throw new BizException("无权限操作"); + } + if (order.getStatus() != OrderStatus.REFUND_REQUESTED) { + throw new BizException("仅退款申请中的订单可处理"); + } + order.setStatus(agree ? OrderStatus.REFUNDED : OrderStatus.PAID); + return ordersRepository.save(order); + } + + public List> merchantReviews(Long merchantId) { + List products = productRepository.findByMerchantId(merchantId); + Set productIds = new HashSet<>(); + for (Product product : products) { + productIds.add(product.getId()); + } + List reviews = reviewRepository.findAll(); + Map orderMap = new HashMap<>(); + for (Orders order : ordersRepository.findAll()) { + orderMap.put(order.getId(), order); + } + List> views = new ArrayList<>(); + for (Review r : reviews) { + if (!productIds.contains(r.getProductId())) continue; + Orders order = orderMap.get(r.getOrderId()); + Map row = new HashMap<>(); + row.put("id", r.getId()); + row.put("orderId", r.getOrderId()); + row.put("orderNo", order == null ? "" : order.getOrderNo()); + row.put("productId", r.getProductId()); + row.put("productName", products.stream().filter(p -> p.getId().equals(r.getProductId())).findFirst().map(Product::getName).orElse("")); + row.put("rating", r.getRating()); + row.put("content", r.getContent()); + row.put("createdAt", r.getCreatedAt()); + views.add(row); + } + return views; + } + + public List> merchantLogistics(Long merchantId) { + List records = logisticsRecordRepository.findByMerchantId(merchantId); + Map orderMap = new HashMap<>(); + for (Orders order : ordersRepository.findAll()) { + orderMap.put(order.getId(), order); + } + List> views = new ArrayList<>(); + for (LogisticsRecord rec : records) { + Orders order = orderMap.get(rec.getOrderId()); + Map row = new HashMap<>(); + row.put("id", rec.getId()); + row.put("orderId", rec.getOrderId()); + row.put("orderNo", order == null ? "" : order.getOrderNo()); + row.put("status", rec.getStatus()); + row.put("note", rec.getNote()); + row.put("createdAt", rec.getCreatedAt()); + views.add(row); + } + return views; + } + + public List> merchantInventory(Long merchantId) { + List records = inventoryRecordRepository.findByMerchantId(merchantId); + Map productMap = new HashMap<>(); + for (Product product : productRepository.findByMerchantId(merchantId)) { + productMap.put(product.getId(), product); + } + List> views = new ArrayList<>(); + for (InventoryRecord rec : records) { + Product product = productMap.get(rec.getProductId()); + Map row = new HashMap<>(); + row.put("id", rec.getId()); + row.put("productId", rec.getProductId()); + row.put("productName", product == null ? "" : product.getName()); + row.put("changeQty", rec.getChangeQty()); + row.put("note", rec.getNote()); + row.put("createdAt", rec.getCreatedAt()); + views.add(row); + } + return views; + } + + public void merchantDeleteInventory(Long merchantId, Long id) { + InventoryRecord rec = inventoryRecordRepository.findById(id).orElseThrow(() -> new BizException("记录不存在")); + if (!rec.getMerchantId().equals(merchantId)) { + throw new BizException("无权限操作"); + } + inventoryRecordRepository.delete(rec); + } + + public MerchantApplication applyMerchant(Long userId, String qualification) { + User user = userRepository.findById(userId).orElseThrow(() -> new BizException("用户不存在")); + if (user.getRole() != UserRole.CUSTOMER) { + throw new BizException("仅顾客账号可提交入驻申请"); + } + if (merchantApplicationRepository.existsByUserIdAndStatus(userId, "PENDING")) { + throw new BizException("已有待审核申请,请勿重复提交"); + } + MerchantApplication m = new MerchantApplication(); + m.setUserId(userId); + m.setQualification(qualification); + m.setStatus("PENDING"); + return merchantApplicationRepository.save(m); + } + + public Map adminOverview() { + List orders = ordersRepository.findAll(); + BigDecimal sales = orders.stream().map(Orders::getTotalAmount).reduce(BigDecimal.ZERO, BigDecimal::add); + Map categoryCount = new HashMap<>(); + Map hotMap = new HashMap<>(); + for (Product p : productRepository.findAll()) { + String category = p.getCategory() == null ? "未分类" : p.getCategory(); + categoryCount.put(category, categoryCount.getOrDefault(category, 0L) + 1); + } + for (Orders order : orders) { + for (OrderItem item : orderItemRepository.findByOrderId(order.getId())) { + hotMap.put(item.getProductId(), hotMap.getOrDefault(item.getProductId(), 0) + item.getQuantity()); + } + } + return Map.of( + "orderCount", orders.size(), + "salesAmount", sales, + "categoryRatio", categoryCount, + "hotProducts", hotMap + ); + } + + public List adminOrders() { + return ordersRepository.findAll(); + } + + public Orders adminUpdateOrder(Long orderId, String status, String logisticsInfo) { + Orders order = ordersRepository.findById(orderId).orElseThrow(() -> new BizException("订单不存在")); + try { + order.setStatus(OrderStatus.valueOf(status)); + } catch (IllegalArgumentException e) { + throw new BizException("订单状态不合法"); + } + order.setLogisticsInfo(logisticsInfo); + return ordersRepository.save(order); + } + + public List> adminRiskOrders() { + List orders = ordersRepository.findAll(); + List> risks = new ArrayList<>(); + LocalDateTime now = LocalDateTime.now(); + for (Orders order : orders) { + if (order.getTotalAmount().compareTo(new BigDecimal("5000")) >= 0) { + risks.add(buildRisk(order, "HIGH_AMOUNT", "订单金额较高,请人工核查")); + } + if (order.getStatus() == OrderStatus.PAID && order.getCreatedAt() != null && order.getCreatedAt().isBefore(now.minusDays(2))) { + risks.add(buildRisk(order, "SHIP_DELAY", "已支付超过48小时未发货")); + } + if (order.getStatus() == OrderStatus.SHIPPED && logisticsRecordRepository.findByOrderId(order.getId()).isEmpty()) { + risks.add(buildRisk(order, "NO_LOGISTICS", "订单标记已发货但无物流轨迹")); + } + if (order.getStatus() == OrderStatus.REFUND_REQUESTED) { + risks.add(buildRisk(order, "REFUND_PENDING", "退款申请待审核")); + } + } + return risks; + } + + private Map buildRisk(Orders order, String type, String reason) { + Map row = new HashMap<>(); + row.put("orderId", order.getId()); + row.put("orderNo", order.getOrderNo()); + row.put("status", order.getStatus()); + row.put("totalAmount", order.getTotalAmount()); + row.put("customerId", order.getCustomerId()); + row.put("merchantId", order.getMerchantId()); + row.put("riskType", type); + row.put("riskReason", reason); + return row; + } + + public Orders adminAuditRefund(Long orderId, boolean approve, String remark) { + Orders order = ordersRepository.findById(orderId).orElseThrow(() -> new BizException("订单不存在")); + if (order.getStatus() != OrderStatus.REFUND_REQUESTED) { + throw new BizException("当前订单不在退款审核状态"); + } + order.setStatus(approve ? OrderStatus.REFUNDED : OrderStatus.PAID); + if (remark != null && !remark.trim().isEmpty()) { + String old = order.getRefundReason() == null ? "" : order.getRefundReason(); + String merged = old.isBlank() ? remark.trim() : old + ";管理员备注:" + remark.trim(); + order.setRefundReason(merged); + } + Orders saved = ordersRepository.save(order); + + LogisticsRecord record = new LogisticsRecord(); + record.setOrderId(orderId); + record.setMerchantId(order.getMerchantId()); + record.setStatus(approve ? "REFUND_APPROVED" : "REFUND_REJECTED"); + record.setNote((approve ? "管理员通过退款审核" : "管理员驳回退款申请") + (remark == null || remark.trim().isEmpty() ? "" : (":" + remark.trim()))); + logisticsRecordRepository.save(record); + return saved; + } + + public Orders adminAuditShipment(Long orderId, boolean approved, String remark) { + Orders order = ordersRepository.findById(orderId).orElseThrow(() -> new BizException("订单不存在")); + if (order.getStatus() != OrderStatus.SHIPPED && order.getStatus() != OrderStatus.PAID) { + throw new BizException("仅已支付或已发货订单可审核发货"); + } + if (approved && order.getStatus() == OrderStatus.PAID) { + order.setStatus(OrderStatus.SHIPPED); + } + if (!approved && order.getStatus() == OrderStatus.SHIPPED) { + order.setStatus(OrderStatus.PAID); + } + if (remark != null && !remark.trim().isEmpty()) { + order.setLogisticsInfo(remark.trim()); + } + Orders saved = ordersRepository.save(order); + + LogisticsRecord record = new LogisticsRecord(); + record.setOrderId(orderId); + record.setMerchantId(order.getMerchantId()); + record.setStatus(approved ? "SHIP_AUDIT_PASS" : "SHIP_AUDIT_REJECT"); + record.setNote((approved ? "管理员发货审核通过" : "管理员发货审核驳回") + (remark == null || remark.trim().isEmpty() ? "" : (":" + remark.trim()))); + logisticsRecordRepository.save(record); + return saved; + } + + public List> adminMerchantApplications() { + List apps = merchantApplicationRepository.findAll(); + List> views = new ArrayList<>(); + Map userMap = new HashMap<>(); + for (User user : userRepository.findAll()) { + userMap.put(user.getId(), user); + } + for (MerchantApplication app : apps) { + User applicant = userMap.get(app.getUserId()); + Map row = new HashMap<>(); + row.put("id", app.getId()); + row.put("userId", app.getUserId()); + row.put("applicantUsername", applicant == null ? "" : applicant.getUsername()); + row.put("applicantNickname", applicant == null ? "" : applicant.getNickname()); + row.put("qualification", app.getQualification()); + row.put("status", app.getStatus()); + row.put("remark", app.getRemark()); + views.add(row); + } + return views; + } + + public MerchantApplication adminAuditApplication(Long id, String status, String remark) { + MerchantApplication app = merchantApplicationRepository.findById(id).orElseThrow(() -> new BizException("申请不存在")); + app.setStatus(status); + app.setRemark(remark); + merchantApplicationRepository.save(app); + + if ("APPROVED".equals(status)) { + User user = userRepository.findById(app.getUserId()).orElseThrow(() -> new BizException("用户不存在")); + user.setRole(UserRole.MERCHANT); + userRepository.save(user); + } + return app; + } + + public List adminUsers() { + return userRepository.findAll(); + } + + public User adminSaveUser(User user) { + String username = user.getUsername() == null ? "" : user.getUsername().trim(); + String password = user.getPassword() == null ? "" : user.getPassword().trim(); + if (username.isEmpty()) { + throw new BizException("用户名不能为空"); + } + if (password.isEmpty()) { + throw new BizException("密码不能为空"); + } + + if (user.getId() == null) { + userRepository.findByUsername(username).ifPresent(u -> { + throw new BizException("账号已存在"); + }); + } else { + User existing = userRepository.findById(user.getId()).orElseThrow(() -> new BizException("用户不存在")); + userRepository.findByUsername(username).ifPresent(u -> { + if (!u.getId().equals(existing.getId())) { + throw new BizException("账号已存在"); + } + }); + } + + user.setUsername(username); + user.setPassword(password); + if (user.getRole() == null) { + user.setRole(UserRole.CUSTOMER); + } + if (user.getNickname() == null || user.getNickname().trim().isEmpty()) { + user.setNickname(username); + } + if (user.getEnabled() == null) { + user.setEnabled(true); + } + return userRepository.save(user); + } + + public void adminDeleteUser(Long id) { + userRepository.deleteById(id); + } + + public List adminBanners() { + return bannerRepository.findAll(); + } + + public Banner adminSaveBanner(Banner banner) { + return bannerRepository.save(banner); + } + + public void adminDeleteBanner(Long id) { + bannerRepository.deleteById(id); + } + + public List adminProducts() { + return productRepository.findAll(); + } + + public List> adminProductViews() { + List products = productRepository.findAll(); + Map userMap = new HashMap<>(); + for (User user : userRepository.findAll()) { + userMap.put(user.getId(), user); + } + List> views = new ArrayList<>(); + for (Product p : products) { + User merchant = userMap.get(p.getMerchantId()); + Map row = new HashMap<>(); + row.put("id", p.getId()); + row.put("name", p.getName()); + row.put("category", p.getCategory()); + row.put("description", p.getDescription()); + row.put("price", p.getPrice()); + row.put("stock", p.getStock()); + row.put("imageUrl", p.getImageUrl()); + row.put("merchantId", p.getMerchantId()); + row.put("merchantUsername", merchant == null ? "" : merchant.getUsername()); + row.put("merchantName", merchant == null ? "" : merchant.getNickname()); + row.put("approved", p.getApproved()); + row.put("createdAt", p.getCreatedAt()); + views.add(row); + } + return views; + } + + public Product adminSaveProduct(Product input) { + if (input.getName() == null || input.getName().trim().isEmpty()) { + throw new BizException("商品名称不能为空"); + } + if (input.getMerchantId() == null) { + throw new BizException("商家ID不能为空"); + } + if (input.getPrice() == null || input.getStock() == null) { + throw new BizException("商品价格或库存不能为空"); + } + Product product = input; + if (input.getId() != null) { + product = productRepository.findById(input.getId()).orElseThrow(() -> new BizException("商品不存在")); + product.setName(input.getName()); + product.setCategory(input.getCategory()); + product.setDescription(input.getDescription()); + product.setPrice(input.getPrice()); + product.setStock(input.getStock()); + product.setImageUrl(input.getImageUrl()); + product.setMerchantId(input.getMerchantId()); + product.setApproved(Boolean.TRUE.equals(input.getApproved())); + } else { + if (product.getApproved() == null) { + product.setApproved(false); + } + } + return productRepository.save(product); + } + + public Product adminApproveProduct(Long productId, boolean approved) { + Product product = productRepository.findById(productId).orElseThrow(() -> new BizException("商品不存在")); + product.setApproved(approved); + return productRepository.save(product); + } + + public void adminDeleteProduct(Long id) { + productRepository.deleteById(id); + } + + public List> adminReviews() { + List reviews = reviewRepository.findAll(); + Map userMap = new HashMap<>(); + for (User user : userRepository.findAll()) { + userMap.put(user.getId(), user); + } + Map orderMap = new HashMap<>(); + for (Orders order : ordersRepository.findAll()) { + orderMap.put(order.getId(), order); + } + Map productMap = new HashMap<>(); + for (Product product : productRepository.findAll()) { + productMap.put(product.getId(), product); + } + List> views = new ArrayList<>(); + for (Review r : reviews) { + User customer = userMap.get(r.getCustomerId()); + Orders order = orderMap.get(r.getOrderId()); + Product product = productMap.get(r.getProductId()); + Map row = new HashMap<>(); + row.put("id", r.getId()); + row.put("orderId", r.getOrderId()); + row.put("orderNo", order == null ? "" : order.getOrderNo()); + row.put("productId", r.getProductId()); + row.put("productName", product == null ? "" : product.getName()); + row.put("customerId", r.getCustomerId()); + row.put("customerUsername", customer == null ? "" : customer.getUsername()); + row.put("rating", r.getRating()); + row.put("content", r.getContent()); + row.put("createdAt", r.getCreatedAt()); + views.add(row); + } + return views; + } + + public List> adminLogistics() { + List records = logisticsRecordRepository.findAll(); + Map userMap = new HashMap<>(); + for (User user : userRepository.findAll()) { + userMap.put(user.getId(), user); + } + Map orderMap = new HashMap<>(); + for (Orders order : ordersRepository.findAll()) { + orderMap.put(order.getId(), order); + } + List> views = new ArrayList<>(); + for (LogisticsRecord rec : records) { + User merchant = userMap.get(rec.getMerchantId()); + Orders order = orderMap.get(rec.getOrderId()); + Map row = new HashMap<>(); + row.put("id", rec.getId()); + row.put("orderId", rec.getOrderId()); + row.put("orderNo", order == null ? "" : order.getOrderNo()); + row.put("merchantId", rec.getMerchantId()); + row.put("merchantUsername", merchant == null ? "" : merchant.getUsername()); + row.put("status", rec.getStatus()); + row.put("note", rec.getNote()); + row.put("createdAt", rec.getCreatedAt()); + views.add(row); + } + return views; + } + + public List> adminInventory() { + List records = inventoryRecordRepository.findAll(); + Map userMap = new HashMap<>(); + for (User user : userRepository.findAll()) { + userMap.put(user.getId(), user); + } + Map productMap = new HashMap<>(); + for (Product product : productRepository.findAll()) { + productMap.put(product.getId(), product); + } + List> views = new ArrayList<>(); + for (InventoryRecord rec : records) { + User merchant = userMap.get(rec.getMerchantId()); + Product product = productMap.get(rec.getProductId()); + Map row = new HashMap<>(); + row.put("id", rec.getId()); + row.put("productId", rec.getProductId()); + row.put("productName", product == null ? "" : product.getName()); + row.put("merchantId", rec.getMerchantId()); + row.put("merchantUsername", merchant == null ? "" : merchant.getUsername()); + row.put("changeQty", rec.getChangeQty()); + row.put("note", rec.getNote()); + row.put("createdAt", rec.getCreatedAt()); + views.add(row); + } + return views; + } +} diff --git a/backend/src/main/resources/application.yml b/backend/src/main/resources/application.yml new file mode 100644 index 0000000..cfbfb44 --- /dev/null +++ b/backend/src/main/resources/application.yml @@ -0,0 +1,20 @@ +server: + port: 8080 + +spring: + datasource: + url: jdbc:mysql://localhost:3306/maternal_mall?useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=Asia/Shanghai&characterEncoding=utf8 + username: root + password: qq5211314 + jpa: + hibernate: + ddl-auto: update + show-sql: false + properties: + hibernate: + format_sql: true + jackson: + time-zone: Asia/Shanghai + +app: + token-header: X-Token diff --git a/backend/target/classes/application.yml b/backend/target/classes/application.yml new file mode 100644 index 0000000..cfbfb44 --- /dev/null +++ b/backend/target/classes/application.yml @@ -0,0 +1,20 @@ +server: + port: 8080 + +spring: + datasource: + url: jdbc:mysql://localhost:3306/maternal_mall?useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=Asia/Shanghai&characterEncoding=utf8 + username: root + password: qq5211314 + jpa: + hibernate: + ddl-auto: update + show-sql: false + properties: + hibernate: + format_sql: true + jackson: + time-zone: Asia/Shanghai + +app: + token-header: X-Token diff --git a/backend/target/classes/com/maternalmall/MaternalMallApplication.class b/backend/target/classes/com/maternalmall/MaternalMallApplication.class new file mode 100644 index 0000000..61c707e Binary files /dev/null and b/backend/target/classes/com/maternalmall/MaternalMallApplication.class differ diff --git a/backend/target/classes/com/maternalmall/common/ApiResponse.class b/backend/target/classes/com/maternalmall/common/ApiResponse.class new file mode 100644 index 0000000..7edbcb3 Binary files /dev/null and b/backend/target/classes/com/maternalmall/common/ApiResponse.class differ diff --git a/backend/target/classes/com/maternalmall/common/BizException.class b/backend/target/classes/com/maternalmall/common/BizException.class new file mode 100644 index 0000000..f7e98f5 Binary files /dev/null and b/backend/target/classes/com/maternalmall/common/BizException.class differ diff --git a/backend/target/classes/com/maternalmall/common/GlobalExceptionHandler.class b/backend/target/classes/com/maternalmall/common/GlobalExceptionHandler.class new file mode 100644 index 0000000..21617df Binary files /dev/null and b/backend/target/classes/com/maternalmall/common/GlobalExceptionHandler.class differ diff --git a/backend/target/classes/com/maternalmall/config/AuthContext.class b/backend/target/classes/com/maternalmall/config/AuthContext.class new file mode 100644 index 0000000..f7bf2e8 Binary files /dev/null and b/backend/target/classes/com/maternalmall/config/AuthContext.class differ diff --git a/backend/target/classes/com/maternalmall/config/AuthInterceptor.class b/backend/target/classes/com/maternalmall/config/AuthInterceptor.class new file mode 100644 index 0000000..c5d6725 Binary files /dev/null and b/backend/target/classes/com/maternalmall/config/AuthInterceptor.class differ diff --git a/backend/target/classes/com/maternalmall/config/WebMvcConfig.class b/backend/target/classes/com/maternalmall/config/WebMvcConfig.class new file mode 100644 index 0000000..2391c3c Binary files /dev/null and b/backend/target/classes/com/maternalmall/config/WebMvcConfig.class differ diff --git a/backend/target/classes/com/maternalmall/controller/AdminController.class b/backend/target/classes/com/maternalmall/controller/AdminController.class new file mode 100644 index 0000000..756a23d Binary files /dev/null and b/backend/target/classes/com/maternalmall/controller/AdminController.class differ diff --git a/backend/target/classes/com/maternalmall/controller/AuthController.class b/backend/target/classes/com/maternalmall/controller/AuthController.class new file mode 100644 index 0000000..a82788f Binary files /dev/null and b/backend/target/classes/com/maternalmall/controller/AuthController.class differ diff --git a/backend/target/classes/com/maternalmall/controller/CustomerController.class b/backend/target/classes/com/maternalmall/controller/CustomerController.class new file mode 100644 index 0000000..3f11720 Binary files /dev/null and b/backend/target/classes/com/maternalmall/controller/CustomerController.class differ diff --git a/backend/target/classes/com/maternalmall/controller/MerchantController.class b/backend/target/classes/com/maternalmall/controller/MerchantController.class new file mode 100644 index 0000000..0c6b552 Binary files /dev/null and b/backend/target/classes/com/maternalmall/controller/MerchantController.class differ diff --git a/backend/target/classes/com/maternalmall/controller/PublicController.class b/backend/target/classes/com/maternalmall/controller/PublicController.class new file mode 100644 index 0000000..27a8a86 Binary files /dev/null and b/backend/target/classes/com/maternalmall/controller/PublicController.class differ diff --git a/backend/target/classes/com/maternalmall/domain/Banner.class b/backend/target/classes/com/maternalmall/domain/Banner.class new file mode 100644 index 0000000..5950cce Binary files /dev/null and b/backend/target/classes/com/maternalmall/domain/Banner.class differ diff --git a/backend/target/classes/com/maternalmall/domain/CartItem.class b/backend/target/classes/com/maternalmall/domain/CartItem.class new file mode 100644 index 0000000..b60aacf Binary files /dev/null and b/backend/target/classes/com/maternalmall/domain/CartItem.class differ diff --git a/backend/target/classes/com/maternalmall/domain/Favorite.class b/backend/target/classes/com/maternalmall/domain/Favorite.class new file mode 100644 index 0000000..751984a Binary files /dev/null and b/backend/target/classes/com/maternalmall/domain/Favorite.class differ diff --git a/backend/target/classes/com/maternalmall/domain/InventoryRecord.class b/backend/target/classes/com/maternalmall/domain/InventoryRecord.class new file mode 100644 index 0000000..5273d60 Binary files /dev/null and b/backend/target/classes/com/maternalmall/domain/InventoryRecord.class differ diff --git a/backend/target/classes/com/maternalmall/domain/LogisticsRecord.class b/backend/target/classes/com/maternalmall/domain/LogisticsRecord.class new file mode 100644 index 0000000..6b5d525 Binary files /dev/null and b/backend/target/classes/com/maternalmall/domain/LogisticsRecord.class differ diff --git a/backend/target/classes/com/maternalmall/domain/MerchantApplication.class b/backend/target/classes/com/maternalmall/domain/MerchantApplication.class new file mode 100644 index 0000000..46203ab Binary files /dev/null and b/backend/target/classes/com/maternalmall/domain/MerchantApplication.class differ diff --git a/backend/target/classes/com/maternalmall/domain/OrderItem.class b/backend/target/classes/com/maternalmall/domain/OrderItem.class new file mode 100644 index 0000000..701ab75 Binary files /dev/null and b/backend/target/classes/com/maternalmall/domain/OrderItem.class differ diff --git a/backend/target/classes/com/maternalmall/domain/OrderStatus.class b/backend/target/classes/com/maternalmall/domain/OrderStatus.class new file mode 100644 index 0000000..62b615f Binary files /dev/null and b/backend/target/classes/com/maternalmall/domain/OrderStatus.class differ diff --git a/backend/target/classes/com/maternalmall/domain/Orders.class b/backend/target/classes/com/maternalmall/domain/Orders.class new file mode 100644 index 0000000..30e34ee Binary files /dev/null and b/backend/target/classes/com/maternalmall/domain/Orders.class differ diff --git a/backend/target/classes/com/maternalmall/domain/Product.class b/backend/target/classes/com/maternalmall/domain/Product.class new file mode 100644 index 0000000..34e988c Binary files /dev/null and b/backend/target/classes/com/maternalmall/domain/Product.class differ diff --git a/backend/target/classes/com/maternalmall/domain/Review.class b/backend/target/classes/com/maternalmall/domain/Review.class new file mode 100644 index 0000000..0ea2dda Binary files /dev/null and b/backend/target/classes/com/maternalmall/domain/Review.class differ diff --git a/backend/target/classes/com/maternalmall/domain/User.class b/backend/target/classes/com/maternalmall/domain/User.class new file mode 100644 index 0000000..949ebe0 Binary files /dev/null and b/backend/target/classes/com/maternalmall/domain/User.class differ diff --git a/backend/target/classes/com/maternalmall/domain/UserRole.class b/backend/target/classes/com/maternalmall/domain/UserRole.class new file mode 100644 index 0000000..7c91f47 Binary files /dev/null and b/backend/target/classes/com/maternalmall/domain/UserRole.class differ diff --git a/backend/target/classes/com/maternalmall/repository/BannerRepository.class b/backend/target/classes/com/maternalmall/repository/BannerRepository.class new file mode 100644 index 0000000..b3b8e20 Binary files /dev/null and b/backend/target/classes/com/maternalmall/repository/BannerRepository.class differ diff --git a/backend/target/classes/com/maternalmall/repository/CartItemRepository.class b/backend/target/classes/com/maternalmall/repository/CartItemRepository.class new file mode 100644 index 0000000..aa84385 Binary files /dev/null and b/backend/target/classes/com/maternalmall/repository/CartItemRepository.class differ diff --git a/backend/target/classes/com/maternalmall/repository/FavoriteRepository.class b/backend/target/classes/com/maternalmall/repository/FavoriteRepository.class new file mode 100644 index 0000000..d0c0d5e Binary files /dev/null and b/backend/target/classes/com/maternalmall/repository/FavoriteRepository.class differ diff --git a/backend/target/classes/com/maternalmall/repository/InventoryRecordRepository.class b/backend/target/classes/com/maternalmall/repository/InventoryRecordRepository.class new file mode 100644 index 0000000..fba31be Binary files /dev/null and b/backend/target/classes/com/maternalmall/repository/InventoryRecordRepository.class differ diff --git a/backend/target/classes/com/maternalmall/repository/LogisticsRecordRepository.class b/backend/target/classes/com/maternalmall/repository/LogisticsRecordRepository.class new file mode 100644 index 0000000..3e42ccc Binary files /dev/null and b/backend/target/classes/com/maternalmall/repository/LogisticsRecordRepository.class differ diff --git a/backend/target/classes/com/maternalmall/repository/MerchantApplicationRepository.class b/backend/target/classes/com/maternalmall/repository/MerchantApplicationRepository.class new file mode 100644 index 0000000..1bec24b Binary files /dev/null and b/backend/target/classes/com/maternalmall/repository/MerchantApplicationRepository.class differ diff --git a/backend/target/classes/com/maternalmall/repository/OrderItemRepository.class b/backend/target/classes/com/maternalmall/repository/OrderItemRepository.class new file mode 100644 index 0000000..a260faf Binary files /dev/null and b/backend/target/classes/com/maternalmall/repository/OrderItemRepository.class differ diff --git a/backend/target/classes/com/maternalmall/repository/OrdersRepository.class b/backend/target/classes/com/maternalmall/repository/OrdersRepository.class new file mode 100644 index 0000000..ef6854f Binary files /dev/null and b/backend/target/classes/com/maternalmall/repository/OrdersRepository.class differ diff --git a/backend/target/classes/com/maternalmall/repository/ProductRepository.class b/backend/target/classes/com/maternalmall/repository/ProductRepository.class new file mode 100644 index 0000000..916fc3f Binary files /dev/null and b/backend/target/classes/com/maternalmall/repository/ProductRepository.class differ diff --git a/backend/target/classes/com/maternalmall/repository/ReviewRepository.class b/backend/target/classes/com/maternalmall/repository/ReviewRepository.class new file mode 100644 index 0000000..f18a61f Binary files /dev/null and b/backend/target/classes/com/maternalmall/repository/ReviewRepository.class differ diff --git a/backend/target/classes/com/maternalmall/repository/UserRepository.class b/backend/target/classes/com/maternalmall/repository/UserRepository.class new file mode 100644 index 0000000..077a313 Binary files /dev/null and b/backend/target/classes/com/maternalmall/repository/UserRepository.class differ diff --git a/backend/target/classes/com/maternalmall/service/AuthService.class b/backend/target/classes/com/maternalmall/service/AuthService.class new file mode 100644 index 0000000..718b8cf Binary files /dev/null and b/backend/target/classes/com/maternalmall/service/AuthService.class differ diff --git a/backend/target/classes/com/maternalmall/service/MallService.class b/backend/target/classes/com/maternalmall/service/MallService.class new file mode 100644 index 0000000..9fce455 Binary files /dev/null and b/backend/target/classes/com/maternalmall/service/MallService.class differ diff --git a/frontend/index.html b/frontend/index.html new file mode 100644 index 0000000..e5f835c --- /dev/null +++ b/frontend/index.html @@ -0,0 +1,12 @@ + + + + + + 萌贝母婴商城 + + +
+ + + diff --git a/frontend/package-lock.json b/frontend/package-lock.json new file mode 100644 index 0000000..01bc18c --- /dev/null +++ b/frontend/package-lock.json @@ -0,0 +1,1684 @@ +{ + "name": "maternal-mall-frontend", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "maternal-mall-frontend", + "version": "1.0.0", + "dependencies": { + "@arco-design/web-vue": "^2.57.0", + "axios": "^1.7.9", + "echarts": "^6.0.0", + "pinia": "^2.3.1", + "vue": "^3.5.13", + "vue-echarts": "^8.0.1", + "vue-router": "^4.5.0" + }, + "devDependencies": { + "@vitejs/plugin-vue": "^5.2.1", + "vite": "^6.0.7" + } + }, + "node_modules/@arco-design/color": { + "version": "0.4.0", + "license": "MIT", + "dependencies": { + "color": "^3.1.3" + } + }, + "node_modules/@arco-design/web-vue": { + "version": "2.57.0", + "license": "MIT", + "dependencies": { + "@arco-design/color": "^0.4.0", + "b-tween": "^0.3.3", + "b-validate": "^1.5.3", + "compute-scroll-into-view": "^1.0.20", + "dayjs": "^1.11.13", + "number-precision": "^1.6.0", + "resize-observer-polyfill": "^1.5.1", + "scroll-into-view-if-needed": "^2.2.31", + "vue": "^3.1.0" + }, + "peerDependencies": { + "vue": "^3.1.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.28.5", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.0", + "license": "MIT", + "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", + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.25.12", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "license": "MIT" + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.57.1", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "dev": true, + "license": "MIT" + }, + "node_modules/@vitejs/plugin-vue": { + "version": "5.2.4", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "peerDependencies": { + "vite": "^5.0.0 || ^6.0.0", + "vue": "^3.2.25" + } + }, + "node_modules/@vue/compiler-core": { + "version": "3.5.28", + "license": "MIT", + "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", + "license": "MIT", + "dependencies": { + "@vue/compiler-core": "3.5.28", + "@vue/shared": "3.5.28" + } + }, + "node_modules/@vue/compiler-sfc": { + "version": "3.5.28", + "license": "MIT", + "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", + "license": "MIT", + "dependencies": { + "@vue/compiler-dom": "3.5.28", + "@vue/shared": "3.5.28" + } + }, + "node_modules/@vue/devtools-api": { + "version": "6.6.4", + "license": "MIT" + }, + "node_modules/@vue/reactivity": { + "version": "3.5.28", + "license": "MIT", + "dependencies": { + "@vue/shared": "3.5.28" + } + }, + "node_modules/@vue/runtime-core": { + "version": "3.5.28", + "license": "MIT", + "dependencies": { + "@vue/reactivity": "3.5.28", + "@vue/shared": "3.5.28" + } + }, + "node_modules/@vue/runtime-dom": { + "version": "3.5.28", + "license": "MIT", + "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", + "license": "MIT", + "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", + "license": "MIT" + }, + "node_modules/asynckit": { + "version": "0.4.0", + "license": "MIT" + }, + "node_modules/axios": { + "version": "1.13.5", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.15.11", + "form-data": "^4.0.5", + "proxy-from-env": "^1.1.0" + } + }, + "node_modules/b-tween": { + "version": "0.3.3", + "license": "MIT" + }, + "node_modules/b-validate": { + "version": "1.5.3", + "license": "MIT" + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/color": { + "version": "3.2.1", + "license": "MIT", + "dependencies": { + "color-convert": "^1.9.3", + "color-string": "^1.6.0" + } + }, + "node_modules/color-convert": { + "version": "1.9.3", + "license": "MIT", + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/color-name": { + "version": "1.1.3", + "license": "MIT" + }, + "node_modules/color-string": { + "version": "1.9.1", + "license": "MIT", + "dependencies": { + "color-name": "^1.0.0", + "simple-swizzle": "^0.2.2" + } + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/compute-scroll-into-view": { + "version": "1.0.20", + "license": "MIT" + }, + "node_modules/csstype": { + "version": "3.2.3", + "license": "MIT" + }, + "node_modules/dayjs": { + "version": "1.11.19", + "license": "MIT" + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/echarts": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/echarts/-/echarts-6.0.0.tgz", + "integrity": "sha512-Tte/grDQRiETQP4xz3iZWSvoHrkCQtwqd6hs+mifXcjrCuo2iKWbajFObuLJVBlDIJlOzgQPd1hsaKt/3+OMkQ==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "2.3.0", + "zrender": "6.0.0" + } + }, + "node_modules/entities": { + "version": "7.0.1", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "license": "MIT", + "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.25.12", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.12", + "@esbuild/android-arm": "0.25.12", + "@esbuild/android-arm64": "0.25.12", + "@esbuild/android-x64": "0.25.12", + "@esbuild/darwin-arm64": "0.25.12", + "@esbuild/darwin-x64": "0.25.12", + "@esbuild/freebsd-arm64": "0.25.12", + "@esbuild/freebsd-x64": "0.25.12", + "@esbuild/linux-arm": "0.25.12", + "@esbuild/linux-arm64": "0.25.12", + "@esbuild/linux-ia32": "0.25.12", + "@esbuild/linux-loong64": "0.25.12", + "@esbuild/linux-mips64el": "0.25.12", + "@esbuild/linux-ppc64": "0.25.12", + "@esbuild/linux-riscv64": "0.25.12", + "@esbuild/linux-s390x": "0.25.12", + "@esbuild/linux-x64": "0.25.12", + "@esbuild/netbsd-arm64": "0.25.12", + "@esbuild/netbsd-x64": "0.25.12", + "@esbuild/openbsd-arm64": "0.25.12", + "@esbuild/openbsd-x64": "0.25.12", + "@esbuild/openharmony-arm64": "0.25.12", + "@esbuild/sunos-x64": "0.25.12", + "@esbuild/win32-arm64": "0.25.12", + "@esbuild/win32-ia32": "0.25.12", + "@esbuild/win32-x64": "0.25.12" + } + }, + "node_modules/esbuild/node_modules/@esbuild/aix-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", + "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/esbuild/node_modules/@esbuild/android-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz", + "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/esbuild/node_modules/@esbuild/android-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz", + "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/esbuild/node_modules/@esbuild/android-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz", + "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/esbuild/node_modules/@esbuild/darwin-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz", + "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/esbuild/node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz", + "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/esbuild/node_modules/@esbuild/freebsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz", + "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/esbuild/node_modules/@esbuild/linux-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz", + "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/esbuild/node_modules/@esbuild/linux-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz", + "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/esbuild/node_modules/@esbuild/linux-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz", + "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/esbuild/node_modules/@esbuild/linux-loong64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz", + "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/esbuild/node_modules/@esbuild/linux-mips64el": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz", + "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/esbuild/node_modules/@esbuild/linux-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz", + "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/esbuild/node_modules/@esbuild/linux-riscv64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz", + "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/esbuild/node_modules/@esbuild/linux-s390x": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz", + "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/esbuild/node_modules/@esbuild/linux-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz", + "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/esbuild/node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz", + "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/esbuild/node_modules/@esbuild/netbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz", + "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/esbuild/node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz", + "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/esbuild/node_modules/@esbuild/openbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz", + "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/esbuild/node_modules/@esbuild/openharmony-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz", + "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/esbuild/node_modules/@esbuild/sunos-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz", + "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/esbuild/node_modules/@esbuild/win32-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz", + "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/esbuild/node_modules/@esbuild/win32-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz", + "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/esbuild/node_modules/@esbuild/win32-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz", + "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/estree-walker": { + "version": "2.0.2", + "license": "MIT" + }, + "node_modules/fdir": { + "version": "6.5.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/follow-redirects": { + "version": "1.15.11", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/form-data": { + "version": "4.0.5", + "license": "MIT", + "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", + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "license": "MIT", + "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", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/is-arrayish": { + "version": "0.3.4", + "license": "MIT" + }, + "node_modules/magic-string": { + "version": "0.30.21", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/nanoid": { + "version": "3.3.11", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/number-precision": { + "version": "1.6.0", + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.3", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pinia": { + "version": "2.3.1", + "license": "MIT", + "dependencies": { + "@vue/devtools-api": "^6.6.3", + "vue-demi": "^0.14.10" + }, + "funding": { + "url": "https://github.com/sponsors/posva" + }, + "peerDependencies": { + "typescript": ">=4.4.4", + "vue": "^2.7.0 || ^3.5.11" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/postcss": { + "version": "8.5.6", + "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" + } + ], + "license": "MIT", + "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", + "license": "MIT" + }, + "node_modules/resize-observer-polyfill": { + "version": "1.5.1", + "license": "MIT" + }, + "node_modules/rollup": { + "version": "4.57.1", + "dev": true, + "license": "MIT", + "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/rollup/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, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/rollup/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, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/rollup/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, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/rollup/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, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/rollup/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, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/rollup/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, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/rollup/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, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/rollup/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, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/rollup/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, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/rollup/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, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/rollup/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, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/rollup/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, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/rollup/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, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/rollup/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, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/rollup/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, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/rollup/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, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/rollup/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, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/rollup/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, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/rollup/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, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/rollup/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, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/rollup/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, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/rollup/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, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/rollup/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, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/rollup/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, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/scroll-into-view-if-needed": { + "version": "2.2.31", + "license": "MIT", + "dependencies": { + "compute-scroll-into-view": "^1.0.20" + } + }, + "node_modules/simple-swizzle": { + "version": "0.2.4", + "license": "MIT", + "dependencies": { + "is-arrayish": "^0.3.1" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.15", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tslib": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.0.tgz", + "integrity": "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==", + "license": "0BSD" + }, + "node_modules/vite": { + "version": "6.4.1", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.25.0", + "fdir": "^6.4.4", + "picomatch": "^4.0.2", + "postcss": "^8.5.3", + "rollup": "^4.34.9", + "tinyglobby": "^0.2.13" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "jiti": ">=1.21.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "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", + "license": "MIT", + "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-demi": { + "version": "0.14.10", + "hasInstallScript": true, + "license": "MIT", + "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/vue-echarts": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/vue-echarts/-/vue-echarts-8.0.1.tgz", + "integrity": "sha512-23rJTFLu1OUEGRWjJGmdGt8fP+8+ja1gVgzMYPIPaHWpXegcO1viIAaeu2H4QHESlVeHzUAHIxKXGrwjsyXAaA==", + "license": "MIT", + "peerDependencies": { + "echarts": "^6.0.0", + "vue": "^3.3.0" + } + }, + "node_modules/vue-router": { + "version": "4.6.4", + "license": "MIT", + "dependencies": { + "@vue/devtools-api": "^6.6.4" + }, + "funding": { + "url": "https://github.com/sponsors/posva" + }, + "peerDependencies": { + "vue": "^3.5.0" + } + }, + "node_modules/zrender": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/zrender/-/zrender-6.0.0.tgz", + "integrity": "sha512-41dFXEEXuJpNecuUQq6JlbybmnHaqqpGlbH1yxnA5V9MMP4SbohSVZsJIwz+zdjQXSSlR1Vc34EgH1zxyTDvhg==", + "license": "BSD-3-Clause", + "dependencies": { + "tslib": "2.3.0" + } + } + } +} diff --git a/frontend/package.json b/frontend/package.json new file mode 100644 index 0000000..fe9bdbe --- /dev/null +++ b/frontend/package.json @@ -0,0 +1,24 @@ +{ + "name": "maternal-mall-frontend", + "version": "1.0.0", + "private": true, + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "preview": "vite preview" + }, + "dependencies": { + "@arco-design/web-vue": "^2.57.0", + "axios": "^1.7.9", + "echarts": "^6.0.0", + "pinia": "^2.3.1", + "vue": "^3.5.13", + "vue-echarts": "^8.0.1", + "vue-router": "^4.5.0" + }, + "devDependencies": { + "@vitejs/plugin-vue": "^5.2.1", + "vite": "^6.0.7" + } +} diff --git a/frontend/src/App.vue b/frontend/src/App.vue new file mode 100644 index 0000000..98240ae --- /dev/null +++ b/frontend/src/App.vue @@ -0,0 +1,3 @@ + diff --git a/frontend/src/api/http.js b/frontend/src/api/http.js new file mode 100644 index 0000000..732f576 --- /dev/null +++ b/frontend/src/api/http.js @@ -0,0 +1,24 @@ +import axios from 'axios' + +const http = axios.create({ + baseURL: 'http://localhost:8080', + timeout: 10000 +}) + +http.interceptors.request.use((config) => { + const token = localStorage.getItem('token') + if (token) { + config.headers['X-Token'] = token + } + return config +}) + +http.interceptors.response.use((resp) => { + const data = resp.data + if (data.code !== 0) { + return Promise.reject(new Error(data.message || '请求失败')) + } + return data.data +}) + +export default http diff --git a/frontend/src/api/index.js b/frontend/src/api/index.js new file mode 100644 index 0000000..cc692ea --- /dev/null +++ b/frontend/src/api/index.js @@ -0,0 +1,65 @@ +import http from './http' + +export const api = { + register: (payload) => http.post('/api/auth/register', payload), + login: (payload) => http.post('/api/auth/login', payload), + me: () => http.get('/api/auth/me'), + updateMe: (payload) => http.put('/api/auth/me', payload), + + banners: () => http.get('/api/public/banners'), + products: (keyword = '') => http.get('/api/public/products', { params: { keyword } }), + + customerCart: () => http.get('/api/customer/cart'), + customerCartViews: () => http.get('/api/customer/cart/views'), + addCart: (payload) => http.post('/api/customer/cart', payload), + delCart: (productId) => http.delete(`/api/customer/cart/${productId}`), + checkout: (payload) => http.post('/api/customer/orders/checkout', payload), + customerBuyNow: (payload) => http.post('/api/customer/orders/buy-now', payload), + customerOrders: () => http.get('/api/customer/orders'), + refundOrder: (id, payload) => http.put(`/api/customer/orders/${id}/refund`, payload), + updateOrderAddress: (id, payload) => http.put(`/api/customer/orders/${id}/address`, payload), + deleteOrder: (id) => http.delete(`/api/customer/orders/${id}`), + orderLogistics: (id) => http.get(`/api/customer/orders/${id}/logistics`), + customerFavorites: () => http.get('/api/customer/favorites'), + customerFavoriteViews: () => http.get('/api/customer/favorites/views'), + addFavorite: (payload) => http.post('/api/customer/favorites', payload), + deleteFavorite: (productId) => http.delete(`/api/customer/favorites/${productId}`), + addReview: (payload) => http.post('/api/customer/reviews', payload), + orderItems: (orderId) => http.get(`/api/customer/orders/${orderId}/items`), + applyMerchant: (payload) => http.post('/api/customer/merchant-applications', payload), + + merchantOverview: () => http.get('/api/merchant/overview'), + merchantProducts: () => http.get('/api/merchant/products'), + saveMerchantProduct: (payload) => http.post('/api/merchant/products', payload), + deleteMerchantProduct: (id) => http.delete(`/api/merchant/products/${id}`), + merchantOrders: () => http.get('/api/merchant/orders'), + shipOrder: (id, payload) => http.put(`/api/merchant/orders/${id}/ship`, payload), + merchantRefund: (id, payload) => http.put(`/api/merchant/orders/${id}/refund`, payload), + merchantReviews: () => http.get('/api/merchant/reviews'), + merchantLogistics: () => http.get('/api/merchant/logistics'), + merchantInventory: () => http.get('/api/merchant/inventory'), + deleteMerchantInventory: (id) => http.delete(`/api/merchant/inventory/${id}`), + + adminOverview: () => http.get('/api/admin/overview'), + adminUsers: () => http.get('/api/admin/users'), + adminSaveUser: (payload) => http.post('/api/admin/users', payload), + adminDeleteUser: (id) => http.delete(`/api/admin/users/${id}`), + adminOrders: () => http.get('/api/admin/orders'), + adminUpdateOrder: (id, payload) => http.put(`/api/admin/orders/${id}`, payload), + adminOrderRisks: () => http.get('/api/admin/orders/risk'), + adminAuditRefund: (id, payload) => http.put(`/api/admin/orders/${id}/refund-audit`, payload), + adminAuditShipment: (id, payload) => http.put(`/api/admin/orders/${id}/ship-audit`, payload), + adminMerchantApplications: () => http.get('/api/admin/merchant-applications'), + adminAuditMerchantApplication: (id, payload) => http.put(`/api/admin/merchant-applications/${id}`, payload), + adminBanners: () => http.get('/api/admin/banners'), + adminSaveBanner: (payload) => http.post('/api/admin/banners', payload), + adminDeleteBanner: (id) => http.delete(`/api/admin/banners/${id}`), + adminProducts: () => http.get('/api/admin/products'), + adminProductViews: () => http.get('/api/admin/products/views'), + adminSaveProduct: (payload) => http.post('/api/admin/products', payload), + adminApproveProduct: (id, payload) => http.put(`/api/admin/products/${id}/approve`, payload), + 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') +} diff --git a/frontend/src/main.js b/frontend/src/main.js new file mode 100644 index 0000000..0355f4e --- /dev/null +++ b/frontend/src/main.js @@ -0,0 +1,9 @@ +import { createApp } from 'vue' +import { createPinia } from 'pinia' +import ArcoVue from '@arco-design/web-vue' +import '@arco-design/web-vue/dist/arco.css' +import App from './App.vue' +import router from './router' +import './style.css' + +createApp(App).use(createPinia()).use(router).use(ArcoVue).mount('#app') diff --git a/frontend/src/router/index.js b/frontend/src/router/index.js new file mode 100644 index 0000000..7400f4d --- /dev/null +++ b/frontend/src/router/index.js @@ -0,0 +1,29 @@ +import { createRouter, createWebHistory } from 'vue-router' +import LoginView from '../views/LoginView.vue' +import HomeView from '../views/HomeView.vue' +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' + +const routes = [ + { path: '/', redirect: '/products' }, + { path: '/products', component: HomeView }, + { path: '/cart', component: CartView }, + { path: '/orders', component: OrdersView }, + { path: '/favorites', component: FavoritesView }, + { path: '/profile', component: ProfileView }, + { path: '/login', component: LoginView }, + { path: '/admin', component: AdminView }, + { path: '/merchant', component: MerchantView }, + { path: '/customer', redirect: '/products' } +] + +const router = createRouter({ + history: createWebHistory(), + routes +}) + +export default router diff --git a/frontend/src/stores/user.js b/frontend/src/stores/user.js new file mode 100644 index 0000000..2bedcbc --- /dev/null +++ b/frontend/src/stores/user.js @@ -0,0 +1,21 @@ +import { defineStore } from 'pinia' +import { api } from '../api' + +export const useUserStore = defineStore('user', { + state: () => ({ + profile: null + }), + getters: { + role: (s) => s.profile?.role, + loggedIn: (s) => !!s.profile + }, + actions: { + async fetchMe() { + this.profile = await api.me() + }, + async logout() { + localStorage.removeItem('token') + this.profile = null + } + } +}) diff --git a/frontend/src/style.css b/frontend/src/style.css new file mode 100644 index 0000000..992e5ef --- /dev/null +++ b/frontend/src/style.css @@ -0,0 +1,13 @@ +:root { + --bg-main: linear-gradient(135deg, #fff8ed 0%, #f2f7ff 100%); +} +body { + margin: 0; + font-family: "PingFang SC", "Helvetica Neue", sans-serif; + background: var(--bg-main); +} +.container { + max-width: 1200px; + margin: 0 auto; + padding: 16px; +} diff --git a/frontend/src/views/AdminView.vue b/frontend/src/views/AdminView.vue new file mode 100644 index 0000000..24de449 --- /dev/null +++ b/frontend/src/views/AdminView.vue @@ -0,0 +1,644 @@ + + + diff --git a/frontend/src/views/CartView.vue b/frontend/src/views/CartView.vue new file mode 100644 index 0000000..b2a04cf --- /dev/null +++ b/frontend/src/views/CartView.vue @@ -0,0 +1,86 @@ + + + diff --git a/frontend/src/views/CustomerView.vue b/frontend/src/views/CustomerView.vue new file mode 100644 index 0000000..b062b6c --- /dev/null +++ b/frontend/src/views/CustomerView.vue @@ -0,0 +1,173 @@ + + + diff --git a/frontend/src/views/FavoritesView.vue b/frontend/src/views/FavoritesView.vue new file mode 100644 index 0000000..ca6fd8c --- /dev/null +++ b/frontend/src/views/FavoritesView.vue @@ -0,0 +1,86 @@ + + + diff --git a/frontend/src/views/HomeView.vue b/frontend/src/views/HomeView.vue new file mode 100644 index 0000000..cb7605d --- /dev/null +++ b/frontend/src/views/HomeView.vue @@ -0,0 +1,132 @@ + + + diff --git a/frontend/src/views/LoginView.vue b/frontend/src/views/LoginView.vue new file mode 100644 index 0000000..02bf452 --- /dev/null +++ b/frontend/src/views/LoginView.vue @@ -0,0 +1,57 @@ + + + diff --git a/frontend/src/views/MerchantView.vue b/frontend/src/views/MerchantView.vue new file mode 100644 index 0000000..b0f5339 --- /dev/null +++ b/frontend/src/views/MerchantView.vue @@ -0,0 +1,355 @@ + + + diff --git a/frontend/src/views/OrdersView.vue b/frontend/src/views/OrdersView.vue new file mode 100644 index 0000000..5c8cb10 --- /dev/null +++ b/frontend/src/views/OrdersView.vue @@ -0,0 +1,154 @@ + + + diff --git a/frontend/src/views/ProfileView.vue b/frontend/src/views/ProfileView.vue new file mode 100644 index 0000000..c396910 --- /dev/null +++ b/frontend/src/views/ProfileView.vue @@ -0,0 +1,75 @@ + + + diff --git a/frontend/vite.config.js b/frontend/vite.config.js new file mode 100644 index 0000000..8084277 --- /dev/null +++ b/frontend/vite.config.js @@ -0,0 +1,9 @@ +import { defineConfig } from 'vite' +import vue from '@vitejs/plugin-vue' + +export default defineConfig({ + plugins: [vue()], + server: { + port: 5173 + } +}) diff --git a/sql/init.sql b/sql/init.sql new file mode 100644 index 0000000..12d32a2 --- /dev/null +++ b/sql/init.sql @@ -0,0 +1,144 @@ +CREATE DATABASE IF NOT EXISTS maternal_mall DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; +USE maternal_mall; + +CREATE TABLE IF NOT EXISTS users ( + id BIGINT PRIMARY KEY AUTO_INCREMENT, + username VARCHAR(50) UNIQUE NOT NULL, + password VARCHAR(100) NOT NULL, + role VARCHAR(20) NOT NULL, + nickname VARCHAR(50), + phone VARCHAR(20), + address VARCHAR(255), + token VARCHAR(100), + enabled BIT NOT NULL DEFAULT b'1', + created_at DATETIME DEFAULT CURRENT_TIMESTAMP, + updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP +); + +CREATE TABLE IF NOT EXISTS product ( + id BIGINT PRIMARY KEY AUTO_INCREMENT, + name VARCHAR(120) NOT NULL, + category VARCHAR(50), + description VARCHAR(1000), + price DECIMAL(10,2) NOT NULL, + stock INT NOT NULL, + image_url VARCHAR(255), + merchant_id BIGINT NOT NULL, + approved BIT NOT NULL DEFAULT b'0', + created_at DATETIME DEFAULT CURRENT_TIMESTAMP, + updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP +); + +CREATE TABLE IF NOT EXISTS cart_item ( + id BIGINT PRIMARY KEY AUTO_INCREMENT, + customer_id BIGINT NOT NULL, + product_id BIGINT NOT NULL, + quantity INT NOT NULL, + UNIQUE KEY uk_customer_product(customer_id, product_id) +); + +CREATE TABLE IF NOT EXISTS favorite ( + id BIGINT PRIMARY KEY AUTO_INCREMENT, + customer_id BIGINT NOT NULL, + product_id BIGINT NOT NULL, + UNIQUE KEY uk_f_customer_product(customer_id, product_id) +); + +CREATE TABLE IF NOT EXISTS orders ( + id BIGINT PRIMARY KEY AUTO_INCREMENT, + order_no VARCHAR(50) UNIQUE NOT NULL, + customer_id BIGINT NOT NULL, + merchant_id BIGINT NOT NULL, + total_amount DECIMAL(10,2) NOT NULL, + status VARCHAR(30) NOT NULL, + address VARCHAR(255), + logistics_info VARCHAR(255), + refund_reason VARCHAR(255), + created_at DATETIME DEFAULT CURRENT_TIMESTAMP, + updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP +); + +CREATE TABLE IF NOT EXISTS order_item ( + id BIGINT PRIMARY KEY AUTO_INCREMENT, + order_id BIGINT NOT NULL, + product_id BIGINT NOT NULL, + product_name VARCHAR(120) NOT NULL, + quantity INT NOT NULL, + unit_price DECIMAL(10,2) NOT NULL +); + +CREATE TABLE IF NOT EXISTS review ( + id BIGINT PRIMARY KEY AUTO_INCREMENT, + order_id BIGINT NOT NULL, + product_id BIGINT NOT NULL, + customer_id BIGINT NOT NULL, + rating INT NOT NULL, + content VARCHAR(1000), + created_at DATETIME DEFAULT CURRENT_TIMESTAMP +); + +CREATE TABLE IF NOT EXISTS logistics_record ( + id BIGINT PRIMARY KEY AUTO_INCREMENT, + order_id BIGINT NOT NULL, + merchant_id BIGINT NOT NULL, + status VARCHAR(255) NOT NULL, + note VARCHAR(255), + created_at DATETIME DEFAULT CURRENT_TIMESTAMP +); + +CREATE TABLE IF NOT EXISTS inventory_record ( + id BIGINT PRIMARY KEY AUTO_INCREMENT, + product_id BIGINT NOT NULL, + merchant_id BIGINT NOT NULL, + change_qty INT NOT NULL, + note VARCHAR(255), + created_at DATETIME DEFAULT CURRENT_TIMESTAMP +); + +CREATE TABLE IF NOT EXISTS merchant_application ( + id BIGINT PRIMARY KEY AUTO_INCREMENT, + user_id BIGINT NOT NULL, + qualification VARCHAR(255), + status VARCHAR(30) NOT NULL, + remark VARCHAR(255), + created_at DATETIME DEFAULT CURRENT_TIMESTAMP, + updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP +); + +CREATE TABLE IF NOT EXISTS banner ( + id BIGINT PRIMARY KEY AUTO_INCREMENT, + image_url VARCHAR(255) NOT NULL, + link_url VARCHAR(255), + sort_no INT NOT NULL DEFAULT 0, + enabled BIT NOT NULL DEFAULT b'1' +); + +INSERT INTO users(username,password,role,nickname,enabled) +SELECT 'admin','123456','ADMIN','平台管理员',b'1' FROM dual +WHERE NOT EXISTS (SELECT 1 FROM users WHERE username='admin'); + +INSERT INTO users(username,password,role,nickname,enabled) +SELECT 'merchant1','123456','MERCHANT','示例商家',b'1' FROM dual +WHERE NOT EXISTS (SELECT 1 FROM users WHERE username='merchant1'); + +INSERT INTO users(username,password,role,nickname,enabled) +SELECT 'customer1','123456','CUSTOMER','示例顾客',b'1' FROM dual +WHERE NOT EXISTS (SELECT 1 FROM users WHERE username='customer1'); + +INSERT INTO product(name,category,description,price,stock,image_url,merchant_id,approved) +SELECT '婴儿纸尿裤L码','尿裤湿巾','高吸收防漏,适合10-14kg','89.00',200,'https://picsum.photos/400/260?diaper', + (SELECT id FROM users WHERE username='merchant1' LIMIT 1), b'1' +FROM dual WHERE NOT EXISTS (SELECT 1 FROM product WHERE name='婴儿纸尿裤L码'); + +INSERT INTO product(name,category,description,price,stock,image_url,merchant_id,approved) +SELECT '孕妇营养奶粉','奶粉辅食','富含叶酸和DHA','168.00',80,'https://picsum.photos/400/260?milk', + (SELECT id FROM users WHERE username='merchant1' LIMIT 1), b'1' +FROM dual WHERE NOT EXISTS (SELECT 1 FROM product WHERE name='孕妇营养奶粉'); + +INSERT INTO banner(image_url,link_url,sort_no,enabled) +SELECT 'https://picsum.photos/1200/220?maternity1','#',1,b'1' FROM dual +WHERE NOT EXISTS (SELECT 1 FROM banner WHERE sort_no=1); + +INSERT INTO banner(image_url,link_url,sort_no,enabled) +SELECT 'https://picsum.photos/1200/220?maternity2','#',2,b'1' FROM dual +WHERE NOT EXISTS (SELECT 1 FROM banner WHERE sort_no=2); diff --git a/崔梦雪-开题报告修改版.md b/崔梦雪-开题报告修改版.md new file mode 100644 index 0000000..88fc734 --- /dev/null +++ b/崔梦雪-开题报告修改版.md @@ -0,0 +1,237 @@ +# 大连科技学院 + +# 毕业设计(论文)开题报告 + +学 院 信息科学与技术学院 + +专业班级 网络工程(专升本)24-1 + +学生姓名 崔梦雪 + +学生学号 2406490119 + +指导教师 郭永伟 + +导师职称 副教授 + +# 1 选题的意义和研究现状 + +# 1.1 选题的意义 + +在数字化浪潮席卷全球的当下,互联网技术与传统行业的深度融合成为经济发展的新引擎,母婴用品消费市场也迎来了消费升级与渠道变革的双重机遇。随着居民生活水平的提高和育儿观念的精细化,消费者对母婴用品的品质、安全性和购买便捷性提出了更高要求,而传统母婴用品销售模式依赖线下门店和多层级经销商,存在流通环节繁琐、信息不对称等问题,已难以满足市场需求。 + +本课题旨在开发萌贝母婴用品销售平台,通过整合先进技术与创新运营模式,破解传统行业痛点,实现多重核心目标,一是打破地域限制与渠道壁垒,缩短母婴用品从生产端到消费端的流通链条,显著降低中间运营成本,提升行业整体运转效率。二是为顾客提供丰富多元的产品选择、简洁高效的购物流程。三是为合作商户搭建低成本、高覆盖的销售渠道,配套数据化运营工具与精准营销支持,助力商户快速适配市场变化。四是通过前后端分离架构等技术的落地应用,为母婴行业数字化转型提供可复制的实践方案,推动行业技术标准升级。 + +# 1.2 本课题所涉及问题在国内外设计或研究的现状 + +国内母婴电商市场呈现多元化发展态势,专属母婴销售平台与综合平台母婴专区协同发力。天猫母婴作为综合平台旗下的核心母婴销售板块,凭借阿里体系的庞大用户基础和成熟物流网络,成为覆盖全品类母婴产品的主流渠道。抖音母婴直播则依托短视频场景化展示优势,通过达人实测、育儿知识分享等互动形式,带动母婴用品销量快速增长。国内相关研究多集中在平台架构搭建、用户消费行为分析和物流配送优化等方面,但市场中仍存在明显短板,部分平台功能同质化严重,农村地区物流覆盖不全、售后服务响应滞后等问题也影响着用户体验。 + +国外母婴电商起步早,形成了一批专注母婴领域的成熟销售平台。美国的 BuyBuyBaby 专注母婴产品零售,以丰富的品类选择和细致的购物指引为核心优势,覆盖从孕期到学龄儿童的全阶段需求。日本的西松屋作为知名母婴销售平台,以精细化运营为特色,注重产品设计的实用性与安全性,物流服务贴合母婴用户应急需求,配送时效稳定。国外研究侧重供应链协同管理、消费者深层需求挖掘和服务模式创新,但整体发展中也存在适配性不足的问题。 + +国内外母婴电商研究与实践存在两大不足,一是信任体系有短板,产品质量信息透明度低且部分平台缺乏权威安全认证展示,二是供应链响应效率待提升,难以灵活应对 + +母婴产品周期性需求波动与应急采购场景。这些问题既影响用户体验和品牌信任度,也 + +制约行业可持续发展,需通过优化服务模式、强化需求适配、完善供应链管理等方式解决。 + +# 2 课题设计或研究的内容、预期目标和实施计划 + +# 2.1 要设计或研究的主要内容方案论证分析 + +# 2.1.1 需求分析 + +对系统的顾客、商家、管理人员等进行需求调研,了解他们对管理平台的功能需求和使用习惯,要求本系统有以下功能。 + +# (1)顾客 + +顾客功能如图 2.1。 + +登录注册:用户可以输入账号密码登录和注册。 + +搜索商品:顾客可以搜索并浏览各种商品。 + +购物车管理:顾客可以把商品加入到购物车与把物品从购物车移除,结算等。 + +购买商品:顾客可以购买物品。 + +收藏商品;顾客可以收藏自己喜欢的物品。 + +评价商品: 顾客可以对购买过的物品进行评价。 + +订单管理: 支持顾客对订单申请退款,查看物流信息,修改收货信息,删除订单等。 + +个人信息处理:顾客可以更改自己的个人信息。 + +![image](https://cdn-mineru.openxlab.org.cn/result/2026-02-09/91525fd6-19b8-4562-80b9-de011d5c58f0/8ea1316ad93d6f7cd5207e95d0a82ed095e26fdc032daedcbc480f80d429ef93.jpg) + + + +图 2.1 顾客功能图 + + +# (2)商家 + +商家功能如图 $2 . 2 \circ$ + +数据概览:查看订单量,销售额,热销商品排行图,品类销售占比图,通知公告等。 + +商品管理:对商品信息进行增删改查。 + +订单管理:查看订单信息,发货,查看物流,退货退款处理等。 + +评价管理:查看顾客的评价。 + +物流管理:查看所有商品的物流信息。 + +库存管理:可以查看商品出入库情况,删除记录等。 + +个人信息修改:支持商家更改自己的个人信息。 + +![image](https://cdn-mineru.openxlab.org.cn/result/2026-02-09/91525fd6-19b8-4562-80b9-de011d5c58f0/8142d559153c56786f3f2b43b38194bcfffb94791fd58b5e61d20356f0b82ea5.jpg) + + + +图2.2商家功能图 + + +# (3)管理员 + +管理员功能如图 2.3。 + +数据概览:查看订单量,销售额,热销商品排行图,品类销售占比图等。 + +订单管理:实时监控订单状态、核查修改订单信息、审核发货与跟踪物流的全流程操 + +作、排查违规订单、审核退款的异常管控。 + +审核管理:核查母婴用品基础信息和商家注册信息、核验商家资质经营能力、监管商 + +家违规行为。 + +用户管理:对顾客和商家信息进行增删改查。 + +个人信息修改:支持顾客和商家更改自己的个人信息。 + +轮播图设置:平台的活动页面轮播。 + +![image](https://cdn-mineru.openxlab.org.cn/result/2026-02-09/91525fd6-19b8-4562-80b9-de011d5c58f0/0292eaa5e2c4697169e862fde65800985883ed35bbaed8e77f63470a58c6607f.jpg) + + + +图2.3管理员功能图 + + +# 2.1.2 可行性分析 + +# (1)技术可行性 + +本平台技术方案具备充分可行性,核心技术均为线上交易系统领域成熟应用的主流选择。前端采用 Vue.js 技术,可快速构建适配多终端的响应式界面。后端基于 Spring Boot框架开发,能简化配置并集成安全管控功能,保障用户数据安全。数据存储选用MySQL,可高效管理结构化数据,配合物流接口,能快速实现核心业务流程。开发工具与技术社区资源丰富,无技术壁垒。 + +# (2)经济可行性 + +成本端依托开源技术大幅降低软件授权支出,采用模块化开发模式缩短研发周期,同时按实际业务需求灵活扩容,有效减少初期固定资产投入,整体成本可控性强。收益端路径清晰,通过商户入驻佣金,叠加母婴线上交易系统市场持续增长的行业红利,精准契 + +合目标用户消费需求,盈利空间广阔,投入产出比处于合理区间,经济价值与商业潜力显著。 + +# (3)操作可行性 + +系统贴合多角色使用习惯,操作门槛低。用户端采用主流线上交易系统交互逻辑,购物流程简洁直观。商户端聚焦产品与订单管理,功能设计贴合运营需求。管理员后台模块化布局,权限划分清晰。全平台均配备操作提示,无需专业培训即可上手,适配不同用户的使用场景。 + +# 2.2 本课题选题特色及预期的目标 + +本课题聚焦母婴线上交易平台领域,选题特色体现在三方面,一是紧扣母婴用品高安全、长周期的行业特性,二是针对性优化产品筛选、物流等模块,弥补通用线上交易平台适配不足,三是构建顾客-商家-管理员三方协同的生态化运营模式,强化服务与管控,突破传统线上交易平台重交易轻服务的局限。预期目标是打造贴合母婴用户需求的专业销售平台,强化服务与管控,突破传统线上交易平台重交易轻服务的局限。 + +# 2.3 本课题实施计划 + +
周数进度计划
第1周确定毕业设计题目,在网络上对“母婴用品销售平台”进行调研
第2周根据前期的调研情况,查阅相关资料完成开题报告撰写
第3周选择与课题相关的外文文献,完成外文翻译。进行前期资料自查,进行系统可行性分析和需求分析
第4周完成毕设前期检查。依据系统功能需求和业务流程分析,完成用例图和用例说明
第5周进行系统分析,以用例图为基础进行类图、活动图和顺序图的绘制,确保系统的一致性和可维护性
第6周完成数据库设计、界面设计,根据反馈意见进行修改
第7周系统实现,按功能模块进行编码
第8周完成毕设中期检查。系统实现,按功能模块进行编码
第9周系统测试,测试本系统各业务功能运行是否正常,验证功能需求是否都符合规范要求。完成论文主体部分
第10周按照系统测试结果修改代码完善功能,并完成论文剩余相关内容编写
第11周提交论文初稿,根据反馈意见修改论文
第12周继续修改论文,完成论文查重稿定稿。检查系统功能,为软件验收做好准备
第13周进行软件验收,参加校级论文查重,根据论文内容制作答辩PPT
第14周进行毕业设计答辩,并按照答辩组意见修改论文定稿,完成毕设资料存档
+ +# 3 主要参考文献 + + + +[1] 汤智宏.基于 SpringBoot+Vue 的线上书店设计与实践[J].电脑编程技巧与维护,2025,(09):71-73. + + + + + +[2] 杨文霞,霍甜甜.基于 Vue.js 的学生选课系统的设计与实现[J].办公自动化,2025,30(18):7-9+31. + + + + + +[3] 张磊.基于 B/S 架构的矿区生态监管系统平台设计与实现[J].河南科技,2025,52(16):17-22. + + + + + +[4] 杨玉,刘杰举.基于Spring Boot与Vue的物业管理系统设计与实现[J].鞋类工艺与设计,2025,5(14):114-116. + + + + + +[5] 项露芬,孙佳怡,李梦婷.基于Vue和Node.js的音乐门票管理系统的设计与实现[J].现代信息科技,2025,9(11):96-101. + + + + + +[6] 闫俊甫.基于 Spring Boot 和 Element UI 的中小型超市 ERP 系统的开发[J].电脑知识与技术,2025,21(16):48-50. + + + + + +[7] Gyan O J ,Raheem A T ,Ogundipe O M ,et al.Enhancing Security Practices across the Software Development Lifecycle:The Role of Artificial Intelligence[J].Asian Journal of Research in Computer Science,2025,18(10):101-114. + + + + + +[8] 徐家喜,王小正,朱杰.Java EE 框架技术与案例教程[M].南京大学出版社:202310:32. + + + + + +[9] 吕善雨.多元化信息融合的电商推荐系统设计与实现[D].北京邮电大学,2022. + + + + + +[10]郑阿奇.MySQL 数据库教程[M].人民邮电出版社:202401:465. + + + + + +[11]付世军,卢淞岩,李梦,等.基于B/S架构的智慧农业管理系统的设计与实现[J].湖北农业科学,2025,64(01):154-161. + + + + + +[12]黑马程序员.Spring Boot 企业级开发教程[M].人民邮电出版社:202407:258. + + + + + +[13]刘张榕.MySQL 数据库课程思政实现与建设策略[J].科学咨询,2025,(10):107-110. + + + + + +[14]冯赛赛,郝婷.影院管理系统的设计与实现[J].福建电脑,2025,41(05):68-72. + + + + + +[15]相景丽.MySQL数据库技术在校园信息管理中的应用研究[J].信息记录材料,2025,26(03):104-106+131. + diff --git a/需要实现的功能总结.md b/需要实现的功能总结.md new file mode 100644 index 0000000..3a8ceb6 --- /dev/null +++ b/需要实现的功能总结.md @@ -0,0 +1,44 @@ +# 需要实现的功能(按角色划分) + +## 顾客端(用户端) +- 登录/注册(账号密码) +- 商品搜索与浏览 +- 购物车管理(加入/移除/结算) +- 下单购买 +- 商品收藏 +- 商品评价(对已购买商品评价) +- 订单管理 + - 申请退款 + - 查看物流信息 + - 修改收货信息 + - 删除订单 +- 个人信息管理(修改个人资料) + +## 商家端 +- 数据概览/看板(订单量、销售额、热销排行、品类占比、通知公告) +- 商品管理(增删改查) +- 订单管理(查看订单、发货、查看物流、退货/退款处理) +- 评价管理(查看顾客评价) +- 物流管理(查看商品物流信息) +- 库存管理(查看出入库记录、删除记录等) +- 个人信息管理(修改个人资料) + +## 管理员端 +- 数据概览/看板(订单量、销售额、热销排行、品类占比) +- 订单管理与风控 + - 实时监控订单状态 + - 核查/修改订单信息 + - 审核发货与跟踪物流 + - 排查违规订单 + - 审核退款(异常管控) +- 审核管理 + - 核查母婴用品基础信息 + - 审核商家注册信息/资质与经营能力 + - 监管商家违规行为 +- 用户管理(顾客/商家信息增删改查) +- 轮播图设置(活动页面轮播) +- 个人信息管理(修改个人资料) + +## 平台通用/支撑能力(文中隐含) +- 物流信息对接与查询(订单/商品物流状态展示) +- 角色与权限划分(顾客/商家/管理员三端功能隔离)