- 关于第三方小程序用户隐私保护指引说明公告
为进一步规范用户个人信息处理行为,保障用户合法权益,服务商代开发小程序中涉及处理用户个人信息的,无论是通过调用涉及用户个人信息的相关接口,还是自行收集用户个人信息,在提交代码版本前,服务商均需为小程序补充相应用户隐私保护指引。 平台在代码审核环节会对协议进行相关核验,对有调用个人信息相关接口但未配置隐私协议、协议填写不规范的小程序进行驳回,建议服务商尽快对需要补充隐私协议的小程序配置对应协议,避免影响相关服务及用户体验。 隐私协议配置方式代开发的小程序需通过接口进行配置非代开发的小程序可通过登陆微信公众平台在【小程序管理后台-设置-功能设置-用户隐私保护指引】进行配置 相关配置接口通过设置小程序用户隐私保护指引可进行协议配置,添加、修改、删除。如果涉及自定义用户隐私保护指引,则可以通过上传小程序用户隐私保护指引接口上传,获得ext_file_media_id,然后再调用接口设置小程序用户隐私保护指引进行配置。可通过查询小程序用户隐私保护指引接口获取已经配置的隐私保护指引。如果尚未进行任何配置,依旧可以通过该接口进行获取有哪些【用户消息类型】可进行配置。 关于协议审核与提审额度分配协议完成配置后,需重新提交代码审核,且需在审核通过后重新将代码发布上线才会生效。每次代码审核环节会对协议进行相关核验,如配置的用户隐私保护指引和代码实际里收集的用户信息不匹配以及未满足相应要求,则无法通过代码审核。平台本月将根据服务商的表现,额外增加服务商的提审额度。若对提审额度有更多需求,可通过小程序服务商助手-我的-咨询反馈联系人工客服进行提额。 其他常见问题Q1:我填写了用户隐私说明,为什么却无法进行代码提审、代码提审无法通过? A1:请确认在代码中有使用涉及用户信息的相关接口,包括但不限于手机号、位置等,如有使用需在用户隐私说明中对所有代码中的接口信息进行协议补充。 Q2:我填写了用户隐私说明,且包含了所有代码中的接口信息,为什么还是无法通过? A2:请确认隐私说明填写内容是否合规(不存在恶意引流、过度营销、违法违规内容等)与清晰(清晰描述用户信息授权目的及信息收集场景)。
2021-11-08 - 合理面对风险,服务号订阅通知的正确打开方式?
在《微信服务号订阅通知灰度测试:模板消息之变》中,笔者分析了消息通知方式改变对大家产生的影响,弊大于利。 基于被“用户体验感”挟持的大前提下,不管微信团队是否执意要对策略做出调整,大家都需要未雨绸缪,合理的面对并解决这个风险,以免措手不及。 这篇内容,将会为大家分享笔者结合自己的产品,总结出改造的设计方案。分享前,先为大家解释一下订阅通知所涉及到的名词、订阅和推送通知的方式。 一、名词解释 一次性订阅:指用户订阅一次,服务号可不限时间地下发一条对应的订阅通知; 长期订阅:指用户订阅一次,服务号可长期多次下发通知,长期订阅通知仅向政务民生、医疗等公共服务领域开放。 二、订阅通知的方式 [图片] 目前微信只开放了两种可以进行消息订阅的途径,具体流程如下。 [图片] 1.文章订阅 可以在服务号发布的文章中,插入订阅通知组件,用户点击进行订阅。 每篇文章最多插入10个订阅通知组件,每个组件最多包含5条通知,每个组件不能同时包含一次性订阅和长期订阅,文章推送后用户才可点击订阅。 [图片] 2.网页订阅 可以在服务号绑定的安全域名中,通过微信JS-SDK调起订阅通知界面。 目前没有组件数量和订阅通知数量的限制,但每个组件不能同时包含一次性订阅和长期订阅,并且实际效果需要真机调试,无法用开发者工具模拟。 三、推送通知的方式 通知的推送流程,与模板消息推送流程基本一致,只是将验证[是否已关注服务号]改变成了,验证[是否已订阅通知]。 [图片] 四、通知中的区别 在用户订阅消息后,已关注和未关注服务号,收到消息通知的表现形态有区别,具体表现形式为: 1.已关注 已关注服务号的用户,和以往下发位置相同,直接推送到服务号内。 [图片] 2.未关注 未关注服务号的用户,会直接下发到服务通知,和小程序的通知入口一致。 [图片] 以上就是笔者对订阅通知功能的理解,下面我将会为大家分享产品改造方案。 由于不同产品的交互和业务逻辑不同,所以订阅通知的改造和实现过程仅供参考,具体实现方案还需要根据自身行业做出调整。 五、设计前言 本次是以一款G2C产品为例,产品功能通过服务号+小程序实现。C端用户在关注服务号并注册小程序的前提下,在小程序中触发需要通知的事件后,会在服务号中,同时给C端用户和G端用户推送通知。 因为一些业务需求的原因,C端用户必须要关注服务号。在1年前设计产品时,笔者根据此原因,并结合小程序通知需要G端用户订阅的前提。本着减少用户操作次数,并且服务号会定期群发文章供G端用户学习的想法,选择了通过服务号下发通知的设计方式。 应用场景举例(请勿带入现实生活):C端用户小赵在关注服务号并注册小程序(注册时获取了手机号码)后,G端用户会收到服务号注册成功的通知。同时开发者后台实时根据小赵的手机号码探测网络中的风险。当小赵的隐私信息泄露后,会通过服务号给小赵和G端用户推送通知,告知存在信息泄露风险。 在互联网中,有类似场景的行业还有很多,甚至有一些产品是单服务号实现业务,对模板消息是具有强依赖性的,那么应该如何进行改造设计? 六、用户特征分析 在设计需求之前,笔者首先思考了产品面向的用户群体的特征,一共有两类。 1.C端用户 特点:对产品依赖性不强; 存量用户:基本不会重新进入服务号订阅通知; 新增用户:基本会订阅通知,但订阅留存率可能较低; 原因:产品是由G端用户进行推广,推广时现场教C端用户如何操作,但事后C端用户基本不会再次打开。所以增加服务号订阅通知流程,对新增用户基本无影响,但存量用户的订阅没有保障。 2.G端用户 特点:工作原因,对产品依赖性高; 要求:自己能收到通知,C端用户必须关注服务号并且可以全部收到通知。 七、确定设计边界 分析用户特征,确定产品用户需求后,需要明确设计边界,防止设计时违背用户需求。 1.C端用户 产品使用方式:依旧采用关注服务号+注册小程序的方式; 消息通知:无论是存量用户还是新增用户,都必须可以收到通知。 2.G端用户 产品使用方式:在满足对C端用户要求的前提下,可以接受任何方式,但应尽量简便; 消息通知:必须可以收到通知。 八、文档分析 确认用户特征和设计边界后,阅读微信开放文档,寻找是否有可以额外利用的点。 在“订阅通知接口-用户操作订阅通知弹窗”中可以发现,用户在订阅/取消订阅通知时,微信都会向开发者服务器发送通知。 注:微信开放文档是动态文档,所以在阅读此文时,要注意自行查看文档,避免因微信单方面更新文档而造成信息差。 参数如下: [图片] 九、设计思路 在经过以上分析后,产品的改造设计思路已经基本明确了。下面采用将C端用户和G端用户拆分开方式,给大家分享我的解决方案。请自行进行差异设计。 (1)C端用户 1.存量用户 首先,通过分析用户特征得知,C端存量用户并非100%不会重新进入服务号订阅通知。 为了保证C端存量用户在想要主动订阅时,能够有一个可订阅的入口。在将模板消息改造为订阅通知后,我们应该在服务号中提供一个便于查找、操作简便的订阅入口。例如,可以将能够订阅消息的文章或H5悬挂在服务号菜单中。 如果用户忠诚度较高,不会因为文章群发而出现大规模取关的话,在改造后可以通过群发文章来提高订阅转化率。 笔者所负责的产品用户忠诚度偏低,属于静默类型,所以不计划通过群发文章提醒存量用户进行订阅,仅在文章和H5中悬挂注册入口和订阅入口。 2.新增用户 在分析用户特征时,笔者有提到这款产品的C端用户来源是G端用户现场推广引导注册。在根据自身业务流程做出调整后,对新增用户影响较小。 模板消息正式被微信团队替换为订阅通知后,也没有其他解决方案能够绕过订阅模式。所以在做调整时,要尽可能的保证减少用户为订阅做出的额外操作次数。例如,笔者在某通快递小程序,仅查询物流动态就需要进行2次消息订阅,很令人不舒服。 我们在做设计功能时,可以考虑将验证是否订阅通知作为使用功能的条件,但并不适用于所有行业。例如,在用户下单时,验证是否订阅了发货通知,如果没有订阅就不允许下单。这种办法虽然可以提升订阅率,但显然也会影响成交率,请谨慎使用。 3.补偿机制 在解决完可以主动订阅的用户的问题后,我们还需要考虑如何对未订阅的用户发送通知。 在这款产品中,不会收到通知的有存量未主动订阅的用户、新增拒绝订阅的用户、订阅后自行取消订阅的用户。 笔者采用的方式是,发送通知前验证用户是否订阅消息,如果没有订阅,就对用户发送手机短信。 同时在短信中,可以结合小程序的“UrlScheme.Generate”能力,实现在短信的URL链接中跳转小程序。 在设计改造方案时,笔者查询了大量资料,得知互联网中有一些能力可以实现URL链接跳转到微信并访问指定H5页面,但此行为违反微信运营规定,所以没有采用。 (2)G端用户 在进行特征分析的时候,我们提到G端用户因为工作原因,只能遵守产品规则。但在改造时仍然需要考虑,因为用户体量大,必然会出现因为操作失误而未订阅的用户,所以无论选择哪种方案,都应该增加与C端用户相同的“补偿机制”。 笔者在改造G端用户的通知接收方式时,考虑了两种解决方案。 1.服务号通知 给G端用户提供一个单独的在服务号进行订阅的入口。但G端用户原有业务是依托小程序进行实现的,所以在服务号中通过文章或H5的方式进行订阅都存在问题。 文章:G端用户为内部用户,如果对外暴露文章形式的订阅入口,在C端用户误操作时,对C端用户不够友好; H5:G端用户依托小程序开展业务,单独开发H5页面提供订阅能力对G端用户来说操作麻烦,并且不利于开发者进行维护。 笔者个人认为,微信生态中,H5的使用体验没有小程序好,并且为了节省项目成本,所以不考虑将小程序业务改为H5实现。 2.小程序通知 将G端用户的通知订阅方式彻底改变为小程序订阅的方式。因为笔者所在行业属于政务类,可以申请到长期订阅的通知模板,在改为小程序订阅后,G端用户只需要订阅一次,就可以长期接收消息通知。 按照这种方式进行改造后,G端用户基本可以完全脱离服务号,仅使用小程序就能开展工作。 经过笔者预估,如果采用这种改造方式,会降低G端用户对服务号的关注率。在“设计前言”中有提到,服务号会定期群发供G端用户学习的文章,所以改造后还计划在小程序中增加浏览文章的功能,但是否增加此功能,还需要在改造后观察服务号文章的阅读数据是否下降。 (3)另外 考虑到改造成订阅通知的形式后,运营人员可能会考虑通知送达率,我们可以结合“文档分析”中的参数,在运营后台中增加一些统计指标。 例如,每条通知的累计订阅数量、留存订阅数量、取消订阅数量,同时可以结合用户数预估通知的送达率。 十、总结 以上就是笔者在本次微信计划调整通知策略的事件中,所做出的产品改造方案。 建议在微信明确下线模板消息前,尽量不要安排研发人员进行备用版本的开发,在调整尚未明确之前,安排研发实现无疑是一种浪费人力成本的做法。但作为项目管理者或产品经理,应该在发现风险时,准备多种方案。 最后,希望微信团队能够充分考虑调整策略对服务商、开发者产生的巨大影响,做出合理的决定。 作者:氟西汀,公众号:氟西汀终究还是没了 学艺不精,欢迎批评。如想交流产品设计方案,欢迎留言。 未经授权禁止转载
2021-02-23 - 服务号订阅通知灰度测试
服务号模板消息能力的设计初衷,旨在帮助开发者实现及时通知,但存在一些问题,如: 1. 部分开发者在用户无预期的情况下,发送与用户无关的信息,对用户造成了骚扰。 2. 模板消息是用户触发后的通知消息,不支持营销类消息,不能满足部分业务需求。 为提升微信用户体验,我们开始灰度测试服务号订阅通知功能。 能力说明 开发者可在服务号图文消息、网页等场景设置订阅功能,用户自主订阅后,开发者可按需求下发一条对应的订阅通知。 [图片] 用户可在图文订阅通知 [图片] 用户可在网页订阅通知 灰度测试计划 服务号订阅通知功能即日上线,已认证的境内主体服务号可前往 MP 后台开通使用,详见说明。 1. 服务号订阅通知灰度测试期自2021年1月27日0:00至4月30日24:00,期间服务号模板消息可正常使用;灰度测试期结束后服务号订阅通知的策略将另行公布,届时以官方信息为准; 2. 开发者使用订阅通知功能时,需遵循运营规范,不可用奖励或其它形式强制用户订阅,不可下发与用户预期不符或违反国家法律法规的内容。具体可参考文档:《微信公众平台运营规范》 微信团队 2021年1月27日
2021-01-29 - 社区每周 | 微信卡券将不支持新申请开通优惠券功能、上周社区问题反馈(11.02-11.06)
各位微信开发者 以下是微信卡券将不再支持新申请开通使用“优惠券”功能及上周问题反馈(11.02-11.06),希望同大家一同打造小程序生态。 微信卡券将不再支持新申请开通使用“优惠券”功能因“微信卡券>优惠券”产品能力未来将统一升级为“微信支付优惠券”,12月10日0点起,“微信卡券>优惠券”功能将不再支持新商户开通,该功能后续将陆续下线。其他微信卡券功能暂无变化。本次调整详细内容如下: 12月10日0点起,商户可正常申请开通“微信卡券”功能,申请开通后,“优惠券”功能将不再支持使用。新开通卡券功能的商户使用“会员卡”、“礼品卡”或“票证”等能力不受影响;历史已开通卡券功能的商户,可继续正常使用“卡券>优惠券”功能(包含新增、发放和核销),不受本次调整影响;微信卡券>优惠券功能入口: [图片] 受影响的优惠券: [图片] 如商户有在微信生态内发放优惠券的需求,可使用微信支付优惠券:商家券或支付券(即代金券)。如需了解更多,可查阅微信支付优惠券产品功能介绍 上周问题反馈和处理进度(11.02-11.06)已修复的问题textarea 组件修改某些样式时会失效 查看详情 picker 限制的问题 查看详情 新版 canvas 无法加载多张图片的问题 查看详情 小游戏销毁camera再创建时获取不到帧数据的问题 查看详情 cover-view外层有时候 position:fixed 无效的问题 查看详情 swiper组件 指示点被swiper-item覆盖的问题 查看详情 地图自定义 callout 溢出的问题 查看详情 移动端链接快速授权的文档URL示例多了个auth_type参数的问题 查看详情 小程序订阅消息无法添加的问题 查看详情 iOS录音,视频,文件均无法选择提交的问题 查看详情 修复中的问题 竖版游戏配置resizable为true后,pc端变横版,显示不全的问题 查看详情 iOS微信7.0.17上JSSDK出现BUG:WeixinJSBridge 未定义的问题 查看详情 服务号对话助手小程序用户昵称搜索的问题 查看详情 iOS14首次进入页面掉帧严重的问题 查看详情 无法识别iPhone 12pro机型的问题 查看详情 border-radius在ios失效的问题 查看详情 输入框嵌套在自定义组件内部时英文输入存在bug的问题 查看详情 需求反馈需求已支持 iOS 支持保存 gif & webp 到相册 查看详情 swiper 支持双指滑动 查看详情 video 组件支持投屏功能 查看详情 需求评估中顾问为什么无法收到顾客发出的位置信息 查看详情 云存储可以灵活设计文件权限的需求 查看详情 原创文章类别能否加一个设计的选项的需求 查看详情 增加同一分组内“图片批量删除”功能的需求 查看详情 微信团队 2020.11.12
2020-11-25 - Omi 多端开发之 - omip 适配 h5 原理揭秘
写在前面 Omi 框架是腾讯开源的下一代前端框架,提供桌面、移动和小程序整体解决方案(One framework. Mobile & Desktop & Mini Program), Omip 是 Omi 团队开发的跨端开发工具集,支持小程序和 H5 SPA,最新的 omip 已经适配了 h5,如下方新增的两条命令: [代码]npm i omi-cli -g omi init-p my-app cd my-app npm start //开发小程序 npm run dev:h5 //开发 h5 npm run build:h5 //发布 h5 [代码] node 版本要求 >= 8 也支持一条命令 [代码]npx omi-cli init-p my-app[代码] (npm v5.2.0+) 当然也支持 TypeScript: [代码]omi init-p-ts my-app [代码] TypeScript 的其他命令和上面一样,也支持小程序和 h5 SPA 开发。 开发预览 [图片] 特性包括: 一次学习,多处开发,一次开发,多处运行 使用 JSX,表达能力和编程体验大于模板 支持使用 npm/yarn 安装管理第三方依赖 支持使用 ES6+,ES2015+,TypeScript 支持使用 CSS 预编译器 小程序 API 优化,异步 API Promise 化 超轻量的依赖包,顺从小程序标签和组件的设计 webpack、热加载、less等你要的都有 支持 SVG ! SVG ! SVG ! Omip 不仅可以一键生成小程序,还能一键生成 h5 SPA。怎么做到的?下面来一一列举难点,逐个击破。 问题列表 CSS rpx 转换问题 app.css 作用域问题 JSX 里的小程序标签映射 CSS 里的小程序标签映射 wx api 适配 集成路由 CSS rpx 转换问题 小程序扩展尺寸单位 rpx(responsive pixel): 可以根据屏幕宽度进行自适应。规定屏幕宽为750rpx。如在 iPhone6 上,屏幕宽度为375px,共有750个物理像素,则750rpx = 375px = 750物理像素,1rpx = 0.5px = 1物理像素。 这个特性大受好评,制作响应式网站非常有用。因为浏览器是不支持 rpx 单位,所以需要运行时转换,刚好 omi 内置了这个函数: [代码]function rpx(str) { return str.replace(/([1-9]\d*|0)(\.\d*)*rpx/g, (a, b) => { return (window.innerWidth * Number(b)) / 750 + 'px' }) } [代码] 从 rpx 源码可以看到,需要运行时转换 rpx,而非编译时!因为只有运行时能拿到 屏幕宽度,omi 早期版本已经支持运行时的 rpx 转换: [代码]import { WeElement, define, rpx } from 'omi' define('my-ele', class extends WeElement { static css = rpx(`div { font-size: 375rpx }`) render() { return ( <div>my ele</div> ) } }) [代码] app.css 作用域问题 小程序 Shadow tree 与 omi 有一点点不一样,omi 是从根开始 shadow root,而小程序是从自定义组件开始,omio 则没有 shadow root。 Omi Omio 小程序 Shadow DOM 从根节点开始 无 从自定义组件开始 Scoped CSS 从根节点开始局部作用域,浏览器 scoped 从根节点开始局部作用域(运行时 scoped) 自定义组件局部作用域 所以,app.css 需要污染到 page 里的 WXML/JSX,但在 omi 和 omio 中样式都是隔离的, 需要怎么做才能突破隔离?先看 app.js 源码: [代码]import './app.css' //注意这行!!! import './pages/index/index' import { render, WeElement, define } from 'omi' define('my-app', class extends WeElement { config = { pages: [ 'pages/index/index', 'pages/list/index', 'pages/detail/index', 'pages/logs/index' ], window: { backgroundTextStyle: 'light', navigationBarBackgroundColor: '#fff', navigationBarTitleText: 'WeChat', navigationBarTextStyle: 'black' } [代码] 上面是使用 omip 开发小程序的入口 js 文件,也是 webpack 编译的入口文件,在 cli 进行语法树分析的时候,可以拿到 import 的各个细节,然后做一些变换处理,比如下面 ImportDeclaration(即 import 语句) 的处理: [代码]traverse(ast, { ImportDeclaration: { enter (astPath) { const node = astPath.node const source = node.source const specifiers = node.specifiers let value = source.value //当 app.js 里 import 的文件是以 .css 结尾的时候 if(value.endsWith('.css')){ //读取对应 js 目录的 css 文件,移除 css 当中的注释,保存到 appCSS 变量中 appCSS = fs.readFileSync(filePath.replace('.js','.css'), 'utf-8').replace(/\/\*[^*]*\*+([^/][^*]*\*+)*\//g, '') //移除这里条 import 语句 astPath.remove() return } [代码] 得到了 appCSS 之后,想办法注入到所有 page 当中: [代码] traverse(ast, { ImportDeclaration: { enter (astPath) { const node = astPath.node const source = node.source let value = source.value const specifiers = node.specifiers //当 import 的文件是以 .css 结尾的时候 if(value.endsWith('.css')){ //读取对应 js 目录的 css 文件,移除 css 当中的注释,保存到 css 变量中 let css = fs.readFileSync(filePath.replace('.js','.css'), 'utf-8').replace(/\/\*[^*]*\*+([^/][^*]*\*+)*\//g, '') //page 注入 appCSS if(filePath.indexOf('/src/pages/') !== -1||filePath.indexOf('\\src\\pages\\') !== -1){ css = appCSS + css } //把 import 语句替换成 const ___css = Omi.rpx(.....) 的形式! astPath.replaceWith(t.variableDeclaration('const',[t.variableDeclarator(t.identifier(`___css`),t.callExpression(t.identifier('Omi.rpx'),[t.stringLiteral(css)]),)])) return } ... [代码] 这就够了吗?不够!因为 ___css 并没有使用到,需要注入到 WeElement Class 的静态属性 css 上,继续 ast transformation: [代码]const programExitVisitor = { ClassBody: { exit (astPath) { //注入静态属性 const css = ___css astPath.unshiftContainer('body', t.classProperty( t.identifier('static css'), t.identifier('___css') )) } } } [代码] 编译出得 page 长这个样子: [代码]import { WeElement, define } from "../../libs/omip-h5/omi.esm"; const ___css = Omi.rpx("\n.container {\n height: 100%;\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: space-between;\n padding: 200rpx 0;\n box-sizing: border-box;\n} \n\n.userinfo {\n display: flex;\n flex-direction: column;\n align-items: center;\n}\n\n.userinfo-avatar {\n width: 128rpx;\n height: 128rpx;\n margin: 20rpx;\n border-radius: 50%;\n}\n\n.userinfo-nickname {\n color: #aaa;\n}\n\n.usermotto {\n margin-top: 200px;\n}"); const app = getApp(); define('page-index', class extends WeElement { static css = ___css; data = { motto: 'Hello Omip', userInfo: {}, hasUserInfo: false, canIUse: wx.canIUse('button.open-type.getUserInfo') ... ... [代码] 大功告成! 标签映射 由于小程序里的一些标签在浏览器中不能够识别,比如浏览器不识别 view、text 等标签,需要转换成浏览器识别的标签,所以这里列了一个映射表: [代码]const mapTag = { 'view': 'div', 'picker': 'select', 'image': 'img', 'navigator': 'a', 'text': 'span' } const getNodeName = function(name){ if(mapTag[name]) return mapTag[name] return name } [代码] 在 [代码]h[代码] 函数创建虚拟 dom 的时候进行 [代码]getNodeName[代码]: [代码]function h(nodeName, attributes) { ... ... var p = new VNode(); p.nodeName = getNodeName(nodeName); p.children = children; p.attributes = attributes == null ? undefined : attributes; p.key = attributes == null ? undefined : attributes.key; ... ... return p; } [代码] 这里还有遗留问题,比如内置的一些原生组件如: scroll-view movable-view cover-view cover-image rich-text picker-view functional-page-navigator live-player live-pusher 这些组件如果你需要开发 h5,就别用上面这些组件。如果一定要使用上面的组件,那么请使用 omi 先实现上面的组件。 CSS 里的小程序标签映射 [代码]const map = require('./tag-mapping') const css = require('css') const cssWhat = require('css-what') const cssStringify = require('./css-stringify') function compileWxss(str) { let obj = css.parse(str) obj.stylesheet.rules.forEach(rule => { rule.selectors && rule.selectors.forEach((selector, index) => { let sltObjs = cssWhat(selector) sltObjs.forEach(sltObj => { sltObj.forEach(item => { if (item.type == 'tag') { item.name = map(item.name) } }) }) rule.selectors[index] = cssStringify(sltObjs) }) }) return css.stringify(obj) } [代码] 转换前: [代码].abc view { color: red; } [代码] 转换后 [代码].abc div { color: red; } [代码] wx api 适配 这里需要注意的是,不是所有 api 都能适配,只能适配一部分: wx web wx.request XMLHttpRequest 界面 api(confirm、loaing、toast等) 实现对应的omi组件 数据存储 api localStorage wx 特有的 api 还包括一些特有的生命周期函数,如: onShow onHide 这是 wx 里 Page 里的生命周期,而 omi 是不包含的。这里需要在 router 的回调函数中进行主动调用。具体怎么出发且看路由管理。 集成路由 先看 cli 编译出来的 app.js 路由部分: [代码] render() { return <o-router mode={"hash"} publicPath={"/"} routes={[{ path: '/pages/index/index', componentLoader: () => import( /* webpackChunkName: "index_index" */'./pages/index/index'), isIndex: true }, { path: '/pages/list/index', componentLoader: () => import( /* webpackChunkName: "list_index" */'./pages/list/index'), isIndex: false }, { path: '/pages/detail/index', componentLoader: () => import( /* webpackChunkName: "detail_index" */'./pages/detail/index'), isIndex: false }, { path: '/pages/logs/index', componentLoader: () => import( /* webpackChunkName: "logs_index" */'./pages/logs/index'), isIndex: false }]} customRoutes={{}} basename={"/"} />; } }); render(<my-app />, '#app'); [代码] 4个页面各自做了分包,这样可以加快首屏节省带宽按需加载。接下来看 [代码]<o-router />[代码] 的实现: [代码]import { WeElement, define, render } from "../omip-h5/omi.esm"; import 'omi-router'; let currentPage = null; let stackList = []; define('o-router', class extends WeElement { _firstTime = true; installed() { ... ... } }); export function routeUpdate(vnode, selector, byNative, root) { ... ... } window.onscroll = function () { ... ... }; [代码] 具体实现细节可以去看 o-router 源码,主要实现了下面一些功能: 依赖了 omi-router 进行路由变更的监听(hash change) 依赖 window.onscroll 记录了上一 page 的滚动位置,方便在回退时候还原滚动条位置 记录了 page 容器的 display,不能无脑 display none 和 display block 切换,因为可能是 display flex 等 依靠 omi-router 判断是否是系统后退行为 在正确的时机触发页面的 onShow 和 onHide 新开页面 scrollTop 重制为 0 wx.navigateTo 直接调用 omi-router 的 route 方法 开始使用吧 → Omip Github Omi 相关任何问题疑问反馈意见欢迎进群交流。
2019-04-26 - 微信小程序会员卡开发跳坑
在Bmob官方群,最近看好多人问,小程序里面怎么显示会员卡,然客户领取后,去对应店铺核销。 本身以为会很简单,最后费了好大心思才找到对应文档。 会员卡的文档不知道该怎么说。。。没说明参数从哪里获取。这篇文章带大家跳坑 看了一下文档,大概是这样一个函数,可以让用户领取会员卡 [代码]wx.navigateToMiniProgram({ appId: 'wxeb490c6f9b154ef9', //固定为此 appid,不可改动 extraData: data, // 包括 encrypt_card_id, outer_str, biz三个字段,须从 step3 中获得的链接中获取参数 success: function() { }, fail: function() { }, complete: function() { } }) [代码] 这里的 extraData: data, // 包括 encrypt_card_id, outer_str, biz三个字段,须从 step3 中获得的链,是关键。 extraData,值文档说的第三步,在文档里面很难找到第三步获取开卡组件参数内容。也找不多哪个接口有返回这三个参数 encrypt_card_id, outer_str, biz。 文档上面有个开卡组件文档,我们打开 https://mp.weixin.qq.com/cgi-bin/announce?action=getannouncement&key=1479824356&version=1&lang=zh_CN&platform=2&token= [图片] 既然开卡组件文档没有,那我们去公众号文档,会员卡相关文档看下。 找到卡券-小程序打通 https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1499332673_Unm7V 找到:接口1:获取开卡插件参数 [图片] 红色箭头返回的URL,就带了我们需要的encrypt_card_id, outer_str, biz 三个参数, 只是文档没有说明,这个是url里面带的值,而不是返回的参数,所以给查找带来了麻烦。 我们试试 [图片] 真的拿到了,我们需要的三参数, 然后通过url解析,得到参数。 [代码]wx.navigateToMiniProgram({ appId: 'wxeb490c6f9b154ef9', //固定为此 appid,不可改动 extraData: data, // 包括 encrypt_card_id, outer_str, biz三个字段,须从 step3 中获得的链接中获取参数 success: function() { }, fail: function() { }, complete: function() { } }) [代码] 小程序里做个按钮,领取会员卡。点击事件执行上面代码 提示此小程序未绑定公众账号 此时我们登陆公众账号,绑定这个小程序。 绑定后,继续提示错误 [代码]"navigateToMiniProgram:fail appId "wxeb490c6f9b154ef9" is not in navigateToMiniProgramAppIdList" [代码] [图片] 看英文的意思是说小程序wxeb490c6f9b154ef9未绑定此公众号。 这里wxeb490c6f9b154ef9 并不是我们自己的一个小程序appid ,而是文档规定必须填写的wxeb490c6f9b154ef9,这个是官方的一个小程序appid, 原理是我们执行调整小程序,跳转到官方小程序领取会员卡。 比较麻烦的是,绑定官方开卡这个小程序,需要官方同意才可以,这里添加了绑定,官方2天没同意,已经失效。 到此,就实现了微信小程序,跳转到卡卷小程序,领取会员卡的开发过程。
2019-03-11 - PWA 实践之路
注:本文需要有一定的 PWA 基础 1. 什么是 PWA? 要知道一个东西是什么,我们通常可以从它的名字入手 因此我们看下 PWA 的全称是: Progressive Web App 回答 what 这种问题,重点在于名词,因此 PWA 是一个 APP,一个独立的、增强的、Web 实现的 APP 要达到这样的目的,PWA 提供了一系列的技术 & 标准,如下图所示: [图片] 具体每一项技术是什么就不再赘述了,感兴趣的同学自行网上搜索! 下面有一个简单的 demo 可以简单体会一下: [图片] 以后我们的 web 站点可以像 app 一样,这难道不是一个令人兴奋的事情吗? 所以 PWA 是值得我们前端开发者一直关注的技术! 按照目前的兼容性和环境来看,大家应用最多的还是 Service Worker,因此接下来我们也是把重点放在 SW 上面 那什么是 Service Worker ? 大家都知道就不卖关子了,其实就是一个 Cache 说到 Cache,就一定会想到性能优化了,请看我们的第二部分 2. 首屏优化 2.1. 静态资源优化 如何利用 Cache 来进行优化?这个基本套路应该无人不知了: [图片] 那么首次加载怎么办呢?首次加载是没有缓存资源的,所以会走到线上,所以等于没有任何优化 答案就是 Cache 的第二种常用技巧: precache(预加载) 预加载的意思就是在某个地方或特定时机预先把需要用到的资源加载并缓存 我们的做法如下图所示: [图片] 构建的时候,把整个项目用到的资源输出到一个 list 中,然后 inline 到 sw.js 里面 当 sw install 时,就会把这个 list 的资源全部请求并进行缓存 这样做的结果就是,无论用户第一次进入到我们站点的哪个页面,我们都会把整个站点所有的资源都加载回来并缓存 当用户跳转另外一个页面的时候,Cache 里面就有相应的资源了! 这是我们辅导课堂页面接入 sw 之后的首屏优化效果: [图片] 2.2. 动态数据优化 除了静态资源之外,我们还能缓存其他的内容吗? 答案肯定是可以的,我们还可以缓存 cgi 数据! [图片] 缓存 cgi 数据的流程和缓存静态资源的流程主要有2个差别,上图标红的地方: 需要添加一个开关功能,因为不是所有 cgi 都需要缓存的! 页面需要展示最新的数据,因此在返回缓存结果之后,还需要请求线上最新的数据,更新缓存,并且返回给页面展示,也就是说页面需要展示2次 页面展示2次需要考虑页面跳动的体验问题,选择权在于业务本身! 这是我们辅导上课页接入该功能后的首屏优化效果: [图片] 动态数据缓存是否有意义还需要额外的逻辑来判断,这块暂时是没有做的,后续会补上相关统计 2.3. 直出html优化 还能缓存什么?我们想到了直出的 html 这里就不展开讲了,因为我们的 jax 同学已经分享了一篇优秀的文章《企鹅辅导课程详情页毫秒开的秘密 - PWA 直出》,可以去看看哈~ 3. 替代离线包 PWA 与离线包本质上是一样的,都是离线缓存 正好,我们 PC 客户端的离线包系统年久失修,在这个契机下,我们启动了使用 PWA 替换离线包的方案! 核心流程不变,基本和缓存静态资源的流程是一致的 但是离线包系统是非常成熟的系统,要完全替换掉它还需要考虑许多方面的问题。 3.1. 更新机制 离线包有个自动更新的机制,每隔一段时间就会去请求离线包管理系统是否有更新,有的话就把最新的离线包拉下来自动更新替换,这样只需要1次跳转就能展示最新的页面。 SW 没有自动更新的逻辑,它需要在页面加载(一次跳转)之后才会去请求 sw.js,判断有变化才会进行更新,更新完了要等到下一次页面跳转(二次跳转)才能展示最新的页面。 这里有两个方案: 参考离线包的更新机制,也给 SW 实现一个自动更新的逻辑,借用 update 接口是可以做到主动去执行 SW 更新的。但是非常遗憾,我们的客户端 webkit 内核版本太低,并不支持这个接口 在第一次跳转之后更新 sw,然后检测 sw 状态,发现如果有更新,就用一定的策略来进行页面的刷新 我们使用第2个方案,部分代码如下: [图片] 在检测到 sw 更新之后,我们可以选择强刷,或者提示用户手动刷新页面,具体实现页面可以通过监听事件来处理 更多详细的实现方案可以参考这篇文章哈:How to Fix the Refresh Button When Using Service Workers 3.2. 首次打开问题 一般离线包是打进 app 的安装包一起发布的,在用户下载安装之后,离线包就已经存在于本地,因此第一次打开就能享受到离线包的缓存。 但是 sw 没有这个能力,同样我们也有两个方案: 在 app 安装的时候,添加一步,通过创建 webview 加载页面,页面执行 SW 的初始化工作,并展示相应的进度提示,在安装完成后需要把 webview 的 SW Cache 底层文件 copy 到相应的安装位置。这个方案太复杂,衡量下来没有采用。 在 app 启动时,创建一个隐藏的 webview,加载空页面去加载 SW。 我们使用第2个方案,因为我们的 app 启动会先让用户登录,如下图所示: [图片] 注:这里 app 不是移动端 app,是 pc 的客户端 app 3.3. 屏蔽机制 有时候我们不想使用离线缓存能力,比如在我们开发的时候 在离线包系统,通常会有一个开发者选项是【屏蔽离线包】 SW 也是需要这种能力的,这个方案就比较简单了,在 sw.js 的逻辑里有一个全局的开关,当开关关闭时,就不会走缓存逻辑 因此,我们可以在 dev 环境下把开关关闭即可达到屏蔽的作用 [图片] 3.4. 降级方案 当我们发布了一个错误代码的时候,我们需要快速降级容错的能力 在离线包系统里面有个过期的功能,可以把某个版本设置过期,也就是废弃掉: [图片] 我们利用之前提到的全局开关,通过一个管理接口来设置开关的起开和关闭,即可达到快速降级的目的: [图片] 整个流程大致是这样: 发布了错误代码,并且用户本地 sw 已经更新缓存了错误代码 在管理端关闭使用缓存开关,让用户走线上 快速修复代码并发布,到这里页面就已经恢复正常 在管理端开启使用缓存开关,恢复 SW 功能 请求管理接口是轮询的,这里后续有计划会改成 push,这样会更加及时,当然还要详细评估方案之后才能落实。 4. 如何方便接入? 我们把上述功能集成到了一个 webpack 插件当中,在构建的时候就自动输出 sw.js 并把相关内容注入到 html 文件中,该插件正准备开源哈~ 5. 未来 未来对于 PWA 还能做些什么?笔者以为有以下 2 个方面 5.1. 业务深耕 目前通用的能力已经基本挖掘完成,但是 SW 因为它独特的特性,它能做的事情太多了,但是具体要不要这么做也是因业务而异,而且这些内容可能会很复杂,所以我称为业务深耕。 SW 什么特性?请看下面 2 张图就可以理解了 [图片] [图片] 这种架构相信已经能够看出来了,没错,SW 有间件(层)的特性,那它能做的东西就太多了(虽然 SW 是用户端本地中间层) 简单举几个例子: server 负载控制:当发现 server 端高负载时,SW 可以丢弃请求,或者缓存请求,合并/延迟发送。 预请求:SW 能预缓存的资源是可以构建出来的资源,但是我们还有许多资源是不能在构建阶段知道的,比如图片,第三方资源等,SW 在返回资源请求(比如HTML、cgi 数据)之后,可以扫描资源里面的内容,如果发现包含了其他资源的 url,那说明页面很有可能待会就会请求这些资源,这时 SW 可以提前去请求这些资源;再比如翻页的数据 缓存自动更新:通过与 server 建立联系,当数据有变化时,server 直接把最新的数据 push 到 SW 进行缓存的更新 5.2. 关注 PWA 回到最开始,PWA 是一项令人兴奋的技术,但是浏览器兼容有限,因此期待并关注 PWA 技术的发展是很有必要的! 当然,能推动就更好了!比如推动我们的 X5 内核尽快支持新特性。 关注我们 IMWeb 团队隶属腾讯公司,是国内最专业的前端团队之一。 我们专注前端领域多年,负责过 QQ 资料、QQ 注册、QQ 群等亿级业务。目前聚焦于在线教育领域,精心打磨 腾讯课堂 及 企鹅辅导 两大产品。 社区官网: http://imweb.io/ 加入我们: https://hr.tencent.com/position_detail.php?id=45616 [图片] 扫码关注 IMWeb前端社区 公众号,获取最新前端好文 微博、掘金、Github、知乎可搜索 IMWeb 或 IMWeb团队 关注我们。
2019-03-12 - 微信收款「商业版」与「个人版」有什么区别?看完这篇你就懂!
前几天,我们曾推出一篇文章,介绍了「收款小账本」小程序新上线的“朋友会员”功能,并认为这个功能的推出,能让一般的中小商家都拥有会员营销能力。 大家在讨论的同时,也有很多小伙伴来问我们,咨询这个“朋友会员”跟微信收款商业版(原名微信买单)里面的会员功能有什么不同?「收款小账本」和微信收款商业版这两款同为收款工具的产品又该如何选择? 今天,我们就认真梳理对比一下。 降低门槛是唯一目的 首先,让我们了解一下这两款产品的产生背景。 为门店而生的“微信收款商业版” 当初,在移动支付巨头们的猛攻之下,在线支付已经飞入了寻常百姓家,哪怕是小摊贩、小饭馆都可以挂上一张个人二维码来收款。 [图片] 但这里的问题是,对于有门店的商家而言,如果放店主的个人二维码,店员每次收款都需要找店主核对,十分麻烦,并且跟店主对账也不方便。 所以微信支付开放了一些接口让商家接入,通过这个接口就能生成一个门店专属的收款码,实现一定的经营功能,但当时打通这些接口比较复杂,需要技术开发能力,对于小商家而言是不可承受之重。 因此,2016年,微信支付推出“微信买单”这个工具,商家直接上传相关证件,包括营业执照、法人身份证、组织机构代码证(三证合一的商户可直接上传营业执照)、结算银行账户这四项,就可以直接申请接入微信支付系统,免去了技术开发的困扰。 此后,“微信买单”的功能不断完善,并改名为“微信收款商业版”,更加通俗易懂。 为小商户打造的“收款小账本” “商业版”虽好,但对于许多小商家而言,依旧嫌麻烦,只用个人二维码收款,上述痛点仍然存在。 2018年2月,为了惠及更多的个体商户和小本买卖经营者,微信支付放出大招,推出“中小商家智慧助力计划”,主要产品之一就是「收款小账本」,这款小程序让小商户零门槛拥有一套智慧收银系统。 只要打开自己的微信支付,在“二维码收款”功能中就能找到,或者直接搜索「收款小账本」,使用特别方便。 [图片] 「收款小账本」以个人收款码为基础,并附加上诸多经营功能,比如收款语音播报、经营报表、添加店员等等,可谓麻雀虽小,五脏俱全。 除了经营功能,「收款小账本」还逐渐推出了一些营销功能,比如我们前几天提到的“朋友会员”。 可以说,从两款产品的诞生背景来看,就已经有明显的区别: 微信收款商业版针对于线下门店,以门店为核心,提供丰富的经营与营销功能。 收款小账本针对于用个人二维码收款的小商贩,以个人为核心,提供基础的经营与营销功能。 而两款产品的目的,都是为了降低中小商家接入微信支付的门槛,实现智慧化经营。 「商业版」功能丰富,「小账本」五脏俱全 那么,从具体的功能来看,二者有何区别? 微信收款商业版的主要功能有: 收款记录、支持信用卡付款、自动提现、多门店/多店员管理、官方营销活动、手机扫码付款、免费官方物料、收款语音播报、经营报表分析、商品货架、积分会员。 收款小账本的主要功能有: 收款语音提醒、添加店员接收通知、免费官方物料、收款记录、经营报表、朋友会员(内测)。 功能对比如下: [图片] 对比之后发现,基本上所有「收款小账本」的功能都被“商业版”涵盖了,并且商业版有许多「小账本」没有的功能,我们一一介绍一下: 信用卡支付:顾客扫描商业版的收款码,就可以使用信用卡支付,个人二维码不行。 [图片] 自动提现:商业版默认资金是自动提现到绑定的结算账户上,一般是1到3个工作日到账。 多门店/多店员管理:拥有多个门店的经营者,可以使用这一功能,为每个门店生成单独的收款码,但都结算到同一账户。还可以为每个门店分配店长/店员,方便管理,老板能够在手机上查看每个门店的收款详情。 [图片] 官方营销活动:商户可以在「微信收款商业版」小程序内创建满减、折扣两个营销活动,并且官方也会经常推出摇一摇免单等活动,官方活动的补贴由官方垫付,商户免费申请使用。 [图片] 手机扫码付款:手机一秒变成POS机,店员可扫描顾客付款码,避免顾客输错付款金额的问题。 商品货架:通过这个功能,门店可以创建自己专属的商品货架,其实就是一个小程序版的线上商城,满足了线上下单的基本需求,可以不用再单独开发小程序。 [图片] 说完了不同点,谈谈相同点,有一个大家关心的问题是,二者都有会员功能,如何选择?让我们看看两种会员功能的具体内容。 商业版以积分制为核心 微信收款商业版的会员功能主要与积分结合,顾客每次消费完,在结算页就可以点击成为会员,之后每次消费都能积累积分,积分直接抵扣现金。 [图片] 这里有一个技巧,一般新加入的会员都会赠送一定积分,这个额度以及消费金额与积分的兑换比都可以手动设定,因此,每个门店就可以根据经营情况动态调整。 比如,想要拉来更多新会员,就将赠送的积分调高,想要促进老会员复购,就把兑换比率抬高,原来是1000积分抵1元,可以调整为500积分抵1元。 [图片] 个人版成为会员心理门槛比较高 而「收款小账本」的“朋友会员”,需要顾客手动扫码,添加店主为好友,之后再消费就能获得折扣,此后店主可以通过朋友圈或者群来进行营销。 但这种方式,顾客需要操作的步骤更多,同时也涉及到一定隐私问题,加入会员的心理门槛比较高。详情可查阅→(点我阅读) [图片] 因此,微信收款商业版的会员功能接入更简单,也更灵活,适合客流量比较大的门店,新客转为会员的概率也比较高;而收款小账本的会员功能,更适合已经有一些老客、熟客的小商贩。 最后,说一下大家最关心的费用问题。 申请微信支付商户并开通微信收款商业版是免费的,但在自动结算时会收取手续费,行业不同,费率也不同,一般是0.6%,即一天收入1000元,需要缴纳6元手续费,实际到账994元。 而收款小账本的使用则是完全免费的,收到的钱直接进入微信钱包。 综上所述,通过背景、功能、费用的对比可以看出:微信收款商业版适合有门店,客流量大,有一定规模的商户;而收款小账本适合规模较小,有固定回头客,灵活性比较强的个体小商贩。 大家对这两款产品还有哪些疑惑,可以在下方留言跟我们讨论。
2019-03-12