Spring Boot+Vue3在线报价系统源码解析与二次开发

发布时间:2026/10/27 6:29:02
Spring Boot+Vue3在线报价系统源码解析与二次开发 简介这是一套基于Web的多功能在线报价系统源码面向需要搭建产品报价与后台管理功能的中小企业、建站开发者及Web学习人员可处理报价生成、产品与品牌分类维护、后台账户登录等基础业务并保留源码级定制空间。压缩包为rar格式共16个文件约22KB以ASP动态页面为主体配合CSS样式、HTM框架页、SQL数据库脚本、INC公共文件及ASA全局配置构成前后台一体的轻量级结构。已有2673人学习下载说明其在ASP报价系统与小型管理后台学习中具有一定参考价值。源码涵盖报价展示、后台产品与品牌类型管理、账户登录入口、数据库连接及样式文件等模块读者可据此梳理ASP应用的目录组织、页面交互与数据表设计并在此基础上进行功能增删、界面调整和二次开发适合作为课程设计、毕业设计或小型企业报价工具的起步代码。1. 从一张改到崩溃的报价单说起这套在线报价系统到底替你省掉了什么上个月有个做设备销售的朋友跟我吐槽说客户要一份带阶梯价、含税/不含税切换、还要分三批发货的报价单他用 Excel 改了四版最后一版把含税价填进了不含税列客户直接质疑他们公司报价不专业。这种场景太典型了产品型号一多、折扣规则一复杂、审批人一多Excel 就变成了一个随时会翻车的黑匣子——谁改了什么、哪一版是最终版、客户看到的 PDF 和内部核价单是否一致全靠人肉记忆。这套多功能在线报价系统源码要解决的就是这个链路把产品库、客户信息、价格策略、报价单生成、审批流转和 PDF 导出串成一条在线流程前端选产品配折扣后端算价出单谁在什么时间改了哪一行都有记录。适合两类人一类是中小公司的技术负责人想拿一套能跑通的报价系统做底座快速改成自己业务的样子另一类是想学完整业务系统落地的前后端开发者这套源码的模块划分和代码组织比很多教学项目要“像真的”。2. 技术栈与目录结构一套报价系统从登录到出单的骨架2.1 为什么这套源码选了前后端分离 RBAC 权限模型报价系统有一个很硬的需求不同角色看到的价格和操作权限不一样。销售能创建报价单但不能改底价销售经理能审批折扣但不能改产品库财务能看含税金额但不能动客户信息。这种需求用传统 Session JSP 也能做但改起来很痛苦所以这套源码走的是前后端分离路线后端只吐 JSON前端用路由守卫控制页面入口后端再用注解 拦截器做接口级鉴权。技术选型上后端是 Spring Boot 2.7 MyBatis-Plus MySQL 8缓存和登录态用 Redis前端是 Vue 3 Element Plus Axios图表统计用了 ECharts。这套组合在国内中小项目里很常见好处是资料多、遇到问题容易搜到坏处是版本组合要自己锁死后面避坑章会讲。权限模型用的是经典 RBAC用户表、角色表、权限表、用户角色关联表、角色权限关联表。报价单本身又加了一层数据权限——销售只能看自己创建的报价单经理能看本部门的管理员看全部。这层数据权限没有用复杂的规则引擎而是在 MyBatis-Plus 的拦截器里拼create_by和dept_id条件代码量不大但很实用。2.2 目录结构先看清代码放哪儿再决定从哪个文件下手拉下源码后根目录通常是这样的结构不同版本可能有细微差异但主干一致quote-system/ ├── quote-server/ # 后端 Spring Boot 工程 │ ├── src/main/java/com/quote/ │ │ ├── common/ # 通用返回体、异常、工具类 │ │ ├── config/ # Redis、MyBatis-Plus、跨域、拦截器配置 │ │ ├── module/ │ │ │ ├── product/ # 产品与价格策略 │ │ │ ├── customer/ # 客户信息 │ │ │ ├── quote/ # 报价单主流程 │ │ │ ├── approval/ # 审批流 │ │ │ └── system/ # 用户、角色、菜单 │ │ └── QuoteApplication.java │ └── src/main/resources/ │ ├── application.yml # 数据源、Redis、文件路径 │ ├── mapper/ # MyBatis XML │ └── template/ # 报价单 PDF/Excel 模板 ├── quote-web/ # 前端 Vue 3 工程 │ ├── src/ │ │ ├── api/ # 按模块拆的接口封装 │ │ ├── views/ │ │ │ ├── quote/ # 报价单列表、编辑、详情 │ │ │ ├── product/ # 产品管理 │ │ │ └── system/ # 用户角色菜单 │ │ ├── router/ # 路由与守卫 │ │ └── store/ # Pinia 状态 │ └── vite.config.js └── sql/ └── quote_init.sql # 建库建表 初始数据后端按业务模块分包每个模块下再分controller、service、mapper、entity、dto这是很常规的写法好处是找代码不用猜。前端views/quote下一般有三个核心页面列表页负责筛选和状态展示编辑页是报价单的核心交互详情页负责审批和导出。第一次读源码建议从quote模块的 Controller 入手顺着 Service 往下看因为产品、客户、审批最终都汇聚到报价单上这条链路看懂之后其他模块都是外围。提示不要一上来就通读所有模块。先跑通一次“新建报价单→保存→导出”的流程再回头按调用链读代码效率会高很多。3. 核心模块拆解报价单从创建到导出的完整链路3.1 报价单创建产品选择、阶梯价与税费计算的落点报价单的核心不是表单本身而是“金额怎么算”。这套源码里报价单主表和明细表是分开的quote_order存客户、报价日期、总金额、状态、审批人quote_item存每一行产品、数量、单价、折扣率、行金额。创建报价单时前端把选中的产品和数量传给后端后端不会信任前端传来的行金额而是根据产品 ID 重新查价格策略再算一遍这是防止篡改的基本操作。阶梯价的计算逻辑在ProductPriceService里核心方法大致长这样// 根据产品ID和数量匹配阶梯价区间返回适用单价 public BigDecimal matchTierPrice(Long productId, Integer quantity) { // 查出该产品所有阶梯价规则按 min_qty 升序 ListPriceTier tiers priceTierMapper.selectByProductId(productId); if (CollectionUtils.isEmpty(tiers)) { // 没有阶梯规则时回落到产品基础价 return productMapper.selectById(productId).getBasePrice(); } for (PriceTier tier : tiers) { // 区间判断minQty quantity maxQtymaxQty 为空表示无上限 boolean upperOk tier.getMaxQty() null || quantity tier.getMaxQty(); if (quantity tier.getMinQty() upperOk) { return tier.getUnitPrice(); } } // 超出所有区间时取最后一档价格常见做法是取最高档而不是报错 return tiers.get(tiers.size() - 1).getUnitPrice(); }这段代码的逻辑说明参数productId定位产品quantity是当前行数量返回的是匹配到的单价。PriceTier表里存的是min_qty、max_qty、unit_price三个关键字段区间是左闭右开避免 100 件同时命中两档。税费计算在QuoteAmountCalculator里通常做法是明细行先算折扣后金额再按报价单级别的税率统一计算税额税率存在quote_order.tax_rate字段里默认 13%允许有权限的角色修改。金额一律用BigDecimal并且显式设置setScale(2, RoundingMode.HALF_UP)不然后面出现 0.01 的尾差对账时会很麻烦。3.2 审批流没有引入工作流引擎用状态机 审批记录表扛住这套源码没有上 Activiti 或 Flowable而是用了一张quote_approval记录表加报价单状态字段来做轻量审批。状态流转大概是DRAFT草稿→PENDING待审批→APPROVED已通过或REJECTED已驳回。提交审批时后端根据报价单总金额和折扣率决定审批层级比如折扣低于 9 折需要经理批低于 8 折需要总监批。核心代码在QuoteApprovalService// 提交审批根据折扣率决定审批角色写入审批记录并更新单据状态 Transactional(rollbackFor Exception.class) public void submitForApproval(Long quoteId) { QuoteOrder order quoteOrderMapper.selectById(quoteId); // 只有草稿或已驳回状态允许重新提交 if (!Arrays.asList(DRAFT, REJECTED).contains(order.getStatus())) { throw new BizException(当前状态不允许提交审批); } // 根据折扣率计算需要的审批角色 String requiredRole SALES_MANAGER; if (order.getDiscountRate().compareTo(new BigDecimal(0.80)) 0) { requiredRole SALES_DIRECTOR; } // 写入审批记录状态置为待审批 QuoteApproval approval new QuoteApproval(); approval.setQuoteId(quoteId); approval.setRequiredRole(requiredRole); approval.setStatus(PENDING); approval.setCreateTime(LocalDateTime.now()); quoteApprovalMapper.insert(approval); order.setStatus(PENDING); quoteOrderMapper.updateById(order); }逻辑说明Transactional保证审批记录写入和单据状态更新要么都成功要么都回滚discountRate是报价单级别的综合折扣率由总金额除以原价总额得到requiredRole决定了哪个角色能在待办列表里看到这条审批。审批通过时approve方法会校验当前登录人是否拥有requiredRole然后更新审批记录和单据状态。这套做法不算优雅但胜在简单直观后期要加审批节点在requiredRole的判断链上继续加分支即可比啃工作流引擎的 BPMN 快得多。3.3 PDF 导出模板放在 resources 下用占位符替换而不是硬编码报价单最终要发给客户导出 PDF 是刚需。这套源码没有用前端截图方案而是在后端用模板引擎生成 HTML 再转 PDF模板放在resources/template/quote-template.html里面用{{customerName}}、{{quoteNo}}、{{itemList}}这类占位符。导出服务大致如下// 导出报价单PDF加载HTML模板填充数据转成PDF输出到响应流 public void exportPdf(Long quoteId, HttpServletResponse response) throws Exception { QuoteDetailVO detail quoteService.getDetail(quoteId); // 用 Thymeleaf 渲染模板上下文里放报价单主信息和明细 Context context new Context(Locale.CHINA); context.setVariable(quote, detail.getOrder()); context.setVariable(items, detail.getItems()); String html templateEngine.process(quote-template, context); // HTML 转 PDF字体需在配置里指定否则中文会变方块 try (OutputStream out response.getOutputStream()) { response.setContentType(application/pdf); response.setHeader(Content-Disposition, attachment;filenamequote.pdf); PdfRendererBuilder builder new PdfRendererBuilder(); builder.useFastMode(); builder.withHtmlContent(html, null); builder.toStream(out); builder.run(); } }参数说明quoteId是要导出的报价单 IDresponse直接写 PDF 流前端用window.open或 Axios blob 下载均可。templateEngine是 Thymeleaf 的SpringTemplateEngineHTML 模板里的表格行用th:each循环明细。这里的关键是字体配置PdfRendererBuilder需要useFont指定一个支持中文的 TTF 文件否则导出的 PDF 里中文全是方框这是第一次跑最容易翻车的地方后面避坑章会细说。Excel 导出走的是另一条路径用 EasyExcel 写ExcelProperty注解的 DTO代码更简单不展开。4. 本地跑起来环境准备、配置修改与启动验证4.1 环境清单与数据库初始化本地运行需要 JDK 8 或 11源码的pom.xml里java.version会写明不要用 JDK 17 硬跑Spring Boot 2.7 对 17 的支持有坑、Maven 3.6、MySQL 8.0、Redis 6、Node.js 16。版本不要贪新这套源码的依赖组合是按稳定版锁的Node 20 配 Vite 某些插件可能会报 ESM 相关错误。第一步先建库。用sql/quote_init.sql导入命令行方式# 登录 MySQL 后执行库名一般叫 quote_system mysql -u root -p -e CREATE DATABASE quote_system DEFAULT CHARACTER SET utf8mb4; mysql -u root -p quote_system sql/quote_init.sql导入完成后检查三张核心表有没有数据sys_user初始管理员、product_info示例产品、price_tier阶梯价规则。如果price_tier是空表新建报价单时阶梯价匹配会回落到基础价看起来“能跑”但算出来的金额不对这是新手容易忽略的验证点。第二步改后端配置。application.yml里要改的是数据源、Redis 和文件上传路径spring: datasource: url: jdbc:mysql://localhost:3306/quote_system?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai username: root password: 你的密码 redis: host: localhost port: 6379 quote: upload-path: D:/quote-files/ # 改成你本机存在的目录 pdf-font-path: D:/quote-files/fonts/simsun.ttfserverTimezone必须写Asia/Shanghai不然报价日期和审批时间会差 8 小时upload-path目录要提前建好程序不会自动创建pdf-font-path指向一个中文 TTF 字体Windows 可以从C:/Windows/Fonts拷一份宋体或黑体出来Linux 服务器上没有字体文件时 PDF 导出会直接抛异常。4.2 启动后端与前端按顺序验证三条链路后端启动命令在quote-server目录下# 先编译再启动跳过测试可以省时间 mvn clean package -DskipTests java -jar target/quote-server-1.0.0.jar # 或者开发模式直接跑 mvn spring-boot:run看到Started QuoteApplication且没有 Redis 连接报错说明基础环境通了。接着启动前端cd quote-web npm install npm run dev浏览器打开控制台提示的地址用sys_user表里的初始账号登录。验证顺序建议按“产品→客户→报价单→导出”走先在产品管理里新建一个产品并配两档阶梯价再建一个客户然后新建报价单选产品、改数量观察行金额是否随阶梯价变化最后点导出 PDF检查中文是否正常。这三步任何一步失败问题范围都能缩到对应模块比一上来就点全部菜单高效得多。注意前端vite.config.js里的代理目标要指向后端实际端口默认 8080。如果后端改了端口而前端没改登录接口会 404但浏览器控制台只报跨域或网络错误容易误判成后端没启动。5. 避坑与排查第一次跑这套源码最容易翻车的五个地方5.1 登录一直提示“用户名或密码错误”但数据库里明明有用户现象是前端登录接口返回 401查sys_user表能看到 admin 记录。原因通常有两个一是初始 SQL 里的密码是 BCrypt 加密后的字符串如果你手动改过密码字段却填了明文匹配必然失败二是 Redis 没启动登录时的 token 写入失败被全局异常捕获前端统一提示成了账号密码错误。解决方式是先用 BCrypt 工具类对明文123456生成一个密文替换进去再确认 Redis 的ping返回PONG。如果 Redis 端口不是 6379application.yml里改了之后要重启后端热加载不会重新初始化连接池。5.2 导出的 PDF 里中文全是方框或空白现象是 PDF 能下载、表格线也在但所有中文都显示为方块。原因是 PDF 渲染库默认没有嵌入中文字体HTML 里的font-family写了“宋体”也没用渲染器找不到对应字体文件。解决办法是在PdfRendererBuilder里显式useFont加载一个 TTF 文件路径配在quote.pdf-font-path。注意不要用.ttc字体集合文件部分渲染库对它支持不好也不要把字体路径写成相对路径因为 jar 运行时的工​​作目录和你 IDE 里不一样血泪经验是直接写绝对路径或者用classpath:前缀把字体打进资源目录。5.3 报价单金额尾差 0.01对账时被财务打回来现象是明细行金额加起来和总金额差一分钱。原因通常是某处用了double做中间计算或者BigDecimal除法时没有指定精度和舍入模式。排查时全局搜一下double和float把金额相关字段全部换成BigDecimal除法统一用divide(x, 2, RoundingMode.HALF_UP)数据库字段用decimal(12,2)而不是double。还有一个隐蔽点前端传过来的单价如果经过 JavaScript 浮点运算可能在序列化时变成99.99999999后端接收后要用setScale(2, HALF_UP)先规整再参与计算。5.4 前端打包后部署到服务器接口全部 404现象是npm run dev本地一切正常npm run build放到 Nginx 后登录接口报 404。原因是开发环境的接口地址走 Vite 代理打包后代理配置不生效而代码里的 AxiosbaseURL可能写的是相对路径或 localhost。解决方式是在src/api/request.js里用环境变量区分开发环境用/api走代理生产环境用 Nginx 反代到后端真实地址。Nginx 配置里加一条location /api/ { proxy_pass http://127.0.0.1:8080/; }注意末尾斜杠的写法少一个斜杠可能导致路径拼接多一层。5.5 审批提交后待办列表是空的现象是销售提交了报价单状态也变成了PENDING但经理登录后待办列表查不到。原因多半是审批记录的required_role存的是角色编码而待办查询用的是角色 ID两边对不上也可能是数据权限拦截器把经理的查询范围限制在了“本人创建”把别人提交的单据过滤掉了。排查时先看quote_approval表里的required_role值和当前登录人的角色编码是否一致再检查 MyBatis-Plus 拦截器里待办查询有没有误加create_by 当前用户的条件。审批场景的数据权限和普通列表不一样待办要按角色查而不是按创建人查这一点在改权限逻辑时特别容易踩。6. 二次开发改造从“能跑”到“贴合业务”的几条实操路子这套源码跑通之后真正花时间的是改成自己公司的报价习惯。我一般建议先从价格策略下手因为这是报价系统的灵魂。源码里的阶梯价是“按数量匹配单价”如果业务是按客户等级给折扣就在ProductPriceService里加一层客户等级系数matchTierPrice返回基础单价后再乘customerLevelDiscount这个折扣可以从客户表里带出来。改动点集中影响面可控而且立刻能看到效果。第二个常见改造是报价单模板。resources/template/quote-template.html就是一个普通 HTML 文件公司 LOGO、抬头、银行账号、条款说明都可以直接改改完重启即可生效。如果要支持多套模板在quote_order表加一个template_id字段导出时根据 ID 选择不同模板文件代码改动不超过二十行。这里有个小技巧模板里的金额格式化不要在 HTML 里写复杂表达式在 Java 侧把BigDecimal转成带千分位的字符串再塞进上下文模板只负责展示出问题好定位。第三个方向是对接外部系统。报价单审批通过后很多公司要把结果推给 CRM 或者生成合同草稿。源码里QuoteApprovalService的approve方法末尾是一个自然的扩展点在这里加一个ApplicationEventPublisher发领域事件再写一个监听器去调外部接口比直接在审批方法里塞 HTTP 调用要干净。验证方式也简单本地起一个 mock 接口审批通过后看监听器日志有没有打出请求体和响应码。最后一个容易被忽略的改造点是权限粒度。源码的 RBAC 是页面级和接口级但报价系统往往需要字段级控制比如成本价字段只有总监能看。这种需求不要硬改拦截器常见做法是在 DTO 序列化时根据当前角色决定是否置空敏感字段用 Jackson 的JsonView或者自定义序列化器都行改动小且不影响查询逻辑。这套代码我前后改过几轮最大的教训是每次动金额计算相关的方法哪怕只改一行也要把“新建报价单→阶梯价匹配→税额计算→PDF 导出”这条链路手工走一遍不能只跑单元测试。因为金额的坑往往藏在前后端精度和模板格式化的交界处单测覆盖不到。希望帮到你。本文还有配套的精品资源点击获取

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询