點餐模板說明

2022-06-06 15:28 更新

本文地址: github https://github.com/apicloudcom/ordering-food/tree/main/docs

堂食點餐源碼解析文檔

《點餐》項目是一個餐飲商戶單商家堂食下單應(yīng)用。 主要功能包括瀏覽商家主頁信息、查看推薦菜品、下單商品、取餐等號等功能。 可以適用于小吃快餐餐飲商戶的堂食點單管理,也可以進行稍微二開成為外賣、店鋪或者是虛擬服務(wù)等電商小應(yīng)用。

項目架構(gòu)

項目中前端技術(shù)要點包括跨頁面通信、全局購物車數(shù)據(jù)管理、自定義復(fù)用組件編寫和輔助助手函數(shù)等等。 使用 APICloud 多端技術(shù)實現(xiàn)了一套代碼,多端運行。 支持編譯成 Android & iOS App 以及微信小程序。

項目后端使用的是 APICloud 數(shù)據(jù)云3.0(未處理) 來構(gòu)建的: 通過編寫云函數(shù)自動管理維護接口和數(shù)據(jù),詳細可以參考數(shù)據(jù)云的文檔。也可以自定義后端接口,通過自寫服務(wù)器完成開發(fā)。

使用步驟

項目的初始化、預(yù)覽、調(diào)試和打包等操作請參考 APICloud多端開發(fā)快速上手教程 。 本文側(cè)重于《點餐》項目的技術(shù)要點分析和說明。

項目源碼位于 https://github.com/apicloudcom/ordering-food 。具體的項目源碼是在倉庫代碼的 widget 目錄下面,此目錄也就是應(yīng)用的根目錄。

[2021-5-22更新] 本項目數(shù)據(jù)云模型代碼已經(jīng)更新到線上的預(yù)置模型中,如有需要請參考本文檔第三大節(jié):《數(shù)據(jù)云模型導(dǎo)入和快速上手》

源碼文件目錄結(jié)構(gòu)

項目源碼在本倉庫的 widget 目錄下。其中該目錄下的文件結(jié)構(gòu)如下:

┌─component/                項目公共組件目錄
│  ├─empty-block.shtml      空數(shù)據(jù)占位圖組件
│  ├─goods-action.shtml     商品下單動作組件
│  ├─goods-counter.shtml    商品加購計數(shù)器組件
│  ├─goods-list-item.shtml  主頁商品列表單品組件
│  ├─order-item.shtml       訂單列表單品組件
│  ├─radio-box.shtml        自定義選擇器組件
├─css/                      css樣式目錄
├─image/                    圖片素材圖標(biāo)資源目錄
├─pages/                    新版的AVM頁面目錄
│  ├─goods_add
│  │  └─goods_add.stml      加購浮層
│  ├─goods_detail
│  │  └─goods_detail.stml   商品詳情頁
│  ├─main_cart
│  │  └─main_cart.stml      主tab-2 購物車頁面
│  ├─main_home
│  │  └─main_home.stml      主tab-0 商家主頁
│  ├─main_menu
│  │  └─main_menu.stml      主tab-1 點餐菜單頁面
│  ├─main_user
│  │  └─main_user.stml      主tab-3 用戶主頁
│  ├─pay_result
│  │  └─pay_result.stml     支付結(jié)果頁
│  ├─pending_order
│  │  └─pending_order.stml  待付款結(jié)算頁
├─script/                   JavaScript腳本目錄
└─config.xml                應(yīng)用配置文件

首頁 TabBar 結(jié)構(gòu)的處理

為什么需要一個 app.json 配置文件

《點餐》項目的首頁是由一個可以同級切換窗口組構(gòu)成的。 在 APP 原生端 上面, 我們可以借助 FrameGroup 來實現(xiàn)這樣的切換組。 小程序原生上則是使用 app.json 配置文件來 配置定義 TabBar 的相關(guān)屬性 。 為了統(tǒng)一兩端的差異問題,通過在 weight 根目錄下定義一個 app.json 文件,具體字段說明請參考《openTabLayout布局文檔》(未處理) 。 所以,如果只書寫原生端 APP ,而不計劃支持小程序的話,這個配置文件就是可選的了。

