乐趣区

关于javascript:APICloud-AVM多端开发-生鲜电商app开发项目源码深度解析

此我的项目为生鲜电商 app 开发类利用,次要性能包含商品列表、商品详情展现、购物车、登录注册、集体核心等。
我的项目源码在 https://github.com/apicloudco… 仓库下”多端案例 - 生鲜电商“目录下。

我的项目中前端采纳 APICloud AVM 多端开发技术进行开发,要点包含 scroll-view 滚动视图、下拉刷新、输出解决、swiper 轮播图、网络申请封装等。应用 APICloud 多端技术进行开发,实现一套代码多端运行,反对编译成 Android & iOS App 以及微信小程序。

我的项目后端则是应用的 APICloud 数据云 3.0 自定义云函数来构建的。

应用步骤

  1. 应用 APICloud Studio 3 作为开发工具。
  2. 下载本我的项目《生鲜电商 app 开发》源码。
  3. 在开发工具中新建我的项目,并将本源码导入新建的我的项目中,留神更新 config.xml 中的 appid 为你我的项目的 appid。
  4. 应用 AppLoader 进行真机同步调试预览。
  5. 或者提交我的项目源码,并为以后我的项目云编译自定义 Loader 进行真机同步调试预览。
  6. 云编译 生成 Android & iOS App 以及微信小程序源码包。

如果之前未接触过 APICloud 开发,倡议先理解一个简略我的项目的初始化、预览、调试和打包等操作,请参考 APICloud 多端开发疾速上手教程。

网络申请接口封装

在 script/kn.js 中,封装了对立的网络申请接口 ajax 办法,对整个我的项目的申请进行对立治理,包含解决传入参数、拼装残缺申请 url、设置申请头等,最初调用 api.ajax 办法发动申请,在申请的回调办法外面还对 cookie 是否过期做了全局判断,过期后会革除本地用户登录信息,并提醒从新登录。

`// kn.js
u.ajax = function(p, callback) {
   var param = p;
   if (!param.headers) {param.headers = {};
   }
   param.headers['x-apicloud-mcm-key'] = 'cZKzX7DabDmYyfez';
   if (param.data && param.data.body) {param.headers['Content-Type'] = 'application/json; charset=utf-8';
   }
   if (param.url) {
       var baseUrl = 'https://a8888888888888-pd.apicloud-saas.com/api/';
       param.url = baseUrl + param.url;
   }
   api.ajax(param, function(ret, err) {if (callback) callback(ret, err);
       if (ret) {
           var status =  ret.status;
           if (status && status == 4001) {
               var didShowLogoutAlert = api.getGlobalData({key: 'didShowLogoutAlert'});
               if (!didShowLogoutAlert) {
                   api.setGlobalData({
                       key: 'didShowLogoutAlert',
                       value: true
                   });

                   u.setUserInfo('');
                   api.alert({msg: '登录已生效,请从新登录'}, function() {
                       api.setGlobalData({
                           key: 'didShowLogoutAlert',
                           value: false
                       });
                       api.closeToWin({name: 'root'});
                   });
               }
           }
       }
   });
}` 

应用示例:

`// 在 stml 页面通过 import 引入
import $kn from "../../script/kn.js"

// 调用 ajax 办法
// main.stml,从服务器端更新商品分类列表
fnGetWareTypeList() {
  var that = this;
  $kn.ajax({
      url: 'wareTypes/getWareTypeList',
      cache: true
  }, function(ret, err) {if (ret && ret.data) {
          that.data.wareTypeList = ret.data;
          that.updatedWareList();} else {
          api.toast({
              msg: '获取商品分类失败',
              duration: 2000,
              location: 'bottom'
          });
      }
  });
}` 

自定义导航栏封装(APICloud 组件的应用)

在 components/navigationBar.stml 页面中,咱们封装了一个通用的导航栏组件,其中 safe-area 组件能保障外面的内容始终显示在屏幕平安区域内,例如不被状态栏遮挡。在组件页面中,能够通过 this.props 拜访父页面传入的属性。

`<template>
    <safe-area class="nav-container">
        <view class="nav-header">
            <view class="nav-header-button nav-left-button" onclick={this.props.onLeftButton ? this.props.onLeftButton : this.onLeftButton}>
                <image width={this.props.leftButtonWidth ? this.props.leftButtonWidth : 11} src={this.props.leftButtonIcon ? this.props.leftButtonIcon : '../../image/back.png'} mode="widthFix"></image>
                <text class="nav-header-text">{this.props.leftButtonText}</text>
            </view>
            <text class="nav-header-title">{this.props.title}</text>
            <view class="nav-header-button nav-right-button" onclick={this.props.onRightButton}>
                <image width={this.props.rightButtonWidth ? this.props.rightButtonWidth : 0} src={this.props.rightButtonIcon ? this.props.rightButtonIcon : ''} mode="widthFix"></image>
                <text class="nav-header-text">{this.props.rightButtonText}</text>
            </view>
        </view>
    </safe-area>
</template>` 

