基於盒馬鮮生改編的微信小程式

TeanLee發表於2019-03-04

前段時間,隨著馬化騰現身全國多地用微信小程式乘坐公交的新聞出現,微信小程式的熱度可謂是更上了一層。微信小程式現身至今,因其不用下載就可使用的方便等優點,發展趨勢一直良好。

盒馬鮮生的問世也是充滿了熱度,實現了快速配送,可謂是阿里巴巴對線下超市完全重構的新零售業態。

兩個都這麼方便的東西碰撞到一起,會發生什麼呢?

最近正好在學習微信小程式,於是照著盒馬鮮生APP改編了一個微信小程式。

文末有GitHub原始碼地址,之後我也會不斷更新完善這個小程式。如果你也對這個微信小程式感興趣,歡迎交流,共同學習。

功能簡介

本著便利的理念,實現了商城類APP的主要功能。

  • 使用者能夠根據點選不同的商品分類,跳轉頁面看到各種商品的列表。
  • 點選商品可以加入購物車,在購物車中還能實現對商品的增加或者減少數量、全選反選商品、刪除購物車商品等操作。
  • 首頁中,點選左上角的按鈕能夠新增預設收貨地址。
  • 點選右上角的掃一掃標誌,還能掃描二維碼(下面的gif介紹是模擬器的效果,只能開啟電腦中的圖片進行掃描,真機可以開啟相機進行掃描)。
  • 首頁中最頂上實現了圖片輪播及自動切換,底部實現了滾動檢視,能夠橫著滑動展示商品資訊。
    我們先來看看效果圖,稍後再仔細介紹每一點的實現方式。

頁面簡介

這裡主要介紹了該小程式的主要介面資訊、展示了幾個頁面跳轉及商品列表資訊。請關注首頁上方的圖片輪播及底部的橫向滾動檢視。

基於盒馬鮮生改編的微信小程式

關於購物車的操作

將商品加入購物車。

基於盒馬鮮生改編的微信小程式

對購物車中商品的數量進行增加或者減少。

基於盒馬鮮生改編的微信小程式

對購物車的商品進行全選或反選。

基於盒馬鮮生改編的微信小程式

刪除購物車內商品。

基於盒馬鮮生改編的微信小程式

增加預設收貨地址、二維碼掃一掃

新增收貨地址。

基於盒馬鮮生改編的微信小程式

二維碼掃一掃(這裡是模擬機測試的,只能新增本地圖片進行掃描,真機可以開啟相機掃一掃)

基於盒馬鮮生改編的微信小程式

接下來就是實現方式的介紹。

功能實現詳解

首頁圖片輪播和底部的滾動檢視

微信小程式自帶元件 滑塊檢視容器swiper,能夠實現滑塊檢視,每一個檢視都放在一個swiper-item中。設定引數auto:play就可以自動播放導致swiper變化。

<swiper class="page__bd__scroll" current=`{{activeIndex}}` bindchange=`swiperTab` autoplay="true" interval="2000" duration="1000">
  // interval是自動切換時間間隔,duration是滑動動畫時長
  // 每一個swiper-item就是一個檢視
  <swiper-item>
    <image class="page__scroll__item" src=""/>
  </swiper-item>
  <swiper-item>
    <image class="page__scroll__item" src=""/>
  </swiper-item>
  <swiper-item>
    <image class="page__scroll__item" src=""/>
  </swiper-item>
  <swiper-item>
    <image class="page__scroll__item" src=""/>
  </swiper-item>
  <swiper-item>
    <image class="page__scroll__item" src=""/>
  </swiper-item>
</swiper>
複製程式碼

橫向滾動檢視的實現:

微信自帶元件 可滾動檢視區域scroll-view,通過設定屬性名scroll-x或者scroll-y可以實現檢視的橫向滾動或者縱向滾動。

// 屬性名scroll-x定義了該檢視允許橫向滾動
<scroll-view scroll-x class="scrollx-section__content">
  // 利用迴圈從後臺獲取資料,在檢視中有多個view,也就是在頁面中能看到的多個商品展示。
  <block wx:for="{{scrollXList}}" wx:key="index" wx:for-index="index">
    <view class="scrollx-section__content__item">
      <view class="scrollx-section__item__wrapper">
        <view class="view__wrapper__image">
            <image src="https://user-gold-cdn.xitu.io/2017/12/15/16057cf8f3865280" />
        </view>
        <view class="view__wrapper__intro">
          <view class="wrapper__intro__title">
            <text>{{item.name}}</text>
            <text>{{item.secName}}</text>
          </view>
          <view class="wrapper__intro__content left">
            <text>{{item.leftTitle}}</text>
            <text>{{item.leftSecTitle}}</text>
          </view>
          <view class="wrapper__intro__content right">
              <text>{{item.rightTitle}}</text>
              <text>{{item.rightSecTitle}}</text>
          </view>
          <view class="wrapper__intro__price">
            <a>¥{{item.price}}</a><a>/{{item.unit}}</a><a id="{{index}}" bindtap="addInCart">+</a>
          </view>
        </view>
      </view>
    </view>
  </block>