TabBar頁面的組織

在這個配置文件中,可以聲明底部欄的標(biāo)簽文案、對應(yīng)圖標(biāo)的選中和未選中狀態(tài)以及對應(yīng)需要跳轉(zhuǎn)的頁面路徑。 所以需要準(zhǔn)備四個主頁面。 在 pages目錄準(zhǔn)備建立這四個頁面。 分別是 “商家主頁” main_home 、 “菜單頁面” main_menu 、 “購物車頁面” main_cart 和 “用戶主頁” main_user 。 為了兼容小程序目錄結(jié)構(gòu),需要使用同名文件夾對其包裹一層。

商家主頁 main_home 的編寫

先看到主頁效果圖,然后大致分析一下頁面結(jié)構(gòu)。源代碼在 /widget/pages/main_home/main_home.stml 。 頁面主要部分是一個滾動效果,需要使用一個 scroll-view 來做滾動部分的容器。 頭部有一個固定頭部,并跟隨上面提到的 scroll-view 的滾動高度來做透明度反饋。

布局結(jié)構(gòu)使用系統(tǒng)推薦的 flex 布局。有一點需要注意的是, flex 布局的 flex-direction 默認是 column , 也就是豎著排列的方向,這一點是和傳統(tǒng)網(wǎng)頁中不一定地方。另外,每一個組件默認會附帶 display:flex;屬性。

請求接口數(shù)據(jù) (數(shù)據(jù)處理和請求庫封裝)

在頁面的生命周期 apiready 中,有一個 this.getData()的方法,就是在請求數(shù)據(jù)。

function getData() {
    GET('shops/getInfo')
        .then(data => {
            this.data.shopInfo = data;
        })
}

這個函數(shù)主要使用一個 GET 方法實現(xiàn)的。這個方法來自于:

import {GET} from "../../script/req";

這個文件中,主要處理了應(yīng)用的請求、會話和異常處理等邏輯。 相關(guān)業(yè)務(wù)代碼可以只是作為參考,具體項目中根據(jù)實際的會話認證方式、服務(wù)接口模式以及個人偏好等方式去組織。

拿到數(shù)據(jù)以后,通過 this.data.shopInfo = data 將數(shù)據(jù)交給到頁面的數(shù)據(jù)域中,以便于接下來的數(shù)據(jù)綁定顯示。

商家頭圖和主要信息 (數(shù)據(jù)綁定)

頭部主圖是不會和 scroll-view 一起滾動的,所以它應(yīng)該在滾動容器的外部。使用一個 img 圖片標(biāo)簽來顯示圖片。 其數(shù)據(jù)是來自服務(wù)器接口的數(shù)據(jù), 使用 avm.js 提供的《數(shù)據(jù)綁定》(未處理) 來處理數(shù)據(jù)。

<img class="shop-photo" style={{'height:'+photoRealHeight+'px'}} src={{shopInfo.img}} alt=""/>

商家的營業(yè)信息也同上,按照接口數(shù)據(jù)綁定出相應(yīng)字段,即可顯示出來。

<view class="shop"
      style={{'margin-top:'+photoRealHeight+'px'}}>
    <view class="shop-header flex-h">
        <text class="shop-name flex-1 ellipsis-1">{{ shopInfo.name }}</text>
        <img class="shop-phone" @click="callPhone" src="../../image/icon/icon-home-phone.png" alt=""/>
    </view>
    <view class="content-wrap">
        <text class="shop-text shop-address">
            {{ shopInfo.city }} {{ shopInfo.country }} {{ shopInfo.address }}
        </text>
    </view>
    <view class="shop-operation content-wrap">
        <text class="shop-text">營業(yè)中 09:00 - 13:00,16:00 - 22:00</text>
    </view>
</view>

撥打電話的動作 (事件綁定)

