小程序-分包加载
目前小程序分包大小有以下限制:
- 整个小程序所有分包大小不超过 16M
- 单个分包/主包大小不能超过 2M
某些情况下,开发者需要将小程序划分成不同的子包,在构建时打包成不同的分包,用户在使用时按需进行加载。
通俗理解,默认建立得quick项目,根目录下的所有文件就是一个独立的分包,大小不超过2M;
使用分包之后,在根目录下建立对应的目录文件,将对应的文件防止对应的目录下,并在 app.json 中声明 subpackages 字段即可
举个例子,现有一个由首页,最新动态 ,个人中心三块组成的,有三个tab切换的小程序。我们在设置分包时,一个tab设置一个分包,建立独立的文件夹,页面文件放置对应文件夹中
并配置subpackages
假设支持分包的小程序目录结构如下:
├── app.js
├── app.json
├── app.wxss
├── packageA
│ └── pages
│ ├── cat
│ └── dog
├── packageB
│ └── pages
│ ├── apple
│ └── banana
├── pages
│ ├── index
│ └── logs
└── utils
开发者通过在 app.json subpackages 字段声明项目分包结构:
{ "pages" :[ "pages/index" , "pages/logs" ], "subpackages" : [ { "root" : "packageA" , "pages" : [ "pages/cat" , "pages/dog" ] }, { "root" : "packageB" , "name" : "pack2" , "pages" : [ "pages/apple" , "pages/banana" ] } ] } |
subpackages 中,每个分包的配置有以下几项:
字段 | 类型 | 说明 |
---|
字段 | 类型 | 说明 |
---|
root | String | 分包根目录 |
name | String | 分包别名,分包预下载时可以使用 |
pages | StringArray | 分包页面路径,相对与分包根目录 |
independent | Boolean | 分包是否是独立分包 |
注意: 主包便是除开定义的分包文件之外的余下文件,大小均不可超过2M
引用原则
packageA
无法 require packageB
JS 文件,但可以 require app
、自己 package 内的 JS 文件packageA
无法 import packageB
的 template,但可以 require app
、自己 package 内的 templatepackageA
无法使用 packageB
的资源,但可以使用 app
、自己 package 内的资源
低版本兼容
由微信后台编译来处理旧版本客户端的兼容,后台会编译两份代码包,一份是分包后代码,另外一份是整包的兼容代码。 新客户端用分包,老客户端还是用的整包,完整包会把各个 subpackage
里面的路径放到 pages 中。
功能-返回上一级页面并刷新(页面栈)
在很多列表查询详情的业务场景中,都需要返回上一级并刷新页面的操作,而在微信小程序中提供的方法 wx.navigateBack 方法中,返回上一级页面是不会刷新的。
如果页面的加载方法是在onload 中触发的话,那么可以将我们的方法调用修改至onShow方法中触发即可。
在小程序的生命周期中,onLoad只会加载一次,而onshow方法会在页面每次显示的时候加载。
注意:
如果上一级的页面是从上上级页面跳过来的话,在onLoad方法中,使用 options 获取参数的方法就要改为在onShow使用,并且此方法需要修改才能使用,我们可以使用页面栈的方式,获取上上级页面传来的参数
下面看代码示例:
/** * 生命周期函数--监听页面加载 */ onLoad: function (options) { var that = this ; that.setData({ id: options.id }) }, |
修改成:
/** * 生命周期函数--监听页面显示 */ onShow: function () { // 页面初始化 options为页面跳转所带来的参数 var that = this ; let pages = getCurrentPages(); //页面栈 let currPage = pages[pages.length - 1]; //当前页面 that.setData({ name: currPage.options.name //获取上上级页面传的参数 }) }, |
原生模板-封装路由文件和路由方法
小程序有5种路由方法的使用场景,本文封装了路由文件和路由方法,提升小程序体验和开发效率
需求产生的原因
- 每次在代码中使用路由时,总是需要粘贴复制路径,当路径有修改时,需要修改对应所有使用到该路径的地方,维护成本高
- 路由跳转拼接参数,当业务庞大时需要拼接十几个参数
- 路由返回只会返回一层,不能直接返回目标页面,因为不知道目标页面是否在路由栈中,也不知道在第几层
封装路由文件,对路由进行通统一的管理
在utils文件夹下创建route.js
// 路由管理页面,在此统一配置 export default { 'index' : 'pages/index/index' , 'logs' : 'pages/logs/logs' , 'list' : 'pages/list/list' } |
所有路由均在上面的文件进行配置
封装路由方法
路由方法有五个,常用 的有三个 switchTab navigateTo navigateBack
简单介绍一下这五个方法及使用场景
- switchTab :跳转tabBar页面专用,其他页面出栈,新页面入栈
- navigateTo :最常用的路由跳转,会加入到页面栈,允许返回,也就是新页面不断入栈
- navigateBack :返回,只能返回到页面栈中存在的页面,页面不断出栈,直到到达目标页
- redirectTo : 关闭当前页面,跳转某个页面,当前页面不会加入到页面栈,也就是说当前页面不能通过返回到达,比如付款页面,付款完成后,最好不要再让用户返回到付款页,再比如一些无法修改的操作,比如删除商品,商品删除后再通过navigateBack返回再删除一次商品,体验肯定不好,表现为当前页面出栈,新页面入栈
- relaunch : 关闭所有页面,打开某个页面,可以打开任意页面包括tabBar,适合强制完成某个操作的页面,比如登录页,当已登录的用户点击退出后,进入登录页,那么就不能通过返回再回去了,就必须留下来登录或注册,适合用这个,表现为所有页面出栈,新页面入栈
开始封装,在utils目录创建UtilRoute.js
// 封装路由方法 export default { /** * function * @param {string} url 目标页面的路由 * @param {Object} param 传递给目标页面的参数 * @description 处理目标页面的参数,转成json字符串传递给param字段,在目标页面通过JSON.parse(options.param)接收 */ navigateTo(url,param={}){ if (param){ url+=`?param=${JSON.stringify(param)}` } wx.navigateTo({ url:url, fail(err) { console.log( 'navigateTo跳转出错' ,err) }, }) }, /** * function * @param {string} url 目标页面的路由 * @param {Object} param 传递给目标页面的参数,只有页面栈无目标页面调用navigateTo时,参数才会生效,单单返回不能设置参数 * @description 先取出页面栈,页面栈最多十层,判断目标页面是否在页面栈中,如果在,则通过目标页的位置,返回到目标页面,否则调用navigateTo方法跳转到目标页 */ navigateBack(url,param={}){ if (url) { const pagesList = getCurrentPages() let index = pagesList.findIndex(e=>{ return url.indexOf(e.route)>=0 }) if (index == -1){ // 没有在页面栈中,可以调用navigateTo方法 this .navigateTo(url,param) } else { wx.navigateBack({ delta: pagesList.length-1-index, fail(err){ console.log( 'navigateBack返回出错' ,err) } }) } } else { wx.navigateBack() } }, switchTab(url){ // 封装switchTab,switchTab不能有参数 wx.switchTab({ url:url }) }, redirectTo(url,param={}){ // 封装redirectTo,和navigateTo没啥区别 if (param){ url+=`?param=${JSON.stringify(param)}` } wx.redirectTo({ url:url, fail(err) { console.log( 'redirectTo跳转出错' ,err) }, }) }, reLaunch(url,param={}){ // 封装reLaunch,和navigateTo没啥区别 if (param){ url+=`?param=${JSON.stringify(param)}` } wx.reLaunch({ url:url, fail(err) { console.log( 'reLaunch跳转出错' ,err) }, }) } } |
使用方法
在页面中引入封装的两个文件
import route from './../../utils/route.js'
import UtilRoute from './../../utils/UtilRoute.js'
只需将路由方法写入函数中调用即可,请看例子:
在list页面中接收参数
参数的传递,在navigateTo方法中使用JSON字符串方法处理过,在接收时需要对应处理获取
以上对参数的封装解决了第二个问题,对 navigateBack 的封装解决了第三个问题
功能-语音合成(tts)
1.申请账号
第三方平台:
1.讯飞:讯飞开放平台-以语音交互为核心的人工智能开放平台
2.百度:百度智能云-登录
2.生成音频文件
三方接入步骤:
1.申请平台账号后,需要新建应用,以获取相应的id和key参数。
2.通过id、key和其它的参数,获取授权验证
3.调用相应语音合成的接口,将文本转化成音频文件
讯飞开发文档:讯飞开放平台文档中心
百度开发文档:百度AI开放平台-全球领先的人工智能服务平台-百度AI开放平台
百度语音合成接入demo:baidu-video-demo.rar
3.音频文件播放
音频组件
<!-- poster:音频封面图片 name:歌名 author:歌手 src:音频地址 controls:是否显示默认控件,也就是下面这个东东 loop:是否循环播放 id:标注唯一组件以this.audioCtx = wx.createAudioContext('myAudio')获取控制组件的对象。 bindplay:播放时触发该事件 bindpause:停止时触发该事件 bindtimeupdate:时间改变时触发,该函数携带有参数detail={currentTime, duration}当前时间,持续播放时间 bindended:播放结束时触发 binderror;播放错误时调用,携带参数detail = {errMsg: MediaError.code} --> < view class = "page" > < view class = "page__bd" > < audio poster = "{{poster}}" name = "{{name}}" author = "{{author}}" src = "{{src}}" id = "myAudio" controls loop bindplay = "funplay" bindpause = "funpause" bindtimeupdate = "funtimeupdate" bindended = "funended" binderror = "funerror" ></ audio > < button type = "primary" bindtap = "audioPlay" >播放</ button > < button type = "primary" bindtap = "audioPause" >暂停</ button > < button type = "primary" bindtap = "audio14" >设置当前播放时间为14秒</ button > < button type = "primary" bindtap = "audioStart" >回到开头</ button > </ view > </ view > |
注意:语音合成只支持纯文本的合成,不支持html,所以html的文本需要处理成存文本。处理方法见:字符串
// pages/video/video.js Page({ /** * 页面的初始数据 */ data: { poster: 'http://y.gtimg.cn/music/photo_new/T002R300x300M000003rsKF44GyaSk.jpg?max_age=2592000' , name: '此时此刻' , author: '许巍' , src: '' , }, onReady: function (e) { // 使用 wx.createAudioContext 获取 audio 上下文 context this .audioCtx = wx.createAudioContext( 'myAudio' ) }, /** * 生命周期函数--监听页面加载 */ onLoad: function (options) { let that = this ; // 百度语音合成接口 wx.request({ url: 'http://192.168.1.151:3000/baiduAI/speech' , data: { text: '日光岩,是鼓浪屿每天第一缕阳光照到的地方,俗称""岩仔山"",别名""晃岩"",相传1641年,郑成功来到晃岩,看到这里的景色胜过日本的日光山,便把""晃""字拆开,称之为""日光岩""。日光岩游览区由日光岩和琴园两个部分组成。日光岩耸峙于鼓浪屿中部偏南,是由两块巨石一竖一横相倚而立,成为龙头山的顶峰,海拔92.7米...' , spd: '6' , pit: '3' , vol: '8' , per: '0' }, method: 'POST' , success(res) { let data = res.data; if (data.ret === 0) { that.setData({ src: data.data.path + '?rnd=' + new Date().getTime() }) } else { wx.showToast({ title: data.msg, }); } }, fail(err) { console.log( 'err' ); console.log(err) } }); }, audioPlay: function () { this .audioCtx.play() }, audioPause: function () { this .audioCtx.pause() }, audio14: function () { this .audioCtx.seek(14) }, audioStart: function () { this .audioCtx.seek(0) }, funplay: function (){ console.log( "audio play" ); }, funpause: function (){ console.log( "audio pause" ); }, funtimeupdate: function (u){ console.log(u.detail.currentTime); console.log(u.detail.duration); }, funended: function (){ console.log( "audio end" ); }, funerror: function (u){ console.log(u.detail.errMsg); }, }) |
功能-语音录制、播放
录音API
wx.getRecorderManager()
获取全局唯一的录音管理器 RecorderManager
注:从基础库1.6.0开始,wx.stopRecord()和wx.startRecord()接口停止维护,全部使用wx.getRecorderManager代替进行操作
使用方法
在生命周期函数--监听页面加载onLoad中对API进行初始化,对录音的各项操作进行监听
示例代码
var that = this ; this .recorderManager = wx.getRecorderManager(); // 监听录音失败 this .recorderManager.onError( function () { that.tip( "录音失败!" ) }); // 监听录音结束 this .recorderManager.onStop( function (res) { that.setData({ src: res.tempFilePath }) console.log(res.tempFilePath) that.tip( "录音完成!" ) }); this .recorderManager.onPause( function () { // 监听录音暂停 // 录音暂停事件的回调函数 }); this .recorderManager.onResume( function () { // 监听录音继续 // 录音继续事件的回调函数 }); this .innerAudioContext = wx.createInnerAudioContext(); // 监听录音播放失败 this .innerAudioContext.onError((res) => { that.tip( "播放录音失败!" ) }) |
录音开始事件
RecorderManager.start(Object object)
参数
属性 | 类型 | 默认值 | 必填 | 说明 |
---|
duration | number | 60000 | 否 | 录音的时长,单位 ms,最大值 600000(10 分钟) |
sampleRate | number | 8000 | 否 | 采样率,每种采样率有对应的编码码率范围有效值,设置不合法的采样率或编码码率会导致录音失败 |
numberOfChannels | number | 2 | 否 | 录音通道数(1或2) |
encodeBitRate | number | 48000 | 否 | 编码码率,8000的采样率对应的是16000~48000的编码码率 |
format | string | aac | 否 | 音频格式(仅支持 aac 和 mp3 格式) |
frameSize | number | | 否 | 指定帧大小,单位 KB。传入 frameSize 后,每录制指定帧大小的内容后,会回调录制的文件内容,不指定则不会回调。暂仅支持 mp3 格式。 |
audioSource | string | auto | 否 | 指定录音的音频输入源,可通过 wx.getAvailableAudioSources() 获取当前可用的音频源 |
object.sampleRate 的合法值
值 | 说明 |
---|
8000 | 8000 采样率 |
11025 | 11025 采样率 |
12000 | 12000 采样率 |
16000 | 16000 采样率 |
22050 | 22050 采样率 |
24000 | 24000 采样率 |
32000 | 32000 采样率 |
44100 | 44100 采样率 |
48000 | 48000 采样率 |
录音开始传参
// 自定义参数 const options = { duration: 10000, sampleRate: 44100, numberOfChannels: 1, encodeBitRate: 192000, format: 'aac' , frameSize: 50 } // 调用 this .recorderManager.start(options) |
停止录音
recorderManager.stop()
在停止事件中直接对这个方法进行调用即可
/** * 停止录音 */ stopRecord: function () { this .recorderManager.stop() } |
在结束录音时,会对事件进行监听,得到返回的录音文件的临时路径,利用这个路径可以进行录音的播放和上传
监听示例
this .recorderManager.onStop( function (res) { // 对录音临时路径进行处理 that.setData({ src: res.tempFilePath }) console.log(res.tempFilePath) that.tip( "录音完成!" ) }); |
在监听录音结束时,可以利用 wx.uploadFile 接口对音频进行上传操作
语音播放
需要使用到 wx.createInnerAudioContext 接口
简单示例代码
// 参考初始化录音接口,对音频播放接口进行初始化 this .innerAudioContext = wx.createInnerAudioContext(); this .innerAudioContext.onError((res) => { // 监听播放失败 that.tip( "播放录音失败!" ) }) /** * 播放录音 */ playRecord: function () { var that = this ; var src = this .data.src; // 判断录制音频是否存在 if (src == '' ) { this .tip( "请先录音!" ) return ; } // 赋值录音文件地址,直接调取播放接口 this .innerAudioContext.src = this .data.src; this .innerAudioContext.play() } |