☰
SpringBoot+Vue打造私人西服定制系统:从建模到部署全解析
2026/10/9 3:07:05 网站建设 项目流程

做私人西服定制系统这个选题,我是有私心的。市面上讲SpringBoot + Vue的教程特别多,但大多围着「增删改查」打转,做完以后你会发现,它其实不适合拿去应对真实业务。私人西服定制看着是个小店生意,真拆开以后,链路比想象中长得多:量体数据怎么存、定制项怎么算价、订单状态怎么流转、复购的时候怎么快速调出老客户的体型档案……这些才是系统的灵魂。这篇文章我打算从业务场景下手,把SpringBoot + Vue这套组合在定制行业里真正用得上的设计思路、建模方法、踩坑经历都梳理一遍,给正要动手做类似全栈项目的朋友一份能直接照着走的参考。

1. 私人西服定制的业务场景:先搞清楚这不是一个电商项目

很多人一听到「定制系统」,下意识就往电商商城上靠,上来就做商品列表、购物车、下单支付。这一步就把方向带偏了。西服定制和标准码服装销售的逻辑完全不一样,它的核心不是「卖货」,而是「服务链路」。

1.1 一套西服从预约到交付,中间到底有多少个环节

我在设计这个系统之前,先花了一周时间泡在朋友的定制工坊里看他们接单。整个流程大概是这样的:

  • 顾客通过电话或者到店预约量体时间;
  • 量体师用皮尺记录二十多项身体数据,包括衣长、胸围、肩宽、袖长、腰围、臀围,以及一些特殊体型修正项;
  • 顾客在店里选择面料,可能是几百种进口羊毛面料中的一种,还要选领型、口袋样式、扣子数量、内衬材质、刺绣内容;
  • 店长根据量体数据和面料信息核算价格,报价给顾客;
  • 顾客确认后,订单进入制版环节,版型师打版、裁剪;
  • 缝制完成后,顾客到店试穿半成品,师傅标记修改点;
  • 返修后再次试穿,最终交付。

这中间任何一个环节的信息断了,后面都得多花几个小时重新沟通。传统做法是纸质单据加老师傅的脑子,订单一多就全乱。所以这个系统真正要解决的,不是「让顾客能在网上下单」,而是「把整条定制服务链路数字化,让每个角色在正确的时间看到正确的信息」。

1.2 系统里都有哪些角色,各自想要什么

角色核心诉求系统里对应的功能
顾客查看面料款式、提交量体预约、跟踪订单进度款式浏览、预约提交、订单状态查询
量体师快速录入体型数据,查看历史数据辅助判断量体数据管理、历史体型对比
店长/管理员管理面料库存、配置定制项、核算价格面料管理、定制项配置、订单审核
版型师/缝纫工查看订单工艺要求,更新生产进度订单工艺单、生产状态流转
系统管理员维护用户权限、监控系统运行用户管理、角色权限配置

把角色和诉求列清楚以后,功能模块的边界就自然出来了。我见过不少毕设项目把「用户管理」做得特别重,但真正的业务价值恰恰在量体数据、订单状态机、定制项配置这几块,这些才是这个系统的技术难点和亮点所在。

1.3 复购场景和尺码漂移:容易被忽略的设计痛点

还有一个特别容易忽略的场景:老顾客复购。西服定制行业的老顾客复购率很高,很多客户一年要做两三套。这时候量体师最需要的是什么?是客户上次的量体数据和体型变化记录。人会长胖也会变瘦,两年前的胸围数据不能直接套用,但可以作为参考基准。

所以量体数据一定不能和订单绑死,而要独立建表、和客户实体关联。每次量体生成一条新记录,同时保留历史记录用于对比。这个设计思路也直接影响了后面的数据库建模,我会在接下来的章节里详细展开。

2. 技术选型的权衡:为什么是SpringBoot + Vue,版本怎么配

