基于vue3+uniapp的少儿编程选课报名作业系统开发实战
2026/9/9 17:54:46 网站建设 项目流程

说实话,刚接到这个需求的时候,我差点把它当成一个普通的CRUD项目来做——少儿编程培训机构,要选课、报名、交作业、管学员,听起来不就是一个后台加一个家长端小程序吗?真把原型捋完才发现,这里头的水比想象中深得多:家长选课要考虑同一时间段的排课冲突、名额不能超卖,老师布置作业要处理代码文件和图片上传,机构端还要盯着微信支付的合规门槛,更不用说后面上架审核那一堆事。这篇就记录我用 vue + uniapp 从零搭一套少儿编程培训机构选课报名作业系统管理小程序的完整过程,从业务模型、技术选型、核心链路实现,到各种绕不开的坑,一次性拆开讲清楚。准备接手这类教育项目、或者打算给自家机构做小程序的开发者和教务负责人,都可以拿这篇当参考。

1. 先画清业务地图:三类角色、六张核心表和边界取舍

做任何系统第一步都不是写代码,而是把业务方摆到同一张桌子上,确认"谁在什么端上做什么事"。这个环节省掉了,后面改需求的时候流的就是眼泪。少儿编程培训机构和普通K12学科机构还不太一样:课程通常按阶段划分(图形化启蒙、Python、C++竞赛等),班次固定、每周一次课,作业以代码和作品截图为主。这些特点直接决定了数据模型和页面设计。

1.1 用户角色拆解:谁在什么端上做什么事

我把系统里的用户分成了三类,每一类的工作频率、操作场景、设备习惯完全不同,直接混搅在一个界面里一定会出问题。

角色使用端核心动作
家长/学生微信小程序浏览课程、选课报名、在线支付、查看作业、提交作业、查看评语
老师小程序 + H5后台维护班次、确认名单、布置作业、批改作业、发送通知
教务运营H5后台课程上架、名额设置、订单管理、异常退款、数据统计

家长端主要在手机上,追求的是下单路径短、提交作业顺畅;老师端反而更适合在电脑上操作,因为批改代码作业的时候要盯着大屏,而且经常要复制代码片段、查看图片;教务端则要处理批量导入和表格核对,操作密度高,不适合塞进小程序里。

所以第一版我没有把三个角色全挤在一个小程序里,而是做成"家长小程序 + 老师/运营H5后台"的组合。有人可能会问,老师为什么不用企业微信?我们一开始也想过,但现实是机构老师流动性不小,企业微信授权和离职交接的流程比独立账号重,一个简单的H5反而更轻。这套vue和uniapp的代码其实是同一个仓库,通过打包平台区分入口,接口全部共用,开发增量很小。

1.2 核心数据模型:六张表把业务串起来

业务方讲需求的时候容易跳跃,语速快,信息像流水一样,但落到表上就特别清晰。少年编程这个项目的核心表我归纳为六张:课程表、班次表、选课表、订单表、作业表、提交表。

  • course(课程表):id、课程名称、分类(图形化/Python/C++)、封面图、价格、总课时、适用年龄、课程介绍。这里要注意,家长买的是"课程",不是"班次",这是一个认知差异。
  • class_schedule(班次表):id、course_id、周几上课、开始时间、结束时间、开课日期、结课日期、教室、最大人数、剩余名额。少儿编程的特点是同一门课会有多个班次,比如"Python入门"周六上午一班、周日下午一班。把班次单独拆出来,后面换班、插班、课消统计都会轻松很多。
  • enrollment(选课表):id、student_id、schedule_id、状态(正常/退课/结课)、报名时间、created_at。这是一张关联表和状态表,也是冲突检测的对象。
  • order(订单表):id、订单号、student_id、金额、支付状态、微信prepay_id、微信交易单号、支付时间、退款状态。订单和选课表是一对一关系,报名成功的前提是订单状态变成已支付。
  • homework(作业表):id、teacher_id、schedule_id、作业标题、作业内容、截止时间、作业类型(客观题/代码题/作品图)。作业挂在班次上,而不是挂在课程上,因为同一个课程不同班次的进度可能不一样。
  • submission(提交表):id、homework_id、student_id、提交内容、附件URL、得分、老师评语、批改状态、提交时间。

