From f006ed4c89482a17f5cfa3c110934863fe9e5066 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E5=AD=90=E7=90=A6?= <95332614+wangziqi0409@users.noreply.github.com> Date: Wed, 14 Jan 2026 15:11:25 +0800 Subject: [PATCH] add --- .gitignore | 16 + README.md | 39 + backend/pom.xml | 76 + backend/sql/init.sql | 85 + .../java/com/car/CarRentalApplication.java | 11 + .../java/com/car/common/ApiException.java | 13 + .../main/java/com/car/common/ApiResponse.java | 26 + .../main/java/com/car/common/ErrorCode.java | 10 + .../car/common/GlobalExceptionHandler.java | 39 + .../java/com/car/config/SecurityConfig.java | 13 + .../java/com/car/config/StpInterfaceImpl.java | 32 + .../main/java/com/car/config/WebConfig.java | 17 + .../com/car/controller/AdminController.java | 107 ++ .../com/car/controller/AuthController.java | 44 + .../com/car/controller/CarController.java | 29 + .../com/car/controller/UserController.java | 109 ++ .../main/java/com/car/dto/BalanceRequest.java | 10 + .../main/java/com/car/dto/LoginRequest.java | 12 + .../java/com/car/dto/OrderCreateRequest.java | 16 + .../src/main/java/com/car/dto/PayRequest.java | 13 + .../java/com/car/dto/RealNameRequest.java | 14 + .../java/com/car/dto/RegisterRequest.java | 14 + backend/src/main/java/com/car/entity/Car.java | 25 + .../main/java/com/car/entity/Favorite.java | 13 + .../src/main/java/com/car/entity/Order.java | 25 + .../src/main/java/com/car/entity/Payment.java | 15 + .../src/main/java/com/car/entity/User.java | 24 + .../main/java/com/car/mapper/CarMapper.java | 20 + .../java/com/car/mapper/FavoriteMapper.java | 18 + .../main/java/com/car/mapper/OrderMapper.java | 27 + .../java/com/car/mapper/PaymentMapper.java | 14 + .../main/java/com/car/mapper/StatsMapper.java | 13 + .../main/java/com/car/mapper/UserMapper.java | 20 + .../main/java/com/car/service/CarService.java | 54 + .../java/com/car/service/FavoriteService.java | 41 + .../java/com/car/service/OrderService.java | 151 ++ .../java/com/car/service/StatsService.java | 23 + .../java/com/car/service/UserService.java | 126 ++ .../src/main/java/com/car/util/AuthUtil.java | 9 + backend/src/main/resources/application.yml | 26 + .../src/main/resources/mappers/CarMapper.xml | 71 + .../main/resources/mappers/FavoriteMapper.xml | 29 + .../main/resources/mappers/OrderMapper.xml | 81 + .../main/resources/mappers/PaymentMapper.xml | 23 + .../main/resources/mappers/StatsMapper.xml | 21 + .../src/main/resources/mappers/UserMapper.xml | 65 + frontend/.gitignore | 24 + frontend/README.md | 5 + frontend/index.html | 13 + frontend/package-lock.json | 1684 +++++++++++++++++ frontend/package.json | 23 + frontend/public/vite.svg | 1 + frontend/src/App.vue | 82 + frontend/src/api/http.js | 28 + frontend/src/assets/vue.svg | 1 + frontend/src/components/HelloWorld.vue | 43 + frontend/src/main.js | 13 + frontend/src/router/index.js | 46 + frontend/src/store/auth.js | 27 + frontend/src/style.css | 60 + frontend/src/views/CarDetail.vue | 97 + frontend/src/views/Favorites.vue | 45 + frontend/src/views/Home.vue | 58 + frontend/src/views/Login.vue | 43 + frontend/src/views/Orders.vue | 76 + frontend/src/views/Profile.vue | 91 + frontend/src/views/Register.vue | 46 + frontend/src/views/admin/AdminCars.vue | 133 ++ frontend/src/views/admin/AdminOrders.vue | 50 + frontend/src/views/admin/AdminStats.vue | 40 + frontend/src/views/admin/AdminUsers.vue | 76 + frontend/vite.config.js | 7 + 张天明-车智车辆租赁平台的设计与实现.md | 141 ++ 73 files changed, 4632 insertions(+) create mode 100644 .gitignore create mode 100644 backend/pom.xml create mode 100644 backend/sql/init.sql create mode 100644 backend/src/main/java/com/car/CarRentalApplication.java create mode 100644 backend/src/main/java/com/car/common/ApiException.java create mode 100644 backend/src/main/java/com/car/common/ApiResponse.java create mode 100644 backend/src/main/java/com/car/common/ErrorCode.java create mode 100644 backend/src/main/java/com/car/common/GlobalExceptionHandler.java create mode 100644 backend/src/main/java/com/car/config/SecurityConfig.java create mode 100644 backend/src/main/java/com/car/config/StpInterfaceImpl.java create mode 100644 backend/src/main/java/com/car/config/WebConfig.java create mode 100644 backend/src/main/java/com/car/controller/AdminController.java create mode 100644 backend/src/main/java/com/car/controller/AuthController.java create mode 100644 backend/src/main/java/com/car/controller/CarController.java create mode 100644 backend/src/main/java/com/car/controller/UserController.java create mode 100644 backend/src/main/java/com/car/dto/BalanceRequest.java create mode 100644 backend/src/main/java/com/car/dto/LoginRequest.java create mode 100644 backend/src/main/java/com/car/dto/OrderCreateRequest.java create mode 100644 backend/src/main/java/com/car/dto/PayRequest.java create mode 100644 backend/src/main/java/com/car/dto/RealNameRequest.java create mode 100644 backend/src/main/java/com/car/dto/RegisterRequest.java create mode 100644 backend/src/main/java/com/car/entity/Car.java create mode 100644 backend/src/main/java/com/car/entity/Favorite.java create mode 100644 backend/src/main/java/com/car/entity/Order.java create mode 100644 backend/src/main/java/com/car/entity/Payment.java create mode 100644 backend/src/main/java/com/car/entity/User.java create mode 100644 backend/src/main/java/com/car/mapper/CarMapper.java create mode 100644 backend/src/main/java/com/car/mapper/FavoriteMapper.java create mode 100644 backend/src/main/java/com/car/mapper/OrderMapper.java create mode 100644 backend/src/main/java/com/car/mapper/PaymentMapper.java create mode 100644 backend/src/main/java/com/car/mapper/StatsMapper.java create mode 100644 backend/src/main/java/com/car/mapper/UserMapper.java create mode 100644 backend/src/main/java/com/car/service/CarService.java create mode 100644 backend/src/main/java/com/car/service/FavoriteService.java create mode 100644 backend/src/main/java/com/car/service/OrderService.java create mode 100644 backend/src/main/java/com/car/service/StatsService.java create mode 100644 backend/src/main/java/com/car/service/UserService.java create mode 100644 backend/src/main/java/com/car/util/AuthUtil.java create mode 100644 backend/src/main/resources/application.yml create mode 100644 backend/src/main/resources/mappers/CarMapper.xml create mode 100644 backend/src/main/resources/mappers/FavoriteMapper.xml create mode 100644 backend/src/main/resources/mappers/OrderMapper.xml create mode 100644 backend/src/main/resources/mappers/PaymentMapper.xml create mode 100644 backend/src/main/resources/mappers/StatsMapper.xml create mode 100644 backend/src/main/resources/mappers/UserMapper.xml create mode 100644 frontend/.gitignore create mode 100644 frontend/README.md create mode 100644 frontend/index.html create mode 100644 frontend/package-lock.json create mode 100644 frontend/package.json create mode 100644 frontend/public/vite.svg create mode 100644 frontend/src/App.vue create mode 100644 frontend/src/api/http.js create mode 100644 frontend/src/assets/vue.svg create mode 100644 frontend/src/components/HelloWorld.vue create mode 100644 frontend/src/main.js create mode 100644 frontend/src/router/index.js create mode 100644 frontend/src/store/auth.js create mode 100644 frontend/src/style.css create mode 100644 frontend/src/views/CarDetail.vue create mode 100644 frontend/src/views/Favorites.vue create mode 100644 frontend/src/views/Home.vue create mode 100644 frontend/src/views/Login.vue create mode 100644 frontend/src/views/Orders.vue create mode 100644 frontend/src/views/Profile.vue create mode 100644 frontend/src/views/Register.vue create mode 100644 frontend/src/views/admin/AdminCars.vue create mode 100644 frontend/src/views/admin/AdminOrders.vue create mode 100644 frontend/src/views/admin/AdminStats.vue create mode 100644 frontend/src/views/admin/AdminUsers.vue create mode 100644 frontend/vite.config.js create mode 100644 张天明-车智车辆租赁平台的设计与实现.md diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..490f154 --- /dev/null +++ b/.gitignore @@ -0,0 +1,16 @@ +# Maven +/backend/target/ + +# Node +/frontend/node_modules/ +/frontend/dist/ + +# Logs +*.log + +# OS / IDE +.DS_Store +Thumbs.db +.idea/ +.vscode/ +*.iml diff --git a/README.md b/README.md index e69de29..1c41bc0 100644 --- a/README.md +++ b/README.md @@ -0,0 +1,39 @@ +车智车辆租赁平台(Vue + Spring Boot + MyBatis + MySQL + Arco Design Vue) + +## 功能模块 + +用户端: +- 登录注册、可租车辆、车辆详情、收藏夹、租车下单、归还车辆 +- 实名认证、余额支付(模拟)、个人中心、我的订单 + +管理员端: +- 管理员登录、车辆管理、特价车辆 +- 订单管理、实名审核、用户管理 +- 数据统计、财务报表 + +## 后端启动 + +1. 初始化数据库(MySQL) + - 执行 `backend/sql/init.sql` + - 默认管理员账号:`admin`,密码:`admin123` +2. 修改配置 + - `backend/src/main/resources/application.yml` 中的数据库连接信息 +3. 启动 + - 在 `backend` 目录执行:`mvn spring-boot:run` + +## 前端启动 + +1. 安装依赖 + - `cd frontend` + - `npm install` +2. 启动 + - `npm run dev` +3. 访问 + - 前端地址:`http://localhost:5173` + - 后端地址:`http://localhost:8080` + +## 说明 + +余额支付、实名认证为模拟流程: +- 余额可在个人中心“余额充值”完成 +- 实名认证提交后由管理员审核 diff --git a/backend/pom.xml b/backend/pom.xml new file mode 100644 index 0000000..6454b5e --- /dev/null +++ b/backend/pom.xml @@ -0,0 +1,76 @@ + + 4.0.0 + com.car + car-rental + 1.0.0 + car-rental + Car rental platform + + + org.springframework.boot + spring-boot-starter-parent + 3.2.5 + + + + + 17 + 3.0.3 + 1.38.0 + + + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-jdbc + + + org.mybatis.spring.boot + mybatis-spring-boot-starter + ${mybatis.version} + + + com.mysql + mysql-connector-j + runtime + + + cn.dev33 + sa-token-spring-boot3-starter + ${sa.token.version} + + + cn.dev33 + sa-token-jwt + ${sa.token.version} + + + org.springframework.boot + spring-boot-starter-validation + + + org.springframework.security + spring-security-crypto + + + org.projectlombok + lombok + true + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + diff --git a/backend/sql/init.sql b/backend/sql/init.sql new file mode 100644 index 0000000..e7bd922 --- /dev/null +++ b/backend/sql/init.sql @@ -0,0 +1,85 @@ +create database if not exists car_rental default character set utf8mb4 collate utf8mb4_general_ci; +use car_rental; + +create table if not exists users ( + id bigint primary key auto_increment, + username varchar(50) not null unique, + password varchar(255) not null, + phone varchar(50), + email varchar(100), + role varchar(20) not null, + status varchar(20) not null, + balance decimal(10,2) default 0, + real_name_status varchar(20) default 'NONE', + real_name varchar(50), + id_number varchar(30), + id_front varchar(255), + id_back varchar(255), + created_at datetime, + updated_at datetime +); + +create table if not exists cars ( + id bigint primary key auto_increment, + brand varchar(50), + model varchar(50), + plate_no varchar(50), + price_per_day decimal(10,2), + deposit decimal(10,2), + status varchar(20), + is_special tinyint(1) default 0, + images text, + description text, + seats int, + transmission varchar(20), + fuel_type varchar(20), + mileage int, + created_at datetime, + updated_at datetime +); + +create table if not exists favorites ( + id bigint primary key auto_increment, + user_id bigint not null, + car_id bigint not null, + created_at datetime, + unique key uk_user_car(user_id, car_id) +); + +create table if not exists orders ( + id bigint primary key auto_increment, + order_no varchar(64) not null unique, + user_id bigint not null, + car_id bigint not null, + start_date date, + end_date date, + days int, + price_per_day decimal(10,2), + deposit decimal(10,2), + total_amount decimal(10,2), + status varchar(20), + pay_type varchar(20), + paid_at datetime, + created_at datetime, + updated_at datetime +); + +create table if not exists payments ( + id bigint primary key auto_increment, + order_id bigint not null, + user_id bigint not null, + amount decimal(10,2), + type varchar(20), + created_at datetime +); + +insert into users(username, password, phone, email, role, status, balance, real_name_status, created_at, updated_at) +values ('admin', '$2a$10$7hErhcmS8xj6QcGbe0yE0eDBn5OQWw4tGxqVylxYxe3CxbJc88x76', '13800000000', 'admin@example.com', 'ADMIN', 'ACTIVE', 0, 'NONE', now(), now()) +on duplicate key update username = username; + +insert into cars(brand, model, plate_no, price_per_day, deposit, status, is_special, seats, transmission, fuel_type, mileage, description, created_at, updated_at) +values +('丰田', '卡罗拉', '粤A12345', 200, 1000, 'AVAILABLE', 0, 5, 'AT', '汽油', 32000, '经济实用,适合通勤', now(), now()), +('特斯拉', 'Model 3', '粤B54321', 500, 2000, 'AVAILABLE', 1, 5, 'AT', '电动', 18000, '电动轿跑,舒适安静', now(), now()), +('本田', 'CR-V', '粤C67890', 350, 1500, 'AVAILABLE', 0, 5, 'AT', '汽油', 40000, '空间大,适合家庭出行', now(), now()) +on duplicate key update plate_no = plate_no; diff --git a/backend/src/main/java/com/car/CarRentalApplication.java b/backend/src/main/java/com/car/CarRentalApplication.java new file mode 100644 index 0000000..7a7db62 --- /dev/null +++ b/backend/src/main/java/com/car/CarRentalApplication.java @@ -0,0 +1,11 @@ +package com.car; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class CarRentalApplication { + public static void main(String[] args) { + SpringApplication.run(CarRentalApplication.class, args); + } +} diff --git a/backend/src/main/java/com/car/common/ApiException.java b/backend/src/main/java/com/car/common/ApiException.java new file mode 100644 index 0000000..dc57ede --- /dev/null +++ b/backend/src/main/java/com/car/common/ApiException.java @@ -0,0 +1,13 @@ +package com.car.common; + +import lombok.Getter; + +@Getter +public class ApiException extends RuntimeException { + private final int code; + + public ApiException(int code, String message) { + super(message); + this.code = code; + } +} diff --git a/backend/src/main/java/com/car/common/ApiResponse.java b/backend/src/main/java/com/car/common/ApiResponse.java new file mode 100644 index 0000000..9bd1e87 --- /dev/null +++ b/backend/src/main/java/com/car/common/ApiResponse.java @@ -0,0 +1,26 @@ +package com.car.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 error(int code, String message) { + return new ApiResponse<>(code, message, null); + } +} diff --git a/backend/src/main/java/com/car/common/ErrorCode.java b/backend/src/main/java/com/car/common/ErrorCode.java new file mode 100644 index 0000000..13be7ee --- /dev/null +++ b/backend/src/main/java/com/car/common/ErrorCode.java @@ -0,0 +1,10 @@ +package com.car.common; + +public class ErrorCode { + public static final int BAD_REQUEST = 400; + public static final int UNAUTHORIZED = 401; + public static final int FORBIDDEN = 403; + public static final int NOT_FOUND = 404; + public static final int CONFLICT = 409; + public static final int SERVER_ERROR = 500; +} diff --git a/backend/src/main/java/com/car/common/GlobalExceptionHandler.java b/backend/src/main/java/com/car/common/GlobalExceptionHandler.java new file mode 100644 index 0000000..4581066 --- /dev/null +++ b/backend/src/main/java/com/car/common/GlobalExceptionHandler.java @@ -0,0 +1,39 @@ +package com.car.common; + +import cn.dev33.satoken.exception.NotLoginException; +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(ApiException.class) + public ApiResponse handleApiException(ApiException ex) { + return ApiResponse.error(ex.getCode(), ex.getMessage()); + } + + @ExceptionHandler(NotLoginException.class) + public ApiResponse handleNotLogin(NotLoginException ex) { + return ApiResponse.error(ErrorCode.UNAUTHORIZED, "请先登录"); + } + + @ExceptionHandler(MethodArgumentNotValidException.class) + public ApiResponse handleValid(MethodArgumentNotValidException ex) { + String msg = ex.getBindingResult().getAllErrors().isEmpty() + ? "参数错误" + : ex.getBindingResult().getAllErrors().get(0).getDefaultMessage(); + return ApiResponse.error(ErrorCode.BAD_REQUEST, msg); + } + + @ExceptionHandler(ConstraintViolationException.class) + public ApiResponse handleConstraint(ConstraintViolationException ex) { + return ApiResponse.error(ErrorCode.BAD_REQUEST, ex.getMessage()); + } + + @ExceptionHandler(Exception.class) + public ApiResponse handleGeneric(Exception ex) { + return ApiResponse.error(ErrorCode.SERVER_ERROR, ex.getMessage()); + } +} diff --git a/backend/src/main/java/com/car/config/SecurityConfig.java b/backend/src/main/java/com/car/config/SecurityConfig.java new file mode 100644 index 0000000..8a81f18 --- /dev/null +++ b/backend/src/main/java/com/car/config/SecurityConfig.java @@ -0,0 +1,13 @@ +package com.car.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; + +@Configuration +public class SecurityConfig { + @Bean + public BCryptPasswordEncoder passwordEncoder() { + return new BCryptPasswordEncoder(); + } +} diff --git a/backend/src/main/java/com/car/config/StpInterfaceImpl.java b/backend/src/main/java/com/car/config/StpInterfaceImpl.java new file mode 100644 index 0000000..be47699 --- /dev/null +++ b/backend/src/main/java/com/car/config/StpInterfaceImpl.java @@ -0,0 +1,32 @@ +package com.car.config; + +import cn.dev33.satoken.stp.StpInterface; +import com.car.entity.User; +import com.car.mapper.UserMapper; +import org.springframework.stereotype.Component; + +import java.util.Collections; +import java.util.List; + +@Component +public class StpInterfaceImpl implements StpInterface { + private final UserMapper userMapper; + + public StpInterfaceImpl(UserMapper userMapper) { + this.userMapper = userMapper; + } + + @Override + public List getPermissionList(Object loginId, String loginType) { + return Collections.emptyList(); + } + + @Override + public List getRoleList(Object loginId, String loginType) { + User user = userMapper.findById(Long.valueOf(loginId.toString())); + if (user == null || user.getRole() == null) { + return Collections.emptyList(); + } + return Collections.singletonList(user.getRole()); + } +} diff --git a/backend/src/main/java/com/car/config/WebConfig.java b/backend/src/main/java/com/car/config/WebConfig.java new file mode 100644 index 0000000..80f501c --- /dev/null +++ b/backend/src/main/java/com/car/config/WebConfig.java @@ -0,0 +1,17 @@ +package com.car.config; + +import org.springframework.context.annotation.Configuration; +import org.springframework.web.servlet.config.annotation.CorsRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +@Configuration +public class WebConfig implements WebMvcConfigurer { + @Override + public void addCorsMappings(CorsRegistry registry) { + registry.addMapping("/**") + .allowedOriginPatterns("*") + .allowedMethods("*") + .allowedHeaders("*") + .allowCredentials(true); + } +} diff --git a/backend/src/main/java/com/car/controller/AdminController.java b/backend/src/main/java/com/car/controller/AdminController.java new file mode 100644 index 0000000..12f8a49 --- /dev/null +++ b/backend/src/main/java/com/car/controller/AdminController.java @@ -0,0 +1,107 @@ +package com.car.controller; + +import cn.dev33.satoken.stp.StpUtil; +import com.car.common.ApiResponse; +import com.car.common.ApiException; +import com.car.common.ErrorCode; +import com.car.entity.Car; +import com.car.entity.Order; +import com.car.entity.User; +import com.car.service.CarService; +import com.car.service.OrderService; +import com.car.service.StatsService; +import com.car.service.UserService; +import org.springframework.web.bind.annotation.*; + +import java.util.List; +import java.util.Map; + +@RestController +@RequestMapping("/api/admin") +public class AdminController { + private final UserService userService; + private final CarService carService; + private final OrderService orderService; + private final StatsService statsService; + + public AdminController(UserService userService, CarService carService, OrderService orderService, StatsService statsService) { + this.userService = userService; + this.carService = carService; + this.orderService = orderService; + this.statsService = statsService; + } + + private void checkAdmin() { + if (!StpUtil.hasRole("ADMIN")) { + throw new ApiException(ErrorCode.FORBIDDEN, "需要管理员权限"); + } + } + + @GetMapping("/users") + public ApiResponse> listUsers(@RequestParam(required = false) String keyword) { + checkAdmin(); + return ApiResponse.ok(userService.listUsers(keyword)); + } + + @PostMapping("/users/{userId}/status") + public ApiResponse changeStatus(@PathVariable Long userId, @RequestParam String status) { + checkAdmin(); + return ApiResponse.ok(userService.changeStatus(userId, status)); + } + + @PostMapping("/real-name/{userId}/approve") + public ApiResponse approve(@PathVariable Long userId) { + checkAdmin(); + return ApiResponse.ok(userService.reviewRealName(userId, true)); + } + + @PostMapping("/real-name/{userId}/reject") + public ApiResponse reject(@PathVariable Long userId) { + checkAdmin(); + return ApiResponse.ok(userService.reviewRealName(userId, false)); + } + + @GetMapping("/cars") + public ApiResponse> listCars(@RequestParam(required = false) String keyword, + @RequestParam(required = false) Boolean isSpecial) { + checkAdmin(); + return ApiResponse.ok(carService.list(keyword, isSpecial)); + } + + @PostMapping("/cars") + public ApiResponse createCar(@RequestBody Car car) { + checkAdmin(); + return ApiResponse.ok(carService.create(car)); + } + + @PutMapping("/cars") + public ApiResponse updateCar(@RequestBody Car car) { + checkAdmin(); + return ApiResponse.ok(carService.update(car)); + } + + @DeleteMapping("/cars/{id}") + public ApiResponse deleteCar(@PathVariable Long id) { + checkAdmin(); + carService.delete(id); + return ApiResponse.ok(); + } + + @GetMapping("/orders") + public ApiResponse> listOrders(@RequestParam(required = false) String status) { + checkAdmin(); + return ApiResponse.ok(orderService.listAll(status)); + } + + @GetMapping("/stats/orders") + public ApiResponse> orderStats() { + checkAdmin(); + return ApiResponse.ok(statsService.orderStats()); + } + + @GetMapping("/stats/finance") + public ApiResponse> financeStats() { + checkAdmin(); + return ApiResponse.ok(statsService.financeStats()); + } +} diff --git a/backend/src/main/java/com/car/controller/AuthController.java b/backend/src/main/java/com/car/controller/AuthController.java new file mode 100644 index 0000000..2f39348 --- /dev/null +++ b/backend/src/main/java/com/car/controller/AuthController.java @@ -0,0 +1,44 @@ +package com.car.controller; + +import cn.dev33.satoken.stp.StpUtil; +import com.car.common.ApiResponse; +import com.car.dto.LoginRequest; +import com.car.dto.RegisterRequest; +import com.car.entity.User; +import com.car.service.UserService; +import jakarta.validation.Valid; +import org.springframework.web.bind.annotation.*; + +import java.util.HashMap; +import java.util.Map; + +@RestController +@RequestMapping("/api/auth") +public class AuthController { + private final UserService userService; + + public AuthController(UserService userService) { + this.userService = userService; + } + + @PostMapping("/register") + public ApiResponse register(@Valid @RequestBody RegisterRequest request) { + return ApiResponse.ok(userService.register(request)); + } + + @PostMapping("/login") + public ApiResponse> login(@Valid @RequestBody LoginRequest request) { + User user = userService.login(request); + StpUtil.login(user.getId()); + Map data = new HashMap<>(); + data.put("token", StpUtil.getTokenValue()); + data.put("role", user.getRole()); + return ApiResponse.ok(data); + } + + @PostMapping("/logout") + public ApiResponse logout() { + StpUtil.logout(); + return ApiResponse.ok(); + } +} diff --git a/backend/src/main/java/com/car/controller/CarController.java b/backend/src/main/java/com/car/controller/CarController.java new file mode 100644 index 0000000..a19ba46 --- /dev/null +++ b/backend/src/main/java/com/car/controller/CarController.java @@ -0,0 +1,29 @@ +package com.car.controller; + +import com.car.common.ApiResponse; +import com.car.entity.Car; +import com.car.service.CarService; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +@RestController +@RequestMapping("/api/cars") +public class CarController { + private final CarService carService; + + public CarController(CarService carService) { + this.carService = carService; + } + + @GetMapping + public ApiResponse> list(@RequestParam(required = false) String keyword, + @RequestParam(required = false) Boolean isSpecial) { + return ApiResponse.ok(carService.list(keyword, isSpecial)); + } + + @GetMapping("/{id}") + public ApiResponse detail(@PathVariable Long id) { + return ApiResponse.ok(carService.get(id)); + } +} diff --git a/backend/src/main/java/com/car/controller/UserController.java b/backend/src/main/java/com/car/controller/UserController.java new file mode 100644 index 0000000..a9da282 --- /dev/null +++ b/backend/src/main/java/com/car/controller/UserController.java @@ -0,0 +1,109 @@ +package com.car.controller; + +import cn.dev33.satoken.stp.StpUtil; +import com.car.common.ApiResponse; +import com.car.dto.BalanceRequest; +import com.car.dto.OrderCreateRequest; +import com.car.dto.PayRequest; +import com.car.dto.RealNameRequest; +import com.car.entity.Favorite; +import com.car.entity.Order; +import com.car.entity.User; +import com.car.service.FavoriteService; +import com.car.service.OrderService; +import com.car.service.UserService; +import com.car.util.AuthUtil; +import jakarta.validation.Valid; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +@RestController +@RequestMapping("/api/user") +public class UserController { + private final UserService userService; + private final FavoriteService favoriteService; + private final OrderService orderService; + + public UserController(UserService userService, FavoriteService favoriteService, OrderService orderService) { + this.userService = userService; + this.favoriteService = favoriteService; + this.orderService = orderService; + } + + @GetMapping("/me") + public ApiResponse me() { + Long userId = AuthUtil.getUserId(); + User user = userService.getById(userId); + if (user != null) { + user.setPassword(null); + } + return ApiResponse.ok(user); + } + + @PutMapping("/profile") + public ApiResponse updateProfile(@RequestBody User user) { + Long userId = AuthUtil.getUserId(); + user.setId(userId); + return ApiResponse.ok(userService.updateProfile(user)); + } + + @PostMapping("/real-name") + public ApiResponse realName(@Valid @RequestBody RealNameRequest request) { + Long userId = AuthUtil.getUserId(); + return ApiResponse.ok(userService.submitRealName(userId, request)); + } + + @PostMapping("/balance") + public ApiResponse addBalance(@Valid @RequestBody BalanceRequest request) { + Long userId = AuthUtil.getUserId(); + return ApiResponse.ok(userService.addBalance(userId, request)); + } + + @PostMapping("/favorite/{carId}") + public ApiResponse addFavorite(@PathVariable Long carId) { + favoriteService.add(AuthUtil.getUserId(), carId); + return ApiResponse.ok(); + } + + @DeleteMapping("/favorite/{carId}") + public ApiResponse removeFavorite(@PathVariable Long carId) { + favoriteService.remove(AuthUtil.getUserId(), carId); + return ApiResponse.ok(); + } + + @GetMapping("/favorite") + public ApiResponse> listFavorites() { + return ApiResponse.ok(favoriteService.list(AuthUtil.getUserId())); + } + + @PostMapping("/order") + public ApiResponse createOrder(@Valid @RequestBody OrderCreateRequest request) { + return ApiResponse.ok(orderService.create(AuthUtil.getUserId(), request)); + } + + @PostMapping("/order/pay") + public ApiResponse pay(@Valid @RequestBody PayRequest request) { + return ApiResponse.ok(orderService.pay(AuthUtil.getUserId(), request)); + } + + @PostMapping("/order/{orderId}/cancel") + public ApiResponse cancel(@PathVariable Long orderId) { + return ApiResponse.ok(orderService.cancel(AuthUtil.getUserId(), orderId)); + } + + @PostMapping("/order/{orderId}/return") + public ApiResponse returnCar(@PathVariable Long orderId) { + return ApiResponse.ok(orderService.returnCar(AuthUtil.getUserId(), orderId)); + } + + @GetMapping("/order") + public ApiResponse> myOrders(@RequestParam(required = false) String status) { + return ApiResponse.ok(orderService.listByUser(AuthUtil.getUserId(), status)); + } + + @GetMapping("/check-login") + public ApiResponse checkLogin() { + return ApiResponse.ok(StpUtil.isLogin()); + } +} diff --git a/backend/src/main/java/com/car/dto/BalanceRequest.java b/backend/src/main/java/com/car/dto/BalanceRequest.java new file mode 100644 index 0000000..96cec91 --- /dev/null +++ b/backend/src/main/java/com/car/dto/BalanceRequest.java @@ -0,0 +1,10 @@ +package com.car.dto; + +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +@Data +public class BalanceRequest { + @NotNull(message = "金额不能为空") + private Double amount; +} diff --git a/backend/src/main/java/com/car/dto/LoginRequest.java b/backend/src/main/java/com/car/dto/LoginRequest.java new file mode 100644 index 0000000..36810cc --- /dev/null +++ b/backend/src/main/java/com/car/dto/LoginRequest.java @@ -0,0 +1,12 @@ +package com.car.dto; + +import jakarta.validation.constraints.NotBlank; +import lombok.Data; + +@Data +public class LoginRequest { + @NotBlank(message = "用户名不能为空") + private String username; + @NotBlank(message = "密码不能为空") + private String password; +} diff --git a/backend/src/main/java/com/car/dto/OrderCreateRequest.java b/backend/src/main/java/com/car/dto/OrderCreateRequest.java new file mode 100644 index 0000000..3e8e3ef --- /dev/null +++ b/backend/src/main/java/com/car/dto/OrderCreateRequest.java @@ -0,0 +1,16 @@ +package com.car.dto; + +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +import java.time.LocalDate; + +@Data +public class OrderCreateRequest { + @NotNull(message = "车辆不能为空") + private Long carId; + @NotNull(message = "开始日期不能为空") + private LocalDate startDate; + @NotNull(message = "结束日期不能为空") + private LocalDate endDate; +} diff --git a/backend/src/main/java/com/car/dto/PayRequest.java b/backend/src/main/java/com/car/dto/PayRequest.java new file mode 100644 index 0000000..9b7ad49 --- /dev/null +++ b/backend/src/main/java/com/car/dto/PayRequest.java @@ -0,0 +1,13 @@ +package com.car.dto; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +@Data +public class PayRequest { + @NotNull(message = "订单不能为空") + private Long orderId; + @NotBlank(message = "支付方式不能为空") + private String payType; +} diff --git a/backend/src/main/java/com/car/dto/RealNameRequest.java b/backend/src/main/java/com/car/dto/RealNameRequest.java new file mode 100644 index 0000000..a5be2b7 --- /dev/null +++ b/backend/src/main/java/com/car/dto/RealNameRequest.java @@ -0,0 +1,14 @@ +package com.car.dto; + +import jakarta.validation.constraints.NotBlank; +import lombok.Data; + +@Data +public class RealNameRequest { + @NotBlank(message = "真实姓名不能为空") + private String realName; + @NotBlank(message = "身份证号不能为空") + private String idNumber; + private String idFront; + private String idBack; +} diff --git a/backend/src/main/java/com/car/dto/RegisterRequest.java b/backend/src/main/java/com/car/dto/RegisterRequest.java new file mode 100644 index 0000000..c21d4e5 --- /dev/null +++ b/backend/src/main/java/com/car/dto/RegisterRequest.java @@ -0,0 +1,14 @@ +package com.car.dto; + +import jakarta.validation.constraints.NotBlank; +import lombok.Data; + +@Data +public class RegisterRequest { + @NotBlank(message = "用户名不能为空") + private String username; + @NotBlank(message = "密码不能为空") + private String password; + private String phone; + private String email; +} diff --git a/backend/src/main/java/com/car/entity/Car.java b/backend/src/main/java/com/car/entity/Car.java new file mode 100644 index 0000000..3ea5434 --- /dev/null +++ b/backend/src/main/java/com/car/entity/Car.java @@ -0,0 +1,25 @@ +package com.car.entity; + +import lombok.Data; + +import java.time.LocalDateTime; + +@Data +public class Car { + private Long id; + private String brand; + private String model; + private String plateNo; + private Double pricePerDay; + private Double deposit; + private String status; + private Boolean isSpecial; + private String images; + private String description; + private Integer seats; + private String transmission; + private String fuelType; + private Integer mileage; + private LocalDateTime createdAt; + private LocalDateTime updatedAt; +} diff --git a/backend/src/main/java/com/car/entity/Favorite.java b/backend/src/main/java/com/car/entity/Favorite.java new file mode 100644 index 0000000..b956991 --- /dev/null +++ b/backend/src/main/java/com/car/entity/Favorite.java @@ -0,0 +1,13 @@ +package com.car.entity; + +import lombok.Data; + +import java.time.LocalDateTime; + +@Data +public class Favorite { + private Long id; + private Long userId; + private Long carId; + private LocalDateTime createdAt; +} diff --git a/backend/src/main/java/com/car/entity/Order.java b/backend/src/main/java/com/car/entity/Order.java new file mode 100644 index 0000000..538aac1 --- /dev/null +++ b/backend/src/main/java/com/car/entity/Order.java @@ -0,0 +1,25 @@ +package com.car.entity; + +import lombok.Data; + +import java.time.LocalDate; +import java.time.LocalDateTime; + +@Data +public class Order { + private Long id; + private String orderNo; + private Long userId; + private Long carId; + private LocalDate startDate; + private LocalDate endDate; + private Integer days; + private Double pricePerDay; + private Double deposit; + private Double totalAmount; + private String status; + private String payType; + private LocalDateTime paidAt; + private LocalDateTime createdAt; + private LocalDateTime updatedAt; +} diff --git a/backend/src/main/java/com/car/entity/Payment.java b/backend/src/main/java/com/car/entity/Payment.java new file mode 100644 index 0000000..8446adb --- /dev/null +++ b/backend/src/main/java/com/car/entity/Payment.java @@ -0,0 +1,15 @@ +package com.car.entity; + +import lombok.Data; + +import java.time.LocalDateTime; + +@Data +public class Payment { + private Long id; + private Long orderId; + private Long userId; + private Double amount; + private String type; + private LocalDateTime createdAt; +} diff --git a/backend/src/main/java/com/car/entity/User.java b/backend/src/main/java/com/car/entity/User.java new file mode 100644 index 0000000..cced845 --- /dev/null +++ b/backend/src/main/java/com/car/entity/User.java @@ -0,0 +1,24 @@ +package com.car.entity; + +import lombok.Data; + +import java.time.LocalDateTime; + +@Data +public class User { + private Long id; + private String username; + private String password; + private String phone; + private String email; + private String role; + private String status; + private Double balance; + private String realNameStatus; + private String realName; + private String idNumber; + private String idFront; + private String idBack; + private LocalDateTime createdAt; + private LocalDateTime updatedAt; +} diff --git a/backend/src/main/java/com/car/mapper/CarMapper.java b/backend/src/main/java/com/car/mapper/CarMapper.java new file mode 100644 index 0000000..33d6012 --- /dev/null +++ b/backend/src/main/java/com/car/mapper/CarMapper.java @@ -0,0 +1,20 @@ +package com.car.mapper; + +import com.car.entity.Car; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +@Mapper +public interface CarMapper { + Car findById(@Param("id") Long id); + + List findAll(@Param("keyword") String keyword, @Param("isSpecial") Boolean isSpecial); + + int insert(Car car); + + int update(Car car); + + int delete(@Param("id") Long id); +} diff --git a/backend/src/main/java/com/car/mapper/FavoriteMapper.java b/backend/src/main/java/com/car/mapper/FavoriteMapper.java new file mode 100644 index 0000000..da24057 --- /dev/null +++ b/backend/src/main/java/com/car/mapper/FavoriteMapper.java @@ -0,0 +1,18 @@ +package com.car.mapper; + +import com.car.entity.Favorite; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +@Mapper +public interface FavoriteMapper { + Favorite findByUserAndCar(@Param("userId") Long userId, @Param("carId") Long carId); + + int insert(Favorite favorite); + + int delete(@Param("id") Long id); + + List findByUserId(@Param("userId") Long userId); +} diff --git a/backend/src/main/java/com/car/mapper/OrderMapper.java b/backend/src/main/java/com/car/mapper/OrderMapper.java new file mode 100644 index 0000000..5831305 --- /dev/null +++ b/backend/src/main/java/com/car/mapper/OrderMapper.java @@ -0,0 +1,27 @@ +package com.car.mapper; + +import com.car.entity.Order; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; + +import java.time.LocalDate; +import java.util.List; + +@Mapper +public interface OrderMapper { + Order findById(@Param("id") Long id); + + Order findByOrderNo(@Param("orderNo") String orderNo); + + int insert(Order order); + + int update(Order order); + + List findByUserId(@Param("userId") Long userId, @Param("status") String status); + + List findAll(@Param("status") String status); + + int countOverlap(@Param("carId") Long carId, + @Param("startDate") LocalDate startDate, + @Param("endDate") LocalDate endDate); +} diff --git a/backend/src/main/java/com/car/mapper/PaymentMapper.java b/backend/src/main/java/com/car/mapper/PaymentMapper.java new file mode 100644 index 0000000..6b7e2b6 --- /dev/null +++ b/backend/src/main/java/com/car/mapper/PaymentMapper.java @@ -0,0 +1,14 @@ +package com.car.mapper; + +import com.car.entity.Payment; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +@Mapper +public interface PaymentMapper { + int insert(Payment payment); + + List findByUserId(@Param("userId") Long userId); +} diff --git a/backend/src/main/java/com/car/mapper/StatsMapper.java b/backend/src/main/java/com/car/mapper/StatsMapper.java new file mode 100644 index 0000000..9b2e305 --- /dev/null +++ b/backend/src/main/java/com/car/mapper/StatsMapper.java @@ -0,0 +1,13 @@ +package com.car.mapper; + +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; + +import java.util.Map; + +@Mapper +public interface StatsMapper { + Map orderStats(@Param("status") String status); + + Map financeStats(); +} diff --git a/backend/src/main/java/com/car/mapper/UserMapper.java b/backend/src/main/java/com/car/mapper/UserMapper.java new file mode 100644 index 0000000..67b8df3 --- /dev/null +++ b/backend/src/main/java/com/car/mapper/UserMapper.java @@ -0,0 +1,20 @@ +package com.car.mapper; + +import com.car.entity.User; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +@Mapper +public interface UserMapper { + User findByUsername(@Param("username") String username); + + User findById(@Param("id") Long id); + + int insert(User user); + + int update(User user); + + List findAll(@Param("keyword") String keyword); +} diff --git a/backend/src/main/java/com/car/service/CarService.java b/backend/src/main/java/com/car/service/CarService.java new file mode 100644 index 0000000..dfe1809 --- /dev/null +++ b/backend/src/main/java/com/car/service/CarService.java @@ -0,0 +1,54 @@ +package com.car.service; + +import com.car.common.ApiException; +import com.car.common.ErrorCode; +import com.car.entity.Car; +import com.car.mapper.CarMapper; +import org.springframework.stereotype.Service; + +import java.util.List; + +@Service +public class CarService { + private final CarMapper carMapper; + + public CarService(CarMapper carMapper) { + this.carMapper = carMapper; + } + + public List list(String keyword, Boolean isSpecial) { + return carMapper.findAll(keyword, isSpecial); + } + + public Car get(Long id) { + Car car = carMapper.findById(id); + if (car == null) { + throw new ApiException(ErrorCode.NOT_FOUND, "车辆不存在"); + } + return car; + } + + public Car create(Car car) { + if (car.getStatus() == null) { + car.setStatus("AVAILABLE"); + } + if (car.getIsSpecial() == null) { + car.setIsSpecial(false); + } + carMapper.insert(car); + return car; + } + + public Car update(Car car) { + Car db = carMapper.findById(car.getId()); + if (db == null) { + throw new ApiException(ErrorCode.NOT_FOUND, "车辆不存在"); + } + carMapper.update(car); + return carMapper.findById(car.getId()); + } + + public void delete(Long id) { + carMapper.delete(id); + } +} diff --git a/backend/src/main/java/com/car/service/FavoriteService.java b/backend/src/main/java/com/car/service/FavoriteService.java new file mode 100644 index 0000000..52a1616 --- /dev/null +++ b/backend/src/main/java/com/car/service/FavoriteService.java @@ -0,0 +1,41 @@ +package com.car.service; + +import com.car.common.ApiException; +import com.car.common.ErrorCode; +import com.car.entity.Favorite; +import com.car.mapper.FavoriteMapper; +import org.springframework.stereotype.Service; + +import java.util.List; + +@Service +public class FavoriteService { + private final FavoriteMapper favoriteMapper; + + public FavoriteService(FavoriteMapper favoriteMapper) { + this.favoriteMapper = favoriteMapper; + } + + public void add(Long userId, Long carId) { + Favorite existing = favoriteMapper.findByUserAndCar(userId, carId); + if (existing != null) { + throw new ApiException(ErrorCode.CONFLICT, "已收藏"); + } + Favorite favorite = new Favorite(); + favorite.setUserId(userId); + favorite.setCarId(carId); + favoriteMapper.insert(favorite); + } + + public void remove(Long userId, Long carId) { + Favorite existing = favoriteMapper.findByUserAndCar(userId, carId); + if (existing == null) { + throw new ApiException(ErrorCode.NOT_FOUND, "收藏不存在"); + } + favoriteMapper.delete(existing.getId()); + } + + public List list(Long userId) { + return favoriteMapper.findByUserId(userId); + } +} diff --git a/backend/src/main/java/com/car/service/OrderService.java b/backend/src/main/java/com/car/service/OrderService.java new file mode 100644 index 0000000..f4d9c8b --- /dev/null +++ b/backend/src/main/java/com/car/service/OrderService.java @@ -0,0 +1,151 @@ +package com.car.service; + +import com.car.common.ApiException; +import com.car.common.ErrorCode; +import com.car.dto.OrderCreateRequest; +import com.car.dto.PayRequest; +import com.car.entity.Car; +import com.car.entity.Order; +import com.car.entity.Payment; +import com.car.entity.User; +import com.car.mapper.CarMapper; +import com.car.mapper.OrderMapper; +import com.car.mapper.PaymentMapper; +import com.car.mapper.UserMapper; +import org.springframework.stereotype.Service; + +import java.time.LocalDate; +import java.time.temporal.ChronoUnit; +import java.util.List; +import java.util.UUID; + +@Service +public class OrderService { + private final OrderMapper orderMapper; + private final CarMapper carMapper; + private final UserMapper userMapper; + private final PaymentMapper paymentMapper; + + public OrderService(OrderMapper orderMapper, CarMapper carMapper, UserMapper userMapper, PaymentMapper paymentMapper) { + this.orderMapper = orderMapper; + this.carMapper = carMapper; + this.userMapper = userMapper; + this.paymentMapper = paymentMapper; + } + + public Order create(Long userId, OrderCreateRequest request) { + Car car = carMapper.findById(request.getCarId()); + if (car == null) { + throw new ApiException(ErrorCode.NOT_FOUND, "车辆不存在"); + } + if (!"AVAILABLE".equals(car.getStatus())) { + throw new ApiException(ErrorCode.CONFLICT, "车辆不可租"); + } + User user = userMapper.findById(userId); + if (user == null) { + throw new ApiException(ErrorCode.NOT_FOUND, "用户不存在"); + } + if (!"APPROVED".equals(user.getRealNameStatus())) { + throw new ApiException(ErrorCode.FORBIDDEN, "请先完成实名认证审核"); + } + LocalDate start = request.getStartDate(); + LocalDate end = request.getEndDate(); + if (end.isBefore(start)) { + throw new ApiException(ErrorCode.BAD_REQUEST, "结束日期不能早于开始日期"); + } + int overlap = orderMapper.countOverlap(request.getCarId(), start, end); + if (overlap > 0) { + throw new ApiException(ErrorCode.CONFLICT, "该日期已被预订"); + } + long days = ChronoUnit.DAYS.between(start, end) + 1; + Order order = new Order(); + order.setOrderNo("ORD" + UUID.randomUUID().toString().replace("-", "")); + order.setUserId(userId); + order.setCarId(request.getCarId()); + order.setStartDate(start); + order.setEndDate(end); + order.setDays((int) days); + order.setPricePerDay(car.getPricePerDay()); + order.setDeposit(car.getDeposit()); + order.setTotalAmount(car.getPricePerDay() * days + car.getDeposit()); + order.setStatus("PENDING_PAY"); + orderMapper.insert(order); + return orderMapper.findById(order.getId()); + } + + public Order pay(Long userId, PayRequest request) { + Order order = orderMapper.findById(request.getOrderId()); + if (order == null || !order.getUserId().equals(userId)) { + throw new ApiException(ErrorCode.NOT_FOUND, "订单不存在"); + } + if (!"PENDING_PAY".equals(order.getStatus())) { + throw new ApiException(ErrorCode.CONFLICT, "订单无法支付"); + } + if (!"BALANCE".equalsIgnoreCase(request.getPayType())) { + throw new ApiException(ErrorCode.BAD_REQUEST, "仅支持余额支付"); + } + User user = userMapper.findById(userId); + if (user.getBalance() < order.getTotalAmount()) { + throw new ApiException(ErrorCode.CONFLICT, "余额不足"); + } + user.setBalance(user.getBalance() - order.getTotalAmount()); + userMapper.update(user); + + Car car = carMapper.findById(order.getCarId()); + car.setStatus("RENTED"); + carMapper.update(car); + + order.setStatus("RENTING"); + order.setPayType("BALANCE"); + order.setPaidAt(java.time.LocalDateTime.now()); + orderMapper.update(order); + + Payment payment = new Payment(); + payment.setOrderId(order.getId()); + payment.setUserId(userId); + payment.setAmount(order.getTotalAmount()); + payment.setType("BALANCE"); + paymentMapper.insert(payment); + + return orderMapper.findById(order.getId()); + } + + public Order cancel(Long userId, Long orderId) { + Order order = orderMapper.findById(orderId); + if (order == null || !order.getUserId().equals(userId)) { + throw new ApiException(ErrorCode.NOT_FOUND, "订单不存在"); + } + if (!"PENDING_PAY".equals(order.getStatus())) { + throw new ApiException(ErrorCode.CONFLICT, "订单无法取消"); + } + order.setStatus("CANCELLED"); + orderMapper.update(order); + return orderMapper.findById(orderId); + } + + public Order returnCar(Long userId, Long orderId) { + Order order = orderMapper.findById(orderId); + if (order == null || !order.getUserId().equals(userId)) { + throw new ApiException(ErrorCode.NOT_FOUND, "订单不存在"); + } + if (!"RENTING".equals(order.getStatus())) { + throw new ApiException(ErrorCode.CONFLICT, "订单无法归还"); + } + order.setStatus("RETURNED"); + orderMapper.update(order); + + Car car = carMapper.findById(order.getCarId()); + car.setStatus("AVAILABLE"); + carMapper.update(car); + + return orderMapper.findById(orderId); + } + + public List listByUser(Long userId, String status) { + return orderMapper.findByUserId(userId, status); + } + + public List listAll(String status) { + return orderMapper.findAll(status); + } +} diff --git a/backend/src/main/java/com/car/service/StatsService.java b/backend/src/main/java/com/car/service/StatsService.java new file mode 100644 index 0000000..5d9861b --- /dev/null +++ b/backend/src/main/java/com/car/service/StatsService.java @@ -0,0 +1,23 @@ +package com.car.service; + +import com.car.mapper.StatsMapper; +import org.springframework.stereotype.Service; + +import java.util.Map; + +@Service +public class StatsService { + private final StatsMapper statsMapper; + + public StatsService(StatsMapper statsMapper) { + this.statsMapper = statsMapper; + } + + public Map orderStats() { + return statsMapper.orderStats(null); + } + + public Map financeStats() { + return statsMapper.financeStats(); + } +} diff --git a/backend/src/main/java/com/car/service/UserService.java b/backend/src/main/java/com/car/service/UserService.java new file mode 100644 index 0000000..c2f115b --- /dev/null +++ b/backend/src/main/java/com/car/service/UserService.java @@ -0,0 +1,126 @@ +package com.car.service; + +import com.car.common.ApiException; +import com.car.common.ErrorCode; +import com.car.dto.BalanceRequest; +import com.car.dto.LoginRequest; +import com.car.dto.RealNameRequest; +import com.car.dto.RegisterRequest; +import com.car.entity.User; +import com.car.mapper.UserMapper; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.stereotype.Service; + +import java.util.List; + +@Service +public class UserService { + private final UserMapper userMapper; + private final BCryptPasswordEncoder encoder; + + public UserService(UserMapper userMapper, BCryptPasswordEncoder encoder) { + this.userMapper = userMapper; + this.encoder = encoder; + } + + public User register(RegisterRequest request) { + User existing = userMapper.findByUsername(request.getUsername()); + if (existing != null) { + throw new ApiException(ErrorCode.CONFLICT, "用户名已存在"); + } + User user = new User(); + user.setUsername(request.getUsername()); + user.setPassword(encoder.encode(request.getPassword())); + user.setPhone(request.getPhone()); + user.setEmail(request.getEmail()); + user.setRole("USER"); + user.setStatus("ACTIVE"); + user.setBalance(0.0); + user.setRealNameStatus("NONE"); + userMapper.insert(user); + user.setPassword(null); + return user; + } + + public User login(LoginRequest request) { + User user = userMapper.findByUsername(request.getUsername()); + if (user == null || !encoder.matches(request.getPassword(), user.getPassword())) { + throw new ApiException(ErrorCode.UNAUTHORIZED, "用户名或密码错误"); + } + if (!"ACTIVE".equals(user.getStatus())) { + throw new ApiException(ErrorCode.FORBIDDEN, "账号已禁用"); + } + return user; + } + + public User getById(Long id) { + return userMapper.findById(id); + } + + public User updateProfile(User input) { + User db = userMapper.findById(input.getId()); + if (db == null) { + throw new ApiException(ErrorCode.NOT_FOUND, "用户不存在"); + } + db.setPhone(input.getPhone()); + db.setEmail(input.getEmail()); + userMapper.update(db); + db.setPassword(null); + return db; + } + + public User submitRealName(Long userId, RealNameRequest request) { + User db = userMapper.findById(userId); + if (db == null) { + throw new ApiException(ErrorCode.NOT_FOUND, "用户不存在"); + } + db.setRealName(request.getRealName()); + db.setIdNumber(request.getIdNumber()); + db.setIdFront(request.getIdFront()); + db.setIdBack(request.getIdBack()); + db.setRealNameStatus("PENDING"); + userMapper.update(db); + db.setPassword(null); + return db; + } + + public User reviewRealName(Long userId, boolean approved) { + User db = userMapper.findById(userId); + if (db == null) { + throw new ApiException(ErrorCode.NOT_FOUND, "用户不存在"); + } + db.setRealNameStatus(approved ? "APPROVED" : "REJECTED"); + userMapper.update(db); + db.setPassword(null); + return db; + } + + public User changeStatus(Long userId, String status) { + User db = userMapper.findById(userId); + if (db == null) { + throw new ApiException(ErrorCode.NOT_FOUND, "用户不存在"); + } + db.setStatus(status); + userMapper.update(db); + db.setPassword(null); + return db; + } + + public User addBalance(Long userId, BalanceRequest request) { + if (request.getAmount() <= 0) { + throw new ApiException(ErrorCode.BAD_REQUEST, "金额必须大于0"); + } + User db = userMapper.findById(userId); + if (db == null) { + throw new ApiException(ErrorCode.NOT_FOUND, "用户不存在"); + } + db.setBalance(db.getBalance() + request.getAmount()); + userMapper.update(db); + db.setPassword(null); + return db; + } + + public List listUsers(String keyword) { + return userMapper.findAll(keyword); + } +} diff --git a/backend/src/main/java/com/car/util/AuthUtil.java b/backend/src/main/java/com/car/util/AuthUtil.java new file mode 100644 index 0000000..87bf8a3 --- /dev/null +++ b/backend/src/main/java/com/car/util/AuthUtil.java @@ -0,0 +1,9 @@ +package com.car.util; + +import cn.dev33.satoken.stp.StpUtil; + +public class AuthUtil { + public static Long getUserId() { + return Long.valueOf(StpUtil.getLoginId().toString()); + } +} diff --git a/backend/src/main/resources/application.yml b/backend/src/main/resources/application.yml new file mode 100644 index 0000000..8d8c2b7 --- /dev/null +++ b/backend/src/main/resources/application.yml @@ -0,0 +1,26 @@ +server: + port: 8080 + +spring: + datasource: + url: jdbc:mysql://localhost:3306/car_rental?useSSL=false&characterEncoding=utf8&serverTimezone=Asia/Shanghai + username: root + password: root + jackson: + date-format: yyyy-MM-dd HH:mm:ss + time-zone: Asia/Shanghai + +mybatis: + mapper-locations: classpath:mappers/*.xml + configuration: + map-underscore-to-camel-case: true + +sa-token: + token-name: Authorization + token-style: jwt + timeout: 86400 + active-timeout: -1 + is-concurrent: true + is-share: true + token-prefix: Bearer + is-log: false diff --git a/backend/src/main/resources/mappers/CarMapper.xml b/backend/src/main/resources/mappers/CarMapper.xml new file mode 100644 index 0000000..78585f7 --- /dev/null +++ b/backend/src/main/resources/mappers/CarMapper.xml @@ -0,0 +1,71 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + insert into cars(brand, model, plate_no, price_per_day, deposit, status, is_special, images, description, seats, transmission, fuel_type, mileage, created_at, updated_at) + values (#{brand}, #{model}, #{plateNo}, #{pricePerDay}, #{deposit}, #{status}, #{isSpecial}, #{images}, #{description}, #{seats}, #{transmission}, #{fuelType}, #{mileage}, now(), now()) + + + + update cars + set brand = #{brand}, + model = #{model}, + plate_no = #{plateNo}, + price_per_day = #{pricePerDay}, + deposit = #{deposit}, + status = #{status}, + is_special = #{isSpecial}, + images = #{images}, + description = #{description}, + seats = #{seats}, + transmission = #{transmission}, + fuel_type = #{fuelType}, + mileage = #{mileage}, + updated_at = now() + where id = #{id} + + + + delete from cars where id = #{id} + + diff --git a/backend/src/main/resources/mappers/FavoriteMapper.xml b/backend/src/main/resources/mappers/FavoriteMapper.xml new file mode 100644 index 0000000..753227e --- /dev/null +++ b/backend/src/main/resources/mappers/FavoriteMapper.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + insert into favorites(user_id, car_id, created_at) + values (#{userId}, #{carId}, now()) + + + + delete from favorites where id = #{id} + + + + diff --git a/backend/src/main/resources/mappers/OrderMapper.xml b/backend/src/main/resources/mappers/OrderMapper.xml new file mode 100644 index 0000000..082d350 --- /dev/null +++ b/backend/src/main/resources/mappers/OrderMapper.xml @@ -0,0 +1,81 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + insert into orders(order_no, user_id, car_id, start_date, end_date, days, + price_per_day, deposit, total_amount, status, pay_type, paid_at, created_at, updated_at) + values (#{orderNo}, #{userId}, #{carId}, #{startDate}, #{endDate}, #{days}, + #{pricePerDay}, #{deposit}, #{totalAmount}, #{status}, #{payType}, #{paidAt}, now(), now()) + + + + update orders + set start_date = #{startDate}, + end_date = #{endDate}, + days = #{days}, + price_per_day = #{pricePerDay}, + deposit = #{deposit}, + total_amount = #{totalAmount}, + status = #{status}, + pay_type = #{payType}, + paid_at = #{paidAt}, + updated_at = now() + where id = #{id} + + + + + + + + diff --git a/backend/src/main/resources/mappers/PaymentMapper.xml b/backend/src/main/resources/mappers/PaymentMapper.xml new file mode 100644 index 0000000..274b31b --- /dev/null +++ b/backend/src/main/resources/mappers/PaymentMapper.xml @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + insert into payments(order_id, user_id, amount, type, created_at) + values (#{orderId}, #{userId}, #{amount}, #{type}, now()) + + + + diff --git a/backend/src/main/resources/mappers/StatsMapper.xml b/backend/src/main/resources/mappers/StatsMapper.xml new file mode 100644 index 0000000..c85b44e --- /dev/null +++ b/backend/src/main/resources/mappers/StatsMapper.xml @@ -0,0 +1,21 @@ + + + + + + + diff --git a/backend/src/main/resources/mappers/UserMapper.xml b/backend/src/main/resources/mappers/UserMapper.xml new file mode 100644 index 0000000..511b0b2 --- /dev/null +++ b/backend/src/main/resources/mappers/UserMapper.xml @@ -0,0 +1,65 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + insert into users(username, password, phone, email, role, status, balance, real_name_status, created_at, updated_at) + values (#{username}, #{password}, #{phone}, #{email}, #{role}, #{status}, #{balance}, #{realNameStatus}, now(), now()) + + + + update users + set password = #{password}, + phone = #{phone}, + email = #{email}, + role = #{role}, + status = #{status}, + balance = #{balance}, + real_name_status = #{realNameStatus}, + real_name = #{realName}, + id_number = #{idNumber}, + id_front = #{idFront}, + id_back = #{idBack}, + updated_at = now() + where id = #{id} + + + + diff --git a/frontend/.gitignore b/frontend/.gitignore new file mode 100644 index 0000000..a547bf3 --- /dev/null +++ b/frontend/.gitignore @@ -0,0 +1,24 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/frontend/README.md b/frontend/README.md new file mode 100644 index 0000000..1511959 --- /dev/null +++ b/frontend/README.md @@ -0,0 +1,5 @@ +# Vue 3 + Vite + +This template should help get you started developing with Vue 3 in Vite. The template uses Vue 3 ` + + diff --git a/frontend/package-lock.json b/frontend/package-lock.json new file mode 100644 index 0000000..ffe2d77 --- /dev/null +++ b/frontend/package-lock.json @@ -0,0 +1,1684 @@ +{ + "name": "frontend", + "version": "0.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "frontend", + "version": "0.0.0", + "dependencies": { + "@arco-design/web-vue": "^2.56.3", + "axios": "^1.7.9", + "dayjs": "^1.11.11", + "pinia": "^2.2.4", + "vue": "^3.5.24", + "vue-router": "^4.4.5" + }, + "devDependencies": { + "@vitejs/plugin-vue": "^5.1.4", + "vite": "^5.4.10" + } + }, + "node_modules/@arco-design/color": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@arco-design/color/-/color-0.4.0.tgz", + "integrity": "sha512-s7p9MSwJgHeL8DwcATaXvWT3m2SigKpxx4JA1BGPHL4gfvaQsmQfrLBDpjOJFJuJ2jG2dMt3R3P8Pm9E65q18g==", + "license": "MIT", + "dependencies": { + "color": "^3.1.3" + } + }, + "node_modules/@arco-design/web-vue": { + "version": "2.57.0", + "resolved": "https://registry.npmjs.org/@arco-design/web-vue/-/web-vue-2.57.0.tgz", + "integrity": "sha512-R5YReC3C2sG3Jv0+YuR3B7kzkq2KdhhQNCGXD8T11xAoa0zMt6SWTP1xJQOdZcM9du+q3z6tk5mRvh4qkieRJw==", + "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", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.6.tgz", + "integrity": "sha512-TeR9zWR18BvbfPmGbLampPMW+uW1NZnJlRuuHso8i87QZNq2JRF9i6RgxRqtEq+wQGsS19NNTWr2duhnE49mfQ==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.6" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/types": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.6.tgz", + "integrity": "sha512-0ZrskXVEHSWIqZM/sQZ4EV3jZJXRkio/WCxaqKZP1g//CEWEPSfeZFcms4XeKBCHU0ZKnIkdJeU/kF+eRp5lBg==", + "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/aix-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", + "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", + "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", + "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", + "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", + "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", + "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", + "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", + "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", + "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", + "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", + "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", + "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", + "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", + "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", + "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", + "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", + "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", + "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", + "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", + "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", + "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", + "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", + "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "license": "MIT" + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.55.1.tgz", + "integrity": "sha512-9R0DM/ykwfGIlNu6+2U09ga0WXeZ9MRC2Ter8jnz8415VbuIykVuc6bhdrbORFZANDmTDvq26mJrEVTl8TdnDg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.55.1.tgz", + "integrity": "sha512-eFZCb1YUqhTysgW3sj/55du5cG57S7UTNtdMjCW7LwVcj3dTTcowCsC8p7uBdzKsZYa8J7IDE8lhMI+HX1vQvg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.55.1.tgz", + "integrity": "sha512-p3grE2PHcQm2e8PSGZdzIhCKbMCw/xi9XvMPErPhwO17vxtvCN5FEA2mSLgmKlCjHGMQTP6phuQTYWUnKewwGg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.55.1.tgz", + "integrity": "sha512-rDUjG25C9qoTm+e02Esi+aqTKSBYwVTaoS1wxcN47/Luqef57Vgp96xNANwt5npq9GDxsH7kXxNkJVEsWEOEaQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.55.1.tgz", + "integrity": "sha512-+JiU7Jbp5cdxekIgdte0jfcu5oqw4GCKr6i3PJTlXTCU5H5Fvtkpbs4XJHRmWNXF+hKmn4v7ogI5OQPaupJgOg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.55.1.tgz", + "integrity": "sha512-V5xC1tOVWtLLmr3YUk2f6EJK4qksksOYiz/TCsFHu/R+woubcLWdC9nZQmwjOAbmExBIVKsm1/wKmEy4z4u4Bw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.55.1.tgz", + "integrity": "sha512-Rn3n+FUk2J5VWx+ywrG/HGPTD9jXNbicRtTM11e/uorplArnXZYsVifnPPqNNP5BsO3roI4n8332ukpY/zN7rQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.55.1.tgz", + "integrity": "sha512-grPNWydeKtc1aEdrJDWk4opD7nFtQbMmV7769hiAaYyUKCT1faPRm2av8CX1YJsZ4TLAZcg9gTR1KvEzoLjXkg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.55.1.tgz", + "integrity": "sha512-a59mwd1k6x8tXKcUxSyISiquLwB5pX+fJW9TkWU46lCqD/GRDe9uDN31jrMmVP3feI3mhAdvcCClhV8V5MhJFQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.55.1.tgz", + "integrity": "sha512-puS1MEgWX5GsHSoiAsF0TYrpomdvkaXm0CofIMG5uVkP6IBV+ZO9xhC5YEN49nsgYo1DuuMquF9+7EDBVYu4uA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.55.1.tgz", + "integrity": "sha512-r3Wv40in+lTsULSb6nnoudVbARdOwb2u5fpeoOAZjFLznp6tDU8kd+GTHmJoqZ9lt6/Sys33KdIHUaQihFcu7g==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.55.1.tgz", + "integrity": "sha512-MR8c0+UxAlB22Fq4R+aQSPBayvYa3+9DrwG/i1TKQXFYEaoW3B5b/rkSRIypcZDdWjWnpcvxbNaAJDcSbJU3Lw==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.55.1.tgz", + "integrity": "sha512-3KhoECe1BRlSYpMTeVrD4sh2Pw2xgt4jzNSZIIPLFEsnQn9gAnZagW9+VqDqAHgm1Xc77LzJOo2LdigS5qZ+gw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.55.1.tgz", + "integrity": "sha512-ziR1OuZx0vdYZZ30vueNZTg73alF59DicYrPViG0NEgDVN8/Jl87zkAPu4u6VjZST2llgEUjaiNl9JM6HH1Vdw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.55.1.tgz", + "integrity": "sha512-uW0Y12ih2XJRERZ4jAfKamTyIHVMPQnTZcQjme2HMVDAHY4amf5u414OqNYC+x+LzRdRcnIG1YodLrrtA8xsxw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.55.1.tgz", + "integrity": "sha512-u9yZ0jUkOED1BFrqu3BwMQoixvGHGZ+JhJNkNKY/hyoEgOwlqKb62qu+7UjbPSHYjiVy8kKJHvXKv5coH4wDeg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.55.1.tgz", + "integrity": "sha512-/0PenBCmqM4ZUd0190j7J0UsQ/1nsi735iPRakO8iPciE7BQ495Y6msPzaOmvx0/pn+eJVVlZrNrSh4WSYLxNg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.55.1.tgz", + "integrity": "sha512-a8G4wiQxQG2BAvo+gU6XrReRRqj+pLS2NGXKm8io19goR+K8lw269eTrPkSdDTALwMmJp4th2Uh0D8J9bEV1vg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.55.1.tgz", + "integrity": "sha512-bD+zjpFrMpP/hqkfEcnjXWHMw5BIghGisOKPj+2NaNDuVT+8Ds4mPf3XcPHuat1tz89WRL+1wbcxKY3WSbiT7w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.55.1.tgz", + "integrity": "sha512-eLXw0dOiqE4QmvikfQ6yjgkg/xDM+MdU9YJuP4ySTibXU0oAvnEWXt7UDJmD4UkYialMfOGFPJnIHSe/kdzPxg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.55.1.tgz", + "integrity": "sha512-xzm44KgEP11te3S2HCSyYf5zIzWmx3n8HDCc7EE59+lTcswEWNpvMLfd9uJvVX8LCg9QWG67Xt75AuHn4vgsXw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.55.1.tgz", + "integrity": "sha512-yR6Bl3tMC/gBok5cz/Qi0xYnVbIxGx5Fcf/ca0eB6/6JwOY+SRUcJfI0OpeTpPls7f194as62thCt/2BjxYN8g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.55.1.tgz", + "integrity": "sha512-3fZBidchE0eY0oFZBnekYCfg+5wAB0mbpCBuofh5mZuzIU/4jIVkbESmd2dOsFNS78b53CYv3OAtwqkZZmU5nA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.55.1.tgz", + "integrity": "sha512-xGGY5pXj69IxKb4yv/POoocPy/qmEGhimy/FoTpTSVju3FYXUQQMFCaZZXJVidsmGxRioZAwpThl/4zX41gRKg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.55.1.tgz", + "integrity": "sha512-SPEpaL6DX4rmcXtnhdrQYgzQ5W2uW3SCJch88lB2zImhJRhIIK44fkUrgIV/Q8yUNfw5oyZ5vkeQsZLhCb06lw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@vitejs/plugin-vue": { + "version": "5.2.4", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-5.2.4.tgz", + "integrity": "sha512-7Yx/SXSOcQq5HiiV3orevHUFn+pmMB4cgbEkDYgnkUWb0WfeQ/wa2yFv6D5ICiCQOVpjA7vYDXrC7AGO8yjDHA==", + "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.26", + "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.26.tgz", + "integrity": "sha512-vXyI5GMfuoBCnv5ucIT7jhHKl55Y477yxP6fc4eUswjP8FG3FFVFd41eNDArR+Uk3QKn2Z85NavjaxLxOC19/w==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.28.5", + "@vue/shared": "3.5.26", + "entities": "^7.0.0", + "estree-walker": "^2.0.2", + "source-map-js": "^1.2.1" + } + }, + "node_modules/@vue/compiler-dom": { + "version": "3.5.26", + "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.26.tgz", + "integrity": "sha512-y1Tcd3eXs834QjswshSilCBnKGeQjQXB6PqFn/1nxcQw4pmG42G8lwz+FZPAZAby6gZeHSt/8LMPfZ4Rb+Bd/A==", + "license": "MIT", + "dependencies": { + "@vue/compiler-core": "3.5.26", + "@vue/shared": "3.5.26" + } + }, + "node_modules/@vue/compiler-sfc": { + "version": "3.5.26", + "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.26.tgz", + "integrity": "sha512-egp69qDTSEZcf4bGOSsprUr4xI73wfrY5oRs6GSgXFTiHrWj4Y3X5Ydtip9QMqiCMCPVwLglB9GBxXtTadJ3mA==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.28.5", + "@vue/compiler-core": "3.5.26", + "@vue/compiler-dom": "3.5.26", + "@vue/compiler-ssr": "3.5.26", + "@vue/shared": "3.5.26", + "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.26", + "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.26.tgz", + "integrity": "sha512-lZT9/Y0nSIRUPVvapFJEVDbEXruZh2IYHMk2zTtEgJSlP5gVOqeWXH54xDKAaFS4rTnDeDBQUYDtxKyoW9FwDw==", + "license": "MIT", + "dependencies": { + "@vue/compiler-dom": "3.5.26", + "@vue/shared": "3.5.26" + } + }, + "node_modules/@vue/devtools-api": { + "version": "6.6.4", + "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-6.6.4.tgz", + "integrity": "sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g==", + "license": "MIT" + }, + "node_modules/@vue/reactivity": { + "version": "3.5.26", + "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.26.tgz", + "integrity": "sha512-9EnYB1/DIiUYYnzlnUBgwU32NNvLp/nhxLXeWRhHUEeWNTn1ECxX8aGO7RTXeX6PPcxe3LLuNBFoJbV4QZ+CFQ==", + "license": "MIT", + "dependencies": { + "@vue/shared": "3.5.26" + } + }, + "node_modules/@vue/runtime-core": { + "version": "3.5.26", + "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.26.tgz", + "integrity": "sha512-xJWM9KH1kd201w5DvMDOwDHYhrdPTrAatn56oB/LRG4plEQeZRQLw0Bpwih9KYoqmzaxF0OKSn6swzYi84e1/Q==", + "license": "MIT", + "dependencies": { + "@vue/reactivity": "3.5.26", + "@vue/shared": "3.5.26" + } + }, + "node_modules/@vue/runtime-dom": { + "version": "3.5.26", + "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.26.tgz", + "integrity": "sha512-XLLd/+4sPC2ZkN/6+V4O4gjJu6kSDbHAChvsyWgm1oGbdSO3efvGYnm25yCjtFm/K7rrSDvSfPDgN1pHgS4VNQ==", + "license": "MIT", + "dependencies": { + "@vue/reactivity": "3.5.26", + "@vue/runtime-core": "3.5.26", + "@vue/shared": "3.5.26", + "csstype": "^3.2.3" + } + }, + "node_modules/@vue/server-renderer": { + "version": "3.5.26", + "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.26.tgz", + "integrity": "sha512-TYKLXmrwWKSodyVuO1WAubucd+1XlLg4set0YoV+Hu8Lo79mp/YMwWV5mC5FgtsDxX3qo1ONrxFaTP1OQgy1uA==", + "license": "MIT", + "dependencies": { + "@vue/compiler-ssr": "3.5.26", + "@vue/shared": "3.5.26" + }, + "peerDependencies": { + "vue": "3.5.26" + } + }, + "node_modules/@vue/shared": { + "version": "3.5.26", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.26.tgz", + "integrity": "sha512-7Z6/y3uFI5PRoKeorTOSXKcDj0MSasfNNltcslbFrPpcw6aXRUALq4IfJlaTRspiWIUOEZbrpM+iQGmCOiWe4A==", + "license": "MIT" + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" + }, + "node_modules/axios": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.2.tgz", + "integrity": "sha512-VPk9ebNqPcy5lRGuSlKx752IlDatOjT9paPlm8A7yOuW2Fbvp4X3JznJtT4f0GzGLLiWE9W8onz51SqLYwzGaA==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.4", + "proxy-from-env": "^1.1.0" + } + }, + "node_modules/b-tween": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/b-tween/-/b-tween-0.3.3.tgz", + "integrity": "sha512-oEHegcRpA7fAuc9KC4nktucuZn2aS8htymCPcP3qkEGPqiBH+GfqtqoG2l7LxHngg6O0HFM7hOeOYExl1Oz4ZA==", + "license": "MIT" + }, + "node_modules/b-validate": { + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/b-validate/-/b-validate-1.5.3.tgz", + "integrity": "sha512-iCvCkGFskbaYtfQ0a3GmcQCHl/Sv1GufXFGuUQ+FE+WJa7A/espLOuFIn09B944V8/ImPj71T4+rTASxO2PAuA==", + "license": "MIT" + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/color": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/color/-/color-3.2.1.tgz", + "integrity": "sha512-aBl7dZI9ENN6fUGC7mWpMTPNHmWUSNan9tuWN6ahh5ZLNk9baLJOnSMlrQkHcrfFgz2/RigjUVAjdx36VcemKA==", + "license": "MIT", + "dependencies": { + "color-convert": "^1.9.3", + "color-string": "^1.6.0" + } + }, + "node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "license": "MIT", + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "license": "MIT" + }, + "node_modules/color-string": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz", + "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==", + "license": "MIT", + "dependencies": { + "color-name": "^1.0.0", + "simple-swizzle": "^0.2.2" + } + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/compute-scroll-into-view": { + "version": "1.0.20", + "resolved": "https://registry.npmjs.org/compute-scroll-into-view/-/compute-scroll-into-view-1.0.20.tgz", + "integrity": "sha512-UCB0ioiyj8CRjtrvaceBLqqhZCVP+1B8+NWQhmdsm0VXOJtobBCf1dBQmebCCo34qZmUwZfIH2MZLqNHazrfjg==", + "license": "MIT" + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "license": "MIT" + }, + "node_modules/dayjs": { + "version": "1.11.19", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.19.tgz", + "integrity": "sha512-t5EcLVS6QPBNqM2z8fakk/NKel+Xzshgt8FFKAn+qwlD1pzZWxh0nVCrvFK7ZDb6XucZeF9z8C7CBWTRIVApAw==", + "license": "MIT" + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "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/entities": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-7.0.0.tgz", + "integrity": "sha512-FDWG5cmEYf2Z00IkYRhbFrwIwvdFKH07uV8dvNy0omp/Qb1xcyCWp2UDtcwJF4QZZvk0sLudP6/hAu42TaqVhQ==", + "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", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "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.21.5", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", + "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.21.5", + "@esbuild/android-arm": "0.21.5", + "@esbuild/android-arm64": "0.21.5", + "@esbuild/android-x64": "0.21.5", + "@esbuild/darwin-arm64": "0.21.5", + "@esbuild/darwin-x64": "0.21.5", + "@esbuild/freebsd-arm64": "0.21.5", + "@esbuild/freebsd-x64": "0.21.5", + "@esbuild/linux-arm": "0.21.5", + "@esbuild/linux-arm64": "0.21.5", + "@esbuild/linux-ia32": "0.21.5", + "@esbuild/linux-loong64": "0.21.5", + "@esbuild/linux-mips64el": "0.21.5", + "@esbuild/linux-ppc64": "0.21.5", + "@esbuild/linux-riscv64": "0.21.5", + "@esbuild/linux-s390x": "0.21.5", + "@esbuild/linux-x64": "0.21.5", + "@esbuild/netbsd-x64": "0.21.5", + "@esbuild/openbsd-x64": "0.21.5", + "@esbuild/sunos-x64": "0.21.5", + "@esbuild/win32-arm64": "0.21.5", + "@esbuild/win32-ia32": "0.21.5", + "@esbuild/win32-x64": "0.21.5" + } + }, + "node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "license": "MIT" + }, + "node_modules/follow-redirects": { + "version": "1.15.11", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz", + "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/form-data": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", + "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", + "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", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": 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", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "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", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "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", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "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", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/is-arrayish": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.4.tgz", + "integrity": "sha512-m6UrgzFVUYawGBh1dUsWR5M2Clqic9RVXC/9f8ceNlv2IcO9j9J/z8UoCLPqtsPBFNzEpfR3xftohbfqDx8EQA==", + "license": "MIT" + }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "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", + "resolved": "https://registry.npmjs.org/number-precision/-/number-precision-1.6.0.tgz", + "integrity": "sha512-05OLPgbgmnixJw+VvEh18yNPUo3iyp4BEWJcrLu4X9W05KmMifN7Mu5exYvQXqxxeNWhvIF+j3Rij+HmddM/hQ==", + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" + }, + "node_modules/pinia": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/pinia/-/pinia-2.3.1.tgz", + "integrity": "sha512-khUlZSwt9xXCaTbbxFYBKDc/bWAGWJjOgvxETwkTN7KRm66EeT1ZdZj6i2ceh9sP2Pzqsbc704r2yngBrxBVug==", + "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", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "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", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "license": "MIT" + }, + "node_modules/resize-observer-polyfill": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz", + "integrity": "sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg==", + "license": "MIT" + }, + "node_modules/rollup": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.55.1.tgz", + "integrity": "sha512-wDv/Ht1BNHB4upNbK74s9usvl7hObDnvVzknxqY/E/O3X6rW1U1rV1aENEfJ54eFZDTNo7zv1f5N4edCluH7+A==", + "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.55.1", + "@rollup/rollup-android-arm64": "4.55.1", + "@rollup/rollup-darwin-arm64": "4.55.1", + "@rollup/rollup-darwin-x64": "4.55.1", + "@rollup/rollup-freebsd-arm64": "4.55.1", + "@rollup/rollup-freebsd-x64": "4.55.1", + "@rollup/rollup-linux-arm-gnueabihf": "4.55.1", + "@rollup/rollup-linux-arm-musleabihf": "4.55.1", + "@rollup/rollup-linux-arm64-gnu": "4.55.1", + "@rollup/rollup-linux-arm64-musl": "4.55.1", + "@rollup/rollup-linux-loong64-gnu": "4.55.1", + "@rollup/rollup-linux-loong64-musl": "4.55.1", + "@rollup/rollup-linux-ppc64-gnu": "4.55.1", + "@rollup/rollup-linux-ppc64-musl": "4.55.1", + "@rollup/rollup-linux-riscv64-gnu": "4.55.1", + "@rollup/rollup-linux-riscv64-musl": "4.55.1", + "@rollup/rollup-linux-s390x-gnu": "4.55.1", + "@rollup/rollup-linux-x64-gnu": "4.55.1", + "@rollup/rollup-linux-x64-musl": "4.55.1", + "@rollup/rollup-openbsd-x64": "4.55.1", + "@rollup/rollup-openharmony-arm64": "4.55.1", + "@rollup/rollup-win32-arm64-msvc": "4.55.1", + "@rollup/rollup-win32-ia32-msvc": "4.55.1", + "@rollup/rollup-win32-x64-gnu": "4.55.1", + "@rollup/rollup-win32-x64-msvc": "4.55.1", + "fsevents": "~2.3.2" + } + }, + "node_modules/scroll-into-view-if-needed": { + "version": "2.2.31", + "resolved": "https://registry.npmjs.org/scroll-into-view-if-needed/-/scroll-into-view-if-needed-2.2.31.tgz", + "integrity": "sha512-dGCXy99wZQivjmjIqihaBQNjryrz5rueJY7eHfTdyWEiR4ttYpsajb14rn9s5d4DY4EcY6+4+U/maARBXJedkA==", + "license": "MIT", + "dependencies": { + "compute-scroll-into-view": "^1.0.20" + } + }, + "node_modules/simple-swizzle": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.4.tgz", + "integrity": "sha512-nAu1WFPQSMNr2Zn9PGSZK9AGn4t/y97lEm+MXTtUDwfP0ksAIX4nO+6ruD9Jwut4C49SB1Ws+fbXsm/yScWOHw==", + "license": "MIT", + "dependencies": { + "is-arrayish": "^0.3.1" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/vite": { + "version": "5.4.21", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.21.tgz", + "integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.21.3", + "postcss": "^8.4.43", + "rollup": "^4.20.0" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || >=20.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", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + }, + "node_modules/vue": { + "version": "3.5.26", + "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.26.tgz", + "integrity": "sha512-SJ/NTccVyAoNUJmkM9KUqPcYlY+u8OVL1X5EW9RIs3ch5H2uERxyyIUI4MRxVCSOiEcupX9xNGde1tL9ZKpimA==", + "license": "MIT", + "dependencies": { + "@vue/compiler-dom": "3.5.26", + "@vue/compiler-sfc": "3.5.26", + "@vue/runtime-dom": "3.5.26", + "@vue/server-renderer": "3.5.26", + "@vue/shared": "3.5.26" + }, + "peerDependencies": { + "typescript": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/vue-demi": { + "version": "0.14.10", + "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.10.tgz", + "integrity": "sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==", + "hasInstallScript": true, + "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-router": { + "version": "4.6.4", + "resolved": "https://registry.npmjs.org/vue-router/-/vue-router-4.6.4.tgz", + "integrity": "sha512-Hz9q5sa33Yhduglwz6g9skT8OBPii+4bFn88w6J+J4MfEo4KRRpmiNG/hHHkdbRFlLBOqxN8y8gf2Fb0MTUgVg==", + "license": "MIT", + "dependencies": { + "@vue/devtools-api": "^6.6.4" + }, + "funding": { + "url": "https://github.com/sponsors/posva" + }, + "peerDependencies": { + "vue": "^3.5.0" + } + } + } +} diff --git a/frontend/package.json b/frontend/package.json new file mode 100644 index 0000000..61539ae --- /dev/null +++ b/frontend/package.json @@ -0,0 +1,23 @@ +{ + "name": "frontend", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "preview": "vite preview" + }, + "dependencies": { + "@arco-design/web-vue": "^2.56.3", + "axios": "^1.7.9", + "dayjs": "^1.11.11", + "pinia": "^2.2.4", + "vue": "^3.5.24", + "vue-router": "^4.4.5" + }, + "devDependencies": { + "@vitejs/plugin-vue": "^5.1.4", + "vite": "^5.4.10" + } +} diff --git a/frontend/public/vite.svg b/frontend/public/vite.svg new file mode 100644 index 0000000..e7b8dfb --- /dev/null +++ b/frontend/public/vite.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/src/App.vue b/frontend/src/App.vue new file mode 100644 index 0000000..8a63a3a --- /dev/null +++ b/frontend/src/App.vue @@ -0,0 +1,82 @@ + + + diff --git a/frontend/src/api/http.js b/frontend/src/api/http.js new file mode 100644 index 0000000..6febf9e --- /dev/null +++ b/frontend/src/api/http.js @@ -0,0 +1,28 @@ +import axios from 'axios' +import { useAuthStore } from '../store/auth' + +const http = axios.create({ + baseURL: 'http://localhost:8080', + timeout: 15000 +}) + +http.interceptors.request.use((config) => { + const store = useAuthStore() + if (store.token) { + config.headers.Authorization = `Bearer ${store.token}` + } + return config +}) + +http.interceptors.response.use( + (response) => { + const res = response.data + if (res.code !== 0) { + return Promise.reject(new Error(res.message || '请求失败')) + } + return res.data + }, + (error) => Promise.reject(error) +) + +export default http diff --git a/frontend/src/assets/vue.svg b/frontend/src/assets/vue.svg new file mode 100644 index 0000000..770e9d3 --- /dev/null +++ b/frontend/src/assets/vue.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/src/components/HelloWorld.vue b/frontend/src/components/HelloWorld.vue new file mode 100644 index 0000000..546ebbc --- /dev/null +++ b/frontend/src/components/HelloWorld.vue @@ -0,0 +1,43 @@ + + + + + diff --git a/frontend/src/main.js b/frontend/src/main.js new file mode 100644 index 0000000..1953937 --- /dev/null +++ b/frontend/src/main.js @@ -0,0 +1,13 @@ +import { createApp } from 'vue' +import { createPinia } from 'pinia' +import ArcoVue from '@arco-design/web-vue' +import '@arco-design/web-vue/dist/arco.css' +import './style.css' +import App from './App.vue' +import router from './router' + +const app = createApp(App) +app.use(createPinia()) +app.use(router) +app.use(ArcoVue) +app.mount('#app') diff --git a/frontend/src/router/index.js b/frontend/src/router/index.js new file mode 100644 index 0000000..5a7dd25 --- /dev/null +++ b/frontend/src/router/index.js @@ -0,0 +1,46 @@ +import { createRouter, createWebHistory } from 'vue-router' +import { useAuthStore } from '../store/auth' +import Home from '../views/Home.vue' +import Login from '../views/Login.vue' +import Register from '../views/Register.vue' +import CarDetail from '../views/CarDetail.vue' +import Favorites from '../views/Favorites.vue' +import Orders from '../views/Orders.vue' +import Profile from '../views/Profile.vue' +import AdminCars from '../views/admin/AdminCars.vue' +import AdminUsers from '../views/admin/AdminUsers.vue' +import AdminOrders from '../views/admin/AdminOrders.vue' +import AdminStats from '../views/admin/AdminStats.vue' + +const routes = [ + { path: '/', component: Home }, + { path: '/login', component: Login }, + { path: '/register', component: Register }, + { path: '/cars/:id', component: CarDetail }, + { path: '/favorites', component: Favorites }, + { path: '/orders', component: Orders }, + { path: '/profile', component: Profile }, + { path: '/admin/cars', component: AdminCars }, + { path: '/admin/users', component: AdminUsers }, + { path: '/admin/orders', component: AdminOrders }, + { path: '/admin/stats', component: AdminStats } +] + +const router = createRouter({ + history: createWebHistory(), + routes +}) + +router.beforeEach((to) => { + const store = useAuthStore() + const requiresAuth = ['/favorites', '/orders', '/profile', '/admin/cars', '/admin/users', '/admin/orders', '/admin/stats'] + if (requiresAuth.includes(to.path) && !store.token) { + return '/login' + } + if (to.path.startsWith('/admin') && store.role !== 'ADMIN') { + return '/' + } + return true +}) + +export default router diff --git a/frontend/src/store/auth.js b/frontend/src/store/auth.js new file mode 100644 index 0000000..c325508 --- /dev/null +++ b/frontend/src/store/auth.js @@ -0,0 +1,27 @@ +import { defineStore } from 'pinia' + +export const useAuthStore = defineStore('auth', { + state: () => ({ + token: localStorage.getItem('token') || '', + role: localStorage.getItem('role') || '', + user: null + }), + actions: { + setAuth(token, role) { + this.token = token + this.role = role + localStorage.setItem('token', token) + localStorage.setItem('role', role) + }, + clear() { + this.token = '' + this.role = '' + this.user = null + localStorage.removeItem('token') + localStorage.removeItem('role') + }, + setUser(user) { + this.user = user + } + } +}) diff --git a/frontend/src/style.css b/frontend/src/style.css new file mode 100644 index 0000000..e19e094 --- /dev/null +++ b/frontend/src/style.css @@ -0,0 +1,60 @@ +body { + margin: 0; + background: #f5f7fa; + color: #1d2129; + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", "PingFang SC", "Microsoft YaHei", sans-serif; +} + +a { + color: inherit; + text-decoration: none; +} + +.layout { + min-height: 100vh; +} + +.header { + display: flex; + align-items: center; + gap: 24px; + background: #ffffff; + border-bottom: 1px solid #e5e6eb; +} + +.logo { + font-weight: 600; + font-size: 18px; + padding: 0 16px; +} + +.user-actions { + margin-left: auto; + padding-right: 16px; + display: flex; + align-items: center; + gap: 12px; +} + +.divider { + color: #c9cdd4; +} + +.content { + padding: 24px; + max-width: 1200px; + margin: 0 auto; + width: 100%; +} + +.card-grid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(260px, 1fr)); + gap: 16px; +} + +.section-title { + font-size: 16px; + font-weight: 600; + margin-bottom: 12px; +} diff --git a/frontend/src/views/CarDetail.vue b/frontend/src/views/CarDetail.vue new file mode 100644 index 0000000..aadf13f --- /dev/null +++ b/frontend/src/views/CarDetail.vue @@ -0,0 +1,97 @@ + + + diff --git a/frontend/src/views/Favorites.vue b/frontend/src/views/Favorites.vue new file mode 100644 index 0000000..f1adab8 --- /dev/null +++ b/frontend/src/views/Favorites.vue @@ -0,0 +1,45 @@ + + + diff --git a/frontend/src/views/Home.vue b/frontend/src/views/Home.vue new file mode 100644 index 0000000..d759913 --- /dev/null +++ b/frontend/src/views/Home.vue @@ -0,0 +1,58 @@ + + + diff --git a/frontend/src/views/Login.vue b/frontend/src/views/Login.vue new file mode 100644 index 0000000..9e0cf1e --- /dev/null +++ b/frontend/src/views/Login.vue @@ -0,0 +1,43 @@ + + + diff --git a/frontend/src/views/Orders.vue b/frontend/src/views/Orders.vue new file mode 100644 index 0000000..9031ace --- /dev/null +++ b/frontend/src/views/Orders.vue @@ -0,0 +1,76 @@ + + + diff --git a/frontend/src/views/Profile.vue b/frontend/src/views/Profile.vue new file mode 100644 index 0000000..a5a262c --- /dev/null +++ b/frontend/src/views/Profile.vue @@ -0,0 +1,91 @@ + + + diff --git a/frontend/src/views/Register.vue b/frontend/src/views/Register.vue new file mode 100644 index 0000000..39d52e1 --- /dev/null +++ b/frontend/src/views/Register.vue @@ -0,0 +1,46 @@ + + + diff --git a/frontend/src/views/admin/AdminCars.vue b/frontend/src/views/admin/AdminCars.vue new file mode 100644 index 0000000..4a01335 --- /dev/null +++ b/frontend/src/views/admin/AdminCars.vue @@ -0,0 +1,133 @@ + + + diff --git a/frontend/src/views/admin/AdminOrders.vue b/frontend/src/views/admin/AdminOrders.vue new file mode 100644 index 0000000..a0a9d14 --- /dev/null +++ b/frontend/src/views/admin/AdminOrders.vue @@ -0,0 +1,50 @@ + + + diff --git a/frontend/src/views/admin/AdminStats.vue b/frontend/src/views/admin/AdminStats.vue new file mode 100644 index 0000000..783d539 --- /dev/null +++ b/frontend/src/views/admin/AdminStats.vue @@ -0,0 +1,40 @@ + + + diff --git a/frontend/src/views/admin/AdminUsers.vue b/frontend/src/views/admin/AdminUsers.vue new file mode 100644 index 0000000..ca7e8cd --- /dev/null +++ b/frontend/src/views/admin/AdminUsers.vue @@ -0,0 +1,76 @@ + + + diff --git a/frontend/vite.config.js b/frontend/vite.config.js new file mode 100644 index 0000000..bbcf80c --- /dev/null +++ b/frontend/vite.config.js @@ -0,0 +1,7 @@ +import { defineConfig } from 'vite' +import vue from '@vitejs/plugin-vue' + +// https://vite.dev/config/ +export default defineConfig({ + plugins: [vue()], +}) diff --git a/张天明-车智车辆租赁平台的设计与实现.md b/张天明-车智车辆租赁平台的设计与实现.md new file mode 100644 index 0000000..7549c71 --- /dev/null +++ b/张天明-车智车辆租赁平台的设计与实现.md @@ -0,0 +1,141 @@ +# 毕业设计(论文)任务书 + +论文题目:车智车辆租赁平台的设计与实现 + +# 1. 任务和目标 + +本课题的总体任务是设计并实现一个基于B/S架构的“车智车辆租赁平台”。该系统旨在利用现代Web技术(前端采用Vue.js框架,后端使用Java语言结合SpringBoot框架),构建一个连接车辆租赁公司与租户用户的数字化桥梁。通过提供车辆信息展示、在线选车、租期预定、订单管理等核心功能,解决传统租车流程中信息不透明、手续繁琐、地域限制等问题,为用户提供便捷、高效、透明的现代化租车服务体验。 + +车智车辆租赁平台通过数字化手段连接车辆租赁服务商与消费者,打破了传统租车行业的信息壁垒和地域限制,为租车服务提供了更广阔的市场空间。这有助于提升车辆使用率,增加租赁公司收入,从而激发汽车租赁市场的活力,促进交通出行服务的多元化发展。 + +汽车租赁作为共享经济的重要环节,车智车辆租赁平台很好地体现了这一经济模式的核心理念。依托平台的高效运营,不仅能够增强社会车辆资源的配置效率,还有效减少了资源闲置现象。同时,这一实践对汽车产业链的上下游产生了积极的带动作用,进 + +一步推动了绿色出行理念的落地及可持续交通体系的构建,为实现更加环保与高效的出 + +行方式提供了可能性。 + +车智车辆租赁平台的设计与实现主要任务如下: + +(1) 根据系统的需求和毕设要求,完成字数不少于 2500 字的开题报告。 + +(2) 根据项目的时间和实习的时间合理安排,拟定毕业设计进度计划表。 + +(3) 确定题目的思路以及具体实现所需的技术。 + +(4)本设计要求开发一个车辆租赁平台。 + +顾客用户的功能模块划分为:登录注册模块,可租车辆模块,车辆详情模块,收藏夹模块,租车下单模块,归还车辆模块,实名认证模块,余额支付模块,个人中心模块,我的订单模块。 + +管理员用户的功能模块划分为:管理员登录模块,特价车辆模块,车辆管理模块,订单管理模块,实名审核模块,用户管理模块,订单管理模块,数据统计模块,财务报表模块。 + +(5) 开发车智车辆租赁平台,功能保证完善。 + +(6) 撰写车智车辆租赁平台的论文。 + +# 2.基本要求 + +(1) 结合自己实习情况安排进度,填写进度计划表交给指导教师审核并严格执行; + + + +(2) 查阅和收集资料,与指导老师进行评审论文课题项目的需求和功能设计; + + + + + +(3) 完成外文资料翻译,要求语言准确、流畅,译文至少 3500 字; + + + + + +(4) 论文要求 10000 字以上, 包括绪论、系统总体设计、系统实现和结论等内容; + + + + + +(5) 保证按照毕业指导进度稳定推进,服从指导教师安排的相关任务; + + + + + +(6) 制作答辩 PPT、准备答辩; + + + + + +(7) 根据答辩教师提出的要求修改论文; + + + + + +(8) 完成论文和相关资料的存档和备份; + + + +# 3.参考文献 + + + +[1]粟梁.基于SSM框架的汽车租赁管理系统设计与实现[J].电脑编程技巧与维护,2024,32(1):43-45,52. + + + + + +[2] 王睿. 互联网模式下汽车租赁行业财务管理策略研究[J]. 活力, 2024, (21):133-135. + + + + + +[3]粟梁.基于SSM框架的汽车租赁管理系统设计与实现[J].电脑编程技巧与维护,2024,32(1):43-45,52. + + + + + +[4] 肖安琪.汽车租赁系统的设计与实现[J].山西大同大学学报(自然科学版),2024,38(2):54-58. + + + + + +[5] 韩鑫.租车自驾游消费新需求[N].人民日报, 2025-10-10(007). + + + + + +[6]黑马程序员.Spring Boot企业级开发教程[M].人民邮电出版社,2024. + + + + + +[7] 张建臣,陈承欢. JavaScript 程序设计基础与实战[M].人民邮电出版社, 2024. + + + + + +[8] 杨玉, 刘杰举. 基 Spring Boot 与 Vue 的物业管理系统设计与实现[J]. 鞋类工艺与设计, + + + +2025,(14):114-116. + +[9] Wenjuan Shao, Kun Liu. Design and Implementation of Online Ordering System Based on SpringBoot[J]. Journal of Big Data and Computing, 2024, 2(3). + +[10] 徐凯鑫.汽车租赁赋能个性化用车需求[N].汽车特刊,2023-07-28(007). + +指导教师签字: 年月日 + +教研室审查意见: + +指导教师签字: 年月日 \ No newline at end of file