☰
基于SpringBoot+Vue的供应商管理系统开发实战解析
2026/10/17 2:49:15 网站建设 项目流程

1. 供应商管理系统的核心价值:从Excel台账到流程协同

先说个真实场景。很多企业内部对供应商的管理,长期停留在一张Excel表加几个微信群的状态。采购员下单前翻表格找联系方式,财务对账时发现税号录错了,老板想看年度供应商绩效排名,得等某个同事手工汇总。这套东西做到后面,问题不是没有数据,而是数据散、口径乱、责任不清。所谓"基于SpringBoot+Vue的供应商管理系统",本质就是把这摊子事收拢到一个Web系统里,让供应商档案、询价报价、采购订单、资质审核、评估记录都走同一套流程。

这套系统适合谁参考?主要是两类人:一类是要做毕业设计或课程项目的学生,需要完整跑通的Java全栈项目;一类是中小公司里想低成本搭建供应商管理工具的开发者。技术栈很标准——后端SpringBoot,持久层MyBatis,数据库MySQL,前端Vue,Java语言。没有花哨的分布式架构,没有中间件大杂烩,就是一个干净利落的管理系统样板,业务逻辑清晰,容易改造成自己需要的场景。

我为什么觉得这类项目值得动手做一遍?因为它覆盖了一个Web系统从零到上线的大部分关键环节:表结构设计、分页查询、登录鉴权、关联表事务处理、前端列表与表单交互、前后端联调。这些能力不是看书看会的,是跑完一个完整项目才真正长在身上的。

做这套系统之前,先把边界想清楚。供应商管理系统和ERP、SRM(供应商关系管理)有区别。ERP管的是计划、库存、财务,SRM是更宏观的供应商全生命周期协同平台。我这个系统定位于中间地带:管好供应商主数据、日常采购协同、基础评估,但不碰复杂的财务结算和生产计划。这样定位下来,功能边界就很清晰了。

角色划分上,常见的做法是三类角色:系统管理员负责系统配置和账号管理,采购员负责供应商入库、询价、下单、评估,供应商账号可以登录查看订单和更新自己的资料。有的项目会把供应商端单独拆成门户,但基础版本共用一套管理端即可。

2. 技术选型:为什么这套组合最省心、最难出错

2.1 后端用SpringBoot,图的是生态和快速启动

供应商管理系统这种体量的应用,用SpringBoot是成本最低的选择。SpringBoot把配置自动化做到了极致,内嵌Tomcat,Jar包一打就能跑。相比传统Spring的XML配置,SpringBoot的starter机制让集成MyBatis、连接池、校验框架都变成了加依赖写配置的事。

版本选择上,我的经验是别追新。SpringBoot用2.7.x,搭配JDK 1.8或JDK 11,兼容性最好。SpringBoot 3.x虽然已经是很成熟的东西了,但部分老版本的MyBatis插件、PageHelper依赖需要切换新的坐标,对新手不够友好。做管理类系统,稳定压倒一切。

JPA和MyBatis之争,在这类项目里有明确答案。供应商管理系统查询场景多,且查询条件组合灵活,比如按供应商名称模糊查询、按状态过滤、按分类统计。MyBatis的XML里写动态SQL非常顺手,<if>、<where>、<foreach>标签组合一下就是一套多条件查询,这是JPA的Specification做法比不了的直观。MyBatis的单表映射规则简单,学起来门槛低,特别适合快速上手。

2.2 数据库选MySQL,理由不用多说

MySQL是这类项目的事实标准。轻量、免费、文档多,Navicat和SQLyog都能连接。唯一要注意的是建表时统一用InnoDB引擎、utf8mb4字符集。字符集这块,老项目喜欢用utf8,碰到生僻字或者特殊符号比如emoji就报错,utf8mb4直接规避这个问题。

表结构设计要刻意做一定的反规范化。什么意思?就是业务上经常要展示的冗余字段,比如供应商表里直接存contact_name和contact_phone,而不是只存一个contact_id关联联系人表。管理系统的列表页需要展示高频字段,每次join一张联系人表虽然也能用,但SQL写起来啰嗦,索引和性能也不好看。合理的冗余换来的是查询的简单直接。

