Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

第 49 章 微信小程序:家访路线规划

预览里跑通的是页面。登录、云函数、地图接口和审核,要放到真机和真实环境里一项一项再检查一遍。

一位当老师的朋友抱怨,排一次家访路线要花半天。我在 Trae 里输入一句“帮我做一个家访路线规划的微信小程序”,二十多分钟以后,预览里就能点了。从“能点”到“能交给老师用”,后面又修改了两轮。

微信小程序比浏览器插件多出四件事:用户要登录,数据要存在服务端,要调用别人的接口,上线要通过平台审核。

这个产品是什么

家访路线规划是给中小学老师用的排程小程序。老师每学期要入户家访,一个班四十个学生,住址分散在各个片区,家长能接待的时间各不相同,老师自己能外出的时间也有限。以前的做法是在微信群里发接龙,把家长回的地址整理进 Excel,打开地图一个一个查找位置,再凭感觉排顺序,路上还要翻聊天记录找电话。仓库里的需求文档写的是“手工在 Excel 里排路线往往要花掉半天到一天”。

小程序把这件事变成四步:老师发问卷,家长填信息,老师一键排路线,实地家访时打卡。

现在的版本有 11 个页面、20 个云函数、4 个数据库集合。老师端有四个底部标签:首页、问卷、行程、我的。家长端只有一个填写页,从老师转发的卡片或者小程序码进入。

谁在用:这个小程序是给开头那位当老师的朋友做的,做完时的打算是部署上线,开学给她用。

需求

第一版做完三天后,我让 Agent 整理了一份需求文档,里面有角色表和三个使用场景。下面五条用户故事是按这份文档和现在的功能补写的。

  1. P1 作为班主任,我想要几步建好一份家访登记问卷并发到家长群,以便不用在群里接龙、不用自己整理表格。做完的标志:填标题、选字段、设置时段三步之后,能转发卡片,也能拿到一张小程序码。
  2. P1 作为家长,我想要点开老师发的卡片就能填,不注册、不安装,以便两分钟填完。做完的标志:填学生姓名、家长姓名、电话、住址、能接待的时段,提交成功;同一个手机号再提交一次会被提示已经提交过。
  3. P1 作为班主任,我想要选好出发地点、天数和自己有空的时段之后一键排出每天的路线,以便不用自己对着地图去排。做完的标志:每一天去哪几家、几点到、路上开多久都列出来;排不进去的学生单独列出并说明原因。
  4. P1 作为正在家访路上的老师,我想要在行程页直接导航、打电话、标记“已访”或“未接通”,以便不用切到别的应用。做完的标志:打卡状态退出再进来还在;未接通的学生下次重新规划时还会被排进去。
  5. P2 作为已经用别的问卷工具收集过信息的老师,我想要上传一份 Excel 直接生成名单,以便不用让家长再填一遍。做完的标志:按模板准备的表格上传后,学生名单和可家访日期都进了系统。

第五条是第三轮才加的,开发前写了一份设计文档。

产品设计

功能结构按两类用户分:

flowchart TB
    A[家访路线规划] --> T[老师端]
    A --> P[家长端]
    T --> L[登录:微信一键登录 / 勾选协议]
    T --> H[首页:待规划 / 待走访 / 已完成 / 三个入口]
    T --> S[问卷]
    S --> S1[新建问卷:标题 → 字段和时段 → 发布]
    S --> S2[导入问卷:上传 Excel]
    S --> S3[问卷详情:学生名单 / 转发给家长 / 小程序码 / 生成行程规划]
    S3 --> S4[学生详情:修改信息 / 优先级 / 指定时段 / 家访记录]
    T --> R[行程]
    R --> R1[行程详情:按天切换 / 地图路线 / 时间轴]
    R1 --> R2[站点卡片:导航 / 打电话 / 复制地址 / 打卡]
    T --> M[我的:统计 / 使用帮助 / 问题反馈 / 用户协议 / 隐私政策 / 退出登录]
    P --> F[填写页:姓名 / 电话 / 住址粘贴识别或地图选点 / 可接待时段]

一份问卷从创建到结束会经过四个状态,需求文档里把它写成了一张状态表:

flowchart LR
    C[收集中:家长填写] -->|老师生成行程规划| D[已规划:等待出发]
    D -->|老师开始家访| E[进行中:实地打卡]
    E -->|全部完成| G[已完成:行程归档]
    E -->|有未接通的学生| D