其中電話的圖標(biāo)點擊以后,需要實現(xiàn)撥打電話的效果。為其綁定一個點擊事件,叫做 callPhone ,并在 methods 去實現(xiàn):

function callPhone() {
    if (isMP()) {
        wx.makePhoneCall({
            phoneNumber: this.data.shopInfo.phone
        })
    } else {
        api.call({
            type: 'tel_prompt',
            number: this.data.shopInfo.phone
        });
    }
}

推薦菜品和欄目 (v-for循環(huán)和組件)

仔細觀察這里的模板和數(shù)據(jù),實際上可以分解為 一個主標(biāo)題 加上 一組菜品 這樣的結(jié)構(gòu)來循環(huán)。 其中 一組菜品 再使用循環(huán),渲染出單品。

使用循環(huán)來展示三個分組數(shù)據(jù)。

<view class="list" v-for="item in classifyList">
    <goods-list-item class="goods-item" :list="item.togc" :title="item.name"></goods-list-item>
</view>

每一個循環(huán)中包含一個 <goods-list-item /> 組件。這個組件來自于自定義組件:

import goodsListItem from '../../components/goods-list-item.stml';

在自定義組件中,完成組件內(nèi)部的組件樣式、數(shù)據(jù)管理和事件響應(yīng)等,符合組件化開發(fā)思想和提高項目的開發(fā)效率和維護性。 在這個組件中,同樣的使用了循環(huán)來處理每個欄目的單品數(shù)據(jù)。 每個單品綁定了一個 intoGoodsDetail 事件來實現(xiàn)跳轉(zhuǎn)到商品詳情頁。

function intoGoodsDetail(item) {
    api.openWin({
        name: 'goods_detail',
        url: '../../pages/goods_detail/goods_detail.stml',
        pageParam: {
            item
        }
    })
}

頁面頭部header

<view class="header-bar"
      style={{'opacity:'+this.data.opacity+';padding-top:'+safeAreaTop+'px'}}>
    <text class="nav-title shop-name">{{ shopInfo.name }}</text>
</view>

頭部是一個普通的 view + text 的結(jié)構(gòu)。為了實現(xiàn)滾動處理透明度,為其綁定一個動態(tài)的 style 屬性。 動態(tài)改變其透明度 opacity

而這個 opacity 的取值依賴于 scroll-view 的滾動高度。 scroll-view 的滾動會觸發(fā)相關(guān)數(shù)據(jù)的變動,所以為其綁定上一個滾動事件 @scroll="onScroll" 和相關(guān)處理邏輯 onScroll 。

function onScroll(e) {
    const y = isMP() ? e.detail.scrollTop : e.detail.y;


    let threshold = this.photoRealHeight - y;
    if (threshold < 0) {
        threshold = 0;
    }
    this.data.opacity = 1 - threshold / this.photoRealHeight;
    api.setStatusBarStyle && api.setStatusBarStyle({
        style: this.statusBarStyle
    });
}

onScroll 中能夠拿到相應(yīng)的滾動高度,并且計算出透明度的最終結(jié)果。 同時發(fā)現(xiàn)透明度的更改也會伴隨著頂部狀態(tài)欄文本的顏色變化。使用端能力 api.setStatusBarStyle 來進行相應(yīng)設(shè)置。

如此一來,商家主頁的相關(guān)邏輯的數(shù)據(jù)處理的差不多了,同時介紹了基礎(chǔ)的事件和數(shù)據(jù)處理等。

商品詳情頁 (組件通信、全局?jǐn)?shù)據(jù)和事件)

頁面加載的時候,通過頁面?zhèn)鲄⒛玫缴唐吩斍閿?shù)據(jù)。另外一個商品的加購數(shù)量是存在名為 CART-DATA的全局?jǐn)?shù)據(jù)中,在頁面生命周期函數(shù) apiready中拿到相關(guān)數(shù)據(jù):

this.data.goods = api.pageParam.item.togoods;  // 拿到商品主數(shù)據(jù)