</scroll-view>
複製程式碼

關於購物車的操作

將商品加入購物車

在不同的頁面根據分類資訊能夠跳轉到不同的頁面進入商品列表,比如能在首頁和分類頁,可以根據分類資訊進入不同的商品列表,將商品加入購物車。要想在首頁、商品分類介面、購物車多個介面都獲取到購物車列表資訊,單個頁面的資料作用域只在本資料夾,要想多個頁面共同操作同一個資料應該怎麼做呢?

對於在多個頁面進行傳值的資料,我們可以在app.js中設定一個全域性變數,再在每個頁面都引入這個全域性資料,就可以實現多個頁面共用一個資料了。

// app.js中的全域性變數
globalData: {
  cardList: [],
  goodsSortsChoice: null // 用來標記首頁商品分類  使用者點選了哪個分類,進而顯示不一樣的商品列表
}
複製程式碼

這裡我設定了兩個全域性變數。cardList是購物車資料,使用者點選商品加入購物車,就能將商品加入該陣列。goodsSortsChoice是一個標記。在首頁和分類介面都有不同的分類介紹,該標記能記住使用者在分類介面點選了哪一個分類,然後根據這個使用者的點選,跳轉至商品展示介面,展示不同的資訊。

在商品列表介面,為每一件商品的加入購物車選項都新增了一個點選事件addCount,同時,要判斷使用者點選了哪一件商品,就要為每一件商品加上一個index,這裡我的做法是在迴圈輸出後臺的商品列表資料時,為每個迴圈動態繫結data-index=”index”,再為每一個“+”設定一個id=”{{index}}”,進行點選判斷。

<block wx:for="{{goods}}" wx:key="index" wx:for-index="index">
    <view class="weui-cells">
        <view class="weui-cell">
            <view class="weui-cell__hd">
                <image src="https://user-gold-cdn.xitu.io/2017/12/15/16057cf8f3865280" />
            </view>
            <view class="weui-cell__bd">
                <view class="goodsList__bd__intro">{{item.name}}</view>
                <view class="view__bd__price">
                    <text class="price left">¥{{item.price}}/{{item.unit}}</text>
                    <text class="add right" bindtap="addInCart" id="{{index}}">+</text>
                </view>
            </view>
        </view>
    </view>
</block>
複製程式碼

js部分就是實現將商品加入購物車的方法addInCart。這裡將商品加入購物車前要先遍歷已有的購物車陣列進行判斷,如果商品已經在購物車中,就直接將購物車中的該商品數量加一,否則才直接將商品新增至全域性的購物車陣列。

addInCart: function(e) {
  const good = this.data.goods[e.currentTarget.id]; // 根據index,判斷使用者點選了哪個商品加入購物車
  const cart = app.globalData.cardList; // 獲取購物車列表
  // 設定一個標記,判斷使用者想加入購物車的商品是否已經存在購物車了
  var flag = false;
  // some 是es6新增的方法,用於遍歷整個陣列,如果陣列中存在一個及以上元素,就返回true
  flag = cart.some((item) => {
    return item === good;
  })
  console.log(flag);
  // 如果購物車中沒有該元素,就將該商品加入購物車,否則就將該商品的購買數量加一
  if(!flag) {
    cart.push(good); // 使用者選擇商品加入購物車後,將該商品加入購物車列表
    wx.showToast({
      title: `商品已加入購物車`,
      icon: `success`,
      duration: 2000
    })
  } else {
    // 商品已經存在購物車,直接將購買數量加一
    this.data.goods[e.currentTarget.id].count ++;
  }
},
複製程式碼

wx.showToast是微信自帶的API,能夠在頁面中出現一個彈窗。

增加或減少購物車中商品的購買數量

增減商品購買數量的思想是,給加減號分別繫結兩個點選事件 addCount 和 reduceCount,並且在迴圈輸出購物車列表的商品時,為加減號新增index索引,用於判斷使用者點選了哪一件商品。