在其它页面应用该组件时,能够设置导航栏题目,以及左右两边按钮的文字、图片、点击事件等。

`// 引入 navigationBar 组件
import navigationBar from "../../components/navigationBar.stml"

// 在其它 stml 页面应用
// login.stml
<navigationBar title="会员登录" rightButtonText="注册" onRightButton={this.onRightButton}></navigationBar>` 

竖向滚动列表

生鲜电商 app 开发我的项目中多处用到了列表展现,比方首页的商品列表、城市抉择页面、购物车列表等,我的项目中的列表都应用 scroll-view 配合 v-for 指令来实现的。

`// cityselector.stml
<scroll-view class="cityselector-section" scroll-y='true'>
  <view v-for="(item, index) in dataList">
      <text class="cityselector-city" data-item={item} onclick={this.fnSelectCity}>{item.name}</text>
  </view>
</scroll-view>` 

如果生鲜电商 app 开发我的项目只须要反对 App 端,长列表倡议应用 list-view 实现,相比于 scroll-view 一下把所有项全副创立进去,list-view 只会创立出可见区域的那几项,并且基本上整个滚动过程中就只有那几项,滚动过程中会对项进行回收重用,性能相比 scroll-view 有十分大的晋升。

横向滚动列表

在首页商品分类、商品详情页举荐商品都应用了横向滚动视图,scroll-x 属性为 true 的时候 scroll-view 的滚动方向为横向,同时布局方向也变成了横向,即 flex-direction 变成了 row。

`// index.stml
<scroll-view class="nav" scroll-x show-scrollbar="false">
   <text class={'nav-menu'+(this.data.currentIndex==index?'nav-menu-selected':'')} v-for="(item,index) in wareTypeList" data-index={index} onclick={this.fnSetNavMenuIndex}>{item.name}</text>
</scroll-view>` 

首页下拉刷新、滚动到底部加载更多

在首页(pages/main/main.stml),通过 scroll-view 实现了商品列表展现,同时实现了下拉刷新、滚动到底部加载更多功能。

`<scroll-view scroll-y='true' class="warelist" enable-back-to-top refresher-enabled refresher-triggered={this.data.refresherTriggered} onrefresherrefresh={this.onrefresherrefresh} onscrolltolower={this.onscrolltolower}>
   <view class="header">
       <image class="banner" src={this.data.bannerUrl} placeholder="../../image/default_rect.png" thumbnail="false" mode="widthFix">
   </view>
   <view>
       <view class="cell" v-for="(item, index) in dataList">
           <view data-id={item.id} data-wareCount={item.wareCount} class="container" onclick={this.fnOpenDetailWin}>
               <image class="thumbnail" src={item.thumbnail} placeholder="../../image/default_square.png"></image>
               <view class="info">
                   <text class="info-name">{item.name + '' + (item.unit||'')}</text>
                   <text class="info-description">{item.description}</text>
                   <text class="info-price">{'¥'+item.price}</text>
                   <text class="info-origin-price">{'¥'+item.originPrice}</text>
               </view>
               <view class="control">
                   <image class={item.wareCount>0?'minus':'none'} data-index={index} src="../../image/minus.png" onclick={this.fnMinus}>
                   <text class={item.wareCount>0?'count':'none'}>{item.wareCount}</text>
                   <image class="add" data-index={index} src="../../image/add.png" onclick={this.fnAdd}>
               </view>
           </view>
       </view>
   </view>
   <view class="footer">
       <text class="loadDesc">{this.data.haveMoreData?'加载中...':'没有啦!'}</text>
   </view>
</scroll-view>` 

下拉刷新应用了 scroll-view 默认的下拉刷新款式,应用 refresher-enabled 字段来开启下拉刷新,为 refresher-triggered 字段绑定了 refresherTriggered 属性来管制下拉刷新状态,须要留神的是,在刷新的事件回调办法外面,咱们须要被动设置 refresherTriggered 的值为 true,在数据加载实现后再设置为 false,这样绑定的值有变动,刷新状态能力告诉到原生外面。

`onrefresherrefresh(){
    this.data.refresherTriggered = true;
    this.fnGetWareList(false);
}` 

滚动到底部监听了 scroll-view 的 scrolltolower 事件,在滚动到底部后主动加载更多数据,加载更多和下拉刷新都是调用 fnGetWareList 办法申请数据,通过 APICloud loadMore_ 参数来进行辨别,做分页申请解决。