原型:第一版的页面结构、配色和组件规范都是 Agent 按 Trae 里的小程序技能自己定的,主色是蓝色,卡片式布局。这种做法的后果在第三轮看得很清楚:一天里有十几次提交是在统一界面风格,问卷页的分类、首页的入口、底部标签的图标都重做了。和第 10 章讲的先画原型相比,这些改动都发生在代码里。

技术组合

部分选了什么为什么
前端Taro 4、React 18、TypeScriptTaro 一套代码能编译成微信小程序,也能编译成网页和其他平台的小程序;React 用的人多,Agent 写得熟
界面组件NutUI React Taro 3.0第一版全部手写组件,第二轮换成了现成的组件库
状态管理Zustand只用来保存登录状态
后端微信云开发:云函数、云数据库、云存储不用买服务器,不用自己做登录,直接和微信账号连在一起
第三方接口高德地图 Web 服务 API把地址换成坐标,算两点之间的驾车时间
微信接口生成小程序码的 wxacode.getUnlimited每份问卷一张码

这套组合是 Agent 给出方案、我确认的。它给了两条理由:没选原生小程序开发,是因为以后想出别的平台版本时要重写;没选自己搭后端,是因为要自己管服务器。

界面组件库中途换过一次。第一版的开发过程文档写的是“不引入第三方 UI 库,所有组件手写”,第二轮的提交把全部页面迁移到了 NutUI,删掉了自己写的空状态和统计卡片组件。手写组件在预览里看着没问题,到真机上出现了标签文字看不见、头部多出空白、底部被遮住这些问题。现成的组件库在各种机型上被很多人用过,这些问题别人已经替你遇到过了。

这个产品没有接任何大模型。需求文档专门有一节写这件事,“一键排路线”用的是地图接口加一套排序规则,同样的输入每次排出同样的结果,老师能看懂为什么这样排,调用费用也比大模型低得多。

开发过程

仓库的 git 记录一共四十多次提交(包括分支合并和自动快照),集中在四天:第一版提交那天两次,十九天后两次,再过一周的一天里近四十次,第二天凌晨一次。按内容分成三轮。

第一轮:一句话生成第一版

这一轮交给 Agent 的提示词只有一句,开发过程文档里记了原话:

帮我做一个家访路线规划的微信小程序

我在 Trae 里输入这句话,Trae 识别出“小程序”后启用了它的小程序生成技能 generate-mini-app。这个技能自带一份分七个阶段的流程:分析需求并写出功能和页面清单;定视觉风格;选依赖库;列出全部文件;写全局配置;一个页面一个页面地写,写页面的同时把对应的云函数一起写掉;最后自己检查一遍文件是否齐全、引用是否存在。七个阶段之间没有停下来问我。

开发过程文档里的时间表取自预览服务的日志:晚上预览服务启动,不到半分钟第一次编译成功,之后的二十多分钟里,文件变更触发了两百多次重新编译。产出是 9 个页面、16 个云函数、4 个数据库集合,大约五千行代码。排路线的云函数有五百多行,是 Agent 自己设计的。

这一轮有一个事后才看清的前提:我全程看的是 Trae 的云端预览,没有打开微信开发者工具。预览把小程序编译成网页来显示,网页环境里没有微信登录,也调用不了云函数,所以 Agent 给每个云函数都准备了一份假数据(代码里叫 mock),预览里显示的名单、路线、统计全是假数据。这一轮验证过的是页面和交互;登录、云函数、数据库、地图接口,一样都没有在真实环境里运行过。

开发过程文档的最后一节还记了一句提示词,是做完之后要上线时说的:

帮我部署

第二轮:十九天后,搬到真机上

这一轮只有两次提交,内容很多。

  • 全部页面迁移到 NutUI 组件库,修复真机上的显示问题:标签文字看不见,问卷页头部空白,行程页底部被遮住。
  • 修复一处运行时崩溃:创建问卷时步骤条组件取不到,页面直接打不开。
  • 给四个标签页加上登录检查,没登录就跳到登录页;登录页改成一键登录加勾选协议。
  • 删掉全部假数据。不在微信环境里时,调用云函数直接报错。
  • 把 Taro 升级了一个小版本,修复真机上正式构建会报错的问题。
  • 把小程序的上传密钥从版本管理里拿出来,加进 .gitignore。