<block wx:for="{{goodsList}}" wx:key="index" data-index="index">
    <view class="weui-cell">
        <view class="weui-cell__hd">
            <icon id="{{index}}" bindtap="selectGoods" type="{{item.type}}" color="#23a3ff"></icon>
        </view>
        <view class="weui-cell__bd">
            <image src="https://user-gold-cdn.xitu.io/2017/12/15/16057cf8f3865280" />
        </view>
        <view class="weui-cell__ft right">
            <text class="proIntr left">{{item.name}}</text>
            <text class="price left">¥{{item.price}}/{{item.unit}}</text>
            <view class="count">
                <text class="reduce left" bindtap="reduceCount" id="{{index}}">-</text>
                <text class="number left">{{item.count}}</text>
                <text class="add left" bindtap="addCount" id="{{index}}">+</text>
            </view>
        </view>
    </view>
</block>
複製程式碼

js部分:

// 增加商品數量
addCount:function (e) {
  var that = this;
  // 根據點選事件獲取使用者點選了哪一件商品
  const goodId = e.currentTarget.id;
  that.data.goodsList[goodId].count++;
  this.setData({
    goodsList: that.data.goodsList
  })
  // 每一次增減商品數量都要重新計算購物車總錢數
  this.sumMoney();
},
// 減少商品數量
reduceCount: function(e) {
  var that = this;
  const goodId = e.currentTarget.id;
  if(that.data.goodsList[goodId].count <= 1) {
    that.data.goodsList[goodId].count = 1;
    wx.showModal({
      title: `數量小於1`,
      content: `不允許操作`,
      duration: 2000
    })
  } else {
    that.data.goodsList[goodId].count--;
  }
  this.setData({
    goodsList: that.data.goodsList
  })
  // 每一次增減商品數量都要重新計算購物車總錢數
  this.sumMoney();
},
複製程式碼

購物車商品總價的計算

對於選中的商品,呼叫sumMoney()計算總價。該方法是遍歷購物車中的商品,獲得每件商品的單價和件數,進行相乘後相加。

// 計算所有商品的錢數
sumMoney: function() {
  // count用於記錄每件商品的購買數量
  var count = 0;
  // goods是購物車中的商品,對其進行遍歷,計算價格
  const goods = this.data.goodsList;
  for(let i = 0; i < goods.length; i++) {
    count += goods[i].count*goods[i].price;
  }
  this.setData({
    sum: count
  })
},
複製程式碼

商品的全選和反選

選中的商品和未選中的商品,在列表中展示時,最重要的一個差別是商品列前是藍色的小勾還是空心的圓點。

因此要先為購物車的商品設定一個狀態,對介面的樣式進行改變。對於這個狀態值,是在載入購物車介面前就要有該狀態,因此最先我想在後臺資料中為每個商品新增一個狀態值。但是這樣做有很大的不足之處,對於這個狀態值,只有購物車介面需要,對於其他介面來說是多餘的,給後臺多新增一個資料就意味著要更改所有後臺商品資料,增大了實現的複雜度。後面我又想到了一個方法。在購物車介面載入前,先遍歷一遍購物車,為每一件購物車新增一個屬性type=”success”(type引數設定的妙處:success 和 circle類名是微信元件icon的一個狀態值,能顯示小勾或空心圓點)。
購物車onload方法,遍歷購物車中的商品,新增狀態type:

onLoad: function (options) {
  const cardList = app.globalData.cardList;
  cardList.map(item => {
    item.type = "success";
  });
},
複製程式碼

前臺介面展示部分:

<icon id="{{index}}" bindtap="selectGoods" type="{{item.type}}" color="#23a3ff"></icon>
複製程式碼

通過這種方式,就實現了動態改變商品狀態的方法。
我們可以建立一個方法,遍歷購物車中的商品,如果全選了,就吧全選的選項勾上。

// 用來判斷是否全選
allSelected: function() {
  const goods = this.data.goodsList;
  // some是es6新增的方法,如果陣列中至少有一個符合條件的,就會返回true
  var symbol = goods.some(good => {
    return good.type === "circle"
  })
  // 經過symbol標記,如果購物車中有未選中的商品,全選狀態就是空心圓
  if(symbol) {
    this.data.allStatus = "circle"
  } else {
    // 如果購物車中所有商品都被選中了,全選狀態就是一個勾
    this.data.allStatus = "success"
  }
  this.setData({
    allStatus: this.data.allStatus
  })
},
複製程式碼

說回全選和反選操作。全選就是頁面底部總計一欄,打上了勾為全選,首先給全選框設定一個點選事件。若當前為全選狀態,點選後變成空心原點,反之亦然。