这六张表的第一版我刻意保持了简洁,没急着加领域模型、多态关联这些概念。教育机构业务日常变动快,表结构过度设计往往到后面只会成为改造的负担。只要字段能表达"一个学生在一个班次上、交了一次作业、产生了一笔订单"这三条主线,就够用了。

1.3 边界取舍:第一版不做这些,反而走得更快

软件项目最大的风险不是功能少,而是做了一堆用不上的功能,把核心路径拖慢。这个项目我明确砍掉了三个东西:

第一,砍掉课消统计报表。机构老板肯定想看"这个月消耗了多少课时、还有多少待消耗",但第一版我建议用Excel过渡,因为课消规则(试听课算不算、请假顺延怎么算)需要机构自己磨合,软件做早了,规则一变就是白做。

第二,砍掉复杂的排课算法。"智能推荐最优班级"听起来很酷,但真正推动家长选班的往往是时间、距离和老师口碑。第一版只需要把同一个时间段已满员的班置灰,把剩余名额展示清楚,就完成了80%的需求。

第三,砍掉多校区支持。如果机构只有一个总校,多校区完全是额外复杂度;等有二校区再动也来得及,前提是数据模型里校区字段一开始就留着,但不用为它专门做界面。

边界定清楚以后,整个开发周期就可以压到一个月左右。后面几章讲的都是第一版必须做扎实的事,尤其是选课报名和作业闭环这两条主线。

2. vue3 + uniapp组合式API的项目骨架:目录划分、登录态与路由拦截

技术选型看起来是第一步,其实在梳理业务的过程中就已经确定了。这个项目我选的是 vue3 组合式API 加 uniapp,后端用现成的 Node.js 或 Java 都行,重点是前端这套骨架。很多教程讲 uniapp 都是从"新建项目"开始,容易让人忽略真正决定项目舒服与否的,是目录结构和工程规范。

2.1 技术选型的三个理由:多端复用、组合式API、生态成熟度

先说多端复用。家长端跑在微信小程序上,老师端如果也想在手机上临时看一眼,App/H5 都要能跑。uniapp 一套代码编译到小程序、App、H5,这个价值在这个项目里被放得很大,因为除了家长微信小程序,老师端偶尔会在手机浏览器里处理紧急批改,机构公众号里也可能嵌 H5 页面。如果分别写两套,维护成本直接翻倍。

再说 vue3 组合式API。有一说一,选项式写法在小页面里很直观,但一旦涉及订单状态流转、选课表单校验、作业提交进度这种逻辑,同一个功能的代码会散落在 data、methods、watch 里,看代码要来回跳。组合式API最大的优势是可以按业务维度抽函数,比如把订单创建和支付状态封装成 useOrder(),把登录和 token 刷新封装成 useLogin(),页面里五行代码就完事。这也是 vue 面试里经常被问到的"选项式和组合式区别"的实际意义,真做一个教育业务项目,你会明显感受到组合式API对复杂状态的整理能力。

最后是生态。uni-ui 组件库覆盖表单、列表、弹出层这些常见场景,微信小程序端有大量现成插件,支付、分享、扫码的能力都是封装好的。遇到问题社区资料也比冷门框架多,对项目周期短的团队来说,趟坑成本是个很现实的考量。

2.2 初始化项目时容易被忽略的两件事

第一件,用 CLI 创建项目,而不是直接用 HBuilderX。不是说 HBuilderX 不好,它对单兵作战很友好,但团队协作时 CLI 工程可以用 git 管理、可以跑 CI、可以固定依赖版本,HBuilderX 的可视化界面在两人以上协作时就容易出幺蛾子。创建命令就是常规的npx degit dcloudio/uni-preset-vue#vite my-project,装完依赖就能跑。

第二件,manifest.json 的配置要提前填完整。微信小程序 appid 要真实填写,否则运行到微信开发者工具后无法调试真机;App 端要提前配置隐私政策弹窗文案,iOS 审核如果在启动 App 时没有弹隐私协议,直接就是审核不过。权限声明只声明真正用到的,比如相册、摄像头,不要偷懒全部勾上,会让审核变得更麻烦。

我用的目录结构长这样:

src/ ├── api/ # 接口请求层,按模块拆分 │ ├── course.js │ ├── order.js │ └── homework.js ├── composables/ # 组合式函数 │ ├── useLogin.js │ ├── useOrder.js │ └── useHomework.js ├── components/ # 通用组件 │ ├── CourseCard.vue │ └── HomeworkItem.vue ├── pages/ │ ├── course/index.vue │ ├── course/detail.vue │ ├── order/confirm.vue │ ├── order/result.vue │ └── homework/submit.vue ├── static/ ├── store/ # pinia 状态 └── utils/ └── request.js

这个结构的关键在于 composables 和 api 的边界:api 只负责 HTTP 请求的封装,composables 负责业务逻辑的组装。比如 useOrder() 里会调用 api/order.js 的接口,但页面不需要关心到底请求了哪个接口,它只关心"当前订单状态是什么、要不要跳转支付"。

2.3 登录态与会话保持:拦截器统一处理路由守卫

uniapp 的路由不是 vue-router,而是靠 pages.json 里的页面配置和 uni.navigateTo 这类API。所以不能按 vue-router 的习惯写全局前置守卫,得用 uni.addInterceptor 来做登录拦截。小程序端登录我用的还是标准流程:uni.login 取 code,传给后端,后端调微信接口换 openid 和 session_key,再把自定义 token 返回来,前端存到 storage 里。

拦截器写法:

import { useUserStore } from '@/store/user' const authInterceptor = { invoke(args) { const userStore = useUserStore() if (!userStore.token) { uni.navigateTo({ url: '/pages/login/index?redirect=' + encodeURIComponent(args.url) }) return false } return true } } // 在 main.js 中注册 uni.addInterceptor('navigateTo', authInterceptor) uni.addInterceptor('redirectTo', authInterceptor) uni.addInterceptor('switchTab', authInterceptor)

这里有三个细节需要注意。一是 switchTab 也必须拦截,因为 tabBar 页面无法用 navigateTo 跳转,跳登录页之前要先把当前路径记录下来,登录成功后再用 uni.switchTab 回跳。二是回调地址要 encodeURIComponent,否则路径上如果带 query 参数,还原的时候会解析错乱。三是 uniapp 中获取路由参数是通过onLoad(options)获取的,options 里所有的值默认是字符串,而且如果来源页面传参时没编码,遇到特殊字符(比如 url 里的 &)就会截断。所以我通常在传参时统一做一次 encodeURIComponent,接收时再 decodeURIComponent,这个习惯能帮你避开无数诡异bug。

3. 选课报名主链路:课程展示、冲突检测、名额锁定与微信支付

选课报名是整个系统最重要的业务闭环,没有之一。家长打开小程序看到课程列表,点进详情,选一个合适时间的班次,提交订单,微信支付,支付成功后才算完成报名。这一条链路里几乎每一个环节都有坑,我把核心思路和代码逻辑拆开说。

3.1 课程列表与详情:分页加载和试听视频

课程列表页是家长的第一印象,不要一屏加载所有数据。用 onReachBottom 触底加载下一页,一次十条,配合骨架屏显示。列表页的课程卡片上要把价格、剩余名额、适用年龄这些关键信息直接露出来,减少家长点进详情才知道价格的挫败感。

课程详情页的核心是视频试听。少儿编程的课程要不要买,家长很看重试听课的感受。视频播放这里用 uniapp 的 video 组件,播放 m3u8 地址是可以直接支持的,你只需要把后端给到的 m3u8 地址丢给 video 的 src 属性。但这里要注意一个微信小程序的特殊点:如果视频域名没有在小程序后台配置到 downloadFile 合法域名里,真机上会播放失败,开发工具里反而正常。另外在部分安卓机型上,小程序的 video 组件对 hls 流兼容一般,如果机构有转码条件,建议同时提供一个 mp4 地址作为降级,或者在域名配置里把点播服务的域名都加全。

还有个小经验:详情页不要上来就自动播放视频,会触发小程序端的音频/视频审核限制,首屏重点还是课程大纲、适龄范围和价格,试听视频放在课程介绍下方,让家长主动点。

3.2 选课核心约束:时间冲突检测与防超卖设计

家长选了一个班次后,服务端要做两件重要的事:检查这个孩子有没有报过同一时间段的课,以及这个班次还有没有名额。这两个逻辑必须在服务端做,不能依赖前端判断。

时间冲突检测的思路是:查这个学生当前所有状态为"正常"的选课记录,关联出对应班次的上课时间(周几、开始时间、结束时间),然后判断目标班次是否与其中任何一个时间段重叠。SQL片段大概是:

SELECT cs.id, cs.weekday, cs.start_time, cs.end_time FROM enrollment e JOIN class_schedule cs ON e.schedule_id = cs.id WHERE e.student_id = ? AND e.status = 'active'

拿到这批时间段后在代码里逐一判断目标班次的 weekday 和起止时间,这里不能只看日期,因为少儿编程班是每周固定一次课,跨多周持续,同一周几的时间段冲突才有意义。

名额防超卖是另一个高频bug点。家长同时有两个手机在选同一个班,如果只靠"先查剩余名额,再减一"的普通逻辑,并发场景下必然超卖。正确做法是在数据库层面做原子扣减:

UPDATE class_schedule SET remaining_quota = remaining_quota - 1 WHERE id = ? AND remaining_quota > 0

如果影响行数为 0,说明名额已经被抢完,直接返回"该班次已满员"。把这个 update 和创建订单放在同一个事务里,就能保证扣名额和创建订单的一致性。我见过不少项目是先创建订单再扣名额,结果用户支付超时订单取消,名额还扣着不释放,后面还得写定时任务补偿,完全没必要。

3.3 订单状态机与微信支付对接细节

订单状态我设计了四个:待支付、已支付、已取消、已退款。状态流转只有四条合法路径:

当前状态触发动作下一状态
待支付用户主动取消已取消
待支付支付超时(30分钟)已取消
待支付微信支付成功回调已支付
已支付教务后台退款已退款

拒绝了"已取消"再变回"已支付"这类非法流转。这个状态机表面简单,但能挡住非常多的脏数据。

前端发起支付的关键代码:

// 创建订单后拿到支付参数 const payment = await request({ url: '/api/order/pay', method: 'POST', data: { orderId: orderInfo.id } }) uni.requestPayment({ provider: 'wxpay', timeStamp: payment.timeStamp, nonceStr: payment.nonceStr, package: payment.package, signType: payment.signType, paySign: payment.paySign, success: () => { // 支付成功,但这里不要急着改订单状态 // 真正的状态变更以服务端回调为准 uni.redirectTo({ url: '/pages/order/result?id=' + orderInfo.id }) }, fail: (err) => { // 处理用户取消支付 / 支付失败 } })

很多人第一次对接微信支付时,会在前端拿到 success 之后就更新 UI 为"已支付",但前端 success 只能说明用户输密码的流程走完了,服务端回调才是确定钱到账的唯一依据。所以我的做法是:前端跳转到支付结果页,结果页先显示"支付确认中",然后靠轮询后端订单状态接口,拿到已支付再刷新界面。这样既不会因为回调延迟显示错误状态,也不会在前端伪造支付结果时造成安全问题。

3.4 支付合规:不想让支付功能被关停,这几件事必须做

这个章节值得单独拎出来说,因为小程序支付被限制的案例实在太多了。微信小程序的支付权限本质上是一个平台风控能力,不是像App那样随便申请的。项目开工之前就要确认主体资质:小程序账号的主体要和机构营业执照一致,教育类目需要提供对应的办学资质或备案材料,类目不要乱选。支付功能上线后也要注意,不能诱导用户虚假交易、刷单,不能出现"支付后返现"这类诱导分享行为。一旦小程序被投诉或触发风控,后台就会提示"由于小程序违规,支付功能暂时无法使用",这种限制不是代码层面的问题,要联系微信客服处理,处理周期通常不短。

所以我的建议是:支付相关的代码尽量按微信官方要求写,前端在 requestPayment 之前检查一下 customer 端的配置,后端回调接口一定要验签,该做的都会做,不该做的别碰。合规经营对教育机构尤为重要,因为家长都很看重机构的稳定性和信誉。项目里如果涉及退款流程,也要走正规退款接口,不要私下在后台改状态,退款不合规也会被风控盯上。

4. 作业模块三端数据流转:布置、提交、批改、通知

选课报名的闭环只是第一步,真正把家长和机构黏在一起的,其实是作业系统。少儿编程课程如果上完课就结束,家长很难感知到学习效果,只有"老师布置了作业、孩子交了作业、老师给了评语"这一连串反馈,才能让家长觉得钱花得值。同时,作业也是老师掌握进度的关键手段。

4.1 作业的数据结构:一个作业如何关联班次和学生