最后一条是反例。第一次提交时,上传密钥跟着别的文件一起被提交进了仓库,十九天后才从版本管理里拿出来。第 14 章讲过密钥只放在本机,这里是没照着做的一次。进过仓库的密钥,从版本管理里拿出来以后还留在历史里,要到平台上作废后重新生成。

描述真机上的问题可以这样写:

打卡后刷新页面状态丢失了

一句话里有操作、有现象,没有猜原因。

第三轮:再过一周,上线前的补齐

这一轮集中在一天,有近四十次提交,提交说明里出现了 Claude 的署名,仓库里也多了一份 CLAUDE.md,写着“这个仓库同时被多个 AI 会话和 Trae 操作”。从这一轮开始,这个项目用上了多个 Agent 会话并行。

  • Excel 导入。 上午先写设计文档,下午提交实现。设计文档写的是“自动识别列名加手动补映射”,实现时改成了固定模板:六列,时长一列可以不填,表头对不上就提示使用系统提供的模板。固定模板少了一整个映射步骤,老师照着模板准备表格就行。
  • 权限校验。 提交说明是“补齐云函数权限校验与数据归属校验”,一共五条,下一节展开讲。
  • 重新规划不丢进度。 之前重新排一次路线,已经打过的卡全没了。改成新规划继承旧规划的打卡状态,旧规划归档。
  • 家访记录和补访。 之前只记录去没去过,聊了什么没有地方写。加了家访记录;重新规划时排除已访的学生,未接通的自动进入下一次路线。
  • 上线阻断项。 “我的”页面加上用户协议和隐私政策入口,协议里补了未成年人信息条款,几个页面加载失败时给出提示。
  • 界面一致性。 有十几次提交在修改界面:问卷页的分类、首页的入口、底部标签的图标都重做了。
  • 首页支持转发。 这是过了零点之后的最后一次提交。

Excel 导入这一项交给 Agent 的是那份设计文档。

这一天还出了一次事故,CLAUDE.md 里记着:一个会话为了把工作区清理干净,执行了暂存、重置、清空暂存三条 git 命令,三十多个文件、一千多行还没提交的改动一次全没了,其中有当时正在写的另一个模块,最后靠 git 的检查命令把内容找了回来。事故之后仓库里加了一个拦截脚本和这份规范,第 14 章引用过规范里的禁令表。

这个方向特有的问题

用户登录

小程序的登录比网站省事。用户已经在微信里了,小程序可以直接拿到一串叫 openid 的标识:同一个微信用户在同一个小程序里,这串标识固定不变。拿到它不需要用户输入任何东西,也不弹授权窗口。家访路线规划的登录云函数核心只有几行:

const wxContext = cloud.getWXContext()
const openid = wxContext.OPENID
const userRes = await db.collection('users').where({ _openid: openid }).get()
// 查不到就新建一条用户记录,昵称默认是“老师”

没有密码,没有验证码,没有“忘记密码”。

要自己想清楚的有三件事。

谁要登录,谁不用。 老师要登录,因为问卷、名单、行程都要记在老师名下。家长不用,家长只填一次表。仓库里的登录检查只加在老师端的四个标签页上,代码注释专门写了一句:家长填写问卷这类分享页面不要用这个检查。

登录页要有协议。 第二轮把登录页改成了“微信一键登录”按钮加一个勾选框,不勾选用户协议和隐私政策就不能进。第三轮又在“我的”页面加了两份协议的入口,提交说明写的原因是“微信审核要求登录后可随时查阅”。

头像、昵称、手机号拿不到是正常的。 微信对这几项信息的获取方式收得很紧。家访路线规划的做法是都不拿:老师的昵称默认叫“老师”;家长的电话由家长自己填,用规则检查格式。

云函数和云数据库

云函数是放在微信云上的一小段后端代码,一个云函数一个文件夹,里面一个 index.js 加一个 package.json。云数据库是按集合存放的文档型数据库,家访路线规划有四个集合:users 存放老师,surveys 存放问卷,students 存放学生和家长信息,plans 存放排好的行程。前端调用云函数只有一种写法,仓库里把它封装成了一个函数,20 个云函数都通过这个函数调用。

这个方向最容易出问题的地方是权限。云函数里能拿到调用者的 openid,但“这个人能不能动这条数据”要每个云函数自己判断。需求文档是第一版做完后让 Agent 写的,里面写着工程质量一项有“云函数 _openid 归属校验防越权”。第三轮把 20 个云函数重新检查了一遍,提交说明列出了五处没做到的。

  • 解析 Excel 的云函数没有检查文件是谁上传的,能解析别人的文件。
  • 取学生名单的云函数,问卷编号传空也能查询。
  • 取问卷详情的云函数,对家长也返回了完整内容,里面有老师填的出发地点和坐标。
  • 添加学生、取行程、提交问卷三个云函数缺少归属和状态检查。
  • 生成小程序码的云函数没有声明要用的微信接口权限。

