- URL Scheme 和 URL Link 优化公告
为了帮助开发者更好地为用户提供服务,降低开发者使用 URL Scheme 和 URL Link 的成本,自 2023 年 12 月 19 日起,微信团队将对 URL Scheme 和 URL Link 进行如下优化: 1、新增明文 URL Scheme,开发者无需调用接口可自行拼接并且生成明文 Scheme; 2、取消 URL Scheme 和 URL Link 打开端一人一链的限制,支持同一条链接被多名用户访问; 3、新增打开端限制:每个小程序每天 URL Scheme 和 URL Link 总打开次数上限为 300 万次。 一、新增明文 URL Scheme开发者无需调用平台接口,可自行根据如下格式拼接 appid 和 path 等参数,作为 URL Scheme 链接。 weixin://dl/business/?appid=*APPID*&path=*PATH*&query=*QUERY*&env_version=*ENV_VERSION* 其中各参数含义如下: [图片] 注意: 1、为保护开发者,通过明文 URL Scheme 拉起的小程序(页面)必须要提前在「小程序管理后台 -> 设置 -> 隐私与安全 -> 明文 scheme 拉起此小程序」中进行声明; 小程序:配置能够通过明文 scheme 进入的小程序页面[图片] 小游戏:打开开关即可通过明文 scheme 拉起小游戏[图片] 2、通过明文 URL Scheme 打开小程序的场景值为 1286; 3、明文 URL Scheme 不受每天 50 万次的生成量限制; 4、明文 URL Scheme 没有有效期的概念,可长期有效; 5、明文 URL Scheme 没有一人一链的打开限制,支持一条链接同时被多名用户访问。 二、原 URL Scheme 升级为加密 URL Scheme,并支持自行拼接参数目前已对外提供的 URL Scheme 能力平滑升级为加密 URL Scheme,取消一人一链的限制,支持开发者自行在链接后拼接参数*CUSTOM PARAMETER*。 注意:之前通过平台接口生成的 URL Scheme 可继续使用,链接自动可支持多人打开。 URL Scheme格式 weixin://dl/business/?t=*TICKET*&cq=*CUSTOM PARAMETER* 其中参数含义如下: [图片] 注意:加密 URL Scheme 打开小程序的场景值保持不变,仍为 1065。 三、原 URL Link 升级为加密 URL Link,并支持自行拼接参数目前已对外提供的 URL Link 能力平滑升级为加密 URL Link,取消一人一链的限制,支持开发者自行在链接后拼接参数*CUSTOM PARAMETER*。 注意:之前通过平台接口生成的 URL Link 可继续使用,链接自动可支持多人打开。 URL Link格式: https://wxaurl.cn/*TICKET*?cq=*CUSTOM PARAMETER* 其中参数含义如下: [图片] 注意:加密 URL Link 打开小程序的场景值保持不变,微信外打开的场景值为 1194;微信内打开会调整为开放标签打开小程序,场景值为 1167。 四、调用规则调整1、加密 URL Scheme 和 URL Link 取消一人一链,支持一条链接同时被多名用户访问,生效后之前生成的链接被多名用户访问时,不会再报错; 2、每个小程序每天能够生成加密 URL Scheme 和 URL Link 共计 50 万条的限制不变,额外增加每个小程序每天在微信外,能够通过链接打开小程序共计 300 万次的打开量限制,其中链接包括加密 URL Scheme、加密 URL Link 和明文 URL Scheme ;若链接打开小程序的次数超过 300 万次/天,则无法通过链接在微信外拉起小程序; 3、URL Scheme (加密和明文)和 URL Link (加密)仅支持非个人主体小程序使用; 4、注意事项:平台有安全策略防止开发者的链接被黑灰产大量打开,可能导致达到访问上限无法正常通过链接打开小程序的问题; 5、查询方式:开发者可复用现有的查询方式对 URL Scheme 和 URL Link 进行打开额度查询和链接状态查询。 [图片]
2023-12-19 - URL Scheme和URL Link在应用场景上有什么区别?
翻阅了文档,发现对于短信外链跳转微信小程序有上述两种实现方式,想请问各位大佬,两者同时存在的原因是什么?两者实现的业务场景有什么不一样? 希望各位大佬能帮忙解答上述的疑问,指导后续对接或开发应该用哪个接口。 https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/url-scheme.html
2022-05-24 - 小程序生成scheme码传参env_version为trial不生效?
https://developers.weixin.qq.com/miniprogram/dev/OpenApiDoc/qrcode-link/url-scheme/generateScheme.html 参数为{"jump_wxa":{"path":"pages/moduleC/myDubDetail/index","query":"id=56765432345s","env_version":"trial"},"is_expire":true,"expire_type":1,"expire_interval":1} 生成的scemeCode 跳转的还是正式版 不是体验版
2023-12-12 - 关于H5跳转小程序签名/invalid url domain问题的处理(成功案例分享!!!)
这两天在搞H5跳转小程序,踩了N次坑,论坛看的解决方案都没有一个完整的说明,Mark一下—— 1、JS安全域名,只需要在小程序关联的公众号(服务号)后台,配置路径【公众号设置》功能设置》JS接口安全域名】 按照提示配置即可 [图片] 2、获取公众号的jsapi_ticket,其中access_token是公众号的【全局唯一接口调用凭据】 调用接口https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token={$access_token}&type=jsapi 3、签名的问题,主要是url [图片] 前端上送:encodeURIComponent(window.location.href.split('#')[0]) 后台:decodeURIComponent(args['url']) 排序拼接字符串signStr,sha1加密 signStr=sha1(signStr) 微信提供了签名校验工具:https://mp.weixin.qq.com/debug/cgi-bin/sandbox?t=jsapisign 4、重点来了 wx.config里面的appId,是公众号的appId(wx...开头的)!!! h5页面dom里面的userName,是要跳转的小程序的原始ID(gh_...开头的)!!! dom里面的path指向小程序的目标页面,index后面带不带.html都可以 我就是因为一直用小程序的jsapi_ticket签名,小程序的appId传给wx.config,导致一直提示invalid url domain!! [图片] [图片]
05-09 - 关于 React 中使用 wx-open-launch-weapp 唤起微信小程序
2021 年 5 月 28 日: 关于此内容,近期有更新。但由于微信社区帖子对 MD 支持度没有简书好,所以本文并没有更新了,请移步看以下两篇文章: 1、关于 React 中使用 wx-open-launch-weapp 唤起微信小程序2、wx-open-launch-weapp 样式问题 最近在做一个中秋国庆活动项目,然后有一个需求是:在 H5 页面中唤起微信小程序。 在前段时间,微信 JSSDK 开放了该接口 [代码]<wx-open-launch-weapp>[代码] ,当然了仅限于微信浏览器内唤起小程序。👉 官方文档在这里。 此功能的开放对象:重要:一定要满足,不然就渲染不出来,就是加了代码,在真机也显示不出来的。 已认证的服务号,服务号绑定“JS接口安全域名”下的网页可使用此标签跳转任意合法合规的小程序。已认证的非个人主体的小程序,使用小程序云开发的静态网站托管绑定的域名下的网页,可以使用此标签跳转任意合法合规的小程序。我遇到的坑就在这里,我一直以为生产环境与测试环境都是用了同一个公众号。后面问了后端的同事才知道,原来测试环境是用了一个私人公众号,不满足上述条件,所以在测试环境捣弄了半天也没弄出来! 😔 版本要求微信 JS-SDK 版本:1.6.0 及以上。 微信版本要求为:7.0.12 及以上。 系统版本要求为:iOS 10.3 及以上、Android 5.0 及以上。 wx.config注意标签,别弄错了! [代码]<wx-open-launch-app>[代码] 打开 APP 标签[代码]<wx-open-launch-weapp>[代码] 打开微信小程序标签wx.config({ debug: true, // 开启调试模式,调用的所有api的返回值会在客户端alert出来,若要查看传入的参数,可以在pc端打开,参数信息会通过log打出,仅在pc端时才会打印 appId: '', // 必填,公众号的唯一标识 timestamp: '', // 必填,生成签名的时间戳 nonceStr: '', // 必填,生成签名的随机串 signature: '',// 必填,签名 jsApiList: [], // 必填,需要使用的JS接口列表 openTagList: [ 'wx-open-launch-weapp' ] // 可选,需要使用的开放标签列表 }) *这里我看过某个帖子说,即使不用 [代码]jsApiList: [][代码] 也要配置一项,[代码]wx.config()[代码] 才能生效。因为我项目本身就有使用它,所以我没有去验证。可通过 [代码]debug: true[代码] 来验证 [代码]wx.config()[代码] 是否成功。 前提是这个配置成功了,后续的功能才能实现。 <wx-open-launch-weapp>属性注意一下: username(必填):所需跳转的小程序原始 id,即小程序对应的以 gh_ 开头的 id。(非小程序 APPID)path(非必填):所需跳转的小程序内页面路径及参数。( 对于[代码]path[代码]属性,所声明的页面路径必须添加[代码].html[代码]后缀,如[代码]pages/index/index.html[代码]。以下是使用 React 的写法,其他的框架或库我没特意去写过,可以参考 👉 官方文档。但看官方文档要带一双慧眼,开放初期官方示例似乎语法有错误,现在似乎修复了,反正认真检查一下就好了。 React 中不支持直接写 [代码]<template />[代码] 标签,需要使用 [代码]<script type="text/wxtag-template />[代码] 替换。或者考虑使用 dangerouslySetInnerHTML。 关于样式上问题,需要在标签上写样式才有效,按照官方的示例似乎有问题。 相关问题或帖子react 里面 h5 wx-open-launch-weapp 跳转小程序按钮样式无法设置?公众号跳转 H5react 里面 h5 wx-open-launch-weapp 不显示,是什么问题?
2023-06-14 - 微信公众号网页JS-SDK使用wx.launchMiniProgram跳转小程序,提示没有权限?
在进行微信公众号网页开发时,有一个要跳转到微信小程序的功能,我看微信的JS-SDK里存在launchMiniProgram这个功能:[图片]我就想用这个API来实现,我在wx.config里的jsApiList也写了这个API名字[图片], 但是在运行时提示launchMiniProgram没有权限:[图片], 配置里写的另外两个API都有权限:[图片],我想问一下launchMiniProgram这个API是有什么使用条件吗?还是说只能用开放标签来实现跳转?
2022-08-13 - 小程序webview中H5打开其他小程序页面失败?
小程序webview中的H5页面调用navigateTo打开其他小程序页面,在有些情况下会失败,报错信息:invokeMiniProgramAPI:can not run in current browser environment是什么原因呢?
2023-07-26 - 小程序在web-view中跳转小程序,提示“当前小程序无法打开***小程序”
每个小程序可跳转的其他小程序数量限制为不超过10个,在web-view中打开的小程序也包含在这个限制中。 从 基础库2.4.0 版本以及指定日期(参考公告)开始,开发者提交新版小程序代码时,如使用了跳转其他小程序功能,则需要在代码配置中声明将要跳转的小程序名单,限定不超过 10 个,否则将无法通过审核。该名单可在发布新版时更新,不支持动态修改。配置方法详见 小程序全局配置。
2019-11-11 - 如何在A小程序中打开了H5页面,在H5页面中再打开B小程序?
在H5页面中,打开B小程序是可以的,但是在A小程序的容器里就不行。有什么方法可以实现嘛? [图片]
03-06 - 手机息屏后,小程序webview内嵌audio播放音频暂停播放问题解决方案
手机息屏后,小程序webview内嵌audio播放音频暂停播放问题解决方案 背景 这个问题其实是去年(2023年)年中遇到的一个问题,当时在社区也翻了不少帖子,但是没有一个写到具体的解决方案 我实际呢,后来是有解决这个问题,也没有来社区反馈给大家,前几天有朋友问起来,今天把解决方案补一下 现象我们做的一款产品是音频播放的产品,由于某些原因,只能使用webview audio来播放,但在具体开发时遇到了以下问题 [图片] ~ 解决方案[图片] ~ 其实我们解决的问题是息屏后播放暂停了,所以在息屏的时候,我们要重新调用audio的播放play()函数,这个方案在实践中是能解决目前问题的
02-19 - 如何实现一个自定义导航栏
自定义导航栏在刚出的时候已经有很多实现方案了,但是还有大哥在问,那这里再贴下代码及原理: 首先在App.js的 onLaunch中获取当前手机机型头部状态栏的高度,单位为px,存在内存中,操作如下: [代码]onLaunch() { wx.getSystemInfo({ success: (res) => { this.globalData.statusBarHeight = res.statusBarHeight this.globalData.titleBarHeight = wx.getMenuButtonBoundingClientRect().bottom + wx.getMenuButtonBoundingClientRect().top - (res.statusBarHeight * 2) }, failure() { this.globalData.statusBarHeight = 0 this.globalData.titleBarHeight = 0 } }) } [代码] 然后需要在目录下新建个components文件夹,里面存放此次需要演示的文件 navigateTitle WXML 文件如下: [代码]<view class="navigate-container"> <view style="height:{{statusBarHeight}}px"></view> <view class="navigate-bar" style="height:{{titleBarHeight}}px"> <view class="navigate-icon"> <navigator class="navigator-back" open-type="navigateBack" wx:if="{{!isShowHome}}" /> <navigator class="navigator-home" open-type="switchTab" url="/pages/index/index" wx:else /> </view> <view class="navigate-title">{{title}}</view> <view class="navigate-icon"></view> </view> </view> <view class="navigate-line" style="height: {{statusBarHeight + titleBarHeight}}px; width: 100%;"></view> [代码] WXSS文件如下: [代码].navigate-container { position: fixed; top: 0; width: 100%; z-index: 9999; background: #FFF; } .navigate-bar { width: 100%; display: flex; justify-content: space-around; } .navigate-icon { width: 100rpx; height: 100rpx; display: flex; justify-content: space-around; } .navigate-title { width: 550rpx; text-align: center; line-height: 100rpx; font-size: 34rpx; color: #3c3c3c; font-weight: bold; text-overflow: ellipsis; overflow: hidden; white-space: nowrap; } /*箭头部分*/ .navigator-back { width: 36rpx; height: 36rpx; align-self: center; } .navigator-back:after { content: ''; display: block; width: 22rpx; height: 22rpx; border-right: 4rpx solid #000; border-top: 4rpx solid #000; transform: rotate(225deg); } .navigator-home { width: 56rpx; height: 56rpx; background: url(https://qiniu-image.qtshe.com/20190301home.png) no-repeat center center; background-size: 100% 100%; align-self: center; } [代码] JS如下: [代码]var app = getApp() Component({ data: { statusBarHeight: '', titleBarHeight: '', isShowHome: false }, properties: { //属性值可以在组件使用时指定 title: { type: String, value: '青团公益' } }, pageLifetimes: { // 组件所在页面的生命周期函数 show() { let pageContext = getCurrentPages() if (pageContext.length > 1) { this.setData({ isShowHome: false }) } else { this.setData({ isShowHome: true }) } } }, attached() { this.setData({ statusBarHeight: app.globalData.statusBarHeight, titleBarHeight: app.globalData.titleBarHeight }) }, methods: {} }) [代码] JSON如下: [代码]{ "component": true } [代码] 如何引用? 需要引用的页面JSON里配置: [代码]"navigationStyle": "custom", "usingComponents": { "navigate-title": "/pages/components/navigateTitle/index" } [代码] WXML [代码]<navigate-title title="青团社" /> [代码] 按上面步骤操作即可实现一个自定义的导航栏。 如何实现通栏的效果默认透明以及滚动更换title为白色背景,如下图所示: [图片] [图片] [图片] [图片] 最后代码片段如下: https://developers.weixin.qq.com/s/wi6Pglmv7s8P。 以下为收集到的社区老哥们的分享: @Yunior: 小程序顶部自定义导航组件实现原理及坑分享 @志军: 微信小程序自定义导航栏组件(完美适配所有手机),可自定义实现任何你想要的功能 @✨o0o有脾气的酸奶💤 [有点炫]自定义navigate+分包+自定义tabbar @安晓苏 分享一个自适应的自定义导航栏组件
2020-03-10 - 教你怎么监听小程序的返回键
更新:2020年7月28日08:51:11 基础库2.12.0起,可以调用wx.enableAlertBeforeUnload监听原生右上角返回、物理返回以及wx.navigateBack时弹框提示 AIP详情请看: https://developers.weixin.qq.com/miniprogram/dev/api/ui/interaction/wx.enableAlertBeforeUnload.html //======================================== 怎么监听小程序的返回键? 应该有很多人想要监听用户的这个动作吧,但是很遗憾,小程序不会给你这个API的,那是不是就没辙了? 幸好我们还可以自定义导航栏,这样一来我们就可以监听用户的这一动作了。 什么?这你已经知道啦? 那好咱们就不说自定义导航栏的返回监听了,说一下物理返回和左滑?右滑?(不管了,反正是滑)返回上一页怎么监听。 监听物理返回 首先说一下这个监听方法的缺点,虽说是监听,但是还是无法真正意义上的监听并拦截来阻止页面跳转,页面还是会返回上一页,而后重新载入刚刚的页面,如果这不是你想要的,那可以不用往下看了 其次说一下用到什么东西: wx.onAppRoute、wx.showModal 最后是一些主要代码: 重写wx.showModal,主要是加个confirmStay参数和使wx.showModal Promise化 [代码]const { showModal } = wx; Object.defineProperty(wx, 'showModal', { configurable: false, // 是否可以配置 enumerable: false, // 是否可迭代 writable: false, // 是否可重写 value(...param) { return new Promise(function (rs, rj) { let { success, fail, complete, confirmStay } = param[0] param[0].success = (res) => { res.navBack = (res.confirm && !confirmStay) || (res.cancel && confirmStay) wx.setStorageSync('showBackModal', !res.navBack) success && success(res) rs(res) } param[0].fail = (res) => { fail && fail(res) rj(res) } param[0].complete = (res) => { complete && complete(res) (res.confirm || res.cancel) ? rs(res) : rj(res) } return showModal.apply(this, param); // 原样移交函数参数和this }.bind(this)) } }); [代码] 使用wx.onAppRoute实现返回原来的页面 [代码]wx.onAppRoute(function (res) { var a = getApp(), ps = getCurrentPages(), t = ps[ps.length - 1], b = a && a.globalData && a.globalData.pageBeforeBacks || {}, c = a && a.globalData && a.globalData.lastPage || {} if (res.openType == 'navigateBack') { var showBackModal = wx.getStorageSync('showBackModal') if (c.route && showBackModal && typeof b[c.route] == 'function') { wx.navigateTo({ url: '/' + c.route + '?useCache=1', }) b[c.route]().then(res => { if (res.navBack){ a.globalData.pageBeforeBacks = {} wx.navigateBack({ delta: 1 }) } }) } } else if (res.openType == 'navigateTo' || res.openType == 'redirectTo') { if (!a.hasOwnProperty('globalData')) a.globalData = {} if (!a.globalData.hasOwnProperty('lastPage')) a.globalData.lastPage = {} if (!a.globalData.hasOwnProperty('pageBeforeBacks')) a.globalData.pageBeforeBacks = {} if (ps.length >= 2 && t.onBeforeBack && typeof t.onBeforeBack == 'function') { let { onUnload } = t wx.setStorageSync('showBackModal', !0) t.onUnload = function () { a.globalData.lastPage = { route: t.route, data: t.data } onUnload() } } t.onBeforeBack && typeof t.onBeforeBack == 'function' && (a.globalData.pageBeforeBacks[t.route] = t.onBeforeBack) } }) [代码] 改造Page [代码]const myPage = Page Page = function(e){ let { onLoad, onShow, onUnload } = e e.onLoad = (() => { return function (res) { this.app = getApp() this.app.globalData = this.app.globalData || {} let reinit = () => { if (this.app.globalData.lastPage && this.app.globalData.lastPage.route == this.route) { this.app.globalData.lastPage.data && this.setData(this.app.globalData.lastPage.data) Object.assign(this, this.app.globalData.lastPage.syncProps || {}) } } this.useCache = res.useCache res.useCache ? reinit() : (onLoad && onLoad.call(this, res)) } })() e.onShow = (() => { return function (res) { !this.useCache && onShow && onShow.call(this, res) } })() e.onUnload = (() => { return function (res) { this.app.globalData = Object.assign(this.app.globalData || {}, { lastPage: this }) onUnload && onUnload.call(this, res) } })() return myPage.call(this, e) } [代码] 在需要监听的页面加个onBeforeBack方法,方法返回Promise化的wx.showModal [代码]onBeforeBack: function () { return wx.showModal({ title: '提示', content: '信息尚未保存,确定要返回吗?', confirmStay: !1 //结合content意思,点击确定按钮,是否留在原来页面,confirmStay默认false }) } [代码] 运行测试,Oj8K 是不是很简单,马上去试试水吧,效果图就不放了,静态图也看不出效果,动态图懒得弄,想看效果的自己运行代码片段吧 代码片段 https://developers.weixin.qq.com/s/hc2tyrmw79hg
2020-07-28 - 如何在小程序中快速实现环形进度条(2)
前不久在社区里发布了第一个版本的环形进度条小工具https://developers.weixin.qq.com/community/develop/article/doc/000e4e66d4c210ae234a10a855ac13 本次针对上一个版本遗留的问题进行修复,还增加增加了全新功能 1、修复圆环半径计算精度的问题 梳理了工具中所有计算单位,严格按照小程序规则进行转换 2、draw增加兜底逻辑,有效值控制在0%-100% 在调用draw方法时,有可能会传入不合法参数,或者参数不在常规有效范围内,工具则会自动转换,比如传入了-10,那对应生效的值是0,如果传入了110,对应生效的值是100 3、进度点的阴影可以自行控制 // 如果dotstyle数据项中包含shadow,则会在这个进度点生成对应颜色的阴影效果 dotStyle: [{r: 24, fillStyle: '#fff', shadow: 'rgba(0,0,0,.15)'}, {r: 10, fillStyle: '#56B37F'}] 4、【重要新功能】增加按百分比显示圆环的功能,有效值控制在50%-100% 在社区当中存在比较多的开发着有按比例显示进度条总弧度的需求,比如 [图片] 本期针对这类需求,新增参数percent,当在初始化是传入参数percent(有效值是50~100,意思是可以显示半弧至完整闭环弧) const mprogress = new MpProgress({ canvasId: 'progress', canvasSize: { width: 400, height: 400 }, percent: 80, barStyle: [{width: 12, fillStyle: '#f0f0f0'}, {width: 12, fillStyle: [{position: 0, color: '#56B37F'}, {position: 1, color: '#c0e674'}]}], }); mprogress.draw(25); 如代码所示,显示圆环的总弧度为360*80%=288,同时工具会根据当前是否需要显示进度点或者进度点是否有shadow的情况自动生产最大的能填满canvas的弧度,以上代码显示效果如下: [图片] 希望能帮助到你~ 喜欢的给个start吧,感谢 https://github.com/lucaszhu2zgf/mp-progress 快速安装:npm install mp-progress --save
2020-06-15 - fileSystemManager.saveFile保存的文件在哪里能找到?
场景是:在小程序中下载docx,并且客户需要拿到这个文件,后续去转发或者打印。 现在找不到这个文件。 请问需要怎么做才能找到这个文件。
2019-09-11 - 云开发之音频列表播放
说明 音频播放列表中播放云开发中存储的音频文件,可以使用fileid,直接播放。 错误实现方式 在xml文件中添加<audio>组件,src为云存储文件的fileid来实现音频播放,报错Uncaught (in promise) NotSupportedError: The element has no supported sources.,截图如下: [图片] 原因为该组件src只能设置具体地址,该组件无法解析云文件的fileid到详情里的地址。 解决办法:使用wx.createInnerAudioContext()创建内部 audio 上下文 InnerAudioContext 对象,xml无需写<audio>组件,通过innerAudioContext.src设置为获取到的fileid。 遇到的问题:因为切换音频列表中的任意音频播放(当前音频正在播放中),而生成的音频实例只有一个,所以播放暂停等操作都是在同一个音频实例上,导致不暂停当前播放直接播放另一个音频还会继续播放前一段音频。 解决办法:定义一个全局InnerAudioContext 对象,在播放方法中首先销毁掉原来的对象并重新生成一个,之后再设置src并play()。注意:需在可播放状态监听事件里play()。暂停方法中则执行pause()。 完善:这样的话暂停当前选中音频,再重新点击播放会从头开始播放。暂停时设置一个变量preIndex存当前选中index,播放时判断当前选中index是否为preIndex,若不是则代表切换到了另一个音频,销毁当前InnerAudioContext 对象并重新生成一个。否则继续播放这个对象。当播放完时可以添加监听方法,改变播放状态。 部分代码: [代码]var innerAudioContext = wx.createInnerAudioContext(); Page({ data:{ voiceList:[],//音频列表,fileid preIndex:-1,//正在播放中的音频index }, //播放 play: function(e) { var that = this; //获取下一个播放的音频index var index = e.currentTarget.dataset.index; //正在播放的音频 var preIndex = that.data.preIndex; if (index != preIndex) { innerAudioContext.destroy(); innerAudioContext = wx.createInnerAudioContext(); innerAudioContext.src = voiceList[index].fileId; innerAudioContext.onCanplay(function() { innerAudioContext.play(); }) } else { innerAudioContext.play(); } }, //暂停 pause: function() { var that = this; innerAudioContext.pause(); that.setData({ preIndex:that.data.selectIndex }) } }) [代码]
2019-03-07 - innerAudioContext在对src重新赋值之后部分机型不能正常播放
- 当前 Bug 的表现(可附上截图) - 预期表现 - 复现路径 - 提供一个最简复现 Demo 在page外面定义了一个 const myaudio = wx.createInnerAudioContext(); 然后在onload里通过后台接口拿到音频的地址通过setData赋值给src。播放这个音频是在点击播放按钮之后出发的事件,这个事件里每次都会myAudio.src=that.data.src。 但是这样给音频的src赋值之后,在部分机型上就不能正常播放音频了,为什么??如果一开始就把src写死了是没问题的。可是写死是不现实的啊。 这个问题该怎么解决呀。
2019-03-18 - 小程序顶部自定义导航组件实现原理及坑分享
为什么使用自定义导航 对比默认导航栏,我们会更需要: 统一Android、IOS手机对于页面title的展示样式及位置 更丰富的导航栏定制效果,如添加home图标等 左上角返回事件的监听处理 统一实现双击返回顶部功能 自定义导航组件实现思路 自定义导航组件实现的核心是需要计算导航栏的真实高度 这里以官方文档->扩展能力中的Navigation组件为例分析实现思路。当使用"navigationStyle": "custom"时,默认导航被移除,页面的开始位置变成了屏幕顶部,这时我们需要实现的导航栏是在状态栏下面。 导航栏的真实高度=状态栏高度+导航栏内容。 [图片] 使用wx.getSystemInfo获取到statusBarHeight便是导航栏的高度,但是导航栏内容高度呢? 有人可能觉得导航栏内容高度顾名思义就是导航栏内容高度啊,内容撑起还用管嘛!要,必须要! 因为右上角胶囊按钮是原生加载的,我们的导航栏内容需要正好贴在胶囊的下方且垂直居中。 导航栏内容高度=(胶囊按钮的顶部距离 - 状态高度)*2 + 胶囊高度 [图片] 如何计算胶囊的数据呢?幸运的是我们有 wx.getMenuButtonBoundingClientRect() 获取胶囊按钮的布局位置信息,那么动态计算导航栏的内容高度就很方便啦。 好了,以上就是动态计算的核心思路,我们再来看官方Navigation组件高度是怎么实现的 [代码]page{--height:44px;--right:190rpx;} .weui-navigation-bar .android{--height:48px;--right:222rpx} .weui-navigation-bar__inner{ position:fixed;top:0;left:0;z-index:5001;display:flex;align-items:center; height:var(--height);padding-right:var(--right);width:calc(100% - var(--right)) } [代码] 导航栏内容的高度是通过- -height这个css变量提前声明好的,安卓机型会重新覆盖为新的css变量值,目前没发现有适配问题。 官方就是官方啊,具体尺寸都知道,那就不用一番计算周折啦,直接拿来主义即可。 导航的布局位置已经搞定啦,剩下就是写具体的内容,不同业务实现需求不同这里就不一一赘述了。 完善官方顶部导航组件 本着拿来主义,直接使用官方Navigation组件,但在实际业务开发中还是遇到不少需要自定义的需求,就比如: loadding样式没实现 标题内容超出没有出现省略号 和原生顶部的样式不兼容,导致单个页面引入时跳转有明显差异出现 没有双击返回顶部功能开关功能 引入页面需要获取导航栏的高度,来控制其他元素距离顶部的位置, 不能根据页面栈数据动态显示隐藏back按钮, 针对以上需求,我们对官方的组件进行二次完善开发,满足常规的自定义需求绰绰有余,直接引入开箱即用。 源码使用示例 https://github.com/YuniorZen/minicode-debug/tree/master/minicode02 [图片] 使用说明 [代码]/*自定义头部导航组件,基于官方组件Navigation开发。*/ <navigation-bar title="会员中心" bindgetBarInfo="getBarInfo"></navigation-bar> [代码] 组件属性列表 属性 类型 描述 bindgetBarInfo function 组件实例载入页面时触发此事件,首参为event对象,event.detail携带当前导航栏信息,如导航栏高度 event.detail.topBarHeight bindback function 点击back按钮触发此事件响应函数 backImage string back按钮的图标地址 homeImage string home按钮的图标地址 ext-class string 添加在组件内部结构的class,可用于修改组件内部的样式 title string 导航标题,如果不提供为空 background string 导航背景色,默认#ffffff color string 导航字体颜色 dbclickBackTop boolean 是否开启双击返回顶部功能,默认true border boolean 是否显示顶部导航下边框分割线,默认false loading boolean 是否显示标题左侧的loading,默认false show boolean 显示隐藏导航,隐藏的时候navigation的高度占位还在,默认true left boolean 左侧区域是否使用slot内容,默认false center boolean 中间区域是否使用slot内容,默认false Slot name 描述 left 左侧slot,在back按钮位置显示,当left属性为true的时候有效 center 标题slot,在标题位置显示,当center属性为true的时候有效 自定义顶部导航目前存在的坑 弹窗的背景蒙层无法覆盖原生胶囊按钮 页面下拉刷新的圆点会被自定义导航遮盖 如果要自定义顶部导航,以上问题避免不了,只能忍着接受。 目前还没遇到完美的解决方案,针对下拉刷新圆点被遮挡的问题微信官方还在需求开发中,如果你有好的想法欢迎留言反馈,一起学习交流。
2019-10-31 - 模块化——加速小程序开发
阅读基础:有小程序项目经验,有查阅官方文档习惯的小伙伴 随着公司小程序项目日益繁多,仅仅靠着官方提供的框架、组件、API,已经远远不能满足项目高效迭代的要求了,于是我们组内萌生了对小程序进行模块化的想法。 实际项目中我们对小程序模块化已经涉及各个模块,我总结一下,从三个方向跟大家分享我们不一样的模块化思路:[代码]Page+[代码],[代码]basePage[代码],[代码]适配层[代码]。 Page+:赋予页面更多的功能 [代码]Page()[代码]作为页面的入口,我们可以通过对其入参对象的封装实现:生命周期的改造、全局状态管理和新增页面功能。 官方删除了小程序分享回调 complete,一起来尝试将其恢复吧。一般我们的逻辑是这样的: [代码]// pages/index/index.js Page({ // 数据初始化 data: { shareFlag: false, //页面是否处于分享中 shareComplete: false //分享回调事件 }, // onShow 生命周期 onShow: function () { const { shareFlag, shareComplete } = this.data if( shareFlag ){ this.data.shareFlag = false //变量不涉及页面渲染,不使用 setData shareComplete && shareComplete() } }, // 分享事件 onShareAppMessage: function () { let shareInfo = { title: '分享测试标题', path: '', complete: function () { console.log('页面分享成功啦~') } } this.data.shareFlag = true this.data.shareComplete = typeof (shareInfo.complete) == 'function' ? shareInfo.complete : false return shareInfo } }) [代码] 在单页面内实现分享回调这样操作是可行的,如果多页面、多项目都要实现该功能,重复拷贝代码,则显格外得繁琐。 我们来将这个功能抽离封装一下吧。 [代码]// pages/index/index.js import PagePlus from './pagePlus.js' PagePlus({ // 分享事件 onShareAppMessage: function () { return { title: '分享测试标题', path: '', complete: function () { console.log('页面分享成功啦~') } } } }) [代码] [代码]// pages/index/pagePlus.js const PagePlus = (pageObj) => { const _onShow = pageObj.onShow, _onShareAppMessage = pageObj.onShareAppMessage, _data = { shareStatus: false, //页面是否处于分享中 shareComplete: false //分享回调事件 } Object.assign(_data, pageObj.data) delete pageObj.data pageObj.onShow = function () { typeof _onShow == 'function' && _onShow.apply(this) const { shareStatus, shareComplete } = this.data if (shareStatus) { this.data.shareStatus = false //变量不涉及页面渲染,不使用 setData shareComplete && shareComplete() } } pageObj.onShareAppMessage = function () { const shareInfo = typeof _onShareAppMessage == 'function' && _onShareAppMessage.apply(this) this.data.shareStatus = true shareInfo && (this.data.shareComplete = shareInfo.complete) return shareInfo } Page({ data: _data, ...pageObj }) } export default PagePlus [代码] 我们来增加一个新的生命周期回调——[代码]onReshow[代码](页面非首次显示回调,常用于详情页操作影响上一页列表数据的场景)。 [代码]// pages/index/index.js import PagePlus from './pagePlus.js' PagePlus({ // 监听页面非首次显示 onReshow: function(){ console.log('onReshow lifeCallBack') }, onShareAppMessage: function () { return { title: '分享测试标题', path: '', complete: function () { console.log('页面分享成功啦~') } } } }) [代码] [代码]// pages/index/pagePlus.js class BasePage{ data = { pagePlus: { shareStatus: false, //页面是否处于分享中 shareComplete: false, //分享回调事件 firstEnter: true //第一次进入页面 } } methods = { onShow: this.onShow, onShareAppMessage: this.onShareAppMessage, onReshow: this.onReshow } onShow(){ const { shareStatus, shareComplete, firstEnter } = this.data.pagePlus if (firstEnter) { this.data.pagePlus.firstEnter = false } else { this.onReshow() } if (shareStatus) { this.data.pagePlus.shareStatus = false shareComplete && shareComplete() } } onShareAppMessage(shareInfo){ this.data.pagePlus.shareStatus = true shareInfo && (this.data.pagePlus.shareComplete = shareInfo.complete) } } const PagePlus = (pageObj) => { const basePage = new BasePage() for (var i in basePage.methods) { basePage.methods[i] = (() => { const key = i const _temFn = basePage.methods[key] return function () { if (key == 'onShareAppMessage') { const shareInfo = typeof pageObj[key] == 'function' && pageObj[key].apply(this, arguments) _temFn.apply(this, [shareInfo]) return shareInfo } typeof pageObj[key] == 'function' && pageObj[key].apply(this, arguments) typeof _temFn == 'function' && _temFn.apply(this, arguments) } })() } Object.assign(basePage.data, pageObj.data) delete pageObj.data Page({ data: basePage.data, ...pageObj, ...basePage.methods }) } export default PagePlus [代码] 自此,我们修改了原生的生命周期回调和增加了新的生命周期回调。当然我们还能为 Page+ 赋予更多的功能,例如: [代码]页面刷新[代码]:下拉自动刷新当前页。 [代码]定时器自动清除[代码]:离开页面时,自动清除页面执行的定时器。 [代码]全局状态管理[代码]:页面间数据共享,相关数据关联的组件即时渲染更新。 相关的代码实现,大家可以自己思考一下怎么实现;我的实现细节,如果大家感兴趣的话就在下方给我留言吧,你们的回复是我更新的动力哦。 basePage:公共 Component 管理器 小程序页面彼此独立,使用 Component 都需要各自引用,为了实现页面公共 Component 的统一管理,这个时候就可以引入 basePage 的概念:以 basePage 作为父组件,其他公共 Component 作为子组件,页面通过 basePage 对公共 Component 进行管理。 实现原理 1、定义一个 Component ,作为 basePage 。 2、每个页面统一引用 basePage ,且规定页面的元素都需要写到 <basePage/> 标签内部 。 3、通过 basePage 引用页面公共的 Component ,并进行业务逻辑编辑。 实现细节 实际使用过程中,我发现有两个问题: 1、Page 和 basePage 通信是非常频繁的,需要通过 WXML 数据绑定和 triggerEvent 触发事件,略显麻烦。 2、setTimeout、webSocket 等后台进程,可能触发[代码]非当前显示页面[代码]的渲染更新,而绝大部分情况,我们只需要[代码]当前显示页面[代码]的渲染更新。 针对这两种场景的优化,我们可以把当前显示页面的 basePage 实例对象赋值到 global 的某个具体变量;每当 Page 触发 show 生命周期回调的时候,我们就对这个变量赋值的实例对象进行更新,这样我们就可以通过 global 的变量直接操作当前显示页面的 basePage 了。 部分代码示例 [代码]{ "文件路径": "pages/index/index.json", "usingComponents": { "basePage": "../../components/basePage/index" } } [代码] [代码]<!--pages/index/index.wxml--> <basePage> <!-- 页面元素 --> </basePage> [代码] [代码]// components/basePage/index.js Component({ /** * Component 所在页面的生命周期函数 */ pageLifetimes: { show: function () { global.basePage = this }, hide: function () { global.basePage = null } } }) [代码] [代码]{ "文件路径": "components/basePage/index.json", "说明": "在此处统一引入页面公共的 Component", "component": true, "usingComponents": {} } [代码] [代码]<!--components/basePage/index.wxml--> <slot /> [代码] 适配层:让代码适应更多的场景 如果你的项目对代码后续维护、迭代和可移植性有较高需求,或者需要多项目并行,这个时候通过适配层去调用各个功能模块就显得尤为重要。适配层方面我做的还是比较粗糙的,如果有建议欢迎指出。 适配层的时机 项目不是 bugfix 级别的迭代,都有适配层设计的必要。 如果是[代码]新项目[代码],心底不认为自己是“咸鱼”而是代码的“亲爹”,[代码]适配层完全可以作为标配[代码]去实现;这就是展现自己对代码全局观的时候了,把自己对代码的理解都用适配层去诠释吧。 如果是[代码]旧项目迭代[代码],在项目排期允许的情况下,尽可能理解原代码的基本实现细节;对比新的项目是要束手束脚一些,适配层的设计要在[代码]尽可能少改变原有代码[代码]的情况下进行;如果排期比紧急,适配层的完整实现[代码]可以在几个版本迭代中逐步实现[代码]。 模块设计必须高内聚低耦合 如果功能模块的设计过于松散、耦合复杂,这就意味着适配层将需要做各种兼容,这和适配层设计的初衷背道而驰,不做也罢。 配置文件 如果你的代码有移植性要求,为这些不同环境准备对应的配置文件吧,配置文件可以通过自制脚手架实现,也可以粗暴地手动替换,在保证尽可能不出错的情况下实现即可。 功能模块的入口 所有整合的功能模块都需要通过适配层进行调用,适配层就是你的“王之财宝”。 规范 && 文档 适配层是从代码的全局考虑,如果是项目是分工完成,项目的开发人员都需要遵守适配层规范进行代码开发;文档我一直都认为都是非常必要的,但还是经常会懈怠,没有进行完整的文档编写,但我基本会在所有项目成员都能理解适配层的情况下,进行简单的口头说明。 因为开心说一些废话 一次需求迭代中,几乎涉及手头上的所有小程序项目;刚好就在需求前的半个月,我们小组完成了对所有项目模块化改造;虽然需求来得很急,我们还是很完美的实现了。毕竟[代码]模块化之前,每个项目的改造都是独立的工作量;模块化之后,就只有适配层迭代的工作量了[代码]。不过真是辛苦了测试小伙伴,因为对所有项目进行模块化改造,意味着测试小伙伴对所有项目进行回归测试,感谢测试小伙伴,比心! 这篇文章,对 Page+ 的具体实现展示比较详细,感觉对 basePage 和适配层讲的都比较偏概念。毕竟这部分内容都和业务逻辑联系比较紧密,很难抽象深入讲解。刚好还有假期还有一段时间,如果自己还有时间就再写一篇关于最近项目的模块化剖析吧,哈哈。
2019-10-09 - 如何监听小程序中的手势事件(缩放、双击、长按、滑动、拖拽)
mina-touch [图片] [代码]mina-touch[代码],一个方便、轻量的 小程序 手势事件监听库 事件库部分逻辑参考[代码]alloyFinger[代码],在此做出声明和感谢 change log: 2019.03.10 优化监听和绘制逻辑,动画不卡顿 2019.03.12 修复第二次之后缩放闪烁的 bug,pinch 添加 singleZoom 参数 2020.12.13 更名 mina-touch 2020.12.27 上传 npm 库;优化使用方式;优化 README 支持的事件 支持 pinch 缩放 支持 rotate 旋转 支持 pressMove 拖拽 支持 doubleTap 双击 支持 swipe 滑动 支持 longTap 长按 支持 tap 按 支持 singleTap 单击 扫码体验 [图片] demo 展示 demo1:监听 pressMove 拖拽 手势 查看 demo 代码 [图片] [图片] demo2: 监听 pinch 缩放 和 rotate 旋转 手势 (已优化动画卡顿 bug) 查看 demo 代码 [图片] [图片] demo3: 测试监听双击事件 查看 demo 代码 [图片] [图片] demo4: 测试监听长按事件 查看 demo 代码 [图片] [图片] demo 代码 demo 代码地址 mina-tools-client/mina-touch 使用方法 大致可以分为 4 步: npm 安装 mina-touch,开发工具构建 npm 引入 mina-touch onload 实例化 mina-touch wxml 绑定实例 命令行 [代码]npm install mina-touch[代码] 安装完成后,开发工具构建 npm *.js [代码]import MinaTouch from 'mina-touch'; // 1. 引入mina-touch Page({ onLoad: function (options) { // 2. onload实例化mina-touch //会创建this.touch1指向实例对象 new MinaTouch(this, 'touch1', { // 监听事件的回调:multipointStart,doubleTap,longTap,pinch,pressMove,swipe等等 // 具体使用和参数请查看github-README(底部有github地址 }); }, }); [代码] NOTE: 多类型事件监听触发 setData 时,建议把数据合并,在 touchMove 中一起进行 setData ,以减少短时内多次 setData 引起的动画延迟和卡顿(参考 demo2) *.wxml 在 view 上绑定事件并对应: [代码]<view catchtouchstart="touch1.start" catchtouchmove="touch1.move" catchtouchend="touch1.end" catchtouchcancel="touch1.cancel" > </view> <!-- touchstart -> 实例对象名.start touchmove -> 实例对象名.move touchend -> 实例对象名.end touchcancel -> 实例对象名.cancel --> [代码] NOTE: 如果不影响业务,建议使用 catch 捕获事件,否则易造成监听动画卡顿(参考 demo2) 以上简单几步即可使用 mina-touch 手势库 😊😊😊 具体使用和参数请查看Github https://github.com/Yrobot/mina-touch 如果喜欢mina-touch的话,记得在github点个start哦!🌟🌟🌟
2021-06-24 - map地图组件设置圆角
- 需求的场景描述(希望解决的问题) [图片] - 希望提供的能力 如图,想要给map组件设置圆角,但无奈一直没有成功,直接给map组件本身设置的话,完全没有效果,外层用view设置的话,模拟器倒是可以,但是真机上原生组件就会覆盖住view,还是不行,有没有大神做过类型的啊?给点意见或想法呗
2018-12-28 - 小程序的移动拖动图片安卓太过卡顿如何解决
[图片] 最后的this.setData的marginTop和marginLeft为控制图片的拖动。我看过官方的文档说不要频繁使用setData。但是不使用setData不知道如何实时的渲染到页面上啊
2018-06-06 - cover-view在安卓手机不能遮住canvas的BUG
按照官方文档的说法 : cover-view 覆盖在原生组件之上的文本视图,可覆盖的原生组件包括[代码]map[代码]、[代码]video[代码]、[代码]canvas[代码]、[代码]camera[代码]、[代码]live-player[代码]、[代码]live-pusher[代码],只支持嵌套[代码]cover-view[代码]、[代码]cover-image[代码],可在[代码]cover-view[代码]中使用[代码]button[代码]。 但是我目前使用cover-view 用来遮住canvas,却发下如下问题 cover-view在安卓手机不能遮住canvas,在iOS设备却没问题(可以遮住). iOS设备系统:ios11,安卓设备系统,Android7.1.1;调试基础库:2.2.5 iOS设备截图: [图片] [图片] 安卓设备截图(Android 7.1.1系统) [图片] [图片]
2018-10-08