2.3 前端Vue层面,版本怎么选

前端这块,我用Vue3加Element Plus组件库,没有用Vue2。原因很简单:Vue3已经普及了很多年,组合式API(setup语法糖)写业务代码更清爽,Element Plus的表格、表单、弹窗组件足够覆盖管理系统的所有页面场景。如果你手里的参考代码是Vue2和Element UI,也不用焦虑,核心逻辑——组件、props、事件、路由、Axios请求——是完全通用的,改造成本很低。

工具链上用Vite构建。相比Webpack,Vite启动速度快到离谱,开发体验好一个量级。Vue Router用4.x版本,状态管理用Pinia。这套组合在2025年已经是标准答案,找资料也方便。

3. 核心表结构设计:先把供应商域的骨架搭对

3.1 供应商主档表,哪怕只做一个表也得做好

供应商主表是整个系统的地基,字段设计直接决定后续开发会不会返工。我的建议是至少包含以下字段:

字段名类型说明
idbigint主键,自增
supplier_codevarchar(32)供应商编码,全局唯一
supplier_namevarchar(100)企业名称
supplier_typevarchar(20)类型:原材料/外包/设备/服务
tax_numbervarchar(32)税号
credit_codevarchar(50)统一社会信用代码
addressvarchar(200)注册地址
statusvarchar(10)状态:潜在/合格/暂停/黑名单
levelvarchar(10)评估等级:A/B/C
contact_namevarchar(50)主要联系人
contact_phonevarchar(20)联系电话
contact_emailvarchar(100)邮箱
bank_accountvarchar(50)开户账号
remarktext备注
create_timedatetime创建时间
update_timedatetime更新时间
deletedtinyint逻辑删除标记

几个容易忽略的设计细节。第一,supplier_code必须唯一,而且编码规则要有意义,比如SUP+年月日+三位序号,方便人工识别和追溯。第二,一定要有逻辑删除deleted字段。真实业务中删除供应商几乎都是假删除,万一误删,还有回退的余地。第三,status和level用字符串而不是数字枚举,代码可读性好,数据导出到Excel后别人也能看懂含义。

3.2 业务关联表:订单和询价怎么挂接

供应商管理系统如果没有采购业务,那只是个花架子通讯录。所以至少还要设计物料表、询价单、采购订单三张业务表。

物料表字段不多:物料编码、名称、规格型号、单位。询价单表记录采购员对供应商发起的报价请求,字段有询价单号、供应商id、物料id、期望单价、报价截止时间、状态(待报价/已报价/已过期)、最终结果。采购订单表记录成交结果:订单号、供应商id、物料id、下单数量、单价、订单金额、交期、状态(待确认/已确认/已发货/已收货/已完成/已取消)。

这三张表之间的关联关系要清楚:询价单是流程起点,报价成交后生成采购订单,订单再关联到供应商和物料。实际项目中还会加报价单表存储每次报价的历史快照,而不是覆盖询价单里的旧值。否则后面做"历史报价对比曲线"就无米下锅了。

供应商评估表也要提前设计。字段包括被评估的供应商id、评分周期、交货准时率、质量合格率、配合度评分、综合得分、评估人和备注。这个表是早晚要用的,别等做评估功能的时候才想起加表,那时候历史数据已经丢了不少。

4. 后端实现:分层架构、鉴权和事务的关键路径

4.1 工程结构与请求处理链路

后端包的划分,我习惯按技术分层和业务域两个维度结合。基础结构是:

com.example.supplier ├── controller # 接收前端请求 ├── service # 业务逻辑层,事务边界 │ └── impl ├── mapper # MyBatis接口层 ├── entity # 数据库实体 ├── dto # 请求和响应对象 ├── common # 通用返回结果、异常处理、工具类 └── config # 拦截器、跨域等配置

controller层只做参数接收、校验和结果封装,不在里面写业务代码。业务逻辑一律放在service层的impl里,事务注解@Transactional也打在service方法上。这样做的原因很简单:一个controller方法可能被多个前端页面调用,比如供应商审核接口既可能被列表页调用,也可能被详情页调用,但业务逻辑只有一份。