`fnGetWareList(loadMore_) {
  // 如果是加载更多,须要实现分页
  var limit = 20;
  if (loadMore_) {this.data.skip += limit;} else {this.data.skip = 0;}

  var currentCity = $kn.getCurrentCityInfo();
  var that = this;
  // 依据城市和商品分类取得相应的商品列表
  $kn.ajax({
      url: 'wares/getWareList',
      method: 'post',
      data: {
          body: {
              supportAreaId: currentCity?currentCity.id:'',
              wareTypeId: this.data.wareTypeId,
              skip: this.data.skip,
              limit: limit
          }
      }
  }, function(ret, err) {if (ret && ret.data) {var cartData = api.getGlobalData({key: 'cartInfo'});
          var data = ret.data;
          that.data.haveMoreData = data.length == limit;
          that.getFixedWareList(data, cartData?cartData.wareList:null);
          if (loadMore_) {that.data.dataList = that.data.dataList.concat(data);
          } else {that.data.dataList = data;}
      } else {
          api.toast({
              msg: '加载数据失败',
              duration: 2000,
              location: 'bottom'
          });
      }
      that.data.refreshState = 'normal';
      that.data.refresherTriggered = false;
  });
}` 

商品详情页轮播图

生鲜电商 app 开发商品详情页门路为 pages/ware/ware.stml,外面轮播图应用 swiper 组件实现,应用 v-for 指令循环 swiper-item,picList 为定义的数组类型的属性。

`<swiper class="swiper shrink" style={'height:'+this.data.swiperHeight+'px;'} indicator-dots indicator-active-color="#e3007f" autoplay circular>
    <swiper-item v-for="(_item,index) in picList">
        <image class="img" placeholder="../../image/default_square.png" src={_item} mode="aspectFit" thumbnail="false"></image>
    </swiper-item>
</swiper>` 

轮播图的宽度追随屏幕宽度变动,高度则通过属性 swiperHeight 来设置,以放弃图片宽高显示比例不变。

登录、注册页面输出解决

登录、注册页面构造类似,从上面的代码能够看到登录页面由两局部组成,顶部为导航栏,剩下的主体内容里面加了一个 scroll-view,加 scroll-view 的目标,一方面是为了保障在小屏幕手机下面显示不齐全时可能滚动查看内容,另一个目标则是为了让输入框获取焦点弹出键盘后,只让 scroll-view 内主体局部往上挪动,而导航栏局部放弃不动。

`// login.stml
<view class="main"> 
   <navigationBar title="会员登录" rightButtonText="注册" onRightButton={this.onRightButton}></navigationBar>
   <scroll-view class="scrollView" scroll-y='true'>
       <view class="container">
           <input id="username" class="input" placeholder="用户名"/>
           <input id="password" class="input" type="password" placeholder="明码"/>
           <text class="btn" onclick={this.fnLogin}> 登录 </text>
           <view class="third-login">
               <text class="third-login-desc"> 第三方登录 </text>
               <view class="icon-container">
                   <image class="icon" data-type="Apple" src="../../image/logo_apple.png" onclick={this.fnThirdLogin}/>
                   <image class="icon" data-type="微信" src="../../image/logo_wx.png" onclick={this.fnThirdLogin}/>
               </view>
           </view>
       </view>
   </scroll-view>
</view>` 

集体核心上传头像

在集体核心页面(pages/personalcenter/personalcenter.stml),咱们应用了 img 标签来展现用户头像,当用户点击头像后能够批改头像。这里依据用户抉择的抉择图片形式,咱们会先判断是否有对应的权限,如果有权限则调用 api.getPicture 办法选取图片,如果没有权限,则提醒用户去设置权限。

`fnSetAvatar() {
  var that = this;
  api.actionSheet({
      title: '抉择图片',
      cancelTitle: '勾销',
      buttons: ['拍照', '相册']
  }, function(ret, err) {if (ret) {var sourceTypes = ['camera', 'album'];
          if (ret.buttonIndex == (sourceTypes.length + 1)) {return;}
          var sourceType = sourceTypes[ret.buttonIndex - 1];
          var permission = ret.buttonIndex == 1?'camera':'photos';
          var resultList = api.hasPermission({list: [permission]
          });
          if (resultList[0].granted) {that.getPicture(sourceType);
          } else {
              api.confirm({msg: '利用须要您的受权能力拜访' + (permission=='camera'?'相机':'相册'),
                  buttons: ['勾销', '去设置']
              }, function(ret1) {if (ret1.buttonIndex == 2) {
                      api.requestPermission({list: [permission],
                      }, function(ret2) {if (ret2.list[0].granted) {that.getPicture(sourceType);
                          }
                      });
                  }
              });
          }
      }
  });
}` 

选取本地图片胜利后调用封装好的 ajax 办法上传抉择的图片。

`fnUpdateAtavar(avatarUrl_) {
  var that = this;
  $kn.ajax({
      url: 'users/updateAvatar',
      method: 'post',
      data: {
          values: {filename: 'icon.jpg'},
          files: {file: avatarUrl_}
      }
  }, function(ret, err) {if (ret && ret.data) { } else {
          api.toast({msg:'头像批改失败'});
      }
  });
}` 

为了让开发者更加疾速的学习和应用 APICloud 平台 AVM 多端开发技术,咱们会持续更新实战我的项目源码解说视频。本期生鲜电商 app 开发案例就到这里,还有企业 app 开发案例、外卖 app 开发案例,戳篮子回顾哦!

退出移动版