搭建环境
- 官方地址下载开发工具
构建项目
app.json全局配置文档
- 解释
```json
{
页面路径
“pages”: [ “pages/index/index”, “pages/cart/cart”, “pages/detail/detail”, “pages/my/my”, “pages/demo/demo” ],
用于设置小程序的状态栏、导航条、标题、窗口背景色
“window”: {
颜色
“navigationBarBackgroundColor”: “#f8f8f8”,
导航栏标题文字内容
“navigationBarTitleText”: “花间”,
导航栏标题颜色,仅支持 black / white
“navigationBarTextStyle”: “black”,
下拉 loading 的样式,仅支持 dark / light
“backgroundTextStyle”: “dark”,
背景颜色
“backgroundColor”: “#f8f8f8” },
底部tab栏
“tabBar”: {
选中的tab颜色
“selectedColor”: “#c03131”,
默认颜色
“color”: “#797d82”,
tab列表配置
“list”: [ { “iconPath”: “./img/home.png”, “selectedIconPath”: “./img/homeing.png”, “pagePath”: “pages/index/index”, “text”: “首页” }, { “iconPath”: “./img/cart.png”, “selectedIconPath”: “./img/carting.png”, “pagePath”: “pages/cart/cart”, “text”: “购物车” }, { “iconPath”: “./img/my.png”, “selectedIconPath”: “./img/mying.png”, “pagePath”: “pages/my/my”, “text”: “个人中心” }, { “iconPath”: “./img/my.png”, “selectedIconPath”: “./img/mying.png”, “pagePath”: “pages/demo/demo”, “text”: “demo” } ] },
使用的组件放在这里
“usingComponents”: {
}, “sitemapLocation”: “sitemap.json” }
<a name="V9wXn"></a>## 基础<a name="us43j"></a>### 静态页面1. 标签1. view => div1. text => span1. img => [image](https://developers.weixin.qq.com/miniprogram/dev/component/image.html)1. 地图,音视频,画布等2. 可同时使用px、rpx 作为像素单位,`1px = 2rpx `2. page 标签为页面的最外层的容器2. [底部 tab 配置](https://developers.weixin.qq.com/miniprogram/dev/reference/configuration/app.html#tabBar)2. [开放数据](https://developers.weixin.qq.com/miniprogram/dev/component/open-data.html)2. 编译模式2. [在小程序中使用 vant-ui](https://vant-contrib.gitee.io/vant-weapp/#/home)2. [小程序中使用 less](https://developers.weixin.qq.com/community/develop/article/doc/000e427c49c218e6b9781bfdf5b013)<a name="Sw2z2"></a>### 生命周期| 周期函数 | 作用 || --- | --- || onLoad | 监听页面加载 || onReady | 监听页面初次渲染完成 || onShow | 监听页面显示 || onHide | 监听页面隐藏 || onUnload | 监听页面卸载 || onPullDownRefresh | 监听用户下拉刷新 || onReachBottom | 监听用户上拉触底事件 || onShareAppMessage | 监听用户点击右上角分享 |**onLoad 与 onShow 的区别:**- onLoad 只会在小程序中执行一次;即当前页面执行过 onLoad 函数时,然后切出该页面再切入时,onLoad 函数不再执行- onShow 每次切回页面时,都会触发这个生命周期函数<a name="HwtWa"></a>### 请求数据1. 关闭合法域名检测- 小程序只支持 https,项目中请求了非 https 和不在域名白名单上的接口会报错- 需要到小程序后台配置域名白名单,小程序右上角详情 =>本地设置 => 不校验合法域名...2. 发送请求[文档地址](https://developers.weixin.qq.com/miniprogram/dev/api/network/request/wx.request.html)```javascriptwx.request({# 请求地址url: "test.php",data: {x: "",y: "",},# 请求方式method: "get",# 默认值,定义响应数据的格式header: {"content-type": "application/json",},# 成功的回调success(res) {console.log(res.data);};# 失败的回调fail(error) {console.log(error);};# 不管是成功还是失败都会调用此方法complete() {console.log("done");};});
封装小程序的请求
本地地址
baseUrl = “https://localhost:3009“; } else if (env === “prod”) { baseUrl = “https://huruqing.cn:3009“; }
导出
export { baseUrl };
- reques.js```javascriptimport { baseUrl } from "./config.js";/*** 封装请求* url:请求地址* data:请求参数* method: 请求类型* */const request = (url, data, method) => {# 获取token,登录时存的let token = wx.getStorageSync("token");url = baseUrl + url;return new Promise((resolve, reject) => {# 请求wx.request({url,method,data,header: {"user-token": token,},success: (res) => {if (res.data.code == 666) {resolve(res.data);} else if (res.data.code == 603) {wx.removeStorageSync("token");wx.showModal({title: "提示",content: "登录已过期,是否重新登录",success(res) {if (res.confirm) {# 跳转到个人中心页面wx.switchTab({url: "/pages/my/my",});} else if (res.cancel) {console.log("用户点击取消");}},});} else {reject(res.data.msg);}},fail: (err) => {reject("网络异常");},});});};const get = (url, data) => {return request(url, data, "get");};const post = (url, data) => {return request(url, data, "post");};export default {get,post,};
渲染页面
data 和 setData
Page({data: {count: 1,},changeCount() {// 获取data里count的值let count = this.data.count;// 加1this.setData({count: ++count,});},});
插值表达式
{{}},wxml 中所有的变量都要使用{{}},(除了wx:key)- 如果数组成员是字符串或者数字,
wx:key="index"或wx:key="*this" - 如果数组成员是对象, 比如:
[{name:'zs',id:1}],wx:key="id"
- 如果数组成员是字符串或者数字,
- 条件渲染
wx:if - 列表渲染
wx:for默认有 item 和 index - 双重
wx:for时需要其中一个指定 item 和 index绑定事件
例子
<view data-username="xxxx" bindtap="tapName"> Click me! </view>
Page({tapName(event) {# 小程序事件不能像 vue 那样传参,只能通过自定义属性来传参let username = event.target.dataset.username;console.log(username);},});
- 注意:定义的实参名只能小写,大驼峰式需要用
data-user-name="xxxx"格式
- 常见事件类型 | 类型 | 触发条件 | | —- | —- | | touchstart | 手指触摸动作开始 | | touchmove | 手指触摸后移动 | | touchcancel | 手指触摸动作被打断,如来电提醒,弹窗 | | touchend | 手指触摸动作结束 | | tap | 手指触摸后马上离开 | | longpress | 手指触摸后,超过 350ms 再离开,如果该事件被触发,tap 事件将不被触发 | | transitionend | 会在 WXSS transition 或 wx.createAnimation 动画结束后触发 | | animationstart | 会在一个 WXSS animation 动画开始时触发 | | animationiteration | 会在一个 WXSS animation 一次迭代结束时触发 | | animationend | 会在一个 WXSS animation 动画完成时触发 | | touchforcechange | 在支持 3D Touch 的 iPhone 设备,重按时会触发 |
页面跳转
非 tab 栏跳转
标签式
<navigator url="/pages/index/index"> 跳转到新页面 </navigator>
函数式
// 普通页面跳转wx.navigateTo({url: "/pages/index/index",});
tab 栏跳转
标签式
<navigator url="/pages/index/index" open-type="switchTab"> 切换 Tab </navigator>
函数式
wx.switchTab({url: "pages/index/index",});
重定向
标签式
<navigator url="/pages/index/index" open-type="redirect"> 在当前页打开 </navigator>
函数式
# 但不允许跳转到 tabbar 页面wx.redirectTo({url: "/pages/index/index"})
跳转到小程序
- 标签式
```html
打开绑定的小程序
- 标签式
```html
<a name="vSjkU"></a>### 路由传参- 跳转的 tabBar 页面的路径后不能带参数。1. 标签式传参```html<navigator wx:for="{{flowerList}}" url="/pages/detail/detail?flowerId={{item.flowerId}}" wx:key="{{flowerId}}"> 路由传参 </navigator>
- 接收
onLoad: function (options) {console.log(options.flowerId);},
- 函数式传参
- 单个参数 ```javascript wx.navigateTo({ url: ‘/pages/detail/detail?flowerId=213213312fwefef’, })
- 多个参数```javascriptlet obj = {useranme: 'zs',age:100};# 将对象转成json字符串let query = JSON.stringify(obj);wx.navigateTo({url: '/pages/detail/detail?query='+query,})
数据缓存
异步方法
存储数据
wx.setStorage({key: "key",data: "value",});
获取数据
wx.getStorage({key: "key",success(res) {console.log(res.data);},});
同步方法
- 存储数据
```javascript
wx.setStorageSync(“
“, “ “);
- 存储数据
```javascript
wx.setStorageSync(“
// 例子 let token = ‘asdfasdfasjdflasdjf;asdf;asdfjsak;ldf’; wx.setStorageSync(‘token’,token);
- 获取数据```javascriptlet token = wx.getStorageSync('token');
进阶
用户授权
- 用户授权的功能列表,必须绑定点击事件, 用户去点击才能调起授权, 我想应该是腾讯想提醒用户要慎重
录音授权
wx.authorize({# 授权类型scope: "scope.record",success() {# 用户已经同意小程序使用录音功能,后续调用 wx.startRecord 接口不会弹窗询问console.log('授权成功');},});
获取用户信息授权
getUserProfile(e) {wx.getUserProfile({# 必须要有该授权的描述desc: '用于完善会员资料',success: (res) => {console.log(res);}})},
获取用户手机号码流程文档地址
<button open-type="getPhoneNumber" bindgetphonenumber="getPhoneNumber"> 获取手机号码</button>
getPhoneNumber (e) {console.log(e.detail.errMsg);console.log(e.detail.iv);console.log(e.detail.encryptedData);}
步骤:
- 需要将 button 组件
open-type的值设置为getPhoneNumber,当用户点击并同意之后,可以通过bindgetphonenumber事件回调获取到微信服务器返回的加密数据 (跟上面获取用户信息步骤类似) - 调用wx.login接口, 获得一个code
- 拿到加密数据之后, 调用后台服务器接口, 把加密数据和 code 传过去
服务器获得传过去的加密数据之后,对数据进行解密(调用腾讯相关接口),得到了手机号码,再返回给前端
登录流程
获取用户信息(需要授权), 绑定点击事件
- 获取用户登录需要的code, 调用wx.login
- 调用后台登录接口, 传入参数
- iv, encryptedData, userInfo(这几个参数在第1步里获得)
- code(这个参数在第2步里获得)
- 登录成功后获得token
- 重置token信息, 以更新当前页面用户状态
- 把token存储在缓存里
- 调用的接口的时候把token放在请求头上(详看请求封装)
```javascript Page({<text bindtap="login">立即登录</text>
页面的初始数据
data: { token: ‘’ },
登录
async login() {
获取用户信息
let userData = await this.getUserInfo();
获取code,这行代码不能放在获取用户信息之前
let code = await this.getCode(); let data = {
code,iv: userData.iv,encryptedData: userData.encryptedData,userInfo: userData.userInfo,
}
调用后台登录接口, 传入数据
app.$post(‘/user/wxlogin’, data).then(res=> {
let token = res.result.token;this.setData({token: token})# 把token放入缓存wx.setStorageSync('token', token);
}).catch(err=> {
console.log(err);
}) },
- 需要将 button 组件
获取code
getCode() { return new Promise((resolve, reject) => {
# 获取code(调用微信接口需要)wx.login({success(res) {# 成功之后, 把code保存起来resolve(res.code);},fail(err) {reject(err);}})})
},
获取用户信息
getUserInfo() {
# 调起用户授权界面,获取用户信息return new Promise((resolve, reject) => {wx.getUserProfile({desc: '用于完善会员资料',success: (res) => {# 成功之后,保存用户信息resolve(res);},fail(err) {reject(err);}})});
} })
获取缓存里的token, 登录成的时候放入的
let token = wx.getStorageSync(“token”); url = baseUrl + url; return new Promise((resolve, reject) => {
请求
wx.request({ url, method, data,
# 把token放入请求头header: {"user-token": token},
}) }
<a name="iFGbg"></a>### 地图选址- [文档地址](https://lbs.qq.com/miniProgram/plugin/pluginGuide/locationPicker)- [申请开发者密钥(key)](https://lbs.qq.com/dev/console/key/add)1. 在 app.json 里添加插件- 引入插件包: 地图选点 `appId: wx76a9a06e5b4e693e````json{"plugins": {"chooseLocation": {"version": "1.0.6","provider": "wx76a9a06e5b4e693e"}}}
- 设置定位授权
{"permission": {"scope.userLocation": {"desc": "你的位置信息将用于小程序定位"}}}
使用插件
<button bindtap="getAddress">获取地址</button><view class="mt-10">{{address}}</view>
```javascript const chooseLocation = requirePlugin(‘chooseLocation’); Page({ data: { address: ‘’, latitude: ‘’, longitude:’’ },
getAddress() {
使用在腾讯位置服务申请的key
const key = ‘xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx’;
调用插件的app的名称
const referer = ‘花间小程序’; const location = JSON.stringify({ latitude: this.data.latitude, longitude: this.data.longitude }); const category = ‘生活服务,娱乐休闲’; wx.navigateTo({ url: ‘plugin://chooseLocation/index?key=’ + key + ‘&referer=’ + referer + ‘&location=’ + location + ‘&category=’ + category }); },
onShow() {
# 获取当前位置wx.getLocation({type: 'wgs84',success :(res)=> {this.setData({latitude : res.latitude,longitude : res.longitude})}})# 选址结束后, 点击确认返回本页面, 下面的代码就能获取选中的地址信息const location = chooseLocation.getLocation();this.setData({address: location && location.name;})
} })
<a name="JnYfe"></a>### 支付流程- [csdn 参考文档](https://blog.csdn.net/qq_38378384/article/details/80882980)- [简书 参考文档](https://www.jianshu.com/p/0835f03c3543)1. 步骤:1. 小程序端请求后端创建订单,把需要的参数传给后台1. 后端调用小程序统一下单的 API,获取预支付订单信息,给前端返回预支付信息1. [小程序支付流流程](https://pay.weixin.qq.com/wiki/doc/api/wxa/wxa_api.php?chapter=7_4&index=3)1. [统一下单 api](https://pay.weixin.qq.com/wiki/doc/api/wxa/wxa_api.php?chapter=9_1)1. 给前端返回支付所需参数3. 小程序端使用 wx.requestPayment 调起支付窗口1. [文档地址](https://developers.weixin.qq.com/miniprogram/dev/api/open-api/payment/wx.requestPayment.html)1. 举例:```javascript<button bindtap="submit">提价订单</button>const app = getApp();Page({submit() {app.$post('/order/create').then(res => {let obj = res.result;# 调用商户端下单接口成功后, 获取所需参数# 调起微信支付窗口, 传入所需参数wx.requestPayment({# 时间戳timeStamp: obj.timeStamp,# 随机字符串nonceStr: obj.nonceStr,# 统一下单接口返回的 prepay_id 参数值package: obj.package,# 签名类型signType: obj.signType,# 签名paySign: obj.paySign,# 支付成功的回调success(res) {console.log(res);},# 支付失败的回调fail(err) {console.log(err);},});})},})
- 小程序端查询支付结果,获取结果后给用户展示支付结果
- 获取支付结果
- 方式1: 小程序端轮询商户端获得支付结果
- 方式2: 小程序使用socket监听商户服务器socket接口,商户端给小程序推送接口
- 相对而言, 使用轮询会消耗一定的性能, 服务器支持的话, 尽量还是使用socket
- 展示支付结果
- 小程序支付模式图
socket
- http 请求是单向, 服务器不能给web发信息, 而 socket 的双向的, 可以互发信息
- 微信支付可以使用 socket 由服务器给前端推送支付结果
- 服务器端代码
- app.js ```javascript const Koa = require(“koa”); const path = require(“path”); const websockify = require(“koa-websocket”); const app = websockify(new Koa()); const serv = require(“koa-static”); app.use(serv(__dirname + “/public”));
app.ws.use((ctx, next) => {
给前端发信息
let count = 1;
setInterval(() => {
ctx.websocket.send(
${count++}. 万能的朋友圈, 想入手一个机械键盘,有没有推荐的, 我两秒后再来问
);
}, 2000);
监听前端发来的信息
ctx.websocket.on(“message”, function (message) { console.log(message); }); });
app.listen(3000,()=>{ console.log(‘http://localhost:3000‘); });
- 根目录创建 public 目录, 然后创建 index.html 文件```javascript<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Document</title></head><body><script>// 创建sockvar ws = new WebSocket("ws://localhost:3000");ws.onopen = function (evt) {console.log("连接成功");ws.send("我是张三");};// 监听服务器信息ws.onmessage = function (evt) {document.write("收到服务器端发来的信息: " + evt.data+'<br/>');};ws.onclose = function (evt) {console.log("Connection closed.");};</script></body></html>
小程序代码
Page({onShow() {# 连接socketwx.connectSocket({url: 'ws://localhost:3000'}),# 监听连接状态wx.onSocketOpen(function (res) {console.log('socket 已連接')}),# 监听服务器信息wx.onSocketMessage(res=> {console.log(res);}),}}
自定义组件
- 以购物车的结算栏为例
- 创建组件
- 右键点击开发工具中的 components => 新建目录 count
- 右键 count 目录 => 新建组件 => 输入 count(组件的名称)
编写静态文件
- wxml文件
```html
合计 ¥99.00
结算 - wxss文件```csspage {background: #ebebeb;}.count-box {height: 45px;background: #fff;position: fixed;bottom: 0;width: 100%;display: flex;justify-content: space-between;padding-left: 20px;align-items: center;font-size: 16px;}.count-box .money {color: #e1544d;margin-left: 5px;}.count-box .total {color: #666;}.count-box .count {text-align: center;line-height: 45px;background: #e1544d;width: 120px;color: #fff;}
- wxml文件
```html
// js文件暂且不动
使用组件
- 新建一个页面,名字随意,比如 shopCart,
- 编写 shopCart 页面的代码
// shopCart.json代码,注册组件{"usingComponents": {"count": "../../components/count/count"}}
<!-- shopCart.wxml 代码 --><count></count>
// js也暂时不动
至此我们已经能在 shopCart 页面看到了 count 组件展示出来的内容了
(四) 父给子传参
shopCart.wxml(父组件) 添加一个总价 totalMoney
<count totalMoney="99.99"></count>// 如果要传变量,请使用<count totalMoney="{{xxx}}"></count>
count(子组件) 组件接收参数,需要改动两个地方
- 修改 count.js 文件,在 properties 里添加 ```javascript properties: { totalMoney: Number },
// 或者写成这样 properties: { totalMoney: {
type: Number,default: '0.00'
} }, ```
- 修改 count.wxml
<count totalMoney="{{totalMoney}}"></count>
(五) 事件和自组件传参
shopCart.wxml(父组件) 自定义一个事件,同时给这个事件绑定一个函数
<count totalMoney="99.00" bind:submit="onSubmit"></count>
shopCart.js(父组件) 编写 onSubmit 函数,event.detail 接收子组件传来的数据
onSubmit(event) {console.log(event.detail);},
修改 count.wxml(子组件), 绑定点击事件
<!-- wxml --><view class="count-box"><view><text class="total">合计</text><text class="money">¥{{totalMoney}}</text></view><view class="count" bindtap="handleClick">结算</view></view>
修改 count.js(子组件), 在 methods 添加 handleClick,使用 triggerEvent 触发父组件的自定义事件
methods: {handlClick() {this.triggerEvent('submit',{name:'老胡',age: 100})}}
点击结算的时候,父组件的自定义就被触发,控制台打印出{name:’老胡’,age: 100}
(六) 完整代码
自定义组件(子组件)
// count.xml<view class="count-box"><view><text class="total">合计</text><text class="money">¥{{totalMoney}}</text></view><view class="count" bindtap="handleClick">结算</view></view>// count.wxsspage {background: #ebebeb;}.count-box {height: 45px;background: #fff;position: fixed;bottom: 0;width: 100%;display: flex;justify-content: space-between;padding-left: 20px;align-items: center;font-size: 16px;}.count-box .money {color: #e1544d;margin-left: 5px;}.count-box .total {color: #666;}.count-box .count {text-align: center;line-height: 45px;background: #e1544d;width: 120px;color: #fff;}// count.jsComponent({/*** 组件的属性列表*/properties: {totalMoney: Number},/*** 组件的初始数据*/data: {},/*** 组件的方法列表*/methods: {handleClick() {this.triggerEvent("submit", { name: "老胡", age: 100 });}}});
父组件
// shopCart.wxml<count totalMoney="{{totalMoney}}" bind:submit="onSubmit" />;// shopCart.jsPage({data: {totalMoney: 10000,},onSubmit(data) {console.log("data", data.detail);},});
