微信小程序——仿盒馬鮮生APP

前段時間,隨着馬化騰現身全國多地用微信小程序乘坐公交的新聞出現,微信小程序的熱度可謂是更上了一層。微信小程序現身至今,因其不用下載就可以使用的方便等優勢,發展趨勢一直良好。
盒馬鮮生的問世也是充滿了熱度,實現了快速配送,可謂是阿里巴巴對線下超市徹底重構的新零售業態。
兩個都這麼方便的東西碰撞到一塊兒,會發生什麼呢?
最近正好在學習微信小程序,因而仿照着盒馬鮮生APP寫了一個微信小程序。
文末有GitHub源碼地址,以後我也會不斷更新完善這個小程序。若是你也對這個微信小程序感興趣,歡迎交流,共同窗習。git

功能簡介

本着便利的理念,實現了商城類APP的主要功能。程序員

  • 用戶可以根據點擊不一樣的商品分類,跳轉頁面看到各類商品的列表。
  • 點擊商品能夠加入購物車,在購物車中還能實現對商品的增長或者減小數量、全選反選商品、刪除購物車商品等操做。
  • 首頁中,點擊左上角的按鈕可以添加默認收貨地址。
  • 點擊右上角的掃一掃標誌,還能掃描二維碼(下面的gif介紹是模擬器的效果,只能打開電腦中的圖片進行掃描,真機能夠打開相機進行掃描)。
  • 首頁中最頂上實現了圖片輪播及自動切換,底部實現了滾動視圖,可以橫着滑動展現商品信息。

咱們先來看看效果圖,稍後再仔細介紹每一點的實現方式。es6

頁面簡介

這裏主要介紹了該小程序的主要界面信息、展現了幾個頁面跳轉及商品列表信息。請關注首頁上方的圖片輪播及底部的橫向滾動視圖。
圖片描述github

關於購物車的操做

將商品加入購物車。
圖片描述小程序

對購物車中商品的數量進行增長或者減小。
圖片描述segmentfault

對購物車的商品進行全選或反選。
圖片描述微信小程序

刪除購物車內商品。
圖片描述數組

增長默認收貨地址、二維碼掃一掃

新增收貨地址。
圖片描述緩存

二維碼掃一掃(這裏是模擬機測試的,只能添加本地圖片進行掃描,真機能夠打開相機掃一掃)
圖片描述微信



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

功能實現詳解

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

微信小程序自帶組件 滑塊視圖容器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="{{item.image}}" />
        </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="{{item.image}}" />
            </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="{{item.image}}" />
        </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="../../assets/icons/position.png" 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="../../assets/icons/scan.png" />
</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鼓勵一下喲~本小程序我也會不斷更新,歡迎批評、指導、交流:WeChat:ltt598625763Email:ltt598625763@qq.com

相關文章
相關標籤/搜索