微信小程序基础
# 微信登录
https://developers.weixin.qq.com/ebook?action=get_post_info&docid=000cc48f96c5989b0086ddc7e56c0a
已有的互联网产品在接入小程序会面临一些和登录态相关的问题:怎么获取微信登录态;怎么把微信帐号和自己的帐号进行打通
# 小程序登录流程图
%
# 为什么不直接获取微信用户唯一 id
说到登录,我们可能很正常地想到一个做法:通过 wx.login 直接拿到微信用户的 id 编号[5],再把这个 id 传到自己的后台,从而知道是哪个微信用户在使用我的服务。而我们上述微信登录的流程中并不是通过 wx.login 直接获取微信用户的 id,那直接获取微信用户 id 的做法有什么问题呢?
假设现在我们有个接口,通过 wx.request 请求 https://test.com/getUserInfo?id=1 拉取到微信用户 id 为 1 在我们业务侧的个人信息,那么黑客就可以通过遍历所有的 id,把整个业务侧的个人信息数据全部拉走,如果我们还有其他接口也是依赖这样的方式去实现的话,那黑客就可以伪装成任意身份来操作任意账户下的数据,想想这给业务带来多大的安全风险。
为了避免这样的风险,wx.login 是生成一个带有时效性的凭证
,就像是一个会过期的临时身份证一样,在 wx.login 调用时,会先在微信后台生成一张临时的身份证,其有效时间仅为 5 分钟
[6]。 然后把这个临时身份证返回给小程序方,这个临时的身份证我们把它称为微信登录凭证 code。如果 5 分钟内小程序的后台不拿着这个临时身份证来微信后台服务器换取微信用户 id 的话,那么这个身份证就会被作废,需要再调用 wx.login 重新生成登录凭证。
由于这个临时身份证 5 分钟后会过期,如果黑客要冒充一个用户的话,那他就必须在 5 分钟内穷举所有的身份证 id,然后去开发者服务器换取真实的用户身份。显然,黑客要付出非常大的成本才能获取到一个用户信息,同时,开发者服务器也可以通过一些技术手段检测到 5 分钟内频繁从某个 ip 发送过来的登录请求,从而拒绝掉这些请求。
开发者服务器和微信服务器通信也是通过 HTTPS 协议,微信服务器提供的接口地址是:
https://api.weixin.qq.com/sns/jscode2session?appid=<AppId>&secret=<AppSecret>&js_code=<code>&grant_type=authorization_code
URL 的 query 部分的参数中 就是前文所提到的三个信息,请求参数合法的话,接口会返回以下字段。
我们暂时只要关注前两个字段即可
openid:
就是前文一直提到的微信用户 id,可以用这个 id 来区分不同的微信用户。
session_key:
则是微信服务器给开发者服务器颁发的身份凭证,开发者可以用 session_key 请求微信服务器其他接口来获取一些其他信息,由此可以看到,session_key 不应该泄露或者下发到小程序前端。
# 获取手机号
https://developers.weixin.qq.com/miniprogram/dev/OpenApiDoc/user-info/phone-number/getPhoneNumber.html
仅针对企业用户开放
<button
open-type="getPhoneNumber"
@getphonenumber="getPhoneNumber"
@click="jump"
>
立即授权
</button>
2
3
4
5
6
7
Page({
getPhoneNumber(e) {
console.log(e.detail.code)
}
})
2
3
4
5
通过 code 获取手机号
POST https://api.weixin.qq.com/wxa/business/getuserphonenumber?access_token=ACCESS_TOKEN
返回参数解析:
# 全局配置与页面配置
全局配置
小程序根目录下的 app.json 文件用来对微信小程序进行全局配置,决定页面文件的路径、窗口表现、设置网络超时时间、设置多 tab 等。
完整配置项说明请参考小程序全局配置
以下是一个包含了部分常用配置选项的 app.json :
{
"pages": ["pages/index/index", "pages/logs/index"],
"window": {
"navigationBarTitleText": "Demo"
},
"tabBar": {
"list": [
{
"pagePath": "pages/index/index",
"text": "首页"
},
{
"pagePath": "pages/logs/index",
"text": "日志"
}
]
},
"networkTimeout": {
"request": 10000,
"downloadFile": 10000
},
"debug": true
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
页面配置
每一个小程序页面也可以使用同名 .json 文件来对本页面的窗口表现进行配置,页面中配置项会覆盖 app.json 的 window 中相同的配置项。
完整配置项说明请参考小程序页面配置
例如:
{
"navigationBarBackgroundColor": "#ffffff",
"navigationBarTextStyle": "black",
"navigationBarTitleText": "微信接口功能演示", // 更改title
"backgroundColor": "#eeeeee",
"backgroundTextStyle": "light"
}
2
3
4
5
6
7
# App:小程序的唯一示例
每个小程序都需要在 app.js 中调用 App 方法注册小程序实例,绑定生命周期回调函数、错误监听和页面不存在监听函数等。
// app.js
App({
onLaunch(options) {
// Do something initial when launch.
},
onShow(options) {
// Do something when show.
},
onHide() {
// Do something when hide.
},
onError(msg) {
console.log(msg)
},
globalData: 'I am global data'
})
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
整个小程序只有一个 App 实例,是全部页面共享的。开发者可以通过 getApp
方法获取到全局唯一的 App 实例,获取 App 上的数据或调用开发者注册在 App 上的函数。
// xxx.js
const appInstance = getApp()
console.log(appInstance.globalData) // I am global data
2
3
# 小程序页面 Page
对于小程序中的每个页面,都需要在页面对应的 js 文件中进行注册,指定页面的初始数据、生命周期回调、事件处理函数等。
使用 Page 构造器注册页面
简单的页面可以使用 Page() 进行构造。
//index.js
Page({
data: {
text: 'This is page data.'
},
onLoad: function (options) {
// 页面创建时执行
},
onShow: function () {
// 页面出现在前台时执行
},
onReady: function () {
// 页面首次渲染完毕时执行
},
onHide: function () {
// 页面从前台变为后台时执行
},
onUnload: function () {
// 页面销毁时执行
},
onPullDownRefresh: function () {
// 触发下拉刷新时执行
},
onReachBottom: function () {
// 页面触底时执行
},
onShareAppMessage: function () {
// 页面被用户分享时执行
},
onPageScroll: function () {
// 页面滚动时执行
},
onResize: function () {
// 页面尺寸变化时执行
},
onTabItemTap(item) {
// tab 点击时执行
console.log(item.index)
console.log(item.pagePath)
console.log(item.text)
},
// 事件响应函数
viewTap: function () {
this.setData(
{
text: 'Set some data for updating view.'
},
function () {
// this is setData callback
}
)
},
// 自由数据
customData: {
hi: 'MINA'
}
})
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
# 使用 Component 构造器构造页面
Page 构造器适用于简单的页面。但对于复杂的页面, Page 构造器可能并不好用。
此时,可以使用 Component 构造器来构造页面。 Component 构造器的主要区别是:方法需要放在 methods: { } 里面。
代码示例:
Component({
data: {
text: 'This is page data.'
},
methods: {
onLoad: function (options) {
// 页面创建时执行
},
onPullDownRefresh: function () {
// 下拉刷新时执行
},
// 事件响应函数
viewTap: function () {
// ...
}
}
})
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
这种创建方式非常类似于 自定义组件 ,可以像自定义组件一样使用 behaviors 等高级特性。
# 小程序的 Store: behaviors
页面可以引用 behaviors 。 behaviors 可以用来让多个页面有相同的数据字段和方法。
// my-behavior.js
module.exports = Behavior({
data: {
sharedText: 'This is a piece of data shared between pages.'
},
methods: {
sharedMethod: function () {
this.data.sharedText === 'This is a piece of data shared between pages.'
}
}
})
2
3
4
5
6
7
8
9
10
11
// page-a.js
var myBehavior = require('./my-behavior.js')
Page({
behaviors: [myBehavior],
onLoad: function () {
this.data.sharedText === 'This is a piece of data shared between pages.'
}
})
2
3
4
5
6
7
8
# 页面路由
在小程序中所有页面的路由全部由框架进行管理。
注意事项
- navigateTo, redirectTo 只能打开非 tabBar 页面。
- switchTab 只能打开 tabBar 页面。
- reLaunch 可以打开任意页面。
- 页面底部的 tabBar 由页面决定,即只要是定义为 tabBar 的页面,底部都有 tabBar。
- 调用页面路由带的参数可以在目标页面的 onLoad 中获取。
# 事件列表
事件分类
事件分为冒泡事件和非冒泡事件:
- 冒泡事件:当一个组件上的事件被触发后,该事件会向父节点传递。
- 非冒泡事件:当一个组件上的事件被触发后,该事件不会向父节点传递。
WXML 的冒泡事件列表:
注:除上表之外的其他组件自定义事件如无特殊声明都是 非冒泡事件
,如 form 的 submit
事件,input 的 input
事件,scroll-view
的 scroll 事件
# 绑定并阻止事件冒泡
除 bind 外,也可以用 catch 来绑定事件。与 bind 不同, catch 会阻止事件向上冒泡。
例如在下边这个例子中,点击 inner view 会先后调用 handleTap3 和 handleTap2 (因为 tap 事件会冒泡到 middle view,而 middle view 阻止了 tap 事件冒泡,不再向父节点传递),点击 middle view 会触发 handleTap2,点击 outer view 会触发 handleTap1。
<view id="outer" bindtap="handleTap1">
outer view
<view id="middle" catchtap="handleTap2">
middle view
<view id="inner" bindtap="handleTap3"> inner view </view>
</view>
</view>
2
3
4
5
6
7
# 获取界面上 dom 节点信息
const query = wx.createSelectorQuery()
query.select('#the-id').boundingClientRect(function (res) {
res.top // #the-id 节点的上边界坐标(相对于显示区域)
})
query.selectViewport().scrollOffset(function (res) {
res.scrollTop // 显示区域的竖直滚动位置
})
query.exec()
2
3
4
5
6
7
8
# 样式穿透:让 css 影响子组件样式
Component({
options: {
styleIsolation: 'shared'
}
})
2
3
4
5
# 小程序监听数据变化
observers: {
inputName: function (e) {
// 使用 setData 设置 this.data.some.subfield 时触发
console.log('inputName', e)
},
dateText: function (e) {
console.log('dateText', e)
}
},
2
3
4
5
6
7
8
9
# 小程序利用 wxs 实现类似 computed 计算属性
// utils.wxs
module.exports = {
msg: 'hello world',
handleMatchStatus: function (source) {
switch (source) {
case '111':
return '已支付'
case '222':
return '支付失败'
case '333':
return '退款中'
default:
return 'other'
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<!-- .wxml -->
<wxs module="utils" src="./game-qr-utils.wxs"></wxs>
<view class="computed-data">
{{utils.msg}}{{utils.handleMatchStatus(selectedPlayer)}}
</view>
2
3
4
5
// .ts文件
Page({
data: {
userInfo: {},
playerList: [],
selectedPlayer: '111'
}
})
2
3
4
5
6
7
8
这样标签 computed-data 里的内容就可以根据 data 中的 selectedPlayer 数据实时变化
# 小程序路由携带参数
//more...
//假设当前页面路由为'/pages/demo/demo?a=1&b=2'
onLoad: function (options) { //options用于接收上个页面传递过来的参数
console.log(options.a, options.b); // =>1, 2
})
//more...
2
3
4
5
6
7
# 小程序 下载上传文件资源
test() {
wx.downloadFile({
url: 'http://172.27.15.109/img/system-logo.d3c30f52.png', //仅为示例,并非真实的资源
success(res) {
// 只要服务器有响应数据,就会把响应内容写入文件并进入 success 回调,业务需要自行判断是否下载到了想要的内容
if (res.statusCode === 200) {
wx.saveImageToPhotosAlbum({
filePath: res.tempFilePath,
success(res) {
console.log('保存成功')
}
})
}
}
})
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
上传
wx.chooseImage({
success(res) {
const tempFilePaths = res.tempFilePaths
wx.uploadFile({
url: 'https://example.weixin.qq.com/upload', //仅为示例,非真实的接口地址
filePath: tempFilePaths[0],
name: 'file',
formData: {
user: 'test'
},
success(res) {
const data = res.data
//do something
}
})
}
})
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 小程序:调用手机振动
wx.vibrateShort({ type: 'light' })
wx.vibrateLong()
2
3
# 微信小程序配置 ts import alias
%
使用 resolveAlias 配置项用来自定义模块路径的映射规则。
配置了之后,会对 require 里的模块路径进行规则匹配并映射成配置的路径。
如果命中多条映射规则,则取最长的命中规则。
如果需要修改映射名
需要更改更改 tsconfig.json
和 app.json
两个地方的配置
- app.json
{
"resolveAlias": {
"@/*": "/*"
}
}
2
3
4
5
- tsconfig.json
{
"compilerOptions": {
"paths": {
"@/*": ["./miniprogram/*"]
}
}
}
2
3
4
5
6
7
# 微信小程序-设置全局的颜色变量
- 在全局
app.wxss
样式中 设置变量颜色
/* 主题颜色 通过变量来实现 */
page {
--themeColor: #e33e33;
}
2
3
4
- 使用
.searchinput {
//使用 全局定义的变量 颜色
background-color: var(--themeColor);
}
2
3
4