第三条最值得记住。问卷详情这个接口家长端也要用,因为填写页要显示问卷标题和字段。同一个接口两类人用,修改后的做法是列一张家长能看到的字段表,问卷的主人拿到全部,其他人只拿到这张表里的字段。Agent 写的文档说“已经做了校验”,和每个接口真的做了校验,是两件事,要一个一个云函数看过才算。

云函数还有四个容易忽略的地方。

  • 云函数要单独部署。 修改了云函数的代码,要上传到云端才生效。仓库里有一个批量部署脚本,但脚本里的云函数名单还是最早一版的名字,和现在的 20 个对不上。
  • 云函数用不了前端的代码。 解析 Excel 的云函数开头有一行注释:云函数不能引用 src 里的文件,需要原样复制。时段的定义因此在前端和云函数里各有一份,修改一处就要记得修改另一处。
  • 超时时间要自己调整。 排路线的云函数要连续调用很多次地图接口,仓库里给它单独配置了 60 秒的超时。
  • 数据库集合的读写权限在云开发控制台里设置,不在代码里。

调用第三方地图接口

排路线要两样数据:每个地址的坐标,每两个地点之间开车要多久。这两样都来自高德地图的 Web 服务接口,一个叫地理编码,一个叫距离测量。

密钥放在哪里是第一件事。高德的密钥通过云函数的环境变量读取,代码里只有一个变量名 AMAP_KEY,值在云开发控制台里配置。第一版并没有这样做:Agent 把密钥直接写在了云函数的代码里,第二轮才改成从环境变量读取,写进去的那个值至今还留在 git 历史里。调用也全部发生在云函数里,小程序前端不直接访问高德。这样做有两个好处:密钥不会随小程序的代码包发给每一个用户;小程序后台有一张“服务器域名”名单,前端只能访问名单里的地址,从云函数发出的请求不受这张名单限制。

第二件事是别人的接口会限速、会出错。排路线的云函数里为此做了几层处理,开发过程文档都记了。

  • 地址一个一个查询,每次间隔 150 毫秒。
  • 算距离时每批最多 8 个起点,比接口的上限少两个。
  • 接口返回“请求太快”时等一会儿再试,等待时间每次加倍,最多试三次。
  • 没有配置密钥,或者接口一直不通时,用两点坐标按球面距离估算,再乘 1.3 当作道路距离。
  • 查不到坐标的地址不会让整次规划失败,这个学生会进入“未纳入规划”名单,页面上提示老师手动处理。

第三件事是小程序自己的位置能力。家长在地图上选住址、老师用自己的位置当出发点,都要用微信的位置接口,这些接口要在配置文件里声明用途:

permission: {
  'scope.userLocation': {
    desc: '用于家长在地图上选择家庭住址'
  }
},
requiredPrivateInfos: ['chooseLocation', 'getLocation']

desc 里这句话会显示在授权弹窗上,审核也会看它和功能是否对得上。

位置还带来过一个真实的问题。家长先在地图上选了点,又手动把地址文字改成了另一个地方,提交时带上去的还是之前选的坐标,排出来的路线就去了错的地方。第三轮的一次提交修复了它:“家长改地址后不再带旧坐标提交”。地址和坐标是两份数据,只要有一份能单独修改,就要想好另一份怎么办。

小程序码和转发

家长进入填写页有两个入口。

转发卡片。 老师在问卷详情页点“转发给家长”,微信群里出现一张卡片,卡片的标题是问卷标题加“家访信息登记”,点开直接到填写页,问卷编号跟在页面地址后面。转发能力要每个页面自己声明,没声明的页面右上角的转发是灰的。首页的转发是最后一次提交才加上的。

小程序码。 发布问卷后生成一张带参数的码,家长扫码进入同一个填写页。生成码要调用微信的接口,家访路线规划选的是不限数量的那一个,问卷编号放在一个叫 scene 的参数里。这个接口有三处要注意。

  • scene 能放的内容很短,放一个编号可以,放一整串参数不行。
  • 填写页要同时支持两种进入方式,从卡片进来读地址里的问卷编号,扫码进来读 scene,仓库里的填写页开头就在做这件事。
  • 云函数调用微信接口要先声明权限,在云函数文件夹里的 config.json 写上接口名。第一版漏了这一项,第三轮补上。

