上週,咱們分享了用APICloud AVM多端開發技術,開發一款《外賣app開發》項目源碼解析上篇,如今把下篇補足,但願能幫助開發者快速體驗一套代碼編譯Android和iOS app+小程序。編程
這篇主要講解菜單點餐頁雙向滾動交互、菜品加購處理、購物車和付款邏輯處理,以及用戶中心開發。查看這篇時,能夠先複習上篇:小程序
APICloud AVM多端開發案例源碼《外賣app開發》深度解析(上)api
一 菜單點餐頁面
分類和菜品的雙向滾動交互
這個頁面是一個左右分欄的佈局。左邊是菜單分類,右邊的菜品。 有一組比較常見的交互:數組
- 滑動右側菜品,左側分類高亮會隨其更改。
- 點擊左側菜品分類,右側菜品回滾到到對應區域。
其中第一個交互相關邏輯相似於在開發商家主頁的滾動scroll-view
觸發頭部透明度 的邏輯。 因此一樣地爲右側的scroll-view
綁定上@scroll="onScroll"
函數。安全
具體邏輯請參考源碼的實現部分,獲取滾動高度等和主頁相似。服務器
重點關注第二個交互的核心在於點擊對應分類,右側的scroll-view
須要滾動到指定位置。 使用屬性來進行位置綁定:scroll-top={scrollTo}
。此時只須要在左邊的分類點擊事件@click="switchCategory(index)"
計算出正確的scrollTo
便可實現。微信
function switchCategory(index) { this.data.categoryIndex = index; this.data.CD = new Date().getTime() + 500; // 手動切換分類後須要鎖定500毫秒 避免右側scroll-view滾動時帶來次生問題 this.data.scrollTo = this.offsetList[index]; }
菜品和加購處理 (跨端特性處理)app
右側的菜品有一個 @click="openAdd(goods)"
事件,用於打開加購頁面。ide
function openAdd(goods) { if (isMP()) { this.data.currentGoods = goods; wx.hideTabBar(); } else { api.openFrame({ name: 'goods_add', url: '../goods_add/goods_add.stml', pageParam: {goods} }) } }
這個函數中展現了端差別上的處理。由於小程序沒有相似 APICloud
的 frame
的概念, 因此新彈出的頁面在小程序上,是一個頁面內部組件實現的。函數
固然這種方式 APP
原生端也是支持的。若是須要進一步提升性能,發揮原生優點,則可使用原生端的frame
來完成。 此時,將目標頁面封裝在一個自定義組件中,並把當前菜品數據傳遞進去。
目前組件和 frame
頁面的獲參形式暫時不一樣。在 goods_add
這個組件中的 installed
生命週期中能夠看到以下的兼容片斷:
this.data.goods = this.props.goods ? this.props.goods : api.pageParam.goods;
在新展開的加購浮層上,看到了以前定義的 goods_action
,因此大體邏輯也是獲取商品數據和加購數,並實現一下addCart函數。 實際上這個頁面很相似商品詳情頁,只是展現UI不太相同。
沉浸式狀態欄 safe-area
在這個頁面中,本身實現了一個頂部導航欄。沉浸式狀態欄通常會須要獲取狀態欄高度等處理能力。 在 avm.js
中提供一個 safe-area
組件,用於自動處理異形屏的邊界問題。
<safe-area> <view class="header"> <text class="title">菜單</text> </view> </safe-area>
在主頁中,也看到相關編程式獲取安全區域數據的代碼:
this.data.safeAreaTop = api.safeArea ? api.safeArea.top : 0;
二 購物車頁面 computed 計算和v-if的條件渲染
購物車頁面是一個比較經典的展現相關頁面內部邏輯的案例。
在頁面初始化的時候, this.getCartData()
拿到本地存儲的購物車全部的數據。
function getCartData() { let cartData = api.getPrefs({sync: true, key: 'CART-DATA'}); if (cartData) { cartData = JSON.parse(cartData); this.data.cartData = cartData; this.generateCartList(); setTabBarBadge(2, Object.keys(cartData).length); } }
其中還混合了一個 generateCartList
邏輯。
function generateCartList() { let cartData = this.data.cartData; let arr = []; for (let i in cartData) { arr.push({checked: true, ...cartData[i]}); } this.data.cartList = arr; }
這是一個生成函數,是將保存的對象構建爲頁面所須要的數組結構,同時增長每個元素的 checked
屬性。 而後再頁面部分經過 v-for
來循環當前購物車的數據。
<view class="main-cart-goods-item" v-for="item in cartList"> <radio-box class="main-cart-radio-box" :checked="item.checked" onChange={this.radioToggle.bind(this)} :item="item"></radio-box> <img class="main-cart-goods-pic" mode="aspectFill" src={{item.goods.thumbnail}} alt=""/> <view class="main-cart-goods-info"> <text class="main-cart-goods-name">{{ item.goods.name }}</text> <view class="main-cart-flex-h"> <text class="main-cart-goods-price-signal">¥</text> <text class="main-cart-goods-price-num">{{ item.goods.curt_price }}</text> <goods-counter onCountChange={this.countChange.bind(this)} :count="item.count" :item="item"></goods-counter> </view> </view> </view>
注意到每個條目的開頭嵌套了一個 <radio-box/>
自定義組件。 這個組件擔負的任務很簡單,就是使用自定的樣式來渲染一個單選框。固然 avm.js
自帶的系統組件 radio
也是能夠實現的。
computed 的使用
下面有一個全選按鈕,用於控制是否全選。
function checkAll() { const checked = !this.allChecked; for (let i = 0; i < this.data.cartList.length; i++) { this.data.cartList[i].checked = checked; } }
而這個函數第一行以來的 this.allChecked
則是一個計算屬性。在 computed
中能找到它的實現:
function allChecked() { return !this.cartList.some((item) => { // 也可使用 every 來修改相反邏輯實現 return !item.checked; }) }
緊接着它下面還有另一個計算屬性: totalPrice
:
function totalPrice() { // 先篩選出選中項 let list = this.data.cartList.filter(item => { return item.checked; }) // 再計算總和而且格式化結果 return (list.length ? list.reduce((total, item) => { return total + item.goods.curt_price * item.count; }, 0) : 0).toFixed(2); }
而後再模板中直接使用這個結果,便可完成總價的顯示:
<view class="text-group"> <text class="main-cart-footer-text">合計</text> <text class="main-cart-footer-price">¥{{ totalPrice }}</text> </view>
能夠看到,計算屬性 computed
是能夠經過一些邏輯計算出須要的結果,而且會暴露給實例自己, 在模板中可以同數據同樣綁定。 同時可以自動處理所依賴的數據變化,作出實時的更新。
v-if 條件渲染
在頁面中,有一個變量標記 isEdit
,用來表示當前頁面是不是在處於編輯狀態。
<view @click="toggleEdit"> <text class="main-cart-finnish-text" v-if="isEdit">完成</text> <view v-else class="main-cart-action"> <img class="main-cart-action-icon" src="../../image/icon/icon-cart-edit.png" alt=""/> <text class="main-cart-action-text">編輯</text> </view> </view>
根據編輯狀態的切換,右上角的按鈕文案變化爲「完成」和「編輯」兩種狀態。這個時候就能夠經過 v-if
來判斷渲染。 下面的結算、移除按鈕也是同樣,只不過是在模板中使用了三元表達式來作顯示。
<text class="main-cart-footer-btn-text">{{ isEdit ? '移除' : '去結算' }}</text>
三 用戶頁面
這個頁面主要有兩個要點:頭部用戶信息區域和訂單列表。
頭部用戶信息
頭部的用戶信息須要在初始化的時候讀取本地用戶數據。
/** * 獲取用戶信息 * @returns {boolean|any} */ function getUser() { let user = api.getPrefs({ sync: true, key: 'USER' }); if (user) { return JSON.parse(user) } return false; }
把獲取到的用戶數據做爲一個普通的頁面數據,用來渲染用戶信息面板。 若是用戶數據不存在,也就是未登陸模式,則須要使用 v-if
條件渲染來展現登陸界面。
<view class="user-info flex flex-h flex-center-v" v-if="userInfo" @click="logout"> <img class="user-avatar" src={{userInfo.avatarUrl}} alt=""/> <text class="user-name">{{ userInfo.nickName }}</text> </view> <view class="user-info flex flex-h flex-center-v" v-else @click="wxLogin"> <img class="user-avatar" src="../../image/icon/icon-user-avatar.png" alt=""/> <text class="user-name">使用微信登陸</text> </view>
登陸邏輯
在未登陸的狀況下,上面的第二塊會展現,點擊觸發 wxLogin
方法:
function wxLogin() { if (isMP()) { this.mpLogin(); } else { this.doLogin({ssid: getDeviceId()}); } }
這裏依然須要對特性平臺差別化處理。由於原生端和小程序端使用微信登陸是兩個不一樣的邏輯。 源代碼 /widget/pages/main_user/main_user.stml
中還展現了一些使用原生模塊來調用微信來登陸的邏輯。
登陸成功之後,開始執行 loginSuccess
,能夠保存相關用戶信息和會話信息,以備之後的使用。同時還須要刷新用戶的購物列表。 若是在真實項目中其餘已經打開的頁面也須要監測用戶狀態變化,能夠藉助廣播事件來處理詳細的邏輯。
function loginSuccess(userInfo) { api.setPrefs({ key: 'USER', value: userInfo }); this.data.userInfo = userInfo; this.getOrderList(); }
頁面的下拉刷新
頁面下拉刷新和觸底加載依賴於 scroll-view
的相關事件綁定和實現。
<scroll-view scroll-y class="flex-1 main-user-scroll-view" enable-back-to-top refresher-enabled refresher-triggered={{loading}} @refresherrefresh="onRefresh"> <view v-if="orderList.length"> <order-item :order="order" v-for="order in orderList" onOrderAction={this.orderAction.bind(this)}></order-item> </view> <view class="empty-block" v-else> <empty-block text="暫無訂單哦~" type="order"></empty-block> </view> </scroll-view>
其中 @refresherrefresh="onRefresh"
就是在下拉刷新須要觸發的邏輯。 refresher-triggered={{loading}}
就是下拉刷新的狀態。(用於通知回彈和設置刷新中)。
function onRefresh() { this.data.loading = true; // 設置正在刷新 if (this.data.userInfo) { //有用戶信息了才刷新 this.getOrderList(); } else { setTimeout(_ => { this.data.loading = false; api.toast({ msg: '請登陸後查看歷史訂單' }) }, 1000) } }
主頁的開發大體就完成了,下面關注一下付款下單的過程。
四 待付款頁面 (表單數據)
該頁面也比較簡單,大多數實現的邏輯在前面的頁面已經說起。 此外有一個輸入框表單 ,用來收集用戶的輸入備註信息。
<view class="order-note"> <text class="order-note-key">備註</text> <input class="order-note-input" placeholder="如需備註請輸入" onBlur="onBlur" maxlength="30" id="remark"/> </view>
經過失去焦點事件 onBlur="onBlur"
來動態獲取數據。
function onBlur(e) { this.data.remark = e.target.value; }
獲取數據也還有其餘多種方式,能夠進一步參考組件 input
以及其餘表單組件文檔。
開始提交訂單,和服務器通訊下單而且支付。下單完成後作一些聯動處理:
function addOrder() { POST('orders/app_addorder', this.formData).then(data => { // 打開結果頁 api.openWin({ name: 'pay_result', url: '../pay_result/pay_result.stml' }); // 通知支付成功 刷新訂單頁面 api.sendEvent({ name: 'PAY-SUCCESS' }) // 清空購物車 api.setPrefs({ key: 'CART-DATA', value: {} }); setTabBarBadge(2, 0); }) }
支付成功頁面的跳轉
下單支付後跳轉到支付結果頁面。(這個過程是模擬成功下單,中間能夠參考微信登陸過程嵌套第三方支付)
-----------
至此,全部的頁面邏輯主線已經完成。應用中還有一些細節處理,能夠參考源碼和文檔進一步學習研究。 想快速上手APICloud AVM多端開發,能夠查看快速上手教程:https://docs.apicloud.com/apicloud3/?uzchannel=7