作业挂在班次(class_schedule)上,而不是挂在单个学生上。因为同一门课不同班次的进度可能不同,周二班学到变量,周六班还在学循环,老师是按班布置作业的。作业表里记录是哪个老师在哪个班次布置的、内容是什么、截止时间是什么时候。作业的类型会影响前端交互,我在数据结构里用 type 字段区分:

  • objective:选择题/判断题,学生端直接点选项,老师批改时看正确率即可
  • code:代码题,学生端粘贴代码,老师端以文本形式查看
  • image:作品截图/成果图,学生端上传图片,老师端大图查看

这个分类决定了不同页面渲染什么组件,但不影响核心状态流转。作业之后是提交表,一个学生一次作业对应一条提交记录。为什么单独拆出来?因为不是所有学生都会在第一次就提交,老师还要打回让学生修改重交,提交表的多版本设计我放到了4.2节讲。

4.2 学生端提交的真实交互:图片上传、代码粘贴与草稿机制

学生端提交作业,最轻的入口是"我拍个代码截图"或者"我把话画出来"。少儿编程的孩子年龄普遍在6到14岁之间,不能要求他们打字打太多,所以提交页面要尽量降低输入成本。图片上传用 uni.chooseImage 选图或拍照,一次最多9张,然后逐个调 uni.uploadFile 上传到对象存储,全部成功后把 URL 数组提交到服务端。

代码上传部分,一般思路是直接给一个大 textarea 让孩子粘贴代码。但实践经验是,对于6-10岁的孩子,粘贴代码往往会出现多余空格或标签,家长们也不一定懂怎么格式化。所以代码题我建议做成"上传 .py 文件 + 预览"的形式,或者做成"粘贴 + 自动去除首尾空白"的格式。至少让家长在电脑上把代码文件发到微信里,再从文件里粘贴到 textarea,方便很多。

图片和代码一起提交的时候,要处理并发和进度。我一般给每个文件建一个上传任务,用 Promise.all 等全部完成:

const uploadTasks = tempFilePaths.map((filePath, index) => { return new Promise((resolve, reject) => { uni.uploadFile({ url: 'https://api.example.com/upload', filePath: filePath, name: `file${index}`, success: (res) => { try { const data = JSON.parse(res.data) resolve(data.url) } catch (e) { reject(e) } }, fail: reject }) }) }) const urls = await Promise.all(uploadTasks)

这里有一个很实际的体验问题:孩子写作业经常写到一半被打断,家长关掉小程序就白填了。所以我会在提交页面定时把表单内容和已上传的图片列表存到本地 storage,下次打开页面时自动填充。这个成本很低,但对家长体验的改善非常明显。

4.3 批改与通知闭环:订阅消息的正确用法

老师批改作业时,在 H5 后台打开提交记录,看到学生的提交内容,打一个分数,写两句评语,点保存。批改完成以后,学生的提交状态变成"已批改",这时候可以给家长发一条微信订阅消息,告诉他"孩子的作业已批改,快来看看成绩吧"。这里涉及到订阅消息的长期可复用问题。

微信小程序的订阅消息一种是长期订阅,一种是一次性订阅。教育类目很容易申请一次性订阅授权,也就是家长在小程序里主动点一次"允许接收作业提醒",系统才能发一条消息。这个能力用起来有个前提:必须在用户点击了某个按钮(比如提交作业)时,立即调 uni.requestSubscribeMessage 申请授权,而且用户点了"总是保持以上选择"以后,后面的授权才能自动通过。如果用户在设置里关了通知,那消息就没法触达了。

所以我在提交成功页面放了一个"开启作业完成提醒"的按钮,点击时申请订阅消息授权,这样能保证授权来源自然,也能提高后续消息的到达率。还有一点,不要设计得过于激进,提交一次就弹一次授权框,微信会把这种行为判为骚扰,轻则模板消息失效,重则小程序被限制服务。

5. 上线前绕不开的坑:从支付合规到打包审核的排查实录

这个章节是踩坑实录,都是实际项目里遇到并且解决过的问题,按主题列出来,每个都附排查思路。要知道这类教育管理小程序,用户量可能不大,但出问题的容忍度极低,家长点两下没反应,转头就在机构微信群里吐槽。

5.1 微信支付被限制该怎么排查

