APICloud AVM多端開發案例深度解析(一)--生鮮電商app開發

AVM多端開發是APICloud定義的一套新的代碼編寫標準(DSL):基於標準Web Components組件化思想,兼容Vue / React語法特性,經過一次編碼,分別編譯爲Android和iOSAPP、小程序代碼,實現多端開發。html

《餐飲點餐》是一個餐飲商戶單商家堂食下單應用,開發者能夠經過這個案例體驗一套代碼編譯Android和iOS app+小程序。它的主要功能包括瀏覽商家主頁信息、查看推薦菜品、下單商品、取餐等號等功能。git

項目源碼位置:https://github.com/apicloudcom/ordering-foodgithub

首頁 TabBar 結構的處理

爲何須要一個 app.json 配置文件

《點餐》項目的首頁是由一個能夠同級切換窗口組構成的。 在 APP 原生端 上面, 咱們能夠藉助 FrameGroup 來實現這樣的切換組。 小程序原生上則是使用 app.json 配置文件來 配置定義 TabBar 的相關屬性 。 爲了統一兩端的差別問題,經過在 weight 根目錄下定義一個 app.json 文件,具體字段說明請參考《openTabLayout佈局文檔》 。 因此,若是隻書寫原生端 APP ,而不計劃支持小程序的話,這個配置文件就是可選的了。json

TabBar頁面的組織

在這個配置文件中,能夠聲明底部欄的標籤文案、對應圖標的選中和未選中狀態以及對應須要跳轉的頁面路徑。 因此須要準備四個主頁面。 在 pages目錄準備創建這四個頁面。 分別是 「商家主頁」 main_home 、 「菜單頁面」 main_menu 、 「購物車頁面」 main_cart 和 「用戶主頁」 main_user 。 爲了兼容小程序目錄結構,須要使用同名文件夾對其包裹一層。小程序

商家主頁 main_home 的編寫

到主頁效果圖,而後大體分析一下頁面結構。源代碼在 /widget/pages/main_home/main_home.stml 。 頁面主要部分是一個滾動效果,須要使用一個 scroll-view 來作滾動部分的容器。 頭部有一個固定頭部,並跟隨上面提到的 scroll-view 的滾動高度來作透明度反饋。api

佈局結構使用系統推薦的 flex 佈局。有一點須要注意的是, flex 佈局的 flex-direction 默認是 column , 也就是豎着排列的方向,這一點是和傳統網頁中不必定地方。另外,每個組件默認會附帶 display:flex;屬性。服務器

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

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

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

這個函數主要使用一個 GET 方法實現的。這個方法來自於:組件化

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

這個文件中,主要處理了應用的請求、會話和異常處理等邏輯。 相關業務代碼能夠只是做爲參考,具體項目中根據實際的會話認證方式、服務接口模式以及我的偏好等方式去組織。

拿到數據之後,經過 this.data.shopInfo = data 將數據交給到頁面的數據域中,以便於接下來的數據綁定顯示。

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

頭部主圖是不會和 scroll-view 一塊兒滾動的,因此它應該在滾動容器的外部。使用一個 img 圖片標籤來顯示圖片。 其數據是來自服務器接口的數據, 使用 avm.js 提供的《數據綁定》 來處理數據。

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

商家的營業信息也同上,按照接口數據綁定出相應字段,便可顯示出來。

<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">營業中 09:00 - 13:00,16:00 - 22:00</text> </view> </view>

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

其中電話的圖標點擊之後,須要實現撥打電話的效果。爲其綁定一個點擊事件,叫作 callPhone ,並在 methods 去實現:

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

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

仔細觀察這裏的模板和數據,實際上能夠分解爲 一個主標題 加上 一組菜品 這樣的結構來循環。 其中 一組菜品 再使用循環,渲染出單品。

一個主標題和一組菜品

使用循環來展現三個分組數據。

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

每個循環中包含一個 <goods-list-item /> 組件。這個組件來自於自定義組件:

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

在自定義組件中,完成組件內部的組件樣式、數據管理和事件響應等,符合組件化開發思想和提升項目的開發效率和維護性。 在這個組件中,一樣的使用了循環來處理每一個欄目的單品數據。 每一個單品綁定了一個 intoGoodsDetail 事件來實現跳轉到商品詳情頁。

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 的結構。爲了實現滾動處理透明度,爲其綁定一個動態的 style 屬性。 動態改變其透明度 opacity

而這個 opacity 的取值依賴於 scroll-view 的滾動高度。 scroll-view 的滾動會觸發相關數據的變更,因此爲其綁定上一個滾動事件 @scroll="onScroll" 和相關處理邏輯 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 中可以拿到相應的滾動高度,而且計算出透明度的最終結果。 同時發現透明度的更改也會伴隨着頂部狀態欄文本的顏色變化。使用端能力 api.setStatusBarStyle 來進行相應設置。

如此一來,商家主頁的相關邏輯的數據處理的差很少了,同時介紹了基礎的事件和數據處理等。

商品詳情頁 (組件通訊、全局數據和事件)

goods_detail

頁面加載的時候,經過頁面傳參拿到商品詳情數據。另一個商品的加購數量是存在名爲 CART-DATA的全局數據中,在頁面生命週期函數 apiready中拿到相關數據:

this.data.goods = api.pageParam.item.togoods; // 拿到商品主數據

let cartList = api.getPrefs({sync: true, key: 'CART-DATA'}); // 獲取加購數量
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;
}
}

計數器組件 goods_counter

商品詳情頁使用了兩個自定義組件,一個是 goods_counter,是一個商品計數器。 之後其餘頁面可能也會使用到,因此將其封裝起來。

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

使用一個動態屬性 :count="count" 將剛剛獲取到的當前商品的加購數量傳入。 在 goods_counter 內部,點擊加減按鈕觸發 countChange 事件。在事件中向父頁面傳遞:

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

因此在組件調用的時候,綁定一個 onCountChange={this.countChange.bind(this)} 。 這裏的 this.countChangegoods_detail 的函數,在建立組件的時候做爲 props 傳遞到了子組件中, 在子組件中能夠直接執行這個函數,或者是使用 fire 的方式「引燃」這個函數。

加購動做條 goods_action

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

結算就是攜帶當前單品數據到預付款頁面。邏輯很簡單,就是攜帶數據到新頁面。

加購稍微複雜一點,不過邏輯依然使用 fire 的方式上拋給一個 addCart的事件到父頁面,由於可能不一樣的頁面的加購後續邏輯不太同樣,具體實現就交給父級。 因此視線仍是轉回到 goods_detailaddCart 的實現。

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

加購後考慮到相關購物車頁面和底部小紅點的數據。此時若是不考慮小程序的話,也能夠直接發送全局廣播,自行處理相關邏輯。

相關文章
相關標籤/搜索