搭建环境
- 官方地址下载开发工具
构建项目
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 => div
1. text => span
1. 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)
```javascript
wx.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
```javascript
import { 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;
// 加1
this.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’, })
- 多个参数
```javascript
let 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);
- 获取数据
```javascript
let 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>
// 创建sock
var 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() {
# 连接socket
wx.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文件
```css
page {
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.wxss
page {
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.js
Component({
/**
* 组件的属性列表
*/
properties: {
totalMoney: Number
},
/**
* 组件的初始数据
*/
data: {},
/**
* 组件的方法列表
*/
methods: {
handleClick() {
this.triggerEvent("submit", { name: "老胡", age: 100 });
}
}
});
父组件
// shopCart.wxml
<count totalMoney="{{totalMoney}}" bind:submit="onSubmit" />;
// shopCart.js
Page({
data: {
totalMoney: 10000,
},
onSubmit(data) {
console.log("data", data.detail);
},
});