支付功能突然用不了,先不要慌,按顺序排查。第一步登录微信公众平台,查看站内信或违规记录,确认是不是收到了限制通知,如果是平台主动限制,后台一般有原因和申诉入口。第二步检查小程序类目与支付场景是否匹配,教育类目如果选成了普通百货类,风控容易误判。第三步排查交易数据:近期有没有大额、高频、来源集中在同一账号的交易,有没有被用户投诉退款的记录,这些都会触发风控。最后一步才是看代码,确认 requestPayment 调用的参数签名是否正常。前几步没走完就怀疑代码,容易被带偏。

5.2 输入框被软键盘顶飞、遮挡查询内容

这个问题在微信小程序里太典型了。家长在"查询作业"页面想搜索某个关键词,手机软键盘一弹出来,底部的内容被遮住,严重的会把 fixed 定位的按钮顶上去。常见网上说法是设置 input 的 adjust-position 属性为 true,实测下来部分安卓机型上完全无效,原因是页面里有 fixed 定位元素或者 web-view 混排时,编译后的原生组件层级和普通标签不一样。

我的解决办法是:查询页面里不要用 fixed 底栏,而是用普通流式布局把按钮放在输入框下面;输入框加 cursor-spacing="10" 留出光标距底部的安全距离;如果页面有多个输入项,用 scroll-into-view 在聚焦时把当前输入项滚动到可视区域。小程序端如果基础库版本够高,还可以用 onKeyboardHeightChange 监听键盘高度,手动调整列表的 padding-bottom,这个方案最稳但工作量稍大,我一般留到客户强制要求时再做。

5.3 分享功能与全局 onShareAppMessage 的冲突

微信小程序的分享,以前需要在每个页面写 onShareAppMessage,后来可以全局配置菜单。但实际开发时有个很隐蔽的坑:App.vue 里如果给 onShareAppMessage 做了全局封装,某些页面的自定义分享参数会失效,显示出来的分享卡片标题、图片不是自己期望的。原因很简单:微信小程序的 onShareAppMessage 只认页面组件里的定义,App.vue 里的定义在某些基础库版本下会干扰页面级的声明。

我排查过一次,最后把全局分享逻辑改成 mixin 方式:定义一个 sharedMixin,统一处理默认的分享标题和图片,页面引入这个 mixin 后再单独重写 onShareAppMessage 合并自定义参数。这样既保证了默认分享可用,又不互相覆盖。如果只想让指定页面能分享,就在 page 级别的 onShareAppMessage 里返回一个空对象,微信默认会带上当前页面截图作为分享图,效果也还行。

5.4 扫码结果是一串数字:先分清码制和业务场景

项目里有一个"扫码签到"功能,老师扫学生卡上的二维码快速点名。有一回测试返回的 result 一直是一串纯数字,而不是预期中的学生ID,逻辑怎么都对不上。后来排查发现,物理学生卡上印的是 Code128 条形码,不是 QR 二维码,uni.scanCode 扫到的 result 就是条形码本身的内容,而条形码内容恰好是一串学号。问题不在代码,而在码制选择上。

这个经验提示:先确认机构已有的卡是什么码制,再决定扫什么码。如果是 qrCode,前端可以直接用;如果是条形码,小程序也支持但需要注意拍摄距离和对焦。还有一种是"扫出来是一串数字"但是二维码,这时候就要怀疑码的内容是不是被其他系统编码了,比如自定义加密或 ID 做了映射,需要去后端查一下二维码生成逻辑。

5.5 安卓应用市场上架:隐私政策、软著与不同意退出的实现

如果这个小程序以后要打包成安卓 App 上架应用市场,事情会变多,但核心是这三个:软件著作权、隐私政策和备案。每个市场的软著要求不太一样,但基本上都要提前准备,别等到要上架了才去申请,软著下证周期可以按一到两个月预估。隐私政策弹窗是所有应用市场的硬性要求,用户首次打开 App 时必须弹窗,用户同意之前不能初始化任何涉及个人信息的 SDK。

这里对应一个热搜词场景:iOS 用户不同意隐私政策及用户协议时怎么退出 App。代码逻辑是,弹窗点击"不同意"时,调用 plus.runtime.quit() 退出应用。注意这里有个审核细节:iOS 审核其实不建议强制退出,更推荐做法是"仅退出到设置页让用户重新选择",但很多机构为了简单直接,仍然采用二次确认后退出应用的做法。如果你要做 iOS 包,我建议按平台规范来,避免审核被拒。

