APICloud AVM多端開發案例源碼《外賣app開發》深度解析(下)

上週,咱們分享了用APICloud AVM多端開發技術,開發一款《外賣app開發》項目源碼解析上篇,如今把下篇補足,但願能幫助開發者快速體驗一套代碼編譯Android和iOS app+小程序。編程

這篇主要講解菜單點餐頁雙向滾動交互、菜品加購處理、購物車和付款邏輯處理,以及用戶中心開發。查看這篇時,能夠先複習上篇:小程序

APICloud AVM多端開發案例源碼《外賣app開發》深度解析(上)api

一 菜單點餐頁面

分類和菜品的雙向滾動交互

這個頁面是一個左右分欄的佈局。左邊是菜單分類,右邊的菜品。 有一組比較常見的交互:數組

  1. 滑動右側菜品,左側分類高亮會隨其更改。
  2. 點擊左側菜品分類,右側菜品回滾到到對應區域。

其中第一個交互相關邏輯相似於在開發商家主頁的滾動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

相關文章
相關標籤/搜索