[TOC]
### 课前准备
#### 微信小程序介绍
**什么是微信小程序?**
[官方文档](https://developers.weixin.qq.com/miniprogram/introduction/)
小程序的一种。
微信小程序是一种不需要下载安装即可使用的应用,它实现了应用“触手可及”的梦想,用户扫一扫或者搜一下即可打开应用。
也体现了“用完即走”的理念,用户不用关心是否安装太多应用的问题。应用将无处不在,随时可用,但又无需安装卸载。
对于开发者而言,微信小程序开发门槛相对较低,难度不及APP,能够满足简单的基础应用,适合生活服务类线下商铺以及非刚需低频应用的转换。微信小程序能够实现消息通知、线下扫码、公众号关联等七大功能。
其中,通过公众号关联,用户可以实现公众号与微信小程序之间相互跳转。由于微信小程序不存在入口。
**为什么要用微信小程序?**
1. 微信用户量大
2. 开发成本低
3. 推广成本低
4. 跨平台
5. 无需安装, 用完即走
**发展历程**
* 2016年1月11日,微信之父张小龙解读了微信的四大价值观, 提出「微信小程序」概念
* 2016年9月21日,微信小程序正式开启内测。
* 2017年1月9日0点,万众瞩目的微信第一批微信小程序正式低调上线。
* 2017年12月28日,微信更新的 6.6.1 版本开放了小游戏,微信启动页面还重点推荐了小游戏「跳一跳」。
* 2018年3月,微信正式宣布微信小程序广告组件启动内测。
**其他小程序**
* 支付宝小程序
* 百度小程序
* 字节跳动小程序
* QQ小程序
* 360小程序
#### 开发环境
1.注册微信小程序, [微信小程序官网](https://mp.weixin.qq.com/?token=&lang=zh_CN)
2.获取 APPID
3.[微信开发者工具](https://developers.weixin.qq.com/miniprogram/dev/devtools/download.html)
一般, 我们不直接使用微信开发者工具进行代码编写, 只是使用微信开发者工具作为预览和调试使用
编写代码的话, 可以使用我们熟悉的开发工具, 安装对应的小程序插件即可
比如 vscode webstorm 等编辑器
以为 webstorm 为例:
打开设置, 找到 `plugins`, 搜索 `wxapp` 安装即可
#### 第一个小程序
打开微信开发者工具, 扫码登录
新建小程序项目
可以先使用测试号
#### 微信开发者工具介绍
[微信开发者工具](https://developers.weixin.qq.com/miniprogram/dev/devtools/devtools.html)
### 配置文件和生命周期
#### 目录结构
```
├── pages // 页面文件夹
│ ├── index // 首页
│ │ ├── index.js // 首页逻辑文件
│ │ ├── index.json // 首页配置文件
│ │ ├── index.wxml // 首页视图文件
│ │ └── index.wxss // 首页样式文件
│ └── logs // 日志页面
│ ├── logs.js
│ ├── logs.json
│ ├── logs.wxml
│ └── logs.wxss
└── utils // 工具文件夹
│ └── util.js
├── app.js // 全局入口文件
├── app.json // 全局配置文件
├── app.wxss // 全局样式文件
├── project.config.json // 项目配置文件
├── sitemap.json // 索引配置文件
```
#### 全局配置
小程序根目录下的 app.json 文件用来对微信小程序进行全局配置,决定页面文件的路径、窗口表现、设置网络超时时间、设置多 tab 等。
**pages配置**
定义页面的路径, 便于微信识别页面的位置
优先显示第一个配的页面
在微信开发者工具中添加pages配置会自动创建目录
**window配置**
定义所有页面的顶部样式
**tabBar配置**
底部 tab 栏的表现
#### 页面配置
每一个小程序页面也可以使用 .json 文件来对本页面的窗口表现进行配置。页面中配置项在当前页面会覆盖 app.json 的 window 中相同的配置项。
#### stiemap配置
可以通过 sitemap.json 配置,或者管理后台页面收录开关来配置其小程序页面是否允许微信索引。
#### 生命周期
**应用生命周期**
|函数|说明|
| --- | --- |
| onLaunch | 监听小程序初始化 |
| onShow | 监听小程序显示(切前台) |
| onHide | 监听小程序隐藏(切后台) |
| onError | 监听小程序异常 |
| onPageNotFound | 监听页面不存在 |
**页面生命周期**
|函数|说明|
| --- | --- |
| onLoad | 监听页面加载 |
| onShow | 监听页面显示 |
| onReady | 监听页面初次渲染完成 |
| onHide | 监听页面隐藏 |
| onUnload | 监听页面卸载 |
| onPullDownRefresh | 监听用户下拉动作 |
| onReachBottom | 监听页面上拉触底 |
| onPageScroll | 监听页面滚动 |
| onResize | 页面尺寸改变时触发 |
### 模板语法
#### 数据绑定
**简单绑定**
WXML 中的动态数据均来自对应 Page 的 data。
数据绑定使用 Mustache 语法(双大括号)将变量包起来
```
<view> {{ message }} </view>
Page({
data: {
message: 'Hello MINA!'
}
})
```
**运算**
可以在 {{}} 内进行简单的运算
```
<view hidden="{{flag ? true : false}}"> Hidden </view>
```
**数组对象**
```
<view wx:for="{{[1,2,3]}}">
{{item}}
</view>
```
#### 列表渲染
在组件上使用 wx:for 控制属性绑定一个数组,即可使用数组中各项的数据重复渲染该组件。
默认数组的当前项的下标变量名默认为 index,数组当前项的变量名默认为 item
```
<view wx:for="{{array}}">
{{index}}: {{item.message}}
</view>
Page({
data: {
array: [{
message: 'foo',
}, {
message: 'bar'
}]
}
})
```
#### 条件渲染
在框架中,使用 wx:if="" 来判断是否需要渲染该代码块:
```
<view wx:if="{{condition}}"> True </view>
```
也可以用 wx:elif 和 wx:else 来添加一个 else 块:
```
<view wx:if="{{length > 5}}"> 1 </view>
<view wx:elif="{{length > 2}}"> 2 </view>
<view wx:else> 3 </view>
```
#### 使用block
`<block/>` 并不是一个组件,它仅仅是一个包装元素,不会在页面中做任何渲染,只接受控制属性。
如果不在遍历或者条件的判断的时候渲染组件本身, 可以使用 `<block />` 组件:
```
<block wx:for="list">
<view>{{item}}</view>
<block/>
```
#### 模板和引用
WXML提供模板(template),可以在模板中定义代码片段,然后在不同的地方调用。
如果想把模板放到单独的文件中, 使用在需要使用的地方引用
WXML 提供两种文件引用方式import和include。
#### wxs用法
WXS(WeiXin Script)是小程序的一套脚本语言,结合 WXML,可以构建出页面的结构。
WXS 与 JavaScript 是不同的语言,有自己的语法,并不和 JavaScript 一致。
#### wxss样式
WXSS (WeiXin Style Sheets)是一套样式语言,用于描述 WXML 的组件样式。
WXSS 用来决定 WXML 的组件应该怎么显示。
为了适应广大的前端开发者,WXSS 具有 CSS 大部分特性。同时为了更适合开发微信小程序,WXSS 对 CSS 进行了扩充以及修改。
与 CSS 相比,WXSS 扩展的特性有:
* 尺寸单位
* 样式导入
**尺寸单位**
rpx(responsive pixel): 可以根据屏幕宽度进行自适应。规定屏幕宽为750rpx。如在 iPhone6 上,屏幕宽度为375px,共有750个物理像素,则750rpx = 375px = 750物理像素,1rpx = 0.5px = 1物理像素。
设备| rpx换算px (屏幕宽度/750)| px换算rpx (750/屏幕宽度)
---|---|---
iPhone5| 1rpx = 0.42px| 1px = 2.34rpx
iPhone6| 1rpx = 0.5px| 1px = 2rpx
iPhone6 Plus| 1rpx = 0.552px| 1px = 1.81rpx
**样式导入**
使用@import语句可以导入外联样式表,@import后跟需要导入的外联样式表的相对路径,用;表示语句结束。
### 常用组件和自定义组件
#### view、text、image
```
<!-- 1. <view>组件, 代替 div -->
<view hover-class="bg-red">视图容器组件</view>
<!-- 2. <text>组件,代替 span -->
<text user-select decode>文本组件 hello</text>
<!--
3. <imgage>组件
使用网络资源, 不要使用本地图片
mode属性可以设置图片的裁剪方式
scaleToFill 默认, 宽高拉伸满,占满容器
aspectFit 长边拉伸满,短边自适应, 常用
aspectFill 短边拉伸满, 长边会裁剪, 不常用
widthFix 宽度100%, 高度自适应, 常用
-->
<image class="image" mode="widthFix" src="https://res.wx.qq.com/wxdoc/dist/assets/img/0.4cb08bb4.jpg" />
```
#### navigator、button
```
<!--
4. <navigator> 超链接 是一个块级元素
target 跳转目标,默认是当前小程序
open-type 跳转的方式
navigate 默认, 跳转之后,可以返回
redirect 关闭当前页面,跳转之后不能返回
switchTab 跳转到 tabBar 页面,并关闭其他所有非 tabBar 页面
reLaunch 关闭所有页面,打开到应用内的某个页面
navigateBack 关闭当前页面,返回上一页面
-->
<navigator url="/pages/page5/page5">跳转到page5</navigator>
<navigator url="/pages/page5/page5" open-type="redirect">redirect 跳转到page5</navigator>
<navigator url="/pages/index/index" open-type="switchTab">switchTab index</navigator>
<navigator url="/pages/index/index" open-type="reLaunch">reLaunch index</navigator>
<!--
5. <button> 按钮
open-type 按钮的开放能力
contact 打开微信客服, 在微信小程序后台设置微信客服, 得用正式的appid, 不能使用测试好, 得真机测试才行
share 转发给朋友,不能发送朋友圈
getPhoneNumber 获取用户手机号, 配合bindgetphonenumber事件来使用,获取到的是加密后的数据,要传给服务端进行解密,服务端得到手机号之后,再返回给小程序
getUserInfo 获取用户信息 2021年4月13号启用废弃,2021年4月13号启用以后的小程序使用 getUserProfile
launchApp 打开app,只能回跳到app
openSetting 打开授权设置页
feedback 打开反馈页面,小程序后台可以获取到反馈的内容, 得真机测试
-->
<button size="mini" type="primary" loading="{{true}}">按钮1</button>
<button open-type="contact">contact</button>
<button open-type="share">share</button>
<button open-type="getPhoneNumber" bindgetphonenumber="bindgetphonenumber">getPhoneNumber</button>
<button open-type="getUserInfo" bindgetuserinfo="bindgetuserinfo">getUserInfo</button>
<button bindtap="getUserInfo">获取用户信息 2021年4月13号启用</button>
<button open-type="launchApp">launchApp</button>
<button open-type="openSetting">openSetting</button>
<button open-type="feedback">feedback</button>
```
#### swiper、rich-text
```
<!--
<swiper> 滑块容器
1. swiper 有默认的宽高 宽100% 高150px
2. image 默认宽320px 高240px
3. 让 swiper 的高度,通过使用的图片的宽高比计算出来
s_width / s_height = i_width / i_height
750rpx / s_height = 700 / 277
s_height = 750rpx * 277 / 700
4. 常用属性
autoplay 自动播放
interval 指定播放间隔
-->
<swiper autoplay interval="2000" indicator-dots circular>
<swiper-item wx:for="{{imgs}}" wx:key="*this" wx:for-item="url">
<image src="{{url}}"/>
</swiper-item>
</swiper>
<!-- <rich-text> 渲染html -->
<rich-text nodes="{{html}}" />
```
#### form、radio、checkedbox
```
<form bindsubmit="handleForm">
用户名:
<input name="username" placeholder="请输入用户名" bindinput="handleUsername" />
密码:
<input name="password" placeholder="请输入密码" password="{{true}}" bindinput="handlePassword"/>
性别:
<radio-group name="sex" bindchange="handleSex">
<radio color="#ff4238" value="男" />男
<radio color="#ff4238" value="女" />女
</radio-group>
爱好:
<checkbox-group name="hobby" bindchange="handleHobby">
<checkbox value="打篮球" checked="{{true}}" />打篮球
<checkbox value="踢足球" />踢足球
<checkbox value="乒乓球" />乒乓球
</checkbox-group>
<button form-type="submit">提交</button>
</form>
<view style="margin-top: 40px;">
<view>用户名: {{username}}</view>
<view>密码: {{password}}</view>
<view>性别: {{sex}}</view>
<view>爱好: {{hobby}}</view>
</view>
Page({
/**
* 页面的初始数据
*/
data: {
username: '',
password: '',
sex: '',
hobby: []
},
/**
* 获取用户名
*/
handleUsername(e) {
// this.setData({username: e.detail.value})
},
/**
* 获取密码
*/
handlePassword(e) {
// this.setData({password: e.detail.value})
},
/**
* 获取性别
*/
handleSex(e) {
// this.setData({sex: e.detail.value})
},
/**
* 获取爱好
*/
handleHobby(e) {
// this.setData({hobby: e.detail.value})
},
/**
* 获取表单数据
*/
handleForm(e) {
this.setData({...e.detail.value})
}
})
```
#### 自定义组件
**创建和使用自定义组件**
组件模板的写法与页面模板相同。组件模板与组件数据结合后生成的节点树,将被插入到组件的引用位置上。
在组件模板中可以提供一个 <slot> 节点,用于承载组件引用时提供的子节点。
**Component构造器**
```
Component({
properties: {
// 这里定义了innerText属性,属性值可以在组件使用时指定
innerText: {
type: String,
value: 'default value',
}
},
data: {
// 这里是一些组件内部数据
someData: {}
},
methods: {
// 这里是一个自定义方法
customMethod: function(){}
}
})
```
**向子组件传递数据**
在
**子组件向父组件传递数据**
父组件:
```
<!-- 当自定义组件触发“myevent”事件时,调用“onMyEvent”方法 -->
<component-tag-name bindmyevent="onMyEvent" />
<!-- 或者可以写成 -->
<component-tag-name bind:myevent="onMyEvent" />
```
子组件:
```
<!-- 在自定义组件中 -->
<button bindtap="onTap">点击这个按钮将触发“myevent”事件</button>
Component({
properties: {},
methods: {
onTap: function(){
var myEventDetail = {} // detail对象,提供给事件监听函数
var myEventOption = {} // 触发事件的选项
this.triggerEvent('myevent', myEventDetail, myEventOption)
}
}
})
```
### 常用Api
#### 路由跳转
|Api|说明|
|---|---|
|wx.switchTab|跳转到 tabBar 页面,并关闭其他所有非 tabBar 页面|
|wx.reLaunch|关闭所有页面,打开到应用内的某个页面|
|wx.redirectTo|关闭当前页面,跳转到应用内的某个页面。但是不允许跳转到 tabbar 页面|
|wx.navigateTo|保留当前页面,跳转到应用内的某个页面。但是不能跳到 tabbar 页面。使用 wx.navigateBack 可以返回到原页面。|
|wx.navigateBack|关闭当前页面,返回上一页面或多级页面。可通过 getCurrentPages 获取当前的页面栈,决定需要返回几层。|
#### 页面交互
|Api|说明|
|---|---|
|wx.showToast|显示消息提示框|
|wx.showModal|显示模态对话框|
|wx.showLoading|显示 loading 提示框。需主动调用 wx.hideLoading 才能关闭提示框|
|wx.hideHomeButton|隐藏返回首页按钮|
#### 网络请求
每个微信小程序需要事先设置通讯域名,小程序只可以跟指定的域名进行网络通信。包括普通 HTTPS 请求(wx.request)、上传文件(wx.uploadFile)、下载文件(wx.downloadFile) 和 WebSocket 通信(wx.connectSocket)。
**wx.request**
发起 HTTPS 网络请求。
```
wx.request({
url: 'test.php', //仅为示例,并非真实的接口地址
data: {
x: '',
y: ''
},
header: {
'content-type': 'application/json' // 默认值
},
success (res) {
console.log(res.data)
}
})
```
data 参数说明:
最终发送给服务器的数据是 String 类型,如果传入的 data 不是 String 类型,会被转换成 String 。转换规则如下:
- 对于 GET 方法的数据,会将数据转换成 query string(encodeURIComponent(k)=encodeURIComponent(v)&encodeURIComponent(k)=encodeURIComponent(v)...)
- 对于 POST 方法且 header['content-type'] 为 application/json 的数据,会对数据进行 JSON 序列化
- 对于 POST 方法且 header['content-type'] 为 application/x-www-form-urlencoded 的数据,会将数据转换成 query string (encodeURIComponent(k)=encodeURIComponent(v)&encodeURIComponent(k)=encodeURIComponent(v)...)
**RequestTask**
网络请求任务对象
#### 数据缓存
|Api|说明|
|---|---|
|wx.setStorage|将数据存储在本地缓存中指定的 key 中。会覆盖掉原来该 key 对应的内容|
|wx.setStorageSync|wx.setStorage 的同步版本|
|wx.getStorage|从本地缓存中异步获取指定 key 的内容。|
|wx.clearStorageSync|wx.clearStorage 的同步版本|
|wx.removeStorage|从本地缓存中移除指定 key|
|wx.removeStorageSync|wx.removeStorage 的同步版本|
|wx.clearStorage|清理本地数据缓存|
|wx.clearStorageSync()|wx.clearStorage 的同步版本|
#### 开放接口
**wx.login**
调用接口获取登录凭证(code)。
获取到的凭证发送给后端, 后端通过凭证(code), 请求微信Api,换取用户信息(openid,unionid等)
后端通过openid或者unionid查询数据库, 返回自定义的登录状态
小程序存储用户的登录状态
### 商城项目
#### 注册微信小程序
[注册微信小程序](https://mp.weixin.qq.com/wxopen/waregister?action=step1&token=&lang=zh_CN)
#### 初始化项目
新建项目
设置项目的TabBar
#### 引入Vant Weapp
[Vant Weapp地址](https://vant-contrib.gitee.io/vant-weapp/#/home)
#### openid和unionid
openid: 微信用户相对于不同的公众号, 不同的小程序, 都有唯一的用户标识, 这个标识就是openid, 在同一个公众号或者同一个小程序中, 微信用户的openid是唯一的, 即每个微信用户的openid是不同的
unionid:微信开放平台帐号下的唯一标识, 只有公众号或者小程序绑定到微信开放平台帐号才能获取到unionid, 同一个微信用户, 在同一个开放平台unionid是惟一的, 用做在不同的公众号或者不同的小程序中,识别同一个用户
#### wx.getUserProfile和wx.login
wx.getUserProfile: 获取用户信息。页面产生点击事件(例如 button 上 bindtap 的回调中)后才可调用,每次请求都会弹出授权窗口,用户同意后返回 userInfo。
wx.login:调用接口获取登录凭证(code)。通过凭证进而换取用户登录态信息,包括用户在当前小程序的唯一标识(openid)、微信开放平台帐号下的唯一标识(unionid,若当前小程序已绑定到微信开放平台帐号)及本次登录的会话密钥(session_key)等。