生成的码存在云存储里。第三轮把云存储的权限收紧成“只有创建者能读写”之后,码是云函数上传的,老师的手机反而读不到,于是改成由云函数换一个临时链接再返回。权限一收紧,原来能用的地方会出问题,修改完权限要把相关功能重新点一遍。

还有一条规则:小程序没有发布之前,扫码能不能打开指定页面受限制。

家长端是整个产品里唯一对陌生人开放的入口,所以填写页背后的云函数做了三道检查:问卷不存在就拒绝;问卷已经停止收集就提示“本次登记已截止,如需补填请联系老师”;同一个手机号在同一份问卷里只能提交一次。

主体认证、类目和审核

这一节写的是这个方向的通用流程,具体规则和费用变化很快,全部要以微信公众平台当时的说明为准。

  • 注册和主体。 注册小程序时要选主体类型,个人和企业能用的功能、能选的类目不一样。
  • 认证和备案。 小程序要完成认证和备案才能上架。
  • 服务类目。 提交审核前要选服务类目,类目要和实际功能一致,一部分类目需要资质。
  • 隐私保护指引。 小程序用到位置、电话、文件这些能力,要在后台填写隐私保护指引,写清收集什么、用来做什么。家访路线规划收集的是学生姓名、家长电话和家庭住址,学生是未成年人,协议里专门有一条未成年人信息条款。
  • 提交审核。 上传代码后先生成体验版,自己和几个人试用,没问题再提交审核,审核通过后发布。

检查和上线

第三轮有一次提交的说明里出现了“清理上线阻断项”,把它列的几项和上一节的内容合在一起,就是小程序提交审核前的检查单。

  • 假数据全部删掉,每个页面的数据都来自云函数。
  • 在真机上把四步流程完整走一遍:建问卷,用另一个微信号填写,排路线,打卡。只在预览或者模拟器里看过不算。
  • 每个云函数都检查过调用者是不是数据的主人;对家长开放的接口只返回填写需要的字段。
  • 修改过的云函数都已经部署到云端,云端的环境变量里配置了地图密钥。
  • 页面打不开、加载失败、参数缺失时有提示,不会一直停在加载中。
  • 登录页有协议勾选,登录后也能找到用户协议和隐私政策。
  • 配置文件里声明的位置权限和用途说明与实际功能一致。
  • 仓库里没有上传密钥和其他凭据。

发布渠道、审核、签名三项里,小程序的渠道只有微信一个;审核是微信的版本审核,每次发新版本都要审核一遍;小程序没有签名这一步,但用命令行上传代码时需要一份上传密钥,它的作用相当于这个小程序的发布权限,要像密码一样保管。

花了多少,怎样收费

时间。 第一版从输入那句话到预览里能点,按日志是二十多分钟;第二天上午提交完整功能。之后隔了十九天做第二轮,一天两次提交;再隔一周做第三轮,一天里近四十次提交,过了零点还有一次。

费用。 我当时的说法是“0 元启动成本”,理由是云开发的免费额度够个人项目起步。这个方向的费用照第 47 章的三类来看:一次性的和每年的,是认证和备案相关的费用;云开发的套餐按月付;按用量的,是高德地图接口的调用次数,需求文档写的是“高德按次计费”,个人开发者有一定的免费调用量。一个班四十个学生排一次路线,要查询四十多次坐标,再分批算两两之间的车程。

怎样收费。 家访路线规划现在没有收费。下面是小程序这个方向常见的几种收费方式。

  • 免费,当作个人作品或者别的产品的入口。
  • 小程序内付费:会员、按次、解锁高级功能。需要能开通支付的主体。
  • 广告:用户量达到平台要求后可以开通广告位。
  • 向机构收费:按学校或者按年级收年费,小程序本身对老师免费。

这个产品如果要收费,我认为第四种更合适。老师是为工作任务用它,自己掏钱的意愿低,学校为老师省下的时间付费更说得通。这个判断还没有经过任何一所学校验证。


本案例在模板仓库的位置:按实践篇的约定放在 cases/<方向>/ 下,docs/ 放用户故事、功能结构图、原型、计划和验收清单,prompts/ 放按轮次整理的提示词和会话记录,src/ 放完整源码,README.md 写明平台规则的核实日期。