共计 10743 个字符,预计需要花费 27 分钟才能阅读完成。
wepy 文档:https://tencent.github.io/wepy/
全局安装 wepy:npm i -g wepy-cli
初始化项目:wepy init standard myproject
切换到项目目录:cd myproject
安装依赖: npm install
wepy 项目目录
├── dist // 打包后的文件├── src // 开发文件目录│ ├── components // 组件目录│ ├── images // 图片文件目录│ ├── mixins // 通用函数│ ├── mocks // 模拟数据目录│ ├── pages // 小程序单个页面目录│ ├── store // redux 存储数据(页面传值)│ ├── app.wpy // 入口文件(配置页面的跳转)│ ├── index.html // 模版文件├── wepy.config.js // 配置文件
重要提醒
使用微信开发者工具 –> 添加项目,项目目录请选择 dist 目录。
微信开发者工具 –> 项目 –> 关闭 ES6 转 ES5。重要:漏掉此项会运行报错。
微信开发者工具 –> 项目 –> 关闭上传代码时样式自动补全。重要:某些情况下漏掉此项也会运行报错。
微信开发者工具 –> 项目 –> 关闭代码压缩上传。重要:开启后,会导致真机 computed, props.sync 等等属性失效。(注:压缩功能可使用 WePY 提供的 build 指令代替,详见后文相关介绍以及 Demo 项目根目录中的 wepy.config.js 和 package.json 文件。)
本地项目根目录运行 npm run dev(wepy build –watch),开启实时编译。(注:如果同时在微信开发者工具 –> 设置 –> 编辑器中勾选了文件保存时自动编译小程序,将可以实时预览,非常方便。)
开发介绍
index.template.html:为模块文件
其中通过 wepy.app 创建入口文件,wepy.page 创建页面文件,wepy.component 创建组件文件。
app.wpy:入口文件,包含 config,globalData,constructor 和生命周期。在 config 中配置路由
config:配置 pages,window,tabBar。分别为页面路由配置,页面导航栏配置,页面底部栏配置。详细配置在小程序框架配置
globalData: 全局参数配置,用于多个页面的公用参数。
constructor:拦截器的配置。
生命周期使用
其中如果在页面开发中需要用到 async/await 的话,需要在 app.wpy 中使用 import ‘wepy-async-function’ 加载模块,不然在编译后页面会报错,导致 async/await 无法使用。
// 设置路由,导航栏,底部栏
config = {
pages: [
‘pages/main’,
‘pages/admin-center’
],
window: {
backgroundTextStyle: ‘light’,
navigationBarBackgroundColor: ‘#fff’,
navigationBarTitleText: ‘WeChat’,
navigationBarTextStyle: ‘black’
},
tabBar = {
color: ‘#AEADAD’,
selectedColor: ‘#049BFF’,
backgroundColor: ‘#fff’,
borderStyle: ‘black’,
list: [{
pagePath: ‘pages/index’,
text: ‘ 首页 ’,
“iconPath”: “images/ico-home.png”,
“selectedIconPath”: “images/ico-home-d.png”
}, {
pagePath: ‘pages/admin-center’,
text: ‘ 借阅 ’,
“iconPath”: “images/ico-setting.png”,
“selectedIconPath”: “images/ico-setting-d.png”
}]
}
}
// 全局参数(方便后期各页面中的使用)
globalData = {
prizeList: [], // 领取的奖品列表
}
// 设置拦截器,intercept 为拦截器函数
constructor () {
super()
intercept(this)
}
// 页面加载
onLaunch(res) {
console.log(res)
}
wpy 模块中的属性
export default class Index @extends wepy.page{
customData = {} // 自定义数据
customFunction () {} // 自定义方法
onLoad () {} // 在 Page 和 Component 共用的生命周期函数
onShow () {} // 只在 Page 中存在的页面生命周期函数
config = {}; // 只在 Page 实例中存在的配置数据,对应于原生的 page.json 文件
data = {}; // 页面所需数据均需在这里声明,可用于模板数据绑定
components = {}; // 声明页面中所引用的组件,或声明组件中所引用的子组件
mixins = []; // 声明页面所引用的 Mixin 实例
computed = {}; // 声明计算属性(详见后文介绍)
watch = {}; // 声明数据 watcher(详见后文介绍)
methods = {}; // 声明页面 wxml 中标签的事件处理函数。注意,此处只用于声明页面 wxml 中标签的 bind、catch 事件,自定义方法需以自定义方法的方式声明
events = {}; // 声明组件之间的事件处理函数
}
属性
说明
config
页面配置对象,对应于原生的 page.json 文件,类似于 app.wpy 中的 config
components
页面组件列表对象,声明页面所引入的组件列表
data
页面渲染数据对象,存放可用于页面模板绑定的渲染数据
methods
wxml 事件处理函数对象,存放响应 wxml 中所捕获到的事件的函数,如 bindtap、bindchange, 不能用于声明自定义方法。
events
WePY 组件事件处理函数对象,存放响应组件之间通过 $broadcast、$emit、$invoke 所传递的事件的函数
其它
小程序页面生命周期函数,如 onLoad、onReady 等,以及其它自定义的方法与属性
wpy 中自定义的函数应当写在与 methods 平级的位置,不用写在 methods 中。
页面的跳转
页面的跳转需要先在 app.wpy 的 config 中的 pages 中设置页面的路由,在页面中通过 navigateTo 跳转到相应页面。在 wepy.page 的脚本中可以通过 this.$navigate({url:””}) 实现页面的跳转。而在 wepy.component 的组件中可以通过 this.$parent.$navigate({url:””}) 或 wepy.navigateTo 实现。
wepy.navigateTo({
url: ‘/pages/info’
})
wepy 中的生命周期
wepy 中的生命周期的钩子函数有:onLoad,onReady,onShow,onPrefetch 等,其中 onReady,onShow,onPrefetch 只有 wepy.page 中才有用。wepy.component 只支持 onLoad,其他都不会触发。
onLoad: 页面加载完成时调用,一个页面只会调用一次。(在路由跳转的时候通过 navigateTo 跳转的话 onload 会重新执行,通过 navigateBack 跳转的话 onLoad 不会重新执行)
onShow:页面显示的时候调用。
onReady: 页面中的所有资源加载完成时调用。
onPrefetch:在页面跳转时触发,用于预加载和预查询数据。
onUnload:在页面卸载时触发(通过 redirectTo,switchTab,navigateBack,reLaunch 会触发当前页面中的 onUnload,但 navigateTo 不会触发)。
生命周期顺序:onPrefetch > onLoad > onShow > onReady。
onPrefetch 这个生命周期是 wepy 中扩展的,它的触发需要通过 this.$navigate 及其他 wepy 封装的跳转方式才能实现。当设置 onPrefetch 后,可以在 onLoad 中设置变量来获取 onPrefetch 中返回的值。
案例:
onLoad (params, data) {
data.prefetch.then((list) => {
this.adminMath = list.chasucccnt.data.succcnt
this.recordInfo = list.adminCenter.data.challengeRecList
this.heightScore = list.adminCenter.data.hs
this.hadMath = list.adminCenter.data.cc
this.$apply()
})
}
// chasucccnt,getAdminCenter 为请求后台的函数,返回的数据结构是 {data:{succcnt:0}。
async onPrefetch () {
let chasucccnt = await this.chasucccnt()
let adminCenter = await this.getAdminCenter()
return new Promise((resolve, reject) => {
resolve({
chasucccnt: chasucccnt,
adminCenter: adminCenter
})
})
}
props 实现父子组件之间的传值
官方案例:
// parent.wpy
<child :title = ‘parentTitle’ :syncTitle.sync = ‘parentTitle’ :twoWayTitle = ‘parentTitle’></child>
在 script 中的设置
data = {
parentTitle: ‘p-title’
}
// child.wpy
props = {
// 静态传值
title: String,
// 父向子单向动态传值
syncTitle: {
type: String,
default: ‘null’
},
twoWayTitle: {
type: Number,
default: ‘nothing’,
twoWay: true
}
};
onLoad () {
console.log(this.title); // p-title
console.log(this.syncTitle); // p-title
console.log(this.twoWayTitle); // p-title
this.title = ‘c-title’;
console.log(this.$parent.parentTitle); // p-title.
this.twoWayTitle = ‘two-way-title’;
this.$apply();
console.log(this.$parent.parentTitle); // two-way-title. — twoWay 为 true 时,子组件 props 中的属性值改变时,会同时改变父组件对应的值
this.$parent.parentTitle = ‘p-title-changed’;
this.$parent.$apply();
console.log(this.title); // ‘c-title’;
console.log(this.syncTitle); // ‘p-title-changed’ — 有.sync 修饰符的 props 属性值,当在父组件中改变时,会同时改变子组件对应的值。
}
有上案例可以知道:title 为静态传值,即只有第一次有效,后面改变值后子组件中的 title 不会发生改变,当在属性后添加.sync 后,即该属性发生改变会导致子组件中相应的值发生改变,当在子组件中的 props 中设置 twoWay: true 后,可以实现父子组件的双向绑定。
组件之间的数据通信
wepy 中的通信主要采用三种方法:$broadcast, $emit, $invoke;
$broadcast: 父组件触发所有子组件(包含子孙级组件)事件。
$emit: 子组件触发所有父组件(包含祖先级组件)事件。当在父组件属性中使用.user 设置自定义事件后,$emit 可以用于触发自定义事件而 events 中声明的函数将不会再执行。(与 vue 中的用法不同,vue 中需要在父组件中设置子组件的属性,用于在子组件中触发。而 wepy 中则不需要,只要在 events 中设置方法就可以在子组件中触发。)
$invoke: 页面或子组件触发另一个子组件事件。
parent.wpy
<template>
<view>
<children @childFun.user = ‘someEvent’></children>
</view>
</template>
<script>
export default class Parent extends wepy.page{
data = {
name: ‘parent’
}
events = {
‘some-event’: (p1, p2, p3, $event) => {
// 输出为 ’parent receive some-event children’,$event.source 指向子组件。
console.log(`${this.name} receive ${$event.name} from ${$event.source.name}`)
}
}
onLoad () {
this.$broadcast(‘getIndex’, 1, 4)
}
methods = {
someEvent (…p) {
// 输出 [1, 2, 3, _class]。
console.log(p)
}
}
}
</script>
children.wpy
<script>
export default class Parent extends wepy.page{
data = {
name: ‘children’
}
onLoad () {
// this.$emit(‘some-event’, 1, 2, 3)
// 触发组件中的自定义事件
this.$emit(‘childFun’, 1, 2, 3)
}
events = {
‘getIndex’: (…p) => {
console.log(p) // 输出 [1, 4]
}
}
}
在父组件中给子组件添加属性 @childFun.user = ‘someEvent’ 后,在子组件中修改触发条件 this.$emit(‘childFun’, 1, 2, 3)
//$invoke
父组件向子组件发送事件:
使用 import 导入子组件后,在使用时可以直接通过
this.$invoke(‘ 子组件,必须要单引号括起来 ’, ‘ 子组件方法名称 ’, param1,param2,param3…….);
子组件间发送事件:
this.$invoke(‘ 子组件的相对路径 ’, ‘ 子组件方法名称 ’, param1,param2,param3…….);
子组件的相对路径的理解:当设置 ’./’ 即当前组件,’../’ 为父组件,以此类推。它可以指定向哪一个组件分发内容,但只适用于简单的组件树结构,复杂的结构考虑使用 redux。
在子组件中使用 $emit 会触发父组件及祖先组件中的相同事件。在父组件中使用 $broadcast 会触发子组件及子孙组件中的相同事件。其中 $emit 和 $broadcast 触发的事件设置在组件中的 events 中,而 $invoke 触发的函数与 events 是平级的,当在组件中已经通过 components 导入组件的话,$invoke 中的第一个参数可以直接设置为 components 中的值,当设置相对路径时,即根据当前组件在整个组件树中的位置来确定路径,它可以指定向特定的组件传值,但当结构复杂时,需要嵌套的路径会比较复杂,不好维护,考虑用 redux 实现。
Mixins 混合
默认式混合(模块中的 data 数据,components,events, 自定义事件)
当在 wepy.mixin 中设置了和页面中相同的函数或变量时,以当前页面的函数和变量为主,mixin 中的函数和变量会失效。官方案例:
// mixin.js
export default class TestMixin extends wepy.mixin {
data = {
foo: ‘foo defined by page’,
bar: ‘bar defined by testMix’
};
methods: {
tap () {
console.log(‘mix tap’);
}
}
}
….
import wepy from ‘wepy’;
import TestMixin from ‘./mixins/test’;
export default class Index extends wepy.page {
data = {
foo: ‘foo defined by index’
};
mixins = [TestMixin];
onShow() {
console.log(this.foo); // foo defined by index
console.log(this.bar); // bar defined by testMix
}
}
在 mixin 中申明的函数可以调用引入 mixin 后的页面的 data 数据和函数。mixin 中的 this 指向当前页面组件。
兼容式混合(生命周期)
当在 mixin 中设置生命周期一类的钩子函数时,会优先执行 mixin 中的生命周期,再执行页面中的函数。
// mixin.js
export default class TestMixin extends wepy.mixin {
onLoad () {
console.log(2222)
}
}
….
import wepy from ‘wepy’;
import TestMixin from ‘./mixins/test’;
export default class Index extends wepy.page {
data = {
foo: ‘foo defined by index’
};
mixins = [TestMixin];
onLoad() {
console.log(11111);
}
}
结果打印为:
2222
11111
wxs 的使用,实现过滤器
在项目目录下新建 wxs 文件夹,在该文件夹中新增 wxs 文件。新增内容如下:
// 设置一个过滤器对超过 10000 的数字进行转化
module.exports = {
filter: function (num) {
if (num < 10000) {
return num
} else {
var reNum = (num / 10000).toFixed(1)
return reNum + ‘ 万 ’
}
}
}
在页面中通过 import 导入该过滤器:
// template 中使用过滤器,mywxs 对应下方 wxs 中设置的 key 值
<view>{{mywxs.filter(mItem.playerCount)}} 人 </view>
…..
import mywxs from ‘@/wxs/fixed.wxs’
export default class Index extends wepy.page{
……
wxs = {
mywxs: mywxs
}
…..
}
导入的 wxs 文件只能在 template 中使用,不能在 js 中使用
Promise 和 async/await 的使用(脏数据的检测)
// 录音
async endVideo (event) {
let endTime = event.timeStamp
let startTime = this.startTime
this.videoInfo = ‘2131’
let video = await new Promise((resolve, reject) => {
recorderManager.onStop((res) => {
console.log(‘recorder stop’, res)
const {tempFilePath} = res
resolve(tempFilePath)
})
recorderManager.stop()
})
if ((endTime – startTime) > 1000) {
this.videoInfo = video
this.$apply()
}
}
当使用异步函数的时候,我们如果需要重新渲染页面的时候,需要手动调用 $apply() 方法,才能触发脏数据检查流程的运行。
群转发
用于实现微信小程序的分享功能,获取每个群的独立 ID。在上弹窗中默认有转发按钮,可以通过使用 hideShareMenu 来隐藏弹窗中的转发按钮.
用法
在 wpy 类型文件的 onload 中设置
// 用于显示分享的群列表
wepy.showShareMenu({
withShareTicket: true
})
在 onShareAppMessage 中设置分享的界面,在分享成功后的回调函数中通过 wepy.getShareInfo 获取分享群的信息。群的信息是经过加密的,需要通过后台来进行解密操作。
onShareAppMessage (res) {
if (res.from === ‘button’) {
console.log(res.target)
}
return {
title: ‘ 自定义转发标题 ’,
path: ‘/pages/main’,
success: function(res) {
let shareId = res.shareTickets[0]
// 转发成功
wepy.getShareInfo({
shareTicket: shareId,
success: (data) => {
var appId = ‘ 小程序的 appID’
var encryptedData = data.encryptedData
var iv = data.iv
wepy.request({
url: ‘http://localhost:3000/api/decode’,
method: ‘post’,
data: {
appId: appId,
encryptedData: encryptedData,
iv: iv
},
success: (info) => {
console.log(‘info:’ + info)
},
fail: (info) => {
console.log(info)
}
})
console.log(data)
},
fail: (data) => {
console.log(data)
}
})
console.log(res)
},
fail: function(res) {
// 转发失败
console.log(res)
}
}
}
其中 onShareAppMessage 需要在 wepy.page 中设置才有效果,在 wepy.component 中设置无效果。在 onShareAppMessage 中的 path 设置的参数可以跟随?ie= 值,然后在对应的页面中设置 onLoad 来获取跟随的值
微信小程序和本地接口对接,模拟请求数据
后台采用 express,代码如下:
var express = require(‘express’);
var app = express();
app.listen(3000, function () {
console.log(‘listen:3000’);
})
设置前后台的对接接口:
var router = express.Router();
var bodyParser = require(‘body-parser’);
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({extended: true}));
router.get(‘/info’, function (req, res) {
console.log(req.query);
console.log(req.body);
}
app.use(‘/api’, router) // 前台通过 ajax 向 ’/api/info’ 发送请求
加载请求模块(因为前台无法直接向 https://api.weixin.qq.com/sns…,该域名无法添加到小程序的服务器域名中):
var request = require(‘request’);
// 在 ’/info’ 配置的接口中使用, 获取 session_key。
request.get({
uri: ‘https://api.weixin.qq.com/sns/jscode2session’,
json: true,
qs: {
grant_type: ‘authorization_code’,
appid: ‘ 小程序的 appID’,
secret: ‘ 小程序的密钥 ’,
js_code: code
}
}, (err, response, data) => {
if (response.statusCode === 200) {
console.log(“[openid]”, data.openid)
console.log(“[session_key]”, data.session_key)
session_key = data.session_key
//TODO: 生成一个唯一字符串 sessionid 作为键,将 openid 和 session_key 作为值,存入 redis,超时时间设置为 2 小时
// 伪代码: redisStore.set(sessionid, openid + session_key, 7200)
res.json({
openid: data.openid,
session_key: data.session_key
});
} else {
console.log(“[error]”, err)
res.json(err)
}
})
解密
在官方案例中下载解密需要的文件 WXBizDataCrypt.js。从案例中知道需要的参数有 4 个:
1. AppID(微信小程序的 id,在微信小程序的后台设置中有),
2. session_key(用户登录的时候可以获取到),
3. encryptedData(需要解密的字符串,在获取的群信息中加密的字段),
4. iv(算法初始向量,在获取的群信息中有返回)。
在 app.js 中引入 WXBizDataCrypt.js:
// 解密模块
var WXBizDataCrypt = require(‘./WXBizDataCrypt’)
// 解密数据
router.post(‘/decode’, function (req, res) {
var appId = req.body.appId;
var sessionKey = session_key;
var encryptedData = req.body.encryptedData;
var iv = req.body.iv;
var pc = new WXBizDataCrypt(appId, sessionKey);
var data = pc.decryptData(encryptedData , iv);
res.json({data: data});
})