ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

基于微信小程序的培训机构客户管理系统设计与实现

基于微信小程序的培训机构客户管理系统设计与实现 说起培训机构客户管理很多人第一反应是弄个Excel表不就行了。真做过这行的人都知道线索一多、销售一换、跟进一乱表格根本扛不住。尤其那种微信里聊着聊着就丢客户的场景谁经历谁头疼。所以当看到基于微信小程序的培训机构客户管理系统这个项目时我挺有感触——这正好戳中了中小型培训机构最实际的需求也非常适合作为课程设计或毕业设计的选题方向。这个项目做出来是一个完整的微信小程序给培训机构的销售、教务和管理人员用核心管三件事客户线索从哪来、跟进到哪一步、怎么转化成报名。配套源码、文档和调试记录都齐相当于一个能直接跑的完整业务闭环。如果你在找毕设/课设选题或者你是机构里想搞信息化改造但预算有限的人这篇文章会从设计思路、数据库建模、前后端实现到真机调试的坑一条线给你讲清楚。1. 项目整体设计与技术选型拆解1.1 培训机构客户管理的真实痛点要做这个系统先得把业务痛点搞清楚否则做出来就是个花架子。我接触过不少小型培训机构的运营方式普遍存在这几个问题。线索散落。潜在客户的联系方式基本躺在销售的个人微信里公司不掌握销售一走客户资源跟着走这是最致命的一点。跟进没章法。今天聊了谁、明天该给谁打电话、谁的试听约了没全靠销售脑子和手写备忘录一忙就忘。转化链路断。家长从咨询到试听、再到报名缴费中间哪个环节流失了说不清楚。数据汇总靠手工。月底统计业绩教务翻聊天记录、销售抄Excel费时费力还不准。这套系统的价值就是把客户从个人微信里拽出来放进一个公共池子里管。谁负责跟进、跟进到哪个状态、下一步该干什么都记录在案。对管理者来说就是一个轻量级的CRM客户关系管理系统只不过它跑在微信小程序里。1.2 为什么选微信小程序而不是App或网页客户管理系统的载体有很多选择为什么这个小程序方案更合适这里有几个很现实的原因。第一是触达成本低。培训机构对接的家长群体微信是日常高频应用。家长想看课程信息、查试听安排不需要下载App、不需要注册网页账号微信里点开就用心理负担小。第二是开发门槛适中。微信小程序的前端语法类似Vue后端用常见的Spring Boot或Node.js都能承接对在校生来说技术栈熟悉、参考资料丰富、答辩好讲。第三是部署和审核路径成熟。小程序通过微信公众平台注册、提交审核有一套完整流程符合国内软件交付的惯例。当然小程序也不是没有短板最典型的就是包体大小限制主包2M以内和需遵循微信平台的审核规范。但这些对于客户管理这类工具型应用来说完全够用——它不需要复杂动画也不需要重型图表核心就是表单、列表、详情页和几个状态流转。1.3 技术栈选型与架构分层这个项目我用了一套比较稳妥的组合适合复刻也适合二次开发前端微信小程序原生框架WXML WXSS JS。为什么不用uni-app因为原生框架调试最直观、报错最直接对新手友好。后续想跨端再迁uni-app也不难。后端Spring Boot MyBatis Plus。Java在课程设计里是主流导师认可度高而且Spring Boot对接口开发、参数校验、异常处理的支持都很完善。数据库MySQL 8.0。单库单表设计用Navicat或命令行都方便操作。鉴权方式登录后后端签发Token小程序端存储到Storage每次请求带上。不引Session那套复杂概念简单干净。联调工具微信开发者工具 Postman。前端联调用开发者工具的Network面板看请求后端接口测试交给Postman。架构上分三层小程序端负责展示和交互后端Controller层提供RESTful接口Service Mapper层处理业务和数据库操作。层与层之间通过JSON传数据接口路径统一用/api/v1/xxx格式方便后期扩展。提示做这类管理系统的毕设最忌讳一上来写代码。先把业务角色和状态流转画清楚后面写代码就是照图施工。2. 核心功能模块拆解与数据库设计2.1 四个核心业务模块线索、试听、报名、跟进我把系统拆成四个模块分别对应培训机构业务里最常见的动作。客户线索管理。销售录入新客户记录姓名、手机号、微信号、意向课程、客户来源朋友圈广告、转介绍、地推等同时分配一个负责人。系统支持按销售、按状态筛选客户列表。跟进记录管理。销售每次和客户沟通后写一句跟进内容设置下次跟进时间。客户卡片页上能看到这条客户从创建到现在所有的跟进轨迹一眼就知道这个人聊到哪了。试听预约与到课管理。教务帮客户约试听课记录预约时间、课程、教师到课之后标记已到/爽约为后续统计试听转化率积累数据。报名缴费与合同管理。客户成交后登记报读课程、缴费金额、合同编号由待办的潜在客户转为正式学员。这四个模块串起来正好覆盖了机构业务的主链路获客 - 跟进 - 试听 - 转化。在给导师或客户讲这个项目时这条链路就是你的核心逻辑比堆砌功能更打动人心。2.2 角色权限设计销售、教务、主管怎么分系统里至少有三种角色如果不好好设计权限后面会乱套销售负责自己名下的客户。只能看到和操作分配给自己的线索录入新客户后默认负责人是自己。教务负责试听课排期、到课登记、课程信息维护。可以看客户列表但不动客户归属和销售数据。主管/管理员看全局。所有客户数据可见、可分配查看各销售的跟进量、成交业绩和线索转化率。权限实现上我不建议搞太复杂的权限框架用小程序的登录态角色字段 后端接口的权限校验就行。后端拦截器里判断当前用户角色然后决定是否放行。比如/api/v1/customers/list所有登录用户可访问但/api/v1/customers/assign只有主管角色能调用。这样代码量可控答辩时也好解释。2.3 数据库表结构与关键字段设计数据库设计是整个项目的基石。下面是几张核心表的结构要点随便拿一张表出来都能在文档里写半页。用户表表名sys_user字段类型说明idbigint主键自增usernamevarchar(50)登录账号passwordvarchar(100)加密存储BCryptroletinyint角色1-销售 2-教务 3-主管real_namevarchar(50)真实姓名openidvarchar(100)微信登录绑定phonevarchar(20)绑定手机号create_timedatetime创建时间客户表表名customer_tbl字段类型说明idbigint主键namevarchar(50)客户称呼phonevarchar(20)联系电话wechatvarchar(50)微信号sourcevarchar(20)来源朋友圈/转介绍/地推等intent_course_idbigint意向课程statustinyint状态1-潜在 2-跟进中 3-已试听 4-已报名 5-已流失owner_idbigint负责人销售idnext_follow_timedatetime下次跟进时间remarkvarchar(255)备注deletedtinyint逻辑删除标志create_timedatetime创建时间跟进记录表表名follow_record_tbl字段类型说明idbigint主键customer_idbigint客户iduser_idbigint跟进人idcontentvarchar(500)跟进内容next_follow_timedatetime下次跟进时间create_timedatetime跟进时间试听预约表表名trial_lesson_tbl字段类型说明idbigint主键customer_idbigint客户idcourse_idbigint课程idteacher_idbigint教师idappoint_timedatetime预约时间attend_statustinyint到课状态0-未到 1-已到 2-爽约create_timedatetime创建时间报名表表名enroll_order_tbl字段类型说明idbigint主键customer_idbigint客户idcourse_idbigint课程idcontract_novarchar(50)合同编号amountdecimal(10,2)缴费金额pay_statustinyint支付状态0-未支付 1-已支付create_timedatetime报名时间在设计时有三个细节我特别想提醒一是逻辑删除字段。客户资料是敏感数据真删了很难恢复所以统一用deleted 1表示已删除查询时条件里默认带上deleted 0。二是状态字段用数字枚举。不要在数据库里存跟进中这种中文扩展性和排序都不方便。代码里定义好常量前端展示时再翻译成中文标签。三是时间字段统一用datetime。虽然可以用时间戳但直接看库的时候datetime可读性强得多前后端传参也方便。3. 微信小程序端核心功能实现与联调3.1 登录与获取手机号从 code 到 token 的完整链路登录是几乎所有小程序的第一个关卡。这片地方的坑很多我把它拆开讲。第一步前端调wx.login()拿到一个临时code这个code有效期很短只能向后端换一次信息。然后把code发给后端接口/api/v1/auth/login。后端拿到code后调用微信官方接口jscode2session用appid secret code换回openid和session_key。openid就是用户在小程序里的唯一身份标识后端拿它来查用户表查到就签发Token查不到就返回未注册状态。// 小程序端登录请求 login() { wx.login({ success: async (res) { if (res.code) { const { data } await wx.request({ url: https://你的后端域名/api/v1/auth/login, method: POST, data: { code: res.code } }); wx.setStorageSync(token, data.token); } } }); }后端Controller核心逻辑大概是PostMapping(/login) public Result login(RequestBody LoginReq req) { String openid wxService.code2Session(req.getCode()); SysUser user userMapper.selectByOpenid(openid); if (user null) { return Result.error(未注册); } String token JwtUtil.createToken(user.getId(), user.getRole()); return Result.success(token); }获取手机号是另一个高频需求培训机构想直接拿到家长的真实手机号方便销售电联。微信目前的规定是必须用button open-typegetPhoneNumber这种用户主动点击的按钮来触发不能静默获取。用户允许后前端拿到一个code注意不是手机号明文后端拿这个code去调微信的接口换手机号信息。而且这个能力要求小程序是企业主体个人主体的号开通不了并且接口调用有次数限制测试阶段很容易踩坑。button open-typegetPhoneNumber bindgetphonenumberonGetPhoneNumber 获取手机号 /button// 前端回调 onGetPhoneNumber(e) { if (e.detail.code) { // 拿着这个code去后端换手机号 request.post(/api/v1/auth/phone, { code: e.detail.code }); } }3.2 客户列表、筛选与跟进记录的前端实现客户列表页是小程序的门面也是销售每天打开最多的页面。这个页面实现时要注意的不只是渲染还有交互体验。分页加载是很常见的设计。用onReachBottom触底加载下一页配合data.page做累加。合理设置pageSize建议10-20条太多会卡顿太少点击累。我在实现时会在请求体里带pageNum和pageSize后端返回total总数前端计算还有没有下一页。onReachBottom() { if (this.data.pageNum * this.data.pageSize this.data.total) { wx.showToast({ title: 没有更多了, icon: none }); return; } this.setData({ pageNum: this.data.pageNum 1 }); this.loadCustomers(); }状态筛选用小程序原生的picker组件就行。筛选条件变化时重置页码重新拉列表。这里有个细节筛选组件放在顶部固定会遮住内容建议用普通的页面内选择器而不是悬浮胶囊。下拉刷新用enablePullDownRefresh开启在页面配置里加enablePullDownRefresh: true配合onPullDownRefresh方法处理。刷新完记得调wx.stopPullDownRefresh()关闭转圈动画否则页面会一直转。跟进记录的时间线是客户详情页的重点。用一个小列表按时间倒序排列字段包括跟进内容、跟进人、跟进时间。如果客户状态变化了也要在时间线里留一条记录比如状态变更为已报名。这样管理者和销售复盘时有一条完整的时间轴可看。3.3 后端接口设计与前端联调注意事项前后端联调是项目里最容易浪费时间的阶段几十个接口每个都有可能出现字段名对不上、类型不一致、时区不对等小问题。我一般这么处理先定接口文档再写代码。用表格或在线文档把每个接口的路径、请求参数、返回字段列清楚。比如获取客户详情GET /api/v1/customers/{id} Header: Authorization: Bearer token 返回: { code: 200, data: { id: 1, name: 王女士, phone: 138****1234, status: 2, nextFollowTime: 2025-03-01 14:00 } }统一返回结构。后端接口不要一会儿返回一个对象、一会儿返回一个数组统一包装成ResultT里面固定放code、message、data三个字段。前端封装一个request函数先判断code 200再取data这样异常情况统一弹 toast代码能少写一半。// 前端封装的request函数 const request (url, method, data) { return new Promise((resolve, reject) { wx.request({ url: BASE_URL url, method, data, header: { Authorization: Bearer wx.getStorageSync(token) }, success: (res) { if (res.data.code 200) { resolve(res.data.data); } else { wx.showToast({ title: res.data.message, icon: none }); reject(res.data); } } }); }); };联调时注意后端跨域问题。虽然小程序不是浏览器没有CORS限制但如果你用开发者工具的不校验合法域名模式请求一个IP或localhost地址后端一般不需要特殊处理跨域而如果你把后端部署到云服务器并配了HTTPS域名那就一切正常。真正要小心的是后端接口返回数据里的时间字段。Java的LocalDateTime序列化后是2025-03-01T14:00:00这种带T的格式小程序端直接展示不够美观最好在后端统一配置Jackson的日期格式为yyyy-MM-dd HH:mm:ss前端拿到就是干净的字符串。4. 从源码到调试工具链与真机排错实录4.1 微信开发者工具的三个调试入口改代码不是写代码调试才是大头。微信开发者工具里有几个入口我几乎天天用效率差距很大。Console面板是最基础的。我在关键操作上都会打印日志比如登录返回的数据、接口报错信息。在代码里加console.log有个技巧不要只打印变量名要加上说明文字比如console.log(customer detail: , detail)否则真机调试时根本分不清这是哪个请求打的。Network面板看网络请求。所有请求的URL、状态码、请求参数、返回数据都在这里。接口报错时第一反应是来这个面板看而不是去翻代码。常见问题比如404路径错、500后端异常、401Token过期一眼定位。真机预览 vConsole是最后一道关。开发者工具模拟器和真机行为不完全一致特别是涉及wx.getPhoneNumber、定位、存储等能力时必须在真机测试。真机预览开启vConsole在预览页面右上角菜单里打开调试手机上能看到Console日志和Network请求排查模拟器复现不了的问题。调试过程中还会用到一个很关键的开关开发者工具的详情 - 本地设置 - 不校验合法域名。开发阶段后端跑在本地局域网IP上小程序默认只允许请求HTTPS和已备案域名勾上这个选项才能访问http://192.168.1.100:8080这种地址。但记住这只是开发模式上线前必须换成正式HTTPS域名否则日常使用打不开接口。4.2 高频报错排查速查表把我在整个开发调试过程遇到的高频报错整理成一张速查表遇到问题直接照表查能省很多时间。报错现象原因定位解决办法request:fail或url not in domain list域名没配置或没勾选不校验域名开发时勾选不校验合法域名上线前配HTTPS域名getPhoneNumber:fail个人主体小程序无权限或需认证用企业主体完成微信认证部署到正式环境测试code2Session返回40029code无效或已经换过一次临时登录凭证只能换一次openid检查是否重复调用login:fail或invalid code前端传的code已过期重新调用wx.login()刷新code接口返回401/403Token缺失或角色权限不足检查请求头是否带Authorization后端检查Token解析逻辑数据库中文乱码连接字符串缺少编码参数JDBC URL加useUnicodetruecharacterEncodingutf8Cannot read property length of undefined后端返回的data结构不是预期数组先打印返回数据确认list在哪个字段里页面渲染空白数据没绑定上或接口报错看Console和Network确认数据是否成功返回并setData真机可以跑但模拟器卡顿setData数据量太大减少一次性渲染的列表项用分页或仅加载可视区域数据4.3 一套实用的分模块排错思路以前我调试这种项目总是哪里报错就查哪里改了东墙补西墙。后来总结出更有效率的排错思路把系统拆成数据链路按层排查。具体来说任何一个页面出问题都按这个顺序走一遍先看前端Console有没有报错Network请求发出去没有如果请求没发问题在前端逻辑如果发出去了看返回状态码。再查后端接口是否收到了请求参数解析成功没有Service层有没有抛异常后端日志有没有打印SQL最后查数据库SQL能否独立执行成功数据表里有没有记录时间字段、状态字段的值是否符合预期这种排查方式还有个附带好处写文档时能把调试过程写得条理分明桥接指导老师问的你是怎么解决这个难题的有具体的排查过程可讲。还有两个小技巧我觉得特别值钱给后端接口预留一个测试入口。比如单独写一个/api/v1/ping接口返回pong前端断连时先调这个接口判断是网络问题还是业务逻辑问题三秒钟定位。在关键业务节点打可辨识日志。后端在客户创建、状态流转、权限拦截等节点加日志日志里带上客户ID和操作人ID。试想客户状态莫名从跟进中变成已报名如果不是有日志你要查多久有了日志翻一下就定位到是哪个人、什么时间、哪个接口干的。注意调试微信小程序时千万不要把appid和secret写死在代码里提交到公开仓库。secret是后端的机密配置应该放在后端的配置文件或环境变量里。之前见过不少人把密钥写在README里后果非常严重一个晚上就能被脚本扫描薅走。5. 源码交付质量的把控很多人觉得源码能跑就行但课程设计、项目交付的具体评分标准往往包含文档的完整性。这篇我单独把源码文档调试这三件套的质量要点列一下因为在做这类交付时它们的重要性不亚于功能本身。源码层面一定要保证开箱能跑。数据库脚本、后端配置、小程序APPID都要在文档里写清楚。小程序端不能让别人拿到后还得猜这个BASE_URL改成什么。我一般会把config.js里所有可变配置集中在一个文件顶部注释写明每项的取值规则。文档层面至少要包含三份需求文档这是做什么的、设计文档数据库表、接口定义、页面流程图、使用说明怎么启动后端、怎么导入数据库、怎么在开发者工具里跑起来。答辩或者项目交付时大篇幅代码里看不到思路文档却能完整体现设计过程。调试记录层面把开发过程中遇到的关键问题、排查过程、最终解决方案整理成一个障碍排查日志章节。一方面它能证明工作量另一方面未来不管是你自己维护还是别人接手都多了一份接地气的经验和支撑。我见过太多课程设计死磕到最后功能全部实现但一问到某个功能怎么排查出来的就支支吾吾这种情况常常影响成绩非常可惜。还有个小细节关于源码工程的命名和结构。建议按以下方式组织文件夹清爽且不容易被评审老师挑毛病training-crm/ ├── backend/ # Spring Boot后端工程 │ ├── sql/ # 数据库初始化脚本 │ └── src/ ├── miniprogram/ # 小程序前端工程 └── docs/ # 需求文档、设计文档、调试记录说实话我做过的课程设计和毕设项目里客户管理系统是性价比很高的选题之一——它业务清晰、模块边界明确、技术栈通用既好演示又禁得住追问。而且这个方向往深了走还能接统计报表、日程提醒、销售业绩看板可变空间大。如果时间充裕我会建议在完成基本功能后加一个简单的数据看板展示本月新增线索量、试听转化率和各销售业绩排行这一项在答辩和实际应用中的加分效果非常明显。6. 扩展思路与其他应用场景系统做完之后我明显感受到这套东西并不只局限于培训机构。稍微改改字段和流程完全可以复用到好几个场景。健身私教工作室。把课程换成私教套餐意向课程换成意向项目减脂、增肌、康复试听预约换成体测预约。客户是谁、跟到哪一步、什么时候该续费提醒这套流程一脉相承。装修公司。销售拿着业主联系方式从首次沟通、量房、出方案、报价到签约主线跟进逻辑几乎不用改。只需要多一个项目地址字段和一个方案版本的记录表。留学中介。流程变成咨询国家、选校方案、递交材料、拿到offer。但客户管理、跟进记录、状态流转还是那套事。换句话说这个项目的价值不在某个培训机构而在于它把线索、跟进、转化这个万能业务骨架搭好了。理解了这份骨架未来换任何行业只是换一层皮的事情。如果想把系统做得更贴近生产还可以在现在基础上加几个功能提醒通知今天到期的待跟进客户推送提醒、数据导出用Apache POI导出Excel报表、地图功能展示客户分布区域。这些功能我在实际工作中都尝试过改动量不大但使用体验会有质的飞跃。最后分享一个核心心得做这类系统先想清业务再动手写代码。数据库表设计好了后面的接口和页面都要遵守设计规则状态字段这些细节将来会让你在调整需求时少掉很多头发。希望这篇分享能给你的项目或选题带来一些实在的参考。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进