<view class="shopping__ft">
    <view class="shopping__ft__hd">
        <!-- 給全選按鈕一個點選事件selOrUnsel -->
        <icon bindtap="selOrUnsel" type="{{allStatus}}" color="#23a3ff"></icon>
        全選
    </view>
    <view class="shopping__ft__bd">
        <text>合計:</text>
        <text>¥{{sum}}</text>
    </view>
    <view class="shopping__ft__ft" bindtap="sumMoney">
        去結算
    </view>
</view>
複製程式碼

js實現:

selOrUnsel: function() {
  // 獲得全選按鈕和商品列表
  const status = this.data.allStatus;
  const goods = this.data.goodsList;
  // 點選按鈕後檢視當前全選框的狀態,對其進行取反的改變,並且對商品進行全選或反選
  if(status === "success") {
    // 如果全選按鈕之前是選中的,就變成空心圓
    this.data.allStatus = "circle";
    // 遍歷商品列表的每一項進行設定狀態屬性未未選中
    goods.map(good => {
      good.type = "circle";
    })
  } else {
    this.data.allStatus = "success";
    // 如果點選之前未選中全選按鈕,就進行全選。遍歷購物車列表改變所有商品的狀態值
    goods.map(good => {
      good.type = "success";
    })
  }
  // 將結果設定回頁面上進行顯示
  this.setData({
    goodsList: this.data.goodsList
  })
  this.setData({
    allStatus: this.data.allStatus
  })
},
複製程式碼

刪除購物車中的商品

購物車介面右上角有一個刪除,能夠刪除選定的商品。先給“刪除”新增一個點選事件delGoods。:

<view class="shopping__hd">
    <view class="shopping__hd__content">
        <view class="shopping__title">
            購物車
            <a class="shopping__title__delete right" bindtap="delGoods">刪除</a>
        </view>
    </view>
</view>
複製程式碼

js實現 delGoods 方法,用一個陣列存放已經選中的要刪除的商品,再遍歷要刪除的商品陣列,用 splice 方法逐個刪除。

// 刪除商品
delGoods: function() {
  const goods = this.data.goodsList;
  // 對購物車中所有的元素進行遍歷,找出選中的元素,組成selGoods陣列
  const selGoods = goods.map(good => {
    if(good.type === "success") {
      return good;
    }
  })
  wx.showModal({
    title: "確定要刪除所選商品?",
    success: (res) => {
      // 使用者點選確定
      if(res.confirm) {
        // 對要刪除的元素陣列進行遍歷,逐個用splice方法進行刪除
        selGoods.map(sel => {
          goods.splice(sel);
        })
        // 刪除成功以後從新設定頁面的值
        this.setData({
          goodsList: this.data.goodsList
        })
      } else if (res.cancel) {

      }
    }
  })
},
複製程式碼

新增預設收貨地址

在首頁的左上角有個小點,點選能夠新增預設的收貨地址。

新增預設收貨地址也需要在多個頁面進行傳值,因為顯示預設地址和設定預設地址不在同一個介面。考慮到預設收貨地址需要長期儲存在使用者的個人資訊中,這次我們用到了 storage 進行資料的儲存。

話不多說,要實現功能,繫結一個事件 chooseAddr 先。

<!-- 我現在是首頁 -->
<view class="page__hd__input-left left">
  <image src="https://user-gold-cdn.xitu.io/2017/12/15/16057cf8f81b6d70" bindtap="chooseAddr" />
</view>
複製程式碼

將頁面跳轉到預設收貨地址展示介面,這裡能顯示輸入的收貨地址

chooseAddr: function() {
  wx.navigateTo({
    url: "../chooseAddress/chooseAddress"
  })
},
複製程式碼

在顯示預設收貨地址的介面,右上角還有一個按鈕,可以新增收貨地址,那我們再跳轉一遍頁面到設定預設收貨地址介面吧。

<!-- 我是顯示預設收貨地址的介面 -->
<view class="choose-addr__hd">
    <text class="choose-addr__title">選擇收貨地址</text>
    <text class="choose-addr__add right" bindtap="addNewAddr">新增地址</text>
</view>
複製程式碼
// 我要跳轉到設定預設收貨地址介面啦
addNewAddr: function() {
  wx.navigateTo({
    url: "../newAddr/newAddr"
  })
},
複製程式碼

在設定預設收貨地址介面,給每個輸入框分別設定輸入改變事件bindinput,用於獲得輸入框的內容。