let cartList = api.getPrefs({sync: true, key: 'CART-DATA'}); // 獲取加購數(shù)量
if (cartList) {
    cartList = JSON.parse(cartList)
    this.data.cartData = cartList[this.data.goods.id];
    if (this.data.cartData) {
        this.data.count = this.data.cartData.count;
    }
}

計數(shù)器組件 goods_counter

商品詳情頁使用了兩個自定義組件,一個是 goods_counter,是一個商品計數(shù)器。 以后其他頁面可能也會使用到,所以將其封裝起來。

<goods-counter onCountChange={this.countChange.bind(this)} :count="count"></goods-counter>

使用一個動態(tài)屬性 :count="count" 將剛剛獲取到的當(dāng)前商品的加購數(shù)量傳入。 在 goods_counter 內(nèi)部,點擊加減按鈕觸發(fā) countChange 事件。在事件中向父頁面?zhèn)鬟f:

function countChange(change) {
    if (this.props.count + change === 0) {
        return api.toast({
            msg: '不能再減少了\n可在購物車編輯模式下移除',
            location: 'middle'
        })
    }
    this.fire('CountChange', {
        change,
        props: this.props
    })
}

所以在組件調(diào)用的時候,綁定一個 onCountChange={this.countChange.bind(this)} 。 這里的 this.countChangegoods_detail 的函數(shù),在創(chuàng)建組件的時候作為 props 傳遞到了子組件中, 在子組件中可以直接執(zhí)行這個函數(shù),或者是使用 fire 的方式“引燃”這個函數(shù)。

加購動作條 goods_action

商品詳情頁使用了兩個自定義組件,另一個是 goods_action,是一個商品加購動作條。 主體是兩個按鈕,一個加購,一個結(jié)算。

結(jié)算就是攜帶當(dāng)前單品數(shù)據(jù)到預(yù)付款頁面。邏輯很簡單,就是攜帶數(shù)據(jù)到新頁面。

加購稍微復(fù)雜一點,不過邏輯依然使用 fire 的方式上拋給一個 addCart的事件到父頁面,因為可能不同的頁面的加購后續(xù)邏輯不太一樣,具體實現(xiàn)就交給父級。 所以視線還是轉(zhuǎn)回到 goods_detailaddCart 的實現(xiàn)。

function addCart() {
    let cartList = api.getPrefs({sync: true, key: 'CART-DATA'}) || '{}'
    cartList = JSON.parse(cartList)
    cartList[this.data.goods.id] = {
        goods: this.data.goods, count: this.data.count
    };
    api.setPrefs({
        key: 'CART-DATA',
        value: cartList
    });


    api.toast({
        msg: '成功加入' + this.data.count + '個到購物車', location: 'middle'
    })
    setTabBarBadge(2, Object.keys(cartList).length);
}

加購后考慮到相關(guān)購物車頁面和底部小紅點的數(shù)據(jù)。此時如果不考慮小程序的話,也可以直接發(fā)送全局廣播,自行處理相關(guān)邏輯。

菜單點餐頁面

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

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

  1. 滑動右側(cè)菜品,左側(cè)分類高亮?xí)S其更改。
  2. 點擊左側(cè)菜品分類,右側(cè)菜品回滾到到對應(yīng)區(qū)域。

其中第一個交互相關(guān)邏輯類似于在開發(fā)商家主頁的滾動 scroll-view 觸發(fā)頭部透明度的邏輯。 所以同樣地為右側(cè)的 scroll-view 綁定上 @scroll="onScroll" 函數(shù)。

具體邏輯請參考源碼的實現(xiàn)部分,獲取滾動高度等和主頁類似。

重點關(guān)注第二個交互的核心在于點擊對應(yīng)分類,右側(cè)的 scroll-view 需要滾動到指定位置。 使用屬性來進行位置綁定: scroll-top={scrollTo} 。此時只需要在左邊的分類點擊事件 @click="switchCategory(index)" 計算出正確的 scrollTo 即可實現(xiàn)。