uni.showModal({ title: '提示', content: '需要同意隐私政策后才能继续使用', showCancel: false, confirmText: '同意', success: (res) => { if (res.confirm) { // 用户同意后继续初始化 } } }) // 如果用户点击不同意,在按钮回调里调用退出

还有一个现象很常见:uniapp 项目在 HBuilderX 里运行到微信开发者工具,点了运行半天没反应,控制台也没报错。这时候十有八九是微信开发者工具的服务端口没开。打开微信开发者工具的"设置-安全设置",把"服务端口"打开,再重新运行就能连上了。这个坑很多人能跳过去,但也真的能卡住半天。

5.6 前端安全底线:密钥别进代码,回调必须验签

这类机构类系统最容易忽略安全,因为用户量不大就觉得无所谓。但涉及支付和家长信息,安全性一点不能妥协。第一,小程序的 AppSecret 绝对不能出现在前端代码里,小程序代码可以被反编译,明文密钥等于裸奔。后端通过 code 换 session_key 时才需要用到 AppSecret,这一步强制放在服务端。第二,所有涉及角色权限的接口,前端传的"我是老师"不可信,权限判断必须在服务端根据登录态完成。第三,支付回调接口必须做微信支付 v3 的验签处理,用微信支付平台证书验证回调请求的签名,而不是简单地校验一下回调里传来的"成功"字段。如果没有验签,攻击者直接伪造回调通知就能把订单改成已支付,而这个漏洞很容易被忽略。

6. 性能与体验优化:分包、列表、媒体与弱网

教育业务小程序对性能的容忍度其实很低,家长在小程序里打不开课程视频或者列表加载转圈超过三秒,马上就流失了。这个项目规模不大,但性能优化的思路还是值得按完整链路走一遍。

6.1 主包瘦身与分包加载

微信小程序有主包和分包的大小限制,主包要控制体积。我的做法是:把所有老师端页面、订单结果页这类低频页面拆到分包里,主包只保留 tabBar 页面和公共组件。在 pages.json 里配置 subPackages:

{ "subPackages": [ { "root": "pages/teacher", "pages": [ "pages/teacher/homework-list", "pages/teacher/homework-detail" ] } ] }

这样主包体积能明显下降,冷启动速度会快很多。对于少儿编程机构这种用户分布比较集中的场景,首屏加载够不够快,直接决定了家长愿不愿意进来逛。

6.2 列表渲染性能与状态管理注意

课程列表页如果一次渲染很多卡片,在低端安卓机上会有明显卡顿。列表项必须加 :key,并且不要在模板里写复杂的方法调用,比如在 v-for 里写{{ formatPrice(item.price) }},每次渲染都会全量执行一遍,数据多了就拖帧。这种计算放到 computed 或 filter 里面,让数据提前处理好,模板只做展示。

另外,不是所有数据都需要进全局 store。有些页面之间的共享状态,比如"当前选择的班次ID",用页面参数传就完了,没必要放到 pinia 里。store 里的数据一旦过多,复杂度和调试成本都会上升,这在教育项目的快速迭代期是负担。我当时只把用户信息和未读作业数放进了 store,其他都是按需请求。

6.3 图片视频资源与弱网兜底

课程封面、老师头像、作业提交图片,这些资源都要走 CDN,并且后端在做图片上传时生成缩略图,列表页加载缩略图,详情页再加载原图。视频资源不要直接放原片,用点播服务的转码能力输出多个清晰度,家长在2G/3G环境下自动选低清晰度,在WiFi下选高清。

弱网兜底主要体现在提交作业这个场景。家长在停车场或者电梯里提交作业,请求很容易失败,要给提交按钮加一个"提交中"的 loading 状态,并且失败时保留表单内容,允许用户重试,而不是把内容清空重填。这个细节其实和草稿机制是配套的,做好了,家长的体验会非常稳定,不会因为有一次上传失败就再也不用了。

其次,我强烈建议在请求封装层做统一的错误提示,让后端接口返回统一的错误码,前端统一弹 toast。对接几十个接口以后,你一定会感谢当初做了这个统一封装。到了后期,如果接口跨服务了,加一个请求日志中间件,也能快速排查问题。

(全文完)

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询