AVM multi-terminal development is a new code writing standard (DSL) defined by APICloud: based on the standard Web Components componentalization idea, compatible with Vue/React syntax features, through one encoding, respectively compiled into Android and iOSAPP, small program code, to achieve multi-terminal development.

“Catering Order” is a catering business single business order application, developers can experience a set of code compilation Android and iOS APP + small program through this case. Its main functions include browsing business home page information, viewing recommended dishes, ordering goods, taking meals and other functions.

Project source location: github.com/apicloudcom…

Home TabBar structure processing

Why do I need an app.json configuration file

The Front page of the Order Menu project is made up of a group of Windows that can be switched horizontally. On the native side of the APP, we can implement such a switch group with the framework Group. The applet natively uses the app.json configuration file to configure the properties that define TabBar. To resolve the differences between the two ends, define an app.json file in the weight root directory. For details about the fields, see the APICloud openTabLayout Layout Document. So, if you only write native apps and do not plan to support applets, this profile is optional.

Organization of TabBar pages

In this configuration file, you can declare the label text in the bottom bar, the selected and unselected status of the corresponding icon, and the corresponding page path to jump to. So you need to prepare four main pages. Prepare to set up these four pages in the Pages directory. They are merchant home page main_HOME, menu page main_menu, shopping cart page main_CART and User home page main_user. To be compatible with the applets directory structure, you need to wrap it with a folder of the same name.

Business homepage main_HOME preparation

Go to the home page and analyze the structure of the page. The source code is at /widget/pages/main_home/main_home.stml. The main part of the page is a scroll effect, need to use a scroll view to do the container of the scroll part. The head has a fixed head and follows the scroll height of the scroll view mentioned above to do transparency feedback.

Layout structure uses the flex layout recommended by the system. Note that the default flex-direction for a Flex layout is column, which is the vertical direction of the layout. This is different from traditional web pages. In addition, each component comes with display:flex by default; Properties.

Request interface data (data processing and request library encapsulation)

In the page lifecycle ApiReady, there is a this.getData() method that requests data.

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

This function is implemented primarily using a GET method. This method comes from:

import {GET} from ".. /.. /script/req";Copy the code

This file handles the application’s request, session, and exception handling logic. The relevant business code can only be used as a reference, and can be organized according to the actual session authentication mode, service interface mode, and personal preference in specific projects.

Once you have the data, pass it to the page’s data field via this.data.shopinfo = data for subsequent data binding display.

Business head diagram and main information (data binding)

The main header is not going to scroll with the Scroll view, so it should be outside of the scroll container. Use an IMG image tag to display images. Its data is data from the server interface, which is processed using data binding provided by AVm.js.

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

The business information of merchants is the same as above, and can be displayed by binding corresponding fields according to the interface data.

<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 "> < span style =" max-width: 100%; clear: both; min-height: 1em;Copy the code

Action for making a call (event binding)

After the phone icon is clicked, you need to make a call. Bind a click event called callPhone to it and implement it in Methods:

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

Recommended dishes and programs (V-for loops and components)

If you look closely at the template and data here, you can actually break it down into a main heading plus a set of dishes to cycle through. One group of dishes is recycled to render individual items.

Use a loop to show three groups of data.

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

Each loop contains a
component. This component comes from a custom component:

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

In the custom component, the internal component style, data management and event response are completed, which conforms to the componentized development thought and improves the development efficiency and maintainability of the project. In this component, loops are also used to process individual data for each column. Each item is bound to an intoGoodsDetail event to jump to the item details page.

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

Page header

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

The header is a normal View + text structure. To achieve scroll processing transparency, bind it to a dynamic style property. Dynamically change its opacity.

The value of opacity depends on the scroll height of the Scroll view. The scroll of the scroll-view will trigger the change of related data, so bind the previous scroll event @scroll=”onScroll” to the related processing logic 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
    });
}
Copy the code

In onScroll we can get the corresponding height of the scroll and calculate the final result of the transparency. Also notice that the opacity change is accompanied by a color change in the top status bar text. Use the side capability api.setStatusBarStyle to set it accordingly.

In this way, the business home page of the relevant logic of data processing, while introducing the basic event and data processing.

Product Details page (component communications, global data, and events)

When the page loads, get the product details data through the page pass parameter. The other added quantity of an item is stored in global DATA called cart-data, which is retrieved from the page lifecycle function APIReady:

this.data.goods = api.pageParam.item.togoods; // Let cartList = api.getPrefs({sync: true, key: 'cart-data '}); Parse (cartList) this.data.cartData = cartList[this.data.id]; if (cartList) {cartList = JSON. if (this.data.cartData) { this.data.count = this.data.cartData.count; }}Copy the code

Counter component goods_counter

The item details page uses two custom components, one is goods_counter, which is an item counter. Other pages may use it later, so encapsulate it.

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

Use a dynamic property :count=”count” to pass in the increment of the current item you just got. Inside Goods_counter, clicking the plus or minus button triggers the countChange event. Pass to the parent page in an event:

function countChange(change) { if (this.props.count + change === 0) { return api.toast({ msg: Fire ('CountChange', {change, props: this.props})}Copy the code

So when the component calls, bind an onCountChange={this.countchange.bind (this)}. The this.countChange function is the goods_detail function, which is passed as props to the child component when the component is created. The child component can either execute this function directly or “fire” the function.

Added action bar goods_action

The item details page uses two custom components, the other being goods_action, which is an item plus action bar. The main body is two buttons, one to add, one to settle.

Settlement is to carry the current item data to the advance payment page. The logic is simple: carry data to a new page.

Add is a little more complicated, but the logic still uses fire to throw an addCart event to the parent page, since the logic may vary from page to page, leaving the implementation to the parent. So let’s go back to the addCart implementation of goods_detail.

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: 'successfully added' + this.data.count + 'to cart ', location: 'middle' }) setTabBarBadge(2, Object.keys(cartList).length); }Copy the code

Add and take into account data from the relevant shopping cart page and the red dot at the bottom. At this point, if you do not consider the small program, you can also directly send a global broadcast and handle the relevant logic by yourself.

APICloud AVM Multiterminal development (ii) stay tuned!