function switchCategory(index) {
    this.data.categoryIndex = index;
    this.data.CD = new Date().getTime() + 500; // 手動切換分類后需要鎖定500毫秒 避免右側(cè)scroll-view滾動時帶來次生問題
    this.data.scrollTo = this.offsetList[index];
}

菜品和加購處理 (跨端特性處理)

右側(cè)的菜品有一個 @click="openAdd(goods)" 事件,用于打開加購頁面。

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}
        })
    }
}

這個函數(shù)中展示了端差異上的處理。因為小程序沒有類似 APICloudframe 的概念, 所以新彈出的頁面在小程序上,是一個頁面內(nèi)部組件實現(xiàn)的。

當(dāng)然這種方式 APP 原生端也是支持的。如果需要進一步提高性能,發(fā)揮原生優(yōu)勢,則可以使用原生端的frame 來完成。 此時,將目標(biāo)頁面封裝在一個自定義組件中,并把當(dāng)前菜品數(shù)據(jù)傳遞進去。

目前組件和 frame 頁面的獲參形式暫時不同。在 goods_add 這個組件中的 installed 生命周期中可以看到如下的兼容片段:

this.data.goods = this.props.goods ? this.props.goods : api.pageParam.goods;

在新展開的加購浮層上,看到了之前定義的 goods_action,所以大致邏輯也是獲取商品數(shù)據(jù)和加購數(shù),并實現(xiàn)一下addCart函數(shù)。 實際上這個頁面很類似商品詳情頁,只是展示UI不太相同。

沉浸式狀態(tài)欄 safe-area

在這個頁面中,自己實現(xiàn)了一個頂部導(dǎo)航欄。沉浸式狀態(tài)欄一般會需要獲取狀態(tài)欄高度等處理能力。 在 avm.js 中提供一個 safe-area 組件,用于自動處理異形屏的邊界問題。

<safe-area>
    <view class="header">
        <text class="title">菜單</text>
    </view>
</safe-area>

在主頁中,也看到相關(guān)編程式獲取安全區(qū)域數(shù)據(jù)的代碼:

this.data.safeAreaTop = api.safeArea ? api.safeArea.top : 0;

購物車頁面 computed 計算和v-if的條件渲染

購物車頁面是一個比較經(jīng)典的展示相關(guān)頁面內(nèi)部邏輯的案例。

在頁面初始化的時候, this.getCartData() 拿到本地存儲的購物車所有的數(shù)據(jù)。

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;
}

這是一個生成函數(shù),是將保存的對象構(gòu)建為頁面所需要的數(shù)組結(jié)構(gòu),同時增加每一個元素的 checked 屬性。 然后再頁面部分通過 v-for 來循環(huán)當(dāng)前購物車的數(shù)據(jù)。

<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/> 自定義組件。 這個組件擔(dān)負的任務(wù)很簡單,就是使用自定的樣式來渲染一個單選框。當(dāng)然 avm.js 自帶的系統(tǒng)組件 radio(未處理) 也是可以實現(xiàn)的。

computed 的使用

下面有一個全選按鈕,用于控制是否全選。

function checkAll() {
    const checked = !this.allChecked;
    for (let i = 0; i < this.data.cartList.length; i++) {
        this.data.cartList[i].checked = checked;
    }
}

而這個函數(shù)第一行以來的 this.allChecked 則是一個計算屬性。在 computed 中能找到它的實現(xiàn):

function allChecked() {
    return !this.cartList.some((item) => { // 也可以使用 every 來修改相反邏輯實現(xiàn)
        return !item.checked;
    })
}

緊接著它下面還有另外一個計算屬性: totalPrice :

function totalPrice() {
    // 先篩選出選中項
    let list = this.data.cartList.filter(item => {
        return item.checked;
    })


    // 再計算總和并且格式化結(jié)果
    return (list.length ? list.reduce((total, item) => {
        return total + item.goods.curt_price * item.count;
    }, 0) : 0).toFixed(2);
}

然后再模板中直接使用這個結(jié)果,即可完成總價的顯示:

