说起培训机构客户管理,很多人第一反应是"弄个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):
| 字段 | 类型 | 说明 |
|---|---|---|
| id | bigint | 主键,自增 |
| username | varchar(50) | 登录账号 |
| password | varchar(100) | 加密存储(BCrypt) |
| role | tinyint | 角色:1-销售 2-教务 3-主管 |
| real_name | varchar(50) | 真实姓名 |
| openid | varchar(100) | 微信登录绑定 |
| phone | varchar(20) | 绑定手机号 |
| create_time | datetime | 创建时间 |
客户表(表名:customer_tbl):
| 字段 | 类型 | 说明 |
|---|---|---|
| id | bigint | 主键 |
| name | varchar(50) | 客户称呼 |
| phone | varchar(20) | 联系电话 |
| varchar(50) | 微信号 | |
| source | varchar(20) | 来源:朋友圈/转介绍/地推等 |
| intent_course_id | bigint | 意向课程 |
| status | tinyint | 状态:1-潜在 2-跟进中 3-已试听 4-已报名 5-已流失 |
| owner_id | bigint | 负责人(销售)id |
| next_follow_time | datetime | 下次跟进时间 |
| remark | varchar(255) | 备注 |
| deleted | tinyint | 逻辑删除标志 |
| create_time | datetime | 创建时间 |
跟进记录表(表名:follow_record_tbl):
| 字段 | 类型 | 说明 |
|---|---|---|
| id | bigint | 主键 |
| customer_id | bigint | 客户id |
| user_id | bigint | 跟进人id |
| content | varchar(500) | 跟进内容 |
| next_follow_time | datetime | 下次跟进时间 |
| create_time | datetime | 跟进时间 |
试听预约表(表名:trial_lesson_tbl):
| 字段 | 类型 | 说明 |
|---|---|---|
| id | bigint | 主键 |
| customer_id | bigint | 客户id |
| course_id | bigint | 课程id |
| teacher_id | bigint | 教师id |
| appoint_time | datetime | 预约时间 |
| attend_status | tinyint | 到课状态:0-未到 1-已到 2-爽约 |
| create_time | datetime | 创建时间 |
报名表(表名:enroll_order_tbl):
| 字段 | 类型 | 说明 |
|---|---|---|
| id | bigint | 主键 |
| customer_id | bigint | 客户id |
| course_id | bigint | 课程id |
| contract_no | varchar(50) | 合同编号 |
| amount | decimal(10,2) | 缴费金额 |
| pay_status | tinyint | 支付状态:0-未支付 1-已支付 |
| create_time | datetime | 报名时间 |
在设计时有三个细节我特别想提醒:
一是逻辑删除字段。客户资料是敏感数据,真删了很难恢复,所以统一用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-type="getPhoneNumber">这种用户主动点击的按钮来触发,不能静默获取。用户允许后,前端拿到一个code(注意不是手机号明文),后端拿这个code去调微信的接口换手机号信息。而且这个能力要求小程序是企业主体,个人主体的号开通不了,并且接口调用有次数限制,测试阶段很容易踩坑。
<button open-type="getPhoneNumber" bindgetphonenumber="onGetPhoneNumber"> 获取手机号 </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" } }统一返回结构。后端接口不要一会儿返回一个对象、一会儿返回一个数组,统一包装成Result<T>,里面固定放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(后端异常)、401(Token过期),一眼定位。
真机预览 + 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返回40029 | code无效或已经换过一次 | 临时登录凭证只能换一次openid,检查是否重复调用 |
login:fail或invalid code | 前端传的code已过期 | 重新调用wx.login()刷新code |
| 接口返回401/403 | Token缺失或角色权限不足 | 检查请求头是否带Authorization,后端检查Token解析逻辑 |
| 数据库中文乱码 | 连接字符串缺少编码参数 | JDBC URL加useUnicode=true&characterEncoding=utf8 |
Cannot 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报表)、地图功能(展示客户分布区域)。这些功能我在实际工作中都尝试过,改动量不大,但使用体验会有质的飞跃。
最后分享一个核心心得:做这类系统,先想清业务,再动手写代码。数据库表设计好了,后面的接口和页面都要遵守设计规则;状态字段这些细节,将来会让你在调整需求时少掉很多头发。希望这篇分享能给你的项目或选题带来一些实在的参考。