tabBar
相当于路由,最少配置两个,每个配置项会匹配一个 page
,配置后会在页面底部生成导航菜单。当点击下方的导航时,会跳转到对应的页面。
"tabBar": {
"selectedColor": "#d81e06",
"color": "#515151",
"list": [
{
"pagePath": "pages/playlist/playlist",
"text": "音乐",
"iconPath": "images/bottomNavIcon/music.png",
"selectedIconPath": "images/bottomNavIcon/music-checked.png"
},{
"pagePath": "pages/blog/blog",
"text": "动态",
"iconPath": "images/bottomNavIcon/release.png",
"selectedIconPath": "images/bottomNavIcon/release-checked.png"
}
]
},
配置项中,pagePath
表示对应的 page 路径;text
表示要显示的文案;
iconPath
表示没有选中时的图标路径;selectedIconPath
表示选中时的图标路径(选中后,页面会切换)color
为本选中时的文案颜色;selectedColor
选中后的文案颜色;使用 bind
绑定事件默认会有事件冒泡;而如果使用 catch
绑定事件,则不会冒泡;
原生组件,比如 textarea
、input
,在绑定事件时,不应使用 bind:
的方式绑定,而应使用 bindxxx
的方式绑定(不在中间加 :
)。
小程序中可以使用自定义事件的方式实现页面与组件通信。组件中使用 this.triggerEvent
调用自定义的事件。
this.triggerEvent('eventName', ...args);
在页面级组件中定义事件函数,并把事件函数传给子组件。
<component bind:myEvent="eventName" />
父组件可以使用 this.selectComponent
获取到子组件的实例,然后就可以调用子组件中的方法。
// 通过类名的方式获取子组件,并调用其 updateTime 方法
this.selectComponent('.curTime').updateTime(new Date());
子组件:
<component class="curTime" />
对于在同一页面中组件与组件之间的通信,可以用页面容器组件为桥接,使用自定义事件或 selectComponent
的方式进行通信。
A 页面在 B 页面跳转时,可以在 URL 上传递数据,但如果 B 页面的操作会影响到 A 页面(回退时页面的内容会发生变化)时,如何把 B 页面的数据回传到 A 页面?
可以使用微信小程序提供的 API,在 app.js 文件中,onLaunch
事件中可以定义 globalData
对象,它是存放公共数据的地方。
// app.js
App({
onLaunch: function () {
// 存放公共数据
this.globalData = {
a: 1
};
},
//
setA(id){
this.globalData.a = id;
},
getA(){
return this.globalData.a;
}
});
在其他页面或者组件中就可以访问到数据了。
const app = getApp();
console.log(app.getA());
场景:组将想要使用页面的 CSS 样式,比如使用字体图标的 class 类名,默认情况下,在组件中使用了字体图标的 class 类名,图标是是用不了的。要想使用可以这么做:
首先页面组件需要传入对应的 css 类名,例如:
<component iconfont="iconfont" />
上面代码,容器组件中给子组件 component 传入了一个 iconfont
属性,值是某个 css 的类名,也叫 iconfont
。子组件要想使用这个类名,可以在自己的脚本中设置:
Component({
externalClasses: [
"iconfont"
],
// ...
});
需要注意的是,引入的 class 类名是“只读的”,即在组件的 wxss 中我们不能再使用该类名编辑样式了,这是没有效果的,你应该再起一个类名。
除了使用 externalClasses
之外,还可以给组件设置 styleIsolation
配置项:
Component({
options: {
styleIsolation: 'isolated'
}
});
设置完之后,就可以直接使用页面级组件中的 css 样式了,也不用设置 externalClasses
。isolated
表示表示启用样式隔离,在自定义组件内外,使用 class 指定的样式将不会相互影响。
如果 styleIsolation
的值设置成 shared
则表示自定义组件 wxss 中指定的样式和页面的样式会相互影响。
四种方式:
open-data
例如:
<open-data type="userAvatarUrl">open-data>
<open-data type="userNickName">open-data>
<open-data type="userCity">open-data>
open-data
获取的用户信息只能用作展示,并且只能展示用户自己的信息。这不需要通知用户授权,因为这些信息只有用户自己可见。
wx.getUserInfo
API。它可以获取到用户的昵称、头像、性别、所在城市等信息。但这种情况下必须是在用户已经授权的情况下,才可以获取到。
wx.getUserInfo({
success: (res) => {
console.log();
}
});
<button open-type="getUserInfo" bindgetuserinfo="onGetUserInfo">
获取用户信息
button>
上面代码中的 button
按钮是特殊的,他有 open-type
属性和 bindgetuserinfo
属性,前者表示这时一个获取用户信息的按钮,后者表示当用户点击后触发的事件函数。事件函数需要我们自己定义,它有一个 event 对象参数。
onGetUserInfo(event){
console.log(event);
}
前两种方式是获取不到 openid
的。传统模式下,即有后端服务器,可以再小程序端调用 wx.login
从微信服务器中获取 code,然后调用 ex.request
将 code 传递给后端服务器,后端服务器拿到 code 后,需要请求微信服务器获取 openid
和 session_key
,获取到之后就可以把用户标识(openid
)发给小程序,并本地存储。
在云开发模式下,用户可以点击按钮,然后触发事件,从云函数中获取到用户信息,在小程序端还可以将用户信息存储到小程序的云数据库中。
在云开发中,小程序自带一个 login
云函数,我们可以让用户点击按钮后,调用云函数获取 openid
。
<button bindtap="getOpenid">获取openidbutton>
会发现,button 只是一个普通的按钮,其实我们也并不需要用户点击,直接调用云函数即可。
getOpenid(){
wx.cloud.callFunction({
name: 'login'
}).then(res => {
console.log(res);
});
}
需要注意的是,返回的 res
中会有 openid
,如果你想使用 openid
获取用户的个人信息,比如昵称、头像等,目前是做不到的,因为获取 openid 是不需要用户授权的,在用户没有授权的情况下(不知情)就可以获取到用户信息,对于用户信息是不安全的(涉及到用户的隐私)。
在 A 页面跳转到 B 页面后,在 B 页面进行了一些操作,然后回退到 A 页面,这时候我们想在回退后刷新 A 页面的内容,如何做到?
这是很常见的场景,比如在 B 页面发布了一些内容,A 页面是展示内容的,我们想在发布后回到 A 页面看到最新的内容。
可以使用 getCurrentPages
这个全局 API,代码如下:
wx.navigateBack();
// 获取到当前页面和上一个页面的对象
const pages = getCurrentPages();
// 取到上一个页面
const prevPage = pages[pages.length - 2];
// onPullDownRefresh 是下拉刷新的事件
// 开启下拉刷新功能需要来到页面的配置文件,添加 `enablePullDownRefresh` 为 `true`。
// 你应在该事件中请求新的数据,更新页面
prevPage.onPullDownRefresh(); // 刷新
图片预览使用下面的API:
wx.previewImage({
urls: this.data.images,
current: imageUrl
});
previewImage
中的 urls
表示要预览的图片列表,可以是超链接或者云文件ID,是图片的,current
表示当前被预览的图片 url 或云文件ID。
要把文件上传到云存储上可以使用 wx.cloud.uploadFile
这个 API,具体可以参考官网:wx.cloud.uploadFile
云调用是小程序·云开发提供的在云函数中调用微信开放接口的能力。它用于小程序云开发。
比如有时候用户在小程序中发表了评论,发表后应该提醒用户,如何做到这样的消息推送能力呢?
首先新建一个云函数,它的 config.json
配置如下:
{
"permissions": {
"openapi": [
"templateMessage.send"
]
}
}
templateMessage.send
是一个云调用函数,开发者可使用订阅消息功能。
编写的云函数内容如下:
exports.main = async (event, context) => {
// 拿到用户的 openid
const { OPENID } = cloud.getWXContext();
// 云调用,函数名称要与 config.json 中配置的一样
const result = await cloud.openapi.templateMessage.send({
touser: OPENID, // 用户 openid
// 点击模板卡片后的跳转页面,仅限本小程序内的页面。
page: `/pages/blogComment/blogComment?blogId=${event.blogId}`,
// 模板内容,不填则下发空模板。
data: {
time1: {
value: new Date().toLocaleString()
},
phrase2: {
value: '评价完成'
},
thing3: {
value: event.content
}
},
templateId: '所需下发的模板消息的id',
// 表单提交场景下,为 submit 事件带上的 formId
formId: event.formId
});
return result;
}
templateId
需要在微信公众平台 的 订阅消息 中添加,里面有很多模板。添加好后就会生成一个 模板ID。
用户输入了评论内容,并点击了发送,这时候就要把数据存入数据库中,评论成功后给用户发通知。代码如下:
wx.cloud.callFunction({
// 调用上面的云函数
name: 'sendMessage',
// 传入数据
data: {
content,
formId,
blogId: this.properties.blogId
}
}).then(res => {
console.log(res);
}).catch(err => {
// console.log(err);
});
有时候为了推广我们的小程序,在小程序内容通常会有分享功能,让用户把小程序的内容分享给他的好友。这个如何实现?
小程序中给 button
按钮提供了一个属性:open-type
属性,当值是 share
时,表示点击这个按钮会触发分享事件。
<button open-type="share">分享button>
用户点击后会触发 Page.onShareAppMessage
事件,即:页面级组件中的 onShareAppMessage
,如果按钮在一个普通组件中,那么它所在的页面组件的 onShareAppMessage
会被触发。
{
onShareAppMessage: function () {
const blog = this.data.blog;
return {
// 分享的标题
title: blog.content,
// 用户点击分享的内容时会跳转到哪个页面
path: `/pages/content?articleId=${blog._id}`
}
}
}
当用户发表评论时,可能并不知道用户的一些个人信息,我们想要获取到用户的一些信息,这可能需要用户的授权才能获取到,毕竟是用户的隐私信息。
点击一个按钮,弹出用户授权框:
onComment(){
// 发表评论,如果没有登录,就不能发表评论
// 点击评论时,判断用户是否已经授权
this.getUserAuth(res => {
userInfo = res.userInfo;
// 用户信息可以拿到,做一些其他的操作,比如把用户评论内容上传到云端
}, () => {
// 未授权,就弹出一个模态框告诉用户要授权才能发布评论
this.setData({
// 弹出授权对话框
showModal: true
});
});
}
getUserAuth
接收两个回调:已经授权的回调和未被授权的回调。getUserAuth
代码实现如下:
getUserAuth(yes, no){
wx.getSetting({
success(res){
if(res.authSetting['scope.userInfo']){
wx.getUserInfo({
success(res){
yes(res);
}
});
}else{ // 没有就弹出警告
no();
}
}
});
}
需要注意的是,button
按钮有专门用于获取用户数据的属性:
<button
open-type="getUserInfo"
bindgetuserinfo="onGetUserInfo"
>
bindgetuserinfo
是小程序一个内置的事件,它可以这么使用:
{
methods: {
onGetUserInfo(event){
const userInfo = event.detail.userInfo;
// 如果用户信息能拿到,说明用户点击了对话框中的“确定”
if(userInfo){
this.setData({
modalShow: false,
});
this.triggerEvent('loginSuccess', userInfo);
}else{
this.triggerEvent('loginFail');
}
}
}
}
在开发完小程序后,一般还要做一个后台管理系统,如果是云开发模式,如何在后台获取云数据?
我们需要准备这几样东西:
app-env
小程序的云开发环境变量,这个在微信开发者工具的 云开发控制台 中可以设置;AppID
在微信开发者工具中的 详情 可以找到,或者在微信公众平台中;AppSecret
密钥,可以在微信公众平台的 开发 中找到;流程如下:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Td4Z3glK-1601299117702)(./image/小程序登录流程.jpg)]
它是后台接口调用凭据,调用绝大多数后台接口时都需使用 access_token
如何获取到它可以看官网:access_token
需要注意的是:access_token 的有效期目前为 2 个小时,需定时刷新,获取新的 access_token
比如下面的服务器端代码,定时获取 access_token,把数据保存到本地的 json 文件中。
const URL = `https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=${APP_ID}&secret=${APP_SECRET}`;
const TOCKEN_PATH = path.join(__dirname, './access_token.json');
const TIMER_DELAY = (7200 - 300) * 1000; // 定时器更新时间间隔
// 更新token
async function updateAccessToken(){
const json = await rp(URL);
const result = JSON.parse(json);
if(result.access_token){
fs.writeFileSync(TOCKEN_PATH, JSON.stringify({
access_token: result.access_token,
createTime: new Date()
}));
}else{
await updateAccessToken();
}
}
// 获取token
async function getAccessToken(){
let hasFile = fs.existsSync(TOCKEN_PATH);
if(hasFile){
const json = fs.readFileSync(TOCKEN_PATH, { encoding: 'utf8' });
const result = JSON.parse(json);
// 对比时间差
const createTime = new Date(result.createTime).getTime();
const nowTime = new Date().getTime();
// 时间差是不是超过两个小时了
const value = (nowTime - createTime) / 1000 / 60 / 60;
if(value >= 2){
await updateAccessToken();
await getAccessToken();
}
return result.access_token;
}else{
await updateAccessToken();
await getAccessToken();
}
}
// 定时触发更新 token
setInterval(async () => {
await updateAccessToken();
}, TIMER_DELAY);
module.exports = {
getAccessToken,
updateAccessToken
};
导出一个访问云数据库的函数:
// callCloudFn.js
const callCloudFn = async (ctx, fnName, params) => {
const ACCESS_TOKEN = await getAccessToken();
const APP_ENV = ctx.state.env;
const URL = `https://api.weixin.qq.com/tcb/invokecloudfunction`;
const options = {
method: 'POST',
uri: URL,
// 查询参数,需要带上访问凭据、小程序的云开发环境变量和云函数名称
qs: {
access_token: ACCESS_TOKEN,
env: APP_ENV,
name: fnName
},
body: {
// params 会传递到云函数的 event 参数中
...params
},
json: true
};
return await rp(options).then(res => {
return res;
}).catch(err => {
// ...
});
}
module.exports = callCloudFn;
服务器端路由书写如下:
const Router = require('koa-router');
const callCloudFn = require('../utils/callCloudFn');
const router = new Router();
const CLOUD_FUNC_NAME = 'music'; // 云函数名称
router.get('/list', async (ctx, next) => {
const query = ctx.request.query;
// 传递给云函数的数据
const params = {
start: parseInt(query.start),
count: parseInt(query.count),
$url: 'playlist'
};
const result = await callCloudFn(ctx, CLOUD_FUNC_NAME, params);
ctx.body = {
data: JSON.parse(result.resp_data).data,
code: 20000
};
});
除了上面的方式之外,还可以使用数据库的方式查询数据库,首先导出一个访问数据库的公共函数:
// callCloudDB.js
const callCloudDB = async (ctx, fnName, query = {}) => {
const access_token = await getAccessToken();
const APP_ENV = ctx.state.env;
// fnName 表示数据库的查询方式,它可以有以下几个值:
// databasequery: 数据库查询记录
// databaseupdate: 数据库更新记录
// databasedelete: 数据库删除记录
// databaseadd: 数据库插入记录
const URL = `https://api.weixin.qq.com/tcb/${fnName}?access_token=${access_token}`;
const options = {
method: 'POST',
uri: URL,
body: {
// query 是数据库查询语句
query,
env: APP_ENV,
},
json: true
};
return await rp(options).then(res => {
return res;
});
}
module.exports = callCloudDB;
关于数据库的更多操作可以参考官方文档:数据库
调用如下:
router.get('/getById', async function fn(ctx) {
// 定义查询语句
const query = `db.collection('playlist').doc('${ctx.request.query.id}').get()`;
const res = await callCloudDB(ctx, 'databasequery', query);
ctx.body = {
code: 20000,
data: JSON.parse(res.data)
};
});
小程序云开发中还可以使用云存储,存储一些上传的文件。服务器也可以操作云存储,比如上传文件、下载文件和删除文件。具体可以参考官方文档:云存储
项 | 网页开发 | 小程序开发 |
---|---|---|
结构 | HTML | WXML |
样式 | CSS | WXSS |
逻辑 | JavaScript | JavaScript |
DOM操作 | DOM API | 无 |
渲染层和逻辑层 | 互斥的 | 分开的 |
运行环境 | 逻辑层 | 渲染层 |
---|---|---|
iOS | JavaScriptCore | WKWebView |
安卓 | V8 | 定制内核 |
小程序开发者工具 | NW.js | Chromiun WebView |
在用户体验方面,合理设置可点击元素的响应区域大小;
避免渲染页面耗时过长;
避免执行脚本耗时过长;
对网络请求做必要的缓存以避免多余的请求;
不要引入未被使用的 wxss 样式;
文字颜色与背景色搭配较好,适宜的颜色对比更方便用户阅读;
所有资源请求建议使用 HTTPS;
不使用废弃的小程序接口;
避免过大的 WXML 节点数量:
避免将不可能访问到的页面打包到小程序包里;
及时回收定时器;
避免使用 :active
伪类来实现点击态(可以使用小程序提供的 hover
、class-hover
);
在滚动区域可以开启惯性滚动以增强体验:
避免出现任何 JavaScript 异常;
所有请求应响应正常;
所有请求的耗时不应太久;
避免短时间内发送太多的图片请求,使用字体图标代替图片;
避免在短时间内发起太多的请求;
避免 setData
的数据过大;
避免 setData
的调用过去频繁;
避免将未绑定在 WXML 的变量传入 setData
,你可以在 Page
之外定义变量;