<view class="text-group">
    <text class="main-cart-footer-text">合計</text>
    <text class="main-cart-footer-price">¥{{ totalPrice }}</text>
</view>

可以看到,計算屬性 computed 是可以通過一些邏輯計算出需要的結(jié)果,并且會暴露給實例本身, 在模板中能夠同數(shù)據(jù)一樣綁定。 同時能夠自動處理所依賴的數(shù)據(jù)變化,做出實時的更新。

v-if 條件渲染

在頁面中,有一個變量標(biāo)記 isEdit,用來表示當(dāng)前頁面是否是在處于編輯狀態(tài)。

<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>

根據(jù)編輯狀態(tài)的切換,右上角的按鈕文案變化為“完成”和“編輯”兩種狀態(tài)。這個時候就可以通過 v-if 來判斷渲染。 下面的結(jié)算、移除按鈕也是一樣,只不過是在模板中使用了三元表達式來做顯示。

<text class="main-cart-footer-btn-text">{{ isEdit ? '移除' : '去結(jié)算' }}</text>

用戶頁面

這個頁面主要有兩個要點:頭部用戶信息區(qū)域和訂單列表。

頭部用戶信息

頭部的用戶信息需要在初始化的時候讀取本地用戶數(shù)據(jù)。

/**
 * 獲取用戶信息
 * @returns {boolean|any}
 */
function getUser() {
    let user = api.getPrefs({
        sync: true,
        key: 'USER'
    });
    if (user) {
        return JSON.parse(user)
    }
    return false;
}

把獲取到的用戶數(shù)據(jù)作為一個普通的頁面數(shù)據(jù),用來渲染用戶信息面板。 如果用戶數(shù)據(jù)不存在,也就是未登錄模式,則需要使用 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>

登錄邏輯

在未登錄的情況下,上面的第二塊會展示,點擊觸發(fā) wxLogin 方法:

function wxLogin() {
    if (isMP()) {
        this.mpLogin();
    } else {
        this.doLogin({ssid: getDeviceId()});
    }
}

這里依然需要對特性平臺差異化處理。因為原生端和小程序端使用微信登錄是兩個不同的邏輯。 源代碼 /widget/pages/main_user/main_user.stml 中還展示了一些使用原生模塊來調(diào)用微信來登錄的邏輯。

登錄成功以后,開始執(zhí)行 loginSuccess ,可以保存相關(guān)用戶信息和會話信息,以備以后的使用。同時還需要刷新用戶的購物列表。 如果在真實項目中其他已經(jīng)打開的頁面也需要監(jiān)測用戶狀態(tài)變化,可以借助廣播事件來處理詳細的邏輯。

function loginSuccess(userInfo) {
    api.setPrefs({
        key: 'USER',
        value: userInfo
    });


    this.data.userInfo = userInfo;
    this.getOrderList();
}

頁面的下拉刷新

頁面下拉刷新和觸底加載依賴于 scroll-view 的相關(guān)事件綁定和實現(xiàn)。

<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" 就是在下拉刷新需要觸發(fā)的邏輯。 refresher-triggered={{loading}} 就是下拉刷新的狀態(tài)。(用于通知回彈和設(shè)置刷新中)。

function onRefresh() {
    this.data.loading = true; // 設(shè)置正在刷新
    if (this.data.userInfo) { //有用戶信息了才刷新
        this.getOrderList();
    } else {
        setTimeout(_ => {
            this.data.loading = false;
            api.toast({
                msg: '請登錄后查看歷史訂單'
            })
        }, 1000)
    }
}

主頁的開發(fā)大致就完成了,下面關(guān)注一下付款下單的過程。

待付款頁面 (表單數(shù)據(jù))

該頁面也比較簡單,大多數(shù)實現(xiàn)的邏輯在前面的頁面已經(jīng)提及。 此外有一個輸入框表單 ,用來收集用戶的輸入備注信息。

<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" 來動態(tài)獲取數(shù)據(jù)。

function onBlur(e) {
    this.data.remark = e.target.value;
}

