关于前端:APICloud-AVM多端开发案例深度解析一生鲜电商app开发

24次阅读

共计 5182 个字符,预计需要花费 13 分钟才能阅读完成。

AVM 多端开发是 APICloud 定义的一套新的代码编写规范(DSL):基于规范 Web Components 组件化思维,兼容 Vue / React 语法个性,通过一次编码,别离编译为 Android 和 iOSAPP、小程序代码,实现多端开发。

《餐饮点餐》是一个餐饮商户单商家堂食下单利用,开发者能够通过这个案例体验一套代码编译 Android 和 iOS app+ 小程序。它的次要性能包含浏览商家主页信息、查看举荐菜品、下单商品、取餐等号等性能。

我的项目源码地位:https://github.com/apicloudcom/ordering-food

首页 TabBar 构造的解决

为什么须要一个 app.json 配置文件

《点餐》我的项目的首页是由一个能够同级切换窗口组形成的。在 APP 原生端 下面,咱们能够借助 FrameGroup 来实现这样的切换组。小程序原生上则是应用 app.json 配置文件来 配置定义 TabBar 的相干属性。为了对立两端的差别问题,通过在 weight 根目录下定义一个 app.json 文件,具体字段阐明请参考《openTabLayout 布局文档》。所以,如果只书写原生端 APP,而不打算反对小程序的话,这个配置文件就是可选的了。

TabBar 页面的组织

在这个配置文件中,能够申明底部栏的标签文案、对应图标的选中和未选中状态以及对应须要跳转的页面门路。所以须要筹备四个主页面。在 pages目录筹备建设这四个页面。别离是“商家主页”main_home、“菜单页面”main_menu、“购物车页面”main_cart 和“用户主页”main_user。为了兼容小程序目录构造,须要应用同名文件夹对其包裹一层。

商家主页 main_home 的编写

到主页效果图,而后大抵剖析一下页面构造。源代码在 /widget/pages/main_home/main_home.stml。页面次要局部是一个滚动成果,须要应用一个 scroll-view 来做滚动局部的容器。头部有一个固定头部,并追随下面提到的 scroll-view 的滚动高度来做透明度反馈。

布局构造应用零碎举荐的 flex 布局。有一点须要留神的是,flex 布局的 flex-direction 默认是 column,也就是竖着排列的方向,这一点是和传统网页中不肯定中央。另外,每一个组件默认会附带 display:flex;属性。

申请接口数据(数据处理和申请库封装)

在页面的生命周期 apiready 中,有一个 this.getData()的办法,就是在申请数据。

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 来进行相应设置。

如此一来,商家主页的相干逻辑的数据处理的差不多了,同时介绍了根底的事件和数据处理等。

商品详情页 (组件通信、全局数据和事件)

页面加载的时候,通过页面传参拿到商品详情数据。另外一个商品的加购数量是存在名为 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);
}

加购后思考到相干购物车页面和底部小红点的数据。此时如果不思考小程序的话,也能够间接发送全局播送,自行处理相干逻辑。

正文完
 0