恒美微站
首页
关于我们
建站服务
主题模板
案例展示
资讯中心
联系我们
基于SpringBoot+Vue的供应商管理系统开发实战解析
首页
资讯中心
/
基于SpringBoot+Vue的供应商管理系统开发实战解析
基于SpringBoot+Vue的供应商管理系统开发实战解析
发布时间:2026/10/17 2:49:17
1. 供应商管理系统的核心价值从Excel台账到流程协同先说个真实场景。很多企业内部对供应商的管理长期停留在一张Excel表加几个微信群的状态。采购员下单前翻表格找联系方式财务对账时发现税号录错了老板想看年度供应商绩效排名得等某个同事手工汇总。这套东西做到后面问题不是没有数据而是数据散、口径乱、责任不清。所谓基于SpringBootVue的供应商管理系统本质就是把这摊子事收拢到一个Web系统里让供应商档案、询价报价、采购订单、资质审核、评估记录都走同一套流程。这套系统适合谁参考主要是两类人一类是要做毕业设计或课程项目的学生需要完整跑通的Java全栈项目一类是中小公司里想低成本搭建供应商管理工具的开发者。技术栈很标准——后端SpringBoot持久层MyBatis数据库MySQL前端VueJava语言。没有花哨的分布式架构没有中间件大杂烩就是一个干净利落的管理系统样板业务逻辑清晰容易改造成自己需要的场景。我为什么觉得这类项目值得动手做一遍因为它覆盖了一个Web系统从零到上线的大部分关键环节表结构设计、分页查询、登录鉴权、关联表事务处理、前端列表与表单交互、前后端联调。这些能力不是看书看会的是跑完一个完整项目才真正长在身上的。做这套系统之前先把边界想清楚。供应商管理系统和ERP、SRM供应商关系管理有区别。ERP管的是计划、库存、财务SRM是更宏观的供应商全生命周期协同平台。我这个系统定位于中间地带管好供应商主数据、日常采购协同、基础评估但不碰复杂的财务结算和生产计划。这样定位下来功能边界就很清晰了。角色划分上常见的做法是三类角色系统管理员负责系统配置和账号管理采购员负责供应商入库、询价、下单、评估供应商账号可以登录查看订单和更新自己的资料。有的项目会把供应商端单独拆成门户但基础版本共用一套管理端即可。2. 技术选型为什么这套组合最省心、最难出错2.1 后端用SpringBoot图的是生态和快速启动供应商管理系统这种体量的应用用SpringBoot是成本最低的选择。SpringBoot把配置自动化做到了极致内嵌TomcatJar包一打就能跑。相比传统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已经普及了很多年组合式APIsetup语法糖写业务代码更清爽Element Plus的表格、表单、弹窗组件足够覆盖管理系统的所有页面场景。如果你手里的参考代码是Vue2和Element UI也不用焦虑核心逻辑——组件、props、事件、路由、Axios请求——是完全通用的改造成本很低。工具链上用Vite构建。相比WebpackVite启动速度快到离谱开发体验好一个量级。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/Ccontact_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 idselectSupplierPage resultTypecom.example.supplier.entity.Supplier SELECT * FROM supplier where deleted 0 if testsupplierName ! null and supplierName ! AND supplier_name LIKE CONCAT(%, #{supplierName}, %) /if if testsupplierCode ! null and supplierCode ! AND supplier_code #{supplierCode} /if if testsupplierType ! null and supplierType ! AND supplier_type #{supplierType} /if if teststatus ! null and status ! AND status #{status} /if if testlevel ! null and level ! AND level #{level} /if /where ORDER BY create_time DESC /selectwhere标签最聪明的地方在于它会自动去掉第一个条件前面的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.js16Vite 4要求Node 16IDEIDEA / VS Code后端IDEA前端VS Code均可启动顺序是固定的先启动MySQL导入数据库脚本再启动后端SpringBoot最后启动前端Vite开发服务器。别一上来就前后端同时启动错了分不清是哪边的锅。6.2 后端启动最常见的三个问题第一个问题是数据库时区报错。MySQL 8.0的连接url需要带serverTimezoneAsia/Shanghai否则报The server time zone value Öйú±ê׼ʱ¼ä is unrecognized。连接串写成jdbc:mysql://localhost:3306/supplier_db?useUnicodetruecharacterEncodingutf8useSSLfalseserverTimezoneAsia/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 serverVite的配置变更需要重启进程才生效。修改代码是热更新修改配置不是。我在这上面浪费过不少时间。Element Plus按需引入的问题也经常遇到。如果用了unplugin-auto-import和unplugin-vue-components按需引入组件但发现某个组件样式没生效检查style.ts配置或者干脆全量引入省心。7. 从能跑到能用这个系统的进阶方向如果你按照上述路径把这套供应商管理系统跑通了别急着收工。我建议接着做几个小而实用的改进项目含金量会明显不同。第一个是Excel导入导出。供应商数据通常不是零起点录入的老系统里有一堆Excel档案。Hutool的Excel工具能轻松实现读取和写出加上一个导入模板下载按钮和一个导出按钮整个系统从手工录入变成批量交接实际使用价值大增。第二个是资质到期预警。供应商的营业执照、ISO证书都有有效期。在资质表里记录证书名称、编号和到期日写一个定时任务每天扫描近30天内到期的证书推到通知栏或者给采购员发站内消息。这个功能在简历上写通过定时任务实现资质到期预警比单纯CRUD有说服力得多。第三个是评估打分功能的完整化。我前面提到的供应商评估表可以把评分细化为多个维度维度——质量合格率、交付准时率、价格竞争力、响应速度——每个维度设权重自动算出综合分。再配合过去12个月的历史订单数据做图表展示前端用ECharts画一个雷达图或折线图。技术难度不大但展示效果很好系统从信息记录变成了决策辅助工具。我在做这套系统的过程中体会最深的一点是管理系统的难点永远不在某一个技术点上而在数据关系的梳理和流程逻辑的严谨性。同一个供应商名称被录成三家公司的脏数据问题、订单状态被别人误改时的并发问题、导出数据时字段和中文表头对不上的问题——这些才是最花时间的部分。把这类经验整理清楚比堆砌几个高深框架重要得多。最后分享一个小技巧开发阶段给每个前端请求都在网络面板里看一遍状态码和响应时间多留意那些200但耗时超过500毫秒的接口把慢查询的SQL拿到数据库软件里EXPLAIN一下加个索引这些动作能让你省下后面联调时大量的排查时间。