技术选型没有绝对的对错,只看合不合适。SpringBoot + Vue 这个组合在国内全栈项目里属于「标准答案」级别的方案,但版本搭配和项目结构还是有不少讲究,这里把我最终落地的方案说清楚。

2.1 这套组合解决了什么问题、替代方案是什么

SpringBoot 负责后端接口和业务逻辑,Vue 负责前端页面和交互体验。为什么选它们,不选别的?

后端方面,SpringBoot 的核心价值是「约定大于配置」。Java 生态的稳定性和 Spring 全家桶的成熟度不用多说,特别是事务管理、安全框架、ORM 整合这些方面,SpringBoot 的解决方案都是经过海量生产环境验证的。相比之下,Node.js 的 Express 或者 Python 的 Flask、Django 写起来确实快,但这种垂直行业的业务系统,后端往往需要稳定可靠的事务支撑和清晰的工程分层,SpringBoot 更合适。

前端方面,Vue 的优势在于学习曲线平缓、中文生态好、配套组件库完善。React 也很强,但如果是中小型团队或者个人开发者,Vue 的开发效率和上手难度都要友好一些。加上 Element Plus 这套组件库,做管理后台和业务表单的效率非常高。

2.2 SpringBoot 版本选择:3.x 还是 2.x,坑在哪

这里必须单独说一嘴版本问题。现在网上的教程一半以上还在用 SpringBoot 2.x,但 SpringBoot 3.x 已经发布很久了,很多人直接新建项目,自动就生成 3.x 版本,结果照着旧教程写代码,一堆报错。

最大的坑就是javax 到 jakarta 的包名迁移。SpringBoot 3.x 基于 Jakarta EE 9,所有原来javax.servlet、javax.persistence开头的包都换成了jakarta.*。如果你的项目用了import javax.servlet.http.HttpServletRequest,在 3.x 环境下直接编译不通过。

我的建议是:如果这是毕业设计或者企业级项目,直接用 SpringBoot 3.x + JDK 17,因为这是目前的趋势,面试问起来也更有说服力。如果只是快速验证想法,SpringBoot 2.7.x + JDK 8 的生态兼容性最稳,市面上几乎所有老教程都能直接用。

我最终选的是这一套:

技术组件版本选择说明
JDK17长期支持版本,配合 SpringBoot 3.x
SpringBoot3.2.x稳定版本,支持虚拟线程
MyBatis-Plus3.5.x增强 MyBatis,内置分页和代码生成器
MySQL8.0主流关系型数据库
Redis7.x缓存、验证码存储、分布式 Session
Vue3.4.x组合式 API 语法
Vite5.x前端构建工具,比 Webpack 快一个量级
Element Plus2.xVue3 配套组件库
Pinia2.x状态管理,Vuex 的替代方案

2.3 前后端项目结构:从零搭建的标准姿势

后端做的是经典分层架构。我建了一个 Maven 多模块或者单模块项目,包结构如下:

com.yourcompany.tailor ├── controller // 接口层,只做参数接收和结果返回 ├── service // 业务逻辑层,事务边界在这里 ├── mapper // MyBatis-Plus 的 DAO 层 ├── entity // 数据库实体类 ├── dto // 接口传输对象,避免实体直接暴露 ├── vo // 视图对象,按需组装返回给前端 ├── config // 配置类:CORS、拦截器、MyBatis-Plus 配置 ├── common // 通用返回结果、异常处理、常量 └── utils // 工具类:JWT、日期处理、加密

前端用 Vite 创建 Vue3 项目,目录设计遵循「按业务模块划分」的原则:

src ├── api // 接口请求封装,按模块拆分 ├── assets // 静态资源 ├── components // 公共组件 ├── router // 路由配置 ├── store // Pinia 状态管理 └── views // 页面组件,按业务模块建文件夹 ├── customer // 顾客端页面 ├── order // 订单管理 ├── measurement // 量体数据 └── system // 系统管理

