- createInnerAudioContext access denied?
当页面onHide后返回,会出现这样的报错,我把onHide里面的暂停函数去掉又好了。 报错: operateAudio:fail:access denied,errCode: -1 重现:代码片段转发文件助手或者好友再返回即可重现 代码片段:https://developers.weixin.qq.com/s/jT7WaIm27Oyl
2022-04-22 - page-container在使用wx:if销毁时页面滚动不了,为啥?
添加条件判断后,关闭弹窗,页面生成不了滚动条https://developers.weixin.qq.com/s/T6UuKmmD7cyU
2022-04-12 - wx.enableAlertBeforeUnload只有在非自定义标题栏的时候才会触发?
wx.enableAlertBeforeUnload 在非自定义标题栏时(navigationStyle为custom) 不会触发弹框 请官方帮忙看看 急!!!
2022-01-05 - imgSecCheck调用时总是不是返回结果,而是抛出异常?
[图片] 这是第一种异常,是调用API超时,正常(不是黄色图片)的图片(1M以内)也会报这个错误,这个不知道怎么解决。 [图片] 这是第二种异常,说是内容有风险,但是为啥不直接抛出结果说是内容有风险,而是抛出异常说内容有风险呢。。。
2022-05-10 - 富文本editor编辑器字体颜色设置,请教,怎么弄呢?
在wxml中该怎么设置字体颜色的代码呢,***那该写什么代码呢,有例子参考吗? <i class="iconfont icon-text_color" ******></i> <i class="iconfont icon-charutupian" catchtouchend="insertImage"></i>
2022-06-23 - 编辑器editor字体颜色和背景色的bug?
EditorContext.format(string name, string value)color和backgroundColor表明支持hex color,但是如果颜色值中的字母是大写,则会出现只能选中,不能取消的情况。 <label class="color {{formats.color === '#0000C0' ? 'active border' : ''}}" data-name="color" data-value="#0000C0" style="background-color: #0000C0;"></label> #0000C0中的C一定要小写。切记切记切记 不知道这算不选bug,待官方反馈下。 如果需要复现地址,官方demo中有设置字体颜色的选项把颜色值里面的字母改成大写就可以复现。 代码片段:https://developers.weixin.qq.com/s/W7uZ3EmU7jbl
2022-01-18 - 微信小程序颜色选择器、拾色器、取色器组件
bao-wecom 微信小程序颜色选择器、拾色器、取色器组件 案例demo [图片] 使用方法 1. 安装组件 [代码]npm install --save bao-wecom [代码] 2. 构建 npm 点击开发者工具中的菜单栏:工具 --> 构建 npm [图片] 3. 颜色选择器使用 在页面的 json 配置文件中添加 bao-wecom-color-picker 自定义组件的配置 [代码]{ "usingComponents": { "bao-wecom-color-picker": "bao-wecom/colorPicker/index" } } [代码] WXML 文件中引用 bao-wecom-color-picker [代码]<bao-wecom-color-picker></bao-wecom-color-picker> [代码] bao-wecom-color-picker 的属性介绍如下: 字段名 类型 必填 描述 show Boolean 是 控制颜色选择器是否显示 color String 否 设置颜色选择器的颜色,默认为#ff0000 ,仅支持rgb、hex格式 commonColor String 否 设置常用颜色值,默认为"#000000,#ffffff,#ff0000,#ffa500,#00ff00,#0000ff,#ff00ff,#00ffff,#ffff00,#70db93,#cccccc,#999999" ,仅支持hex格式,最多12个 comfirmText String 否 设置确定按钮的文字,默认为确定 cancelText String 否 设置取消按钮的文字,默认为取消 title String 否 设置标题,默认为请选择 bao-wecom-color-picker 的事件介绍如下: 事件名 描述 confirm 点击确定时触发,返回值{hex: 16进制色值, rgb: rgb色值, hsv: hsv色值} cancel 点击取消时触发 如果觉得作者不易,打赏一下呗 [图片]
2024-10-30 - 现在不能在小程序后台手动新增订阅消息模板了吗?
如题
2024-08-27 - 开源日记 持续更新NO.3 | SkylineUI组件库之基础图表
小程序展示: [图片] 今日更新:半圆进度条 今天更新的进度图表组件仍是使用canvas绘制。 后续还会更新整圆、大半圆、矩形树图、面积图、柱状图等基础图表组件,虽然市面上有echarts和antv f2等非常成熟的图表组件,但我不能确认这些组件是否适配Skyline框架,所以还是决定添加一组轻量化、风格标准极简但满足大部分场景的图表。 说回半圆,开发中最有挑战的,就是完成了计算手指点击坐标是否在进度内,并完成反馈动画 接下来直接展示: [图片] [图片] 当然,也适配了css色彩变量,兼容了深色模式: [图片]
2023-12-29 - 一文教你如何更优雅的处理微信支付商户订单投诉?
在国内做产品涉及使用微信支付商户收款时,难免会遇到一些消费者对支付订单发起投诉,商户如果处理不当会导致商户号出现被延长收款结算周期、限制收款能力、调整交易额度或限制提现等处罚,严重的会关闭商户主体下商户全部支付权限。 那么商户该如何正确处理消费者投诉,降低各项投诉指标呢?一些简单实用的小技巧解除你的烦恼。 微信支付对于消费者投诉处理时效要求: 1)商户最晚需在投诉单生成1天内回复用户投诉受理情况(如9月20日的投诉,商户需在9月21日24点前回复用户) 2)商户最晚需在投诉单生成3天内处理完用户问题,并在商户平台标记“投诉处理完成”(如9月20日的投诉,商户需要在9月23日24点前处理完结) 如何判断一笔投诉单属于已解决?对与投诉单微信支付的认可的处理完成标准如下: ①订单原路全额退款 ②用户主动在投诉入口回复“撤诉” ③商户通过投诉交互功能协商,确认投诉已协商达成一致,点击处理完成,用户点击“已解决”且用户不再重复投诉。 一些处理小技巧: 1)在产品中增加明显的“联系客服”入口,一般用户可以联系到客服的情况下,一般不会优先去投诉支付订单,可以有效降低商户号的客诉率 2)接入微信支付商户的“消费者投诉”接口,实时获取投诉通知,通过类似企业微信机器人能力,让指定人员可以实时接到客户投诉信息,及时处理客户投诉;针对一些投诉即将到达72h的投诉单对指定人员进行多次推送,以免出现投诉单出现超时未处理的情况,微信支付考核商户投诉处理三大指标之一的“及时处理率”就是近30天(T-33天到T-4天)内发起的投诉单,在首次投诉发起后72h内处理完成的比例。 3)在投诉单对用户进行回复后,不要直接去点击“处理完成”,当商家点击“处理完成”后,用户是无法再进行直接回复的,会发起二次投诉,标记”投诉处理完成"前是需要已完成处理用户问题的,此类操作会导致“重复投诉率”大幅度提升,不少商户都在此处踩坑。建议申请结单前确认是否已经与消费者妥善协商处理,针对重复投诉的商户需要关注并及时处理,如重复投诉较多,需要及时排查原因并做优化。 4)当遇到一些恶意投诉/不合理的投诉,请按照商户公司自己的流程妥善处理,并将具体的处理情况和结果回复用户。如果确认无法满足用户的诉求且已经是最终处理方案,在连续发起结单3次后用户仍不满意可以先暂停处理,但这单会被记录服务不满意。如果是恶意投诉,微信侧针对重复投诉会提供10%的“容错率”,如果因为此类恶意投诉导致风控,提供相关凭证在商户后台进行申诉即可。 以下是在处理客诉过程中不可取的行为,会触及“平台消费者投诉管理规范”高压线: 🙅不要主观上先去给用户扣上一顶“恶意投诉”的帽子,搞清楚状况之前,不要妄下结论,主观是大忌,会影响你对客诉处理过程中的判断 🙅不要在沟通过程中(无论是在交易投诉系统还是电话或者商家自建客服系统等)去恶意诱导或辱骂用户 🙅不要去恶意骚扰用户,例如在交易投诉系统拿到用户手机号给他来个“轰炸机”套餐 🙅不要脱离平台去使用三方聊天工具进行沟通 有问题欢迎跟帖讨论,文明沟通,理性发言!!!
2023-09-19 - 微信小程序 Editor组件在无内容的情况下,长按无法粘贴,会自动收起键盘,但没有失焦
微信小程序 Editor组件在无内容的情况下,长按无法粘贴,会自动收起键盘,但没有失焦
2023-07-11 - 小程序频发网络请求失败,错误码有“600001”,“5”,“600003”等,原因是什么?请官方协助
[13:57:19] {"env":"release"} 接口调用报错 {"errno":600001,"errMsg":"request:fail -109:net::ERR_ADDRESS_UNREACHABLE"} [13:42:47] {"env":"release"} 接口调用报错 {"errno":600001,"errMsg":"request:fail -101:net::ERR_CONNECTION_RESET"} [13:37:20] {"env":"release"} 接口调用报错 {"errno":600001,"errMsg":"request:fail errcode:-7 cronet_error_code:-7 error_msg:net::ERR_TIMED_OUT"} [11:16:55] {"env":"release"} 接口调用报错 {"errno":5,"errMsg":"request:fail fail:time out"} [11:16:52] {"env":"release"} 接口调用报错 {"errno":600001,"errMsg":"request:fail -103:net::ERR_CONNECTION_ABORTED"} 等等类似情况,请微信官方协助解决 [图片]
2023-11-13 - 小程序备案指南(企业备案),持续更新
在mp后台: 1:未上架的小程 首页--小程序发布流程--小程序备案(查看能否备案)。 说明:此页面是未发布小程序前的首页页面,发布后的不一样,不要纠结找不找得到、没有这个页面。已经发布的看下方第二张图。 可以备案: [图片] 2:已上架的小程 可以备案: 小程序管理后台顶部会提示“小程序需补充备案信息”的提醒,点击【去备案】即可进入备案流程 或在设置--基本设置--小程序备案(去备案) 不能备案: 设置--基本设置--小程序备案(暂未对存量小程序开放) [图片] 企业小程序备案准备材料: 营业执照(副本扫描件或加盖公章的复印件,建议用副本扫描件,在上面加上**小程序备案所用,他用无效)。法人身份(最好是法人,负责人的也可以)证正反面照片,彩色的,拍照要拍全。管理员个人信息,姓名,身份证号,电话,备用电话,常用邮箱。(建议管理人员和负责人是同一个人)地址填写,最好是营业执照上地址,也可以是常用地址。前置审批/专项审批(具体可查看https://developers.weixin.qq.com/miniprogram/product/record_guidelines.html)。补充材料:根据规则提供包括但不限于授权书、党建证明、居住证、情况说明、承诺书等。互联网信息服务备案承诺书(单位)。资料提前准备好,需要法人扫码验证(和小程序认证差不多)。根据不同地区,准备资料可能有所差异,详细需要什么资料,审核备案时具体再做补充。如果上述没有看懂,转移这里,官方给出的流程https://developers.weixin.qq.com/miniprogram/product/record_guidelines.html[图片] 备案流程 [图片] 。。。。。。(持续更新详细步骤) 九月八号上午填写备案信息,九月十三号成功备案(本来九月八号当天发验证短信的,用户这边没及时验证,耽搁一天) [图片] 常见信息填写问题 1、备案流程中的主办单位、主体负责人具体指的是谁?主办单位 又称互联网信息服务主办者,主要指内容服务提供者,包括单位(如企业、政府机关等)和个人两类。主体负责人 个人:主体负责人应为主办单位本人。非个人:通常由单位法定代表人担任,如有特殊情况(如法代身份涉密、长期不在国内等)可授权单位高管担任。2、个体户没有公章怎么办?若个体工商户无公章,需要主体负责人手写日期+签名+盖手印+身份证号码,同时请在主体备注处备注“个体工商户无公章”。 3、填写小程序主体信息的通讯地址是指的什么地址?可填写主体证件上的地址,也可填写你实际的办公或住所地址。 若你是个人开发者:需精确到门牌号码,若已是最详细的地址或无门牌号的,在主体备注中说明“通信地址已为最详细”。若你是单位开发者需精确到门牌号码,且至少和主体证件所省份保持一致(如证件住所和通讯地址都是广东省),不能使用特殊符号(如:2#楼2-3-301);若已是最详细的地址,无门牌号的,在主体备注中说明“通信地址已为最详细”。备注:若你是北京地区,通讯地址填写时不能使用特殊符号(如:2#楼2-3-301)。4、什么情况下需要上传居住/暂住证明?当个人主体小程序备案申请人的身份证证件地址与申请小程序备案的省份不一致时,需要提供暂住证或居住证等证明材料。 涉及省份包括:吉林、上海、江苏、浙江、安徽、山东、湖北、广东、四川、贵州、云南。 5、小程序备案主体负责人必须填写法定代表人吗?每个省份管局的要求不一致,请按照备案小程序所属省管局要求进行填写,具体请参考: 类型地区主体负责人不是法定代表人需提供小程序主体负责人授权书吉林、山西、甘肃、江苏、安徽、四川 主体负责人必须是法定代表人:天津、内蒙古、陕西、宁夏、新疆、湖北、湖南、河南、上海、浙江、江西、贵州、重庆、云南、西藏、广西、广东、福建、黑龙江、河北、山东、青海 主体负责人可以不是法定代表人吉林、山西、甘肃、江苏、安徽、四川、海南、北京、辽宁 6、提示:主体负责人与法定代表人不一致,且备案所在地不支持法定代表人授权?你填写的【主体负责人】姓名与营业执照证件上的【法定代表人】姓名不一致,请重新填写,并保持一致。你在小程序备案 -【验证备案类型】页面中 - 主办人信息 - 选择地区中选择的省份,不支持法定代表人授权,【主体负责人】需填写【法定代表人】姓名。备案省份需填写小程序备案主体实际所在地,系统会根据你选择的区域自动匹配当地管局规则。7、在填写负责人手机号、应急手机号、邮箱时,提示:不允许被多人使用?在填写负责人手机号、应急手机号、邮箱时,提示“不允许被多人使用”,一般是出现了个人信息混用的情况,即手机号/应急手机号/邮箱填写的是其他人的信息。 在平台备案系统中,人,手机号,应急手机号,邮箱均一一绑定,同一个人允许为多个小程序备案(同一主体下),可以提交一致的手机号、应急手机号及邮箱,但不能出现不同人共用手机号/邮箱的情况。 小程序负责人授权书、小程序主体负责人授权、互联网信息服务承诺书怎么填写?小程序备案材料示例及填写指引:小程序备案材料示例小程序信息填写相关1、什么是服务内容标识?怎么选?服务内容标识是通信管局对各个行业的分类,平台部分行业类目与管局行业类目名称不完全不一致,建议根据备案小程序实际运营内容尽可能选择对应的服务内容标识。 若你是个人主体,请勿选择经营性质、企业/单位性质、涉及有关主管部门审批等的内容,如不可选择“批发和零售业-零售批发”。若你是单位主体,应选择与主体经营范围、资质相符合的内容,如你是医药公司,可选择“医疗服务-医药”,并上传《互联网药品信息服务许可证》。非政府单位不得选择“政务民生”内容。2、小程序负责人具体是指谁?是小程序管理员吗?个人主体:小程序负责人应为主办人本人。 非个人主体:小程序负责人应为本单位/公司具体负责小程序管理、小程序维护的相关人员。 3、怎么判断备案小程序是否要选择前置审批项?可参考:前置审批类别及审批部门 4、小程序管理员信息填写时,负责人姓名已填写为小程序管理员的姓名,为什么还是提示:负责人与小程序管理员不一致?出现这种提示一般都是第三方服务商协助创建的小程序未完善管理员实名信息,需补充管理员实名信息后才能进行备案,补充指引参考: 小程序MP后台-成员管理-管理员-修改。验证原管理员-填写原管理员身份证信息-扫码验证。绑定新管理员-填写【原管理员的信息】并提交,即完成管理员实名信息补充。相关文档可参考:如何完善小程序实名信息 小程序备案常见问题:https://developers.weixin.qq.com/community/develop/article/doc/000ac251a9c340df3e6073ee566c13 最后祝大家,一次备案成功
2023-10-08 - 小程序editor富文本编辑器长按显示系统复制粘贴,抬手时失去焦点导致复制粘贴弹窗不显示bug?
editor组件地址:https://developers.weixin.qq.com/miniprogram/dev/component/editor.html#%E5%B1%9E%E6%80%A7%E8%AF%B4%E6%98%8E 组件名称:editor 富文本编辑器 微信版本号:8.0.38 基础库版本号:2.30.2bug复现步骤:1.长按显示系统复制粘贴, 2.抬手时自动失去焦点,键盘收起,复制、全选粘贴弹窗自动隐藏了,不显示bug。
2023-07-14 - 微信支付平台证书更换指引
微信支付平台证书(以下简称平台证书)是微信支付和商户交互过程中用于认证微信支付平台身份的证书,商户会在微信支付APIv3接口的请求应答、回调、敏感信息加密场景用到平台证书。 平台证书的有效期为5年,如不及时更换,会导致商户调用微信支付接口失败,出现商户业务中断的情况。如果你有使用微信支付APIv3接口,请务必重视。 如果你有收到微信支付商户平台站内信、电话等渠道通知你更换平台证书,请参考以下指引处理。另外,有不少商户和开发者容易混淆平台证书和商户API证书,请注意它们并不相同,因此即使你已更换过商户API证书,也依然要更换平台证书。 平台证书简介及使用说明:https://pay.weixin.qq.com/doc/v3/merchant/4012068814 平台证书更换操作指引:https://pay.weixin.qq.com/doc/v3/merchant/4012068829 为避免更换不及时或更换过程中出现的系统风险影响业务,商户可将平台证书模式切换为微信支付公钥模式。 平台证书切换微信支付公钥指引:https://pay.weixin.qq.com/doc/v3/partner/4012925289 如有疑问,通过如下链接联系到技术支持寻求帮助:https://support.pay.weixin.qq.com/online-service?from=wechatpay
03-24 - “网赚”小程序,你只了解1%
大部分微信开发者对“网赚”的初步认识仅仅局限于网上刷单赚佣金、或阅读文章赚佣金等业务模式。 除以上模式外,还包括自行或协助他人以拟人程序、利诱其他用户参与、转发、下载或委托刷单平台等方式等网赚行为。 今天小编通过实际案例给大家详细剖析相关网赚违规行为: 1. 小程序内纯粹做分享文章/内容后可立即得到奖励的内容(奖励包括但不限于现金、积分、礼品等) 违规示例:如下图违规小程序通过做阅读/转发文章即可获得金币奖励的模式贯穿业务,金币支持兑换现金并提现到账,属于网赚行为。[图片] 2. 小程序内含网赚刷单业务 违规示例:如下图违规小程序为APP提供刷单业务,完成刷单任务后,下载APP即可获取收益,属于网赚行为。[图片] 3. 小程序内存在通过体验APP/小程序/小游戏等产品赚取奖励的行为 违规示例:如下图违规小程序通过体验/转发小程序获取奖励,奖励包括但不限于现金、礼品、积分等,属于网赚行为。 [图片] 4. 小程序内存在通过体验自身业务获取奖励的行为 违规示例:如下图违规小程序通过体验自身业务15秒,即可获得5-10g水滴,水滴可兑换实物或现金等,属于网赚行为。 [图片] 5. 小程序昵称/简介/头像含明显网赚信息 违规示例:如下图违规小程序的头像/简介/昵称含明显网赚信息,诱导进入后获取用户信息,达到网赚推广目的,属于网赚行为。 [图片] 6. 小程序涉及以体验赚奖励、分享赚奖励等业务模式贯穿整个业务 违规示例:如下图违规小程序表面包装成打卡瓜分奖金的业务形态,实际必须通过跳转体验其他小程序、公众号后完成体验任务,才能完成每日打卡任务,属于网赚行为。 [图片] 7、小程序内无实质内容,存在通过批量观看激励视频的形式进行网赚的行为 违规示例①:如下图违规小程序内无实质内容,通过批量观看视频广告获得刮刮卡解锁机会。 [图片] 违规示例②:如下图违规小程序的每一份测试题结果,均需通过观看视频广告才能获得。[图片] 通过以上网赚违规类型及示例的介绍,希望开发者们能对小程序网赚违规有更进一步的了解。如若小程序存在网赚内容,平台将下发警告限期整改,视违规情节严重程度对小程序功能进行限制,或封号处理。
2020-03-18 - 2022-09-14
- RecorderManager录制时录音丢失,stop时报错,右上角录音图标一直是灰的,无法正常结束
RecorderManger录制时有小概率(但近期偏多)出现卡死的情况,为了排查,我们在录制相关的每一个地方都加了日志,下面是根据日志还原的一次问题现场: [图片] 上面提到的另一个录音丢失的帖子:https://developers.weixin.qq.com/community/develop/doc/000c88692982c858a41fddc0c5b800 ,这个当时比较容易复现。 小概率事件,没法提供可复现的代码片段。如有需要,我们可以让出现问题的用户配合提供微信日志。 2023-8-18 21:12 补充: 下面是小程序的日志,用户点击结束录音时,提示录音已经结束了,说明录音是自己提前异常结束的,在此之前小程序也没有收到过onError或者onStop回调。从分析的已录制时长52秒少于录制时长96秒也说明录音事实上早就结束了。用户提供的截图显示右上角是一个灰色的话筒,也说明了录音状态是异常的。 2023-8-18 20:32:35 [info] recorder.onStart 2023-8-18 20:34:11 [info] recorderManager.stop() called. 2023-8-18 20:34:11 [error] recorder.onError: {"errMsg":"operateRecorder:fail:audio is stop, don't stop record again"} 2023-8-18 20:34:11 [info] frameCount: 2025, estimated duration: 52.903124999999996s [图片]
2023-08-18 - Skyline|长列表也可以丝滑~
[图片] [图片] 对于长列表出现的白屏、卡顿、界面跳动等问题,小程序提供了新 scroll-view 来解决这一系列问题。我们先来看看效果~ 快速滚动效果对比我们通过一组长列表来展示新旧 scroll-view 在快速滚动下的效果对比。 当长列表快速滚动时,旧 scroll-view 容易出现白屏的情况,新 scroll-view 则不会出现白屏。 左:旧 scroll-view、右:新 scroll-view [视频] 在安卓机器快速滚动过程中,旧 scroll-view 反应卡顿,容易出现手指离开操作时,滚动动画还在进行。 而新 scroll-view 则可以保持界面滚动效果跟随手指,停止滚动则缓慢结束动画效果。 左:旧 scroll-view、右:新 scroll-view ,测试机型:Xiaomi MIX 3 [视频] 反向滚动效果对比在对话等场景下,反向滚动是常见的功能,旧 scroll-view 并没有提供反向滚动的能力,我们来看看旧 scroll-view 下是怎么完成反向滚动的~ 在对话数据在加载的时候,对话列表需要在更新完列表数据之后,再使用 scroll-into-view 或者 scroll-top 来保持当前滚动位置,因为设置滚动位置会有延迟,所以容易出现 界面跳动 的情况。 // .js // scroll-view 滚动到顶部时触发 bindscrolltoupper() { // 先更新列表数据 this.setData({ recycleList: getnewList() }, () => { // 更新完数据后再设置滚动位置 this.setData({ scrollintoview: scrollintoview }) }) } 为了解决界面跳动的问题,社区上也有通过翻转的方法来解决,将 scroll-view 与 scroll-view 的子元素进行翻转。 // .wxss .reserve { transform: rotateX(180deg); } // .wxml 然而进行翻转之后,会遇到手指滚动方向与列表滚动方向相反、scroll-into-view 属性无效等问题。 为了帮开发者们解决反向滚动类列表的一系列问题,新 scroll-view 直接提供了 reverse 属性支持反向滚动的能力,滚动效果更加顺滑。 左:旧 scroll-view、右:新 scroll-view(图片加载期间,GIF 渲染较慢) [视频] 怎么接入新 scroll-view ?新的 scroll-view 使用起来很简单,主要有以下两个步骤: 修改小程序配置scroll-view 增加 type="list"// app.json // "renderer": "skyline" 开启之后所有页面会变成自定义导航,可参考 https://developers.weixin.qq.com/s/Y5Y8rrm37qEY 实现自定义导航 // 也可在 page.json 中配置 "renderer": "skyline" 逐个页面开启 { ... "lazyCodeLoading": "requiredComponents", "renderer": "skyline" } // page.json { ... "disableScroll": true, "navigationStyle": "custom" } // page.wxml ... // 反向滚动 新的 scroll-view 从安卓 8.0.28 / iOS 8.0.30 开始支持,如需兼容低版本需要进行兼容处理。 wx.getSkylineInfo({ success(res) { if (res.isSupported) { // 使用新版 scroll-view } else { // 使用旧版 scroll-view } } }) 如需体验长列表效果,可在微信开发者工具导入该代码片段即可体验:https://developers.weixin.qq.com/s/Y5Y8rrm37qEY 更多接入详情请参考文档 怎么做到的?大家肯定好奇为什么新 scroll-view 可以解决这个头疼的问题呢? 我们来对比一下新旧 scroll-view 有什么区别就可以知道答案啦~ 旧 scroll-view 逻辑层与渲染层的通信需要通过 JSBridge 进行转换,需要一定的时间开销渲染采用异步分块光栅化,当渲染赶不上滚动的速度,来不及渲染则会出现白屏渲染大量节点内存占用高,需要开发者自行优化只渲染在屏节点,开发成本高新 scroll-view 逻辑层与渲染层的通信无需通过 JSBridge 进行转换,减少了大量通信时间开销渲染采用同步光栅化,滚动与渲染在同一线程,不会出现白屏针对长列表进行优化,只渲染在屏节点,内存占用低,减少了一些渲染耗时,且开发接入成本低[图片] 除此之外,新 scroll-view 后续将提供 type="custom" 支持 sticky 吸顶效果、网格布局、瀑布流布局等能力便于开发者接入使用~
2023-08-03 - 一种新颖的解决IOS虚拟支付的方式
一种新颖的解决IOS虚拟支付的方式 ~ 今天群里有朋友问起这么一件事, [图片] 这里有个很重要的信息,那就是苹果手机虚拟支付不允许,那么安卓手机完成支付,然后将这个付费的服务赠送给苹果手机是否可行 然后我顺着这个思路想到了两个小程序 1)腾讯手机充值 2)乘车码 这里面都有付费赠送的场景 ~ [图片] ~ ~ [图片]~ ~ [图片] [图片] ~ [图片] [图片] ~ [图片] ~ 一种新颖的解决IOS虚拟支付的方式 ~ 目前从合规角度这种方式肯定是通过的,但是不能在ios端做任何引导,只能在安卓端展示这个赠送功能,可以在苹果端写一个类似锦囊的按钮略作提示
2022-01-06 - 小程序功能页面内容接入已开启,但无任何数据收录,请问下是什么问题导致?需要怎么操作?
你好,请问下,小程序内页面内容接入,已经创建有很长一段时间了,页面内容接入从一开始创建后就开启,到现在一条内容都没收录进来,这个需要怎么进行操作才能有内容收录呢? 又或者是不是有考核UV达到多少级别,才能使用该功能?烦请官方帮忙解释下,处理下这个问题,谢谢了![图片][图片][图片]
2023-02-07 - 搜一搜让我们进行页面内容接入-页面收录的对接,但这个该功已经下线,请问我们小程后续应该如何接入搜索?
背景 我们小程序接入了微信搜一搜,审核后提示“同类内容目前较满足,小程序内容质量暂不具备接口接入优势 ,建议可先通过页面内容接入-页面收录的方式进行内容合作” 接下来我们进行页面内容接入-页面收录的对接,有两种形式;1、爬虫自动抓取,这个功能我们一直是开启的,但从去年10月开始微信关闭了此功能;2、接口对接,接口文档的页面无法进入(404) [图片] 问题 请问:接下来我们的小程序应该如何对接微信的搜索功能呢?
2022-06-09 - Collection.watch中监听失效/onChange无返回数据的可能性原因
Collection.watch中监听失效/onChange无返回数据的可能性原因 const watcher = db.collection('todos') // 按 progress 降序 .orderBy('progress', 'desc') // 取按 orderBy 排序之后的前 10 个 .limit(10) // 筛选语句 .where({ // 填入当前用户 openid,或如果使用了安全规则,则 {openid} 即代表当前用户 openid //_openid: '{openid}' }) // 发起监听 .watch({ onChange: function (snapshot) { console.log('snapshot', snapshot) }, onError: function (err) { console.error('the watch closed because of error', err) } }) 尝试了很久发现没返回数据,核查后原因如下: 数据权限问题,需要调整成“所有用户可读,仅创建者可读写”,否则onChange方法将会无任何返回结果,这一点官方文档目前(2022/09/03)没有说明,略坑!; [图片]
2022-09-03 - 「基础库2.29.2」RecorderManager在苹果手机上暂停几次后,就录不进声音了?
代码片段中自动每隔几秒pause并resume,下面截图记录了相关的事件回调(苹果手机),一开始还能看到onFrameRecorded回调,但到后面就没了,不再有新的录音数据了: [图片]
2023-01-10 - 小程序云开发获取并保存用户IP属地
现在各大平台发表文章、评论等内容都显示出了用户的IP属地,现在来探讨一下小程序使用云开发怎么获取并保存用户IP属地。 1、获取到用户ip,这里演示使用云函数获取。 2、使用腾讯位置服务的WebService API的IP定位接口,获取归属地。 响应示例: { "status": 0, "message": "Success", "result": { "ip": "111.206.145.41", "location": { "lat": 39.90469, "lng": 116.40717 }, "ad_info": { "nation": "中国", "province": "北京市", "city": "北京市", "district": "", "adcode": 110000 } } } 演示代码: // 云函数入口文件 const cloud = require('wx-server-sdk') const axios = require('axios') cloud.init() // 云函数入口函数 exports.main = async (event, context) => { const wxContext = cloud.getWXContext(); var ip = wxContext.CLIENTIP ? wxContext.CLIENTIP : wxContext.CLIENTIPV6; if (ip) { const res = await axios.get("https://apis.map.qq.com/ws/location/v1/ip", { params: { ip: ip, key: "xxx" // 使用腾讯WebService API:https://lbs.qq.com/service/webService/webServiceGuide/webServiceIp } }); return res; } return null; }
2022-05-11 - 小程序app.onLaunch与page.onLoad异步问题的最佳实践
场景: 在小程序中大家应该都有这样的场景,在onLaunch里用wx.login静默登录拿到code,再用code去发送请求获取token、用户信息等,整个过程都是异步的,然后我们在业务页面里onLoad去用的时候异步请求还没回来,导致没拿到想要的数据,以往要么监听是否拿到,要么自己封装一套回调,总之都挺麻烦,每个页面都要写一堆无关当前页面的逻辑。 直接上终极解决方案,公司内部已接入两年很稳定: 1.可完美解决异步问题 2.不污染原生生命周期,与onLoad等钩子共存 3.使用方便 4.可灵活定制异步钩子 5.采用监听模式实现,接入无需修改以前相关逻辑 6.支持各种小程序和vue架构 。。。 //为了简洁明了的展示使用场景,以下有部分是伪代码,请勿直接粘贴使用,具体使用代码看Github文档 //app.js //globalData提出来声明 let globalData = { // 是否已拿到token token: '', // 用户信息 userInfo: { userId: '', head: '' } } //注册自定义钩子 import CustomHook from 'spa-custom-hooks'; CustomHook.install({ 'Login':{ name:'Login', watchKey: 'token', onUpdate(token){ //有token则触发此钩子 return !!token; } }, 'User':{ name:'User', watchKey: 'userInfo', onUpdate(user){ //获取到userinfo里的userId则触发此钩子 return !!user.userId; } } }, globalData) // 正常走初始化逻辑 App({ globalData, onLaunch() { //发起异步登录拿token login((token)=>{ this.globalData.token = token //使用token拿用户信息 getUser((user)=>{ this.globalData.user = user }) }) } }) //关键点来了 //Page.js,业务页面使用 Page({ onLoadLogin() { //拿到token啦,可以使用token发起请求了 const token = getApp().globalData.token }, onLoadUser() { //拿到用户信息啦 const userInfo = getApp().globalData.userInfo }, onReadyUser() { //页面初次渲染完毕 && 拿到用户信息,可以把头像渲染在canvas上面啦 const userInfo = getApp().globalData.userInfo // 获取canvas上下文 const ctx = getCanvasContext2d() ctx.drawImage(userInfo.head,0,0,100,100) }, onShowUser() { //页面每次显示 && 拿到用户信息,我要在页面每次显示的时候根据userInfo走不同的逻辑 const userInfo = getApp().globalData.userInfo switch(userInfo.sex){ case 0: // 走女生逻辑 break case 1: // 走男生逻辑 break } } }) 具体文档和Demo见↓ Github:https://github.com/1977474741/spa-custom-hooks 祝大家用的愉快,记得star哦
2023-04-23 - 重渲染与自定义组件优化(下)
[视频] 接下来我们看实践二实现wxs版本的stopwatch组件。 另一个组件stopwatch_wxs这个组件版本与stopwatch组件它实现了相同的方法,调用方式也是一样的,不同点在于我们wxs组件。这个版本的组件,它在wxml这个页面里边引入了一个wxs脚本。关于时间计算的逻辑,包括格式化等等这些全部从逻辑层移到了wxs脚本里面去,从这个组件的代码来看wxs组件的JS代码与我们原来的stopwatch组件,它的代码是一样的。 主要也是实现了三个方法start、stop和switch,并且它的wxss的样式代码相比原组件也没有一个变化。在组件的wxml代码里面引入了一个wxs脚本,JS这个代码层对wxs脚本的一个控制,它是通过一个叫做change:mode这样的一个属性去完成的。index.wxs这个脚本的这个内容我们可以看一下,稍后会看到,这个文件只能使用ES5的语法,所有ES6的语法都不能使用,并且这和我们在这个项目详情面板里边是否开启将JS变成ES5没有关系,你开不开启这个设置你都不可以使用,这样一个ES6语法。 wxs脚本它没有定时器,我们可以使用组件对象的requestAnimationFrame方法代替于原来的定时器方法,在这个组件对象上我们当然也可以使用setTimeout方法创建定制器,setInterval也可以,但显然在这个地方与帧频天然合拍的requestAnimationFrame方法,它更适合干这个工作,它能使我们这个数字的切换刷新的动画看起来更加的一个流畅。在测试的时候我们仍然可以打开Performance面板,使用wxs脚本实现了单次视图更新,它的消耗大概是在20毫秒左右,比原来的执行性能要稍好一些。 下面我们开始代码实践。 首先第一步我们需要去创建另外一个版本也就是wxs版本的这样的一个组件。这个组件在我们最终源码里面都有了,都已经实现了。我们看一下它的一个效果。首先我们看wxml这个标签代码,标签代码相比原来我们增加了第一个,增加了这个模块wxs这个模块的一个引入,然后在下面这个地方加了一个change:mode等于index.modeObserver。这个是为了实现一个属性监听,就是将我们对mode属性的一个变化,将这个事件然后传递给这样的一个方法 传递给它去驱动这个方法的一个执行。当我们加了这个属性以后,后面mode等于什么一个值我们必须也是要添加的,这个是必不可少的。 再看一下JS代码里面,首先这个地方,我们可以看到把它放在了data里面,因为要通过setData去改变它去触发我们视图上面属性它的一个改变,所以我们把这个地方把它放到data数据对象里面。然后start方法很简单,就是一个setData的调用,stop也是。switch方法没有变化,整个现在我们自定义组件对于wxs这个版本它的JS逻辑层代码已经达到了最简化,它没有一点多余的一个代码了。 那么这个组件的功能在哪里实现的,我们看一下。最重要的一个角色就是index.wxs脚本,这个脚本我们刚才提到了是在这个地方引入的对吧,引入以后然后我们看它里面干了什么事情。它有一个导出的方法在最下面,模块导出然后modeobserver这个方法干了什么事情,我们看一下。在这个地方它接收了一些参数,这些参数都是视图在调用它的时候负责传递给它的,这是它的新值就是mode的新值,因为这个监听的是mode属性,然后这是它的旧值,ownerInstance,这个是它本身组件它所属的实例,它属于哪个实例对象,这个它本身的一个实例。在这个里面我们需要去做判断,然后去看新值,判断如果它等于start,我们就调用这个start,如果它等于stop,我们就调用stop对吧。 start与stop怎么实现的?我们再接着往上面看,start里面这个地方有一个配置,这个配置其实传的其实就是ownerInstance。如果说我们组件在这个页面里面的话,它本身它的所有者其实就是一个page,它不是page也没有关系,因为我们用的它里面的这个方法是callMethod,调用它的方法还有requestAnimation调用它,只要这个方法可以调用就可以了,在这个里面我们再看一下干了一个什么事情。这个地方仍然会有一个convertTimeStampToString这样的一个时间格式化的方法,这个方法全是用ES5的这样一个语法写的。这个逻辑我们可以看到与我们原来的组件的逻辑,我们可以看一下是一样的,我们只是把它换了一个地方,然后拷贝在这个地方,同时将我们的let关键字改成var。为啥改成var?因为前面我们提到了,在wxs脚本里面你不可以使用ES6的语法,只能使用ES5对吧,然后这个地方就是为了格式化。 格式化以后在这个地方注意,我们有了一个小小的处理,为啥要处理?因为本身在wxs脚本里边去调callMethod的方法也是间接调setData,我们这个代码调用本身也是要通过Native层进行中转的。如果你这个方法调得太频繁的话显然它也会影响性能,所以我们这个地方做了一个限流,判断一下消失的时间是不是大于100毫秒,如果大于100毫秒然后允许它调用,如果没有那就先跳过去本次更新 让更新不太频繁。100毫秒间隔是可以的,因为我们人类对变化的一个感知时间大概是200毫秒,你可以感知它的一个变化100毫秒的话,基本上你对人类来讲是无感知的。 这个地方会有一个mode等于start的判断,如果是它还等于start这个状态,组件这个状态的话我们继续用requestAnimationFrame去启动本身上面的调用它本身是一个递归调用。只有当mode等于stop的时候,它才会停止调用 不再会调用了,这是它的一个调用方法。这个地方我们再多说一下,这地方有一个是什么 requestAnimationFrame这个方法本身是由我们渲染的时候,视图它本身帧频去驱动的,就是视图层渲染一次,它触发一次这个事件,然后我们通过这个方法去绑定的这些回调执行,它也会再跟着再执行一次,就这样一种方式。 它可以最大程度的跟我们这个视图的渲染进行合拍,如果是你不用这种方式,你用定时器的话,定时器写在逻辑层里面很可能跟我们这个视图渲染它不是合拍的,视图渲染了比如说你每秒渲染30帧,然后JS定时器你要求它每秒更新60帧,它跟不上。它跟不上的时候视图就会卡顿。所以这个地方我们用requestAnimationFrame这个方法,这是我们优先要使用的一个方法。前面我们提到了在这个里面,我们不可以使用setInterval setTimeout,本身在wxs脚本里面是不能使用的。但是我们在我们传进来的对象上比如说像ownerInstance,我们在这个上面它其实是有定时器方法的,你明白吗?它是有定时器方法的,就是我们可以在这个上面去调用定时器方法,有这个选项可以选择。但是就如我们刚才所说它性能其实不如requestAnimationFrame,所以我们在这个地方还是选择这个方法。 下面我们再看stop,其实它什么事都没干,为什么?正常情况下我们在这个地方要干一个清扫的工作,就是我们要调cancelAnimationFrame,但这个cancelAnimationFrame这个方法在这个对象上面它是不存在的。你把这个配置换成这个也是一样的,本身这个方法在这个上面目前是不存在的,以后可能会有,如果以后有的话,这个地方我们要做一个清扫工作,但目前它没有,所以这个地方这个方法什么也没有做这样的一种方式。 还有一个地方我们需要说明一下,就是这个mode,这个mode我们可以看一下我们是在哪里定义的,是本身在这个模块代码里面对吧。在这个地方进行定义的,它相当于什么,它其实相当于是一个模块变量。这个模块当它在我们的页面里面 在这个地方,当它在这个地方引入的时候,其实它已经有了一个模块的实例。明白了吗?它已经有了一个实例,当它有了一个实例以后,里面的这些变量它其实都是有状态的,都可以自己持有自己的一个状态,持有自己一种状态。所以在这个地方我们可以拿这个mode然后去做临时的一个状态的储存,然后这个地方可以去判断。 如果我们不用这种方式还有另外一种方式可以用是什么方式,就是通过instance.getDataset然后调用它的mode,调用它的mode去判断是否等于start,这个取它是从哪取的,它其实从我们这个组件。我们看一下传递过来的事件是这个,因为这个事件是从这传的 ,instance它其实等于谁?它其实等于这个view对不对,我们取它的dataset取到哪里,取到这个地方看到没有?这个地方有一个data-mode,等于mode对不对。如果是我们按照我们后面提到的这个方式去取,取到的信息其实是它。当然这个地方我们要有一个对比,在这个模块里边让它自己持有这个状态,与我们从这个视图上去取这个状态,两种方式进行对比。 我们下面的这种方式它的效率会更高,因为它本身自己就把这个状态给它做了很好的一个判断,你不需要去麻烦别人再去取那个状态了,你多了一层调用效率又会降低,这是关于这个脚本的一个说明说得稍微多一点,因为所有的代码都是在这里面实现的。 这个工作做完以后,接下来我们要做什么?因为我们组件的向外暴露的方法都是一致的,行为也是一致的,所以我们只需要在我们组件引入的在这个地方加了一个wxs,只需要加这个以后组件引用就变成从原来引入这个组件,然后变成了引入这个组件,这个组件名我们不变,组件名我们仍然可以按原来的方式进行使用,现在代码终于改完了。然后单击编译,我们看一下它的实际运行效果。选择我们首页然后运行首页 ,已经渲染出来了,然后单击 然后它运行,我们看到也在快速地进行切换,现在我们要对比,要打开Performance面板,可以让它先停下来把我们原来的给它清除一下,重新单击录制,然后单击开始,已经有了 然后停止 不需要太长,因为我们只需要测一段时间,只要它有执行就可以了,看一下 仍然是有问题的。因为我们的优化看来是无止境的,而我们对比一下看看跟原来相比是不是有所改善,你比如这个我们看到它渲染任务大概是54.30对不对,然后它里边这里面有个setData,然后这个地方也是大概57.40,这个是66 稍微长一点66,这个地方是61,然后这个大概是26,这个里面我看一下,它里面其实也是有setData的 每次都不一样。 这个地方它性能我们可以做个小小的测试,它主要是由什么样的一个代码然后引发的,我们可以改一个地方 改哪里,你觉得我们改哪里可以让性能稍微变好一点,改我们的这个地方对不对。因为它现在性能其实主要是在这个地方受影响的,我们只需要在这个地方将这个阈值改一下,我们改成200 改成200之后再做一个测试,再看一下表现 把这个清掉,然后再单击开始 然后录制,它本身面板的开启也是需要CPU的,所以它开启的时候我们可以明显感到它有一个卡顿,好,停止,现在这个测试我们看一下跟我们刚才的操作相比,明显可以感到我这个界面没有刚才卡顿了对不对,比刚才好了很多,这个地方已经没有红三角了,红三角的显示与刚才相比已经少了很多,看我们刚才这些东西已经没有了对不对,我们可以看到里面的方法,这个地方这有一个 很多都是匿名函数的一个回调,我们再看这个地方有setData对不对,setData大概是浪费了6.98毫秒,它整体上这个时间 我们渲染这个时间大概是消耗了到36.40,刚才我们看到大概是60毫秒,现在我们把它 间隔100改成200以后,消耗大概是变到了36毫秒,基本上效率提升了一半。 这和我们的预想也是一致的,因为我们将本身这个地方将100改成200的时候,从理论上来讲它这个性能应该会提升100对吧,这个从侧面也说明这个地方它其实就是影响我们性能的主要的一个关键点。从理论上讲也是因为我们所有的这些其他的一个代码,它都是这个wxs代码对吧,只有这一个地方我们是用到了JS的逻辑层计算这个结果,然后要通过这样一种方式传给JS逻辑层,由JS逻辑层再去更新这样视图里面的数据,本质上我们这种优化把这个组件变成wxs这样的一个版本,变了以后 其实只有我们上面的这些计算代码是用新的脚本语言,就是wxs脚本去执行的,而渲染数据的传递还是原来的那种方式并且它还绕了一个弯对不对,所以从这两种组件的表现来看,我们新的组件它在渲染的时候 它性能比原来的组件会稍微好那么一点点,但是好的也不算太多,因为本质上它还是受限于callMethod以及setData这个方法的一个限制,它有限制在这个地方,我们这个演示就说到这里。 最后我们总结一下,小程序的视图更新有重渲染机制,当这个逻辑层代码通过setData方法 改变视图数据的时候它会触发新的wxml节点树的一个生成以及新旧节点树的一个差异比较将数据密集更新的功能组件化可以显著提升我们视图渲染的运行时效率。此外wxs脚本,它由于不需要底层的一个中转,也就是通过evalulateJavaScript方法的一个帮助,它可以绕过逻辑层直接操作这个视图层的一个组件,使用它辅助完成这个组件的一个开发也可以大大提升我们这个视图渲染的一个效率,在wxs脚本与逻辑层需要相互调用的地方我们也有办法进行处理 我们举个例子,例如在wxs脚本里面可以通过ownerInstance的callMethod方法去调用这个页面上的方法。例如刚才我们看到的对setData这个方法的一个调用反过来逻辑层怎么样去调用wxs脚本里面的方法,我们可以通过视图层上绑定一个名称,为changexxx这样的一个特别属性,触发对wxs脚本的里面的方法的一个调用。 这节课我们涉及到的文档如屏幕上所示,这节课我们就讲到这里。 这节课我们主要学习了如何将数据更新频繁的功能区域进行组件化以及如何使用wxs脚本辅助完成组件的一个自定义以提升运行时的渲染性能。 下节课我们学习代码按需注入与初始渲染缓存,这里有个问题请你思考一下:在传统动态的网页开发里面,可以将HTML这个页面在这个服务器端缓存下来甚至写成静态的HTML文件以此来加快下一次用户访问,同一个页面的加载速度在这个小程序开发里面有类似的技术也可以实现这样的一个效果,你知道是哪个技术吗? 下节课我们一起来深入探讨一下这个问题。 点击查看开放文档: WXS 语法参考WXS响应事件
2022-07-13 - 微信小程序使用科大讯飞语音评测,保姆级教程!
最近微信小程序项目中,需要添加语音评测功能,就选用了科大讯飞的语音评测流式版接口,但在使用过程中,遇到了很多问题,再网上搜资料,搜了好多,也没直接能用的,好在后来参考了许多资料后,终于调试成功了,接下来,跟大家分享一下我是怎么处理的。 1.第一步,准备所用到的工具,下载官方jsdemo,将 base64js 文件复制到自己的小程序项目中,用npm安装crypto-js xmldom这2个工具 然后,将工具导入到页面中 const CryptoJS = require('crypto-js') const Base64 = require('../../tools/base64js').Base64; var DOMParser = require('xmldom').DOMParser; 2.第二步,初始化用到的变量,定义用到的关键函数 const APPID = '替换成你自己的' const API_SECRET = '替换成你自己的' const API_KEY = '替换成你自己的' let audioData = [] //存储音频流的数组 let socketTask = null //小程序的socketTask let handlerInterval = null // 定时器,用来定时发送数据流 function getWebSocketUrl() {//生成socket使用的url return new Promise((resolve, reject) => { var url = 'wss://ise-api.xfyun.cn/v2/open-ise' var host = 'ise-api.xfyun.cn' var apiKey = API_KEY var apiSecret = API_SECRET var date = new Date().toGMTString() var algorithm = 'hmac-sha256' var headers = 'host date request-line' var signatureOrigin = `host: ${host}\ndate: ${date}\nGET /v2/open-ise HTTP/1.1` var signatureSha = CryptoJS.HmacSHA256(signatureOrigin, apiSecret) var signature = CryptoJS.enc.Base64.stringify(signatureSha) var authorizationOrigin = `api_key="${apiKey}", algorithm="${algorithm}", headers="${headers}", signature="${signature}"` var authorization =Base64.encode(authorizationOrigin) url = `${url}?authorization=${authorization}&date=${date}&host=${host}` resolve(url) }) } 3.开始录音 //开始录音 startVoiceRecord(){ let that = this that.setData({recordState:'recording'}) recorderManager.onStart(() => { console.log('recorder start') }) recorderManager.onPause(() => { console.log('recorder pause') }) recorderManager.onStop((res) => { console.log('recorder stop', res) const { tempFilePath } = res that.startUpRecord()//录音完成,准备调用讯飞接口 }) recorderManager.onFrameRecorded((res) => { const { frameBuffer } = res console.log('frameBuffer.byteLength', frameBuffer.byteLength) let u8Arr = new Uint8Array(frameBuffer) audioData.push(u8Arr) //将每一帧的数据取出,放到audioData中,准备使用 }) const options = { duration: 180000, sampleRate: 16000, numberOfChannels: 1, encodeBitRate: 44100, frameSize: 2, format: 'pcm', } recorderManager.start(options) }, 4. 开始socket连接,准备上传数据并处理 startUpRecord(){ let that = this getWebSocketUrl().then(( url)=>{ let newURL = encodeURI(url) socketTask = wx.connectSocket({ url: newURL, }) socketTask.onOpen(()=>{ console.log('打开了socket') that.webSocketSend() }) socketTask.onMessage((e)=>{ // result 在这里做信息处理 console.log('收到了结果:',e) that.result(e.data) }) socketTask.onError((err)=>{ //结束录音 console.log('socket 出错:',err) }) socketTask.onClose(()=>{ // 结束录音 console.log('socket 关闭:') }) }) }, webSocketSend() { console.log('开始发送数据',audioData) let that = this let audioDataUp = audioData.splice(0, 1) var params = { common: { app_id:APPID, }, business: { category: 'read_sentence', // read_syllable/单字朗读,汉语专有 read_word/词语朗读 read_sentence/句子朗读 https://www.xfyun.cn/doc/Ise/IseAPI.html#%E6%8E%A5%E5%8F%A3%E8%B0%83%E7%94%A8%E6%B5%81%E7%A8%8B rstcd: 'utf8', group: 'pupil', sub: 'ise', ent: 'cn_vip', tte: 'utf-8', cmd: 'ssb', auf: 'audio/L16;rate=16000', aus: 1, aue: 'raw', text: '\uFEFF' + '今天天气怎么样?' }, data: { status: 0, encoding: 'raw', data_type: 1, data: that.toBase64(audioDataUp[0]), }, } console.log(JSON.stringify(params)) socketTask.send({data: JSON.stringify(params)}) handlerInterval = setInterval(() => { // websocket未连接 if (!socketTask) { clearInterval(handlerInterval) return } // 最后一帧 if (audioData.length === 0) { console.log('数据发送完毕') socketTask.send( {data: JSON.stringify({ business: { cmd: 'auw', aus: 4, aue: 'raw' }, data: { status: 2, encoding: 'raw', data_type: 1, data: '', }, })} ) audioData = [] clearInterval(handlerInterval) return false } audioDataUp = audioData.splice(0, 1) // 中间帧 console.log('audioDataUp:',audioDataUp[0]) socketTask.send( { data: JSON.stringify({ business: { cmd: 'auw', aus: 2, aue: 'raw' }, data: { status: 1, encoding: 'raw', data_type: 1, data: that.toBase64(audioDataUp[0]), }, })} ) }, 40) }, result(resultData) { // 识别结束 let jsonData = JSON.parse(resultData) if (jsonData.data && jsonData.data.data) { let data = Base64.decode(jsonData.data.data) const doc=new DOMParser().parseFromString(data,'text/xml'); let sentence = doc.getElementsByTagName('read_sentence')[1] let accuracy_score = sentence.getAttribute('accuracy_score') let emotion_score = sentence.getAttribute('emotion_score') let fluency_score = sentence.getAttribute('fluency_score') let total_score = sentence.getAttribute('total_score') let integrity_score = sentence.getAttribute('integrity_score') let phone_score = sentence.getAttribute('phone_score') let tone_score = sentence.getAttribute('tone_score') let is_rejected = sentence.getAttribute('is_rejected') console.log('parseRes:',accuracy_score,emotion_score,fluency_score,total_score) //评测结果在这里,就出来了,然后就可以拿评测数据去使用了 } if (jsonData.code === 0 && jsonData.data.status === 2) { // 在这里结束socket socketTask.close() } if (jsonData.code !== 0) { socketTask.close() console.log(`${jsonData.code}:${jsonData.message}`) } }, 到这里,整个流程就完了,祝愿大家都能一次调用成功,有什么问题的话,咱们再讨论!
2023-06-13 - 请教一下:RecorderManager能否获取到音量?
想实现录音时麦克风随音量大小变化的动画效果,不知道RecorderManager如何实现。
2022-10-03 - 「基础库2.26.1」我们又有被灰度到的用户无法播放mp3了。关于小程序基础库灰度流程的建议?
建议:每次灰度新的基础库版本时,能不能最先灰度我们这些开发者提供的微信号,以方便我们尽早的进行新版本测试?可以每次提供个表单让我们来填要灰度的微信号。 从昨晚开始,我们被灰度到2.26.1的用户很多都出现了mp3无法播放的问题,基础库版本升级前都是正常的。而类似这样因为基础库被灰度到更高版本导致小程序无法播放mp3的情况已经出现好几次了。
2022-09-24 - 小程序性能优化实践
小程序性能优化课程基于实际开发场景,由资深开发者分享小程序性能优化的各项能力及应用实践,提升小程序性能表现,满足用户体验。
2024-10-09 - 小程序录音实时波形图
首先,做这个不需要把MP3转pcm。 结果就是,转了pcm也不知道怎么出波形。搞了我好几天。。。 但是微信现在不需要引入js-mp3库就可以转,仅仅记录一下,代码如下: var audioCtx = wx.createWebAudioContext() audioCtx.decodeAudioData(frameBuffer, audioBuffer => { let float32Array = audioBuffer.getChannelData(0) ...} AudioContext.createAnalyser(),这是浏览器的接口,微信的WebAudioContext暂时没有这个。 页面js: const that = this; var recorderManager = wx.getRecorderManager() //把frameBuffer转一下,再转成普通Array,传给voice组件 recorderManager.onFrameRecorded((res) => { const { frameBuffer } = res let uint8Array = new Uint8Array(frameBuffer) that.setData({ voiceLine: new Array(...uint8Array) }) }) const options = { duration: 40000, numberOfChannels: 1, format: 'mp3', frameSize: 0.01, //这里设置很小,就会只取一帧就触发onFrameRecorded事件 // sampleRate: 44100, encodeBitRate: 16000, } recorderManager.start(options) 页面wxml:引入组件,传递数据 组件voice.wxml: 组件voice.wxss: .canvas { position: fixed; top: 0; left: 10px; right: 60; bottom: 0; width: 300px; height: 100%; } 组件js: var voiceLine = []; var MaxValue = 0; Component({ /** * 组件的属性列表 */ properties: { voice: Array }, /** * 组件的初始数据 */ data: { }, lifetimes: { // 组件刚刚被创建时执行 attached() { wx.createSelectorQuery().in(this) .select('#canvas') .fields({ node: true, size: true, }) .exec(this.init.bind(this)) }, //删除该组件绑定的所有事件 detached() { } }, pageLifetimes: { show: function () { }, hide: function () { // 页面被隐藏 }, resize: function (size) { // 页面尺寸变化 } }, observers: { 'voice': function (voice) { if (!voice instanceof Array || voice.length === 0) { return; } // 显示单一波形,调试用 //解开注释可以查看每个波形,慢慢找规律 // this.renderVoice(voice); // return; let voiceLen = voice.length, plantValue = 0, //常见数据 serialValueNum = 0 //连续计数器 for (var i = 0; i < voiceLen; i++) { //连续9个表示该帧空了 if (serialValueNum > 9) { console.log('总长:', voiceLen, '常见数据:', plantValue, '干了:', serialValueNum) if (voiceLine.length > 60) { voiceLine.shift() } voiceLine.push(0) this.renderCanvas(); return; } if (plantValue != voice[i]) { plantValue = voice[i] serialValueNum = 0 } else { serialValueNum++; } } // 按长度显示 let middleNum = voice.length if (middleNum > MaxValue) { MaxValue = middleNum console.log('MaxValue:', MaxValue); } //长度<100抛弃数据 if (middleNum < 100) { console.log('长度<100抛弃数据:', middleNum, voice); middleNum = 100 } if (voiceLine.length > 60) { voiceLine.shift() } voiceLine.push(middleNum) this.renderCanvas(); }, }, /** * 组件的方法列表 */ methods: { init(res) { const width = res[0].width const height = res[0].height const canvas = res[0].node const ctx = canvas.getContext('2d') const dpr = wx.getSystemInfoSync().pixelRatio canvas.width = width * dpr canvas.height = height * dpr ctx.scale(dpr, dpr) this.ctx = ctx this.width = width this.height = height }, renderVoice(voiceLine) { if (typeof this.ctx === 'undefined') { return } const width = this.width const height = this.height const ctx = this.ctx ctx.clearRect(0, 0, width, height) var len = voiceLine.length, barHeight = 1, q = 0, left = 0 let plantValue = 0, //常见数据 serialValueNum = 0 //连续计数器 console.log(voiceLine); ctx.strokeStyle = 'blue' for (var i = 0; i < len; i++) { if (i == len - 1 && serialValueNum > 9) { //连续5个很罕见 console.log('总长:', len, '常见数据:', plantValue, '干了:', serialValueNum) } if (plantValue != voiceLine[i]) { plantValue = voiceLine[i] serialValueNum = 0 } else { serialValueNum++; } q = i % height left = parseInt(i / height) * 60 + 60 barHeight = (voiceLine[i] / 255) * 60 // 绘制向上的线条 ctx.beginPath(); ctx.moveTo(left, q); ctx.lineTo(left + barHeight, q); ctx.stroke(); } }, renderCanvas() { if (typeof this.ctx === 'undefined') { return } const width = this.width const height = this.height const ctx = this.ctx ctx.clearRect(0, 0, width, height) var len = voiceLine.length, barHeight = 1, t_arr = [] let now_V, min = 100, max = MaxValue - min; ctx.strokeStyle = 'blue' ctx.lineWidth = 3; for (var i = 0; i < len; i++) { now_V = voiceLine[i] == 0 ? 0 : voiceLine[i] - min if (now_V == 0) { ctx.beginPath(); ctx.moveTo(3, i * 8 - 4); ctx.lineTo(3, i * 8 + 4); ctx.stroke(); continue; } t_arr.push(now_V) barHeight = (now_V / max) * 60 // 绘制向上的线条 ctx.beginPath(); ctx.moveTo(0, i * 8); ctx.lineTo(barHeight, i * 8); ctx.stroke(); } t_arr.sort((a, b) => a - b); console.log('Min', t_arr[0], 'Max', t_arr[t_arr.length - 1]) } } }) 原理:说白了,传过来的一串数据,当有大声音时,会突然变长。 当完全静音时,会连续出现85或170这个值。如果是转Int8Array,会有-86这个值。 至于为什么,上面代码解开注释可以观察单个波形。 [图片] 最后成品的案例在:“艺匠人”小程序->新建作品。 感兴趣的童鞋搜一下玩玩吧~
2023-02-07 - 这些 Canvas 小技巧,保证你新年用得上
来自「微信开发者」公众号,作者为微信小程序技术研发工程师binnie。 本文主要介绍了3个隐藏的 Canvas 小技巧: - 绘制并生成图片 - Video 绘制 Canvas / webgl - 视频解码并绘制到 webgl - 录制并导出 webgl 视频 一键加滤镜 快速合成音视频 轻松挑选视频封面 …… Canvas 能够做这些? 作为资深的开发者,相信大家对 Canvas 都不陌生。这项能力在绘制图形方面发挥着极大的作用,高效支持图片编辑、数据可视化等应用场景。但是只局限于一般能力应用,那格局就小了。 Canvas 的应用场景非常丰富!赶紧往下看看这些隐藏的 Canvas 小技巧,保证你新年用得上!还有手把手教程以及文末彩蛋哟。 -- • 绘制并生成图片 • -- [图片] 示例:新年模板长按保存祝福 适用场景:图片分享海报 相关 API:RenderingContext/Canvas/wx.canvasToTempFilePath Step 1: 创建实例获取对象 创建 Canvas 实例,获取 CanvasRenderingContext2D 对象(Canvas 绘图上下文)来绘制形状、文本、图像等。 const query = wx.createSelectorQuery() let canvas = null query.select('#myCanvas') .fields({ node: true, size: true }) .exec((res) => { // 通过 wx.createSelectorQuery 获取到 canvas 实例 canvas = res[0].node // 通过 canvas.getContext('2d') 获取 CanvasRenderingContext2D 对象 const ctx = canvas.getContext('2d') }) Step 2: 设置宽高调整图片 获取 Canvas 绘图上下文后,将 Canvas 的宽高设置为节点宽高 * 设备像素比,绘制出来的图片更清晰 // 获取设备像素比 const dpr = wx.getSystemInfoSync().pixelRatio // 将 canvas 宽高设置为 canvas.width = res[0].width * dpr canvas.height = res[0].height * dpr Step 3: 绘制内容 使用 CanvasRenderingContext2D 绘制,根据业务需要在画布中绘制头像、文字、背景等 // 矩形 ctx.fillStyle = '#FFFFFF' ctx.fillRect(0, 0, canvas.width , canvas.height ) // 图片 var image = canvas.createImage() himage.src = 'https://example.com/example.jpg' headImage.onload = (res) => { ctx.drawImage(himage 0, 0, 32, 32; } // 文本 ctx.font = "18px SimHei"; ctx.textAlgin = "left" ctx.fillStyle = "#07c160"; ctx.fillText("这是我的名字", 0, 0); Step 4: 生成并保存本地 使用 wx.canvasToTempFilePath 将画布生成图片,wx.saveImageToPhotosAlbum 将图片保存到本地。 wx.canvasToTempFilePath({ canvas: canvas, // canvas 实例 success(res) { // canvas 生成图片成功 wx.saveImageToPhotosAlbum({ filePath: res.tempFilePath, success(res) { // 保存成功 } }) } }) -- • Video 绘制 Canvas / webgl • -- [图片] 示例:视频文件绘制 Canvas 适用场景:制作 Video 滤镜、挑选 Video 封面等 相关 API:RenderingContext/Canvas Step 1: 获取实例 通过 wx.createSelectorQuery 获取 VideoContext 实例 let video = null wx.createSelectorQuery().select('#video').context(res => { // 通过 wx.createSelectorQuery 获取 VideoContext 实例 video = res.context; }) Step 2: 绘制内容 获取 VideoContext 实例后,将 VideoContext 传递给 Canvas 进行绘制。开发者根据业务需求选择绘制类型: Canvas 2d 写法:canvas.drawImage(video, ...)webgl 写法:gl.texImage2D(..., video) wx.createSelectorQuery().selectAll('#myCanvas,#webglCanvas').node(res => { const ctx = res[0].node.getContext('2d') const gl = res[1].node.getContext('webgl') setInterval(() => { // canvas 2d // 将 video 纹理对象传入 drawImage 进行绘制 ctx1.drawImage(video, 0, 0, w * dpr, h * dpr); // 添加一个蒙层 ctx1.fillStyle = 'rgba(0, 0, 0, 0.3)' ctx1.fillRect(0, 0, w * dpr, h * dpr); // webgl const render = createRenderer(res[1].node, w, h) render(new Uint8Array(ctx1.getImageData(0, 0, w * dpr, h * dpr).data), w * dpr, h * dpr) }, 1000 / 24) }).exec() function createRenderer(canvas, width, height) { const gl = canvas.getContext("webgl") ... return (arrayBuffer, width, height) => { ... // 指定二维纹理图像 gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, width, height, 0, gl.RGBA, gl.UNSIGNED_BYTE, arrayBuffer) gl.drawElements(gl.TRIANGLES, 6, gl.UNSIGNED_SHORT, 0) } } -- • 视频解码并绘制到 webgl • -- [图片] 示例:视频一键解码并绘制到 webgl 适用场景:添加特效、贴图等视频编辑场景 相关 API:wx.createVideoDecoder/VideoDecoder/RenderingContext/Canvas.requestAnimationFrame/wx.createMediaAudioPlayer/MediaAudioPlayer Step 1: 创建视频解码器进行解码 1. 调用 createVideoDecoder 对视频进行解码 2. 使用 videodecoder.start 启动解码,视频源文件不限制本地或远程路径 3. 通过 videodecoder.on('start', res => {}) 监听解码,通过 videodecoder.getFrameData() 获取到解码数据 // 获取视频解码器 getVideoDecoder(source, abortAudio) { return new Promise((resolve, reject) => { // 创建视频解码器 videodecoder = wx.createVideoDecoder() // 开始解码 videodecoder.start({ abortAudio: abortAudio, source: source, // 视频源文件,支持本地路径&远程路径 mode: 0 // 按pts解码,保证音画同步 }) // 监听解码 开始 videodecoder.on('start', res => { console.log('videodecoder start', res) // 状态初始化 isStop = false resolve(videodecoder) }) // 监听解码 结束 videodecoder.on('ended', res => { // 状态设置为结束,停止画面录制器 isStop = true }) }) }, Step 2: 解码数据绘制到 webgl 1. 通过 gl.texImage2D(..., image) 将解码数据绘制到 webgl 2. 使用 webgl.requestAnimationFrame 继续绘制,效果更加流畅 // 将解码数据绘制到 webgl 中 const query = wx.createSelectorQuery() query.select('#webglCanvas').node().exec((res) => { const webgl = res[0].node const requestAnimationFrame = webgl.requestAnimationFrame; // 初始化webgl let render = null if (!render) { render = createRenderer(webgl, 600, 400) } /** * 绘制视频帧到 canvas */ let i = 1 let loop = () => { // 解码结束,停止循环 if (isStop) { return } // 获取解码数据,绘制到 webgl 中 const imageData = videodecoder.getFrameData() if (imageData) { // render 的高宽需要设置为图片的宽高才可以绘制出来 render(new Uint8Array(imageData.data), imageData.width, imageData.height) } // 继续绘制 console.log('绘制帧数:', i++) requestAnimationFrame(loop) } // 启动录制循环 requestAnimationFrame(loop) }) Step 3: 添加音频播放器同步播放音频 完成 Step2 后,webgl 只有视频播放,缺少音频。因此使用 wx.createMediaAudioPlayer(),支持 addAudioSource 传入 videodecoder,保证视频帧渲染音画同步 /** * 创建媒体音频播放器 */ let mediaAudioPlayer = null let addAudio = () => { if (mediaAudioPlayer) return mediaAudioPlayer = wx.createMediaAudioPlayer() mediaAudioPlayer.start().then(() => { // 添加播放器音频来源 mediaAudioPlayer.addAudioSource(videodecoder).then(res => { console.log('add mediaAudioPlayer: ',) }) }) } // render 绘制视频同时添加音频 render(new Uint8Array(imageData.data), imageData.width, imageData.height) addAudio() -- • 录制并导出 webgl 视频 • -- [图片] 示例:录制并一键导出 webgl 视频 适用场景:将动画、编辑过的视频导出视频文件保存 相关 API:wx.createMediaRecorder/MediaRecorder/wx.createMediaContainer/MediaContainer/MediaTrack Step 1: 创建 webgl 画面录制器进行录制 通过 createMediaRecorder 创建页面录制器,并且绑定 webgl(建议离屏状态,效果更好)进行录制 /** * 获取画面录制器 */ getRecorder() { let canvas = this.getMainCanvasNode() let recorder = wx.createMediaRecorder(canvas, { fps: choosedVideoInfo.fps, // 实际视频的 fps videoBitsPerSecond: choosedVideoInfo.bitrate, // 实际视频的 bitrate gop: 12 }) // 监听录制事件 recorder.on("timeupdate", (res) => { console.log('recorder 录制中,当前时间:', res.currentTime) }) recorder.on("stop", (res) => { console.log('recorder停止') this.saveMedia(res.tempFilePath) }) // 开始录制 recorder.start() this.recorder = recorder return recorder }, // 初始化 画面录制器 并进行录制 await this.initRenderer() this.getDecoder().then((decoder) => { let recorder = this.getRecorder() var self = this function loop() { if (self.stopped) { return } let frameData = decoder.getFrameData() if (!frameData) { console.log('没取到帧') setTimeout(() => { loop() }, 1000/60) } else { self.renderFrame(frameData) recorder.requestFrame(() => { console.log('录制帧数:', i++) loop() }) } } loop() }) Step 2: 添加音频合成音视频 1. 通过 createMediaContainer 创建音视频处理容器来合成音视频 2. 通过 MediaContainer.extractDataSource 将视频源分离出视频轨道和音频轨道,将需要的轨道通过 MediaContainer.addTrack 添加到容器中 3. 通过 MediaContainer.export 导出即可获得合成后的视频文件 /** * 将视频和音频合到一起并保存到本地 * @param {*} videoTempFilePath */ saveMedia(videoTempFilePath) { const self = this let choosedFile = this.choosedFile const MediaContainer = wx.createMediaContainer() // webgl的取视频 MediaContainer.extractDataSource({ source: videoTempFilePath, success(res) { MediaContainer.addTrack(res.tracks[0]) // 源视频取音频 MediaContainer.extractDataSource({ source: choosedFile, success(res) { // 拿到音频轨道并加入到容器 res.tracks[0].kind == 'audio' && MediaContainer.addTrack(res.tracks[0]) res.tracks[1].kind == 'audio' && MediaContainer.addTrack(res.tracks[1]) // 合成视频并导出视频文件 MediaContainer.export({ success(res) { // 保存视频到本地 wx.saveVideoToPhotosAlbum({ filePath: res.tempFilePath, success() { wx.showToast({ title: '导出成功', icon: 'success', duration: 2000 }) self.destroy() } }) } }) } }) } }) }, -- •高效图像处理彩蛋 • -- 学会以上这些 Canvas 小技巧,还担心新年的美图美照美视频处理不过来?赶紧码下这个 Canvas 代码包,保证你就是家里最闪耀的靓女靓仔。 预祝大家新的一年 Canvas 在手,红包一直有!
2022-03-24 - 请问一下注销微信赞赏用户后,赞赏用户的小程序里没有信息了但是公众号也邀请不了新的微信账号作为赞赏?
【公众号】: 我有一篇小作文 【浏览器UA】: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.138 Safari/537.36 NetType/WIFI MicroMessenger/7.0.20.1781(0x6700143B) WindowsWechat(0x63060012) 【页面链接】: https://mp.weixin.qq.com/cgi-bin/home?t=home/index&lang=zh_CN&token=1797172545 【问题描述】: 请描述问题发生时的操作步骤,并最好能附出现问题的截图
2022-04-12 - 小程序链接生成与使用规则调整公告
各位开发者: 为确保小程序链接合理使用,自 2022 年 4 月 11 日起,URL Scheme 和 URL Link (以下统称为 “链接” )接口能力规则将进行以下调整: 每个 URL Scheme 或 URL Link 有效期最长 30 天,均不再支持永久有效的链接、不再区分短期有效链接与长期有效链接;链接生成后,若在微信外打开,用户可以在浏览器页面点击进入小程序。每个独立的链接被用户访问后,仅此用户可以再次访问并打开对应小程序,其他用户无法再次通过相同链接打开该小程序;单个小程序每天生成链接数(URL Scheme 和 URL Link 总数)上限为 50 万条。 对于上述 1,在开发层面,相应的服务端接口 urlscheme.generate 和 urllink.generate 将进行以下调整: is_expire 值固定为 true,可不再传该值,若传值为 false 也与 true 一样会生成到期失效链接;若 expire_type 传值为 0,需注意 expire_time 传值的时间戳不超过 30 天,即该参数最长传值有效期为 30 天;若 expire_type 传值为 1,需注意 expire_interval 传值范围为 [1, 30],即该参数最长传值间隔天数为 30。详细对比见下表: [图片] 已使用该后端接口的开发者可以不进行任何修改,不会出现返回异常。若传值超过新规则合法值,或声明使用永久有效的链接,则均会被赋最长有效期值(30天);需注意以上新规则生效后的有效期和访问规则变化。 在本次规则调整生效前已经生成的链接,也将自动生效以下规则: 如果有效期超过30天或长期会被降级为30天有效,开始时间从调整日期开始计算;在调整生效后,只能被1个用户访问。 当前已使用微信云开发 静态网站H5跳小程序 与 短信跳小程序、微信服务平台短信服务为用户提供链接的功能不受影响,但同样适用以上规则。 微信团队 2022年3月9日 相关QAQ1:每天下发的短信量级超过50万条,不够用怎么办? A1:可将生成 scheme 的时机改为在用户打开 H5 时再生成: [图片]
2023-09-26 - 小程序关联公众号策略调整
各位开发者,大家好。 目前,小程序需要与公众号关联,才可被使用在公众号自定义菜单、模板消息、客服消息等场景中。而公众号关联小程序时,需要小程序管理员确认,该环节增加了开发者之间的沟通成本。 为了降低公众号与小程序间的合作门槛,我们将调整小程序关联公众号策略如下: 公众号关联小程序将无需小程序管理员确认。 取消“小程序最多关联500个公众号”的限制。 若希望小程序在被关联时保留管理员确认环节,可前往“小程序管理后台-设置-基本设置-关联公众号设置”修改设置项。 公众号文章中可直接使用小程序素材,无需关联小程序。 开发者可在“小程序管理后台-设置-关联设置”中管理已关联的公众号。 微信团队 2019.04.04
2019-04-08 - 小程序搜索优化指南(SEO)
2019年上半年微信发布了基于小程序页面的搜索,为了让我们更好地发现及理解小程序的页面,结合过去一段时间来我们遇到的各种情况,我们强烈建议各位开发者花一些宝贵的时间认真阅读本文:) 爬虫访问小程序内页面时,会携带特定的 user-agent "mpcrawler" 及场景值:1129 1. 小程序里跳转的页面 (url) 可被直接打开。 小程序页面内的跳转url是我们爬虫发现页面的重要来源,且搜索引擎召回的结果页面 (url) 是必须能直接打开,不依赖上下文状态的。特别的:建议页面所需的参数都包含在url 2. 页面跳转优先采用navigator组件。 小程序提供了两种页面路由方式: a.navigator 组件 b. 路由 API,包括 navigateTo / redirectTo / switchTab / navigateBack / reLaunch 建议使用 navigator 组件,若不得不使用API,可在爬虫访问时屏蔽针对点击设置的时间锁或变量锁。 3.清晰简洁的页面参数。 结构清晰、简洁、参数有含义的 querystring 对抓取以及后续的分析都有很大帮助,但是将 JSON 数据作为参数的方式是比较糟糕的实现。 4. 必要的时候才请求用户进行授权、登录、绑定手机号等。 建议在必须的时候才要求用户授权(比如阅读文章可以匿名,而发表评论需要留名)。 5. 我们不收录 web-view 中的任何内容。 我们暂时做不到这一点,长期来看,我们可能也做不到。 6. 利用 sitemap 配置引导爬虫抓取,同时屏蔽无搜索价值的路径。 https://developers.weixin.qq.com/miniprogram/dev/framework/sitemap.html 7. 设置一个清晰的标题和页面缩略图。 页面标题和缩略图对于我们理解页面和提高曝光转化有重要的作用。 通过wx.setNavigationBarTitle或 自定义转发内容onShareAppMessage对页面的标题和缩略图设置,另外也为 video、audio 组件补齐 poster /poster-for-crawler属性。 8. 使用页面路径推送能力 可极大丰富微信可以收录的内容,进而提高小程序内容的曝光机会。请参考: https://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/search/search.submitPages.html
2020-01-14 - 小程序模板消息能力调整通知
小程序模板消息能力在帮助小程序实现服务闭环的同时,也存在一些问题,如: 1. 部分开发者在用户无预期或未进行服务的情况下发送与用户无关的消息,对用户产生了骚扰; 2. 模板消息需在用户访问小程序后的 7 天内下发,不能满足部分业务的时间要求。 为提升小程序模板消息能力的使用体验,我们对模板消息的下发条件进行了调整,由用户自主订阅所需消息。 一次性订阅消息 一次性订阅消息用于解决用户使用小程序后,后续服务环节的通知问题。用户自主订阅后,开发者可不限时间地下发一条对应的服务消息;每条消息可单独订阅或退订。 [图片] (一次性订阅示例) 长期性订阅消息 一次性订阅消息可满足小程序的大部分服务场景需求,但线下公共服务领域存在一次性订阅无法满足的场景,如航班延误,需根据航班实时动态来多次发送消息提醒。为便于服务,我们提供了长期性订阅消息,用户订阅一次后,开发者可长期下发多条消息。 目前长期性订阅消息仅向政务民生、医疗、交通、金融、教育等线下公共服务开放,后期将逐步支持到其他线下公共服务业务。 调整计划 小程序订阅消息接口上线后,原先的模板消息接口将停止使用,详情如下: 1. 开发者可登录小程序管理后台开启订阅消息功能,接口开发可参考文档:《小程序订阅消息》 2. 开发者使用订阅消息能力时,需遵循运营规范,不可用奖励或其它形式强制用户订阅,不可下发与用户预期不符或违反国家法律法规的内容。具体可参考文档:《小程序订阅消息接口运营规范》 3. 原有的小程序模板消息接口将于 2020 年 1 月 10 日下线,届时将无法使用此接口发送模板消息,请各位开发者注意及时调整接口。 微信团队 2019.10.12
2019-10-13