=================================================
微信小程序开发实战
=================================================
cemike@126.com
本文介绍了作者开发Swing English微信小程序的实践。从实践经验提出微信小程序开发的最佳实践步骤和常见问题的解决方法。
现在真是个好时代。以前要搞个个人HomePage可以说非常麻烦,甚至是不可能。即使可能也得花相当可观的一笔钱。后来有了博客这些东西之后,写博客这种静态网页的时代来临了。但在博客上只能显示文字图片给用户看,用户顶多只能回复评论,不能有其他交互。
现在腾讯(还有其他大佬,如阿里也提供云服务,新浪也有新浪云)提供了一个用户可以设计动态网页的的地方,叫做微信小程序。就是说,你可以自己决定在网页上显示些什么(像小孩玩积木一样,放哪些方块),怎么布局(方块放哪儿),怎么交互(用户点击了某个方块以后接下来做什么,如弹出个提示等等)。这下你可以折腾很久了。
而且这一切都是免费的。
这在以前就像是在做梦。以前我们都说天下没有免费的午餐。现在真有免费的午餐了!
一、概述
先简要介绍一下本文要说的Swing English微信小程序。
1. Swing English微信小程序是一个致力于提高学生英语词汇和口语学习的应用,与Swing English微信公众号同步服务,与MyOral.Android手机apk相互配合使用。
2. 通过WordQuiz测试(语音播放和图片卡片选择)让学生实现从最初级的简单英语单词到高级复杂英语句子的听力和理解。
3. 通过ImageQuiz测试(看图从四个选项中选择正确单词)等练习巩固词汇学习。
4. 通过Gallery输入关键词查询和展示相关图片,同时显示中英文对照的例句,提供图文并茂的富媒体的英语学习环境。
二、说人话:微信小程序是什么,提供了什么,可以做什么,有什么限制
微信小程序是什么?
微信小程序就是可以在微信里运行的小程序。个人理解,微信小程序其实就是必须根据官方的标准设计,只能在微信平台上显示的一组网页。比微信公众号提供了更多可定制的交互方式,如界面按钮、文本框、列表等;而公众号基本上只能通过菜单和回复进行用户交互。
提供了什么?
微信小程序提供了一种机会和能力。有点儿像给你一个建立个人网页的地方。开发者可以在微信上向其他微信用户展示一些信息,完成交互,收集用户提供的数据或者在交互过程中生成的数据。
可以做什么?
能做些什么呢?几乎网页能做的大部分事情。用几乎是因为只能在微信上运行,商业竞争的关系还有腾讯的尿性,所以有诸多限制。无论如何,以前你在自己的电脑上玩静态网页设计能做的大部分事情都能做。尤其是可以使用类似JS(JavaScript)操控浏览器的能力(页面跳转,根据用户选择改变页面上显示的内容),这样就比较好玩了。
有什么限制?
如开头所说,微信小程序是腾讯老爷提供的免费午餐。但这免费午餐吃得滋味如何,吃过的人都知道。俗话说,吃人家的嘴软,拿人家的手软。吃免费午餐当然就得有各种限制。据我所知,微信小程序有以下限制:
1. 小程序整个的开发打包的文件体积不能超过1M。
现在随便拍张图片都几十M了。是啊,所以小程序上基本只能放代码(文本文件),别指望放很多图片,视频什么的更别想。
这也太小气了点,你也许会说。但是你想象一下目前微信上上线的有多少小程序!不用看官方统计都知道,可以说是成千上万,犹如星辰大海。如果不限制大小,人人免费开发上传,马化腾也会肚子痛的。这也可以理解嘛。
既然是人家施舍的,那还是忍了吧。
2.只提供数据访问的能力,不提供数据访问的服务
腾讯只给你提供存储并在微信里加载展示网页的地方。如果你要用到访问数据库什么的(从数据库查询数据,或者把用户的数据上传到数据库;少量的只是查询的话,可以硬编码在JS代码里,我就喜欢这么干),当然小程序提供这种访问的能力。谁给你提供这种数据存储和查询服务,那你就得另找高明了,如我用过leancloud(也是一部血泪史)。当然微信小程序现在推出了云开发可以在云函数中读写的 JSON 数据库(参考https://developers.weixin.qq.com/miniprogram/dev/wxcloud/basis/getting-started.html)。本人没用过,是否收费等等还不知道。
3.封闭性
限制或禁止访问外部网址。也就是说要跳转网页的话,就只有微信朋友圈里那些鸡汤文是畅通无阻的。如果你搞小心机,想要直接在微信里展示某个腾讯系统之外的网页,那对不起,好点的,弹出提示,用户允许后可以访问;运气不好的话直接禁止。
这也是被无数键盘侠猛烈抨击的地方。腾讯几乎要被键盘侠们的口水淹没了。
对此我不想说什么,既然前面的都忍了,那就再忍忍吧。
4.不可预测性
人在屋檐下,不得不低头。在比人的地盘上当然任人宰割。现在当然这些都是免费的。大家都乐呵呵。可是别忘了互联网上有个说法叫“割韭菜”。现在给你们这些吃瓜群众免费使用叫做培植用户土壤,扩大用户基数。等具备一定规模了(还有压制竞争对手),就要“割韭菜”了。到时想收费就收费。收多少都是大爷说了算。不爽?你可以滚蛋!
所以要随时做好撤出腾讯系的准备(备份代码和数据),别到时候哭都来不及!
三、微信小程序申请和开发环境搭建
1. 微信小程序申请 https://mp.weixin.qq.com/
这里就不展开了。网上有大把文章可参考。
2. 开发环境搭建还是比较傻瓜化的,下载安装官方提供的微信开发者工具就可以了。
开发环境下载地址:https://developers.weixin.qq.com/miniprogram/dev/devtools/download.html
四、Swing English微信小程序构架
A. 第三方框架
由于我们的小程序比较简单,所以没有使用第三方框架,直接使用的官方提供的开发工具。
感兴趣的同学可以参考:小程序第三方框架对比 ( wepy / mpvue / taro )
https://www.cnblogs.com/Smiled/p/9806781.html
B. 导航
1. 除非是功能非常单一的小程序(例如我之前练手时开发的英语音标学习小程序MyPhonetics), 一般我们需要一个导航栏。
2. 开始时我采用了最简单的只有顶部两栏的一个导航设置。参考:微信小程序tab切换效果 https://jingyan.baidu.com/album/59703552b60db78fc0074036.html?picindex=7
但是这种设计其实是一种伪导航,并不是真正的跳转到一个新页面。它是通过显示和隐藏底部的组件来实现的。所有内容其实都在一个页面上。所以随着内容越来越多,页面就会变得很臃肿。而且也不容易扩展。将两个栏目扩展成三栏四栏也不是不可能,但是肯定很麻烦。为了最大程度使各个页面解耦合,两栏不够用了之后就不用这种设计了。
3. 后来尝试过官方自带的微信小程序底部导航Tabbar,参考
https://www.cnblogs.com/huangjialin/p/6278429.html。但是觉得太丑,弃用。
4. 开始打起自定义头部导航栏("navigationStyle": "custom")的主意。并且参照https://blog.csdn.net/hangGe0111/article/details/85232022改动做成了自己的。
微信官方的顶部组件只能显示标题和右侧胶囊按钮。胶囊按钮里有三点的弹出菜单和关闭小程序按钮。
自定义头部导航栏之后除了胶囊按钮仍然保留,其他的如增加背景图片、在左侧放入一个返回按钮甚至增加更复杂的页面元素可以完全自己定制。但是这个顶部的空间高度是有限制的,而且胶囊按钮不能去掉也不能挪动位置,所以可以发挥的空间还是有限。纠结之后还是决定只在左侧放一个返回按钮,然后在首页放上跳转到其他页面的按钮。
这个定义头部导航栏有个好处就是灵活。通过设置js代码中的flag属性可以控制点击左侧按钮的三种不同的动作: 0表示无返回;1表示返回上一级;2表示返回home(首页)。
这个定义头部导航栏设置有点儿复杂。每个页面都要做3方面的设置(以index页面为例):
1) index.wxml里第一行增加以下内容,相当于包含进来一个组件。注意:每个页面都必须这样做,否则顶部风格就不统一了。
<my-component my-property='{{aa}}' />
2) index.js里增加以下内容:
Page({
data: {
aa: {
"bg_color": "orange",
"color": "#000",
"flag": 1, //flag 0:无返回;1:返回上一级;2:home
"name": "我是标题独一无二天下无敌我是标题独一无二天下无敌"
}
},
})
3) index.json里增加以下内容:
{
"usingComponents": {
"my-component": "/components/custom-component"
}
}
这个定义头部导航栏还用到了一种技术:用Base64编码字符串保存图片。你会注意到左侧的房子和左箭头按钮其实都是图片,但组件的定义里没有引用任何png或jpg图片文件。这样做的好处是可以节省文件的体积。这种技术对于总体积不能超过1M的尴尬限制来说非常有价值。更多用法可参考:
Css中路径data:image/png;base64的用法详解 (转载)
https://www.cnblogs.com/OpenCoder/p/7127256.html
图片转换Base64
http://imgbase64.duoshitong.com/
5. 最后发现官方还有一种导航组件
再改版时考虑尝试一下这种navigator导航组件。
更多用法可参考:
微信小程序导航组件官方文档navigator
https://developers.weixin.qq.com/miniprogram/dev/component/navigator.html
微信小程序之导航组件
https://www.jianshu.com/p/53ac270215cb
C.数据
(一)少量的数据
1. 可以直接定义在Page的data里面,冒号:左边的相当于变量名称,不需要任何引号;冒号右边的相当于变量的值,字符串需要用单引号或者双引号包括,数字不需要引号:
Page({
data: {
motto: "Let's Swing English",
imageSrc1:'http://lc-GI3Digt4.cn-n1.lcfile.com/92f945005f25e192f75e.jpg',
inputCon: {},
map:app.globalData.map,
entry:'',
sentenceEn:'',
sentenceCn:'',
total:1,
correct:0,
rate:0,
}
})
2. 在.wxml文件里引用的时候可以直接用双花括号包含变量名称{{motto}}
<view class="usermotto">
<text class="user-motto">{{motto}}</text>
</view>
3. 甚至可以在style属性里引用,如
<view class='flex commonHead' style='color:{{myProperty.color?myProperty.color:"#000"}};background-color:{{myProperty.bg_color?myProperty.bg_color:"white"}}; height:{{commonHeadHeight.titleHeight}}px;'>
4. 也可以放在data-[参数名]这样的属性里,如data-id定义了一个叫做id的参数,data-text定义了一个叫做text的参数,用作参数传给事件,如bindtap,bindinput,bindconfirm,bindchange等等,如bindtap="onViewClick"表示用户点击事件由onViewClick函数处理,bindchange="bindChange"表示文本框输入的文字改变事件由bindChange函数处理:
<view class="item" data-id='1' bindtap="onViewClick">Gallery</view>
<view class="sentence" data-text='{{sentenceEn}}' bindtap="playTTS2" >{{sentenceEn}}</view>
<view class="sentence" data-text='{{sentenceCn}}' bindtap="playTTS2" >{{sentenceCn}}</view>
<view class="query-form">
<input id="keyword" confirm-type="search" placeholder-style="color:black;" placeholder-class="place-holder" class="keyword-input" placeholder=" Enter keyword" bindinput="bindChange" bindconfirm="query"/>
<button class="query-btn" loading="" plain="true" disabled="{{disabled}}" bindtap="query"> GO </button>
</view>
bindChange函数则是定义在.js文件里:
bindChange: function (e) {
var id;
var value;
id = e.target.id;
value = e.detail.value + '';
this.data.inputCon[id] = value;
},
其中id = e.target.id;表示取出前面.wxml文件里定义的id参数。
5. 数据也可以直接定义在app.js文件里App的globalData里面:
App({
globalData: {
userInfo: null,
ctxPath: 'https://www.baidu.com',
statusBarHeight:wx.getSystemInfoSync()['statusBarHeight'],
map:[
{id:18, word:"accident", pos:"n.", meaning:"事故,意外的事", url:"http://lc-GI3Digt4.cn-n1.lcfile.com/f418c66cc34c4e986504.jpg", sentence:"The accident scared me out of sense. ", chinese:"那事故真把我吓死了。"},
{id:32, word:"actor", pos:"n.", meaning:"男演员", url:"http://lc-GI3Digt4.cn-n1.lcfile.com/ba0bcef519d5cddb9af3.jpg", sentence:"", chinese:""},
],
})
这样的好处就是,这样定义的变量是全局的,可以在任意页面里引用。如可以在任意页面用app.globalData.ctxPath引用app.js文件里定义的ctxPath变量:
var app = getApp();
that.setData({
web_src: app.globalData.ctxPath,//ctxPath
})
6. 除了data,globalData这种常用的变量命名,也可以定义自己特有的变量名:
Component({
properties: {
myProperty: {
type: Object,
value: {
"bg_color": "white",
"color": "#000",
"flag": 1,
"name": "我是标题"
}
},
commonHeadHeight: {
type: Object,
value: {}
}
},
})
其中myProperty,commonHeadHeight都是自定义的对象类型的变量。可以在js代码里操作:
ready: function () {
var that = this;
wx.getSystemInfo({
success(res) {
that.setData({
"commonHeadHeight.statusBarHeight": (34 * 2),
"commonHeadHeight.titleHeight": res.statusBarHeight + 46
});
}
})
},
7.还有些数据是定义在.json文件里,如app.json文件里。
{
"pages": [
"pages/index/index",
"pages/user/user",
"pages/setting/setting"
],
"window": {
"backgroundTextStyle": "light",
"navigationBarBackgroundColor": "#fff",
"navigationBarTitleText": "WeChat",
"navigationBarTextStyle": "black",
"navigationStyle": "custom"
},
"plugins": {
"WechatSI": {
"version": "0.2.2",
"provider": "wx069ba97219f66d99"
}
},
"sitemapLocation": "sitemap.json"
}
其中:pages数组的第一条就是默认的首页(小程序默认加载的页面);一些插件如同声传译(文本转语音/朗读)插件WechatSI就是定义在这里。
(二)大量的数据
超大规模大量的数据还有依赖大量图片的小程序当然是要通过第三方提供的数据库服务如leancloud进行查询。
但是如果规模不是很巨大,也是可以硬编码在js代码中的。如下面这个map数组里我存储了4000多条词典数据,上线运行以后仍然非常流畅,并没有预期的卡顿现象。
考虑到第三方数据服务不是永远可靠,而且动辄收费,硬编码的数据其实是一个较好的折衷选择,尤其是对于只需查询,不需要日常更新的数据。
App({
globalData: {
map:[
{id:18, word:"accident", pos:"n.", meaning:"事故,意外的事", url:"http://lc-GI3Digt4.cn-n1.lcfile.com/f418c66cc34c4e986504.jpg", sentence:"The accident scared me out of sense. ", chinese:"那事故真把我吓死了。"},
{id:32, word:"actor", pos:"n.", meaning:"男演员", url:"http://lc-GI3Digt4.cn-n1.lcfile.com/ba0bcef519d5cddb9af3.jpg", sentence:"", chinese:""},
//more
],
})
D. 查询
查询第三方提供的数据库服务,这个另开一篇文章专门说明。
五、踩过的坑和解决方法
(一)开发环境的坑:调试不通过我很开心
查询存储在leancloud上的数据,真机调试时通过,上线版本无响应(不显示图片)。微信小程序这个报错是什么原因?怎么解决?
报错提示如下:
VM45:1 Error: Request has been terminated
Possible causes: the network is offline, Origin is not allowed by Access-Control-Allow-Origin, the page is being unloaded, etc. [N/A POST https://gi3digt4.api.lncld.net/1.1/users]
注:使用了leancloud
我在微信开放社区(https://developers.weixin.qq.com/community/develop/question)提问,有人回答说没配置安全域名。这个应该不可能。已经配置安全域名。再说,如果没配置,真机调试时就不可能通过。
有人回答说Access-Control-Allow-Origin 发生跨域了。这个的确很有误导性,因为报错提示中的确提到Origin is not allowed by Access-Control-Allow-Origin。
我开始以为是因为访问了leancloud。但是我是根据leancloud的样板工程代码改的啊,样板工程为什么就能运行正常呢?
经过无数种尝试,所有可能的组合我都试过了,还是解决不了问题。最后我咬着牙把开发环境升级了。因为一般不到万不得已我是不会这么做的。对于软件,这么多年的经验告诉我,能用就不要升级。升级后即使体验好了也会使软件体积急剧膨胀。还有很多时候升级了就没法用了。尤其是那些破解的收费软件,千万不能升级。
升级之后终于开发环境调试也无法通过了。这是什么话?别人都指望调试能通过。我却因为调试我通过而开心。因为至少本地的和上线版本的表现一致了。这比本地好好的,上线就出错,让人百思不得其解的痛苦少一些。
我终于搞明白了。和新版本的开发环境相比,一定是旧版本各种安全检查少一些,还存在一些漏洞。所以旧版本真机调试能通过。但是线上版本的运行环境一定是和最新版本的开发环境是一致的。所以才导致以上如此诡异的现象。
(二)函数/方法
微信小程序的自定义方法都是定义在.js文件里。
注意:
1. 方法其实有两种定义格式:App({...})或者Page({...})里面的和外面的。
2. App({...})或者Page({...})里面的格式:先方法名bindChange,冒号后面才是 function,然后是参数列表,最后花括号包住方法体,如
bindChange: function (e) {
var id;
var value;
id = e.target.id;
value = e.detail.value + '';
this.data.inputCon[id] = value;
},
3. App({...})或者Page({...})里面的方法前后都要有英文逗号,否则报错。
因为小程序整个的这种代码组织架构,都是json模式化的,不管是数据还是方法都这样。这好像已经成了一种潮流。
其实第一个这么干的不是json(LISP语言的S表达式是Lisp语言的鲜明特点,使数据和代码形式统一)。想当初LISP语言(LISP,List Processing,1958年诞生)大行其道的时候,现在主流的那些编程语言(C,1970年;C++,1985年;Perl,1987年;Python,1994年;PHP,1995年;Java,1996年;参考:当编程语言都变成女孩子 https://www.oschina.net/news/53201/programming-language-as-a-girl)都还没诞生。
可惜LISP风光不再( 许多人对Lisp语言的第一印象就是一层层的括号,很老的关于苏联黑客偷到Lisp源码的最后一页全是括号的笑话就不用再说了。参考:Lisp永远成不了编程主流语言 https://www.cnblogs.com/tkt2016/p/5755974.html),没想到它的这种极简的语法风格倒是被json(严格来说不是一种语言)这样的后辈继承了,现在又因为在网络编程中广泛运用而变一种样式火了起来。
《Lisp神化之路》这篇文章中说:如今越来越多的Lisp特性被加入到大家使用的编程语言中了。Python有了列表comprehensions。C#有了Linq。Ruby有了…,好吧,Ruby就是一种Lisp。
参考:https://blog.csdn.net/code_for_fun/article/details/83304873
4. 如果不在App({...})或者Page({...})里面那么就是以前为浏览器网页编写js方法那种格式:function关键字后面跟方法名,如
function playTTS(text) {
//need to add WXAPP plug-in unit: WechatSI
plugin.textToSpeech({
lang: "zh_CN",
tts: true,
content: text,
success: function (res) {
//log("succ tts", res.filename)
innerAudioContext.src = res.filename;
innerAudioContext.play()
},
fail: function (res) {
//log("fail tts", res)
}
})
}
5. 两种格式定义的方法,调用方式也不一样。
在App({...})或者Page({...})里面定义的方法,必须在前面加上this.否则报找不到方法的错误(这个坑浪费了我大半天的时间,sigh!)
onReady: function () {
this.playTTS(this.data.stem);
},
在App({...})或者Page({...})外面定义的方法,不要在前面加上this. 。
(三)that = this ?
这不是文字游戏!前面提到微信小程序.js文件App({...})或者Page({...})里面引用变量和方法都要在前面加上this. 。
但是我们也经常看到网上复制下来的代码有下面这种赋值操作: var that = this;。这是在搞笑吗?还是作者故弄悬殊?其实不是的,听我慢慢道来。
如果不是方法里嵌套方法,用this.是没有问题的。但是如果是像下面这样setTimeout()方法里面再嵌套一层function () {}再用this.就不起作用了。
这时用上面那句类似巫术一样的代码并且在内嵌的方法中用that.引用自己定义的变量和方法就又起作用了。这里面有一些原理,我就不去深究了,代码能用就行。
var that = this;
setTimeout(function () {
//要延时执行的代码
//语音朗读
that.playTTS(that.data.stem);
}, 1500) //延迟时间 这里是1秒
六、小程序发布:开发版、体验版、审核版、上线版和建议
小程序界面设计得差不多了就要调试。实际上调试占了小程序开发的大部分时间。
1. 先在开发环境了调试。然后真机调试。真机调试这一步不能少。有些特性在开发环境上和真机上表现是不一致的。例如,弹出提示框里的文字换行\r\n,在开发环境上看不到换行效果。但在手机上就能换行。
2. 最后终于要上传发布了。发布分很多版本:开发版(真机调试的版本)、体验版(通过微信开发者工具菜单:工具>上传 上传的版本)、审核版(登录微信公众平台https://mp.weixin.qq.com在版本管理页面开发版本一栏点击“提交审核”该版本就成为审核版)、上线版(审核通过后上线的版本,所有人都能访问)。
3. 其中体验版只有你登录微信公众平台https://mp.weixin.qq.com在成员管理页面体验成员一栏指定的微信用户能访问。版本管理页面开发版本一栏点击带二维码的按钮弹出的页面有小程序二维码,可以自己下载二维码发给体验成员。
4. 审核这个环节要耐心等待,且做好多次修改提交审核的准备。根据个人经验,腾讯这方面很不厚道。审核非常挑剔,经常找茬让你通不过。说是鸡蛋里挑骨头也不为过。
以下是几次我经历的审核未通过的原因:
(1) 功能不完善/功能不完整。
第一次开发的英语音标学习小程序MyPhonetics只有一个页面。被认为功能不完整拒绝通过。费了一番口舌才让通过。
(2) 存在诱导类行为,包括但不限于诱导消费、诱导分享;
这个很无语,我猜了半天,估计是因为用的模板工程中有一个命名为pay的页面,事实上英语音标学习小程序MyPhonetics根本没有用到这个页面。删除之后才给通过。
(3) 在版本描述处详细说明提供“IT科技-软件服务提供商”插件的实际使用场景和用途。
因为用了同声传译插件和leancload服务,说明了以后给通过。
更多信息可参考:
微信小程序10个最常见的审核的核不通过的原因 https://www.jianshu.com/p/3d765ef6cd7d
七、其他
1. 微信小程序显示加载弹窗 https://blog.csdn.net/henryhu712/article/details/80335351
2. 微信小程序取 dataset 值、取其他页面传过来的值 https://blog.csdn.net/rolan1993/article/details/79742653
3. 微信小程序——页面跳转三种方法
https://blog.csdn.net/wl1769127285/article/details/53691551
4. 微信同声传译插件(文本转语音)
https://developers.weixin.qq.com/community/develop/doc/0004aa70d609e099c1d671b2a56009
github:Tencent/Face2FaceTranslator
https://github.com/Tencent/Face2FaceTranslator
参考文献
1. 微信公众平台 https://mp.weixin.qq.com/
2. 开发环境下载地址 https://developers.weixin.qq.com/miniprogram/dev/devtools/download.html
3. 微信小程序tab切换效果 https://jingyan.baidu.com/album/59703552b60db78fc0074036.html?picindex=7
4. 小程序第三方框架对比 ( wepy / mpvue / taro ) https://www.cnblogs.com/Smiled/p/9806781.html
5. 微信云开发 https://developers.weixin.qq.com/miniprogram/dev/wxcloud/basis/getting-started.html
6. 微信小程序底部导航Tabbar https://www.cnblogs.com/huangjialin/p/6278429.html
7. 微信小程序导航组件官方文档navigator https://developers.weixin.qq.com/miniprogram/dev/component/navigator.html
8. 微信小程序之导航组件 https://www.jianshu.com/p/53ac270215cb
9. Css中路径data:image/png;base64的用法详解 (转载) https://www.cnblogs.com/OpenCoder/p/7127256.html
10. 图片转换Base64 http://imgbase64.duoshitong.com/
11. 微信开放社区 https://developers.weixin.qq.com/community/develop/question
12. 主流编程语言拟人化 https://www.oschina.net/news/53201/programming-language-as-a-girl
13. Lisp永远成不了编程主流语言 https://www.cnblogs.com/tkt2016/p/5755974.html
14. 《Lisp神化之路》 https://blog.csdn.net/code_for_fun/article/details/83304873
15. 微信小程序10个最常见的审核的核不通过的原因 https://www.jianshu.com/p/3d765ef6cd7d
16. 微信小程序显示加载弹窗 https://blog.csdn.net/henryhu712/article/details/80335351
17. 微信小程序取 dataset 值、取其他页面传过来的值 https://blog.csdn.net/rolan1993/article/details/79742653
18. 微信小程序——页面跳转三种方法 https://blog.csdn.net/wl1769127285/article/details/53691551
19. 微信同声传译插件(文本转语音) https://developers.weixin.qq.com/community/develop/doc/0004aa70d609e099c1d671b2a56009
20. 微信同声传译插件github:Tencent/Face2FaceTranslator https://github.com/Tencent/Face2FaceTranslator
联系我们
-------------------------------------------------
cemike@126.com
欢迎交流。