獲取數(shù)據(jù)也還有其他多種方式,可以進一步參考組件 input以及其他表單組件文檔。

開始提交訂單,和服務(wù)器通信下單并且支付。下單完成后做一些聯(lián)動處理:

function addOrder() {
    POST('orders/app_addorder', this.formData).then(data => {


        // 打開結(jié)果頁
        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);


    })
}

支付成功頁面的跳轉(zhuǎn)

下單支付后跳轉(zhuǎn)到支付結(jié)果頁面。(這個過程是模擬成功下單,中間可以參考微信登錄過程嵌套第三方支付)

至此,所有的頁面邏輯主線已經(jīng)完成。應(yīng)用中還有一些細節(jié)處理,可以參考源碼和文檔進一步學(xué)習(xí)研究。

數(shù)據(jù)云模型導(dǎo)入和快速上手

開啟數(shù)據(jù)云和導(dǎo)入模型

《堂食點餐》模板的數(shù)據(jù)云模型和云函數(shù)現(xiàn)已上線到數(shù)據(jù)云預(yù)置模型中了。進入項目的控制面板,選擇“云開發(fā)”中的“云設(shè)置”。 如果是第一次打開這個界面,數(shù)據(jù)云默認是么有開啟的。需要點擊歡迎頁的開啟按鈕,即可開啟數(shù)據(jù)云。

開啟數(shù)據(jù)云之后,可以在“云設(shè)置”頁面進行一些基礎(chǔ)設(shè)置。 接下來重點關(guān)注到“數(shù)據(jù)模型”頁面。點擊“數(shù)據(jù)模型”打開相關(guān)頁面,我們可以自行創(chuàng)建模型和云函數(shù),也可以在右側(cè)“預(yù)制模型”中看到“堂食點餐"同名模型。 點擊右下角綠色小加號,將該模型進行導(dǎo)入。

導(dǎo)入成功以后,可以在左側(cè)看到相應(yīng)的數(shù)據(jù)模型已經(jīng)顯示出來。點選模型,可以進入相關(guān)模型數(shù)據(jù)的預(yù)覽。 或者是點擊左側(cè)底部的“云函數(shù)開發(fā)”會彈出云函數(shù)管理浮層,浮層中間是使用引導(dǎo)和文檔鏈接??梢渣c選左側(cè)頂部的綠色按鈕進行創(chuàng)作新的云函數(shù), 也可以點選已有的云函數(shù),學(xué)習(xí)研究預(yù)置的函數(shù)和接口是如何設(shè)計的。

體驗一下

以左側(cè)的 shop 模型為例,點擊模型打開“遠程函數(shù)”。在遠程函數(shù)中找到 getInfo 接口,點選后右側(cè)就會展現(xiàn)相關(guān)代碼實現(xiàn)。 此時需要進行一次全量發(fā)布,點擊右側(cè)上方的發(fā)布右側(cè)的下拉箭頭,選擇全量發(fā)布,將剛剛導(dǎo)入的所有模型和云函數(shù)發(fā)布并生效。

接下來可以點擊接口聯(lián)調(diào),打開API接口生成列表。在 shop 分組下找到 getInfo 接口,并可以點擊 “Try it out”進行接口測試。

請求后將會看到完整的請求地址。

接下來打開App端的源碼,找到 script/req.js 大約第三行的位置,將代碼中的請求二級前綴更改為項目的真實API路徑。 例如:

const config = {
    schema: 'https',
-    host: 'a7777777777777-pd.apicloud-saas.com',
+    host: 'a6176110219206-dev.apicloud-saas.com',
    path: 'api'
}

保存后,打開首頁開始測試一下: 進入 pages/main_home/main_home.stml 頁面,右鍵點擊空白區(qū)域,選擇“實時預(yù)覽”。

