拓冰建站拓冰建站
首页 / 资讯中心 / 正文

微信小程序+Spring Boot实战:销售项目流程化管理系统设计

1. 项目整体设计与技术选型思路先交代一下背景这个项目是我做的一个毕业设计选题是《基于微信小程序的销售项目流程化管理系统》。当时选这个题倒不是为了赶什么热点而是我自己在实习的时候深有体会——很多中小公司的销售业务管理真的还停留在Excel表格 微信群接龙 销冠用自己的小本本记录客户的阶段。客户跟到哪一步了全靠销售自己回忆管理层想看个数据得挨个催报表项目丢单了复盘都不知道是哪一环出了问题。所以当时就想能不能做一套轻量级的、销售人员和主管都愿意用的流程化管理系统把客户从线索到成交甚至交付的全过程给管起来。在这套系统里微信小程序承担的是人人可用、随时可打开的角色。相比原生App小程序最大的优势就是不用安装、适配成本低、微信生态内天然有用户基础对销售这种经常在外跑的人群来说打开微信就能写日报、录客户、提审批接受度比任何独立App都高。而后端我用的是 Spring Boot MyBatis-Plus MySQL 这套经典组合一是因为成熟稳定二是因为毕业设计答辩时候这套技术栈老师熟能讲的内容也多不容易被问倒。鉴权走的是微信生态最常见的 openid 方案没有自己做用户名密码体系这也是小程序端的默认做法。1.1 为什么用流程化来切销售管理这里必须先说清楚一个容易含糊的点销售管理和销售项目流程化管理系统不是一回事。传统的销售管理侧重客户信息的增删改查说白了就是一个客户档案库。但流程化管理核心是项目 阶段 流转这三个词。我当时的设计思路是这样把一个销售机会看成一个项目这个项目从建立到赢单会经历若干固定阶段。比如常见的 B2B 销售流程会拆成初步接洽 → 需求沟通 → 方案报价 → 商务谈判 → 合同签订 → 回款 → 交付服务。每个阶段有固定表单要填、有对应负责人、有预计完成时间。只有当当前阶段的必要信息填写完整项目才能推进到下一个阶段。这样一来管理层看到的就不是一盘散沙的客户而是每个项目卡在哪个阶段、是谁在负责、停留了多久、下一步是什么。这套逻辑放在毕设里好看放在真实业务里更实用。我自己在实习时经历过的那种销售离职客户直接带走的情况在流程化管理下会大幅减少因为客户脉络、跟进记录、阶段资料全部沉淀在系统里。1.2 技术选型里面的几个关键决策前后端分离是明确的小程序端原生开发没有用 uni-app。原因很简单这个项目的核心在于业务逻辑和流程设计uni-app 确实能一套代码多端复用但它的编译层会带来额外的调试成本而且很多小程序原生 API 在 uni-app 里封装得不够透出了问题反而不好排查。毕业设计讲究的是可控我宁可多写几个页面文件也不想在为什么这个组件在真机上渲染不出来上浪费时间。后端方面Spring Boot 版本选的 2.7.xJDK 用的 1.8。这两个版本组合网上资料特别多遇到问题基本一搜就有答案。数据访问层用 MyBatis-Plus说实在的这个框架对业务开发的友好度真的高单表 CRUD 基本不用写 SQL条件构造器 QueryWrapper 做动态查询非常方便能省掉大量重复的 mapper 代码。项目里只有几个复杂的统计报表查询是手写的 SQL这点也符合实际开发中的主流习惯。数据库设计走的是经典的单体架构下的多表关联模式。重点表包括用户表、客户表、销售项目表、项目阶段表、阶段任务表、审批记录表、操作日志表。这里有个经验可以分享学生做毕设很容易把表设计得特别多、特别碎追求所谓的设计感但实际开发时会发现表越多关联越乱光是联表查询就能让你写到怀疑人生。我最终把表控制在 10 张以内每张表都有明确的业务职责宁可字段多一点也不要为了范式强行拆表。1.3 项目目录结构与包结构规划一个好的包结构对后期开发效率影响非常大。当时我的后端包结构是这样划分的controller接收前端请求只做参数校验和结果返回service业务逻辑层项目流转、权限判断这些核心逻辑都在这一层mapper数据层接口配合 MyBatis-Plusentity数据库实体类dto前端请求参数的封装对象vo返回给前端的数据视图对象config配置类包含微信小程序配置、拦截器配置等utils工具类比如 JWT 工具、日期工具、微信请求工具这里要重点说一下 entity、dto、vo 的分离很多同学觉得麻烦一个 User 类走天下。但实际写下来你会发现数据库实体类里有createTime、updateTime、deleted这类字段前端可能根本不需要返回前端提交的数据未必能和数据库字段一一对应。分层之后controller 层接收 dtoservice 层做转换外层返回 vo每一层的字段变化都限制在局部不至于改一个需求整个项目受伤。小程序端的目录则是按页面和组件划分的pages/login登录页pages/index首页pages/project项目列表和详情pages/task待办任务pages/customer客户管理pages/mine个人中心components/自定义组件utils/请求封装、工具函数另外每个页面配套四个文件这一结构其实可以理解为页面结构文件 样式文件 逻辑文件 配置声明小程序端页面就是一个多面体把结构、样式、逻辑、配置拆开管理各自高内聚这是小程序框架本身的设计红利用好了页面代码会非常清爽。2. 数据库设计与流程状态机核心逻辑2.1 核心数据表设计详解先说说最关键的表。客户表customer我设计的字段包括客户名称、联系人、联系电话、客户来源转介绍、线上广告、展会、自拓、所属行业、客户等级A/B/C、状态潜在、跟进中、已成交、已流失、备注、创建人、创建时间、更新时间。客户表是销售系统的地基它和销售项目表是一对多的关系也就是说一个客户可以对应多个销售项目这符合真实业务——同一个客户可能同时跟你买产品 A 和产品 B这是两个独立的项目。销售项目表sales_project这个表是整个系统的核心字段包括项目名称、客户ID、负责人销售员ID、项目金额、当前阶段编码、预计成交时间、状态进行中、已赢单、已丢单、暂停、阶段推进历史、赢单概率、备注等。这里有个细节值得琢磨——current_stage字段存的是阶段编码而不是阶段名称。阶段名称是可能变化的而阶段编码是固定的这样即使前端展示文案变化后端流程逻辑也不会受影响。项目阶段配置表project_stage_config这张表存的是流程模板。字段有阶段编码、阶段名称、阶段序号、所需必填字段配置、允许推进到下一阶段的角色。为什么要把阶段配置做成表而不是写死在代码里因为不同公司的销售流程是不一样的有的公司五个阶段、有的公司七个阶段写死的话每次调整流程都要改代码重新发布做成配置表之后管理员在后台就能调整流程代码一行不用改。对毕设来说这也多了一个功能性亮点答辩的时候可以拿出来说流程可配置。阶段推进记录表则是一个任务清单每个阶段可能拆出若干项待办比如发送产品资料预约演示提交报价单完成一项勾选一项全部完成后项目才能进入下一阶段。这张表的存在让流程化这三个字落地了——管理者不再只能看到一句话的项目状态而是能看到阶段内所有事情的完成度。2.2 状态流转的核心逻辑与实现项目的状态流转逻辑是这个系统的大脑也是我当时写代码时最谨慎的部分。项目状态我设计成一个枚举类ProjectStageEnum每个阶段的编码、名称、序号都在这里定义和数据库中的配置表对应public enum ProjectStageEnum { STAGE_INITIAL_CONTACT(1, 初步接洽, INITIAL_CONTACT), STAGE_NEEDS_COMMUNICATION(2, 需求沟通, NEEDS_COMMUNICATION), STAGE_SOLUTION_QUOTATION(3, 方案报价, SOLUTION_QUOTATION), STAGE_NEGOTIATION(4, 商务谈判, NEGOTIATION), STAGE_CONTRACT(5, 合同签订, CONTRACT), STAGE_PAYMENT(6, 回款阶段, PAYMENT), STAGE_DELIVERY(7, 交付服务, DELIVERY); private final int seq; private final String name; private final String code; }推进项目的核心方法我把它封装在了ProjectService中调用链路上会做三次校验第一道校验当前用户是否是这个项目的负责人。不是负责人无权操作。第二道校验当前阶段的所有任务是否已经完成。有任何一个任务没勾选完成系统会直接提示请先完成当前阶段的全部待办事项项目无法推进。第三道校验目标阶段是否合法。只能从第 N 阶段推进到第 N1 阶段不允许跳阶段操作。回退操作倒是可以但会记录操作日志防止有人恶意回退数据。public ResultString advanceProject(Long projectId, Long operatorId) { SalesProject project salesProjectMapper.selectById(projectId); // 校验1操作者是否是项目负责人 if (!project.getOwnerId().equals(operatorId)) { return Result.error(只有项目负责人才能推进流程); } // 校验2当前阶段任务是否全部完成 Integer unfinishedCount taskMapper .selectCount(new LambdaQueryWrapperProjectTask() .eq(ProjectTask::getProjectId, projectId) .eq(ProjectTask::getStageCode, project.getCurrentStage()) .eq(ProjectTask::getCompleted, 0)); if (unfinishedCount 0) { return Result.error(当前阶段还有未完成的待办任务无法推进); } // 校验3目标阶段是否使当前阶段的下一阶段 ProjectStageEnum current ProjectStageEnum.getByCode(project.getCurrentStage()); ProjectStageEnum next ProjectStageEnum.getBySeq(current.getSeq() 1); if (next null) { return Result.error(当前已经是最终阶段无需推进); } project.setCurrentStage(next.getCode()); project.setUpdateTime(new Date()); salesProjectMapper.updateById(project); return Result.success(流程已推进至 next.getName()); }这里有一个斩钉截铁的经验流程状态机的校验逻辑必须放在后端绝不能让前端只靠按钮禁用或隐藏来控制。小程序端请求接口是可以被任意人构造的如果前面只做了界面上的推进限制而接口本身不校验任何知道接口地址的人都能绕过界面直接把项目推到终态这在真实系统里是不可接受的。2.3 权限体系的坑和应对方案权限体系这个事我在做这个项目之前以为很简单做完之后发现里面的坑相当多。最简单粗暴的做法是每个用户表里加一个角色字段1 表示销售2 表示主管。但实际业务场景中一个人可能既是销售又是主管他既需要录入自己的项目也需要查看团队的数据。直接改成多对多关系用户表、角色表、用户角色关联表三张表代码写起来又明显复杂对毕设来说有点头重脚轻。我最终的做法是单用户多角色并存取最高权限。用户表里用一个roleLevel字段1 表示普通销售2 表示销售主管3 表示系统管理员。主管可以查看自己负责团队所有销售的项目看板但操作权限仍然受限——主管可以编辑项目信息但推进流程的操作只允许项目负责人执行。管理员则拥有全部配置权限包括管理用户、配置阶段流程、查看全公司项目数据。这样划分之后大部分场景都能覆盖权限逻辑也清楚菜单按角色级别动态加载接口按角色做拦截。拦截器的实现其实很简单定义一个自定义注解标记在需要权限校验的接口上然后实现 HandlerInterceptor 做一个统一的鉴权处理代码量不大效果却很直观。我当时还特意在小程序前端的登录逻辑里做了角色判断主管登录之后底部 TabBar 会多一个团队看板入口这个细节在演示的时候很加分。3. 小程序端核心功能实现要点3.1 登录与 openid 获取全流程小程序端登录是一个看起来很基础、但很多人搞不清楚完整链路的环节。尤其容易出错的是为什么我的后端拿不到用户信息。登录链路分三步第一步前端调用wx.login()这个接口会返回一个临时凭证code。注意这个 code 有效期只有 5 分钟而且只能用一次用完之后作废所有拿到 code 一定要尽快传给后端。第二步后端拿到 code 之后调用微信的接口https://api.weixin.qq.com/sns/jscode2session用 code 换取用户的 openid 和 session_key。这个接口需要四个参数小程序的 appid、secret、刚才传过来的 code以及固定值authorization_code。第三步后端拿到 openid 之后先去数据库查这个 openid 是否已经存在。如果不存在说明是新用户自动创建账号如果存在直接基于用户信息生成一个自定义登录态返回给前端。关于自定义登录态我这里用的是 JWT生成的 token 里包含 userId 和 roleLevel设置 7 天有效期前端每次请求都放在 header 里带过来后端拦截器解析。之所以不用后端 session 方案是因为小程序一旦关闭重新打开session 的持久化往往不可靠而 JWT 天然无状态服务端不存任何会话记录压力小且天然适合前后端分离。还要特别提醒一个容易踩的坑尽量不要在小程序端用wx.getUserProfile()直接拿用户头像昵称存入自己的数据库。从某次微信版本更新之后这个接口返回的昵称已经统一变成微信用户头像也是灰色默认头像真正的用户信息已经被微信收走了。对于需要用户昵称头像展示的业务常规做法是让用户去个人中心里手动上传头像、填写昵称或者走微信提供的头像昵称填写能力组件这个组件会弹出微信自己的选择页让用户选择微信头像昵称或者自定义填写。我当时系统里直接提供了编辑资料页面用户进来自己设置没有纠结实时拿微信头像这件事。3.2 项目列表、详情与流程推进页项目列表页是整个销售日常使用频率最高的页面。我做了三个维度切换我负责的、我参与的、团队全部对应查询的时候就是按 ownerId 筛选、按 team 关联筛选、按数据权限全查。列表的每一项卡片展示项目名称、客户名称、当前阶段、项目金额、赢单概率配合一个进度条样式的组件视觉上直观展示项目走到哪一步了。这里有个代码细节可以分享项目列表接口必须做分页而且最好用滚动触底加载下一页的方式而不是一次性拉全部数据。销售项目一多一次性渲染几百条数据小程序端会明显卡顿。我当时是用的onReachBottom生命周期函数实现触底加载分页大小为 10 条配合后端用 MyBatis-Plus 的Page分页参数效果很平滑。项目详情页则是流程化管理的核心展示区。上半部分展示项目基本信息下半部分用时间线组件展示当前项目的阶段推进记录。时间线每个节点显示阶段名称、负责人、推进时间当前阶段高亮显示。再往下是阶段任务清单任务是勾选式的用户勾选完成后任务的完成状态立即回写到后端。这种所见即所得的交互方式是我在原型设计阶段就定下来的——销售在外面跑没有耐心在密密麻麻的表单里填一堆东西好的交互应该把信息录入量降到最低。流程推进按钮放在详情页的最底部按钮文案会跟随当前阶段动态变化。比如当前阶段是初步接洽按钮文案就是推进至需求沟通用户点一下弹出确认框显示确认将项目推进至下一阶段点击确认后调用后端接口校验通过则整个时间线刷新新的阶段出现在时间线上。3.3 顶部导航栏高度与页面适配小程序顶部导航栏的高度问题看起来很细枝末节但真机调试时一旦没处理好页面就会明显歪掉体验非常拉胯而且这个坑几乎所有第一次做小程序的人都会踩一遍。微信小程序的顶部区域由两部分组成状态栏显示手机电量、时间的那一条和导航栏显示页面标题的那一条。状态栏高度在不同手机上不一样iPhone 的刘海屏和普通安卓全面屏高度都有差异。导航栏的默认高度一般是 44px 或 48px但在自定义导航栏模式下这个高度完全由你的代码决定。如果你在页面的app.json或页面的.json文件里配置了navigationStyle: custom那么页面顶部就完全由自己绘制这时候需要动态获取导航栏高度来做占位。获取方式是这样的const systemInfo wx.getSystemInfoSync(); const menuButton wx.getMenuButtonBoundingClientRect(); const statusBarHeight systemInfo.statusBarHeight; const navBarHeight (menuButton.top - statusBarHeight) * 2 menuButton.height;这里面的逻辑是wx.getMenuButtonBoundingClientRect()返回的是右上角胶囊按钮的位置信息胶囊按钮的垂直居中位置大致就是导航栏的垂直中心所以导航栏总高度等于胶囊按钮顶部到状态栏底部的距离的两倍加上胶囊按钮本身的高度。这套公式是官方社区里比较通用的算法实测下来各机型误差很小。我的建议是不要给自己的毕设项目增加这种自定义导航栏的工作量直接用微信自带的基础导航栏把标题配置好省事儿又不容易出错。但如果你的项目有那种首页需要放一个大搜索框之类的需求必须自定义导航栏那就把上面的适配代码封装成一个工具函数放到 utils 里在每个使用自定义导航栏的页面 onLoad 时调用一次把返回的高度值存到 data 里然后在 wxml 的占位 view 上绑定这个高度。千万别忘记这一步否则你会在 iPhone 上测试没问题、换到安卓上标题就顶到状态栏里去了。4. 微信支付 V3 对接全流程与避坑记录4.1 V2 与 V3 的核心区别这个项目里我用到了微信支付过程属实是踩了不少坑特别是小程序微信支付V3的对接值得单独写一段。先说一下为什么直接选 V3 不选 V2这也是很多人的疑惑。V2 和 V3 最核心的区别在两点。第一是接口风格V2 使用的是 XML 格式的数据交互V3 则全面改用 JSON这对接 Java 体系非常友好不需要再做 XML 的序列化与反序列化。第二是签名机制V2 用的是 MD5 或 HMAC-SHA256V3 用的是微信支付平台证书 商户私钥的双向签名和验签安全性强很多但配置复杂度也上了一个台阶。微信官方已在推动商户迁移到 V3新商户默认只能接 V3所以这个方向是正确的。V3 对接需要准备的核心材料有这四样商户号mchid申请微信支付成功后分配的一串数字。商户 API 私钥在商户平台生成相当于你作为商户的身份证印章所有需要商户签名的请求都要用它。商户证书序列号商户 API 证书里的序列号微信用来识别你的证书。APIv3 密钥在商户平台手动设置的一串 32 字符密钥主要用于回调数据解密或平台证书下载时的加密。其中经常出问题的就是第四项——APIv3 密钥。很多人在商户平台设置的时候没注意设置完之后就找不到了。这个密钥和微信支付商户平台登录密码不一样它是在API安全页面设置的设置后不可找回只能重置。我当时就因为这个问题卡了半天最后打了客服电话才搞清楚有种守着宝藏钥匙却死活找不到门的感觉。4.2 小程序支付下单与回调完整流程小程序端支付的核心流程是这样的第一步用户在小程序端点击去支付小程序把订单号发送给自己的后端服务器。第二步后端接收支付请求根据订单号在本地数据库查询订单信息包括订单金额、商品描述、用户 openid然后调用微信支付的下单接口。第三步微信支付接口返回一个prepay_id预支付交易会话标识。后端拿到这个 prepay_id 之后需要生成一个新的签名然后返回给前端以下五个参数appId timeStamp nonceStr package值为 prepay_idxxx signTypeRSA这里的 signType 是重点中的重点。V3 的签名算法是 RSA和 V2 时代的 MD5 完全不同。很多人对接的时候报了sign error或者invalid signature绝大多数是因为这里的签名算法弄错了。第四步小程序端拿到这五个参数以后调用wx.requestPayment()拉起支付界面。用户输入密码、支付成功微信会以异步的方式发送支付结果通知到你的回调地址。第五步后端拿到回调通知之后需要先做验签再解密报文拿到支付结果更新数据库里的订单状态。这一步也是 V3 区别于 V2 最明显的地方回调通知的报文是加密的需要用你的 APIv3 密钥做 AES-256-GCM 解密。整个流程中我有一个特别深刻的教训回调接口必须要有幂等处理。因为微信的支付回调不保证只发送一次极端情况下同一个支付结果会回调多次。如果你在回调里执行的是更新订单状态为已支付这个操作本身是幂等的没关系但如果你不小心在回调里执行给用户增加余额这样的操作回调重复一次用户余额就会多出一份。所以回调处理的代码里一定要先查订单状态判断这个订单是否已经处理过了如果已处理直接返回成功应答不再重复执行业务逻辑。PostMapping(/notify/pay) public String payNotify(RequestBody String requestBody, RequestHeader(Wechatpay-Timestamp) String timestamp, RequestHeader(Wechatpay-Nonce) String nonce, RequestHeader(Wechatpay-Signature) String signature, RequestHeader(Wechatpay-Serial) String serial) { // 1. 验签确认请求来自微信支付 boolean verify wxPayService.verifyNotifySignature(requestBody, timestamp, nonce, signature, serial); if (!verify) { return failResponse(); } // 2. 解密通知报文 WxPayNotifyResult notifyResult wxPayService.decryptNotifyData(requestBody); String outTradeNo notifyResult.getOutTradeNo(); // 3. 幂等处理先查订单状态 Order order orderMapper.selectByOutTradeNo(outTradeNo); if (order ! null PAID.equals(order.getStatus())) { return successResponse(); } // 4. 更新订单状态记录支付流水号 order.setStatus(PAID); order.setTransactionId(notifyResult.getTransactionId()); orderMapper.updateById(order); return successResponse(); }这个小节的代码逻辑是我在整个毕设里最骄傲的部分因为它在真实业务上也是完全能跑通的写法。每次答辩或者给同学演示的时候我会特意把这段代码拉出来详细讲一下为什么要先查状态再更新既能展示业务思考也能体现工程素养。4.3 平台证书与无可用的平台证书报错微信支付V3这个无可用的平台证书报错是我当时搜遍全网才搞定的一个问题现在看到这个关键字都觉得亲切。这个问题出现的背景是这样的V3 接口在回调验签和解密时正常情况下应该使用微信支付平台证书。但这个证书的有效期只有一年过期之后需要更新。很多用 SDK 的同学会遇到这个报错是因为在初始化 SDK 的时候下载了平台证书并保存在本地但证书过期后本地文件没有更新SDK 拿着过期的证书去验签或解密就会提示平台证书不可用。我当时用微信官方推荐的wechatpay-javaSDK这个问题可以通过配置wechatpay4j的方式来解决——在商户平台下载新的平台证书放到项目的src/main/resources/cert/目录下然后在配置文件里指定证书路径。注意平台证书和商户证书是两回事商户证书是证明你是你平台证书是证明微信是微信配置的时候别放颠倒文件名也别写错。另一个更加省心的思路是用自动更新平台证书方案SDK 启动时先从微信服务器动态拉取最新的平台证书而不是用本地文件。这种方式避免了手动更新证书的烦恼但要求首次启动时网络环境能正常访问微信支付服务器部署到内网服务器时需要额外放通白名单。4.4 小程序违规与支付功能被限制的应对这个场景虽然不太愉快但确实值得记录一下。我开发的系统在测试阶段经历过一次小程序违规支付功能暂时无法使用的限制原因是小程序的主体资质没有完成微信认证支付权限被临时关闭。当时用户一打开支付页面就出现由于小程序违规支付功能暂时无法使用的提示。这里要重点说明一下这个提示并不一定是真的违规更多情况下是主体的微信认证没完成、支付权限没有成功开通或者开通了但没通过审核。排查路径是打开微信公众平台 → 左侧菜单支付 → 查看支付权限状态。如果是未开通状态需要先完成小程序认证个人主体无法认证必须企业或个体工商户主体然后申请微信支付提交营业执照和银行账户信息审核通常需要一至三天。如果你在开发测试阶段还没申请到正式的微信支付商户号可以暂时在开发者工具里使用虚拟支付模拟体验在开发者工具中点击编译模式旁边的下拉箭头选择添加编译模式然后勾选模拟支付接口选项这样无需真实的商户号也能调通整个支付流程等到正式上线前再替换成真实参数就行。这个功能对毕设演示和开发联调来说简直救命我当时就是靠这个方式在没有商户号的情况下把前端支付流程全部调通的。5. 常见问题与排查技巧实录这部分我整理一下整个项目开发过程中遇到的高频问题和对应的排查思路这些问题有些是我踩过的有些是我帮同学排查过的每一类都很有代表性。5.1 图片保存到相册失败uniApp或原生小程序中调用wx.saveImageToPhotosAlbum()时提示saveImageToPhotosAlbum:fail这个报错的常见原因有两个第一个原因是缺少权限声明。在小程序的app.json文件中需要配置permission字段尤其是安卓平台读取相册权限必须在配置文件里声明。同时在调用保存图片之前最好先通过wx.getSetting检查当前用户的权限状态如果用户拒绝了授权要引导用户去wx.openSetting里手动开启权限这一步不能省略。第二个原因是图片格式问题。saveImageToPhotosAlbum要求图片必须是本地路径或临时路径如果传入的是网络图片 URL会直接失败。正确的做法是先用wx.downloadFile把网络图片下载到本地临时路径拿到临时路径之后再调用保存接口。如果是 base64 格式的图片还得先通过wx.getFileSystemManager().writeFile写成临时文件才能保存。5.2 手机软键盘遮挡输入框真机调试时在小程序里填写表单软键盘弹起来后把输入框或查询内容挡住是一个极其常见但也极其影响体验的问题。原因在于页面底部固定元素如提交按钮没有随软键盘的出现而上移。我从这个项目里总结出来的常规方案是这样不要把按钮设计成固定在页面底部的形式取而代之把底部操作区放在内容流里让整页内容自然滚动。如果业务上确实需要底部固定按钮可以使用adjust-position属性来控制页面整体上移或者监听键盘高度wx.onKeyboardHeightChange()手动给底部按钮设置一个动态的bottom值等于键盘高度。当然更粗暴也更好用的办法是在小程序页面的.json配置文件里把disableScroll: false同时使用cursor-spacing属性给输入框设置光标与键盘之间的间距。像搜索查询场景只要给输入框设置了合理的cursor-spacing100软键盘弹出后遮住查询内容的概率就会降低很多。5.3 蓝牙搜索部分手机收不到设备如果你的销售系统场景里用到了蓝牙搜索设备比如连接便携打印机会遇到有的手机能搜索到设备有的手机搜索不到的问题这个问题的根源往往不在代码而在手机系统的蓝牙权限策略。排查思路分三步走。第一步确认小程序已在app.json中声明蓝牙相关权限第二步确认手机系统授予了微信应用位置信息权限这一点尤其在 iOS 设备上非常严格——蓝牙扫描会用到位置权限没有位置权限蓝牙扫描大概率失效第三步在代码中开启蓝牙适配器之前先检查手机蓝牙是否已经打开以及系统权限是否已经授好。wx.openBluetoothAdapter({ success: function (res) { wx.startBluetoothDevicesDiscovery({ allowDuplicatesKey: false, interval: 2000, success: function () { console.log(开始搜索蓝牙设备); } }); }, fail: function (err) { console.log(蓝牙适配器初始化失败, err); } });如果你确定权限都开了还是搜索不到可以在同样的位置用微信-发现-小程序里的官方蓝牙调试工具对比测试如果官方工具也搜不到那基本可以排除代码问题判断为手机系统或硬件兼容性问题。5.4 微信开发者工具与真机表现不一致真机调试和开发者工具表现不一致这个属于经典中的经典。常见表现有开发者工具上样式正常真机上布局错乱开发者工具上接口请求正常真机上请求失败开发者工具上支付模拟正常真机上无法调起支付。排查顺序是这样的先看详情 - 本地设置里有没有勾选不效验合法域名。开发者工具默认对请求域名做合法性校验如果没有在微信公众平台配置合法服务器域名就必须勾选这个选项才能调试。但真机上没有这个开关真机运行时必须配置合法域名才行否则请求直接被拦截。再看wx.request的请求地址是不是用了 localhost。开发者工具里 localhost 指向电脑本机真机上 localhost 指向手机自己完全不是一回事。正确的联调方式是让电脑和手机处于同一个局域网电脑端启动后端服务监听 0.0.0.0然后真机调试时把请求地址改成电脑的局域网 IP比如http://192.168.1.100:8080。最后看有没有用到真机才有的原生能力。像微信支付、蓝牙、扫码这类能力在开发者工具里只是模拟很多细节在真机上行为完全不同。这类问题没有捷径建议在每个真机调试环节集中完整测一遍并写一个自测清单避免反复打包发现遗漏。5.5 小程序源码保护与反编译话题开发过程中还有一个绕不开的话题是已经部署的小程序能不能拿到源码。我必须先说结论可以而且难度远比你想象的低。小程序代码包在发布时会打包成wxapkg文件网络上流传的反编译工具可以还原出大部分前端代码包括页面的 wxml、wxss、js 逻辑。这给我们的警示是永远不要把敏感信息写在客户端代码里。我在这个项目中特别注意了一点小程序端的代码不包含任何后端接口密钥、数据库连接信息、加密私钥后端接口所有敏感操作都必须有鉴权校验。如果你的项目里有管理员的默认密码或者微信支付的 APIv3 密钥一旦被反编译提取到后果就是整个系统裸奔。从开发规范上讲前端代码要假设任何人能看到所以前端的代码要干净、无密钥、无敏感逻辑。需要保密的业务逻辑应该永远放在后端服务中。对毕设来说这个安全习惯不仅是在作业里体现专业度也是未来进入工作时最基础的安全红线。6. 这套系统的价值延伸与扩展方向做完整个毕设我最大的感受是毕业设计的价值不在于代码量有多少而在于它是否能真正解决一个具体场景的问题。销售项目流程化管理系统这个小程序切入点不大但业务链路完整——从客户信息维护、项目阶段流转、待办任务管理到支付流程、统计看板每一条链路都有实际业务场景做支撑这比浮于表面的XX管理系统扎实得多。如果在现有基础上继续扩展有几个方向非常值得做。第一个方向是数据分析看板。项目数据沉淀下来之后可以统计每个销售人员的项目转化率、平均成交周期、阶段停留时长甚至能推出哪个阶段最容易丢单这样对管理真正有用的结论。我当时只做了基础的按状态统计这个已经能在答辩时展示得不错了。第二个方向是流程模板的可视化配置。目前阶段配置虽然做成了数据库表但调整规则还是需要后端代码更新。如果做一个管理端页面让管理员通过拖拽的方式配置流程阶段、必填字段、审批人这套系统的适应面会从销售场景扩展到更多行业。当然这部分完整做下来工作量不小适合作为二期规划。第三个方向是消息通知的智能触达。比如某个项目在需求沟通阶段停留超过七天系统自动给对应销售推送一条该跟进了的提醒比如客户长时间未跟进时系统自动给主管发送预警。这类智能化提醒对销售业务的提效非常明显实现也不复杂就是在定时任务里查一下阶段状态和更新时间触发对应的订阅消息发送。这三个方向里我当时选了第三个作为进阶功能做了一个简化版本订阅消息发送。说实在的微信订阅消息本身比想象中麻烦一点主要在于小程序需要用户主动订阅授权而且每次授权只能推送一条内容。但即便如此系统主动找用户这个体验还是要比用户自己打开小程序查看高一个量级。这个功能当时在答辩演示环节提供了不少亮点老师们普遍觉得这个不是玩具是真的有使用场景。7. 写在最后的几点真心话项目做完了答辩也顺利过了回头再看整个过程有几句话想对准备做类似项目的同学说。第一毕业设计的功能宁做深不做宽。我当时其实想过加考勤打卡、加销售排行榜、加日报周报但后来都砍掉了只保留了客户、项目流程、任务、支付、统计这五个和销售项目流程化最相关的核心模块。功能越聚焦每个模块能打磨的细节越多答辩的时候展示起来也越有底气。第二技术选型不要盲目追新。Spring Boot 2.7 足够稳定MyBatis-Plus 足够顺手小程序原生语法虽然啰嗦但资料多。追求最新的技术栈不是不行但毕业设计的时间成本摆在那里与其花两周踩新版本的坑不如把这些时间用在把业务流程打磨得更完整上。第三数据设计要反复推敲。状态字段用字符串还是数字、项目金额用什么类型、时间字段如何统一格式这些细节表面上不起眼但等到联调阶段出现各种类型转换问题、筛选条件对不上数据的时候你就会明白前期设计的重要性。第四也是最重要的一条一定要在开发之前把业务流程画清楚。我当时画了一张完整的业务流程图贴在自己桌子前面每个阶段做什么事、谁能操作、数据怎么流动都写清楚以后才开始写代码。事实证明这个先行的设计过程帮我省去了大量的返工时间。整个过程做下来写代码其实是最简单的一环难的是想清楚各个业务节点之间的约束和关系。最后说个私心的话。毕设并不是做完交了答辩就完事儿的阶段性产物你在选题时如果可以贴近真实应用场景做完之后的收获会比做一个题库管理或者宿舍报修大得多。因为真实的业务场景会逼你想清楚权限、状态机、幂等、数据一致性问题做完了你就真的会用这些概念了。
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门