不要小看目录结构的规范程度。前后端联调的时候,接口路径、返回结构、状态码能不能对齐,很大程度取决于一开始的约定是否清晰。我通常会在一开始就定义好统一的返回格式,比如{ code: 200, message: "success", data: {...} },后面前后端所有模块都按这个规范来。

3. 数据库建模:把量体数据和定制项这些非结构化信息结构化

数据库建模是整个系统最核心的部分,没有之一。西服定制系统最复杂的地方不在于订单表怎么建,而在于量体数据、定制项、价格策略这些「非标准信息」怎么建模才能既灵活又高效。

3.1 核心表设计:八张表撑起整个业务

我把整个系统的数据表拆成了几个核心模块,整理一下大致是这样的:

  • 用户表(sys_user):存储所有登录用户,包括顾客、量体师、店长、管理员,通过role字段区分;
  • 客户表(customer):存储顾客档案,姓名、电话、生日、会员等级;
  • 量体数据表(measurement):这是核心中的核心,每行记录一位顾客某次量体的完整数据,包含数十个尺寸字段,通过customer_id关联客户,用measured_at标记量体时间;
  • 面料表(fabric):面料编号、名称、成分、颜色、单价(元/米)、库存;
  • 款式表(style):西服版型,比如修身款、标准款、宽松款,同时配置可选的领型、口袋样式、扣子数等定制项;
  • 订单表(order):订单主表,关联客户、量体数据、面料、版型,以及总价和订单状态;
  • 订单明细表(order_item):记录该订单选择了哪些定制项,比如刺绣文字、特殊里料,每个定制项独立计价;
  • 订单状态记录表(order_status_log):记录订单状态变更的历史轨迹。

这里我特别想强调一下:为什么量体数据要独立建表,而不用所谓的「JSON 字段塞进订单里」这种偷懒方案?

因为量体数据本质上是一个客户的长期资产。一位顾客可能在这个店里做了五年衣服,量了十次体,这些数据按时间排列就是他的「体型变化曲线」。量体师在给老客户做新衣服时,调出最近两次的量体数据,马上能判断出客户体型变化趋势,哪些尺寸要放宽、哪些要收窄,这个参考价值极大。

3.2 量体数据表怎么建:字段拆解和单位设计

量体数据的单位设计也踩过坑。网上很多教程里尺寸字段直接用「胸围」「腰围」命名,但真正到了系统里,不同的人可能录入的单位不同——有人习惯用厘米,有人可能用英寸。为了避免单位混乱,我在设计表结构时有两条路:

  1. 所有字段统一用厘米,录入界面做单位换算;
  2. 增加一个unit字段记录单位,所有计算逻辑先统一转成基准单位。

我的选择是第一种,数据库统一存厘米。前端录入页面上做一个「英寸/厘米」的切换开关,提交前统一换算成厘米再传给后端。这样既照顾了量体师的使用习惯,又保证了数据库存储的一致性。

字段方面,至少要包含这些核心尺寸:

字段名说明合理范围校验
height身高120 - 230 cm
bust胸围60 - 200 cm
waist腰围50 - 180 cm
hip臀围60 - 210 cm
shoulder_width肩宽30 - 70 cm
sleeve_length袖长40 - 90 cm
garment_length衣长50 - 130 cm
neck颈围30 - 60 cm
back_width背宽30 - 70 cm
bicep臂围20 - 60 cm

注意加校验范围的逻辑。量体师手工录入时难免手误,比如把 95 误填成 9.5,系统应该在录入时就拦截。我做了一个自定义校验注解,在 DTO 层做范围校验,不符合直接报错。

3.3 定制项和价格计算:不能用电商的 SKU 思维硬套

西服定制的可选配置非常多,面料不同、领型不同、刺绣要钱、加某种工艺要加钱。电商系统一般用 SKU(库存量单位)来管理,每个 SKU 对应一个价格。但定制业务中,组合可能性是乘法级别的——几百种面料乘以几十种工艺组合,用 SKU 去穷举根本不可行。

