今天做了什么
今天给 AR 绘图桌小程序首页加分享——转发给好友、分享到朋友圈。这套东西我做过好几回,本以为两行 hook 就能搞定,结果在真机上一连踩出四个坑:从"菜单点了没反应",到"当前页面不可分享",再到"朋友圈打开线稿库是空的"。整条排查路径比功能本身更能说明工程方法,我完整记下来。
AR 绘图桌是一个 Taro 4.2.1 + Vue3 + Skyline 渲染器的微信小程序,首页是一个"把手机架高、照着屏幕描线稿"的引导页。分享需求本身不复杂:转发好友带 from=share、分享朋友圈带 query。但每一个看似简单的点,后面都藏着平台约束。
坑一:hook 注册 vs 选项式回调
第一处写法就错了。我在 <script setup> 里顺手写了 onShareAppMessage,但 Taro 不会收集这种选项式回调——组合式 API 下必须用 useShareAppMessage / useShareTimeline 这两个 hook 注册:
import { useShareAppMessage, useShareTimeline } from '@tarojs/taro'
useShareAppMessage(() => ({ title: SHARE_TITLE, imageUrl: SHARE_IMG_5X4, path: '/pages/index/index?from=share' }))
useShareTimeline(() => ({ title: SHARE_TITLE, imageUrl: SHARE_IMG_1X1, query: 'from=timeline' }))
注意 useShareTimeline 的 query 不带 ? 前缀、也不是完整路径,且朋友圈不支持自定义 path,这是平台硬约束。
坑二:真正的根因在页面 config 约定字段
hook 改完、编译也过了,Android 真机点"分享到朋友圈"还是弹"当前页面不可分享"。我先 grep 产物搜 onShareAppMessage 搜不到,还以为是没编译进去——其实产物里只有 hook 名,回调是运行时由 hook 注册的,搜回调名当然搜不到。
顺着 useShareTimeline 去读 Taro 源码才定位到根因:
@tarojs/taro-loader/lib/page.js:只有页面 config 里enableShareTimeline为真,才注入component.enableShareTimeline = true。@tarojs/runtime的createPageConfig:注释明说onShareAppMessage/onShareTimeline会影响右上角按钮选项、因此不能默认注册,只在四个条件之一满足时才把onShareTimeline挂到页面配置上。
我用 hook 注册,前三条条件(component / prototype / component 上的字段)都不满足;页面 config 又没声明那两个字段,第四条也不成立,于是 config.onShareTimeline 压根没被定义,微信就判定这个页面没开启朋友圈分享。
修复方式是在 src/pages/index/index.config.js 补上这两个字段,并写长注释说明为什么不能删:
export default defineAppConfig({
enableShareAppMessage: true,
enableShareTimeline: true,
// 这两个是 Taro 的约定字段,不是微信配置项。
// 生成小程序 .json 时会被 MiniPlugin 删掉(属正常),但缺了它 Taro 就不会把分享生命周期挂到 Page 上,
// 真机点分享会提示"当前页面不可分享"。
})
这里有个排查提醒:production 产物函数名被 minify、中文被转成 \uXXXX,直接 grep 中文或函数名会搜不到,要搜 1154 / duration:2400 / showToast 这类不会被改的字面量。
坑三:朋友圈只在 Android 真机 + 单页模式
朋友圈分享入口在微信开发者工具里根本不显示,实际只在 Android 真机上出现,iOS 长期不支持。想在开发者工具里验证"分享朋友圈"是不存在的,必须真机 + 安卓。
而且朋友圈打开的是"单页模式"(scene 1154):跳转 API 全部禁用、storage 与正常模式隔离、自定义导航栏默认会被系统栏 float 覆盖。我针对性做了这些改动:
index.config.js增加singlePage: { navigationBarFit: 'squeezed' }(首页是 custom 导航,不设会被系统栏盖住 hero 区;对普通模式无影响,可常驻)。index.vue:isSinglePage同步判定(scene === 1154)、guardJump()在所有跳转入口最前面拦截、useDidShow跳过新手引导(单页 storage 不共用,否则必弹蒙层)、增加一条"前往小程序看完整功能"的提示条。
以后新增任何跳转入口,都要在最前面加 if (guardJump()) return,否则朋友圈里点了没反应。
坑四:单页模式 = 云开发未登录模式(-501023)
最隐蔽的数据坑在最后。单页模式下线稿库是空的。根因是单页模式等于微信官方定义的"未登录模式",默认拒绝一切云资源访问,控制台报 errCode: -501023 permission denied。
修法分两步,少一步都不行:
- 云开发控制台 → 设置 → 权限设置 → 开启"未登录用户访问云资源"开关。
- 配云函数安全规则(自定义,只放开只读的
lineart):
{ "*": { "invoke": "auth != null" }, "lineart": { "invoke": true } }
admin / secCheck 绝不能放开,否则绕过登录就能写数据。
这里有个关键认知,值得单独强调:我这个项目首页是走云函数 lineart 取数的,云函数里用 wx-server-sdk 是管理员权限,不经过 C 端安全规则。所以数据库和文件存储的安全规则根本不用改——网上教程让你三个入口(集合 / 云函数 / 存储)全配一遍,纯属多此一举。判别方法很简单:看页面是直连数据库(要配集合规则、且 fileId 得是 cloud://)还是走云函数(只需配云函数规则)。
顺带说下云函数安全规则的几个坑:三个入口语法完全不同,粘错页会报 InvalidParameter, rule invalid;集合规则里不支持 auth != null(null 不在官方变量表);规则值只支持 true / false / "auth != null" / "auth.loginType != 'ANONYMOUS' && auth != null",不支持 ||;必须包含 * 且每个函数配置必须含 invoke。
全局预设放开是否安全,取决于云函数内部有没有服务端鉴权。我读了本仓库云函数源码:admin 除 whoami 外所有 action 都先 isAdmin(openid)(openid 取自 getWXContext()、前端伪造不了),未登录直接返回 403;已给 whoami 加 if (!openid) return {...} 防御空记录。也就是说,即使把权限设成"所有用户可调用",写操作也进不来,全局放开只是降级方案,最小权限仍是按函数名放开。
坑五:分享封面双比例与 PIL 处理
顺手还做了分享封面图。好友分享 imageUrl 显示 5:4,朋友圈显示 1:1,比例不同不能共用一张——不符会被居中裁切。原图是正方形、主体内切,直接当 5:4 会被上下切平。处理方式是整幅等比缩到贴合高度(800)再两侧补原图底色(四角中位数采样得 #fefae4)。
PIL 有个隐蔽坑:最初用 img.crop((left, 0, left+S, S)),缩放后宽度 794 < 800 导致 left = -3,PIL 对负坐标出界的 crop 会静默填黑边而不是报错。改成 Image.new(bg) + paste 居中贴,四角像素复核干净。产物压到 40KB 左右(quality=84, optimize, progressive)。
服务端这边,上传服务白名单新增了 share type,封面图传到自建 CDN getoffer.alleria.cn/images/share/,首页常量 SHARE_IMG_5X4 / SHARE_IMG_1X1 分别填进好友与朋友圈分享。
方法论沉淀
这次最值钱的是几件事:查编译产物要搜 hook 名不是回调名;读源码定位比在现象层反复试快得多;平台限制(朋友圈仅 Android、单页模式)必须真机验证,开发者工具会骗你;"页面走云函数 vs 直连库"这个判别决定了安全规则要配到哪一层。
我把这套"分享 → 单页模式 → 未登录访问"的判别方法和精确控制台路径,沉淀进了团队的 Taro 样式技能,下次再给别的页面加分享就是照着做,不会再重踩一遍。这也是我做每日技术沉淀的初衷:让每一次踩坑都变成可复用的工程资产。
如果你也在做 Taro 小程序分享,或者踩过朋友圈单页模式的坑,欢迎在评论区聊聊你的处理思路。