wepy文檔:https://tencent.github.io/wepy/
全局安裝wepy: npm i -g wepy-cli
初始化項(xiàng)目: wepy init standard myproject
切換到項(xiàng)目目錄: cd myproject
安裝依賴: npm install
wepy目錄:
├── dist // 打包后的文件
├── src // 開發(fā)文件目錄
│ ├── components // 組件目錄
│ ├── images // 圖片文件目錄
│ ├── mixins // 通用函數(shù)
│ ├── mocks // 模擬數(shù)據(jù)目錄
│ ├── pages // 小程序單個(gè)頁面目錄
│ ├── store // redux存儲(chǔ)數(shù)據(jù)(頁面?zhèn)髦担?br>
│ ├── app.wpy // 入口文件(配置頁面的跳轉(zhuǎn))
│ ├── index.html // 模版文件
├── wepy.config.js // 配置文件
重要提醒
- 使用微信開發(fā)者工具-->添加項(xiàng)目,項(xiàng)目目錄請選擇dist目錄秒咨。
- 微信開發(fā)者工具-->項(xiàng)目-->關(guān)閉ES6轉(zhuǎn)ES5霍狰。 重要:漏掉此項(xiàng)會(huì)運(yùn)行報(bào)錯(cuò)韧拒。
- 微信開發(fā)者工具-->項(xiàng)目-->關(guān)閉上傳代碼時(shí)樣式自動(dòng)補(bǔ)全潮剪。 重要:某些情況下漏掉此項(xiàng)也會(huì)運(yùn)行報(bào)錯(cuò)砸捏。
- 微信開發(fā)者工具-->項(xiàng)目-->關(guān)閉代碼壓縮上傳。 重要:開啟后,會(huì)導(dǎo)致真機(jī)computed, props.sync 等等屬性失效爬骤。(注:壓縮功能可使用WePY提供的build指令代替,詳見后文相關(guān)介紹以及Demo項(xiàng)目根目錄中的wepy.config.js和package.json文件莫换。)
- 本地項(xiàng)目根目錄運(yùn)行npm run dev(wepy build --watch)霞玄,開啟實(shí)時(shí)編譯。(注:如果同時(shí)在微信開發(fā)者工具-->設(shè)置-->編輯器中勾選了文件保存時(shí)自動(dòng)編譯小程序拉岁,將可以實(shí)時(shí)預(yù)覽坷剧,非常方便。)
開發(fā)
index.template.html: 為模塊文件
其中通過
wepy.app
創(chuàng)建入口文件喊暖,wepy.page
創(chuàng)建頁面文件惫企,wepy.component
創(chuàng)建組件文件。
app.wpy:入口文件,包含config狞尔,globalData丛版,constructor和生命周期。在config中配置路由
- config:配置pages偏序,window页畦,tabBar。分別為頁面路由配置研儒,頁面導(dǎo)航欄配置豫缨,頁面底部欄配置。詳細(xì)配置在小程序框架配置
- globalData: 全局參數(shù)配置殉摔,用于多個(gè)頁面的公用參數(shù)州胳。
- constructor: 攔截器的配置。
- 生命周期使用
其中如果在頁面開發(fā)中需要用到async/await的話逸月,需要在app.wpy中使用import 'wepy-async-function'加載模塊,不然在編譯后頁面會(huì)報(bào)錯(cuò)遍膜,導(dǎo)致async/await無法使用碗硬。
// 設(shè)置路由,導(dǎo)航欄瓢颅,底部欄
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"
}]
}
}
// 全局參數(shù)(方便后期各頁面中的使用)
globalData = {
prizeList: [], // 領(lǐng)取的獎(jiǎng)品列表
}
// 設(shè)置攔截器挽懦,intercept為攔截器函數(shù)
constructor () {
super()
intercept(this)
}
// 頁面加載
onLaunch(res) {
console.log(res)
}
wpy模塊中的屬性
export default class Index @extends wepy.page{
customData = {} // 自定義數(shù)據(jù)
customFunction () {} //自定義方法
onLoad () {} // 在Page和Component共用的生命周期函數(shù)
onShow () {} // 只在Page中存在的頁面生命周期函數(shù)
config = {}; // 只在Page實(shí)例中存在的配置數(shù)據(jù)翰意,對應(yīng)于原生的page.json文件
data = {}; // 頁面所需數(shù)據(jù)均需在這里聲明,可用于模板數(shù)據(jù)綁定
components = {}; // 聲明頁面中所引用的組件信柿,或聲明組件中所引用的子組件
mixins = []; // 聲明頁面所引用的Mixin實(shí)例
computed = {}; // 聲明計(jì)算屬性(詳見后文介紹)
watch = {}; // 聲明數(shù)據(jù)watcher(詳見后文介紹)
methods = {}; // 聲明頁面wxml中標(biāo)簽的事件處理函數(shù)冀偶。注意,此處只用于聲明頁面wxml中標(biāo)簽的bind渔嚷、catch事件进鸠,自定義方法需以自定義方法的方式聲明
events = {}; // 聲明組件之間的事件處理函數(shù)
}
屬性 | 說明 |
---|---|
config | 頁面配置對象,對應(yīng)于原生的page.json文件形病,類似于app.wpy中的config |
components | 頁面組件列表對象客年,聲明頁面所引入的組件列表 |
data | 頁面渲染數(shù)據(jù)對象,存放可用于頁面模板綁定的渲染數(shù)據(jù) |
methods | wxml事件處理函數(shù)對象漠吻,存放響應(yīng)wxml中所捕獲到的事件的函數(shù)量瓜,如bindtap、bindchange,不能用于聲明自定義方法途乃。 |
events | WePY組件事件處理函數(shù)對象绍傲,存放響應(yīng)組件之間通過 |
其它 | 小程序頁面生命周期函數(shù)唧取,如onLoad铅鲤、onReady等,以及其它自定義的方法與屬性 |
wpy中自定義的函數(shù)應(yīng)當(dāng)寫在與methods平級的位置枫弟,不用寫在methods中邢享。
頁面的跳轉(zhuǎn)
頁面的跳轉(zhuǎn)需要先在app.wpy
的config
中的pages
中設(shè)置頁面的路由,在頁面中通過navigateTo跳轉(zhuǎn)到相應(yīng)頁面淡诗。在wepy.page的腳本中可以通過this.$navigate({url:""})
實(shí)現(xiàn)頁面的跳轉(zhuǎn)骇塘。而在wepy.component的組件中可以通過this.$parent.$navigate({url:""})
或wepy.navigateTo
實(shí)現(xiàn)。
wepy.navigateTo({
url: '/pages/info'
})
wepy中的生命周期
wepy中的生命周期的鉤子函數(shù)有:onLoad韩容,onReady款违,onShow,onPrefetch等群凶,其中onReady插爹,onShow,onPrefetch
只有wepy.page中才有用请梢。wepy.component只支持onLoad赠尾,其他都不會(huì)觸發(fā)。
- onLoad: 頁面加載完成時(shí)調(diào)用毅弧,一個(gè)頁面只會(huì)調(diào)用一次气嫁。(在路由跳轉(zhuǎn)的時(shí)候通過
navigateTo
跳轉(zhuǎn)的話onload
會(huì)重新執(zhí)行,通過navigateBack
跳轉(zhuǎn)的話onLoad
不會(huì)重新執(zhí)行) - onShow:頁面顯示的時(shí)候調(diào)用够坐。
- onReady: 頁面中的所有資源加載完成時(shí)調(diào)用寸宵。
- onPrefetch:在頁面跳轉(zhuǎn)時(shí)觸發(fā),用于預(yù)加載和預(yù)查詢數(shù)據(jù)元咙。
- onUnload: 在頁面卸載時(shí)觸發(fā)(通過redirectTo梯影,switchTab,navigateBack蛾坯,reLaunch會(huì)觸發(fā)當(dāng)前頁面中的onUnload光酣,但navigateTo不會(huì)觸發(fā))。
生命周期順序:onPrefetch > onLoad > onShow > onReady脉课。
onPrefetch
這個(gè)生命周期是wepy中擴(kuò)展的救军,它的觸發(fā)需要通過this.$navigate
及其他wepy封裝的跳轉(zhuǎn)方式才能實(shí)現(xiàn)。當(dāng)設(shè)置onPrefetch
后倘零,可以在onLoad
中設(shè)置變量來獲取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為請求后臺的函數(shù),返回的數(shù)據(jù)結(jié)構(gòu)是{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實(shí)現(xiàn)父子組件之間的傳值
官方案例:
// parent.wpy
<child :title = 'parentTitle' :syncTitle.sync = 'parentTitle' :twoWayTitle = 'parentTitle'></child>
在script中的設(shè)置
data = {
parentTitle: 'p-title'
}
// child.wpy
props = {
// 靜態(tài)傳值
title: String,
// 父向子單向動(dòng)態(tài)傳值
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時(shí)拷泽,子組件props中的屬性值改變時(shí),會(huì)同時(shí)改變父組件對應(yīng)的值
this.$parent.parentTitle = 'p-title-changed';
this.$parent.$apply();
console.log(this.title); // 'c-title';
console.log(this.syncTitle); // 'p-title-changed' --- 有.sync修飾符的props屬性值,當(dāng)在父組件中改變時(shí)司致,會(huì)同時(shí)改變子組件對應(yīng)的值拆吆。
}
有上案例可以知道
:title
為靜態(tài)傳值,即只有第一次有效脂矫,后面改變值后子組件中的title不會(huì)發(fā)生改變枣耀,當(dāng)在屬性后添加.sync后,即該屬性發(fā)生改變會(huì)導(dǎo)致子組件中相應(yīng)的值發(fā)生改變庭再,當(dāng)在子組件中的props
中設(shè)置twoWay: true
后捞奕,可以實(shí)現(xiàn)父子組件的雙向綁定。
組件之間的數(shù)據(jù)通信
wepy中的通信主要采用三種方法:emit, $invoke;
- $broadcast:父組件觸發(fā)所有子組件(包含子孫級組件)事件拄轻。
-
emit可以用于觸發(fā)自定義事件而events中聲明的函數(shù)將不會(huì)再執(zhí)行恨搓。(與vue中的用法不同院促,vue中需要在父組件中設(shè)置子組件的屬性,用于在子組件中觸發(fā)奶卓。而wepy中則不需要一疯,只要在events中設(shè)置方法就可以在子組件中觸發(fā)。)
- $invoke:頁面或子組件觸發(fā)另一個(gè)子組件事件夺姑。
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)
// 觸發(fā)組件中的自定義事件
this.$emit('childFun', 1, 2, 3)
}
events = {
'getIndex': (...p) => {
console.log(p) // 輸出[1, 4]
}
}
}
在父組件中給子組件添加屬性@childFun.user = 'someEvent'后盏浙,在子組件中修改觸發(fā)條件this.$emit('childFun', 1, 2, 3)
//$invoke
父組件向子組件發(fā)送事件:
使用import導(dǎo)入子組件后,在使用時(shí)可以直接通過
this.$invoke('子組件荔茬,必須要單引號括起來', '子組件方法名稱', param1,param2,param3.......);
子組件間發(fā)送事件:
this.$invoke('子組件的相對路徑', '子組件方法名稱', param1,param2,param3.......);
子組件的相對路徑的理解: 當(dāng)設(shè)置'./'即當(dāng)前組件废膘,'../'為父組件,以此類推慕蔚。它可以指定向哪一個(gè)組件分發(fā)內(nèi)容丐黄,但只適用于簡單的組件樹結(jié)構(gòu),復(fù)雜的結(jié)構(gòu)考慮使用redux孔飒。
在子組件中使用
broadcast會(huì)觸發(fā)子組件及子孫組件中的相同事件。其中
broadcast觸發(fā)的事件設(shè)置在組件中的events中坏瞄,而
invoke中的第一個(gè)參數(shù)可以直接設(shè)置為components中的值鸠匀,當(dāng)設(shè)置相對路徑時(shí)蕉斜,即根據(jù)當(dāng)前組件在整個(gè)組件樹中的位置來確定路徑,它可以指定向特定的組件傳值,但當(dāng)結(jié)構(gòu)復(fù)雜時(shí)宅此,需要嵌套的路徑會(huì)比較復(fù)雜机错,不好維護(hù),考慮用redux實(shí)現(xiàn)父腕。
Mixins混合
- 默認(rèn)式混合(模塊中的data數(shù)據(jù)弱匪,components,events,自定義事件)
當(dāng)在wepy.mixin中設(shè)置了和頁面中相同的函數(shù)或變量時(shí)侣诵,以當(dāng)前頁面的函數(shù)和變量為主痢法,mixin中的函數(shù)和變量會(huì)失效。官方案例:
// 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中申明的函數(shù)可以調(diào)用引入mixin后的頁面的data數(shù)據(jù)和函數(shù)杜顺。mixin中的this指向當(dāng)前頁面組件财搁。
- 兼容式混合(生命周期)
當(dāng)在mixin中設(shè)置生命周期一類的鉤子函數(shù)時(shí),會(huì)優(yōu)先執(zhí)行mixin中的生命周期躬络,再執(zhí)行頁面中的函數(shù)尖奔。
// 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);
}
}
結(jié)果打印為:
2222
11111
wxs的使用,實(shí)現(xiàn)過濾器
在項(xiàng)目目錄下新建wxs文件夾穷当,在該文件夾中新增wxs文件提茁。新增內(nèi)容如下:
// 設(shè)置一個(gè)過濾器對超過10000的數(shù)字進(jìn)行轉(zhuǎn)化
module.exports = {
filter: function (num) {
if (num < 10000) {
return num
} else {
var reNum = (num / 10000).toFixed(1)
return reNum + '萬'
}
}
}
在頁面中通過import導(dǎo)入該過濾器:
// template中使用過濾器,mywxs對應(yīng)下方wxs中設(shè)置的key值
<view>{{mywxs.filter(mItem.playerCount)}}人</view>
.....
import mywxs from '@/wxs/fixed.wxs'
export default class Index extends wepy.page{
......
wxs = {
mywxs: mywxs
}
.....
}
導(dǎo)入的wxs文件只能在template中使用馁菜,不能在js中使用
Promise和async/await的使用(臟數(shù)據(jù)的檢測)
// 錄音
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()
}
}
當(dāng)使用異步函數(shù)的時(shí)候茴扁,我們?nèi)绻枰匦落秩卷撁娴臅r(shí)候,需要手動(dòng)調(diào)用$apply()方法汪疮,才能觸發(fā)臟數(shù)據(jù)檢查流程的運(yùn)行峭火。
群轉(zhuǎn)發(fā)
用于實(shí)現(xiàn)微信小程序的分享功能,獲取每個(gè)群的獨(dú)立ID智嚷。在上彈窗中默認(rèn)有轉(zhuǎn)發(fā)按鈕卖丸,可以通過使用hideShareMenu
來隱藏彈窗中的轉(zhuǎn)發(fā)按鈕.
用法
在wpy類型文件的onload中設(shè)置
// 用于顯示分享的群列表
wepy.showShareMenu({
withShareTicket: true
})
在onShareAppMessage中設(shè)置分享的界面,在分享成功后的回調(diào)函數(shù)中通過wepy.getShareInfo獲取分享群的信息盏道。群的信息是經(jīng)過加密的稍浆,需要通過后臺來進(jìn)行解密操作。
onShareAppMessage (res) {
if (res.from === 'button') {
console.log(res.target)
}
return {
title: '自定義轉(zhuǎn)發(fā)標(biāo)題',
path: '/pages/main',
success: function(res) {
let shareId = res.shareTickets[0]
// 轉(zhuǎn)發(fā)成功
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) {
// 轉(zhuǎn)發(fā)失敗
console.log(res)
}
}
}
其中onShareAppMessage需要在wepy.page中設(shè)置才有效果猜嘱,在wepy.component中設(shè)置無效果衅枫。在onShareAppMessage中的path設(shè)置的參數(shù)可以跟隨
?ie=值
,然后在對應(yīng)的頁面中設(shè)置onLoad
來獲取跟隨的值
微信小程序和本地接口對接泉坐,模擬請求數(shù)據(jù)
后臺采用express为鳄,代碼如下:
var express = require('express');
var app = express();
app.listen(3000, function () {
console.log('listen:3000');
})
設(shè)置前后臺的對接接口:
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'發(fā)送請求
加載請求模塊(因?yàn)榍芭_無法直接向https://api.weixin.qq.com/sns/jscode2session發(fā)送請求,該域名無法添加到小程序的服務(wù)器域名中):
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: 生成一個(gè)唯一字符串sessionid作為鍵孤钦,將openid和session_key作為值歧斟,存入redis,超時(shí)時(shí)間設(shè)置為2小時(shí)
//偽代碼: 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偏形。
從案例中知道需要的參數(shù)有4個(gè):
1. AppID(微信小程序的id静袖,在微信小程序的后臺設(shè)置中有),
2. session_key(用戶登錄的時(shí)候可以獲取到),
3. encryptedData(需要解密的字符串俊扭,在獲取的群信息中加密的字段)队橙,
4. iv(算法初始向量,在獲取的群信息中有返回)萨惑。
在app.js中引入WXBizDataCrypt.js:
// 解密模塊
var WXBizDataCrypt = require('./WXBizDataCrypt')
// 解密數(shù)據(jù)
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});
})
個(gè)人文章
微信小程序開發(fā)中的注意點(diǎn): http://www.reibang.com/p/d8d6c97d9c68
flex屬性的介紹和使用: http://www.reibang.com/p/f79e74113350
canvas繪制圓形圖片:http://www.reibang.com/p/dd0487ff4293