稍等片刻,在右側(cè)的預(yù)覽區(qū)域?qū)霈F(xiàn)預(yù)覽畫面。 點擊地址后面的復(fù)制圖標(biāo),拿到預(yù)覽地址。放置到chrome等瀏覽器中可以觀察請求,確認渲染數(shù)據(jù)的確是來自當(dāng)前項目的數(shù)據(jù)云接口的模型數(shù)據(jù)。

云模型 快速上手

云模型也就是云數(shù)據(jù)庫??梢源嫒I(yè)務(wù)數(shù)據(jù),還提供了數(shù)據(jù)訪問的接口和相關(guān)API。

在一個項目中可以建立業(yè)務(wù)所需要的數(shù)據(jù)表模型。還是以 shop 為例:打開模型后,是一個表格的形式展現(xiàn)了模型內(nèi)存在的數(shù)據(jù)。

表頭的內(nèi)容是該模型的字段,表中的數(shù)據(jù)是模型下保存的記錄??梢栽陬^部的按鈕中進行添加數(shù)據(jù)、刪除數(shù)據(jù)、添加字段、設(shè)置關(guān)聯(lián)等管理操作。

云函數(shù)示例代碼 快速上手

獲取商家信息

GET` `/shops/getInfo
shop.getInfo = async()=> {
    try{
        const data = await shop.findOne({where: {"status":1}});
        return {status:0,msg:"成功",data:data};
    }catch(err){
        return {status:1,msg:"獲取商家信息失敗!",data:err};
    }
};

通過閱讀上面的云函數(shù)源代碼,可以看到一個云函數(shù)組成是十分簡單的。在編輯狀態(tài)下,可以看到表單中顯示出了一個云函數(shù)的一些必要元素: 選擇 Model ,確定函數(shù)類型為“遠程函數(shù)”,選擇請求類型為“get”。完善函數(shù)名稱和描述,最后設(shè)置一個函數(shù)(方法)名, 作為函數(shù)名稱,也是遠程接口的訪問地址。

在函數(shù)中,通過模型的數(shù)據(jù)操作api來對接口做具體的功能實現(xiàn):在上面的代碼中, 就是從 shop 模型中找到一個 status1的一條符合條件的數(shù)據(jù)??梢钥吹剑檎覘l件是以 JSON 的形式放在 where 條件中的。

正常情況下,成功找到數(shù)據(jù)并使用 return 關(guān)鍵字為函數(shù)返回值。而這個值也會作為云函數(shù)生成的接口的 response 的數(shù)據(jù)域, 來返回給前端。

使用 try-catch 代碼塊來捕獲相關(guān)錯誤,如果查找失敗的情況下,并且也會返回給前端。

關(guān)于更多的模型方法可以參考數(shù)據(jù)云3的完整文檔。

管理后臺 快速上手

除了模型和云函數(shù)之外,數(shù)據(jù)云3還提供一個快速后臺生成管理系統(tǒng)。

為方便用戶使用,我們內(nèi)置了管理后臺模塊,用戶開啟服務(wù)后可通過 "https://appid-dev.apicloud-saas.com/admin/" 在測試環(huán)境進行訪問。

此功能需要全局配置開啟session服務(wù)以及開通文件存儲,請在全局配置進行相關(guān)操作。

還是上面的例子:假設(shè)當(dāng)前的 APPIDa6176110219206 ,那么對應(yīng)的管理地址就是:https://a6176110219206-dev.apicloud-saas.com/admin/ 默認的賬號和密碼是: 賬號:admin 密碼:123456

在后臺可以進行相關(guān)數(shù)據(jù)設(shè)置和頁面快速開發(fā)。后臺頁面開發(fā)的規(guī)則是引入了低代碼框架 AMIS 。詳細可以參閱:https://baidu.github.io/amis/zh-CN/docs/

深入使用和進階

回到數(shù)據(jù)云面板,可以查看示例模型和云函數(shù)。還可以通過數(shù)據(jù)云完整文檔學(xué)習(xí)完整的數(shù)據(jù)云使用方法。 數(shù)據(jù)云文檔鏈接:https://docs.apicloud.com/Cloud-API/sentosa(未處理)

以上內(nèi)容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號