Spring Boot 3.x与Vue 3实战:构建现代化汽车租赁系统全栈应用

📅 2026/7/21 23:09:34
Spring Boot 3.x与Vue 3实战:构建现代化汽车租赁系统全栈应用
如果你正在开发一个汽车租赁系统或者正在学习如何将前后端分离架构应用到实际项目中那么这篇文章就是为你准备的。很多开发者尤其是刚接触全栈开发的朋友常常会陷入一个误区以为只要把 Spring Boot 和 Vue3 两个框架拼在一起项目就能跑起来。但现实是从数据库设计、接口定义到前后端联调、权限控制每一步都有无数个“坑”在等着你。一个看似简单的“租车”功能背后涉及车辆状态流转、订单计费逻辑、用户权限隔离等复杂的业务规则。本文不会只给你一个干巴巴的“图书管理系统”的变种。我们将深入一个基于 Spring Boot 3.x 与 Vue 3 的现代化汽车租赁系统从零开始拆解其核心架构与实现细节。你将看到的不是一个玩具 Demo而是一个具备车辆管理、在线预订、订单处理、用户权限、操作日志等核心模块的实战项目。更重要的是我们会重点探讨如何借鉴“智能推荐系统”的设计思想为租赁系统注入“智慧”例如根据用户历史行为推荐车型以及如何通过详尽的“操作日志”确保系统安全与可审计性。通过本文你将彻底掌握如何设计一个清晰、可扩展的汽车租赁领域模型。如何使用 Spring Boot 3.x 构建高效、安全的后端 RESTful API包括 JWT 认证、全局异常处理、操作日志切面等企业级特性。如何使用 Vue 3 的 Composition API 与 Pinia 状态管理构建模块化、响应式的前端应用。如何实现前后端分离开发与联调解决跨域、数据格式、状态同步等典型问题。如何规划“智能推荐”与“操作日志”等进阶功能提升系统价值。我们直接从最核心的问题开始一个汽车租赁系统到底需要解决哪些业务和技术难题1. 汽车租赁系统的核心业务与技术挑战在开始写代码之前我们必须想清楚系统要做什么。一个完整的汽车租赁系统远不止“用户选车-下单”这么简单。它本质上是一个小型电商系统与资源调度系统的结合体同时兼具强烈的线下服务属性。核心业务挑战包括车辆状态管理车辆有“可租”、“已预订”、“出租中”、“维修中”、“已下架”等多种状态。状态转换必须严谨例如不能将“出租中”的车辆再次出租。这需要设计健壮的状态机。订单与计费逻辑计费规则复杂可能涉及按天计费、超时费、基础服务费、不同车型的不同单价、节假日价格浮动等。订单的创建、支付、取消、完成全生命周期需要严密跟踪。库存与预订冲突这是最经典的并发问题。如何防止同一时间段内同一辆车被重复预订这需要在数据库层面如乐观锁、悲观锁或应用层面如分布式锁做处理。用户与权限体系通常涉及普通用户、客服、车辆管理员、系统管理员等多角色不同角色看到的数据和操作权限截然不同。对应的技术挑战包括后端架构如何组织 Controller、Service、Repository 层如何设计 RESTful API如何统一处理认证、授权、异常、日志前端工程化如何管理复杂的组件状态如何优雅地调用后端 API如何实现路由权限控制数据一致性在涉及订单、支付、库存变更时如何保证事务的ACID特性可维护性与扩展性当需要增加“智能推荐”或更复杂的“操作日志审计”功能时现有架构是否能平滑接入理解了这些我们的技术选型才有了意义。Spring Boot 提供了快速构建稳健后端服务的能力而 Vue 3 的响应式系统和组合式 API 非常适合构建复杂交互的前端界面。2. 技术栈选型与环境准备一个清晰的技术栈是项目成功的基石。以下是本项目的核心技术选型及版本说明。后端技术栈 (Spring Boot 3.x)框架Spring Boot 3.1.x (选用3.x以支持Java 17特性如Record类)安全Spring Security 6.x JWT (用于API接口认证与授权)数据层Spring Data JPA Hibernate (快速ORM开发) 数据库选用 MySQL 8.x缓存Spring Boot Starter Cache Redis (用于缓存车辆信息、城市列表等)文档SpringDoc OpenAPI 3 (替代传统的Swagger2生成API文档)工具Lombok (减少样板代码) MapStruct (高效对象映射) Hutool (工具类)前端技术栈 (Vue 3)框架Vue 3.3.x (使用script setup语法)构建工具Vite 4.x (极速的开发服务器和构建工具)路由Vue Router 4.x状态管理Pinia 2.x (Vue官方推荐的状态管理库比Vuex更简洁)UI组件库Element Plus (基于Vue 3的组件库成熟且丰富)HTTP客户端Axios工具TypeScript (增强代码类型安全) ESLint Prettier (代码规范)开发环境准备JDK确保安装 JDK 17 或更高版本。Spring Boot 3.x 最低要求 JDK 17。java -versionNode.js安装 Node.js 18.x LTS 或更高版本并包含 npm。node -v npm -vIDE后端推荐 IntelliJ IDEA Ultimate 或 VS Code Spring Boot插件。前端推荐 VS Code并安装Volar插件。数据库安装 MySQL 8.x并创建一个名为car_rental的数据库。CREATE DATABASE IF NOT EXISTS car_rental DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;Redis安装 Redis用于会话管理和缓存。3. 后端核心Spring Boot 项目结构与领域模型设计我们采用经典的分层架构Controller - Service - Repository。但关键在于领域模型的设计。核心实体关系设计车辆(Car)、用户(User)、订单(RentalOrder)是三大核心实体。此外还需要CarModel(车型)、Location(租还车网点)、Payment(支付记录)等。-- 简化的核心表结构示意 CREATE TABLE car ( id bigint NOT NULL AUTO_INCREMENT, license_plate varchar(20) NOT NULL COMMENT 车牌号, car_model_id bigint NOT NULL COMMENT 车型ID, current_status varchar(20) NOT NULL DEFAULT AVAILABLE COMMENT 当前状态: AVAILABLE, RENTED, MAINTENANCE, current_location_id bigint COMMENT 当前所在网点ID, mileage int DEFAULT 0 COMMENT 里程数, daily_price decimal(10,2) NOT NULL COMMENT 日租金, PRIMARY KEY (id), UNIQUE KEY uk_license_plate (license_plate) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; CREATE TABLE rental_order ( id bigint NOT NULL AUTO_INCREMENT, order_no varchar(32) NOT NULL COMMENT 订单号, user_id bigint NOT NULL COMMENT 用户ID, car_id bigint NOT NULL COMMENT 车辆ID, start_time datetime NOT NULL COMMENT 取车时间, end_time datetime NOT NULL COMMENT 还车时间, total_amount decimal(10,2) NOT NULL COMMENT 订单总金额, order_status varchar(20) NOT NULL DEFAULT PENDING COMMENT 订单状态: PENDING, CONFIRMED, IN_PROGRESS, COMPLETED, CANCELLED, create_time datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uk_order_no (order_no) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;对应的Java实体类示例// 文件路径src/main/java/com/example/carrental/entity/Car.java import jakarta.persistence.*; import lombok.Data; import java.math.BigDecimal; Entity Table(name car) Data // Lombok注解自动生成getter, setter, toString等 public class Car { Id GeneratedValue(strategy GenerationType.IDENTITY) private Long id; Column(nullable false, unique true) private String licensePlate; ManyToOne JoinColumn(name car_model_id, nullable false) private CarModel model; Enumerated(EnumType.STRING) Column(length 20) private CarStatus currentStatus CarStatus.AVAILABLE; ManyToOne JoinColumn(name current_location_id) private Location currentLocation; private Integer mileage; private BigDecimal dailyPrice; public enum CarStatus { AVAILABLE, RENTED, UNDER_MAINTENANCE, UNAVAILABLE } }Spring Boot 主配置# 文件路径src/main/resources/application.yml spring: datasource: url: jdbc:mysql://localhost:3306/car_rental?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai username: root password: your_password driver-class-name: com.mysql.cj.jdbc.Driver jpa: hibernate: ddl-auto: update # 开发环境可用update生产环境用validate或none show-sql: true properties: hibernate: format_sql: true data: redis: host: localhost port: 6379 server: port: 8080 # JWT配置 jwt: secret: your-256-bit-secret-key-change-in-production expiration: 86400000 # 24小时单位毫秒 # 自定义配置 app: cors: allowed-origins: http://localhost:5173 # Vite默认前端地址4. 关键业务逻辑实现以创建订单为例创建订单是系统的核心流程涉及业务规则校验、状态变更和事务管理。Service层实现// 文件路径src/main/java/com/example/carrental/service/impl/RentalOrderServiceImpl.java Service Transactional Slf4j public class RentalOrderServiceImpl implements RentalOrderService { Autowired private CarRepository carRepository; Autowired private RentalOrderRepository orderRepository; Autowired private UserRepository userRepository; Override public RentalOrder createOrder(OrderCreateRequest request, Long userId) { // 1. 参数校验 if (request.getStartTime().isAfter(request.getEndTime())) { throw new BusinessException(租车开始时间不能晚于结束时间); } // 2. 检查车辆是否存在且可用 Car car carRepository.findById(request.getCarId()) .orElseThrow(() - new BusinessException(车辆不存在)); if (car.getCurrentStatus() ! Car.CarStatus.AVAILABLE) { throw new BusinessException(该车辆当前不可租); } // 3. 检查时间冲突核心 boolean isConflict orderRepository.existsByCarIdAndTimeRange( request.getCarId(), request.getStartTime(), request.getEndTime() ); if (isConflict) { throw new BusinessException(该车辆在所选时间段内已被预订); } // 4. 获取用户信息 User user userRepository.findById(userId) .orElseThrow(() - new BusinessException(用户不存在)); // 5. 计算租金 long days ChronoUnit.DAYS.between(request.getStartTime(), request.getEndTime()); BigDecimal totalAmount car.getDailyPrice().multiply(BigDecimal.valueOf(days)); // 6. 创建订单实体 RentalOrder order new RentalOrder(); order.setOrderNo(generateOrderNo()); // 生成唯一订单号 order.setUser(user); order.setCar(car); order.setStartTime(request.getStartTime()); order.setEndTime(request.getEndTime()); order.setTotalAmount(totalAmount); order.setOrderStatus(RentalOrder.OrderStatus.PENDING); // 初始状态为待确认 // 7. 更新车辆状态在同一个事务中 car.setCurrentStatus(Car.CarStatus.RENTED); carRepository.save(car); RentalOrder savedOrder orderRepository.save(order); log.info(订单创建成功订单号{}用户{}车辆{}, savedOrder.getOrderNo(), userId, car.getLicensePlate()); return savedOrder; } private String generateOrderNo() { // 示例时间戳随机数生产环境建议用更健壮的分布式ID生成器 return ORD System.currentTimeMillis() (int)(Math.random() * 1000); } }Controller层实现// 文件路径src/main/java/com/example/carrental/controller/RentalOrderController.java RestController RequestMapping(/api/orders) RequiredArgsConstructor public class RentalOrderController { private final RentalOrderService orderService; PostMapping public ResponseEntityApiResponseRentalOrder createOrder(Valid RequestBody OrderCreateRequest request, Principal principal) { // 从JWT中获取当前用户ID Long userId Long.parseLong(principal.getName()); RentalOrder order orderService.createOrder(request, userId); return ResponseEntity.ok(ApiResponse.success(order)); } GetMapping(/{id}) public ResponseEntityApiResponseRentalOrder getOrder(PathVariable Long id, Principal principal) { // 这里应添加权限校验确保用户只能查看自己的订单 RentalOrder order orderService.getOrderByIdAndUserId(id, Long.parseLong(principal.getName())); return ResponseEntity.ok(ApiResponse.success(order)); } }DTO数据传输对象示例// 文件路径src/main/java/com/example/carrental/dto/request/OrderCreateRequest.java Data public class OrderCreateRequest { NotNull(message 车辆ID不能为空) private Long carId; NotNull(message 开始时间不能为空) FutureOrPresent(message 开始时间必须是现在或将来) private LocalDateTime startTime; NotNull(message 结束时间不能为空) Future(message 结束时间必须是将来) private LocalDateTime endTime; }5. 前端核心Vue 3 Pinia 实现车辆浏览与预订前端采用模块化设计使用Pinia管理全局状态如用户信息、购物车。Pinia Store 示例 (用户与车辆状态)// 文件路径src/stores/carStore.js import { defineStore } from pinia import { ref, computed } from vue import axios from axios export const useCarStore defineStore(car, () { // 状态 const carList ref([]) const selectedCar ref(null) const searchParams ref({ location: , startDate: , endDate: , carType: }) // Getter const availableCars computed(() { return carList.value.filter(car car.status AVAILABLE) }) // Action const fetchCars async (params {}) { try { const response await axios.get(/api/cars, { params }) carList.value response.data.data } catch (error) { console.error(获取车辆列表失败:, error) throw error } } const selectCar (car) { selectedCar.value car } const clearSelection () { selectedCar.value null } return { carList, selectedCar, searchParams, availableCars, fetchCars, selectCar, clearSelection } })车辆列表与预订组件示例!-- 文件路径src/views/CarList.vue -- template div classcar-list el-row :gutter20 !-- 搜索条件区域 -- el-col :span6 el-card el-form :modelsearchForm label-width80px el-form-item label取车地点 el-select v-modelsearchForm.location placeholder请选择 el-option label全部 value/el-option el-option v-forloc in locations :keyloc.id :labelloc.name :valueloc.id/el-option /el-select /el-form-item el-form-item label取车时间 el-date-picker v-modelsearchForm.startDate typedatetime placeholder选择日期时间 value-formatYYYY-MM-DD HH:mm:ss / /el-form-item el-form-item label还车时间 el-date-picker v-modelsearchForm.endDate typedatetime placeholder选择日期时间 value-formatYYYY-MM-DD HH:mm:ss / /el-form-item el-form-item el-button typeprimary clickhandleSearch搜索车辆/el-button /el-form-item /el-form /el-card /el-col !-- 车辆列表区域 -- el-col :span18 el-row :gutter20 el-col :span8 v-forcar in carStore.carList :keycar.id el-card :body-style{ padding: 0px } img :srccar.imageUrl classcar-image / div stylepadding: 14px; h3{{ car.model.brand }} {{ car.model.name }}/h3 div classcar-info span车牌{{ car.licensePlate }}/span el-tag :typecar.status AVAILABLE ? success : danger {{ car.status AVAILABLE ? 可租 : 已租 }} /el-tag /div div classbottom span classprice¥{{ car.dailyPrice }}/天/span el-button typeprimary :disabledcar.status ! AVAILABLE clickhandleBook(car) 立即预订 /el-button /div /div /el-card /el-col /el-row /el-col /el-row !-- 预订对话框 -- el-dialog v-modeldialogVisible title确认预订 width30% div v-ifselectedCar p车型{{ selectedCar.model.brand }} {{ selectedCar.model.name }}/p p取还时间{{ searchForm.startDate }} 至 {{ searchForm.endDate }}/p p预计租金strong¥{{ calculatedPrice }}/strong/p /div template #footer span classdialog-footer el-button clickdialogVisible false取消/el-button el-button typeprimary clickconfirmBooking :loadingloading 确认下单 /el-button /span /template /el-dialog /div /template script setup import { ref, computed, onMounted } from vue import { useCarStore } from /stores/carStore import { useOrderStore } from /stores/orderStore import { ElMessage } from element-plus import axios from axios const carStore useCarStore() const orderStore useOrderStore() const searchForm ref({ location: , startDate: , endDate: , carType: }) const locations ref([]) const dialogVisible ref(false) const selectedCar ref(null) const loading ref(false) // 计算租金 const calculatedPrice computed(() { if (!selectedCar.value || !searchForm.value.startDate || !searchForm.value.endDate) return 0 const start new Date(searchForm.value.startDate) const end new Date(searchForm.value.endDate) const days Math.ceil((end - start) / (1000 * 60 * 60 * 24)) return days * selectedCar.value.dailyPrice }) onMounted(async () { // 加载初始数据 await carStore.fetchCars() await fetchLocations() }) const fetchLocations async () { const response await axios.get(/api/locations) locations.value response.data.data } const handleSearch async () { await carStore.fetchCars(searchForm.value) } const handleBook (car) { if (!searchForm.value.startDate || !searchForm.value.endDate) { ElMessage.warning(请先选择取还车时间) return } selectedCar.value car dialogVisible.value true } const confirmBooking async () { loading.value true try { const orderData { carId: selectedCar.value.id, startTime: searchForm.value.startDate, endTime: searchForm.value.endDate } await orderStore.createOrder(orderData) ElMessage.success(预订成功) dialogVisible.value false // 刷新车辆列表 await carStore.fetchCars(searchForm.value) } catch (error) { ElMessage.error(error.response?.data?.message || 预订失败) } finally { loading.value false } } /script6. 进阶功能智能推荐与操作日志智能推荐功能设计在汽车租赁场景推荐可以基于协同过滤与你相似的用户租了哪些车基于内容的推荐你之前租过SUV可能还喜欢其他SUV。热门推荐近期最受欢迎的车型。我们可以设计一个简单的推荐服务// 文件路径src/main/java/com/example/carrental/service/RecommendationService.java Service public class RecommendationService { Autowired private RentalOrderRepository orderRepository; Autowired private CarRepository carRepository; /** * 为用户推荐车辆 * param userId 用户ID * param limit 推荐数量 * return 推荐车辆列表 */ public ListCar recommendCarsForUser(Long userId, int limit) { // 1. 获取用户历史订单中的车型 ListLong userRentedCarModelIds orderRepository.findDistinctCarModelIdsByUserId(userId); ListCar recommendations new ArrayList(); if (!userRentedCarModelIds.isEmpty()) { // 2. 基于内容推荐推荐同车型的其他可用车辆 recommendations carRepository.findAvailableCarsByModelIds(userRentedCarModelIds, limit); } // 3. 如果推荐不足补充热门车型 if (recommendations.size() limit) { ListCar popularCars carRepository.findPopularAvailableCars(limit - recommendations.size()); recommendations.addAll(popularCars); } // 去重 return recommendations.stream().distinct().collect(Collectors.toList()); } }操作日志切面实现操作日志对于后台管理系统至关重要。我们可以使用Spring AOP统一记录。// 文件路径src/main/java/com/example/carrental/aspect/OperationLogAspect.java Aspect Component Slf4j public class OperationLogAspect { Autowired private OperationLogService logService; Around(annotation(operationLog)) public Object around(ProceedingJoinPoint joinPoint, OperationLog operationLog) throws Throwable { String methodName joinPoint.getSignature().getName(); String module operationLog.module(); String type operationLog.type(); String desc operationLog.desc(); // 获取当前用户从SecurityContext Authentication authentication SecurityContextHolder.getContext().getAuthentication(); String username authentication ! null ? authentication.getName() : anonymous; // 获取请求参数 Object[] args joinPoint.getArgs(); String params JSONUtil.toJsonStr(args); long startTime System.currentTimeMillis(); Object result; try { result joinPoint.proceed(); // 执行原方法 long endTime System.currentTimeMillis(); // 记录成功日志 logService.saveLog(username, module, type, desc, params, true, 成功, endTime - startTime); return result; } catch (Exception e) { long endTime System.currentTimeMillis(); // 记录失败日志 logService.saveLog(username, module, type, desc, params, false, e.getMessage(), endTime - startTime); throw e; } } } // 自定义注解 Target(ElementType.METHOD) Retention(RetentionPolicy.RUNTIME) public interface OperationLog { String module() default ; // 模块名如车辆管理 String type() default ; // 操作类型如新增、删除、修改 String desc() default ; // 操作描述 } // 在Controller方法上使用 RestController RequestMapping(/api/admin/cars) public class CarAdminController { PostMapping OperationLog(module 车辆管理, type 新增, desc 添加新车) public ApiResponseCar addCar(RequestBody Car car) { // ... 业务逻辑 } }7. 项目运行与效果验证后端启动确保MySQL和Redis服务已启动。在项目根目录下执行./mvnw spring-boot:run # 或使用Maven mvn spring-boot:run看到类似以下日志表示启动成功Started CarRentalApplication in 5.123 seconds (process running for 5.456)访问http://localhost:8080/swagger-ui.html查看自动生成的API文档。前端启动进入前端项目目录 (frontend)。安装依赖并启动开发服务器npm install npm run dev看到以下输出表示启动成功VITE v4.5.0 ready in 320 ms ➜ Local: http://localhost:5173/在浏览器打开http://localhost:5173访问前端应用。核心功能验证流程用户注册/登录使用前端界面注册账号并登录观察后端控制台JWT Token生成日志。浏览车辆在前端选择取还车时间和地点点击搜索查看车辆列表是否正确筛选和显示。创建订单选择一辆可用车辆点击“立即预订”填写信息并提交。观察前端是否成功收到订单创建响应。数据库rental_order表是否新增记录。对应车辆car表的状态是否从AVAILABLE变为RENTED。查看订单在“我的订单”页面查看刚创建的订单详情。后台操作日志以后台管理员身份登录进行车辆增删改操作检查数据库operation_log表是否记录了相应日志。8. 常见问题与排查思路问题现象可能原因排查方式解决方案前端访问后端API 4041. 后端服务未启动2. 端口被占用3. API路径错误1. 检查后端控制台日志2.netstat -ano | findstr :8080(Windows)3. 核对Swagger文档中的API路径1. 重启后端2. 杀死占用端口的进程或修改server.port3. 修正前端请求URL前端跨域(CORS)错误后端未配置CORS或配置不正确浏览器开发者工具Network标签查看错误详情检查后端application.yml中的app.cors.allowed-origins配置确保包含前端地址(如http://localhost:5173)数据库连接失败1. MySQL服务未启动2. 连接URL、用户名、密码错误3. 数据库car_rental不存在查看后端启动日志中的数据库连接错误信息1. 启动MySQL服务2. 核对application.yml中的spring.datasource配置3. 执行SQL创建数据库JWT认证失败1. 请求头未携带Token2. Token已过期3. Token密钥不匹配1. 检查前端请求头Authorization: Bearer token2. 后端日志查看JWT解析错误1. 确保登录后Token被正确存储和发送2. 重新登录获取新Token3. 检查前后端JWT密钥(jwt.secret)是否一致车辆状态更新异常并发预订导致数据不一致查看订单创建逻辑检查是否存在“先查后改”的非原子操作在createOrder方法上添加Transactional并使用数据库悲观锁(SELECT ... FOR UPDATE)或应用层分布式锁Vue页面数据不更新1. Pinia状态未正确响应2. 组件未正确使用ref/reactive3. 异步操作未处理1. 使用Vue Devtools检查Pinia状态2. 检查组件代码确保使用ref/reactive包装响应式数据1. 确保在Pinia的action中正确修改state2. 在模板中使用.value访问ref值3. 使用async/await或.then()处理异步调用9. 最佳实践与工程建议API设计规范使用RESTful风格资源使用复数名词如/api/cars,/api/orders。统一响应格式例如{ code: 200, message: “success”, data: {} }。使用HTTP状态码正确反映结果200成功400客户端错误401未认证403无权限500服务器错误。安全性密码存储务必使用BCrypt等强哈希算法加密切勿明文存储。SQL注入坚持使用JPA的查询方法或Query注解避免拼接SQL字符串。XSS防护前端对用户输入进行转义后端可考虑使用HttpServletResponse设置安全头。权限控制使用PreAuthorize注解在方法级别进行细粒度控制。性能优化数据库索引为经常查询的字段如order_no,user_id,car_id,start_time,end_time添加索引。缓存策略对不常变但高频访问的数据如车型列表、城市列表使用Redis缓存。分页查询列表接口务必支持分页避免一次性加载大量数据。代码质量后端使用Valid进行参数校验使用全局异常处理器(ControllerAdvice)统一处理异常业务逻辑放在Service层Controller保持轻薄。前端组件按功能拆分保持单一职责使用Composition API逻辑复用Pinia Store避免存储过多非全局状态。部署与监控使用Docker容器化部署保证环境一致性。生产环境务必关闭JPA的ddl-auto: update使用Flyway或Liquibase进行数据库版本管理。集成Spring Boot Actuator用于健康检查和应用监控。日志使用Logback或Log4j2并合理配置日志级别和输出格式便于问题排查。这个项目麻雀虽小五脏俱全。它涵盖了从前端到后端从数据库设计到业务逻辑从基础增删改查到进阶功能推荐、日志的全链路开发。通过亲手实现这样一个系统你不仅能巩固Spring Boot和Vue3的技术栈更能深刻理解一个真实业务系统是如何从设计到落地的。建议你在理解本文代码的基础上尝试扩展更多功能如优惠券系统、车辆保险、在线支付集成等这会让你的项目经验更加丰满。