我的方案是「基础价 + 加价项」模式:

  • 基础价:由版型 + 面料决定。面料有每米的单价,一件西服约用 1.6 米到 2.5 米面料,实际用料根据身高体重估算;
  • 加价项:领型、口袋样式、刺绣、特殊里料等,每个选项配一个单独的加价金额;
  • 总价 = 面料单价 × 预计用料 + 基础工艺费 + 所有加价项之和。

前端在下单向导里实时计算总价,每次勾选一个加价项就重新计算一次。这个逻辑用 Vue 的计算属性实现非常自然。后端在下单接口里再次核算总价,防止前端篡改价格——这是电商项目里必须注意的安全边界。

3.4 订单状态流转和状态记录表

定制订单的状态和普通电商订单还有很大区别。电商订单无非待付款、待发货、待收货、已完成,定制订单则是:

待量体 → 量体完成 → 待确认方案 → 已确认/待付款 → 制版中 → 裁剪中 → 缝制中 → 待试穿 → 返修中 → 已完成

每个状态变更可能由不同角色触发,比如量体师把「待量体」改成「量体完成」,店长把「待确认方案」改成「已付款」,缝纫小组把「缝制中」改成「待试穿」。

我单独建了一张order_status_log表,记录每次状态变更的操作人、变更前后状态、变更时间和备注。这张表的好处在于:

  • 顾客前端可以展示一条完整的「订单时间轴」,从预约到交付每一步时间清清楚楚;
  • 遇到纠纷可以回溯到底是谁在什么时间把状态改错了;
  • 后续做数据分析,比如统计平均每单的缝制时长,也可以直接从日志表查区间。

4. 后端核心模块:订单状态机与定制项价格计算

后端开发是工作量最大的部分。这一节不会把每个接口的代码都贴一遍,而是把几个核心模块的实现思路和关键代码讲透,毕竟这部分的逻辑是整个系统的技术调性和亮点所在。

4.1 用户认证与权限控制:JWT 方案落地

这个系统的角色比较多,顾客、量体师、店长、管理员各有各的操作边界。我用的是 JWT(JSON Web Token)做认证,配合 Spring Security 或者拦截器做权限控制。

JWT 的流程很经典:用户登录成功后,后端生成 token 返回给前端;前端每次请求在请求头加上Authorization: Bearer <token>;后端拦截器验证 token 的合法性,并从 token 中解析出用户 ID 和角色信息,放入请求上下文。

关键代码是自定义一个拦截器或用 Spring Security 的过滤器链。我选了不用 Spring Security、而是写拦截器的方案,理由是:Spring Security 的配置复杂度偏高,自定义拦截器在这个规模的项目里更可控,也更容易被没接触过安全框架的同学看懂。

