微信小程序版五【五洲到家】實(shí)戰(zhàn)及操作中遇到的坑。
微信小程序?qū)崙?zhàn)
版本:0.15.152900(暫未升級(jí)原因:升級(jí)后需要圖片無法本地引用,必須使用image或是遠(yuǎn)程路徑引用)
先上一波gif圖片
cmd-markdown-logocmd-markdown-logocmd-markdown-logocmd-markdown-logocmd-markdown-logocmd-markdown-logocmd-markdown-logocmd-markdown-logo
首頁(定位成功情況且有門店)
門店列表(定位成功情況卻沒有門店)
更多(定位沒有成功情況也沒有門店)
產(chǎn)品詳情頁
門店詳情
更多門店
訂單及訂單詳情
個(gè)人中心
目錄結(jié)構(gòu)
官方request代碼:
wx.request({
url: 'test.php', //僅為示例,并非真實(shí)的接口地址
data: {
x: '' ,
y: ''
},
header: {
'content-type': 'application/json'
},
success: function(res) {
console.log(res.data)
}
})
但是有很多場景需要promise化的,所以使用第三方promise庫(es6-promise.min.js),對request進(jìn)行了一層包裝:
/* utils/util.js */
/* api接口promise 柯里化*/
var Promise = require('../lib/es6-promise.min.js');
function wxPromisify(fn, scope) {
return function (obj = {}) {
return new Promise((resolve, reject) => {
obj.success = function (res) {
resolve(res);
}
obj.fail = function (res) {
reject(res);
}
if(scope){
//改變this指向
var newFn = fn.bind(scope);
newFn(obj);
}else{
fn(obj);
}
})
}
}
/* request 封裝*/
var wxrequest = wxPromisify(wx.request);
function wxRequest(options, tokenNotRequired){
return wxrequest(options).then(res => {
var data = res.data;
if(data.status === 404404) {
if(tokenNotRequired){
delete options.headers;
return wxRequest(options);
}else{
return updateToken().then(token => {
return wxRequest(object.assignIn(options, {
headers: { 'X-Auth-Token': token }
}));
});
}
}else {
return Promise.resolve(data);
}
}).catch(err => {
return Promise.reject(err);
});
}
由于小程序默認(rèn)給的微信地圖api有些需求達(dá)不到要求,于是使用第三方庫(qqmap-wx-jssdk.min.js,這是絕配),這樣定位功能也比較好做,以及后續(xù)要做的地址管理模塊也比較好下手,但是有個(gè)問題,對微信地圖jdk接口進(jìn)行promise化后,使用過程會(huì)報(bào)錯(cuò),導(dǎo)致定位失敗,所以需要改變其執(zhí)行作用,于是對wxPromisify()方法做了些改造,重新綁定作用域至qqmapsdk,調(diào)用如下:
//address.js
// 引入SDK核心類
var QQMapWX = require('../lib/qqmap-wx-jssdk.min.js');
// 實(shí)例化API核心類(需要配置安全域名https://apis.map.qq.com)
var qqmapsdk = new QQMapWX({
key: 'xxxxx' //需要到騰地圖上申請key
});
...
...
// 請求用戶授權(quán)定位
//逆地址解析
var ReverseGeocoder = util.wxPromisify(qqmapsdk.reverseGeocoder, qqmapsdk); //需改變作用域
對于小程序是需要配置對應(yīng)的安全域名的,這樣才能執(zhí)行request
模板頁(template)沒有天生配對js,但是也可以實(shí)現(xiàn),實(shí)現(xiàn)面向?qū)ο蟮乃枷耄瑢δ0逅枰膉s進(jìn)行一層類的封裝,保證構(gòu)造函數(shù)需要接受父頁面的上下文對象,然后可以把聲明好的類方法綁定到父頁面上面去,對于模板頁js方法,以_FUN()方式命名。下面是為圖片懶加載優(yōu)化而做的swiper模板組件,可以參考一下。
/**
* 圖片預(yù)加載組件
*
* @author xiaobin_wu
* template/silder/silder.js
*/
class Slider {
constructor(pageContext, options = { picList: [], showArr:[] }){
this.page = pageContext; //獲取頁面上下文
this.page.data.slider = {
picList: options.picList,
showArr: options.showArr
}; //初始化data
this.page._sliderChange = this._sliderChange.bind(this);
}
//監(jiān)聽滑動(dòng)事件,實(shí)現(xiàn)圖片懶加載
_sliderChange(e){
if(this.page.data.slider.showArr){
let showArr = this.page.data.slider.showArr;
for(let i = 0; i < showArr.length; i++){
if(i === e.detail.current){
showArr[i] = true;
}
}
this.page.setData({
'slider.showArr': showArr
});
}
}
initData(imgs){
const arr = new Array(imgs.length).fill(false);
this.page.setData({
'slider.picList': imgs,
'slider.showArr': arr.fill(true, 0 , 1)
});
}
}
module.exports = Slider
以類形式module.exports出去,Page頁面,以var Slider = require('../../template/slider/slider.js');
形式引入,然后new
操作,模板wxml也參考template/silder/silder.wxml
,也可以對應(yīng)寫wxss,這樣做模板頁復(fù)用性高,類似組件的模式。
剛開始使用scroll-view,scroll-x一直失效,不能水平scroll,折騰了好多時(shí)間,結(jié)果這樣就成了,大概如下結(jié)構(gòu)(home.wxml):
<scroll-view scroll-x="{{true}}" scroll-left="{{scrollLeft}}" class="scroll-bar" style="width:100%;" >
<view style="width: {{idxData.navbar.length * 168}}rpx">
<view wx:for="{{idxData.navbar}}" wx:for-item="cate" class="cate-item {{index == currentIndex ? 'active' : ''}}" data-id="{{cate.nav_id}}" data-index="{{index}}" bindtap="cateClick">{{cate.nav_name}}</view>
</view>
</scroll-view>
忽略其他亂起八糟的代碼,主要是這個(gè)<view style="width: {{idxData.navbar.length * 168}}rpx">
,需要保證scroll-view下面的view的width必須要大于100%,充滿整個(gè)scroll-view
于是對于紅線部分的產(chǎn)品分類swiper,就只能手動(dòng)計(jì)算swiper高度,來實(shí)現(xiàn)swiper的效果,但是由于對應(yīng)每個(gè)swiper-item還會(huì)有個(gè)下拉加載,所以產(chǎn)品數(shù)目會(huì)一直變化,所以計(jì)算起來相當(dāng)于耗性能,希望官方能盡快讓swiper高度允許自動(dòng)撐開
template模板,對象傳遞方式=>data={{a: x1,b: x2}}
,x1、x2對應(yīng)data綁定的變量
可能你會(huì)遇到這種情況(設(shè)置動(dòng)態(tài)數(shù)據(jù)):
this.setData({
'array[0]': 1
});
/*
上面這樣設(shè)置是沒問題的,但是是動(dòng)態(tài)的,那該怎么辦?這樣...
*/
this.setData({
'array['+ index +']': 1
});
/*
很遺憾,無法怎么做
*/
解決辦法,聲明中間量,如下:
/* utils/util.js */
//動(dòng)態(tài)setData
function dynamicSetData(field, index, value, suffix, type='object'){
var param = {};
var string = field + '[' + index + ']' + (typeof suffix !== 'undefined' ? type === 'object' ? '.' + suffix : '[' + suffix + ']' : '');
param[string] = value;
return param;
}
這樣最后就可以這樣,this.setData(util.dynamicSetData('firstLoadDataFlag', index, true));
,即可用于對象的改變,也可以用于數(shù)組的改變。
對于小程序中,也有一些組件需要傳遞變量單位為px的,如果這個(gè)變量是需要計(jì)算出來的,但是我們使用的確是rpx單位,那么他們之間的轉(zhuǎn)化比例是有必要知道的
/* utils/util.js */
//獲取px與rpx之間的比列
function getRpx(){
var winWidth = wx.getSystemInfoSync().windowWidth;
return 750/winWidth;
}
image組件,其實(shí)對于src圖片路徑,是以背景圖展示的,并不是真的類似img,auto是不生效的。
wx.navigateBack返回通知上一頁執(zhí)行指定函數(shù)的作用,可以使用getCurrentPages()來獲取上一頁page對象,事先執(zhí)行,如下:
/* pages/order-detail/order-detail.js */
//返回執(zhí)行上一個(gè)頁面的函數(shù),good
navigateBackFun: function(){
var pages = getCurrentPages();
var prevPage = pages[pages.length - 2];
if(prevPage.__route__.indexOf("pages/order/order") != -1) {
prevPage.actionCallback(this.data.btnAction,this.data.page);
}
}
<view class="status-item {{index == orderData.progress.last_index? 'active' : ''}} {{index === orderData.progress.info.length - 1 ? 'last-status-item' : ''}}"></view>
對于下面的字體文件的引用會(huì)導(dǎo)致報(bào)錯(cuò),微信小程序似乎不支持怎么使用
@font-face {
font-family: 'Glyphicons Halflings';
src: url('/assets/fonts/glyphicons-halflings-regular.eot');
src: url('/assets/fonts/glyphicons-halflings-regular.eot?#iefix') format('embedded-opentype'), url('/assets/fonts/glyphicons-halflings-regular.woff2') format('woff2'), url('/assets/fonts/glyphicons-halflings-regular.woff') format('woff'), url('/assets/fonts/glyphicons-halflings-regular.ttf') format('truetype'), url('/assets/fonts/glyphicons-halflings-regular.svg#glyphicons_halflingsregular') format('svg');
}
解決辦法,將ttf文件拿出,轉(zhuǎn)化成base64,以wxss引入。base64轉(zhuǎn)化
對于購車功能也是相當(dāng)折騰的,通過在app.js定義全局變量:
cartData:{
list:[],
totalCount:1,
totalPrice:0,
// 起送價(jià)
floorPrice:0,
// 總價(jià)達(dá)到此價(jià)免配送費(fèi)
freeShipPrice:0,
// 運(yùn)費(fèi)
deliveryFee:0,
storeId:0,
storeName:''
}
然后每次加減產(chǎn)品,清空購物車來操作cartData的變化,list存儲(chǔ)購物車產(chǎn)品數(shù)據(jù),在首頁和產(chǎn)品詳情頁,可以來獲取購物車的數(shù)據(jù),當(dāng)然也會(huì)把購物車數(shù)據(jù)的商品id和門店id存儲(chǔ)到Storage,可以用來異步更新最新的購物車數(shù)據(jù),在首頁和產(chǎn)品詳情頁的來回切換,對于購物車需要時(shí)刻去檢查,映射到對應(yīng)分類的swiper產(chǎn)品的加減變化,這里有沒有像vue中vuex的狀態(tài)管理能對數(shù)據(jù)集中管理,(對于vuex的使用 點(diǎn)擊),導(dǎo)致監(jiān)聽變化變得很復(fù)雜,有把加減部件cart-ctrl和購物車cart提取成template模板組件,結(jié)果處理起來,這里一萬個(gè)省略號(hào),很悲催!
微信小程序類似瀏覽器一樣,借助一個(gè)HTML頁面來引用加載所有的JS文件,但是并不會(huì)馬上去執(zhí)行,代理服務(wù)部分代碼又怎么兩個(gè)全局函數(shù)define和require,類似amd,使用define函數(shù)對請求回來的js文件內(nèi)容進(jìn)行包裝,但是不去執(zhí)行,然后可以使用require函數(shù)按需遞歸式進(jìn)行初始化,這個(gè)時(shí)候js就會(huì)執(zhí)行了,并且只執(zhí)行一次,那么對于Page頁面為什么js能一開始不使用require函數(shù)就能加載呢?因?yàn)槟J(rèn)page頁面js文件,會(huì)自動(dòng)添加上require自己,加載后立即初始化。這樣是不需要擔(dān)心多次require會(huì)執(zhí)行多次js的。
這篇文章寫得很詳細(xì) 微信小程序的require機(jī)制淺析,順便貼一下幾段代碼(幫助理解):
代理服務(wù)部分代碼:
(projectManager.js)
function getScripts(projInfo, callback) {
...
fs.readFile(fname, 'utf8', function(err, scripts) {
....
scripts = 'define("' + moduleName + '", function(require, module, exports, ' + noBrowserStr +
'){ ' + scripts + '\n});',
needRequire && (scripts += 'require("' + moduleName + '")'), //page頁面js文件,會(huì)添加上require自己,加載后立即初始化。
.....
callback(null, scripts) //scripts串內(nèi)容作為HTTP GET的返回
var
......
moduleList = {};
define = function(moduleName, factory) { //define是全局函數(shù),每個(gè)JS文件都默認(rèn)會(huì)調(diào)用.
moduleList[moduleName] = { status: status1, factory: factory }
};
....
require = function(moduleName) {
....
var module = moduleList[moduleName]; //define函數(shù)調(diào)用時(shí)為moduleList賦的值
.....
if (module.status === status1) {
//如果未初始化,則初始化
var factory = module.factory, //這個(gè)factory就是這個(gè)JS文件的腳本.
obj = { exports: {} }, u = void 0;
factory && (u = factory(o(moduleName), obj, obj.exports)), module.exports = obj.exports || u, module.status = status2
}
return module.exports
}