Last week, we shared with APICloud AVM multi-terminal development technology to develop a “takeaway app development” project source analysis of the first part, now the next part of the hope to help developers quickly experience a set of code to compile Android and iOS APP + small program.

This paper mainly explains the bidirectional scrolling interaction of the menu and order page, the processing of food purchase, the processing of shopping cart and payment logic, and the development of user center. While viewing this article, you can review the previous one:

APICloud AVM Multi-terminal development case in-depth analysis (part 1)– food ordering and delivery app development

——-

A menu order page

Two-way scrolling interaction between categories and dishes

The page is a left-right column layout. Menu categories on the left, dishes on the right. There is a common set of interactions:

  1. Slide the menu on the right and the category highlight on the left will change with it.
  2. Click the classification of dishes on the left and roll back the dishes on the right to the corresponding area.

The first of these interactive correlation logic is similar to the logic that triggers header transparency in the scrolling Scroll View of the developing merchant home page. So again, bind the right hand side of the scroll view to the at sign scroll equals onScroll function.

Specific logic please refer to the implementation of the source code part, get rolling height and similar to the home page.

Focus on the core of the second interaction is to click the corresponding category and scroll to the specified position in the right scroll view. Use attributes to bind positions: scrolltop ={scrollTo}. In this case, just click on the left category event @click=”switchCategory(index)” to calculate the correct scrollTo.

function switchCategory(index) { this.data.categoryIndex = index; this.data.CD = new Date().getTime() + 500; This.data.scrollto = this.offsetList[index]; // This.data.scrollTo = this.offsetList[index]; }Copy the code

Dishes and purchase processing (cross-end feature processing)

The item on the right has an @click=”openAdd(Goods)” event that opens the add page.

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} }) } }Copy the code

This function shows the handling of end differences. Because APICloud doesn’t have the concept of a frame, the new pop-up page is implemented by an internal page component on the applet.

Of course, this approach is also supported by the APP native. If you need to further improve performance to take advantage of native, you can use frame on the native side. At this point, encapsulate the target page in a custom component and pass the current menu data into it.

Component and frame pages currently have different entries. In the INSTALLED lifecycle of the goods_add component, you can see the following compatible fragments:

this.data.goods = this.props.goods ? this.props.goods : api.pageParam.goods;
Copy the code

On the newly expanded add float, you see the goods_action defined earlier, so the general logic is also to get the item data and add number, and implement the addCart function. In fact, this page is similar to the product details page, except that the display UI is different.

Immersive status bar Safe-area

In this page, you implement a top navigation bar. Immersive status bars typically require processing capabilities such as the height of the status bar. A safe-area component is provided in avm.js to automatically handle the boundary problems of special-shaped screens.

<safe-area> <view class="header"> <text class="title"> Menu </text> </view> </safe-area>Copy the code

On the home page, you can also see the code for programmatically retrieving security zone data:

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

Two shopping cart pages for computed and V-if conditional rendering

The shopping cart page is a classic example of showing the internal logic of a related page.

When the page is initialized, this.getcartData () gets all the data stored locally in the cart.

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); }}Copy the code

There is also a generateCartList logic mixed in.

function generateCartList() { let cartData = this.data.cartData; let arr = []; for (let i in cartData) { arr.push({checked: true, ... cartData[i]}); } this.data.cartList = arr; }Copy the code

This is a generator function that builds the saved objects into the array structure needed for the page, adding the Checked attribute to each element. Then the page section circulates the current shopping cart data through 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>Copy the code

Notice that a
custom component is nested at the beginning of each entry. The simple task of this component is to render a checkbox with a custom style. Of course, radio, the system component of AVm. Js, can also be realized.

The use of computed

There is an all button below, used to control whether all.

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

This. allChecked from the first line of this function is a calculated property. Its implementation can be found in computed:

function allChecked() { return ! This.cartlist.some ((item) => {// You can also use every to change the reverse logic to return! item.checked; })}Copy the code

It is followed by another calculated property: totalPrice:

Function totalPrice () {/ / first out let selected item 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); }Copy the code

Then use this result directly in the template to complete the display of the total price:

<view class="text-group"> <text class="main-cart-footer-text"> </text> <text class="main-cart-footer-price">¥{{ totalPrice }}</text> </view>Copy the code

As you can see, computed attributes can be computed with some logic, exposed to the instance itself, and can be bound in templates as well as data. At the same time, it can automatically deal with the data changes and make real-time updates.

V-if conditional rendering

In the page, there is a variable marker, isEdit, that indicates whether the page is currently being edited.

< view@click ="toggleEdit"> <text class=" main-cart-finn-text "V-if ="isEdit"> Complete </text> <view V-else class="main-cart-action"> <img class="main-cart-action-icon" src=".. /.. PNG "Alt =""/> <text class="main-cart-action-text"> edit </text> </view> </view>Copy the code

According to the change of editing status, the button text in the upper right corner changes into two states: “Done” and “Edit”. At this point the render can be judged by v-if. The same is true for the settle and remove buttons below, but they are displayed using a ternary expression in the template.

<text class="main-cart-footer-btn-text">{{ isEdit ? }}</text>Copy the code

Three User Page

This page has two main points: the header user information area and the order list.

Header user information

The user information in the header needs to read the local user data at initialization.

Get user information / * * * * @ returns {Boolean | any} * / function getUser () {let user = API. GetPrefs ({sync: true, key: 'user'}); if (user) { return JSON.parse(user) } return false; }Copy the code

The user data obtained is used as a common page data to render the user information panel. If the user data does not exist, i.e., not logged in mode, v-if conditional rendering is used to display the login interface.

<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=".. /.. PNG "Alt =""/> <text class="user-name"> </text> </view>Copy the code

The login logic

In the case you are not logged in, the second block above shows that clicking triggers the wxLogin method:

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

There is still a need for feature platform differentiation. Because the native end and the small program end using wechat login are two different logic. The source code /widget/pages/main_user/main_user.stml also shows some logic for using native modules to call wechat to log in.

After the login is successful, the loginSuccess command is executed to save user information and session information for future use. You also need to refresh the user’s shopping list. If other pages that are already open in a real project also need to monitor user status changes, you can handle the detailed logic with broadcast events.

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

    this.data.userInfo = userInfo;
    this.getOrderList();
}
Copy the code

Drop-down refresh of the page

Page drop-down refresh and bottom loading depend on the binding and implementation of related events of 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=" no order" type="order"></empty-block> </ scrollview >Copy the code

Where @refresherRefresh =”onRefresh” is the logic that needs to be triggered for pull-down refresh. Refresher-triggered ={{loading}} is triggered by pull-down. (used to notify springback and Settings refresh).

function onRefresh() { this.data.loading = true; If (this.data.userinfo) {this.getorderList (); } else { setTimeout(_ => { this.data.loading = false; API. Toast ({MSG: 'please login to view the historical order'})}, 1000)}}Copy the code

The development of the home page is almost complete, now pay attention to the process of ordering payment.

4 Page to be paid (Form data)

The page is also relatively simple, and most of the implementation logic was mentioned in the previous page. There is also an input box form to collect input remarks from the user.

<view class="order-note"> <text class="order-note-key"> </text> <input class="order-note-input" placeholder=" onBlur="onBlur" maxlength="30" id="remark"/> </view>Copy the code

Get data dynamically by losing focus event onBlur=”onBlur”.

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

There are many other ways to get data, and you can further refer to the component input and other form component documents.

Start placing orders, communicate with the server to place and pay. After placing an order, do some linkage processing:

Function addOrder() {POST(' Orders/app_addOrder ', this.formData). Then (data => {// open the result page api.openwin ({name: 'pay_result', url: '.. /pay_result/pay_result.stml' }); SendEvent ({name: 'pay-success'}) // Empty shopping CART api.setPrefs({key: 'cart-data ', value: {}}); setTabBarBadge(2, 0); })}Copy the code

Jump to the payment success page

Jump to the payment result page after placing order and payment. (This process simulates successful placing of orders, and the third party payment can be nested by referring to the wechat login process)

——-

At this point, the logic mainline of all delivery APP development pages has been completed. There are some details in the application, you can refer to the source code and documentation for further study. In addition, you can check out the APICloud AVM multi-terminal development quick start tutorial to master the AVM development skills.