<view class="newAddr__bd">
    <view class="weui-cells weui-cells_form gray-input">
        <view class="weui-cell">
            <view class="weui-cell__hd">
                <text class="weui-label mr60">收貨地址</text>
            </view>
            <view class="weui-cell__bd">
                <input bindinput="getAddress" class="weui-input" placeholder="請輸入收貨地址" />
            </view>
        </view>
        <view class="weui-cell">
            <view class="weui-cell__hd">
                <text class="weui-label mr94">門牌號</text>
            </view>
            <view class="weui-cell__bd">
                <input bindinput="getNum" class="weui-input" placeholder="門牌號" />
            </view>
        </view>
    </view>
    <view class="weui-cells weui-cells_form second-weui-cells">
        <view class="weui-cell">
            <view class="weui-cell__hd">
                <text class="weui-label mr48">聯絡人</text>
            </view>
            <view class="weui-cell__bd">
                <input bindinput="getName" class="weui-input" placeholder="聯絡人" />
            </view>
        </view>
        <view class="weui-cell">
            <view class="weui-cell__hd">
                <label class="weui-label mr44">手機號</label>
            </view>
            <view class="weui-cell__bd">
                <input bindinput="getPhone" class="weui-input" placeholder="請輸入手機號" />
            </view>
        </view>
    </view>
</view>
複製程式碼

輸入改變事件獲得輸入框的內容:

data: {
  address: ``,
  num: ``,
  name: ``,
  phone: ``
},

getAddress: function(e) {
  this.setData({
    address: e.detail.value
  })
},
getNum: function(e) {
  this.setData({
    num: e.detail.value
  })
},
getName: function(e) {
  this.setData({
    name: e.detail.value
  })
},
getPhone: function(e) {
  this.setData({
    phone: e.detail.value
  })
},
複製程式碼

噹噹噹!說了這麼多,storage 儲存資料的重點來啦!

wxml 介面,設定一個點選事件 saveInfo ,點選後進行資料的儲存:

<view class="newAddr__hd">
    <text class="newAddr__add left" bindtap="backToChooseAddr">返回</text>
    <text class="newAddr__title">選擇收貨地址</text>
    <text bindtap="saveInfo" class="newAddr__add right">儲存</text>
</view>
複製程式碼

微信提供了一個API:wx.setStorage 能按照鍵(key)值(data)對的方式在快取中存資料。

key 是 本地快取中的指定的 key,data 是需要儲存的內容,success 是介面呼叫成功的回撥函式。

// 使用者點選儲存後,對輸入的資料進行儲存,並反饋儲存狀態
saveInfo: function() {
  wx.setStorage({
    key: "name",
    data: [{address:this.data.address}, {num: this.data.num}, {name: this.data.name}, {phone: this.data.phone}],
    success: function() {
      // 資料設定成功後,彈框提示使用者資訊儲存完整,並跳回展示預設地址的介面
      wx.showToast({
        title: "地址儲存成功",
        icon: `success`,
        duration: 2000
      })
      setTimeout(function(){
        wx.navigateTo({
          url: "../chooseAddress/chooseAddress"
        })
      },1000);
      
    }
  })
複製程式碼

資料設定成功後,跳回展示預設地址的介面。在這裡,我們要獲取快取中的資料。

微信提供了一個API:wx.getStorage 能從本地快取中非同步獲取指定 key 對應的內容。key 是本地快取中的指定的 key,success 是介面呼叫的回撥函式。

onShow: function () {
  var that = this;
  wx.getStorage({
    key: "name",
    success: function(res) {
      if(res.data.length > 0) {
        that.setData({
          address: res.data[0].address,
          num: res.data[1].num,
          name: res.data[2].name,
          phone: res.data[3].phone
        })
      }
    }
  })
},
複製程式碼

掃描二維碼

在首頁的右上角,有一個掃一掃的圖片,點選後能掃描二維碼。

給這張圖片繫結一個點選事件scan:

<view class="page__hd__input-right left">
  <image bindtap="scan" src="https://user-gold-cdn.xitu.io/2017/12/15/16057cf8f3b9287a" />
</view>
複製程式碼

呼叫微信自帶的 API:wx.scanCode 就能掃描二維碼啦。

scan: function() {
  wx.scanCode({
    success: (res) => {
      console.log(res)
    }
  })
},
複製程式碼

後臺資料

關於後臺資料的來源,我使用的是用 easymock 構建模擬資料。easymock 對於暫時只關注前臺介面的程式設計師來說是真的超級好用。之前寫過一篇文章使用Easy Mock構建模擬資料歡迎移步檢視。

寫在最後

寫微信小程式最重要的可能就是檢視文件了。微信給開發人員提供了很詳細的各種元件和API的介紹和使用方法。

附上微信小程式開發文件

最後奉上GitHub原始碼地址:https://github.com/TeanLee/hema

如果覺得還不錯的話,請給個start鼓勵一下~

本小程式我也會不斷更新,歡迎批評、指導、交流:

相關文章