APICloud AVM多端開發 |《生鮮電商app開發》項目源碼深度解析

此項目爲生鮮電商app開發類應用,主要功能包括商品列表、商品詳情展現、購物車、登陸註冊、我的中心等。 項目源碼在 https://github.com/apicloudcom/avm-simple 倉庫下」多端案例-生鮮電商「目錄下。前端

項目中前端採用APICloud AVM多端開發技術進行開發,要點包括 scroll-view 滾動視圖、下拉刷新、輸入處理、swiper 輪播圖、網絡請求封裝等。使用 APICloud 多端技術進行開發,實現一套代碼多端運行,支持編譯成 Android & iOS App 以及微信小程序。git

項目後端則是使用的 APICloud 數據雲 3.0 自定義雲函數來構建的。github

使用步驟

  1. 使用 APICloud Studio 3 做爲開發工具。
  2. 下載本項目《生鮮電商app開發》源碼。
  3. 在開發工具中新建項目,並將本源碼導入新建的項目中,注意更新config.xml 中的 appid 爲你項目的 appid。
  4. 使用 AppLoader 進行真機同步調試預覽。
  5. 或者提交項目源碼,併爲當前項目雲編譯自定義 Loader 進行真機同步調試預覽。
  6. 雲編譯 生成 Android & iOS App以及微信小程序源碼包。

若是以前未接觸過 APICloud 開發,建議先了解一個簡單項目的初始化、預覽、調試和打包等操做,請參考APICloud 多端開發快速上手教程ajax

網絡請求接口封裝

在 script/kn.js 中,封裝了統一的網絡請求接口 ajax 方法,對整個項目的請求進行統一管理,包括處理傳入參數、拼裝完整請求 url、設置請求頭等,最後調用 api.ajax 方法發起請求,在請求的回調方法裏面還對 cookie 是否過時作了全局判斷,過時後會清除本地用戶登陸信息,並提示從新登陸。json

// 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 指令來實現的。api

// 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 端,長列表建議使用 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開發案例,戳籃子回顧哦!

相關文章
相關標籤/搜索