分页查询是管理系统的重头戏。我的做法是统一封装一个分页请求对象和一个分页结果对象。前端传pageNum和pageSize,配合MyBatis的PageHelper插件,PageHelper.startPage(pageNum, pageSize)之后紧跟的select语句就会自动分页,返回的PageInfo里直接带total总数。查询条件用DTO对象接收,里面放供应商名称、状态、类型这些模糊查询或精确查询字段。

4.2 登录鉴权:JWT加拦截器就够用

管理系统的鉴权方案,没必要引入Spring Security那一套重武器。Spring Security的过滤器链、权限表达式对新手来说调试成本太高。用JWT加HandlerInterceptor,小而美,逻辑直观。

JWT流程是:用户登录成功后,后端校验用户名密码(密码用BCrypt加密存储),签发一个token返回前端。token里可以带上userId和角色信息。前端把token存在localStorage里,每次请求放在Authorization请求头。后端写一个拦截器,拦截非白名单路径,解析token,解析成功就把用户信息放进ThreadLocal,失败返回401。

拦截器里要做两件事:一个是token过期判断,一个是角色权限判断。角色权限可以用简单的注解加自定义拦截实现。比如定义一个@RequireRole("ADMIN")注解,拦截器里读取当前请求处理方法的注解,比对当前用户角色。这套方案代码量控制在200行以内,排查问题也容易。

给个权限矩阵参考:

功能模块管理员采购员供应商
供应商档案维护全部操作新增、编辑查看及编辑部分字段
采购订单全部操作新增、审核确认、查看
系统用户管理全部操作无权限无权限
供应商评估全部操作发起评估查看结果

4.3 事务细节:订单审核不是简单的改状态

做订单审核功能时,新手最容易犯的错是把"审核通过"写成一次update订单表。实际上审核动作是一个事务里串行执行的多个写操作:更新订单状态为"已确认"、扣减物料库存、生成一条订单确认记录。这三个操作任何一个失败,整个审核动作必须回滚,否则会出现订单已确认但库存没扣的脏数据。

所以审核代码长这样:

@Transactional public void approveOrder(Long orderId, Long userId) { PurchaseOrder order = orderMapper.selectById(orderId); if (!"PENDING".equals(order.getStatus())) { throw new BusinessException("订单状态已变化,请刷新后重试"); } order.setStatus("CONFIRMED"); orderMapper.updateById(order); Material material = materialMapper.selectById(order.getMaterialId()); material.setStock(material.getStock() - order.getQuantity()); materialMapper.updateById(material); OrderAuditLog log = new OrderAuditLog(); log.setOrderId(orderId); log.setAuditUserId(userId); log.setAction("APPROVE"); log.setAuditTime(new Date()); auditLogMapper.insert(log); }

这里有个关键点:状态更新前要做乐观锁校验。selectById拿到订单后,在内存里判断状态是不是预期的"待确认",而不是无条件执行更新。两个用户同时审核同一张订单时,后提交的人会因为状态已经变了而拿到异常提示,而不是默默覆盖。更严谨的做法是update语句里带上where status = 'PENDING',用数据库行锁兜底。

4.4 MyBatis的动态SQL实战

MyBatis的XML是这类系统的数据访问核心。供应商列表页有5个查询条件——名称模糊查询、编码精确查询、类型下拉框、状态下拉框、等级下拉框,加上分页,对应的Mapper写法如下:

<select id="selectSupplierPage" resultType="com.example.supplier.entity.Supplier"> SELECT * FROM supplier <where> deleted = 0 <if test="supplierName != null and supplierName != ''"> AND supplier_name LIKE CONCAT('%', #{supplierName}, '%') </if> <if test="supplierCode != null and supplierCode != ''"> AND supplier_code = #{supplierCode} </if> <if test="supplierType != null and supplierType != ''"> AND supplier_type = #{supplierType} </if> <if test="status != null and status != ''"> AND status = #{status} </if> <if test="level != null and level != ''"> AND level = #{level} </if> </where> ORDER BY create_time DESC </select>

<where>标签最聪明的地方在于它会自动去掉第一个条件前面的AND,你不需要为"第一个查询条件要不要加and"做模板判断。这种动态SQL,比拼接字符串安全得多,也天然防了SQL注入。

5. 前端Vue实战:列表交互、表单校验和动态菜单

5.1 请求层封装:先定好规矩再写页面

Vue前端第一件事不是写页面,而是封装Axios。管理者系统的请求有大量重复逻辑:带token、统一处理401、统一弹错误提示、后端返回码判断。把这些放在一层统一处理,能少写很多重复代码。

import axios from 'axios'; import { ElMessage } from 'element-plus'; import router from '@/router'; const request = axios.create({ baseURL: '/api', timeout: 10000 }); request.interceptors.request.use(config => { const token = localStorage.getItem('token'); if (token) { config.headers.Authorization = `Bearer ${token}`; } return config; }); request.interceptors.response.use( response => { const res = response.data; if (res.code === 200) { return res.data; } ElMessage.error(res.msg || '请求失败'); return Promise.reject(new Error(res.msg)); }, error => { if (error.response && error.response.status === 401) { localStorage.removeItem('token'); router.push('/login'); } ElMessage.error(error.message || '网络异常'); return Promise.reject(error); } );

开发环境的跨域问题,可以通过Vite的proxy配置解决。在vite.config.js里把/api前缀的请求转发到后端端口:

server: { port: 3000, proxy: { '/api': { target: 'http://localhost:8080', changeOrigin: true } } }

这样前端开发请求/api/supplier/list时,Vite自动转发到http://localhost:8080/api/supplier/list。后端Controller的RequestMapping统一加/api前缀,两边约定好了,正式环境部署时用Nginx做反向代理,规则是一样的,不用改代码。

5.2 供应商档案页:表格加弹窗的标准范式

供应商管理的核心页面是列表页加新增编辑弹窗。列表页用Element Plus的el-table,列定义对应后端返回的字段。操作列放编辑、详情、删除按钮。搜索区放名称输入框、状态下拉框、查询和重置按钮。

弹窗表单用el-form加校验规则。几个典型的校验需求:供应商名称必填、编码必填且不可重复、税号格式校验(15位或18位)、联系电话格式校验。Element Plus的校验规则基于async-validator,写法如下:

const rules = { supplierName: [{ required: true, message: '请输入供应商名称', trigger: 'blur' }], taxNumber: [ { required: true, message: '请输入税号', trigger: 'blur' }, { pattern: /^[0-9A-Z]{15,18}$/, message: '税号格式不正确', trigger: 'blur' } ] };

这里有个实操经验:编辑模式下,表单回填时不能只回填一层对象。用Object.assign({}, row)去拷贝当前行的数据,再赋值给表单的响应式对象。直接form = row的话,表格行数据会和表单形成引用关系,表单里改了字段,表格行显示也被改了,页面会跳一下。

新增和保存的逻辑可以封装成一个方法。区别只是有id走update接口,没id走insert接口。接口调用成功后刷新列表,同时关闭弹窗。注意清空表单和校验状态,否则下次打开还能看到上一次的残留内容。

5.3 动态路由与菜单权限

做权限菜单有两条路:一条是前端写死路由表,根据角色判断是否渲染;一条是登录时后端返回该用户的权限码列表,前端动态筛选路由。管理系统我建议走后端返回菜单的方式,灵活一些,权限调整不用改前端代码。

动态路由的坑在于:刷新页面时路由要重新注册。用户登录成功后,我们把动态路由addRoute进去了,但刷新后Vue Router实例重新创建,动态路由就丢了。解决办法是在路由全局守卫里做判断:本地没有用户信息,却访问了非登录页,先去拿用户信息再addRoute,然后next({ ...to, replace: true })重新进入一次。这个逻辑写不对,刷新页面就会白屏或404,我见过很多人卡在这一步。

菜单可以根据路由表自动生成。el-menu的router属性开启后,el-menu-item的index和路由路径绑定,点击就跳转。子菜单对应路由表里的children。

6. 完整跑通项目:环境准备、启动顺序与排坑清单

6.1 环境版本组合

组件版本建议备注
JDK1.8 或 11高版本JDK搭配SpringBoot 2.x有模块限制需处理
Maven3.6+配置阿里云镜像加速依赖下载
MySQL5.7 或 8.08.0需要设置时区参数
Node.js16+Vite 4要求Node 16+
IDEIDEA / VS Code后端IDEA,前端VS Code均可

启动顺序是固定的:先启动MySQL,导入数据库脚本,再启动后端SpringBoot,最后启动前端Vite开发服务器。别一上来就前后端同时启动,错了分不清是哪边的锅。

6.2 后端启动最常见的三个问题

第一个问题是数据库时区报错。MySQL 8.0的连接url需要带serverTimezone=Asia/Shanghai,否则报The server time zone value 'Öйú±ê׼ʱ¼ä' is unrecognized。连接串写成:

jdbc:mysql://localhost:3306/supplier_db?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai

第二个问题是Maven依赖包下载慢或失败。在Maven的settings.xml里配置阿里云公共镜像,依赖秒下。第三个问题是端口冲突。SpringBoot默认8080端口,如果本机被其他服务占用,启动直接报Port already in use。Windows下用netstat -ano | findstr 8080找到占用进程,任务管理器结束它,或者改application.yml里的server.port。

6.3 前端联调阶段的高频坑

前端启动起来后第一个看到的往往是登录页,但登录接口调不通。排查顺序:先看浏览器Network面板请求有没有发出去,发了看状态码,404是后端路径不对,401是token问题,500是先看后端控制台堆栈。

这里要特别提一个容易被忽略的坑:Vite proxy配置了但没生效。可能是改了vite.config.js后没有重启dev server,Vite的配置变更需要重启进程才生效。修改代码是热更新,修改配置不是。我在这上面浪费过不少时间。

Element Plus按需引入的问题也经常遇到。如果用了unplugin-auto-import和unplugin-vue-components按需引入组件,但发现某个组件样式没生效,检查style.ts配置,或者干脆全量引入省心。

7. 从"能跑"到"能用":这个系统的进阶方向

如果你按照上述路径把这套供应商管理系统跑通了,别急着收工。我建议接着做几个小而实用的改进,项目含金量会明显不同。

第一个是Excel导入导出。供应商数据通常不是零起点录入的,老系统里有一堆Excel档案。Hutool的Excel工具能轻松实现读取和写出,加上一个导入模板下载按钮和一个导出按钮,整个系统从"手工录入"变成"批量交接",实际使用价值大增。

第二个是资质到期预警。供应商的营业执照、ISO证书都有有效期。在资质表里记录证书名称、编号和到期日,写一个定时任务每天扫描近30天内到期的证书,推到通知栏或者给采购员发站内消息。这个功能在简历上写"通过定时任务实现资质到期预警",比单纯CRUD有说服力得多。

第三个是评估打分功能的完整化。我前面提到的供应商评估表,可以把评分细化为多个维度维度——质量合格率、交付准时率、价格竞争力、响应速度——每个维度设权重,自动算出综合分。再配合过去12个月的历史订单数据做图表展示,前端用ECharts画一个雷达图或折线图。技术难度不大,但展示效果很好,系统从"信息记录"变成了"决策辅助工具"。

我在做这套系统的过程中体会最深的一点是:管理系统的难点永远不在某一个技术点上,而在数据关系的梳理和流程逻辑的严谨性。同一个供应商名称被录成"三家公司"的脏数据问题、订单状态被别人误改时的并发问题、导出数据时字段和中文表头对不上的问题——这些才是最花时间的部分。把这类经验整理清楚,比堆砌几个高深框架重要得多。最后分享一个小技巧:开发阶段给每个前端请求都在网络面板里看一遍状态码和响应时间,多留意那些200但耗时超过500毫秒的接口,把慢查询的SQL拿到数据库软件里EXPLAIN一下,加个索引,这些动作能让你省下后面联调时大量的排查时间。

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

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

立即咨询