public class JwtInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) { // 放行登录接口和静态资源 if (request.getRequestURI().contains("/auth/login")) { return true; } String token = request.getHeader("Authorization"); // 解析 token,校验签名和过期时间 Claims claims = JwtUtil.parseToken(token.replace("Bearer ", "")); // 将用户信息存入 request attribute,供后续业务逻辑使用 request.setAttribute("userId", claims.get("userId")); request.setAttribute("role", claims.get("role")); return true; } }

这里要特别加一个处理:自定义拦截器无法直接给 Handler 的参数注入自定义对象,但在 controller 里可以通过@RequestAttribute获取拦截器存入的用户信息。权限判断就在拦截器里做——先解析出角色,再判断该角色是否允许访问当前路径。

我用了一个简单的路径 + 角色映射表来配置权限规则,比如/api/customer/**需要顾客或管理员角色,/api/measurement/**需要量体师或管理员角色。没有用复杂的注解驱动,因为这个阶段更重要的是让权限规则能对着业务方讲清楚。

4.2 量体数据录入接口:联动校验是难点

量体数据录入接口不复杂,最难的地方在于数据校验。二十多个尺寸字段,每个都有范围,还有一些联动约束——比如袖长不可能比臂长还长,衣长通常大于袖长。这些专业约束如果不在后端校验,脏数据进了库,后续打版全都会错。

我在 DTO 上做了 JSR 303 参数校验,同时写了一个「跨字段校验器」专门处理联动逻辑。

@Data public class MeasurementDTO { @NotNull(message = "客户ID不能为空") private Long customerId; @NotNull(message = "身高不能为空") @DecimalMin(value = "120", message = "身高最小120cm") @DecimalMax(value = "230", message = "身高最大230cm") private BigDecimal height; // 其余尺寸字段省略... @AssertTrue(message = "袖长不能超过衣长") public boolean isSleeveValid() { if (sleeveLength == null || garmentLength == null) return true; return sleeveLength.compareTo(garmentLength) <= 0; } }

这里有个小技巧:@AssertTrue注解的方法会在所有字段校验完成后执行,非常适合做跨字段的联动校验,而且校验失败的错误信息能直接绑定到字段错误上,前端展示起来也直观。

4.3 下单接口:事务边界和价格的安全防线

下单是典型的「多表写入」操作,涉及主订单、订单明细、订单状态记录、可能还要扣减面料库存,必须加事务。SpringBoot 里直接在 Service 方法上标注@Transactional即可。

但如果只是单库事务,还没完——还有个被很多人忽略的问题:前端传过来的价格不能直接信任。我的做法是后端根据订单里的面料 ID、定制项 ID 重新查库核算价格,然后把前端传的总价和后台核算价做比对,不一致就不允许下单。

@Transactional(rollbackFor = Exception.class) public Long createOrder(OrderCreateDTO dto) { Fabric fabric = fabricMapper.selectById(dto.getFabricId()); BigDecimal basePrice = fabric.getPricePerMeter() .multiply(estimateFabricUsage(dto.getHeight(), dto.getBust())); BigDecimal extraPrice = calculateExtraPrice(dto.getCustomItemIds()); BigDecimal totalPrice = basePrice.add(extraPrice); // 防止前端篡改价格 if (totalPrice.compareTo(dto.getTotalPrice()) != 0) { throw new BusinessException("价格校验失败,请刷新后重试"); } // 扣减面料库存,用乐观锁防止超卖 int affected = fabricMapper.deductStock(dto.getFabricId(), 1); if (affected == 0) { throw new BusinessException("面料库存不足"); } // 创建订单主记录 Order order = new Order(); order.setCustomerId(dto.getCustomerId()); order.setMeasurementId(dto.getMeasurementId()); order.setFabricId(dto.getFabricId()); order.setTotalPrice(totalPrice); order.setStatus(OrderStatus.PENDING_MEASUREMENT); orderMapper.insert(order); // 循环插入订单明细 for (Long itemId : dto.getCustomItemIds()) { OrderItem item = new OrderItem(); item.setOrderId(order.getId()); item.setCustomItemId(itemId); orderItemMapper.insert(item); } // 记录初始状态 StatusLog log = new StatusLog(); log.setOrderId(order.getId()); log.setFromStatus(null); log.setToStatus(OrderStatus.PENDING_MEASUREMENT); log.setOperatorId(dto.getOperatorId()); statusLogMapper.insert(log); return order.getId(); }

这段代码里有几个点值得琢磨:

第一个,estimateFabricUsage估算面料用量。我用的近似公式是:用料 ≈ 身高(cm)× 0.04 + 胸围(cm)× 0.15,再乘以一个版型系数(修身款 0.95,标准款 1.0,宽松款 1.05),估算值在一定范围内取整到 0.5 的倍数,方便实际剪裁。

第二个,库存扣减用了fabricMapper.deductStock这个自定义 SQL,它就是update fabric set stock = stock - 1 where id = ? and stock > 0。这种「条件更新」天然具备乐观锁的效果,返回影响行数为 0 就说明库存不足,比「先查后改」的方式更稳妥。

第三个,状态日志表和主订单在同一个事务里写入。如果日志写失败,整个订单回滚,保证一致性。

4.4 订单状态机:用枚举 + 配置替代状态模式

很多教程喜欢用状态模式,每两个状态之间写一个类,代码量庞大而且难以维护。我的选择是「状态字段 + 合法状态迁移表」。

定义一个枚举:

public enum OrderStatus { PENDING_MEASUREMENT("待量体", 0), MEASUREMENT_DONE("量体完成", 1), WAITING_CONFIRM("待确认方案", 2), PENDING_PAYMENT("待付款", 3), CUTTING("制版裁剪中", 4), SEWING("缝制中", 5), WAITING_FITTING("待试穿", 6), ALTERING("返修中", 7), COMPLETED("已完成", 8), CANCELLED("已取消", -1); }

再做一个状态迁移校验工具类,在状态变更接口里先校验「当前状态 → 目标状态」是否在合法迁移列表里:

public class StateMachineUtils { private static final Map<OrderStatus, Set<OrderStatus>> TRANSITIONS = new HashMap<>(); static { // 待量体可以被量体师改为量体完成,或顾客取消 TRANSITIONS.put(OrderStatus.PENDING_MEASUREMENT, Set.of(OrderStatus.MEASUREMENT_DONE, OrderStatus.CANCELLED)); // 量体完成可以进入待确认方案 TRANSITIONS.put(OrderStatus.MEASUREMENT_DONE, Set.of(OrderStatus.WAITING_CONFIRM, OrderStatus.CANCELLED)); // 待确认方案可以改为待付款,也可以退回重新量体 TRANSITIONS.put(OrderStatus.WAITING_CONFIRM, Set.of(OrderStatus.PENDING_PAYMENT, OrderStatus.PENDING_MEASUREMENT)); // 其余状态迁移略... } public static boolean canTransit(OrderStatus from, OrderStatus to) { return TRANSITIONS.getOrDefault(from, Set.of()).contains(to); } }

有人可能会说,这不是比状态模式更简陋吗?其实恰恰相反。对于业务规则不复杂的系统,状态迁移表反而最直观——业务方要改流程规则,你只需要改静态代码块里的配置,而不是到处找状态类的实现改逻辑。等业务复杂度真到了需要状态模式的程度,比如每个状态有完全不同的行为策略时,再演进也不迟。

5. 前端交互:Vue 3 组合式 API 定制向导

前端不是简单的后台管理界面,它要服务两种截然不同的用户场景:顾客为了看款式、提交预约、跟踪进度,员工为了录量体数据、维护订单。所以前端在设计上要同时兼顾展示型页面和表单密集型页面。

5.1 多步骤定制向导:用 Pinia 管理草稿状态

定制下单是整个前端交互最重的部分,我把它做成了一个多步骤向导:

  1. 选择版型(修身/标准/宽松);
  2. 选择面料,支持颜色和成分筛选;
  3. 配置定制项(领型、口袋、扣子、刺绣等),实时显示加价;
  4. 确认量体数据(从最近一次量体记录自动带入,可修改);
  5. 预览订单汇总并提交。

跨步骤的数据共享是这里的难点。如果用组件内状态,一旦切换到下一步,上一步的数据就丢了;如果全部塞给路由参数,URL 会被撑得没法看。正确的方案是引入 Pinia,把整个向导的草稿状态放在 store 里。

export const useOrderDraftStore = defineStore('orderDraft', { state: () => ({ step: 1, styleId: null, fabricId: null, customItemIds: [], measurement: null, priceDetail: null }), getters: { totalPrice (state) { // 计算属性实时计算总价,任何一项变更都会触发重新计算 if (!state.priceDetail) return 0 return state.priceDetail.basePrice + state.priceDetail.extraPrice } }, actions: { setStep (step) { this.step = step }, selectStyle (styleId) { this.styleId = styleId // 切换版型后重新请求价格详情 this.fetchPriceDetail() }, async fetchPriceDetail () { const res = await api.getPriceDetail({ styleId: this.styleId, fabricId: this.fabricId, customItemIds: this.customItemIds }) this.priceDetail = res.data } } })

组合式 API 的写法在现代 Vue3 项目里也是主流,配合<script setup>语法非常清爽。上面的写法用的是选项式风格,换成组合式写法同样能实现,核心思路完全一样:全局唯一的响应式草稿,任何组件都可以读写,天然解决跨步骤数据传递问题。

5.2 量体数据录入表单:动态校验和单位切换

量体录入是量体师每天都要用的高频功能,表单体验必须做到位。我的页面设计是左侧客户信息,右侧一整排尺寸输入项,每个输入项都有单位切换和一键填入上次数据的功能。

尺寸校验做在前端,用 Element Plus 的 Form 组件的 rules 规则:

const rules = { bust: [ { required: true, message: '请输入胸围', trigger: 'blur' }, { validator: (rule, value, callback) => { if (value < 60 || value > 200) { callback(new Error('胸围范围应为60-200cm')) } else { callback() } }, trigger: 'blur' } ], // 其他字段类似... }

单位切换这里我使用了一个计算属性转换函数:所有的数据在表单中以用户当前使用的单位展示,提交的时候换成厘米。切换单位时不做数据舍入,等真正提交那一步再统一转换,避免来回切换导致精度丢失。

还有一个小细节:量体师经常需要参考历史数据判断体型变化,我在表单右侧放了一个「历史量体对比」抽屉,把最近 3 次的量体记录用表格展示,尺寸变化超过 2cm 的字段用红色标注。这个功能虽然实现起来不复杂,但是在真实业务里却是量体师最常夸赞的功能。

5.3 订单进度时间轴:用状态日志驱动

顾客端最核心的体验就是「随时知道我的西服做到哪一步了」。后端有完整的order_status_log记录,前端只要把这个列表渲染成时间轴即可。

Element Plus 的el-timeline组件正好派上用场。我把状态日志按时间倒序排列,最新状态在最顶端并高亮显示,同时展示操作时间和操作角色:

<el-timeline> <el-timeline-item v-for="log in reversedStatusLogs" :key="log.id" :timestamp="formatTime(log.createdAt)" :type="log.toStatus === currentStatus ? 'primary' : 'info'" > <div>{{ statusText[log.toStatus] }}</div> <div class="operator">{{ log.operatorName }}</div> <div v-if="log.remark" class="remark">{{ log.remark }}</div> </el-timeline-item> </el-timeline>

这个接口返回的就是statusLogMapper.selectByOrderId(orderId),按时间排序,没有额外复杂度。把一部分业务复杂度沉淀到数据库模型上,前端反而越简单越好。

5.4 路由和权限控制:动态路由按角色加载

系统有管理员、店长、量体师、顾客四种角色,每种角色看到的菜单和页面完全不同。把前端路由配置用「静态路由 + 动态路由」结合的方式处理:

静态路由放登录页、404 页这类所有人可见的页面;动态路由根据后端返回的用户权限菜单,在登录完成后通过router.addRoute动态挂载。

// 登录成功后调用 async function loadRoutes (role) { const menuData = await api.getMenusByRole(role) // 根据 menuData 动态生成路由表 menuData.forEach(item => { router.addRoute({ path: item.path, name: item.name, component: () => import(`../views/${item.componentPath}`), meta: { title: item.title, icon: item.icon } }) }) }

这里有个需要注意的细节:使用 Vite 构建时,动态导入路径不能完全使用变量,因为 Vite 需要静态分析才能打包。解决办法是提前把需要动态加载的 view 组件文件维护为一个映射表,而不是直接拼接字符串路径。这个小坑我当初查了挺久才搞明白。

6. 联调与部署踩坑实录:从控制台报错到上线跑通

最后这部分我把自己在这个项目里踩过的一些比较有代表性的坑整理出来,都是文档里很少写、但特别耽误出活的地方,供参考。

6.1 SpringBoot 3.x 的 jakarta 迁移问题

这是我前面提到过的大坑,这里再展开一下。SpringBoot 3.x 的项目里如果要用 Java 自带的 HTTP Servlet 相关对象,或者接第三方库时发现编译报错、很多类找不到,八成是包名问题。

解决方案很简单粗暴:把javax.servlet.*全部替换成jakarta.servlet.*。但如果是第三方库内部依赖了老的javax命名空间,那就要看这个库有没有适配 SpringBoot 3 的版本,比如 MyBatis-Plus 3.5.3+ 才支持 SpringBoot 3,更老版本编译直接报错。

6.2 前端 Vite 版本和 Node 版本不匹配

Vite 5.x 要求 Node.js 18+,如果你本机 Node 还停留在 16.x,npm install时会看到一串类似requires Node >=18的报错。这时候不要硬降 Vite 版本,优先升级 Node,用 nvm 管理 Node 版本最方便。

6.3 跨域问题:开发环境有一种解法,生产环境又是另一种

开发时我用 Vite 的 proxy 配置把/api前缀代理到后端服务端口,不需要后端单独配 CORS:

// vite.config.js export default defineConfig({ server: { proxy: { '/api': { target: 'http://localhost:8080', changeOrigin: true } } } })

生产环境前后端分开部署时,则要在 SpringBoot 里配置 CORS 放行规则,或者更稳妥的做法是直接用 Nginx 做反向代理,让前端页面和后端接口在同一个域名下,从根本上规避跨域问题。

6.4 Vue 样式作用域冲突

Element Plus 组件的样式覆盖是个经典问题。<style scoped>会给选择器加><style scoped> /* 修改 el-table 表头背景色 */ .table-wrapper :deep(.el-table__header th) { background-color: #f5f7fa; } </style>

另外如果还要覆盖弹窗这类 Teleport 渲染到 body 下的组件,scoped加上:deep也失效,因为弹窗内容根本不在组件 DOM 树内。这种场景要么用非scoped样式加类名前缀,要么直接把弹窗内容相关的深度选择器写到全局样式里。

6.5 部署:宝塔 Docker + Nginx 的组合拳

部署方案是比较省心的搭配:后端用 Docker 部署,前端打包成静态文件交给 Nginx。我在docker-compose.yml里把 MySQL、Redis、SpringBoot 应用编排在一起,数据目录挂载到宿主机,方便备份。

前端 build 之后把dist目录上传到服务器,Nginx 配置 root 指向它。这里有一个必须处理的点:前端用了 Vue Router 的 history 模式,刷新非首页路由时会 404,因为 Nginx 找不到对应的物理文件。必须加一个try_files配置:

location / { root /var/www/tailor-frontend/dist; try_files $uri $uri/ /index.html; }

再加一条后端接口的反向代理:

location /api/ { proxy_pass http://127.0.0.1:8080; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; }

部署完成后基本可以稳定运行,内存占用也不算高,这套组合在中小型业务的负载下绰绰有余。

6.6 上线前别忘了给用户批量初始化账号

这个细节非常容易被忽略。系统开发完以后,测试数据里难免有各种脏数据,上线时千万记得清空或者重置数据库里的测试用户,给真正要用系统的员工和店长创建好初始账号并分配角色。我在这个阶段吃过一次亏,测试时留了个无密码的管理员账号,上线第二天就被安全扫描发现,最后花了半天改密码策略和加固接口鉴权。

每次做完一个完整项目,我都会回头审视最开始画的业务流程图和数据模型。定制的本质不是把线下流程原样搬到线上,而是让数据在各个环节流动起来——量体数据、面料信息、订单状态、工艺要求,每一条都串联起来,系统才开始产生真正的业务价值。如果这篇文章能帮你少踩几个坑,后面的路会走得顺很多。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询